Skip to main content

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 as baseline.xml or baseline*.xml
  • current: Current JUnit XML file or glob pattern such as current.xml or current*.xml

Options:

  • --threshold: Ratio by which a single test must change to be listed, default 1.10. A test is reported as slower at 1.10x its baseline duration and as faster at 1/1.10x
  • --min-diff: Minimum absolute duration change in seconds for a single test to be listed, default 0.01. Both this and --threshold must 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 code 1, default 5

Exit Codes

  • 0: no significant regression. Either no test got slower, or the tests common to both runs stayed within --overall-threshold
  • 1: the tests common to both runs are more than --overall-threshold percent 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

junit_time_diff-0.2.0.tar.gz (12.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

junit_time_diff-0.2.0-py3-none-any.whl (10.1 kB view details)

Uploaded Python 3

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

Hashes for junit_time_diff-0.2.0.tar.gz
Algorithm Hash digest
SHA256 c462b4fc7901432048785942fbc9546c31cbe38ee63fa5dee175c2cbbb44dd87
MD5 2978fb830800b2189b88274855866af4
BLAKE2b-256 f2480587b8b15cda6b624330b4efd79a948f358385690bc9fe0f4012d88d927f

See more details on using hashes here.

Provenance

The following attestation bundles were made for junit_time_diff-0.2.0.tar.gz:

Publisher: tests.yaml on mauvilsa/junit-time-diff

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for junit_time_diff-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 67d1ef19e78404aed78bf5e08e4ea3cee96366f367082f46c87d891a1ba09e2a
MD5 8281ff77052ce406451932da62d15d03
BLAKE2b-256 1e3af3655e493a1bb95712293bb40ffe4e2462efe446913af7125d80a064b030

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page