From a596c390fc8bcde5911c025e4fb5b3238f77a0ee Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Mon, 15 Dec 2025 22:58:38 +0000 Subject: [PATCH 01/23] Start async benchmark --- src/pytest_codspeed/plugin.py | 89 ++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 18 deletions(-) diff --git a/src/pytest_codspeed/plugin.py b/src/pytest_codspeed/plugin.py index 0d2853b..11fa56e 100644 --- a/src/pytest_codspeed/plugin.py +++ b/src/pytest_codspeed/plugin.py @@ -5,10 +5,13 @@ import json import os import random +from collections.abc import AsyncIterator +from contextlib import contextmanager from dataclasses import dataclass, field +from inspect import isawaitable from pathlib import Path from time import time -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING, cast, overload import pytest from _pytest.fixtures import FixtureManager @@ -232,35 +235,85 @@ def pytest_collection_modifyitems( items[:] = selected -def _measure( - plugin: CodSpeedPlugin, - node: pytest.Item, - config: pytest.Config, - pedantic_options: PedanticOptions | None, - fn: Callable[..., T], - args: tuple[Any, ...], - kwargs: dict[str, Any], -) -> T: +@contextmanager +def _measure_context(node: pytest.Item) -> AsyncIterator[None] marker_options = BenchmarkMarkerOptions.from_pytest_item(node) random.seed(0) is_gc_enabled = gc.isenabled() if is_gc_enabled: gc.collect() gc.disable() - try: - uri, name = get_git_relative_uri_and_name(node.nodeid, config.rootpath) + + yield + + # Ensure GC is re-enabled even if the test failed + if is_gc_enabled: + gc.enable() + + +async def _async_measure( + plugin: CodSpeedPlugin, + node: pytest.Item, + pedantic_options: PedanticOptions | None, + fn: Awaitable[T], + args: tuple[Any, ...], + kwargs: dict[str, Any], +) -> T: + with _measure_context(node): if pedantic_options is None: - return plugin.instrument.measure( + return await plugin.instrument.measure_async( marker_options, name, uri, fn, *args, **kwargs ) else: - return plugin.instrument.measure_pedantic( + return await plugin.instrument.measure_pedantic_async( marker_options, pedantic_options, name, uri ) - finally: - # Ensure GC is re-enabled even if the test failed - if is_gc_enabled: - gc.enable() + + +@overload +def _measure( + plugin: CodSpeedPlugin, + node: pytest.Item, + config: pytest.Config, + pedantic_options: PedanticOptions | None, + fn: Awaitable[T], + args: tuple[Any, ...], + kwargs: dict[str, Any], +) -> Awaitable[T]: + ... +@overload +def _measure( + plugin: CodSpeedPlugin, + node: pytest.Item, + config: pytest.Config, + pedantic_options: PedanticOptions | None, + fn: Callable[..., T], + args: tuple[Any, ...], + kwargs: dict[str, Any], +) -> T: + ... +def _measure( + plugin: CodSpeedPlugin, + node: pytest.Item, + config: pytest.Config, + pedantic_options: PedanticOptions | None, + fn: Callable[..., T] | Awaitable[T], + args: tuple[Any, ...], + kwargs: dict[str, Any], +) -> T | Awaitable[T]: + uri, name = get_git_relative_uri_and_name(node.nodeid, config.rootpath) + if isawaitable(fn): + return _async_measure(plugin, node, pedantic_options, fn, args, kwargs) + else: + with _measure_context(node): + if pedantic_options is None: + return plugin.instrument.measure( + marker_options, name, uri, fn, *args, **kwargs + ) + else: + return plugin.instrument.measure_pedantic( + marker_options, pedantic_options, name, uri + ) def wrap_runtest( From 6a9950e0e9f80a38e09bd81fc0f1aa32284ea478 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Mon, 15 Dec 2025 23:00:45 +0000 Subject: [PATCH 02/23] Update plugin.py --- src/pytest_codspeed/plugin.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pytest_codspeed/plugin.py b/src/pytest_codspeed/plugin.py index 11fa56e..5afb914 100644 --- a/src/pytest_codspeed/plugin.py +++ b/src/pytest_codspeed/plugin.py @@ -244,11 +244,12 @@ def _measure_context(node: pytest.Item) -> AsyncIterator[None] gc.collect() gc.disable() - yield - - # Ensure GC is re-enabled even if the test failed - if is_gc_enabled: - gc.enable() + try: + yield + finally: + # Ensure GC is re-enabled even if the test failed + if is_gc_enabled: + gc.enable() async def _async_measure( From 6ed79b28e924af7ac6af42f4cd69718deb7e5926 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Mon, 15 Dec 2025 23:09:58 +0000 Subject: [PATCH 03/23] Update plugin.py --- src/pytest_codspeed/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pytest_codspeed/plugin.py b/src/pytest_codspeed/plugin.py index 5afb914..ab3d422 100644 --- a/src/pytest_codspeed/plugin.py +++ b/src/pytest_codspeed/plugin.py @@ -236,7 +236,7 @@ def pytest_collection_modifyitems( @contextmanager -def _measure_context(node: pytest.Item) -> AsyncIterator[None] +def _measure_context(node: pytest.Item) -> AsyncIterator[None]: marker_options = BenchmarkMarkerOptions.from_pytest_item(node) random.seed(0) is_gc_enabled = gc.isenabled() From 3fb2ed1b182c6f347edbc1f00c2fc115978c7007 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Mon, 15 Dec 2025 23:13:15 +0000 Subject: [PATCH 04/23] Update plugin.py --- src/pytest_codspeed/plugin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pytest_codspeed/plugin.py b/src/pytest_codspeed/plugin.py index ab3d422..e8456b1 100644 --- a/src/pytest_codspeed/plugin.py +++ b/src/pytest_codspeed/plugin.py @@ -237,7 +237,6 @@ def pytest_collection_modifyitems( @contextmanager def _measure_context(node: pytest.Item) -> AsyncIterator[None]: - marker_options = BenchmarkMarkerOptions.from_pytest_item(node) random.seed(0) is_gc_enabled = gc.isenabled() if is_gc_enabled: @@ -255,6 +254,7 @@ def _measure_context(node: pytest.Item) -> AsyncIterator[None]: async def _async_measure( plugin: CodSpeedPlugin, node: pytest.Item, + marker_options: BenchmarkMarkerOptions, pedantic_options: PedanticOptions | None, fn: Awaitable[T], args: tuple[Any, ...], @@ -302,9 +302,10 @@ def _measure( args: tuple[Any, ...], kwargs: dict[str, Any], ) -> T | Awaitable[T]: + marker_options = BenchmarkMarkerOptions.from_pytest_item(node) uri, name = get_git_relative_uri_and_name(node.nodeid, config.rootpath) if isawaitable(fn): - return _async_measure(plugin, node, pedantic_options, fn, args, kwargs) + return _async_measure(plugin, node, marker_options, pedantic_options, fn, args, kwargs) else: with _measure_context(node): if pedantic_options is None: From b19ae85c953f3f9d2cfdefd4432d8e5cbaa9134f Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Mon, 15 Dec 2025 23:14:19 +0000 Subject: [PATCH 05/23] Update plugin.py --- src/pytest_codspeed/plugin.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pytest_codspeed/plugin.py b/src/pytest_codspeed/plugin.py index e8456b1..4d7ce1e 100644 --- a/src/pytest_codspeed/plugin.py +++ b/src/pytest_codspeed/plugin.py @@ -236,7 +236,7 @@ def pytest_collection_modifyitems( @contextmanager -def _measure_context(node: pytest.Item) -> AsyncIterator[None]: +def _measure_context() -> AsyncIterator[None]: random.seed(0) is_gc_enabled = gc.isenabled() if is_gc_enabled: @@ -253,14 +253,13 @@ def _measure_context(node: pytest.Item) -> AsyncIterator[None]: async def _async_measure( plugin: CodSpeedPlugin, - node: pytest.Item, marker_options: BenchmarkMarkerOptions, pedantic_options: PedanticOptions | None, fn: Awaitable[T], args: tuple[Any, ...], kwargs: dict[str, Any], ) -> T: - with _measure_context(node): + with _measure_context(): if pedantic_options is None: return await plugin.instrument.measure_async( marker_options, name, uri, fn, *args, **kwargs @@ -305,7 +304,7 @@ def _measure( marker_options = BenchmarkMarkerOptions.from_pytest_item(node) uri, name = get_git_relative_uri_and_name(node.nodeid, config.rootpath) if isawaitable(fn): - return _async_measure(plugin, node, marker_options, pedantic_options, fn, args, kwargs) + return _async_measure(plugin, marker_options, pedantic_options, fn, args, kwargs) else: with _measure_context(node): if pedantic_options is None: From 67524ff0422c8b62a69de9ec736cc08af8a04f33 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Mon, 15 Dec 2025 23:15:11 +0000 Subject: [PATCH 06/23] Update plugin.py --- src/pytest_codspeed/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pytest_codspeed/plugin.py b/src/pytest_codspeed/plugin.py index 4d7ce1e..83e1758 100644 --- a/src/pytest_codspeed/plugin.py +++ b/src/pytest_codspeed/plugin.py @@ -306,7 +306,7 @@ def _measure( if isawaitable(fn): return _async_measure(plugin, marker_options, pedantic_options, fn, args, kwargs) else: - with _measure_context(node): + with _measure_context(): if pedantic_options is None: return plugin.instrument.measure( marker_options, name, uri, fn, *args, **kwargs From b3fdba1ff2265da494c862c3c117b77345948c27 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sun, 21 Dec 2025 01:15:22 +0000 Subject: [PATCH 07/23] Update valgrind.py --- src/pytest_codspeed/instruments/valgrind.py | 107 +++++++++++++++----- 1 file changed, 81 insertions(+), 26 deletions(-) diff --git a/src/pytest_codspeed/instruments/valgrind.py b/src/pytest_codspeed/instruments/valgrind.py index b6667f8..9d25174 100644 --- a/src/pytest_codspeed/instruments/valgrind.py +++ b/src/pytest_codspeed/instruments/valgrind.py @@ -2,6 +2,8 @@ import os import warnings +from collections.abc import Awaitable +from contextlib import contextmanager from typing import TYPE_CHECKING from pytest_codspeed import __semver_version__ @@ -52,6 +54,24 @@ def get_instrument_config_str_and_warns(self) -> tuple[str, list[str]]: ) return config, warnings + @contextmanager + def _measure_context(self, uri: str): + self.benchmark_count += 1 + + if not self.instrument_hooks: + yield + return + + # Manually call the library function to avoid an extra stack frame. Also + # call the callgrind markers directly to avoid extra overhead. + self.instrument_hooks.lib.callgrind_start_instrumentation() + try: + yield + finally: + # Ensure instrumentation is stopped even if the test failed + self.instrument_hooks.lib.callgrind_stop_instrumentation() + self.instrument_hooks.set_executed_benchmark(uri) + def measure( self, marker_options: BenchmarkMarkerOptions, @@ -61,11 +81,6 @@ def measure( *args: P.args, **kwargs: P.kwargs, ) -> T: - self.benchmark_count += 1 - - if not self.instrument_hooks: - return fn(*args, **kwargs) - def __codspeed_root_frame__() -> T: return fn(*args, **kwargs) @@ -73,22 +88,31 @@ def __codspeed_root_frame__() -> T: # Warmup CPython performance map cache __codspeed_root_frame__() - # Manually call the library function to avoid an extra stack frame. Also - # call the callgrind markers directly to avoid extra overhead. - self.instrument_hooks.lib.callgrind_start_instrumentation() - try: + with self._measure_context(uri): return __codspeed_root_frame__() - finally: - # Ensure instrumentation is stopped even if the test failed - self.instrument_hooks.lib.callgrind_stop_instrumentation() - self.instrument_hooks.set_executed_benchmark(uri) - def measure_pedantic( + async def measure_async( self, marker_options: BenchmarkMarkerOptions, - pedantic_options: PedanticOptions[T], name: str, uri: str, + fn: Callable[P, Awaitable[T]], + *args: P.args, + **kwargs: P.kwargs, + ) -> T: + async def __codspeed_root_frame__() -> T: + return await fn(*args, **kwargs) + + if SUPPORTS_PERF_TRAMPOLINE: + # Warmup CPython performance map cache + await __codspeed_root_frame__() + + with self._measure_context(uri): + return await __codspeed_root_frame__() + + @contextmanager + def _measure_pedantic_context( + self, pedantic_options: PedanticOptions[T], uri: str, ) -> T: if pedantic_options.rounds != 1 or pedantic_options.iterations != 1: warnings.warn( @@ -97,11 +121,29 @@ def measure_pedantic( ) if not self.instrument_hooks: args, kwargs = pedantic_options.setup_and_get_args_kwargs() - out = pedantic_options.target(*args, **kwargs) + yield + if pedantic_options.teardown is not None: + pedantic_options.teardown(*args, **kwargs) + return + + # Compute the actual result of the function + args, kwargs = pedantic_options.setup_and_get_args_kwargs() + self.instrument_hooks.lib.callgrind_start_instrumentation() + try: + yield + finally: + self.instrument_hooks.lib.callgrind_stop_instrumentation() + self.instrument_hooks.set_executed_benchmark(uri) if pedantic_options.teardown is not None: pedantic_options.teardown(*args, **kwargs) - return out + def measure_pedantic( + self, + marker_options: BenchmarkMarkerOptions, + pedantic_options: PedanticOptions[T], + name: str, + uri: str, + ) -> T: def __codspeed_root_frame__(*args, **kwargs) -> T: return pedantic_options.target(*args, **kwargs) @@ -115,18 +157,31 @@ def __codspeed_root_frame__(*args, **kwargs) -> T: if pedantic_options.teardown is not None: pedantic_options.teardown(*args, **kwargs) - # Compute the actual result of the function - args, kwargs = pedantic_options.setup_and_get_args_kwargs() - self.instrument_hooks.lib.callgrind_start_instrumentation() - try: - out = __codspeed_root_frame__(*args, **kwargs) - finally: - self.instrument_hooks.lib.callgrind_stop_instrumentation() - self.instrument_hooks.set_executed_benchmark(uri) + with self._measure_pedantic_context(pedantic_options, uri): + return __codspeed_root_frame__(*args, **kwargs) + + async def measure_pedantic_async( + self, + marker_options: BenchmarkMarkerOptions, + pedantic_options: PedanticOptions[T], + name: str, + uri: str, + ) -> T: + async def __codspeed_root_frame__(*args, **kwargs) -> T: + return await pedantic_options.target(*args, **kwargs) + + # Warmup + warmup_rounds = max( + pedantic_options.warmup_rounds, 1 if SUPPORTS_PERF_TRAMPOLINE else 0 + ) + for _ in range(warmup_rounds): + args, kwargs = pedantic_options.setup_and_get_args_kwargs() + await __codspeed_root_frame__(*args, **kwargs) if pedantic_options.teardown is not None: pedantic_options.teardown(*args, **kwargs) - return out + with self._measure_pedantic_context(pedantic_options, uri): + return await __codspeed_root_frame__(*args, **kwargs) def report(self, session: Session) -> None: reporter = session.config.pluginmanager.get_plugin("terminalreporter") From faf14b471ecd5f07b21dcb0eb39014389270b6fc Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sun, 21 Dec 2025 01:38:57 +0000 Subject: [PATCH 08/23] Update walltime.py --- src/pytest_codspeed/instruments/walltime.py | 113 ++++++++++++++++---- 1 file changed, 90 insertions(+), 23 deletions(-) diff --git a/src/pytest_codspeed/instruments/walltime.py b/src/pytest_codspeed/instruments/walltime.py index f85f857..17ac0b7 100644 --- a/src/pytest_codspeed/instruments/walltime.py +++ b/src/pytest_codspeed/instruments/walltime.py @@ -2,6 +2,7 @@ import os import warnings +from collections.abc import Awaitable from dataclasses import asdict, dataclass from math import ceil from statistics import mean, quantiles, stdev @@ -182,31 +183,22 @@ def get_instrument_config_str_and_warns(self) -> tuple[str, list[str]]: ) return config_str, [] - def measure( + def _measure_iter( self, marker_options: BenchmarkMarkerOptions, name: str, uri: str, - fn: Callable[P, T], - *args: P.args, - **kwargs: P.kwargs, ) -> T: benchmark_config = BenchmarkConfig.from_codspeed_config_and_marker_data( self.config, marker_options ) - def __codspeed_root_frame__() -> T: - return fn(*args, **kwargs) - - # Compute the actual result of the function - out = __codspeed_root_frame__() - # Warmup times_per_round_ns: list[float] = [] warmup_start = start = perf_counter_ns() while True: start = perf_counter_ns() - __codspeed_root_frame__() + yield end = perf_counter_ns() times_per_round_ns.append(end - start) if end - warmup_start > benchmark_config.warmup_time_ns: @@ -236,7 +228,7 @@ def __codspeed_root_frame__() -> T: for _ in range(rounds): start = perf_counter_ns() for _ in iter_range: - __codspeed_root_frame__() + yield end = perf_counter_ns() times_per_round_ns.append(end - start) @@ -260,9 +252,48 @@ def __codspeed_root_frame__() -> T: self.benchmarks.append( Benchmark(name=name, uri=uri, config=benchmark_config, stats=stats) ) + + def measure( + self, + marker_options: BenchmarkMarkerOptions, + name: str, + uri: str, + fn: Callable[P, T], + *args: P.args, + **kwargs: P.kwargs, + ) -> T: + def __codspeed_root_frame__() -> T: + return fn(*args, **kwargs) + + # Compute the actual result of the function + out = __codspeed_root_frame__() + + for _ in self._measure_iter(marker_options, name, uri): + __codspeed_root_frame__() + + return out + + async def measure_async( + self, + marker_options: BenchmarkMarkerOptions, + name: str, + uri: str, + fn: Callable[P, Awaitable[T]], + *args: P.args, + **kwargs: P.kwargs, + ) -> T: + async def __codspeed_root_frame__() -> T: + return await fn(*args, **kwargs) + + # Compute the actual result of the function + out = await __codspeed_root_frame__() + + for _ in self._measure_iter(marker_options, name, uri): + await __codspeed_root_frame__() + return out - def measure_pedantic( # noqa: C901 + def _measure_pedantic_iter( # noqa: C901 self, marker_options: BenchmarkMarkerOptions, pedantic_options: PedanticOptions[T], @@ -273,16 +304,12 @@ def measure_pedantic( # noqa: C901 self.config, marker_options ) - def __codspeed_root_frame__(*args, **kwargs) -> T: - return pedantic_options.target(*args, **kwargs) - iter_range = range(pedantic_options.iterations) # Warmup for _ in range(pedantic_options.warmup_rounds): args, kwargs = pedantic_options.setup_and_get_args_kwargs() - for _ in iter_range: - __codspeed_root_frame__(*args, **kwargs) + yield iter_range, args, kwargs if pedantic_options.teardown is not None: pedantic_options.teardown(*args, **kwargs) @@ -294,8 +321,7 @@ def __codspeed_root_frame__(*args, **kwargs) -> T: for _ in range(pedantic_options.rounds): start = perf_counter_ns() args, kwargs = pedantic_options.setup_and_get_args_kwargs() - for _ in iter_range: - __codspeed_root_frame__(*args, **kwargs) + yield iter_range, args, kwargs end = perf_counter_ns() times_per_round_ns.append(end - start) if pedantic_options.teardown is not None: @@ -313,15 +339,56 @@ def __codspeed_root_frame__(*args, **kwargs) -> T: warmup_iters=pedantic_options.warmup_rounds, ) + self.benchmarks.append( + Benchmark(name=name, uri=uri, config=benchmark_config, stats=stats) + ) + + def measure_pedantic( + self, + marker_options: BenchmarkMarkerOptions, + pedantic_options: PedanticOptions[T], + name: str, + uri: str, + ) -> T: + def __codspeed_root_frame__(*args, **kwargs) -> T: + return pedantic_options.target(*args, **kwargs) + + for i, args, kwargs in self._measure_pedantic_iter( + marker_options, pedantic_options, name, uri + ): + for _ in i: + __codspeed_root_frame__(*args, **kwargs) + # Compute the actual result of the function args, kwargs = pedantic_options.setup_and_get_args_kwargs() out = __codspeed_root_frame__(*args, **kwargs) if pedantic_options.teardown is not None: pedantic_options.teardown(*args, **kwargs) - self.benchmarks.append( - Benchmark(name=name, uri=uri, config=benchmark_config, stats=stats) - ) + return out + + async def measure_pedantic_async( + self, + marker_options: BenchmarkMarkerOptions, + pedantic_options: PedanticOptions[T], + name: str, + uri: str, + ) -> T: + async def __codspeed_root_frame__(*args, **kwargs) -> T: + return await pedantic_options.target(*args, **kwargs) + + for i, args, kwargs in self._measure_pedantic_iter( + marker_options, pedantic_options, name, uri + ): + for _ in i: + await __codspeed_root_frame__(*args, **kwargs) + + # Compute the actual result of the function + args, kwargs = pedantic_options.setup_and_get_args_kwargs() + out = await __codspeed_root_frame__(*args, **kwargs) + if pedantic_options.teardown is not None: + pedantic_options.teardown(*args, **kwargs) + return out def report(self, session: Session) -> None: From 3b6ca4905eb72967fa4578af6e46a1a01c4161b6 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sun, 21 Dec 2025 01:45:37 +0000 Subject: [PATCH 09/23] Update plugin.py --- src/pytest_codspeed/plugin.py | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/src/pytest_codspeed/plugin.py b/src/pytest_codspeed/plugin.py index 83e1758..89f6cb9 100644 --- a/src/pytest_codspeed/plugin.py +++ b/src/pytest_codspeed/plugin.py @@ -8,10 +8,10 @@ from collections.abc import AsyncIterator from contextlib import contextmanager from dataclasses import dataclass, field -from inspect import isawaitable +from inspect import iscoroutinefunction from pathlib import Path from time import time -from typing import TYPE_CHECKING, cast, overload +from typing import TYPE_CHECKING, cast import pytest from _pytest.fixtures import FixtureManager @@ -270,18 +270,6 @@ async def _async_measure( ) -@overload -def _measure( - plugin: CodSpeedPlugin, - node: pytest.Item, - config: pytest.Config, - pedantic_options: PedanticOptions | None, - fn: Awaitable[T], - args: tuple[Any, ...], - kwargs: dict[str, Any], -) -> Awaitable[T]: - ... -@overload def _measure( plugin: CodSpeedPlugin, node: pytest.Item, @@ -291,19 +279,9 @@ def _measure( args: tuple[Any, ...], kwargs: dict[str, Any], ) -> T: - ... -def _measure( - plugin: CodSpeedPlugin, - node: pytest.Item, - config: pytest.Config, - pedantic_options: PedanticOptions | None, - fn: Callable[..., T] | Awaitable[T], - args: tuple[Any, ...], - kwargs: dict[str, Any], -) -> T | Awaitable[T]: marker_options = BenchmarkMarkerOptions.from_pytest_item(node) uri, name = get_git_relative_uri_and_name(node.nodeid, config.rootpath) - if isawaitable(fn): + if iscoroutinefunction(fn): return _async_measure(plugin, marker_options, pedantic_options, fn, args, kwargs) else: with _measure_context(): From 2efe5ed7423df0f4dbf41e4e1461c15d4516978b Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sun, 21 Dec 2025 01:47:43 +0000 Subject: [PATCH 10/23] Update valgrind.py --- src/pytest_codspeed/instruments/valgrind.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pytest_codspeed/instruments/valgrind.py b/src/pytest_codspeed/instruments/valgrind.py index 9d25174..78fd5e7 100644 --- a/src/pytest_codspeed/instruments/valgrind.py +++ b/src/pytest_codspeed/instruments/valgrind.py @@ -2,7 +2,6 @@ import os import warnings -from collections.abc import Awaitable from contextlib import contextmanager from typing import TYPE_CHECKING @@ -12,6 +11,7 @@ from pytest_codspeed.utils import SUPPORTS_PERF_TRAMPOLINE if TYPE_CHECKING: + from collections.abc import Awaitable from typing import Any, Callable from pytest import Session @@ -112,7 +112,9 @@ async def __codspeed_root_frame__() -> T: @contextmanager def _measure_pedantic_context( - self, pedantic_options: PedanticOptions[T], uri: str, + self, + pedantic_options: PedanticOptions[T], + uri: str, ) -> T: if pedantic_options.rounds != 1 or pedantic_options.iterations != 1: warnings.warn( From 4a8df61e6c3ac29adfac249644ca3420921ee1e2 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sun, 21 Dec 2025 01:51:33 +0000 Subject: [PATCH 11/23] Update plugin.py --- src/pytest_codspeed/plugin.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pytest_codspeed/plugin.py b/src/pytest_codspeed/plugin.py index 89f6cb9..7a1d597 100644 --- a/src/pytest_codspeed/plugin.py +++ b/src/pytest_codspeed/plugin.py @@ -33,6 +33,7 @@ from . import __version__ if TYPE_CHECKING: + from collections.abc import Awaitable from typing import Any, Callable, ParamSpec, TypeVar from pytest_codspeed.instruments import Instrument @@ -255,6 +256,8 @@ async def _async_measure( plugin: CodSpeedPlugin, marker_options: BenchmarkMarkerOptions, pedantic_options: PedanticOptions | None, + name: str, + uri: str, fn: Awaitable[T], args: tuple[Any, ...], kwargs: dict[str, Any], @@ -282,7 +285,9 @@ def _measure( marker_options = BenchmarkMarkerOptions.from_pytest_item(node) uri, name = get_git_relative_uri_and_name(node.nodeid, config.rootpath) if iscoroutinefunction(fn): - return _async_measure(plugin, marker_options, pedantic_options, fn, args, kwargs) + return _async_measure( + plugin, marker_options, pedantic_options, name, uri, fn, args, kwargs + ) else: with _measure_context(): if pedantic_options is None: From 5b86d751f53e271b68c82509edd89e45efda18cc Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sun, 21 Dec 2025 01:54:32 +0000 Subject: [PATCH 12/23] Update plugin.py --- src/pytest_codspeed/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pytest_codspeed/plugin.py b/src/pytest_codspeed/plugin.py index 7a1d597..b9c937c 100644 --- a/src/pytest_codspeed/plugin.py +++ b/src/pytest_codspeed/plugin.py @@ -5,7 +5,7 @@ import json import os import random -from collections.abc import AsyncIterator +from collections.abc import Iterator from contextlib import contextmanager from dataclasses import dataclass, field from inspect import iscoroutinefunction @@ -237,7 +237,7 @@ def pytest_collection_modifyitems( @contextmanager -def _measure_context() -> AsyncIterator[None]: +def _measure_context() -> Iterator[None]: random.seed(0) is_gc_enabled = gc.isenabled() if is_gc_enabled: From c0453b5c992841edc25aa8e03d57a9e09d9fc54e Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sun, 21 Dec 2025 01:56:13 +0000 Subject: [PATCH 13/23] Update __init__.py --- src/pytest_codspeed/instruments/__init__.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/pytest_codspeed/instruments/__init__.py b/src/pytest_codspeed/instruments/__init__.py index 08dace3..52bd920 100644 --- a/src/pytest_codspeed/instruments/__init__.py +++ b/src/pytest_codspeed/instruments/__init__.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: + from collections.abc import Awaitable from typing import Any, Callable, ClassVar, TypeVar import pytest @@ -37,6 +38,17 @@ def measure( **kwargs: P.kwargs, ) -> T: ... + @abstractmethod + async def measure_async( + self, + marker_options: BenchmarkMarkerOptions, + name: str, + uri: str, + fn: Callable[P, Awaitable[T]], + *args: P.args, + **kwargs: P.kwargs, + ) -> T: ... + @abstractmethod def measure_pedantic( self, @@ -46,6 +58,15 @@ def measure_pedantic( uri: str, ) -> T: ... + @abstractmethod + async def measure_pedantic( + self, + marker_options: BenchmarkMarkerOptions, + pedantic_options: PedanticOptions[T], + name: str, + uri: str, + ) -> T: ... + @abstractmethod def report(self, session: pytest.Session) -> None: ... From a59aaa5b92f3cf32b9b5d81e666f129b5f5a5b7e Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sun, 21 Dec 2025 01:58:15 +0000 Subject: [PATCH 14/23] Update __init__.py --- src/pytest_codspeed/instruments/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pytest_codspeed/instruments/__init__.py b/src/pytest_codspeed/instruments/__init__.py index 52bd920..acf9fe5 100644 --- a/src/pytest_codspeed/instruments/__init__.py +++ b/src/pytest_codspeed/instruments/__init__.py @@ -59,7 +59,7 @@ def measure_pedantic( ) -> T: ... @abstractmethod - async def measure_pedantic( + async def measure_pedantic_async( self, marker_options: BenchmarkMarkerOptions, pedantic_options: PedanticOptions[T], From de43347c8543ad1e18f197104fdf1b157e0f52e3 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sun, 21 Dec 2025 02:00:39 +0000 Subject: [PATCH 15/23] Update plugin.py --- src/pytest_codspeed/plugin.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pytest_codspeed/plugin.py b/src/pytest_codspeed/plugin.py index b9c937c..79f78e6 100644 --- a/src/pytest_codspeed/plugin.py +++ b/src/pytest_codspeed/plugin.py @@ -5,7 +5,6 @@ import json import os import random -from collections.abc import Iterator from contextlib import contextmanager from dataclasses import dataclass, field from inspect import iscoroutinefunction @@ -33,7 +32,7 @@ from . import __version__ if TYPE_CHECKING: - from collections.abc import Awaitable + from collections.abc import Awaitable, Iterator from typing import Any, Callable, ParamSpec, TypeVar from pytest_codspeed.instruments import Instrument @@ -258,7 +257,7 @@ async def _async_measure( pedantic_options: PedanticOptions | None, name: str, uri: str, - fn: Awaitable[T], + fn: Callable[..., Awaitable[T]], args: tuple[Any, ...], kwargs: dict[str, Any], ) -> T: From b321b82f4ecc23fa3598a31e55862103199e6d04 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sun, 21 Dec 2025 02:09:05 +0000 Subject: [PATCH 16/23] Update walltime.py --- src/pytest_codspeed/instruments/walltime.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pytest_codspeed/instruments/walltime.py b/src/pytest_codspeed/instruments/walltime.py index 17ac0b7..6dcb48b 100644 --- a/src/pytest_codspeed/instruments/walltime.py +++ b/src/pytest_codspeed/instruments/walltime.py @@ -2,7 +2,6 @@ import os import warnings -from collections.abc import Awaitable from dataclasses import asdict, dataclass from math import ceil from statistics import mean, quantiles, stdev @@ -20,6 +19,7 @@ from pytest_codspeed.utils import SUPPORTS_PERF_TRAMPOLINE if TYPE_CHECKING: + from collections.abc import Awaitable, Iterator from typing import Any, Callable from pytest import Session @@ -188,7 +188,7 @@ def _measure_iter( marker_options: BenchmarkMarkerOptions, name: str, uri: str, - ) -> T: + ) -> Iterator[None]: benchmark_config = BenchmarkConfig.from_codspeed_config_and_marker_data( self.config, marker_options ) @@ -299,7 +299,7 @@ def _measure_pedantic_iter( # noqa: C901 pedantic_options: PedanticOptions[T], name: str, uri: str, - ) -> T: + ) -> Iterator[None]: benchmark_config = BenchmarkConfig.from_codspeed_config_and_marker_data( self.config, marker_options ) From 10547f53f404d7698459e72381205572f9dc8f7e Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sun, 21 Dec 2025 02:12:01 +0000 Subject: [PATCH 17/23] Update valgrind.py --- src/pytest_codspeed/instruments/valgrind.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pytest_codspeed/instruments/valgrind.py b/src/pytest_codspeed/instruments/valgrind.py index 78fd5e7..67f76f6 100644 --- a/src/pytest_codspeed/instruments/valgrind.py +++ b/src/pytest_codspeed/instruments/valgrind.py @@ -11,7 +11,7 @@ from pytest_codspeed.utils import SUPPORTS_PERF_TRAMPOLINE if TYPE_CHECKING: - from collections.abc import Awaitable + from collections.abc import Awaitable, Iterator from typing import Any, Callable from pytest import Session @@ -113,9 +113,9 @@ async def __codspeed_root_frame__() -> T: @contextmanager def _measure_pedantic_context( self, - pedantic_options: PedanticOptions[T], + pedantic_options: PedanticOptions[object], uri: str, - ) -> T: + ) -> Iterator[None]: if pedantic_options.rounds != 1 or pedantic_options.iterations != 1: warnings.warn( "Valgrind instrument ignores rounds and iterations settings " @@ -165,7 +165,7 @@ def __codspeed_root_frame__(*args, **kwargs) -> T: async def measure_pedantic_async( self, marker_options: BenchmarkMarkerOptions, - pedantic_options: PedanticOptions[T], + pedantic_options: PedanticOptions[Awaitable[T]], name: str, uri: str, ) -> T: From 9ed015c7388dc6285a2f4790e6ab561ca0d8ab62 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sun, 21 Dec 2025 02:12:55 +0000 Subject: [PATCH 18/23] Update walltime.py --- src/pytest_codspeed/instruments/walltime.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pytest_codspeed/instruments/walltime.py b/src/pytest_codspeed/instruments/walltime.py index 6dcb48b..04b765c 100644 --- a/src/pytest_codspeed/instruments/walltime.py +++ b/src/pytest_codspeed/instruments/walltime.py @@ -296,7 +296,7 @@ async def __codspeed_root_frame__() -> T: def _measure_pedantic_iter( # noqa: C901 self, marker_options: BenchmarkMarkerOptions, - pedantic_options: PedanticOptions[T], + pedantic_options: PedanticOptions[object], name: str, uri: str, ) -> Iterator[None]: @@ -370,7 +370,7 @@ def __codspeed_root_frame__(*args, **kwargs) -> T: async def measure_pedantic_async( self, marker_options: BenchmarkMarkerOptions, - pedantic_options: PedanticOptions[T], + pedantic_options: PedanticOptions[Awaitable[T]], name: str, uri: str, ) -> T: From e93fbd95fc5c8f52d5154dcade95603199831b2f Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sun, 21 Dec 2025 02:16:20 +0000 Subject: [PATCH 19/23] Update walltime.py --- src/pytest_codspeed/instruments/walltime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pytest_codspeed/instruments/walltime.py b/src/pytest_codspeed/instruments/walltime.py index 04b765c..360fa9a 100644 --- a/src/pytest_codspeed/instruments/walltime.py +++ b/src/pytest_codspeed/instruments/walltime.py @@ -299,7 +299,7 @@ def _measure_pedantic_iter( # noqa: C901 pedantic_options: PedanticOptions[object], name: str, uri: str, - ) -> Iterator[None]: + ) -> Iterator[int, tuple[Any], dict[str, Any]]: benchmark_config = BenchmarkConfig.from_codspeed_config_and_marker_data( self.config, marker_options ) From b80c810edd47ab032efe6920698faffa68456ef3 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sun, 21 Dec 2025 02:18:23 +0000 Subject: [PATCH 20/23] Update valgrind.py --- src/pytest_codspeed/instruments/valgrind.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pytest_codspeed/instruments/valgrind.py b/src/pytest_codspeed/instruments/valgrind.py index 67f76f6..b9c7dbe 100644 --- a/src/pytest_codspeed/instruments/valgrind.py +++ b/src/pytest_codspeed/instruments/valgrind.py @@ -113,7 +113,7 @@ async def __codspeed_root_frame__() -> T: @contextmanager def _measure_pedantic_context( self, - pedantic_options: PedanticOptions[object], + pedantic_options: PedanticOptions[Any], uri: str, ) -> Iterator[None]: if pedantic_options.rounds != 1 or pedantic_options.iterations != 1: From d43588d1a8e660ec1f72f930a921ede3a2564b6f Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sun, 21 Dec 2025 02:19:32 +0000 Subject: [PATCH 21/23] Update __init__.py --- src/pytest_codspeed/instruments/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pytest_codspeed/instruments/__init__.py b/src/pytest_codspeed/instruments/__init__.py index acf9fe5..5dcbbad 100644 --- a/src/pytest_codspeed/instruments/__init__.py +++ b/src/pytest_codspeed/instruments/__init__.py @@ -62,7 +62,7 @@ def measure_pedantic( async def measure_pedantic_async( self, marker_options: BenchmarkMarkerOptions, - pedantic_options: PedanticOptions[T], + pedantic_options: PedanticOptions[Awaitable[T]], name: str, uri: str, ) -> T: ... From 1a29494f8fa61adead5aa67f28aad5edce4661fa Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sun, 21 Dec 2025 02:21:55 +0000 Subject: [PATCH 22/23] Update walltime.py --- src/pytest_codspeed/instruments/walltime.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pytest_codspeed/instruments/walltime.py b/src/pytest_codspeed/instruments/walltime.py index 360fa9a..4ff876c 100644 --- a/src/pytest_codspeed/instruments/walltime.py +++ b/src/pytest_codspeed/instruments/walltime.py @@ -296,10 +296,10 @@ async def __codspeed_root_frame__() -> T: def _measure_pedantic_iter( # noqa: C901 self, marker_options: BenchmarkMarkerOptions, - pedantic_options: PedanticOptions[object], + pedantic_options: PedanticOptions[Any], name: str, uri: str, - ) -> Iterator[int, tuple[Any], dict[str, Any]]: + ) -> Iterator[tuple[int, tuple[Any], dict[str, Any]]]: benchmark_config = BenchmarkConfig.from_codspeed_config_and_marker_data( self.config, marker_options ) From d6002372e65c86d8396224a1fe1767da2bdf3a23 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sun, 21 Dec 2025 02:27:56 +0000 Subject: [PATCH 23/23] Apply suggestions from code review --- src/pytest_codspeed/instruments/walltime.py | 2 +- src/pytest_codspeed/plugin.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pytest_codspeed/instruments/walltime.py b/src/pytest_codspeed/instruments/walltime.py index 4ff876c..7422fd4 100644 --- a/src/pytest_codspeed/instruments/walltime.py +++ b/src/pytest_codspeed/instruments/walltime.py @@ -299,7 +299,7 @@ def _measure_pedantic_iter( # noqa: C901 pedantic_options: PedanticOptions[Any], name: str, uri: str, - ) -> Iterator[tuple[int, tuple[Any], dict[str, Any]]]: + ) -> Iterator[tuple[range, tuple[Any, ...], dict[str, Any]]]: benchmark_config = BenchmarkConfig.from_codspeed_config_and_marker_data( self.config, marker_options ) diff --git a/src/pytest_codspeed/plugin.py b/src/pytest_codspeed/plugin.py index 79f78e6..3da71b8 100644 --- a/src/pytest_codspeed/plugin.py +++ b/src/pytest_codspeed/plugin.py @@ -284,7 +284,7 @@ def _measure( marker_options = BenchmarkMarkerOptions.from_pytest_item(node) uri, name = get_git_relative_uri_and_name(node.nodeid, config.rootpath) if iscoroutinefunction(fn): - return _async_measure( + return _async_measure( # type: ignore[return-value] plugin, marker_options, pedantic_options, name, uri, fn, args, kwargs ) else: