Skip to content
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ Daw-Ran Liou
Debi Mishra
Denis Kirisov
Denivy Braiam Rück
Deysha Rivera
Dheeraj C K
Dhiren Serai
Diego Russo
Expand Down
1 change: 1 addition & 0 deletions changelog/13384.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed an issue where pytest could report negative durations.
5 changes: 3 additions & 2 deletions src/_pytest/junitxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,15 +637,16 @@ def pytest_internalerror(self, excrepr: ExceptionRepr) -> None:

def pytest_sessionstart(self) -> None:
self.suite_start_time = timing.time()
self.suite_start_perf = timing.perf_counter()

def pytest_sessionfinish(self) -> None:
dirname = os.path.dirname(os.path.abspath(self.logfile))
# exist_ok avoids filesystem race conditions between checking path existence and requesting creation
os.makedirs(dirname, exist_ok=True)

with open(self.logfile, "w", encoding="utf-8") as logfile:
suite_stop_time = timing.time()
suite_time_delta = suite_stop_time - self.suite_start_time
suite_stop_perf = timing.perf_counter()
suite_time_delta = suite_stop_perf - self.suite_start_perf

numtests = (
self.stats["passed"]
Expand Down
8 changes: 4 additions & 4 deletions src/_pytest/pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -1150,7 +1150,7 @@ def runpytest_inprocess(

if syspathinsert:
self.syspathinsert()
now = timing.time()
now = timing.perf_counter()
capture = _get_multicapture("sys")
capture.start_capturing()
try:
Expand Down Expand Up @@ -1180,7 +1180,7 @@ class reprec: # type: ignore

assert reprec.ret is not None
res = RunResult(
reprec.ret, out.splitlines(), err.splitlines(), timing.time() - now
reprec.ret, out.splitlines(), err.splitlines(), timing.perf_counter() - now
)
res.reprec = reprec # type: ignore
return res
Expand Down Expand Up @@ -1408,7 +1408,7 @@ def run(
print(" in:", Path.cwd())

with p1.open("w", encoding="utf8") as f1, p2.open("w", encoding="utf8") as f2:
now = timing.time()
now = timing.perf_counter()
popen = self.popen(
cmdargs,
stdin=stdin,
Expand Down Expand Up @@ -1445,7 +1445,7 @@ def handle_timeout() -> None:

with contextlib.suppress(ValueError):
ret = ExitCode(ret)
return RunResult(ret, out, err, timing.time() - now)
return RunResult(ret, out, err, timing.perf_counter() - now)

def _dump_lines(self, lines, fp):
try:
Expand Down
8 changes: 4 additions & 4 deletions src/_pytest/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -769,7 +769,7 @@ def pytest_collection(self) -> None:
if self.isatty:
if self.config.option.verbose >= 0:
self.write("collecting ... ", flush=True, bold=True)
self._collect_report_last_write = timing.time()
self._collect_report_last_write = timing.perf_counter()
elif self.config.option.verbose >= 1:
self.write("collecting ... ", flush=True, bold=True)

Expand All @@ -789,7 +789,7 @@ def report_collect(self, final: bool = False) -> None:

if not final:
# Only write "collecting" report every 0.5s.
t = timing.time()
t = timing.perf_counter()
if (
self._collect_report_last_write is not None
and self._collect_report_last_write > t - REPORT_COLLECTING_RESOLUTION
Expand Down Expand Up @@ -823,7 +823,7 @@ def report_collect(self, final: bool = False) -> None:
@hookimpl(trylast=True)
def pytest_sessionstart(self, session: Session) -> None:
self._session = session
self._sessionstarttime = timing.time()
self._sessionstarttime = timing.perf_counter()
if not self.showheader:
return
self.write_sep("=", "test session starts", bold=True)
Expand Down Expand Up @@ -1202,7 +1202,7 @@ def summary_stats(self) -> None:
if self.verbosity < -1:
return

session_duration = timing.time() - self._sessionstarttime
session_duration = timing.perf_counter() - self._sessionstarttime
(parts, main_color) = self.build_summary_stats_line()
line_parts = []

Expand Down
8 changes: 4 additions & 4 deletions testing/_py/test_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from py import error
from py.path import local

import _pytest.timing
import pytest


Expand Down Expand Up @@ -738,7 +739,6 @@ def test_dump(self, tmpdir, bin):

def test_setmtime(self):
import tempfile
import time

try:
fd, name = tempfile.mkstemp()
Expand All @@ -747,7 +747,7 @@ def test_setmtime(self):
name = tempfile.mktemp()
open(name, "w").close()
try:
mtime = int(time.time()) - 100
mtime = int(_pytest.timing.time()) - 100
path = local(name)
assert path.mtime() != mtime
path.setmtime(mtime)
Expand Down Expand Up @@ -1405,15 +1405,15 @@ def test_atime(self, tmpdir):
import time

path = tmpdir.ensure("samplefile")
now = time.time()
now = _pytest.timing.perf_counter()
atime1 = path.atime()
# we could wait here but timer resolution is very
# system dependent
path.read_binary()
time.sleep(ATIME_RESOLUTION)
atime2 = path.atime()
time.sleep(ATIME_RESOLUTION)
duration = time.time() - now
duration = _pytest.timing.perf_counter() - now
assert (atime2 - atime1) <= duration

def test_commondir(self, path1):
Expand Down
6 changes: 3 additions & 3 deletions testing/test_pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import os
import subprocess
import sys
import time
from types import ModuleType

from _pytest.config import ExitCode
Expand All @@ -16,6 +15,7 @@
from _pytest.pytester import Pytester
from _pytest.pytester import SysModulesSnapshot
from _pytest.pytester import SysPathsSnapshot
import _pytest.timing
import pytest


Expand Down Expand Up @@ -451,9 +451,9 @@ def test_pytester_run_with_timeout(pytester: Pytester) -> None:

timeout = 120

start = time.time()
start = _pytest.timing.perf_counter()
result = pytester.runpytest_subprocess(testfile, timeout=timeout)
end = time.time()
end = _pytest.timing.perf_counter()
duration = end - start

assert result.ret == ExitCode.OK
Expand Down