Hone logo
Hone
Problems

Jest Mutation Fuzzing: Testing Resilience to Code Changes

Mutation fuzzing is a powerful testing technique that introduces small, deliberate errors (mutations) into your code and then runs your tests to see if they catch these errors. This challenge asks you to implement a basic mutation fuzzing system within a Jest testing environment. The goal is to increase confidence in your test suite's ability to detect subtle code regressions.

Problem Description

You need to create a Jest plugin that automatically mutates your TypeScript code before running tests. The plugin should:

  1. Identify Target Code: Locate functions within your TypeScript files that are being tested. Assume these functions are exported and called within your test files.
  2. Introduce Mutations: For each identified function, introduce a single, simple mutation. The initial mutation to implement is replacing a numerical literal (e.g., 1, 2, 10) with another numerical literal. The replacement number should be randomly generated within a reasonable range (e.g., 1-100).
  3. Run Tests: After introducing the mutation, run your Jest test suite.
  4. Report Results: If any tests fail after a mutation, report the mutation, the function it was applied to, and the failing tests. If all tests pass, the mutation is considered "killed" (detected by the tests).
  5. Restore Original Code: After running tests, revert the code to its original state.

Key Requirements:

  • The plugin should be compatible with Jest's existing configuration.
  • The mutation process should be automated and require minimal user intervention.
  • The plugin should provide clear and informative output about the mutations performed and their results.
  • The plugin should not significantly slow down the test execution time.

Expected Behavior:

  • When the plugin is enabled, it should automatically mutate the code before each test run.
  • The plugin should report which functions were mutated and the nature of the mutation.
  • The plugin should indicate whether each mutation was "killed" (detected by failing tests) or "survived" (tests passed).
  • The original code should be restored after each test run.

Edge Cases to Consider:

  • Functions with no numerical literals.
  • Functions that are not directly called within tests.
  • Complex expressions involving numerical literals (e.g., a + 1 * b). Focus on simple literals for this initial implementation.
  • Handling of comments within the code.
  • Performance impact of mutation and restoration.

Examples

Example 1:

// Original Code (myModule.ts)
export function add(a: number, b: number): number {
  return a + b;
}

// Test Code (myModule.test.ts)
import { add } from './myModule';

test('adds 1 + 2 to equal 3', () => {
  expect(add(1, 2)).toBe(3);
});

Mutation: The 1 in add(1, 2) might be mutated to 5.

Output (Console):

Mutation: Replaced 1 with 5 in add(a: number, b: number): number at myModule.ts:2
Tests Failed: add.test.ts
Mutation Killed: true

Example 2:

// Original Code (myModule.ts)
export function multiply(a: number, b: number): number {
  return a * b;
}

// Test Code (myModule.test.ts)
import { multiply } from './myModule';

test('multiplies 2 * 3 to equal 6', () => {
  expect(multiply(2, 3)).toBe(6);
});

Mutation: The 3 in multiply(2, 3) might be mutated to 7.

Output (Console):

Mutation: Replaced 3 with 7 in multiply(a: number, b: number): number at myModule.ts:2
Tests Failed: multiply.test.ts
Mutation Killed: true

Example 3: (Edge Case - No Numerical Literals)

// Original Code (myModule.ts)
export function greet(name: string): string {
  return `Hello, ${name}!`;
}

// Test Code (myModule.test.ts)
import { greet } from './myModule';

test('greets a user', () => {
  expect(greet('World')).toBe('Hello, World!');
});

Output (Console):

Mutation: No numerical literals found in greet(name: string): string at myModule.ts:2
Mutation Survived: true

Constraints

  • Numerical Literal Range: The replacement numerical literal must be between 1 and 100 (inclusive).
  • Single Mutation per Function: Only one mutation should be applied to each function per test run.
  • TypeScript Only: The plugin should only target TypeScript files.
  • Performance: The plugin should not increase test execution time by more than 20% on average.
  • Mutation Type: Only numerical literals are to be mutated in this initial implementation.

Notes

  • You'll need to use Jest's API to modify the test environment and run tests.
  • Consider using a library like ast-node-matcher or similar to help identify and modify numerical literals in the AST (Abstract Syntax Tree) of your TypeScript code. However, for simplicity, a basic string-based search and replace might be sufficient for this initial implementation.
  • Focus on the core functionality of mutation and test execution. Error handling and advanced features can be added later.
  • Think about how to efficiently restore the original code after each mutation. Storing the original code before mutation is crucial.
  • This is a simplified version of mutation fuzzing. Real-world mutation fuzzing tools employ a much wider range of mutations.
Loading editor...
typescript