Skip to content

Commit 7cb2614

Browse files
authored
fix: include time on longer runs (#1014)
* fix: include time on longer runs Signed-off-by: Henry Schreiner <[email protected]> * fix: include time on final summary, too Signed-off-by: Henry Schreiner <[email protected]> --------- Signed-off-by: Henry Schreiner <[email protected]>
1 parent 4eb0037 commit 7cb2614

File tree

7 files changed

+73
-15
lines changed

7 files changed

+73
-15
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ repos:
4242
- attrs
4343
- colorlog
4444
- dependency-groups>=1.2
45+
- humanize
4546
- jinja2
4647
- orjson # Faster mypy
4748
- packaging

nox/sessions.py

Lines changed: 61 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from __future__ import annotations
1616

1717
import contextlib
18+
import datetime
1819
import enum
1920
import hashlib
2021
import os
@@ -23,13 +24,16 @@
2324
import shutil
2425
import subprocess
2526
import sys
27+
import time
2628
import unicodedata
2729
from typing import (
2830
TYPE_CHECKING,
2931
Any,
3032
NoReturn,
3133
)
3234

35+
import humanize
36+
3337
import nox.command
3438
import nox.virtualenv
3539
from nox.logger import logger
@@ -1100,9 +1104,11 @@ def execute(self) -> Result:
11001104
f"Prerequisite session {dependency.friendly_name} was not"
11011105
" successful"
11021106
),
1107+
duration=0,
11031108
)
11041109
return self.result
11051110

1111+
start = time.perf_counter()
11061112
try:
11071113
cwd = os.path.realpath(os.path.dirname(self.global_config.noxfile))
11081114

@@ -1113,42 +1119,83 @@ def execute(self) -> Result:
11131119
self.func(session)
11141120

11151121
# Nothing went wrong; return a success.
1116-
self.result = Result(self, Status.SUCCESS)
1122+
self.result = Result(
1123+
self, Status.SUCCESS, duration=time.perf_counter() - start
1124+
)
11171125

11181126
except nox.virtualenv.InterpreterNotFound as exc:
11191127
if self.global_config.error_on_missing_interpreters:
1120-
self.result = Result(self, Status.FAILED, reason=str(exc))
1128+
self.result = Result(
1129+
self,
1130+
Status.FAILED,
1131+
reason=str(exc),
1132+
duration=time.perf_counter() - start,
1133+
)
11211134
else:
11221135
logger.warning(
11231136
"Missing interpreters will error by default on CI systems."
11241137
)
1125-
self.result = Result(self, Status.SKIPPED, reason=str(exc))
1138+
self.result = Result(
1139+
self,
1140+
Status.SKIPPED,
1141+
reason=str(exc),
1142+
duration=time.perf_counter() - start,
1143+
)
11261144

11271145
except _SessionQuit as exc:
1128-
self.result = Result(self, Status.ABORTED, reason=str(exc))
1146+
self.result = Result(
1147+
self,
1148+
Status.ABORTED,
1149+
reason=str(exc),
1150+
duration=time.perf_counter() - start,
1151+
)
11291152

11301153
except _SessionSkip as exc:
1131-
self.result = Result(self, Status.SKIPPED, reason=str(exc))
1154+
self.result = Result(
1155+
self,
1156+
Status.SKIPPED,
1157+
reason=str(exc),
1158+
duration=time.perf_counter() - start,
1159+
)
11321160

11331161
except nox.command.CommandFailed:
1134-
self.result = Result(self, Status.FAILED)
1162+
self.result = Result(
1163+
self, Status.FAILED, duration=time.perf_counter() - start
1164+
)
11351165

11361166
except KeyboardInterrupt:
11371167
logger.error(f"Session {self.friendly_name} interrupted.")
11381168
raise
11391169

11401170
except Exception as exc:
11411171
logger.exception(f"Session {self.friendly_name} raised exception {exc!r}")
1142-
self.result = Result(self, Status.FAILED)
1172+
self.result = Result(
1173+
self, Status.FAILED, duration=time.perf_counter() - start
1174+
)
11431175

11441176
return self.result
11451177

11461178

1179+
def _duration_str(seconds: float, text: str) -> str:
1180+
time_str = humanize.naturaldelta(datetime.timedelta(seconds=seconds))
1181+
1182+
# Might be "a moment" if short, return empty string in that case
1183+
if time_str == "a moment":
1184+
return ""
1185+
1186+
return text.format(time=time_str)
1187+
1188+
11471189
class Result:
11481190
"""An object representing the result of a session."""
11491191

11501192
def __init__(
1151-
self, session: SessionRunner, status: Status, reason: str | None = None
1193+
self,
1194+
session: SessionRunner,
1195+
status: Status,
1196+
reason: str | None = None,
1197+
*,
1198+
duration: float = 0.0,
11521199
) -> None:
11531200
"""Initialize the Result object.
11541201
@@ -1157,10 +1204,12 @@ def __init__(
11571204
The session runner which ran.
11581205
status (~nox.sessions.Status): The final result status.
11591206
reason (str): Additional info.
1207+
duration (float): Time taken in seconds.
11601208
"""
11611209
self.session = session
11621210
self.status = status
11631211
self.reason = reason
1212+
self.duration = duration
11641213

11651214
def __bool__(self) -> bool:
11661215
return self.status.value > 0
@@ -1173,12 +1222,12 @@ def imperfect(self) -> str:
11731222
str: A word or phrase representing the status.
11741223
"""
11751224
if self.status == Status.SUCCESS:
1176-
return "was successful"
1225+
return "was successful" + _duration_str(self.duration, " in {time}")
11771226

11781227
status = self.status.name.lower()
11791228
if self.reason:
1180-
return f"{status}: {self.reason}"
1181-
1229+
duration_err = _duration_str(self.duration, " (took {time})")
1230+
return f"{status}: {self.reason}{duration_err}"
11821231
return status
11831232

11841233
def log(self, message: str) -> None:
@@ -1208,4 +1257,5 @@ def serialize(self) -> dict[str, Any]:
12081257
"result": self.status.name.lower(),
12091258
"result_code": self.status.value,
12101259
"signatures": self.session.signatures,
1260+
"duration": self.duration,
12111261
}

nox/tasks.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from nox._version import InvalidVersionSpecifier, VersionCheckFailed, check_nox_version
3030
from nox.logger import logger
3131
from nox.manifest import WARN_PYTHONS_IGNORED, Manifest
32-
from nox.sessions import Result, Status
32+
from nox.sessions import Result, Status, _duration_str
3333

3434
if TYPE_CHECKING:
3535
import types
@@ -408,14 +408,17 @@ def print_summary(
408408

409409
# Iterate over the results and print the result for each in a
410410
# human-readable way.
411-
logger.session_info("Ran multiple sessions:")
411+
total_duration = sum(result.duration for result in results)
412+
duration_str = _duration_str(total_duration, " in {time}")
413+
logger.session_info(f"Ran {len(results)} sessions{duration_str}:")
412414
for result in results:
413415
name = result.session.friendly_name
414416
status = result.status.name.lower()
417+
duration_str = _duration_str(result.duration, ", took {time}")
415418
if result.status is Status.SKIPPED and result.reason:
416-
result.log(f"* {name}: {status} ({result.reason})")
419+
result.log(f"* {name}: {status} ({result.reason}){duration_str}")
417420
else:
418-
result.log(f"* {name}: {status}")
421+
result.log(f"* {name}: {status}{duration_str}")
419422

420423
# Return the results that were sent to this function.
421424
return results

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ dependencies = [
4343
"attrs>=24.1",
4444
"colorlog>=2.6.1,<7",
4545
"dependency-groups>=1.1",
46+
"humanize>=4",
4647
"packaging>=20.9; python_version<'3.10'",
4748
"packaging>=21; python_version>='3.10'",
4849
"tomli>=1.1; python_version<'3.11'",

requirements-conda-test.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ attrs >=23.1
33
colorlog >=2.6.1,<7.0.0
44
dependency-groups >=1.1
55
httpx
6+
humanize
67
jinja2
78
pbs-installer>=2025.1.6
89
pytest

tests/test__cli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def test_get_dependencies() -> None:
3434
"attrs",
3535
"colorlog",
3636
"dependency-groups",
37+
"humanize",
3738
"jinja2",
3839
"nox",
3940
"packaging",

tests/test_tasks.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,7 @@ def test_create_report() -> None:
773773
"result": "success",
774774
"result_code": 1,
775775
"args": {},
776+
"duration": 0.0,
776777
}
777778
],
778779
},

0 commit comments

Comments
 (0)