Skip to content

Commit 0cf9cd9

Browse files
committed
Code review requests
1 parent a79e1b7 commit 0cf9cd9

File tree

7 files changed

+63
-53
lines changed

7 files changed

+63
-53
lines changed

src/_pytest/junitxml.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
from __future__ import annotations
1212

1313
from collections.abc import Callable
14-
from datetime import datetime
15-
from datetime import timezone
1614
import functools
1715
import os
1816
import platform
@@ -636,15 +634,15 @@ def pytest_internalerror(self, excrepr: ExceptionRepr) -> None:
636634
reporter._add_simple("error", "internal error", str(excrepr))
637635

638636
def pytest_sessionstart(self) -> None:
639-
self.suite_start_instant = timing.Instant()
637+
self.suite_start = timing.Instant()
640638

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

646644
with open(self.logfile, "w", encoding="utf-8") as logfile:
647-
suite_time_delta = self.suite_start_instant.elapsed_s()
645+
duration = self.suite_start.duration()
648646

649647
numtests = (
650648
self.stats["passed"]
@@ -662,12 +660,8 @@ def pytest_sessionfinish(self) -> None:
662660
failures=str(self.stats["failure"]),
663661
skipped=str(self.stats["skipped"]),
664662
tests=str(numtests),
665-
time=f"{suite_time_delta:.3f}",
666-
timestamp=datetime.fromtimestamp(
667-
self.suite_start_instant.interval()[0], timezone.utc
668-
)
669-
.astimezone()
670-
.isoformat(),
663+
time=f"{duration.elapsed_s:.3f}",
664+
timestamp=duration.start_utc().astimezone().isoformat(),
671665
hostname=platform.node(),
672666
)
673667
global_properties = self._get_global_properties_node()

src/_pytest/pytester.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,7 +1150,7 @@ def runpytest_inprocess(
11501150

11511151
if syspathinsert:
11521152
self.syspathinsert()
1153-
now = timing.Instant()
1153+
instant = timing.Instant()
11541154
capture = _get_multicapture("sys")
11551155
capture.start_capturing()
11561156
try:
@@ -1179,7 +1179,9 @@ class reprec: # type: ignore
11791179
sys.stderr.write(err)
11801180

11811181
assert reprec.ret is not None
1182-
res = RunResult(reprec.ret, out.splitlines(), err.splitlines(), now.elapsed_s())
1182+
res = RunResult(
1183+
reprec.ret, out.splitlines(), err.splitlines(), instant.duration().elapsed_s
1184+
)
11831185
res.reprec = reprec # type: ignore
11841186
return res
11851187

@@ -1406,7 +1408,7 @@ def run(
14061408
print(" in:", Path.cwd())
14071409

14081410
with p1.open("w", encoding="utf8") as f1, p2.open("w", encoding="utf8") as f2:
1409-
now = timing.Instant()
1411+
instant = timing.Instant()
14101412
popen = self.popen(
14111413
cmdargs,
14121414
stdin=stdin,
@@ -1443,7 +1445,7 @@ def handle_timeout() -> None:
14431445

14441446
with contextlib.suppress(ValueError):
14451447
ret = ExitCode(ret)
1446-
return RunResult(ret, out, err, now.elapsed_s())
1448+
return RunResult(ret, out, err, instant.duration().elapsed_s)
14471449

14481450
def _dump_lines(self, lines, fp):
14491451
try:

src/_pytest/runner.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -347,12 +347,11 @@ def from_call(
347347
if reraise is not None and isinstance(excinfo.value, reraise):
348348
raise
349349
result = None
350-
duration = instant.elapsed_s()
351-
start, stop = instant.interval()
350+
duration = instant.duration()
352351
return cls(
353-
start=start,
354-
stop=stop,
355-
duration=duration,
352+
start=duration.start,
353+
stop=duration.stop,
354+
duration=duration.elapsed_s,
356355
when=when,
357356
result=result,
358357
excinfo=excinfo,

src/_pytest/terminal.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -789,7 +789,7 @@ def report_collect(self, final: bool = False) -> None:
789789
if not final:
790790
# Only write the "collecting" report every `REPORT_COLLECTING_RESOLUTION`.
791791
if (
792-
self._collect_report_last_instant.elapsed_s()
792+
self._collect_report_last_instant.duration().elapsed_s
793793
< REPORT_COLLECTING_RESOLUTION
794794
):
795795
return
@@ -821,7 +821,7 @@ def report_collect(self, final: bool = False) -> None:
821821
@hookimpl(trylast=True)
822822
def pytest_sessionstart(self, session: Session) -> None:
823823
self._session = session
824-
self._session_start_instant = timing.Instant()
824+
self._session_start = timing.Instant()
825825
if not self.showheader:
826826
return
827827
self.write_sep("=", "test session starts", bold=True)
@@ -1200,7 +1200,7 @@ def summary_stats(self) -> None:
12001200
if self.verbosity < -1:
12011201
return
12021202

1203-
session_duration = self._session_start_instant.elapsed_s()
1203+
session_duration = self._session_start.duration()
12041204
(parts, main_color) = self.build_summary_stats_line()
12051205
line_parts = []
12061206

@@ -1215,7 +1215,7 @@ def summary_stats(self) -> None:
12151215
msg = ", ".join(line_parts)
12161216

12171217
main_markup = {main_color: True}
1218-
duration = f" in {format_session_duration(session_duration)}"
1218+
duration = f" in {format_session_duration(session_duration.elapsed_s)}"
12191219
duration_with_markup = self._tw.markup(duration, **main_markup)
12201220
if display_sep:
12211221
fullwidth += len(duration_with_markup) - len(duration)

src/_pytest/timing.py

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import dataclasses
1212
from datetime import datetime
13+
from datetime import timezone
1314
from time import perf_counter
1415
from time import sleep
1516
from time import time
@@ -20,36 +21,49 @@
2021
from pytest import MonkeyPatch
2122

2223

24+
@dataclasses.dataclass(frozen=True)
2325
class Instant:
2426
"""
25-
Provides a measurement of timing between different points in the code.
26-
27-
Useful to compute the elapsed time between two points in time,
28-
using a performance counter, and the beginning/end in actual times (as seconds since epoch).
27+
Measures timing between different points in the code.
2928
3029
Inspired by Rust's `std::time::Instant`.
3130
"""
3231

33-
def __init__(self) -> None:
34-
import _pytest.timing # noqa: PLW0406
35-
36-
self._start_perf = _pytest.timing.perf_counter()
37-
self._start_time = _pytest.timing.time()
38-
39-
def elapsed_s(self) -> float:
40-
"""Return the elapsed time (in seconds) since this Instant was created, using a precise clock."""
41-
import _pytest.timing # noqa: PLW0406
42-
43-
return _pytest.timing.perf_counter() - self._start_perf
44-
45-
def interval(self) -> tuple[float, float]:
46-
"""Return the beginning and end times of this instant, in seconds since epoch, as provided by time.time()."""
47-
import _pytest.timing # noqa: PLW0406
48-
49-
return self._start_time, _pytest.timing.time()
50-
51-
def __repr__(self) -> str: # pragma: no cover
52-
return f"Instant(start_time={self._start_time}, elapsed_s={self.elapsed_s()})"
32+
# Creation time of this instant, using time.time(), to measure actual time.
33+
# Use a `lambda` to initialize the default to correctly get the mocked time via `MockTiming`.
34+
_start_time: float = dataclasses.field(default_factory=lambda: time())
35+
36+
# Initial "tick" of the performance counter to measure precise elapsed time.
37+
# Use a `lambda` to initialize the default to correctly get the mocked time via `MockTiming`.
38+
_start_perf: float = dataclasses.field(default_factory=lambda: perf_counter())
39+
40+
def duration(self) -> Duration:
41+
"""Measure the duration since `Instant` was created."""
42+
return Duration(
43+
start=self._start_time,
44+
elapsed_s=perf_counter() - self._start_perf,
45+
stop=time(),
46+
)
47+
48+
49+
@dataclasses.dataclass(frozen=True)
50+
class Duration:
51+
"""A slice of time as measured by `Instant.duration()`."""
52+
53+
# Start time of the duration, as seconds since epoch.
54+
start: float
55+
# Stop time of the duration, as seconds since epoch.
56+
stop: float
57+
# Elapsed time of the duration, in seconds, measured using a performance counter for precise timing.
58+
elapsed_s: float
59+
60+
def start_utc(self) -> datetime:
61+
"""Start time of the duration, as a datetime in UTC."""
62+
return datetime.fromtimestamp(self.start, timezone.utc)
63+
64+
def stop_utc(self) -> datetime: # pragma: no cover
65+
"""Stop time of the duration, as a datetime in UTC."""
66+
return datetime.fromtimestamp(self.stop, timezone.utc)
5367

5468

5569
@dataclasses.dataclass

testing/_py/test_local.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
from py import error
1313
from py.path import local
1414

15-
import _pytest.timing
1615
import pytest
1716

1817

@@ -747,7 +746,8 @@ def test_setmtime(self):
747746
name = tempfile.mktemp()
748747
open(name, "w").close()
749748
try:
750-
mtime = int(_pytest.timing.time()) - 100
749+
# Do not use _pytest.timing here, as we do not want time mocking to affect this test.
750+
mtime = int(time.time()) - 100
751751
path = local(name)
752752
assert path.mtime() != mtime
753753
path.setmtime(mtime)
@@ -1405,15 +1405,16 @@ def test_atime(self, tmpdir):
14051405
import time
14061406

14071407
path = tmpdir.ensure("samplefile")
1408-
now = _pytest.timing.Instant()
1408+
# Do not use _pytest.timing here, as we do not want time mocking to affect this test.
1409+
now = time.time()
14091410
atime1 = path.atime()
14101411
# we could wait here but timer resolution is very
14111412
# system dependent
14121413
path.read_binary()
14131414
time.sleep(ATIME_RESOLUTION)
14141415
atime2 = path.atime()
14151416
time.sleep(ATIME_RESOLUTION)
1416-
duration = now.elapsed_s()
1417+
duration = time.time() - now
14171418
assert (atime2 - atime1) <= duration
14181419

14191420
def test_commondir(self, path1):

testing/test_pytester.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -453,10 +453,10 @@ def test_pytester_run_with_timeout(pytester: Pytester) -> None:
453453

454454
instant = _pytest.timing.Instant()
455455
result = pytester.runpytest_subprocess(testfile, timeout=timeout)
456-
duration = instant.elapsed_s()
456+
duration = instant.duration()
457457

458458
assert result.ret == ExitCode.OK
459-
assert duration < timeout
459+
assert duration.elapsed_s < timeout
460460

461461

462462
def test_pytester_run_timeout_expires(pytester: Pytester) -> None:

0 commit comments

Comments
 (0)