Skip to content

Commit a983559

Browse files
committed
Resolve BENCH_DIR at call time in the benchmark runner's main()
discover_benchmarks() goes out of its way to avoid def-time binding, and says so: # Resolve the default inside the call so tests (and embedders) can # monkeypatch ``BENCH_DIR`` at the module level - Python binds default # args at def-time, so a literal default would ignore later patches. if bench_dir is None: bench_dir = BENCH_DIR main() then reintroduces exactly that binding: def main( *, bench_dir: Path = BENCH_DIR, default_output: Path = DEFAULT_OUTPUT, ... registry = discover_benchmarks(bench_dir=bench_dir, ...) Because main() always passes a non-None bench_dir down, the sentinel branch in discover_benchmarks() can never be taken on this path, and patching runner.main.BENCH_DIR - the documented mechanism - has no effect on main(). Same for DEFAULT_OUTPUT. run_pyperf.py calls main() with no arguments, so this is the production path. The existing tests patch BENCH_DIR and call discover_benchmarks() directly, which is why the gap is invisible today. Apply the same sentinel to both parameters. Explicit arguments keep working unchanged, so the embedder API is unaffected. Adds test_main_honors_a_monkeypatched_bench_dir, which patches BENCH_DIR to a tmp dir holding one bench_*.py and drives main() with --list. It fails before this change (main() lists the repo's real benchmarks instead).
1 parent 3bd069a commit a983559

2 files changed

Lines changed: 30 additions & 2 deletions

File tree

benchmarks/cuda_bindings/runner/main.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,18 @@ def parse_args(argv: list[str], default_output: Path = DEFAULT_OUTPUT) -> tuple[
232232

233233
def main(
234234
*,
235-
bench_dir: Path = BENCH_DIR,
236-
default_output: Path = DEFAULT_OUTPUT,
235+
bench_dir: Path | None = None,
236+
default_output: Path | None = None,
237237
module_name_prefix: str = DEFAULT_MODULE_NAME_PREFIX,
238238
bench_filter_env_var: str = DEFAULT_BENCH_FILTER_ENV_VAR,
239239
) -> None:
240+
# Resolve the defaults inside the call, for the same reason
241+
# discover_benchmarks() does: a literal default would be bound at def-time
242+
# and would ignore a later monkeypatch of the module-level constant.
243+
if bench_dir is None:
244+
bench_dir = BENCH_DIR
245+
if default_output is None:
246+
default_output = DEFAULT_OUTPUT
240247
parsed, remaining_argv = parse_args(sys.argv[1:], default_output=default_output)
241248

242249
registry = discover_benchmarks(bench_dir=bench_dir, module_name_prefix=module_name_prefix)

benchmarks/cuda_bindings/tests/test_runner.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,24 @@ def test_bench_launch_initializes_on_first_use(monkeypatch):
164164

165165
assert len(compile_calls) == 1
166166
assert len(launch_calls) == 2
167+
168+
169+
def test_main_honors_a_monkeypatched_bench_dir(monkeypatch, tmp_path, capsys):
170+
"""main() must resolve BENCH_DIR at call time, like discover_benchmarks() does.
171+
172+
A literal default would be bound at def-time and would silently ignore a
173+
later patch of the module-level constant.
174+
"""
175+
runner_main = load_runner_main(monkeypatch)
176+
177+
(tmp_path / "bench_patched.py").write_text(
178+
"def bench_only_here(loops: int) -> float:\n return loops + 0.5\n",
179+
encoding="utf-8",
180+
)
181+
monkeypatch.setattr(runner_main, "BENCH_DIR", tmp_path)
182+
runner_main._MODULE_CACHE.clear()
183+
monkeypatch.setattr(sys, "argv", ["run_pyperf.py", "--list"])
184+
185+
runner_main.main()
186+
187+
assert capsys.readouterr().out.split() == ["patched.only_here"]

0 commit comments

Comments
 (0)