rustest 0.3.0__cp311-cp311-win_amd64.whl → 0.5.0__cp311-cp311-win_amd64.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/cli.py CHANGED
@@ -52,6 +52,12 @@ def build_parser() -> argparse.ArgumentParser:
52
52
  "--pattern",
53
53
  help="Substring to filter tests by (case insensitive).",
54
54
  )
55
+ _ = parser.add_argument(
56
+ "-m",
57
+ "--marks",
58
+ dest="mark_expr",
59
+ help='Run tests matching the given mark expression (e.g., "slow", "not slow", "slow and integration").',
60
+ )
55
61
  _ = parser.add_argument(
56
62
  "-n",
57
63
  "--workers",
@@ -81,7 +87,13 @@ def build_parser() -> argparse.ArgumentParser:
81
87
  action="store_false",
82
88
  help="Disable colored output.",
83
89
  )
84
- _ = parser.set_defaults(capture_output=True, color=True)
90
+ _ = parser.add_argument(
91
+ "--no-codeblocks",
92
+ dest="enable_codeblocks",
93
+ action="store_false",
94
+ help="Disable code block tests from markdown files.",
95
+ )
96
+ _ = parser.set_defaults(capture_output=True, color=True, enable_codeblocks=True)
85
97
  return parser
86
98
 
87
99
 
@@ -94,10 +106,12 @@ def main(argv: Sequence[str] | None = None) -> int:
94
106
  Colors.disable()
95
107
 
96
108
  report = run(
97
- paths=tuple(args.paths),
109
+ paths=list(args.paths),
98
110
  pattern=args.pattern,
111
+ mark_expr=args.mark_expr,
99
112
  workers=args.workers,
100
113
  capture_output=args.capture_output,
114
+ enable_codeblocks=args.enable_codeblocks,
101
115
  )
102
116
  _print_report(report, verbose=args.verbose, ascii_mode=args.ascii)
103
117
  return 0 if report.failed == 0 else 1
rustest/core.py CHANGED
@@ -11,11 +11,28 @@ from .reporting import RunReport
11
11
  def run(
12
12
  *,
13
13
  paths: Sequence[str],
14
- pattern: str | None,
15
- workers: int | None,
16
- capture_output: bool,
14
+ pattern: str | None = None,
15
+ mark_expr: str | None = None,
16
+ workers: int | None = None,
17
+ capture_output: bool = True,
18
+ enable_codeblocks: bool = True,
17
19
  ) -> RunReport:
18
- """Execute tests and return a rich report."""
20
+ """Execute tests and return a rich report.
19
21
 
20
- raw_report = rust.run(list(paths), pattern, workers, capture_output)
22
+ Args:
23
+ paths: Files or directories to collect tests from
24
+ pattern: Substring to filter tests by (case insensitive)
25
+ mark_expr: Mark expression to filter tests (e.g., "slow", "not slow", "slow and integration")
26
+ workers: Number of worker slots to use (experimental)
27
+ capture_output: Whether to capture stdout/stderr during test execution
28
+ enable_codeblocks: Whether to enable code block tests from markdown files
29
+ """
30
+ raw_report = rust.run(
31
+ paths=list(paths),
32
+ pattern=pattern,
33
+ mark_expr=mark_expr,
34
+ workers=workers,
35
+ capture_output=capture_output,
36
+ enable_codeblocks=enable_codeblocks,
37
+ )
21
38
  return RunReport.from_py(raw_report)
rustest/decorators.py CHANGED
@@ -157,8 +157,81 @@ class MarkGenerator:
157
157
  @mark.slow
158
158
  @mark.integration
159
159
  @mark.timeout(seconds=30)
160
+
161
+ Standard marks:
162
+ @mark.skipif(condition, *, reason="...")
163
+ @mark.xfail(condition=None, *, reason=None, raises=None, run=True, strict=False)
164
+ @mark.usefixtures("fixture1", "fixture2")
160
165
  """
161
166
 
167
+ def skipif(
168
+ self,
169
+ condition: bool | str,
170
+ *,
171
+ reason: str | None = None,
172
+ ) -> MarkDecorator:
173
+ """Skip test if condition is true.
174
+
175
+ Args:
176
+ condition: Boolean or string condition to evaluate
177
+ reason: Explanation for why the test is skipped
178
+
179
+ Usage:
180
+ @mark.skipif(sys.platform == "win32", reason="Not supported on Windows")
181
+ def test_unix_only():
182
+ pass
183
+ """
184
+ return MarkDecorator("skipif", (condition,), {"reason": reason})
185
+
186
+ def xfail(
187
+ self,
188
+ condition: bool | str | None = None,
189
+ *,
190
+ reason: str | None = None,
191
+ raises: type[BaseException] | tuple[type[BaseException], ...] | None = None,
192
+ run: bool = True,
193
+ strict: bool = False,
194
+ ) -> MarkDecorator:
195
+ """Mark test as expected to fail.
196
+
197
+ Args:
198
+ condition: Optional condition - if False, mark is ignored
199
+ reason: Explanation for why the test is expected to fail
200
+ raises: Expected exception type(s)
201
+ run: Whether to run the test (False means skip it)
202
+ strict: If True, passing test will fail the suite
203
+
204
+ Usage:
205
+ @mark.xfail(reason="Known bug in backend")
206
+ def test_known_bug():
207
+ assert False
208
+
209
+ @mark.xfail(sys.platform == "win32", reason="Not implemented on Windows")
210
+ def test_feature():
211
+ pass
212
+ """
213
+ kwargs = {
214
+ "reason": reason,
215
+ "raises": raises,
216
+ "run": run,
217
+ "strict": strict,
218
+ }
219
+ args = () if condition is None else (condition,)
220
+ return MarkDecorator("xfail", args, kwargs)
221
+
222
+ def usefixtures(self, *names: str) -> MarkDecorator:
223
+ """Use fixtures without explicitly requesting them as parameters.
224
+
225
+ Args:
226
+ *names: Names of fixtures to use
227
+
228
+ Usage:
229
+ @mark.usefixtures("setup_db", "cleanup")
230
+ def test_with_fixtures():
231
+ pass
232
+ """
233
+ return MarkDecorator("usefixtures", names, {})
234
+
162
235
  def __getattr__(self, name: str) -> Any:
163
236
  """Create a mark decorator for the given name."""
164
237
  # Return a callable that can be used as @mark.name or @mark.name(args)
Binary file
rustest/rust.pyi CHANGED
@@ -28,8 +28,10 @@ class PyRunReport:
28
28
  def run(
29
29
  paths: Sequence[str],
30
30
  pattern: str | None,
31
+ mark_expr: str | None,
31
32
  workers: int | None,
32
33
  capture_output: bool,
34
+ enable_codeblocks: bool,
33
35
  ) -> PyRunReport:
34
36
  """Execute tests and return a report."""
35
37
  ...
@@ -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=SkRHcV4Pv7s5Hfo9ywd77-b3wuJtgbPvtrYY6LQoaYk,8225
2
+ rustest-0.5.0.dist-info/WHEEL,sha256=bNaa2-XeaoMXnkzV391Sm2NgCjpJ3A2VmfN6ZUnNTZA,96
3
+ rustest-0.5.0.dist-info/entry_points.txt,sha256=7fUa3LO8vudQ4dKG1sTRaDnxcMdBSZsWs9EyuxFQ7Lk,48
4
+ rustest-0.5.0.dist-info/licenses/LICENSE,sha256=Ci0bB0T1ZGkqIV237Zp_Bv8LIJJ0Vxwc-AhLhgDgAoQ,1096
5
+ rustest/__init__.py,sha256=LL9UloOClzeNO6A-iMkEFtHDrBTAhRLko3sXy55H0QA,542
6
+ rustest/__main__.py,sha256=yMhaWvxGAV46BYY8fB6GoRy9oh8Z8YrS9wlZI3LmoyY,188
7
+ rustest/approx.py,sha256=sGaH15n3vSSv84dmR_QAIDV-xwaUrX-MqwpWIp5ihjk,5904
8
+ rustest/cli.py,sha256=Y7I3sLMA7icsHFLdcGJMy0LElEcoqzrVrTkedE_vGTs,9474
9
+ rustest/core.py,sha256=2_kav-3XPhrtXnayQBDSc_tCaaaEj07rFHq5ln4Em8Q,1227
10
+ rustest/decorators.py,sha256=mCiqNJXYpbQMCMaVEdeiebN9KWLvgtRmqZ6nbUXpm6E,14138
11
+ rustest/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ rustest/reporting.py,sha256=g4jdlwjFoKF_fWA_sKfeDwmhDeHxPJQUqypa_E2XQlc,1686
13
+ rustest/rust.cp311-win_amd64.pyd,sha256=giKggSy_pVhZ4mrH7m2sT88obuBhuJbMdckK4K7Vak4,1344000
14
+ rustest/rust.py,sha256=N_1C-uXRiC2qkV7ecKVcb51-XXyfhYNepd5zs-RIYOo,682
15
+ rustest/rust.pyi,sha256=D2PL2GamLpcwdBDZxFeWC1ZgJ01CkvWycU62LnVTMD0,799
16
+ rustest-0.5.0.dist-info/RECORD,,
@@ -1,658 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: rustest
3
- Version: 0.3.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
- Provides-Extra: dev
23
- License-File: LICENSE
24
- Summary: Rust powered pytest-compatible runner
25
- Author: rustest contributors
26
- Requires-Python: >=3.10
27
- Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
28
- Project-URL: Homepage, https://github.com/Apex-Engineers-Inc/rustest
29
- Project-URL: Repository, https://github.com/Apex-Engineers-Inc/rustest
30
-
31
- # rustest
32
-
33
- 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.
34
-
35
- ## Why rustest?
36
-
37
- - 🚀 **About 2x faster** than pytest on the rustest integration test suite
38
- - ✅ Familiar `@fixture`, `@parametrize`, `@skip`, and `@mark` decorators
39
- - 🔍 Automatic test discovery (`test_*.py` and `*_test.py` files)
40
- - 🎯 Simple, clean API—if you know pytest, you already know rustest
41
- - 🧮 Built-in `approx()` helper for tolerant numeric comparisons across scalars, collections, and complex numbers
42
- - 🪤 `raises()` context manager for precise exception assertions with optional message matching
43
- - 📦 Easy installation with pip or uv
44
- - ⚡ Low-overhead execution keeps small suites feeling instant
45
-
46
- ## Performance
47
-
48
- 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:
49
-
50
- | Test Runner | Reported Runtime† | Wall Clock‡ | Speedup (wall) | Command |
51
- |-------------|------------------|-------------|----------------|---------|
52
- | pytest | 0.43–0.59s | 1.33–1.59s | 1.0x (baseline) | `pytest tests/ examples/tests/ -q`
53
- | rustest | 0.003s | 0.69–0.70s | **~2.1x faster** | `python -m rustest tests/ examples/tests/`§
54
-
55
- ### Large parametrized stress test
56
-
57
- We also profiled an extreme case with **10,000 parametrized invocations** to ensure rustest scales on synthetic but heavy workloads. The test lives in [`benchmarks/test_large_parametrize.py`](benchmarks/test_large_parametrize.py) and simply asserts `value + value == 2 * value` across every case. Running the module on its own shows a dramatic gap:
58
-
59
- | Test Runner | Avg. Wall Clock (3 runs) | Speedup | Command |
60
- |-------------|--------------------------|---------|---------|
61
- | pytest | 9.72s | 1.0x | `pytest benchmarks/test_large_parametrize.py -q`§
62
- | rustest | 0.41s | **~24x** | `python -m rustest benchmarks/test_large_parametrize.py`§
63
-
64
- † pytest and rustest both report only active test execution time; rustest's figure omits Python interpreter start-up overhead.
65
-
66
- ‡ Integration-suite wall-clock timing measured with the shell `time` builtin across two consecutive runs in the same environment.
67
-
68
- § Commands executed with `PYTHONPATH=python` in this repository checkout to exercise the local sources. Pytest relies on a small compatibility shim in [`benchmarks/conftest.py`](benchmarks/conftest.py) so it understands the rustest-style decorators. Large-parametrization timings come from averaging three `time.perf_counter()` measurements with output suppressed via `subprocess.DEVNULL`.
69
-
70
- Rustest counts parametrized cases slightly differently than pytest, so you will see 199 executed cases vs. pytest's 201 discoveries on the same suite—the reported pass/skip counts still align.
71
-
72
- **Why is rustest faster?**
73
- - **Near-zero startup time**: Native Rust binary minimizes overhead before Python code starts running.
74
- - **Rust-native test discovery**: Minimal imports until test execution keeps collection quick.
75
- - **Optimized fixture resolution**: Efficient dependency graph resolution reduces per-test work.
76
- - **Lean orchestration**: Rust handles scheduling and reporting so the Python interpreter focuses on running test bodies.
77
-
78
- **Real-world impact:**
79
- - **200 tests** (this repository): 1.46s → 0.70s (average wall-clock, ~0.76s saved per run)
80
- - **1,000 tests** (projected): ~7.3s → ~3.4s assuming similar scaling
81
- - **10,000 tests** (projected): ~73s → ~34s—minutes saved across CI runs
82
-
83
- See [BENCHMARKS.md](BENCHMARKS.md) for detailed performance analysis and methodology.
84
-
85
- ## Installation
86
-
87
- Rustest supports Python **3.10 through 3.14**.
88
-
89
- ### Using pip
90
- ```bash
91
- pip install rustest
92
- ```
93
-
94
- ### Using uv
95
- ```bash
96
- uv add rustest
97
- ```
98
-
99
- ### For Development
100
- If you want to contribute to rustest, see [DEVELOPMENT.md](DEVELOPMENT.md) for setup instructions.
101
-
102
- ## Quick Start
103
-
104
- ### 1. Write Your Tests
105
-
106
- Create a file `test_math.py`:
107
-
108
- ```python
109
- from rustest import fixture, parametrize, mark, approx, raises
110
-
111
- @fixture
112
- def numbers() -> list[int]:
113
- return [1, 2, 3, 4, 5]
114
-
115
- def test_sum(numbers: list[int]) -> None:
116
- assert sum(numbers) == approx(15)
117
-
118
- @parametrize("value,expected", [(2, 4), (3, 9), (4, 16)])
119
- def test_square(value: int, expected: int) -> None:
120
- assert value ** 2 == expected
121
-
122
- @mark.slow
123
- def test_expensive_operation() -> None:
124
- # This test is marked as slow for filtering
125
- result = sum(range(1000000))
126
- assert result > 0
127
-
128
- def test_division_by_zero_is_reported() -> None:
129
- with raises(ZeroDivisionError, match="division by zero"):
130
- 1 / 0
131
- ```
132
-
133
- ### 2. Run Your Tests
134
-
135
- ```bash
136
- # Run all tests in the current directory
137
- rustest
138
-
139
- # Run tests in a specific directory
140
- rustest tests/
141
-
142
- # Run tests matching a pattern
143
- rustest -k "test_sum"
144
-
145
- # Show output during test execution
146
- rustest --no-capture
147
- ```
148
-
149
- ## Usage Examples
150
-
151
- ### CLI Usage
152
-
153
- ```bash
154
- # Run all tests in current directory
155
- rustest
156
-
157
- # Run tests in specific paths
158
- rustest tests/ integration/
159
-
160
- # Filter tests by name pattern
161
- rustest -k "user" # Runs test_user_login, test_user_signup, etc.
162
- rustest -k "auth" # Runs all tests with "auth" in the name
163
-
164
- # Control output capture
165
- rustest --no-capture # See print statements during test execution
166
- ```
167
-
168
- ### Python API Usage
169
-
170
- You can also run rustest programmatically from Python:
171
-
172
- ```python
173
- from rustest import run
174
-
175
- # Basic usage
176
- report = run(paths=["tests"])
177
- print(f"Passed: {report.passed}, Failed: {report.failed}")
178
-
179
- # With pattern filtering
180
- report = run(paths=["tests"], pattern="user")
181
-
182
- # Without output capture (see print statements)
183
- report = run(paths=["tests"], capture_output=False)
184
-
185
- # Access individual test results
186
- for result in report.results:
187
- print(f"{result.name}: {result.status} ({result.duration:.3f}s)")
188
- if result.status == "failed":
189
- print(f" Error: {result.message}")
190
- ```
191
-
192
- ### Writing Tests
193
-
194
- #### Basic Test Functions
195
-
196
- ```python
197
- def test_simple_assertion() -> None:
198
- assert 1 + 1 == 2
199
-
200
- def test_string_operations() -> None:
201
- text = "hello world"
202
- assert text.startswith("hello")
203
- assert "world" in text
204
- ```
205
-
206
- #### Using Fixtures
207
-
208
- Fixtures provide reusable test data and setup:
209
-
210
- ```python
211
- from rustest import fixture
212
-
213
- @fixture
214
- def database_connection() -> dict:
215
- # Setup: create a connection
216
- conn = {"host": "localhost", "port": 5432}
217
- return conn
218
- # Teardown happens automatically
219
-
220
- @fixture
221
- def sample_user() -> dict:
222
- return {"id": 1, "name": "Alice", "email": "alice@example.com"}
223
-
224
- def test_database_query(database_connection: dict) -> None:
225
- assert database_connection["host"] == "localhost"
226
-
227
- def test_user_email(sample_user: dict) -> None:
228
- assert "@" in sample_user["email"]
229
- ```
230
-
231
- #### Fixtures with Dependencies
232
-
233
- Fixtures can depend on other fixtures:
234
-
235
- ```python
236
- from rustest import fixture
237
-
238
- @fixture
239
- def api_url() -> str:
240
- return "https://api.example.com"
241
-
242
- @fixture
243
- def api_client(api_url: str) -> dict:
244
- return {"base_url": api_url, "timeout": 30}
245
-
246
- def test_api_configuration(api_client: dict) -> None:
247
- assert api_client["base_url"].startswith("https://")
248
- assert api_client["timeout"] == 30
249
- ```
250
-
251
- #### Assertion Helpers
252
-
253
- Rustest ships helpers for expressive assertions:
254
-
255
- ```python
256
- from rustest import approx, raises
257
-
258
- def test_nearly_equal() -> None:
259
- assert 0.1 + 0.2 == approx(0.3, rel=1e-9)
260
-
261
- def test_raises_with_message() -> None:
262
- with raises(ValueError, match="invalid configuration"):
263
- raise ValueError("invalid configuration")
264
- ```
265
-
266
- #### Yield Fixtures with Setup/Teardown
267
-
268
- Fixtures can use `yield` to perform cleanup after tests:
269
-
270
- ```python
271
- from rustest import fixture
272
-
273
- @fixture
274
- def database_connection():
275
- # Setup: create connection
276
- conn = create_db_connection()
277
- print("Database connected")
278
-
279
- yield conn
280
-
281
- # Teardown: close connection
282
- conn.close()
283
- print("Database connection closed")
284
-
285
- @fixture
286
- def temp_file():
287
- # Setup
288
- file = open("temp.txt", "w")
289
- file.write("test data")
290
-
291
- yield file
292
-
293
- # Teardown
294
- file.close()
295
- os.remove("temp.txt")
296
-
297
- def test_database_query(database_connection):
298
- result = database_connection.query("SELECT 1")
299
- assert result is not None
300
- ```
301
-
302
- #### Fixture Scopes
303
-
304
- Fixtures support different scopes to control when they are created and destroyed:
305
-
306
- ```python
307
- from rustest import fixture
308
-
309
- @fixture # Default: function scope - new instance per test
310
- def function_fixture() -> dict:
311
- return {"value": "reset each test"}
312
-
313
- @fixture(scope="class") # Shared across all tests in a class
314
- def class_database() -> dict:
315
- return {"connection": "db://test", "shared": True}
316
-
317
- @fixture(scope="module") # Shared across all tests in a module
318
- def module_config() -> dict:
319
- return {"env": "test", "timeout": 30}
320
-
321
- @fixture(scope="session") # Shared across entire test session
322
- def session_cache() -> dict:
323
- return {"global_cache": {}}
324
-
325
- # Fixtures can depend on fixtures with different scopes
326
- @fixture(scope="function")
327
- def request_handler(module_config: dict, session_cache: dict) -> dict:
328
- return {
329
- "config": module_config, # module-scoped
330
- "cache": session_cache, # session-scoped
331
- "request_id": id(object()) # unique per test
332
- }
333
- ```
334
-
335
- **Scope Behavior:**
336
- - `function` (default): New instance for each test function
337
- - `class`: Shared across all test methods in a test class
338
- - `module`: Shared across all tests in a Python module
339
- - `session`: Shared across the entire test session
340
-
341
- Scoped fixtures are especially useful for expensive setup operations like database connections, API clients, or configuration loading.
342
-
343
- **Using conftest.py for Shared Fixtures:**
344
-
345
- You can define fixtures in a `conftest.py` file to share them across multiple test files:
346
-
347
- ```python
348
- # conftest.py
349
- from rustest import fixture
350
-
351
- @fixture(scope="session")
352
- def database():
353
- """Shared database connection for all tests."""
354
- db = setup_database()
355
- yield db
356
- db.cleanup()
357
-
358
- @fixture(scope="module")
359
- def api_client():
360
- """API client shared across a module."""
361
- return create_api_client()
362
- ```
363
-
364
- All test files in the same directory (and subdirectories) can use these fixtures automatically.
365
-
366
- #### Parametrized Tests
367
-
368
- Run the same test with different inputs:
369
-
370
- ```python
371
- from rustest import parametrize
372
-
373
- @parametrize("input,expected", [
374
- (1, 2),
375
- (2, 4),
376
- (3, 6),
377
- ])
378
- def test_double(input: int, expected: int) -> None:
379
- assert input * 2 == expected
380
-
381
- # With custom test IDs for better output
382
- @parametrize("value,expected", [
383
- (2, 4),
384
- (3, 9),
385
- (4, 16),
386
- ], ids=["two", "three", "four"])
387
- def test_square(value: int, expected: int) -> None:
388
- assert value ** 2 == expected
389
- ```
390
-
391
- #### Combining Fixtures and Parameters
392
-
393
- ```python
394
- from rustest import fixture, parametrize
395
-
396
- @fixture
397
- def multiplier() -> int:
398
- return 10
399
-
400
- @parametrize("value,expected", [
401
- (1, 10),
402
- (2, 20),
403
- (3, 30),
404
- ])
405
- def test_multiply(multiplier: int, value: int, expected: int) -> None:
406
- assert multiplier * value == expected
407
- ```
408
-
409
- #### Skipping Tests
410
-
411
- ```python
412
- from rustest import skip, mark
413
-
414
- @skip("Not implemented yet")
415
- def test_future_feature() -> None:
416
- assert False
417
-
418
- @mark.skip(reason="Waiting for API update")
419
- def test_deprecated_api() -> None:
420
- assert False
421
- ```
422
-
423
- #### Using Marks to Organize Tests
424
-
425
- ```python
426
- from rustest import mark
427
-
428
- @mark.unit
429
- def test_calculation() -> None:
430
- assert 2 + 2 == 4
431
-
432
- @mark.integration
433
- def test_database_integration() -> None:
434
- # Integration test
435
- pass
436
-
437
- @mark.slow
438
- @mark.integration
439
- def test_full_workflow() -> None:
440
- # This test has multiple marks
441
- pass
442
- ```
443
-
444
- #### Test Classes
445
-
446
- Rustest supports pytest-style test classes, allowing you to organize related tests together:
447
-
448
- ```python
449
- from rustest import fixture, parametrize, mark
450
-
451
- class TestBasicMath:
452
- """Group related tests in a class."""
453
-
454
- def test_addition(self):
455
- assert 1 + 1 == 2
456
-
457
- def test_subtraction(self):
458
- assert 5 - 3 == 2
459
-
460
- def test_multiplication(self):
461
- assert 3 * 4 == 12
462
- ```
463
-
464
- **Using Fixtures in Test Classes:**
465
-
466
- Test methods can inject fixtures just like standalone test functions:
467
-
468
- ```python
469
- from rustest import fixture
470
-
471
- @fixture
472
- def calculator():
473
- return {"add": lambda x, y: x + y, "multiply": lambda x, y: x * y}
474
-
475
- class TestCalculator:
476
- """Test class using fixtures."""
477
-
478
- def test_addition(self, calculator):
479
- assert calculator["add"](2, 3) == 5
480
-
481
- def test_multiplication(self, calculator):
482
- assert calculator["multiply"](4, 5) == 20
483
- ```
484
-
485
- **Class-Scoped Fixtures:**
486
-
487
- Class-scoped fixtures are shared across all test methods in the same class, perfect for expensive setup operations:
488
-
489
- ```python
490
- from rustest import fixture
491
-
492
- @fixture(scope="class")
493
- def database():
494
- """Expensive setup shared across all tests in a class."""
495
- db = {"connection": "db://test", "data": []}
496
- return db
497
-
498
- class TestDatabase:
499
- """All tests share the same database fixture instance."""
500
-
501
- def test_connection(self, database):
502
- assert database["connection"] == "db://test"
503
-
504
- def test_add_data(self, database):
505
- database["data"].append("item1")
506
- assert len(database["data"]) >= 1
507
-
508
- def test_data_persists(self, database):
509
- # Same database instance, so previous test's data is still there
510
- assert len(database["data"]) >= 1
511
- ```
512
-
513
- **Fixture Methods Within Test Classes:**
514
-
515
- You can define fixtures as methods inside test classes, providing class-specific setup:
516
-
517
- ```python
518
- from rustest import fixture
519
-
520
- class TestWithFixtureMethod:
521
- """Test class with its own fixture methods."""
522
-
523
- @fixture(scope="class")
524
- def class_resource(self):
525
- """Fixture method shared across tests in this class."""
526
- resource = {"value": 42, "name": "test_resource"}
527
- yield resource
528
- # Teardown happens after all tests in class
529
- resource["closed"] = True
530
-
531
- @fixture
532
- def per_test_data(self, class_resource):
533
- """Fixture method that depends on another fixture."""
534
- return {"id": id(self), "resource": class_resource}
535
-
536
- def test_uses_class_resource(self, class_resource):
537
- assert class_resource["value"] == 42
538
-
539
- def test_uses_per_test_data(self, per_test_data):
540
- assert "resource" in per_test_data
541
- assert per_test_data["resource"]["value"] == 42
542
- ```
543
-
544
- **Class Variables and Instance Variables:**
545
-
546
- Test classes can use class variables for shared state and instance variables for per-test isolation:
547
-
548
- ```python
549
- class TestWithVariables:
550
- """Test class with class and instance variables."""
551
-
552
- class_variable = "shared_data" # Shared across all tests
553
-
554
- def test_class_variable(self):
555
- # Access class variable
556
- assert self.class_variable == "shared_data"
557
- assert TestWithVariables.class_variable == "shared_data"
558
-
559
- def test_instance_variable(self):
560
- # Each test gets a fresh instance
561
- self.instance_var = "test_specific"
562
- assert self.instance_var == "test_specific"
563
- ```
564
-
565
- **Parametrized Test Methods:**
566
-
567
- Use `@parametrize` on class methods just like regular test functions:
568
-
569
- ```python
570
- from rustest import parametrize
571
-
572
- class TestParametrized:
573
- """Test class with parametrized methods."""
574
-
575
- @parametrize("value,expected", [(2, 4), (3, 9), (4, 16)])
576
- def test_square(self, value, expected):
577
- assert value ** 2 == expected
578
- ```
579
-
580
- ### Test Output
581
-
582
- When you run rustest, you'll see clean, informative output:
583
-
584
- ```
585
- PASSED 0.001s test_simple_assertion
586
- PASSED 0.002s test_string_operations
587
- PASSED 0.001s test_database_query
588
- PASSED 0.003s test_square[two]
589
- PASSED 0.001s test_square[three]
590
- PASSED 0.002s test_square[four]
591
- SKIPPED 0.000s test_future_feature
592
- FAILED 0.005s test_broken_feature
593
- ----------------------------------------
594
- AssertionError: Expected 5, got 4
595
- at test_example.py:42
596
-
597
- 8 tests: 6 passed, 1 failed, 1 skipped in 0.015s
598
- ```
599
-
600
- ## Feature Comparison with pytest
601
-
602
- Rustest aims to provide the most commonly-used pytest features with dramatically better performance. Here's how the two compare:
603
-
604
- | Feature | pytest | rustest | Notes |
605
- |---------|--------|---------|-------|
606
- | **Core Test Discovery** |
607
- | `test_*.py` / `*_test.py` files | ✅ | ✅ | Rustest uses Rust for dramatically faster discovery |
608
- | Test function detection (`test_*`) | ✅ | ✅ | |
609
- | Test class detection (`Test*`) | ✅ | ✅ | Full pytest-style class support with fixture methods |
610
- | Pattern-based filtering | ✅ | ✅ | `-k` pattern matching |
611
- | **Fixtures** |
612
- | `@fixture` decorator | ✅ | ✅ | Rust-based dependency resolution |
613
- | Fixture dependency injection | ✅ | ✅ | Much faster in rustest |
614
- | Fixture scopes (function/class/module/session) | ✅ | ✅ | Full support for all scopes |
615
- | Yield fixtures (setup/teardown) | ✅ | ✅ | Full support with cleanup |
616
- | Fixture methods within test classes | ✅ | ✅ | Define fixtures as class methods |
617
- | Fixture parametrization | ✅ | 🚧 | Planned |
618
- | **Parametrization** |
619
- | `@parametrize` decorator | ✅ | ✅ | Full support with custom IDs |
620
- | Multiple parameter sets | ✅ | ✅ | |
621
- | Parametrize with fixtures | ✅ | ✅ | |
622
- | **Marks** |
623
- | `@mark.skip` / `@skip` | ✅ | ✅ | Skip tests with reasons |
624
- | Custom marks (`@mark.slow`, etc.) | ✅ | ✅ | Just added! |
625
- | Mark with arguments | ✅ | ✅ | `@mark.timeout(30)` |
626
- | Selecting tests by mark (`-m`) | ✅ | 🚧 | Mark metadata collected, filtering planned |
627
- | **Test Execution** |
628
- | Detailed assertion introspection | ✅ | ❌ | Uses standard Python assertions |
629
- | Parallel execution | ✅ (`pytest-xdist`) | 🚧 | Planned (Rust makes this easier) |
630
- | Test isolation | ✅ | ✅ | |
631
- | Stdout/stderr capture | ✅ | ✅ | |
632
- | **Reporting** |
633
- | Pass/fail/skip summary | ✅ | ✅ | |
634
- | Failure tracebacks | ✅ | ✅ | Full Python traceback support |
635
- | Duration reporting | ✅ | ✅ | Per-test timing |
636
- | JUnit XML output | ✅ | 🚧 | Planned |
637
- | HTML reports | ✅ (`pytest-html`) | 🚧 | Planned |
638
- | **Advanced Features** |
639
- | Plugins | ✅ | ❌ | Not planned (keeps rustest simple) |
640
- | Hooks | ✅ | ❌ | Not planned |
641
- | Custom collectors | ✅ | ❌ | Not planned |
642
- | `conftest.py` | ✅ | ✅ | Shared fixtures across test files |
643
- | **Developer Experience** |
644
- | Fully typed Python API | ⚠️ | ✅ | rustest uses `basedpyright` strict mode |
645
- | Fast CI/CD runs | ⚠️ | ✅ | 78x faster = dramatically shorter feedback loops |
646
-
647
- **Legend:**
648
- - ✅ Fully supported
649
- - 🚧 Planned or in progress
650
- - ⚠️ Partial support
651
- - ❌ Not planned
652
-
653
- **Philosophy:** Rustest implements the 20% of pytest features that cover 80% of use cases, with a focus on raw speed and simplicity. If you need advanced pytest features like plugins or custom hooks, stick with pytest. If you want fast, straightforward testing with familiar syntax, rustest is for you.
654
-
655
- ## License
656
-
657
- rustest is distributed under the terms of the MIT license. See [LICENSE](LICENSE).
658
-
@@ -1,16 +0,0 @@
1
- rustest-0.3.0.dist-info/METADATA,sha256=esXlixx0RIYl37WTKb13SkHQ2HrtF7LJDM698ymGoo8,20976
2
- rustest-0.3.0.dist-info/WHEEL,sha256=bNaa2-XeaoMXnkzV391Sm2NgCjpJ3A2VmfN6ZUnNTZA,96
3
- rustest-0.3.0.dist-info/entry_points.txt,sha256=7fUa3LO8vudQ4dKG1sTRaDnxcMdBSZsWs9EyuxFQ7Lk,48
4
- rustest-0.3.0.dist-info/licenses/LICENSE,sha256=Ci0bB0T1ZGkqIV237Zp_Bv8LIJJ0Vxwc-AhLhgDgAoQ,1096
5
- rustest/__init__.py,sha256=LL9UloOClzeNO6A-iMkEFtHDrBTAhRLko3sXy55H0QA,542
6
- rustest/__main__.py,sha256=yMhaWvxGAV46BYY8fB6GoRy9oh8Z8YrS9wlZI3LmoyY,188
7
- rustest/approx.py,sha256=sGaH15n3vSSv84dmR_QAIDV-xwaUrX-MqwpWIp5ihjk,5904
8
- rustest/cli.py,sha256=-y-NDR9ndxK21Rlunm-Z5I0hPn95SdZ6RqHRG25KNVI,8958
9
- rustest/core.py,sha256=oMNUz_9oa3j3FmKn41ttZxcS3IaOjRglYEKo46zifms,506
10
- rustest/decorators.py,sha256=21PNOMl9nZoAm8kUnms9_tkhX5gAGNvr7xYG8kwESts,11723
11
- rustest/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- rustest/reporting.py,sha256=g4jdlwjFoKF_fWA_sKfeDwmhDeHxPJQUqypa_E2XQlc,1686
13
- rustest/rust.cp311-win_amd64.pyd,sha256=kRwTphiGE2mF4hT7zZ_suOr9d03h92AHGfpu4DvGCWc,1303040
14
- rustest/rust.py,sha256=N_1C-uXRiC2qkV7ecKVcb51-XXyfhYNepd5zs-RIYOo,682
15
- rustest/rust.pyi,sha256=rV1Msn2dGUeQ_GuPSoKjkgcFaoEehXD_QtzCY4-77_M,741
16
- rustest-0.3.0.dist-info/RECORD,,