go-mutesting

repository·master·Indexed 42 days ago

https://github.com/avito-tech/go-mutesting

A framework for performing mutation testing on Go source code. It identifies inadequately covered code by injecting small changes (mutants) into the source and checking if existing test suites fail. Features include a wide variety of arithmetic, loop, conditional, and statement mutators, support for custom execution commands via the --exec flag, and the ability to blacklist false positives using MD5 checksums.

Tokens
4.1K
Snippets
12
Records
19
Agent score
53%

What's inside go-mutesting

  1. What is mutation testing?

    master

    Mutation testing is a technique used to evaluate the quality of existing software tests and design new ones. It works by making small, programmatic changes to the source code (creating 'mutants').

    • Killing a mutant: If your test suite fails due to the mutation, the mutant is considered 'killed'.
    • Surviving a mutant: If your tests still pass despite the mutation, the mutant has 'survived', indicating that your test suite is not sufficiently covering that logic.

    Beyond finding untested code, mutation testing can also uncover dead code or implementation flaws that existing tests are blind to.

  2. Control mutations via code annotations

    master

    You can use special comment annotations directly in your Go source code to provide granular control over which parts of the code are mutated. These annotations only apply to the file where they are declared.

    Supported Annotations

    1. // mutator-disable-func: Disables all mutations for the function immediately following the comment.
    2. // mutator-disable-next-line <mutator1>, <mutator2>: Disables specific mutators (or * for all) for the next line.
    3. // mutator-disable-regexp <pattern> <mutator1>, <mutator2>: Disables mutations for any line matching the provided regex pattern.
    // mutator-disable-func  
    func CalculateDiscount(price float64) float64 {  
        return price * 0.9  
    } 
    
    // mutator-disable-next-line *  
    x = 42  // Fully protected
    
    // mutator-disable-next-line branch/if, increment  
    if x > 0 {  // Only branch/if and increment mutators are disabled
        y += 1  
    }
    
    // mutator-disable-regexp s\.Method\(\) *  
    s.Method()
  3. How to use go-mutesting

    master

    To run mutation testing, provide targets such as Go source files, directories, or packages. You can use the ... wildcard for recursive searching. Note that files ending in _test are automatically excluded.

    By default, go-mutesting uses a built-in execution command that replaces the original file with the mutation, runs the package tests, and reports if the mutation was killed.

    To use a custom execution command, use the --exec flag. This is useful if you need specific testing logic or want to use the provided scripts in the /scripts/exec directory.

    # Run mutation testing on specific files, directories, and packages
    go-mutesting parse.go example/ github.com/avito-tech/go-mutesting/mutator/...
    
    # Run mutation testing using a custom exec command
    go-mutesting --exec "$GOPATH/src/github.com/avito-tech/go-mutesting/scripts/exec/test-mutated-package.sh" github.com/avito-tech/go-mutesting/example
  4. Blacklist false positives using checksums

    master

    Mutation testing can produce false positives (e.g., from optimizations like early exits). You can ignore these mutations using the --blacklist flag, which points to a file containing the MD5 checksums of the mutations you want to skip. Each checksum should be on a new line in the blacklist file.

    # 1. Create a blacklist file (e.g., example.blacklist) containing the checksum
    echo "5b1ca0cfedd786d9df136a0e042df23a" > example.blacklist
    
    # 2. Run go-mutesting with the blacklist
    go-mutesting --blacklist example.blacklist github.com/avito-tech/go-mutesting/example
  5. How to write custom mutators

    master

    To implement a custom mutator, you must follow two steps:

    1. Implement the Mutator interface defined in the github.com/avito-tech/go-mutesting/mutator package. The specific requirements for the interface methods are documented in the package source code.
    2. Register your new mutator using the Register function in the github.com/avito-tech/go-mutesting/mutator package so that the go-mutesting binary can recognize and use it.
  6. Run a quick mutation testing example

    master

    To perform mutation testing on a Go project using all available mutators, run the go-mutesting command followed by the package path you wish to test.

    When a mutation is not detected by your test suite (i.e., the mutant is not 'killed'), go-mutesting will print the source code patch. This patch identifies the specific line change that your tests failed to catch, allowing you to investigate untested code or potential implementation flaws like memory leaks.

    go-mutesting github.com/avito-tech/go-mutesting/...
  7. Custom mutation execution commands

    master

    By default, go-mutesting uses go test to verify mutants. However, you can override this by providing a custom execution command using the --exec <command> flag.

    When using a custom command, the tool sets several environment variables that your command can use to identify the mutation context:

    • MUTATE_CHANGED: Path to the mutated file.
    • MUTATE_DEBUG: Boolean string indicating if debug mode is enabled.
    • MUTATE_ORIGINAL: Path to the original source file.
    • MUTATE_PACKAGE: The path of the package being tested.
    • MUTATE_TIMEOUT: The timeout value in seconds.
    • MUTATE_VERBOSE: Boolean string indicating if verbose mode is enabled.
    • TEST_RECURSIVE: Set to true if the --recursive flag was used.
    # Example: Using a custom test runner
    go-mutesting --exec "custom-test-runner --flag" ./...
  8. Configure go-mutesting via YAML

    master

    You can fine-tune mutation testing behavior using a YAML configuration file. To use a custom configuration, provide the path to your file using the --config CLI flag. If this flag is not provided, no default configuration is used.

    Available configuration parameters:

    skip_without_test: true
    skip_with_build_tags: true
    json_output: false
    html_output: false
    silent_mode: false
    exclude_dirs: []
  9. Use go-mutesting CLI

    master

    The go-mutesting command performs mutation testing on Go source code by injecting faults (mutants) into the code and running tests to see if they catch the changes.

    Basic usage involves passing target files or packages as arguments. The tool will identify suitable Go files, apply mutators, execute tests, and generate a report.

    Common workflows:

    • List available mutators: Use the --list-mutators flag to see which mutation types are implemented.
    • List target files: Use the --list-files flag to see which files the tool has identified as targets.
    • Print AST: Use the --print-ast flag to inspect the Abstract Syntax Tree of the target files.
    • Filter by function: Use the --filter-match <regex> flag to only mutate functions whose names match the provided regular expression.
    # Example: Run mutation testing on a specific file
    go-mutesting ./path/to/your/file.go
    
    # Example: List all implemented mutators
    go-mutesting --list-mutators
    
    # Example: Mutate only functions matching a regex
    go-mutesting --filter-match "^Test.*" ./pkg/...
  10. Implement custom mutation exec commands

    master

    A mutation exec command is a script or binary invoked for every mutation to determine if it was 'killed'. It must be stateless and isolated.

    Required Workflow

    1. Setup: Apply the mutation to the source.
    2. Test: Run the test suite.
    3. Cleanup: Revert changes and remove temporary files.
    4. Report: Exit with a specific code indicating the result.

    Environment Variables

    go-mutesting passes the following variables to your command:

    • MUTATE_CHANGED: Filename of the mutated file.
    • MUTATE_DEBUG: Enable debugging output.
    • MUTATE_ORIGINAL: Filename of the original file.
    • MUTATE_PACKAGE: Import path of the original file.
    • MUTATE_TIMEOUT: Timeout for the execution.
    • MUTATE_VERBOSE: Enable verbose output.
    • TEST_RECURSIVE: Whether to run tests recursively.

    Exit Codes

    Exit codeDescription
    0Killed: The mutation caused a test failure (Good).
    1Alive: The mutation did not cause a failure (Bad/Flaw).
    2Skipped: Mutation was skipped (e.g., due to compilation errors).
    >2Unknown: The command returned an unexpected exit code.
  11. Reference implemented mutators by category

    master

    Below is a detailed list of the mutators implemented in go-mutesting.

    Arithmetic Mutators

    arithmetic/base

    NameOriginalMutated
    Plus+-
    Minus-+
    Multiplication*/
    Division/*
    Modulus%*

    arithmetic/bitwise

    NameOriginalMutated
    BitwiseAnd&|
    BitwiseOr|&
    BitwiseXor^&
    BitwiseAndNot&^&
    ShiftRight>><<
    ShiftLeft<<>>

    arithmetic/assign_invert

    NameOriginalMutated
    AddAssign+=-=
    SubAssign-=+=
    MulAssign*=/=
    QuoAssign/=*=
    RemAssign%=*=

    arithmetic/assignment

    NameOriginalMutated
    AddAssignment+==
    SubAssignment-==
    MulAssignment*==
    QuoAssignment/==
    RemAssignment%==
    AndAssignment&==
    OrAssignment|==
    XorAssignment^==
    SHLAssignment<<==
    SHRAssignment>>==
    AndNotAssignment&^==

    Loop Mutators

    loop/break

    NameOriginalMutated
    Breakbreakcontinue
    Continuecontinuebreak

    loop/condition

    NameOriginalMutated
    for k < 100k < 1001 < 1
    for i := 0; i < 5; i++i < 51 < 1

    loop/range_break

    Adds a break to the beginning of each range loop.

    NameOriginal BodyMutated Body
    for i,v := range xwithout breakwith break

    Numbers Mutators

    numbers/incrementer

    NameOriginalMutated
    IncrementInteger100101
    IncrementFloat10.111.1

    numbers/decrementer

    NameOriginalMutated
    DecrementInteger10099
    DecrementFloat10.19.1

    Conditional Mutators

    conditional/negated

    NameOriginalMutated
    GreaterThanNegotiation><=
    LessThanNegotiation<>=
    GreaterThanOrEqualToNegotiation>=<
    LessThanOrEqualToNegotiation<=>
    Equal==!=
    NotEqual!===

    Branch Mutators

    branch/case

    Empties case bodies.

    branch/if

    Empties branches of if and else if statements.

    branch/else

    Empties branches of else statements.

    Expression Mutators

    expression/comparison

    Replaces comparison operators with similar operators to catch off-by-one errors.

    NameOriginalMutated
    GreaterThan>>=
    LessThan<<=
    GreaterThanOrEqualTo>=>
    LessThanOrEqualTo<=<

    expression/remove

    Replaces && and || operators by making each term irrelevant using true or false.

    Statement Mutators

    statement/remove

    Removes assignment, increment, decrement, and expression statements.