When a test starts failing, you often need to find the exact commit that
introduced the regression. The bisect functions perform an automatic
binary search over a range of git commits — similar to git bisect, but
fully integrated with the test infrastructure so that each candidate
commit is built, tested, and evaluated automatically.
- You provide a good commit (where the test passes) and a bad commit (where it fails).
- The bisect function enumerates all commits between the two using
git rev-list --ancestry-path. - It verifies that the good commit really passes and the bad commit really fails.
- It performs a binary search: at each step, a middle commit is checked out as a temporary git worktree, built, and tested.
- Depending on whether the result is good or bad, the search range is narrowed.
- When the search converges, the offending commit — the first one that causes the failure — is reported.
Each worktree is created next to the original repository root so that the original working tree is never modified. Baseline results (fingerprints, statistics, speed measurements, etc.) are automatically loaded from the good commit.
There is a convenience wrapper for each test type:
| Function | Test type |
|---|---|
bisect_fingerprint_tests() |
Fingerprint tests |
bisect_statistical_tests() |
Statistical tests |
bisect_smoke_tests() |
Smoke tests |
bisect_speed_tests() |
Speed tests |
bisect_chart_tests() |
Chart tests |
bisect_sanitizer_tests() |
Sanitizer tests |
All wrappers accept the same simulation_project, good_hash, and
bad_hash arguments, plus any keyword arguments supported by the
underlying test runner (e.g. working_directory_filter, config_filter,
sim_time_limit).
Every wrapper also accepts update_good_results=True (the default),
which causes the corresponding update_*_test_results() function to be
called on the good commit before bisection starts. This refreshes the
baseline data (fingerprints, statistics, speed measurements, chart
images) so the comparison at each step uses an up-to-date reference.
Set it to False to skip the refresh and use whatever baseline is
checked in at the good commit.
For bisect_smoke_tests() and bisect_sanitizer_tests() there is no
baseline to refresh, so the flag is accepted for API symmetry but has
no effect.
result = bisect_fingerprint_tests(
simulation_project=inet_project,
good_hash="v4.5.0",
bad_hash="master",
sim_time_limit="1s",
)This finds the first commit between v4.5.0 and master that causes a
fingerprint test to fail. Tags, branch names, and full/short commit
hashes are all accepted.
You can pass any filter supported by the underlying test runner to limit which simulations are tested at each step. This makes bisecting much faster when you already know which area is affected:
result = bisect_fingerprint_tests(
simulation_project=inet_project,
good_hash="abc1234",
bad_hash="def5678",
working_directory_filter="examples/ethernet",
config_filter="MixedLAN",
sim_time_limit="10s",
)# Statistical tests
result = bisect_statistical_tests(
simulation_project=inet_project,
good_hash="v4.5.0",
bad_hash="master",
)
# Smoke tests
result = bisect_smoke_tests(
simulation_project=inet_project,
good_hash="v4.5.0",
bad_hash="master",
)
# Speed tests
result = bisect_speed_tests(
simulation_project=inet_project,
good_hash="v4.5.0",
bad_hash="master",
)Every bisect function returns a BisectResult object:
print(result)
# Bisect result: offending commit abc1234567 found in 7 steps across 128 commits (v4.5.0..master) in 0:12:34Attributes of BisectResult:
offending_commit— full git hash of the first bad commitresult— the test result object from the offending commitnum_steps— number of bisect steps performednum_commits— total commits in the search rangegood_hash/bad_hash— the original bounds passed by the callersteps— list of(commit_hash, result, is_good)tuples for every tested commitelapsed_wall_time— total wall-clock time of the bisectionerror_message— set when bisection could not complete (e.g. the good commit does not actually pass)
for commit, step_result, is_good in result.steps:
verdict = "GOOD" if is_good else "BAD"
print(f"{commit[:10]} {verdict}")The convenience wrappers call bisect_simulations_between_commits() with
the appropriate test runner. You can call it directly for custom test
logic:
result = bisect_simulations_between_commits(
simulation_project=inet_project,
good_hash="v4.5.0",
bad_hash="master",
run_function=run_fingerprint_tests,
is_good_result=lambda r: r.is_all_results_expected(),
sim_time_limit="1s",
)Parameters:
simulation_project— the project whose git repository is bisectedgood_hash/bad_hash— bounds of the commit rangerun_function— called asrun_function(simulation_project=..., **kwargs)for each candidate commitgood_result— expectedresultattribute value for good commits (default"PASS"); ignored whenis_good_resultis providedis_good_result— optional predicateis_good_result(result) -> boolfor custom pass/fail logicupdate_good_results_function— optional; when provided, called on the good commit to refresh baseline data before testing beginsbuild_log_level/simulation_log_level— control verbosity during bisection (default"WARN")
Suppose fingerprint tests pass on the v4.5.0 tag but fail on the
current master branch. Here is a complete REPL session that locates
the offending commit:
# Load the workspace
In [1]: load_opp_file("inet.opp")
# Confirm the failure exists
In [2]: r = run_fingerprint_tests(simulation_project=inet_project, sim_time_limit="1s")
# Multiple fingerprint test results: FAIL, summary: 3 FAIL, 39 PASS in 0:00:01.567479
# Bisect to find the culprit
In [3]: b = bisect_fingerprint_tests(
...: simulation_project=inet_project,
...: good_hash="v4.5.0",
...: bad_hash="master",
...: sim_time_limit="1s",
...: )
# Verifying good v4.5.0 GOOD ...
# Verifying bad master BAD ...
# Step 1/128 a1b2c3d4e5 GOOD ...
# Step 2/128 f6a7b8c9d0 BAD ...
# ...
# Bisect complete: offending commit is e1f2a3b4c5 (found in 9 steps)
# Inspect the result
In [4]: print(b)
# Bisect result: offending commit e1f2a3b4c5 found in 9 steps across 128 commits (v4.5.0..master) in 0:08:42
# View the offending commit
In [5]: !git -C /home/user/workspace/inet log --oneline -1 e1f2a3b4c5
# e1f2a3b4c5 Refactored MAC address resolution
# Narrow a re-bisect to only the failing area for faster results
In [6]: b2 = bisect_fingerprint_tests(
...: simulation_project=inet_project,
...: good_hash="v4.5.0",
...: bad_hash="master",
...: working_directory_filter="examples/ethernet",
...: sim_time_limit="1s",
...: )- Narrow your filters — the fewer simulations tested at each step, the faster the bisect completes. Use
working_directory_filter,config_filter, andsim_time_limitto focus on the failing area. - Use short sim_time_limit — if the failure reproduces with a short time limit, set it explicitly to speed up each step.
- Check the steps — if the result seems wrong, inspect
result.stepsto see whether any commits had unexpected outcomes (e.g. non-monotonic failures). - Tags and branches work —
good_hashandbad_hashaccept anything thatgit rev-listunderstands: tags, branch names, and commit hashes.