Pitest Documentation

repository·master·Indexed 23 days ago

https://github.com/hcoles/pitest

Pitest (PIT) is a mutation testing system for Java and the JVM that improves test suite quality by injecting faults (mutants) into code. It includes support for Maven, Ant, and various test frameworks like JUnit 5 and TestNG. The system features a modular architecture consisting of a main mutation engine, entry points for build tools, and specialized plugins for HTML reporting and command-line usage.

Tokens
3.9K
Snippets
6
Records
28
Agent score
83%

What's inside Pitest

  1. Dependency management and JVM isolation rules

    master

    When contributing to Pitest, follow these strict rules regarding dependencies and class loading to prevent conflicts with the code under test (SUT):

    1. JVM Isolation: Do not load the code under test into the JVM within the pitest-entry module (e.g., via reflection).
    2. pitest module restrictions: Do not introduce third-party dependencies into the pitest module, as they may conflict with the SUT's dependencies. If a dependency is absolutely unavoidable (e.g., ASM), it must be shaded.
    3. Other modules: Dependencies are allowed in other modules but are discouraged; consult the maintainers before adding them. Any dependencies added to these modules must be provided on the classpath by users of the command-line tool or Ant.
  2. How Pitest handles infinite loops and unresponsive mutants

    master

    Since it is impossible to reliably kill a single thread in Java, Pitest handles infinite loops or memory exhaustion by killing the entire minion (child) process.

    Detection Mechanism:

    • Timing-based: Pitest records the normal execution time of tests during the coverage stage. If a test takes $x$ times longer than the recorded time (plus a fudge factor), the mutant is flagged as having caused an infinite loop.
    • Probe-based (Alternative): A more robust method involves inserting probes to count instruction hits. If a mutant causes a probe to be hit significantly more times than the unmutated code, the process is terminated.

    Benefits of Process-level Termination:

    • Robustly stops unresponsive code.
    • Ensures complete isolation of JVM state (static variables, etc.) between mutations.
  3. Understand the Pitest module structure

    master

    Pitest is organized into several specialized modules. Understanding these is critical for knowing where to add code or dependencies:

    • pitest: The main mutation engine. This code must share a JVM with the system under test (SUT).
    • pitest-entry: The main entry point for build tools. This code runs in the main controller process.
    • pitest-html-report: Responsible for generating HTML reports.
    • pitest-command-line: The command line tool for running Pitest.
    • pitest-maven: The Maven mojo for running Pitest.
    • pitest-ant: The Ant task for running Pitest.
    • pitest-maven-verification: Integration tests executing Pitest via the Maven module.
    • pitest-java8-verification: Integration tests validating Pitest against Java 8 features.
    • pitest-groovy-verification: Integration tests validating Pitest behavior with Groovy.
    • pitest-build-config: A minimal Checkstyle configuration used across other modules.
  4. How Pitest generates and identifies mutants

    master

    Pitest generates mutants by manipulating bytecode rather than source code. This approach is fast but can occasionally produce "junk" mutations (mutations that don't map to real programmer errors), especially when dealing with bytecode generated from languages like Scala.

    The Two-Stage Generation Process:

    1. Scanning: The main process scans the bytecode and creates a list of all possible mutations. This list is lightweight and can handle very large programs.
    2. Identification: Each mutant is uniquely identified by a combination of:
      • The mutation operator used.
      • The class + method signature.
      • The index of the mutated bytecode instruction.

    Note: The actual mutated bytecode is only generated by the minion process when it is ready to run the tests.

  5. High-level architecture of Pitest

    master

    Pitest uses a master-minion architecture to perform mutation testing efficiently:

    1. Main Process (Master): Acts as the controller. It analyzes the bytecode to identify all possible mutations but never loads the code under test. It maintains a list of mutation identifiers.
    2. Minion Processes (Children): These are separate JVM processes created to run tests against specific mutants.

    Key Design Principles:

    • Isolation: The code under test is only loaded in the minion processes. This ensures that state changes (like static variables) in one mutation do not affect subsequent mutations.
    • Dependency Management: Minion dependencies are relocated to new packages to prevent conflicts with the code under test.
    • Efficiency: Minions use Java's instrumentation API to insert bytecode in-memory, avoiding expensive disk I/O and minimizing process startup overhead by analyzing multiple mutants within a single minion.
  6. How Pitest selects tests for mutants

    master

    To optimize performance, Pitest uses coverage-based selection rather than running every test against every mutant.

    The Workflow:

    1. Coverage Phase: Before generating mutants, Pitest inserts coverage probes into the code and runs all available tests.
    2. Mapping: It generates a map showing exactly which lines of code are executed by each individual test.
    3. Execution: When a mutant is created at a specific instruction, Pitest only executes the subset of tests that are known to cover that instruction.

    Limitation: Because coverage is determined during class loading, code in static initializers may appear to be executed only by the first test that triggers the class load.

  7. Optimization techniques: Early exit and Test splitting

    master

    Pitest employs two main strategies to improve performance:

    1. Early Exit: Instead of running a full matrix of tests to see which tests kill which mutants, Pitest can be configured to stop analyzing a mutation as soon as a single test fails. This provides a significant performance boost (up to 50%).
    2. Test Splitting: Pitest attempts to split tests into the smallest possible individually executable units. This prevents the system from having to run an entire test class if only one test within that class is required to kill a mutant.
  8. Maintain code formatting and cleanup

    master

    To maintain consistency with the project's coding standards, use the following profile files located in the root of the repository:

    • code_format_profile.xml (for formatting)
    • code_cleanup_profile.xml (for cleanup)

    These files create profiles named henry in your IDE.

  9. Set up Pitest in Eclipse

    master

    To set up the development environment in Eclipse:

    1. Import the repository as an existing Maven project.
    2. Groovy Note: If you do not have Groovy plugins installed, the pitest-groovy-verification module will report errors. Unless you are specifically working on Groovy-related features, it is recommended to simply close that module rather than installing the Groovy dependencies.
  10. Java version compatibility and development recommendations

    master

    Pitest supports Java bytecode generated by versions as old as Java 5. However, execution requires Java 8 or higher.

    Development Recommendations:

    • Recommended Version: Use Java 9 for development. This ensures you can use language features and APIs available in Java 9, while remaining compatible with the requirement to support Java 8+ execution.
    • Constraint: APIs and language features from Java 9 and later cannot be used if you intend to maintain compatibility with Java 8 execution environments.