|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | | -from dataclasses import dataclass |
4 | | -from typing import TYPE_CHECKING |
| 3 | +import dataclasses |
| 4 | +from dataclasses import dataclass, field |
| 5 | +from typing import TYPE_CHECKING, Generic, TypeVar |
| 6 | + |
| 7 | +T = TypeVar("T") |
5 | 8 |
|
6 | 9 | if TYPE_CHECKING: |
| 10 | + from typing import Any, Callable |
| 11 | + |
7 | 12 | import pytest |
8 | 13 |
|
9 | 14 |
|
@@ -64,17 +69,51 @@ def from_pytest_item(cls, item: pytest.Item) -> BenchmarkMarkerOptions: |
64 | 69 | raise ValueError( |
65 | 70 | "Positional arguments are not allowed in the benchmark marker" |
66 | 71 | ) |
| 72 | + kwargs = marker.kwargs |
67 | 73 |
|
68 | | - options = cls( |
69 | | - group=marker.kwargs.pop("group", None), |
70 | | - min_time=marker.kwargs.pop("min_time", None), |
71 | | - max_time=marker.kwargs.pop("max_time", None), |
72 | | - max_rounds=marker.kwargs.pop("max_rounds", None), |
73 | | - ) |
74 | | - |
75 | | - if len(marker.kwargs) > 0: |
| 74 | + unknown_kwargs = set(kwargs.keys()) - { |
| 75 | + field.name for field in dataclasses.fields(cls) |
| 76 | + } |
| 77 | + if unknown_kwargs: |
76 | 78 | raise ValueError( |
77 | 79 | "Unknown kwargs passed to benchmark marker: " |
78 | | - + ", ".join(marker.kwargs.keys()) |
| 80 | + + ", ".join(sorted(unknown_kwargs)) |
79 | 81 | ) |
80 | | - return options |
| 82 | + |
| 83 | + return cls(**kwargs) |
| 84 | + |
| 85 | + |
| 86 | +@dataclass(frozen=True) |
| 87 | +class PedanticOptions(Generic[T]): |
| 88 | + """Parameters for running a benchmark using the pedantic fixture API.""" |
| 89 | + |
| 90 | + target: Callable[..., T] |
| 91 | + setup: Callable[[], Any | None] | None |
| 92 | + teardown: Callable[..., Any | None] | None |
| 93 | + rounds: int |
| 94 | + warmup_rounds: int |
| 95 | + iterations: int |
| 96 | + args: tuple[Any, ...] = field(default_factory=tuple) |
| 97 | + kwargs: dict[str, Any] = field(default_factory=dict) |
| 98 | + |
| 99 | + def __post_init__(self) -> None: |
| 100 | + if self.rounds < 0: |
| 101 | + raise ValueError("rounds must be positive") |
| 102 | + if self.warmup_rounds < 0: |
| 103 | + raise ValueError("warmup_rounds must be non-negative") |
| 104 | + if self.iterations <= 0: |
| 105 | + raise ValueError("iterations must be positive") |
| 106 | + if self.iterations > 1 and self.setup is not None: |
| 107 | + raise ValueError( |
| 108 | + "setup cannot be used with multiple iterations, use multiple rounds" |
| 109 | + ) |
| 110 | + |
| 111 | + def setup_and_get_args_kwargs(self) -> tuple[tuple[Any, ...], dict[str, Any]]: |
| 112 | + if self.setup is None: |
| 113 | + return self.args, self.kwargs |
| 114 | + maybe_result = self.setup(*self.args, **self.kwargs) |
| 115 | + if maybe_result is not None: |
| 116 | + if len(self.args) > 0 or len(self.kwargs) > 0: |
| 117 | + raise ValueError("setup cannot return a value when args are provided") |
| 118 | + return maybe_result |
| 119 | + return self.args, self.kwargs |
0 commit comments