Skip to content

fix(subtests): use _pytest.timing instead of time module to prevent negative durations - #15261

Closed
Abhiroop3917 wants to merge 2 commits into
pytest-dev:mainfrom
Abhiroop3917:fix/subtests-timing-15201
Closed

Abhiroop3917 wants to merge 2 commits into
pytest-dev:mainfrom
Abhiroop3917:fix/subtests-timing-15201

Conversation

@Abhiroop3917

Copy link
Copy Markdown

Type of changes

  • Bug fix
  • New feature
  • Documentation / docstrings
  • Tests
  • Other

AI?

  • AI was used to generate this PR

Checklist

  • I've run the latest black with default args on new code.
  • I've updated CHANGELOG.md and CONTRIBUTORS.md where appropriate.
  • I've added tests for new code.
  • I accept that @pytest-dev/pytest may be pedantic in the code review.

Description

When a test mocks time.perf_counter or time.time (as freezegun, time-machine, or monkeypatch do), subtests report negative durations and a stop timestamp of 0.0 (1970-01-01) in JUnit XML output. Normal tests are unaffected by the same mocking.

Root cause: subtests.py imported the time module and called time.time() / time.perf_counter() directly. When a mock is installed inside the subtest body, the start-time is already captured from the real clock, but the stop-time reads the patched value (0.0), giving stop − start < 0.

The rest of pytest uses _pytest.timing which captures real function references at import time (before any test can patch them). This pattern was introduced specifically to prevent this class of bug (issue #185) and extended in #7764. subtests.py was added later and missed it.

Fix: Replace the four time.xxx() calls in subtests.py with timing.xxx().

Reproducer (before fix):

import time

def test_with_subtest(subtests, monkeypatch):
    with subtests.test(msg="frozen clock"):
        monkeypatch.setattr(time, "perf_counter", lambda: 0.0)
        monkeypatch.setattr(time, "time", lambda: 0.0)
<!-- JUnit XML output: -->
<testcase name="test_with_subtest" time="-1194.217" .../>

After fix: duration is always non-negative.

closes #15201

…cess

subtests.py called time.time() and time.perf_counter() via the time module
directly. When a test mocks time.perf_counter to return 0.0 inside a subtest
body, the start-time was already captured from the real clock but the
stop-time would read 0.0, producing a large negative duration and a
stop-timestamp of 1970-01-01 in JUnit XML output.

The rest of pytest uses _pytest.timing (which captures the real function
references at import time, before any test can patch them) specifically to
prevent this class of bug (first fixed in pytest-dev#185, extended in pytest-dev#7764).

Replace the four time.xxx() calls in subtests.py with timing.xxx() to bring
subtests in line with the rest of the codebase.

Closes pytest-dev#15201
@pyba-chronographer pyba-chronographer Bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Oct 25, 2026
@RonnyPfannschmidt

Copy link
Copy Markdown
Member

i already had a pr open for that since a while

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided (automation) changelog entry is part of PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Subtest durations become negative when time is mocked (subtests.py bypasses _pytest.timing)

2 participants