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:
- Identify Target Code: Locate functions within your TypeScript files that are being tested. Assume these functions are exported and called within your test files.
- 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). - Run Tests: After introducing the mutation, run your Jest test suite.
- 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).
- 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-matcheror 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.