Skip to content

Latest commit

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

FailMapper

Automated generation of unit tests guided by failure scenarios — a failure-aware Monte Carlo Tree Search (MCTS) framework that generates JUnit 5 tests optimized for finding real logical bugs, not just coverage.

This is the Java-native framework (a full port and re-architecture of the original Python research prototype from our ASE 2025 paper; the Python implementation is archived under python-baseline/).

How it works

Java project ──► Build oracle ──► Source analysis ──► Failure scenarios
(real classpath) (JavaParser AST) (21 detectors)
│
Bug reports ◄── LLM verification ◄── Failure-aware MCTS search
+ JUnit tests (batch verdicts) │
├─ LLM generates targeted tests
├─ in-memory compile (ms)
├─ forked JUnit run + timeout kill
└─ JaCoCo coverage → reward

Instead of pure coverage maximization, the search extracts a per-class failure model (boundary conditions, logical operations, complexity) plus ~21 logical-bug pattern categories, and biases MCTS toward code likely to fail. The LLM is the expansion operator; execution is real (compile + run + coverage); a verification pass separates real source bugs from bad tests.


1. Prerequisites

Check each of these before your first run:

RequirementCheck withNotes
JDK 17+java -version17, 21 both fine
Maven 3.9+mvn -versionused to build FailMapper and to compile your target project
DeepSeek API keyget one at https://platform.deepseek.com; other OpenAI-compatible endpoints work via env vars
Internet accessLLM API + Maven Central for dependency resolution

Your target project (the code you want tested) must satisfy three things:

  1. It is a Maven project that has been compiled at least once — run mvn compile in it first. FailMapper reads your build model but never runs your build.
  2. JUnit 5 is on its test classpath (junit-jupiter or junit-jupiter-api in test scope). Generated tests are JUnit 5 and are compiled against your project's test classpath.
  3. Its dependencies resolve from your local ~/.m2 repository or Maven Central (private-only repositories are not supported yet).

Multi-module projects are supported — FailMapper locates the module that owns your target class and uses that module's classpath.

2. Install (build from source)

git clone <this-repository>cd FailMapper/java
mvn package -DskipTests

This takes about a minute and produces a single self-contained executable:

java/failmapper-app/target/failmapper.jar (~17 MB, no other files needed)

To also run FailMapper's own test suite (650+ tests, ~10 minutes), drop -DskipTests.

3. Your first run (complete copy-paste example)

This example targets a real open-source class from Apache Commons CLI:

# 1. get a target project and compile it once
git clone --depth 1 https://github.com/apache/commons-cli.git /tmp/commons-cli
cd /tmp/commons-cli && mvn -q compile
# 2. set your API keyexport DEEPSEEK_API_KEY=sk-...
# 3. run FailMapper (args: <project> <class-fqn> <output-dir> [iterations] [seed])
java -jar /path/to/FailMapper/java/failmapper-app/target/failmapper.jar \
/tmp/commons-cli \
org.apache.commons.cli.OptionValidator \
/tmp/fm-out \
5 42

What you will see: the run is quiet by default and finishes with a summary like

best test: /tmp/fm-out/best_test.java
coverage 95.65%, 0 real bug(s), 5 iteration(s)

Add FM_LLM_VERBOSE=1 before the command to watch each LLM call live ([llm] call #3 ok reply=6831 chars elapsed=60.8s).

Duration & cost: roughly 2–3 minutes per iteration (dominated by LLM latency). A 5-iteration run makes ~6–17 LLM calls (extra calls appear when failing tests go through bug verification) — typically a few cents with deepseek-v4-pro.

4. Command reference

java -jar failmapper.jar <project-root> <target-class-fqn> <output-dir> [maxIterations] [seed]
ArgumentRequiredMeaningDefault
project-rootyesroot directory of the target Maven project
target-class-fqnyesfully-qualified class to test, e.g. com.example.Parser
output-diryeswhere results are written; must be outside the target project (enforced)
maxIterationsnoMCTS iterations; more = deeper search, more LLM cost20
seednorandom seed for the search (LLM sampling stays stochastic)42
Environment variableRequiredMeaning
DEEPSEEK_API_KEYyesAPI key; read from the environment, never persisted
DEEPSEEK_MODELnomodel id (default deepseek-v4-pro)
FM_LLM_VERBOSEnoset to 1 for one log line per LLM call (sizes + latency only, never prompt content)

Fixed behavior worth knowing: each generated test executes in a forked JVM with a 60-second hard timeout — an accidentally-infinite test is killed and recorded, it cannot hang the run.

5. Understanding the output

Five files appear in your output directory:

FileWhat it is
best_test.javaThe best generated JUnit 5 test class (highest coverage, with verified bug-detecting methods merged in). Copy it into your project's src/test/java if you want to keep it.
summary.jsonOne-look result: coverage, iterations, bug counts, model, seed
verified_bugs.jsonEvery failing test method that went through LLM verification, with the verdict
potential_bugs.jsonRaw failing-test observations collected during the search (pre-verification)
iteration_log.jsonPer-iteration trace: action chosen, reward, coverage — useful to understand what the search did

The file you care about most is verified_bugs.json. Each entry:

{
"methodName": "testStripHyphensShortPrefix",
"isRealBug": true, // true = verified source bug// false = bad test / false alarm"verificationConfidence": 0.95, // 0..0.95"explanation": "The source code of Util.stripLeadingHyphens contains..."
}

Read isRealBug: true entries first and check explanation against the source. Expect some duplicates (several test methods often hit the same underlying bug) and treat verdicts as strong leads, not court rulings — in our benchmark roughly 1 false alarm per clean run slips through.

6. What FailMapper will never do to your project

  • It never modifies your pom.xml, sources, or tests (verified at runtime: the tool refuses to write inside the target tree).
  • Coverage instrumentation happens in FailMapper's own forked JVM via the JaCoCo agent — nothing is injected into your build.
  • All compilation of generated tests happens in temporary directories.

Delete the output directory and there is no trace the tool ever ran.

7. Troubleshooting

SymptomCause / fix
DEEPSEEK_API_KEY environment variable is not setexport DEEPSEEK_API_KEY=... in the same shell
usage: FaMctsRunner ... and exitfewer than 3 arguments — see §4
source file not found for <fqn>wrong FQN (check package), or the class lives in a module/source root the project's POM doesn't declare
every iteration reports compile errors mentioning org.junityour target project has no JUnit 5 test dependency — add junit-jupiter (test scope) and re-run mvn compile
coverage is always 0.00%target project not compiled (target/classes missing) — run mvn compile in it
output dir ... inside the target project errorchoose an output directory outside the project tree
HTTP 401invalid API key; HTTP 429 / retries logged — rate limited, the client backs off automatically
run seems stuck ~1 min then continuesthat's the 60 s fork timeout killing a runaway generated test — by design
parent POM ... could not be resolvedthe target's parent/BOM lives in a private repository — build it once locally (mvn install) so it lands in ~/.m2

8. Architecture

ModuleRole
failmapper-coreTyped domain contracts (FQN-keyed end to end)
failmapper-analysisJavaParser/SymbolSolver extraction: class model, failure model, 21 failure-scenario detectors, symbol API retrieval
failmapper-buildBuild-system oracle: effective POM, transitive test classpath (Maven Resolver), multi-module reactors, Gradle Tooling API
failmapper-execIn-memory compilation (structured diagnostics) + forked JUnit Platform execution with hard timeouts
failmapper-coverageJaCoCo agent attach + core-API reading, exact per-class attribution
failmapper-searchThe FA-MCTS kernel: UCB selection with failure-aware bonus, reward composition, strategy selection, bug classification
failmapper-llmLLM clients (DeepSeek, OpenAI-compatible), prompt templates, code extraction with fallback salvage
failmapper-appEnd-to-end composition and the failmapper.jar CLI

9. Validation

The port was validated against the original Python implementation with a four-layer differential methodology:

  • Layer A — the search-kernel formulas verified bit-identical against fixtures generated from the original implementation
  • Layer B — source-analysis outputs aligned on real open-source classes
  • Layer P — LLM prompt renderings verified byte-identical
  • Layer C — end-to-end benchmark against the original: better seeded-bug recall at lower token cost and wall time, with zero project mutations

The full test suite (650+ tests, including the differential fixtures) runs with cd java && mvn test.

10. The Python baseline

The original prototype is preserved unmodified in python-baseline/ as the frozen differential-testing oracle (see python-baseline/ARCHIVED.md).

Citation

@inproceedings{dong2025failmapper,
title={FailMapper: Automated Generation of Unit Tests Guided by Failure Scenarios},
author={Dong, Ruiqi and Deng, Zehang and Zhu, Xiaogang and Du, Xiaoning and Liu, Huai and Wang, Shaohua and Wen, Sheng and Xiang, Yang},
booktitle={2025 40th IEEE/ACM International Conference on Automated Software Engineering (ASE)},
pages={2388--2400},
year={2025},
organization={IEEE}
}

About

FailMapper: Failure-Scenario-Guided Unit Test Generation using Monte Carlo Tree Search (MCTS) and LLMs. Detects 233% more bugs than baselines on Defects4J. Covers 9 failure scenarios for automated bug detection in Java programs. [ASE 2025]

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages