junit-time-diff
junit-time-diff compares test execution times from JUnit XML reports and highlights meaningful slowdowns, speedups, new tests, and removed tests.
It works especially well with pytest's built-in --junit-xml output and can compare either single runs or averages computed from multiple XML files.
Two Overall Comparisons
Every report contains two overall timing comparisons, because they answer different questions:
- All tests: the time of the whole baseline run against the time of the whole current run. This tells you whether the test suite as a whole became faster or slower, which is what matters for CI wall clock time. It moves whenever tests are added or removed, even if no existing test changed.
- Common tests: the same comparison restricted to the tests present in both runs. Since existing tests normally keep testing the same thing, this is the number that tells you whether the code itself became faster or slower. Adding or removing tests does not affect it.
The verdict and the exit code are based on the common tests comparison, so that a new slow test does not report a false regression, and deleting a slow test does not hide a real one.
Installation
pip install junit-time-diff
For local development from this repository:
pip install -e .[test]
This installs the junit-time-diff command.
Quick Start
Single Run Comparison
pytest --junit-xml=baseline.xml
# make changes
pytest --junit-xml=current.xml
junit-time-diff baseline.xml current.xml
Averaged Comparison Across Multiple Runs
for i in {1..5}; do pytest --junit-xml=baseline$i.xml; done
# make changes
for i in {1..5}; do pytest --junit-xml=current$i.xml; done
junit-time-diff "baseline*.xml" "current*.xml"
Quoting the glob pattern is recommended so the tool receives the pattern and expands it consistently.
CLI Usage
junit-time-diff --help
usage: junit-time-diff [-h] [--version] [--threshold THRESHOLD]
[--min-diff MIN_DIFF]
[--overall-threshold OVERALL_THRESHOLD]
baseline current
Arguments:
baseline: Baseline JUnit XML file or glob pattern such asbaseline.xmlorbaseline*.xmlcurrent: Current JUnit XML file or glob pattern such ascurrent.xmlorcurrent*.xml
Options:
--threshold: Ratio by which a single test must change to be listed, default1.10. A test is reported as slower at1.10xits baseline duration and as faster at1/1.10x--min-diff: Minimum absolute duration change in seconds for a single test to be listed, default0.01. Both this and--thresholdmust be exceeded, so tiny fast tests never create noise--overall-threshold: Percentage by which the total time of the tests common to both runs may grow before the command exits with code1, default5
Exit Codes
0: no significant regression. Either no test got slower, or the tests common to both runs stayed within--overall-threshold1: the tests common to both runs are more than--overall-thresholdpercent slower, or the reports could not be read
A run with no tests in common exits with 0 and prints a warning, since there is nothing to compare.
Example Output
====================================================================================================
TEST TIMING COMPARISON REPORT
====================================================================================================
SUMMARY
----------------------------------------------------------------------------------------------------
Baseline: 9 tests, 3.51s total
Current: 9 tests, 3.69s total
Common tests: 8, New tests: 1, Removed tests: 1
OVERALL TIMING
----------------------------------------------------------------------------------------------------
Scope Before After Diff Change
----------------------------------------------------------------------------------------------------
All tests (9 -> 9) 3.51s 3.69s +0.18s +5.1%
Common tests (8) 3.51s 3.69s +0.18s +5.0%
The all-tests change is the common-tests change (+0.176s) plus new tests (+0.004s) minus removed tests (0.000s).
SLOWER TESTS (2 tests)
----------------------------------------------------------------------------------------------------
Test Before After Diff Change
----------------------------------------------------------------------------------------------------
tests.test_complex::test_complex_four 1.108s 1.422s +0.314s +28.3%
tests.test_complex::test_complex_one 1.527s 1.694s +0.167s +10.9%
====================================================================================================
VERDICT
----------------------------------------------------------------------------------------------------
⚠ WARNING: Tests common to both runs are slower: 3.51s -> 3.69s (+5.0%)!
2 tests got slower (per-test threshold: 10%)
✓ 1 test got faster!
ℹ The whole test suite changed by +5.1%, new and removed tests included
====================================================================================================
The full report also lists faster tests, new tests, and removed tests, each sorted by duration.
Typical Workflows
Compare Python Versions
python3.11 -m pytest --junit-xml=py311.xml
python3.12 -m pytest --junit-xml=py312.xml
junit-time-diff py311.xml py312.xml
Compare a Specific Test Subset
pytest tests/test_api.py --junit-xml=baseline_api.xml
# make changes
pytest tests/test_api.py --junit-xml=current_api.xml
junit-time-diff baseline_api.xml current_api.xml
Use a Stricter Threshold
junit-time-diff baseline.xml current.xml --threshold 1.05 --overall-threshold 2
Fail a CI Job on a Regression
# Exits with 1 when the tests present in both runs are more than 5% slower.
junit-time-diff "baseline*.xml" "current*.xml"
Tips
- Run several repetitions and compare averages to reduce noise.
- Compare results on the same machine when possible.
- Keep the selected test set consistent between baseline and current runs.
- Ignore tiny changes unless they are part of a repeated pattern.
- Read the common-tests comparison to judge the code, and the all-tests comparison to judge the CI time.
Development
Build the package:
python3 -m build
Run tests:
pytest
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file junit_time_diff-0.2.0.tar.gz.
File metadata
- Download URL: junit_time_diff-0.2.0.tar.gz
- Upload date:
- Size: 12.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c462b4fc7901432048785942fbc9546c31cbe38ee63fa5dee175c2cbbb44dd87
|
|
| MD5 |
2978fb830800b2189b88274855866af4
|
|
| BLAKE2b-256 |
f2480587b8b15cda6b624330b4efd79a948f358385690bc9fe0f4012d88d927f
|
Provenance
The following attestation bundles were made for junit_time_diff-0.2.0.tar.gz:
Publisher:
tests.yaml on mauvilsa/junit-time-diff
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
junit_time_diff-0.2.0.tar.gz -
Subject digest:
c462b4fc7901432048785942fbc9546c31cbe38ee63fa5dee175c2cbbb44dd87 - Sigstore transparency entry: 2528619082
- Sigstore integration time:
-
Permalink:
mauvilsa/junit-time-diff@f9b4ff8c5a9ab9a8b84b0dd253244c91cba9a36b -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/mauvilsa
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
tests.yaml@f9b4ff8c5a9ab9a8b84b0dd253244c91cba9a36b -
Trigger Event:
push
-
Statement type:
File details
Details for the file junit_time_diff-0.2.0-py3-none-any.whl.
File metadata
- Download URL: junit_time_diff-0.2.0-py3-none-any.whl
- Upload date:
- Size: 10.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67d1ef19e78404aed78bf5e08e4ea3cee96366f367082f46c87d891a1ba09e2a
|
|
| MD5 |
8281ff77052ce406451932da62d15d03
|
|
| BLAKE2b-256 |
1e3af3655e493a1bb95712293bb40ffe4e2462efe446913af7125d80a064b030
|
Provenance
The following attestation bundles were made for junit_time_diff-0.2.0-py3-none-any.whl:
Publisher:
tests.yaml on mauvilsa/junit-time-diff
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
junit_time_diff-0.2.0-py3-none-any.whl -
Subject digest:
67d1ef19e78404aed78bf5e08e4ea3cee96366f367082f46c87d891a1ba09e2a - Sigstore transparency entry: 2528619206
- Sigstore integration time:
-
Permalink:
mauvilsa/junit-time-diff@f9b4ff8c5a9ab9a8b84b0dd253244c91cba9a36b -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/mauvilsa
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
tests.yaml@f9b4ff8c5a9ab9a8b84b0dd253244c91cba9a36b -
Trigger Event:
push
-
Statement type: