rustest 0.1.0__cp311-cp311-macosx_11_0_arm64.whl → 0.5.0__cp311-cp311-macosx_11_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rustest/__init__.py +11 -7
- rustest/__main__.py +1 -1
- rustest/approx.py +160 -0
- rustest/{_cli.py → cli.py} +17 -3
- rustest/core.py +24 -7
- rustest/decorators.py +423 -0
- rustest/py.typed +0 -0
- rustest/{_reporting.py → reporting.py} +3 -3
- rustest/rust.cpython-311-darwin.so +0 -0
- rustest/{_rust.py → rust.py} +1 -1
- rustest/{_rust.pyi → rust.pyi} +9 -7
- rustest-0.5.0.dist-info/METADATA +208 -0
- rustest-0.5.0.dist-info/RECORD +16 -0
- rustest/_decorators.py +0 -194
- rustest/_rust.cpython-311-darwin.so +0 -0
- rustest-0.1.0.dist-info/METADATA +0 -487
- rustest-0.1.0.dist-info/RECORD +0 -14
- {rustest-0.1.0.dist-info → rustest-0.5.0.dist-info}/WHEEL +0 -0
- {rustest-0.1.0.dist-info → rustest-0.5.0.dist-info}/entry_points.txt +0 -0
- {rustest-0.1.0.dist-info → rustest-0.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rustest
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Classifier: Programming Language :: Rust
|
|
14
|
+
Classifier: Topic :: Software Development :: Testing
|
|
15
|
+
Requires-Dist: typing-extensions>=4.15
|
|
16
|
+
Requires-Dist: basedpyright>=1.19 ; extra == 'dev'
|
|
17
|
+
Requires-Dist: maturin>=1.4,<2 ; extra == 'dev'
|
|
18
|
+
Requires-Dist: poethepoet>=0.22 ; extra == 'dev'
|
|
19
|
+
Requires-Dist: pre-commit>=3.5 ; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest>=7.0 ; extra == 'dev'
|
|
21
|
+
Requires-Dist: ruff>=0.1.9 ; extra == 'dev'
|
|
22
|
+
Requires-Dist: mkdocs>=1.5.0 ; extra == 'docs'
|
|
23
|
+
Requires-Dist: mkdocs-material>=9.5.0 ; extra == 'docs'
|
|
24
|
+
Requires-Dist: mkdocstrings[python]>=0.24.0 ; extra == 'docs'
|
|
25
|
+
Requires-Dist: mkdocs-autorefs>=0.5.0 ; extra == 'docs'
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Provides-Extra: docs
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Summary: Rust powered pytest-compatible runner
|
|
30
|
+
Author: rustest contributors
|
|
31
|
+
Requires-Python: >=3.10
|
|
32
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
33
|
+
Project-URL: Homepage, https://github.com/Apex-Engineers-Inc/rustest
|
|
34
|
+
Project-URL: Repository, https://github.com/Apex-Engineers-Inc/rustest
|
|
35
|
+
Project-URL: Documentation, https://apex-engineers-inc.github.io/rustest
|
|
36
|
+
|
|
37
|
+
# rustest
|
|
38
|
+
|
|
39
|
+
Rustest (pronounced like Russ-Test) is a Rust-powered test runner that aims to provide the most common pytest ergonomics with a focus on raw performance. Get **~2x faster** test execution with familiar syntax and minimal setup.
|
|
40
|
+
|
|
41
|
+
📚 **[Full Documentation](https://apex-engineers-inc.github.io/rustest)** | [Getting Started](https://apex-engineers-inc.github.io/rustest/getting-started/quickstart/) | [User Guide](https://apex-engineers-inc.github.io/rustest/guide/writing-tests/) | [API Reference](https://apex-engineers-inc.github.io/rustest/api/overview/)
|
|
42
|
+
|
|
43
|
+
## Why rustest?
|
|
44
|
+
|
|
45
|
+
- 🚀 **About 2x faster** than pytest on the rustest integration test suite
|
|
46
|
+
- ✅ Familiar `@fixture`, `@parametrize`, `@skip`, and `@mark` decorators
|
|
47
|
+
- 🔍 Automatic test discovery (`test_*.py` and `*_test.py` files)
|
|
48
|
+
- 📝 **Built-in markdown code block testing** (like pytest-codeblocks, but faster)
|
|
49
|
+
- 🎯 Simple, clean API—if you know pytest, you already know rustest
|
|
50
|
+
- 🧮 Built-in `approx()` helper for tolerant numeric comparisons
|
|
51
|
+
- 🪤 `raises()` context manager for precise exception assertions
|
|
52
|
+
- 📦 Easy installation with pip or uv
|
|
53
|
+
- ⚡ Low-overhead execution keeps small suites feeling instant
|
|
54
|
+
|
|
55
|
+
## Performance
|
|
56
|
+
|
|
57
|
+
Rustest is designed for speed. Our latest benchmarks on the rustest integration suite (~200 tests) show a consistent **2.1x wall-clock speedup** over pytest:
|
|
58
|
+
|
|
59
|
+
| Test Runner | Wall Clock | Speedup | Command |
|
|
60
|
+
|-------------|------------|---------|---------|
|
|
61
|
+
| pytest | 1.33–1.59s | 1.0x (baseline) | `pytest tests/ examples/tests/ -q` |
|
|
62
|
+
| rustest | 0.69–0.70s | **~2.1x faster** | `python -m rustest tests/ examples/tests/` |
|
|
63
|
+
|
|
64
|
+
### Large Parametrized Stress Test
|
|
65
|
+
|
|
66
|
+
With **10,000 parametrized invocations**:
|
|
67
|
+
|
|
68
|
+
| Test Runner | Avg. Wall Clock | Speedup | Command |
|
|
69
|
+
|-------------|-----------------|---------|---------|
|
|
70
|
+
| pytest | 9.72s | 1.0x | `pytest benchmarks/test_large_parametrize.py -q` |
|
|
71
|
+
| rustest | 0.41s | **~24x faster** | `python -m rustest benchmarks/test_large_parametrize.py` |
|
|
72
|
+
|
|
73
|
+
**[📊 View Detailed Performance Analysis →](https://apex-engineers-inc.github.io/rustest/advanced/performance/)**
|
|
74
|
+
|
|
75
|
+
## Installation
|
|
76
|
+
|
|
77
|
+
Rustest supports Python **3.10 through 3.14**.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Using pip
|
|
81
|
+
pip install rustest
|
|
82
|
+
|
|
83
|
+
# Using uv
|
|
84
|
+
uv add rustest
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**[📖 Installation Guide →](https://apex-engineers-inc.github.io/rustest/getting-started/installation/)**
|
|
88
|
+
|
|
89
|
+
## Quick Start
|
|
90
|
+
|
|
91
|
+
### 1. Write Your Tests
|
|
92
|
+
|
|
93
|
+
Create a file `test_math.py`:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from rustest import fixture, parametrize, mark, approx, raises
|
|
97
|
+
|
|
98
|
+
@fixture
|
|
99
|
+
def numbers() -> list[int]:
|
|
100
|
+
return [1, 2, 3, 4, 5]
|
|
101
|
+
|
|
102
|
+
def test_sum(numbers: list[int]) -> None:
|
|
103
|
+
assert sum(numbers) == approx(15)
|
|
104
|
+
|
|
105
|
+
@parametrize("value,expected", [(2, 4), (3, 9), (4, 16)])
|
|
106
|
+
def test_square(value: int, expected: int) -> None:
|
|
107
|
+
assert value ** 2 == expected
|
|
108
|
+
|
|
109
|
+
@mark.slow
|
|
110
|
+
def test_expensive_operation() -> None:
|
|
111
|
+
result = sum(range(1000000))
|
|
112
|
+
assert result > 0
|
|
113
|
+
|
|
114
|
+
def test_division_by_zero() -> None:
|
|
115
|
+
with raises(ZeroDivisionError, match="division by zero"):
|
|
116
|
+
1 / 0
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 2. Run Your Tests
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Run all tests
|
|
123
|
+
rustest
|
|
124
|
+
|
|
125
|
+
# Run specific tests
|
|
126
|
+
rustest tests/
|
|
127
|
+
|
|
128
|
+
# Filter by test name pattern
|
|
129
|
+
rustest -k "test_sum"
|
|
130
|
+
|
|
131
|
+
# Filter by marks
|
|
132
|
+
rustest -m "slow" # Run only slow tests
|
|
133
|
+
rustest -m "not slow" # Skip slow tests
|
|
134
|
+
rustest -m "slow and integration" # Run tests with both marks
|
|
135
|
+
|
|
136
|
+
# Show output during execution
|
|
137
|
+
rustest --no-capture
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**[📖 Full Quick Start Guide →](https://apex-engineers-inc.github.io/rustest/getting-started/quickstart/)**
|
|
141
|
+
|
|
142
|
+
## Documentation
|
|
143
|
+
|
|
144
|
+
**[📚 Full Documentation](https://apex-engineers-inc.github.io/rustest)**
|
|
145
|
+
|
|
146
|
+
### Getting Started
|
|
147
|
+
- [Installation](https://apex-engineers-inc.github.io/rustest/getting-started/installation/)
|
|
148
|
+
- [Quick Start](https://apex-engineers-inc.github.io/rustest/getting-started/quickstart/)
|
|
149
|
+
|
|
150
|
+
### User Guide
|
|
151
|
+
- [Writing Tests](https://apex-engineers-inc.github.io/rustest/guide/writing-tests/)
|
|
152
|
+
- [Fixtures](https://apex-engineers-inc.github.io/rustest/guide/fixtures/)
|
|
153
|
+
- [Parametrization](https://apex-engineers-inc.github.io/rustest/guide/parametrization/)
|
|
154
|
+
- [Marks & Skipping](https://apex-engineers-inc.github.io/rustest/guide/marks/)
|
|
155
|
+
- [Test Classes](https://apex-engineers-inc.github.io/rustest/guide/test-classes/)
|
|
156
|
+
- [Assertion Helpers](https://apex-engineers-inc.github.io/rustest/guide/assertions/)
|
|
157
|
+
- [Markdown Testing](https://apex-engineers-inc.github.io/rustest/guide/markdown-testing/)
|
|
158
|
+
- [CLI Usage](https://apex-engineers-inc.github.io/rustest/guide/cli/)
|
|
159
|
+
- [Python API](https://apex-engineers-inc.github.io/rustest/guide/python-api/)
|
|
160
|
+
|
|
161
|
+
### API Reference
|
|
162
|
+
- [API Overview](https://apex-engineers-inc.github.io/rustest/api/overview/)
|
|
163
|
+
- [Decorators](https://apex-engineers-inc.github.io/rustest/api/decorators/)
|
|
164
|
+
- [Test Execution](https://apex-engineers-inc.github.io/rustest/api/core/)
|
|
165
|
+
- [Reporting](https://apex-engineers-inc.github.io/rustest/api/reporting/)
|
|
166
|
+
- [Assertion Utilities](https://apex-engineers-inc.github.io/rustest/api/approx/)
|
|
167
|
+
|
|
168
|
+
### Advanced Topics
|
|
169
|
+
- [Performance](https://apex-engineers-inc.github.io/rustest/advanced/performance/)
|
|
170
|
+
- [Comparison with pytest](https://apex-engineers-inc.github.io/rustest/advanced/comparison/)
|
|
171
|
+
- [Development Guide](https://apex-engineers-inc.github.io/rustest/advanced/development/)
|
|
172
|
+
|
|
173
|
+
## Feature Comparison with pytest
|
|
174
|
+
|
|
175
|
+
Rustest implements the 20% of pytest features that cover 80% of use cases, with a focus on raw speed and simplicity.
|
|
176
|
+
|
|
177
|
+
**[📋 View Full Feature Comparison →](https://apex-engineers-inc.github.io/rustest/advanced/comparison/)**
|
|
178
|
+
|
|
179
|
+
✅ **Supported:** Fixtures, parametrization, marks, test classes, conftest.py, markdown testing
|
|
180
|
+
🚧 **Planned:** Parallel execution, mark filtering, JUnit XML output
|
|
181
|
+
❌ **Not Planned:** Plugins, hooks, custom collectors (keeps rustest simple)
|
|
182
|
+
|
|
183
|
+
## Contributing
|
|
184
|
+
|
|
185
|
+
We welcome contributions! See the [Development Guide](https://apex-engineers-inc.github.io/rustest/advanced/development/) for setup instructions.
|
|
186
|
+
|
|
187
|
+
Quick reference:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
# Setup
|
|
191
|
+
git clone https://github.com/Apex-Engineers-Inc/rustest.git
|
|
192
|
+
cd rustest
|
|
193
|
+
uv sync --all-extras
|
|
194
|
+
uv run maturin develop
|
|
195
|
+
|
|
196
|
+
# Run tests
|
|
197
|
+
uv run poe pytests # Python tests
|
|
198
|
+
cargo test # Rust tests
|
|
199
|
+
|
|
200
|
+
# Format and lint
|
|
201
|
+
uv run pre-commit install # One-time setup
|
|
202
|
+
git commit -m "message" # Pre-commit hooks run automatically
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
rustest is distributed under the terms of the MIT license. See [LICENSE](LICENSE).
|
|
208
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
rustest-0.5.0.dist-info/METADATA,sha256=t13FMGBrjwTTq6iJ3xqPuJbO7zMriNL3MS87YAopr5Y,8054
|
|
2
|
+
rustest-0.5.0.dist-info/WHEEL,sha256=dewdenAwp3PDth0u4HpQhcjieEs1_hiwRbm3WvCuoaI,104
|
|
3
|
+
rustest-0.5.0.dist-info/entry_points.txt,sha256=7fUa3LO8vudQ4dKG1sTRaDnxcMdBSZsWs9EyuxFQ7Lk,48
|
|
4
|
+
rustest-0.5.0.dist-info/licenses/LICENSE,sha256=s64ibUGtb6jEDBsYuxUFtMr_c4PaqYP-vj3YY6QtTGw,1075
|
|
5
|
+
rustest/__init__.py,sha256=0CkHfrmIjpGw6DMu2VcZLOUpBVtdwsN-aitn-RzOglo,514
|
|
6
|
+
rustest/__main__.py,sha256=bBvo5gsSluUzlDTDvn5bP_gZZEXMwJQZMqVA5W1M1v8,178
|
|
7
|
+
rustest/approx.py,sha256=MKmuorBBHqpH0h0QaIMVjbm3-mXJ0E90limEgSHHVfw,5744
|
|
8
|
+
rustest/cli.py,sha256=U7mlUghUnkrRcmjUD_R0leNgdC43fzUrki6XzWmUpRE,9201
|
|
9
|
+
rustest/core.py,sha256=KoifFNU3tvOZgLU_zLvpcyEciOE4rAkQfpXWO2_EQmo,1189
|
|
10
|
+
rustest/decorators.py,sha256=yQD6EV3mM2cAx2yns1YM22LfRw5m1k62yZXUZwm3Vz4,13715
|
|
11
|
+
rustest/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
rustest/reporting.py,sha256=3-R8aljv2ZbmjOa9Q9KZeCPsMaitm8nZ96LoJS_NnUQ,1623
|
|
13
|
+
rustest/rust.cpython-311-darwin.so,sha256=TX-2ZgwYJA1alIrRHvI2IR6kiTROIupeknFDg9oe-B8,1502880
|
|
14
|
+
rustest/rust.py,sha256=tCIvjYd06VxoT_rKvv2o8CpXW_pFNua5VgcRDjLgU78,659
|
|
15
|
+
rustest/rust.pyi,sha256=-eAPqXfM9KwXHX_-oEuPwIU1x1mDEw_qkbur3mRouT0,762
|
|
16
|
+
rustest-0.5.0.dist-info/RECORD,,
|
rustest/_decorators.py
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
"""User facing decorators mirroring the most common pytest helpers."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from collections.abc import Callable, Mapping, Sequence
|
|
6
|
-
from typing import Any, TypeVar
|
|
7
|
-
|
|
8
|
-
F = TypeVar("F", bound=Callable[..., object])
|
|
9
|
-
|
|
10
|
-
# Valid fixture scopes
|
|
11
|
-
VALID_SCOPES = frozenset(["function", "class", "module", "session"])
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def fixture(
|
|
15
|
-
func: F | None = None,
|
|
16
|
-
*,
|
|
17
|
-
scope: str = "function",
|
|
18
|
-
) -> F | Callable[[F], F]:
|
|
19
|
-
"""Mark a function as a fixture with a specific scope.
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
func: The function to decorate (when used without parentheses)
|
|
23
|
-
scope: The scope of the fixture. One of:
|
|
24
|
-
- "function": New instance for each test function (default)
|
|
25
|
-
- "class": Shared across all test methods in a class
|
|
26
|
-
- "module": Shared across all tests in a module
|
|
27
|
-
- "session": Shared across all tests in the session
|
|
28
|
-
|
|
29
|
-
Usage:
|
|
30
|
-
@fixture
|
|
31
|
-
def my_fixture():
|
|
32
|
-
return 42
|
|
33
|
-
|
|
34
|
-
@fixture(scope="module")
|
|
35
|
-
def shared_fixture():
|
|
36
|
-
return expensive_setup()
|
|
37
|
-
"""
|
|
38
|
-
if scope not in VALID_SCOPES:
|
|
39
|
-
valid = ", ".join(sorted(VALID_SCOPES))
|
|
40
|
-
msg = f"Invalid fixture scope '{scope}'. Must be one of: {valid}"
|
|
41
|
-
raise ValueError(msg)
|
|
42
|
-
|
|
43
|
-
def decorator(f: F) -> F:
|
|
44
|
-
setattr(f, "__rustest_fixture__", True)
|
|
45
|
-
setattr(f, "__rustest_fixture_scope__", scope)
|
|
46
|
-
return f
|
|
47
|
-
|
|
48
|
-
# Support both @fixture and @fixture(scope="...")
|
|
49
|
-
if func is not None:
|
|
50
|
-
return decorator(func)
|
|
51
|
-
return decorator
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def skip(reason: str | None = None) -> Callable[[F], F]:
|
|
55
|
-
"""Skip a test or fixture."""
|
|
56
|
-
|
|
57
|
-
def decorator(func: F) -> F:
|
|
58
|
-
setattr(func, "__rustest_skip__", reason or "skipped via rustest.skip")
|
|
59
|
-
return func
|
|
60
|
-
|
|
61
|
-
return decorator
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def parametrize(
|
|
65
|
-
arg_names: str | Sequence[str],
|
|
66
|
-
values: Sequence[Sequence[object] | Mapping[str, object]],
|
|
67
|
-
*,
|
|
68
|
-
ids: Sequence[str] | None = None,
|
|
69
|
-
) -> Callable[[F], F]:
|
|
70
|
-
"""Parametrise a test function."""
|
|
71
|
-
|
|
72
|
-
normalized_names = _normalize_arg_names(arg_names)
|
|
73
|
-
|
|
74
|
-
def decorator(func: F) -> F:
|
|
75
|
-
cases = _build_cases(normalized_names, values, ids)
|
|
76
|
-
setattr(func, "__rustest_parametrization__", cases)
|
|
77
|
-
return func
|
|
78
|
-
|
|
79
|
-
return decorator
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def _normalize_arg_names(arg_names: str | Sequence[str]) -> tuple[str, ...]:
|
|
83
|
-
if isinstance(arg_names, str):
|
|
84
|
-
parts = [part.strip() for part in arg_names.split(",") if part.strip()]
|
|
85
|
-
if not parts:
|
|
86
|
-
msg = "parametrize() expected at least one argument name"
|
|
87
|
-
raise ValueError(msg)
|
|
88
|
-
return tuple(parts)
|
|
89
|
-
return tuple(arg_names)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def _build_cases(
|
|
93
|
-
names: tuple[str, ...],
|
|
94
|
-
values: Sequence[Sequence[object] | Mapping[str, object]],
|
|
95
|
-
ids: Sequence[str] | None,
|
|
96
|
-
) -> tuple[dict[str, object], ...]:
|
|
97
|
-
case_payloads: list[dict[str, object]] = []
|
|
98
|
-
if ids is not None and len(ids) != len(values):
|
|
99
|
-
msg = "ids must match the number of value sets"
|
|
100
|
-
raise ValueError(msg)
|
|
101
|
-
|
|
102
|
-
for index, case in enumerate(values):
|
|
103
|
-
# Mappings are only treated as parameter mappings when there are multiple parameters
|
|
104
|
-
# For single parameters, dicts/mappings are treated as values
|
|
105
|
-
if isinstance(case, Mapping) and len(names) > 1:
|
|
106
|
-
data = {name: case[name] for name in names}
|
|
107
|
-
elif isinstance(case, tuple) and len(case) == len(names):
|
|
108
|
-
# Tuples are unpacked to match parameter names (pytest convention)
|
|
109
|
-
# This handles both single and multiple parameters
|
|
110
|
-
data = {name: case[pos] for pos, name in enumerate(names)}
|
|
111
|
-
else:
|
|
112
|
-
# Everything else is treated as a single value
|
|
113
|
-
# This includes: primitives, lists (even if len==names), dicts (single param), objects
|
|
114
|
-
if len(names) == 1:
|
|
115
|
-
data = {names[0]: case}
|
|
116
|
-
else:
|
|
117
|
-
raise ValueError("Parametrized value does not match argument names")
|
|
118
|
-
case_id = ids[index] if ids is not None else f"case_{index}"
|
|
119
|
-
case_payloads.append({"id": case_id, "values": data})
|
|
120
|
-
return tuple(case_payloads)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
class MarkDecorator:
|
|
124
|
-
"""A decorator for applying a mark to a test function."""
|
|
125
|
-
|
|
126
|
-
def __init__(self, name: str, args: tuple[Any, ...], kwargs: dict[str, Any]) -> None:
|
|
127
|
-
super().__init__()
|
|
128
|
-
self.name = name
|
|
129
|
-
self.args = args
|
|
130
|
-
self.kwargs = kwargs
|
|
131
|
-
|
|
132
|
-
def __call__(self, func: F) -> F:
|
|
133
|
-
"""Apply this mark to the given function."""
|
|
134
|
-
# Get existing marks or create a new list
|
|
135
|
-
existing_marks: list[dict[str, Any]] = getattr(func, "__rustest_marks__", [])
|
|
136
|
-
|
|
137
|
-
# Add this mark to the list
|
|
138
|
-
mark_data = {
|
|
139
|
-
"name": self.name,
|
|
140
|
-
"args": self.args,
|
|
141
|
-
"kwargs": self.kwargs,
|
|
142
|
-
}
|
|
143
|
-
existing_marks.append(mark_data)
|
|
144
|
-
|
|
145
|
-
# Store the marks list on the function
|
|
146
|
-
setattr(func, "__rustest_marks__", existing_marks)
|
|
147
|
-
return func
|
|
148
|
-
|
|
149
|
-
def __repr__(self) -> str:
|
|
150
|
-
return f"Mark({self.name!r}, {self.args!r}, {self.kwargs!r})"
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
class MarkGenerator:
|
|
154
|
-
"""Namespace for dynamically creating marks like pytest.mark.
|
|
155
|
-
|
|
156
|
-
Usage:
|
|
157
|
-
@mark.slow
|
|
158
|
-
@mark.integration
|
|
159
|
-
@mark.timeout(seconds=30)
|
|
160
|
-
"""
|
|
161
|
-
|
|
162
|
-
def __getattr__(self, name: str) -> Any:
|
|
163
|
-
"""Create a mark decorator for the given name."""
|
|
164
|
-
# Return a callable that can be used as @mark.name or @mark.name(args)
|
|
165
|
-
return self._create_mark(name)
|
|
166
|
-
|
|
167
|
-
def _create_mark(self, name: str) -> Any:
|
|
168
|
-
"""Create a MarkDecorator that can be called with or without arguments."""
|
|
169
|
-
|
|
170
|
-
class _MarkDecoratorFactory:
|
|
171
|
-
"""Factory that allows @mark.name or @mark.name(args)."""
|
|
172
|
-
|
|
173
|
-
def __init__(self, mark_name: str) -> None:
|
|
174
|
-
super().__init__()
|
|
175
|
-
self.mark_name = mark_name
|
|
176
|
-
|
|
177
|
-
def __call__(self, *args: Any, **kwargs: Any) -> Any:
|
|
178
|
-
# If called with a single argument that's a function, it's @mark.name
|
|
179
|
-
if (
|
|
180
|
-
len(args) == 1
|
|
181
|
-
and not kwargs
|
|
182
|
-
and callable(args[0])
|
|
183
|
-
and hasattr(args[0], "__name__")
|
|
184
|
-
):
|
|
185
|
-
decorator = MarkDecorator(self.mark_name, (), {})
|
|
186
|
-
return decorator(args[0])
|
|
187
|
-
# Otherwise it's @mark.name(args) - return a decorator
|
|
188
|
-
return MarkDecorator(self.mark_name, args, kwargs)
|
|
189
|
-
|
|
190
|
-
return _MarkDecoratorFactory(name)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
# Create a singleton instance
|
|
194
|
-
mark = MarkGenerator()
|
|
Binary file
|