rustest 0.2.0__cp313-cp313-macosx_10_12_x86_64.whl → 0.7.0__cp313-cp313-macosx_10_12_x86_64.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 CHANGED
@@ -2,17 +2,17 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from . import _decorators
6
- from ._approx import approx
7
- from ._cli import main
8
- from ._reporting import RunReport, TestResult
5
+ from . import decorators
6
+ from .approx import approx
7
+ from .cli import main
8
+ from .reporting import RunReport, TestResult
9
9
  from .core import run
10
10
 
11
- fixture = _decorators.fixture
12
- mark = _decorators.mark
13
- parametrize = _decorators.parametrize
14
- raises = _decorators.raises
15
- skip = _decorators.skip
11
+ fixture = decorators.fixture
12
+ mark = decorators.mark
13
+ parametrize = decorators.parametrize
14
+ raises = decorators.raises
15
+ skip = decorators.skip
16
16
 
17
17
  __all__ = [
18
18
  "RunReport",
rustest/__main__.py CHANGED
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import sys
6
6
 
7
- from ._cli import main
7
+ from .cli import main
8
8
 
9
9
  if __name__ == "__main__":
10
10
  sys.exit(main())
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import argparse
6
6
  from collections.abc import Sequence
7
7
 
8
- from ._reporting import RunReport, TestResult
8
+ from .reporting import RunReport, TestResult
9
9
  from .core import run
10
10
 
11
11
 
@@ -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,41 @@ 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.add_argument(
97
+ "--lf",
98
+ "--last-failed",
99
+ action="store_true",
100
+ dest="last_failed",
101
+ help="Rerun only the tests that failed in the last run.",
102
+ )
103
+ _ = parser.add_argument(
104
+ "--ff",
105
+ "--failed-first",
106
+ action="store_true",
107
+ dest="failed_first",
108
+ help="Run previously failed tests first, then all other tests.",
109
+ )
110
+ _ = parser.add_argument(
111
+ "-x",
112
+ "--exitfirst",
113
+ action="store_true",
114
+ dest="fail_fast",
115
+ help="Exit instantly on first error or failed test.",
116
+ )
117
+ _ = parser.set_defaults(
118
+ capture_output=True,
119
+ color=True,
120
+ enable_codeblocks=True,
121
+ last_failed=False,
122
+ failed_first=False,
123
+ fail_fast=False,
124
+ )
85
125
  return parser
86
126
 
87
127
 
@@ -93,11 +133,23 @@ def main(argv: Sequence[str] | None = None) -> int:
93
133
  if not args.color:
94
134
  Colors.disable()
95
135
 
136
+ # Determine last_failed_mode
137
+ if args.last_failed:
138
+ last_failed_mode = "only"
139
+ elif args.failed_first:
140
+ last_failed_mode = "first"
141
+ else:
142
+ last_failed_mode = "none"
143
+
96
144
  report = run(
97
- paths=tuple(args.paths),
145
+ paths=list(args.paths),
98
146
  pattern=args.pattern,
147
+ mark_expr=args.mark_expr,
99
148
  workers=args.workers,
100
149
  capture_output=args.capture_output,
150
+ enable_codeblocks=args.enable_codeblocks,
151
+ last_failed_mode=last_failed_mode,
152
+ fail_fast=args.fail_fast,
101
153
  )
102
154
  _print_report(report, verbose=args.verbose, ascii_mode=args.ascii)
103
155
  return 0 if report.failed == 0 else 1
rustest/core.py CHANGED
@@ -4,18 +4,41 @@ from __future__ import annotations
4
4
 
5
5
  from collections.abc import Sequence
6
6
 
7
- from . import _rust
8
- from ._reporting import RunReport
7
+ from . import rust
8
+ from .reporting import RunReport
9
9
 
10
10
 
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,
19
+ last_failed_mode: str = "none",
20
+ fail_fast: bool = False,
17
21
  ) -> RunReport:
18
- """Execute tests and return a rich report."""
22
+ """Execute tests and return a rich report.
19
23
 
20
- raw_report = _rust.run(list(paths), pattern, workers, capture_output)
24
+ Args:
25
+ paths: Files or directories to collect tests from
26
+ pattern: Substring to filter tests by (case insensitive)
27
+ mark_expr: Mark expression to filter tests (e.g., "slow", "not slow", "slow and integration")
28
+ workers: Number of worker slots to use (experimental)
29
+ capture_output: Whether to capture stdout/stderr during test execution
30
+ enable_codeblocks: Whether to enable code block tests from markdown files
31
+ last_failed_mode: Last failed mode: "none", "only", or "first"
32
+ fail_fast: Exit instantly on first error or failed test
33
+ """
34
+ raw_report = rust.run(
35
+ paths=list(paths),
36
+ pattern=pattern,
37
+ mark_expr=mark_expr,
38
+ workers=workers,
39
+ capture_output=capture_output,
40
+ enable_codeblocks=enable_codeblocks,
41
+ last_failed_mode=last_failed_mode,
42
+ fail_fast=fail_fast,
43
+ )
21
44
  return RunReport.from_py(raw_report)
@@ -157,8 +157,195 @@ 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")
165
+ @mark.asyncio(loop_scope="function")
160
166
  """
161
167
 
168
+ def asyncio(
169
+ self,
170
+ func: Callable[..., Any] | None = None,
171
+ *,
172
+ loop_scope: str = "function",
173
+ ) -> Callable[..., Any]:
174
+ """Mark an async test function to be executed with asyncio.
175
+
176
+ This decorator allows you to write async test functions that will be
177
+ automatically executed in an asyncio event loop. The loop_scope parameter
178
+ controls the scope of the event loop used for execution.
179
+
180
+ Args:
181
+ func: The function to decorate (when used without parentheses)
182
+ loop_scope: The scope of the event loop. One of:
183
+ - "function": New loop for each test function (default)
184
+ - "class": Shared loop across all test methods in a class
185
+ - "module": Shared loop across all tests in a module
186
+ - "session": Shared loop across all tests in the session
187
+
188
+ Usage:
189
+ @mark.asyncio
190
+ async def test_async_function():
191
+ result = await some_async_operation()
192
+ assert result == expected
193
+
194
+ @mark.asyncio(loop_scope="module")
195
+ async def test_with_module_loop():
196
+ await another_async_operation()
197
+
198
+ Note:
199
+ This decorator should only be applied to async functions (coroutines).
200
+ Applying it to regular functions will raise a TypeError.
201
+ """
202
+ import asyncio
203
+ import inspect
204
+ from functools import wraps
205
+
206
+ valid_scopes = {"function", "class", "module", "session"}
207
+ if loop_scope not in valid_scopes:
208
+ valid = ", ".join(sorted(valid_scopes))
209
+ msg = f"Invalid loop_scope '{loop_scope}'. Must be one of: {valid}"
210
+ raise ValueError(msg)
211
+
212
+ def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
213
+ # Handle class decoration - apply mark to all async methods
214
+ if inspect.isclass(f):
215
+ # Apply the mark to the class itself
216
+ mark_decorator = MarkDecorator("asyncio", (), {"loop_scope": loop_scope})
217
+ marked_class = mark_decorator(f)
218
+
219
+ # Wrap all async methods in the class
220
+ for name, method in inspect.getmembers(
221
+ marked_class, predicate=inspect.iscoroutinefunction
222
+ ):
223
+ wrapped_method = _wrap_async_function(method, loop_scope)
224
+ setattr(marked_class, name, wrapped_method)
225
+ return marked_class
226
+
227
+ # Validate that the function is a coroutine
228
+ if not inspect.iscoroutinefunction(f):
229
+ msg = f"@mark.asyncio can only be applied to async functions or test classes, but '{f.__name__}' is not async"
230
+ raise TypeError(msg)
231
+
232
+ # Store the asyncio mark
233
+ mark_decorator = MarkDecorator("asyncio", (), {"loop_scope": loop_scope})
234
+ marked_f = mark_decorator(f)
235
+
236
+ # Wrap the async function to run it synchronously
237
+ return _wrap_async_function(marked_f, loop_scope)
238
+
239
+ def _wrap_async_function(f: Callable[..., Any], loop_scope: str) -> Callable[..., Any]:
240
+ """Wrap an async function to run it synchronously in an event loop."""
241
+
242
+ @wraps(f)
243
+ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
244
+ # Get or create event loop based on scope
245
+ # For now, we'll always create a new loop - scope handling will be
246
+ # implemented in a future enhancement via fixtures
247
+ loop = asyncio.new_event_loop()
248
+ asyncio.set_event_loop(loop)
249
+ try:
250
+ # Run the coroutine in the event loop
251
+ # Get the original async function
252
+ original_func = getattr(f, "__wrapped__", f)
253
+ coro = original_func(*args, **kwargs)
254
+ return loop.run_until_complete(coro)
255
+ finally:
256
+ # Clean up the loop
257
+ try:
258
+ # Cancel any pending tasks
259
+ pending = asyncio.all_tasks(loop)
260
+ for task in pending:
261
+ task.cancel()
262
+ # Run the loop one more time to let tasks finish cancellation
263
+ if pending:
264
+ loop.run_until_complete(
265
+ asyncio.gather(*pending, return_exceptions=True)
266
+ )
267
+ except Exception:
268
+ pass
269
+ finally:
270
+ loop.close()
271
+
272
+ # Store reference to original async function
273
+ sync_wrapper.__wrapped__ = f
274
+ return sync_wrapper
275
+
276
+ # Support both @mark.asyncio and @mark.asyncio(loop_scope="...")
277
+ if func is not None:
278
+ return decorator(func)
279
+ return decorator
280
+
281
+ def skipif(
282
+ self,
283
+ condition: bool | str,
284
+ *,
285
+ reason: str | None = None,
286
+ ) -> MarkDecorator:
287
+ """Skip test if condition is true.
288
+
289
+ Args:
290
+ condition: Boolean or string condition to evaluate
291
+ reason: Explanation for why the test is skipped
292
+
293
+ Usage:
294
+ @mark.skipif(sys.platform == "win32", reason="Not supported on Windows")
295
+ def test_unix_only():
296
+ pass
297
+ """
298
+ return MarkDecorator("skipif", (condition,), {"reason": reason})
299
+
300
+ def xfail(
301
+ self,
302
+ condition: bool | str | None = None,
303
+ *,
304
+ reason: str | None = None,
305
+ raises: type[BaseException] | tuple[type[BaseException], ...] | None = None,
306
+ run: bool = True,
307
+ strict: bool = False,
308
+ ) -> MarkDecorator:
309
+ """Mark test as expected to fail.
310
+
311
+ Args:
312
+ condition: Optional condition - if False, mark is ignored
313
+ reason: Explanation for why the test is expected to fail
314
+ raises: Expected exception type(s)
315
+ run: Whether to run the test (False means skip it)
316
+ strict: If True, passing test will fail the suite
317
+
318
+ Usage:
319
+ @mark.xfail(reason="Known bug in backend")
320
+ def test_known_bug():
321
+ assert False
322
+
323
+ @mark.xfail(sys.platform == "win32", reason="Not implemented on Windows")
324
+ def test_feature():
325
+ pass
326
+ """
327
+ kwargs = {
328
+ "reason": reason,
329
+ "raises": raises,
330
+ "run": run,
331
+ "strict": strict,
332
+ }
333
+ args = () if condition is None else (condition,)
334
+ return MarkDecorator("xfail", args, kwargs)
335
+
336
+ def usefixtures(self, *names: str) -> MarkDecorator:
337
+ """Use fixtures without explicitly requesting them as parameters.
338
+
339
+ Args:
340
+ *names: Names of fixtures to use
341
+
342
+ Usage:
343
+ @mark.usefixtures("setup_db", "cleanup")
344
+ def test_with_fixtures():
345
+ pass
346
+ """
347
+ return MarkDecorator("usefixtures", names, {})
348
+
162
349
  def __getattr__(self, name: str) -> Any:
163
350
  """Create a mark decorator for the given name."""
164
351
  # Return a callable that can be used as @mark.name or @mark.name(args)
rustest/py.typed ADDED
File without changes
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  from collections.abc import Iterable
6
6
  from dataclasses import dataclass
7
7
 
8
- from . import _rust
8
+ from . import rust
9
9
 
10
10
 
11
11
  @dataclass(slots=True)
@@ -23,7 +23,7 @@ class TestResult:
23
23
  stderr: str | None
24
24
 
25
25
  @classmethod
26
- def from_py(cls, result: _rust.PyTestResult) -> "TestResult":
26
+ def from_py(cls, result: rust.PyTestResult) -> "TestResult":
27
27
  return cls(
28
28
  name=result.name,
29
29
  path=result.path,
@@ -47,7 +47,7 @@ class RunReport:
47
47
  results: tuple[TestResult, ...]
48
48
 
49
49
  @classmethod
50
- def from_py(cls, report: _rust.PyRunReport) -> "RunReport":
50
+ def from_py(cls, report: rust.PyRunReport) -> "RunReport":
51
51
  return cls(
52
52
  total=report.total,
53
53
  passed=report.passed,
Binary file
@@ -19,5 +19,5 @@ def run(
19
19
  """Placeholder implementation that mirrors the extension signature."""
20
20
 
21
21
  raise NotImplementedError(
22
- "The rustest native extension is unavailable. Tests must patch rustest._rust.run."
22
+ "The rustest native extension is unavailable. Tests must patch rustest.rust.run."
23
23
  )
@@ -1,11 +1,11 @@
1
- """Type stubs for the Rust extension module."""
1
+ """Type stubs for the rustest Rust extension module."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from collections.abc import Sequence
5
+ from typing import Sequence
6
6
 
7
7
  class PyTestResult:
8
- """Test result from Rust layer."""
8
+ """Individual test result from the Rust extension."""
9
9
 
10
10
  name: str
11
11
  path: str
@@ -16,20 +16,24 @@ class PyTestResult:
16
16
  stderr: str | None
17
17
 
18
18
  class PyRunReport:
19
- """Run report from Rust layer."""
19
+ """Test run report from the Rust extension."""
20
20
 
21
21
  total: int
22
22
  passed: int
23
23
  failed: int
24
24
  skipped: int
25
25
  duration: float
26
- results: Sequence[PyTestResult]
26
+ results: list[PyTestResult]
27
27
 
28
28
  def run(
29
- paths: list[str],
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,
35
+ last_failed_mode: str,
36
+ fail_fast: bool,
33
37
  ) -> PyRunReport:
34
- """Run tests and return a report."""
38
+ """Execute tests and return a report."""
35
39
  ...
@@ -0,0 +1,232 @@
1
+ Metadata-Version: 2.4
2
+ Name: rustest
3
+ Version: 0.7.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: pytest-asyncio>=1.2.0 ; extra == 'dev'
22
+ Requires-Dist: pytest-codeblocks>=0.17.0 ; extra == 'dev'
23
+ Requires-Dist: ruff>=0.1.9 ; extra == 'dev'
24
+ Requires-Dist: mkdocs>=1.5.0 ; extra == 'docs'
25
+ Requires-Dist: mkdocs-material>=9.5.0 ; extra == 'docs'
26
+ Requires-Dist: mkdocstrings[python]>=0.24.0 ; extra == 'docs'
27
+ Requires-Dist: mkdocs-autorefs>=0.5.0 ; extra == 'docs'
28
+ Provides-Extra: dev
29
+ Provides-Extra: docs
30
+ License-File: LICENSE
31
+ Summary: Rust powered pytest-compatible runner
32
+ Author: rustest contributors
33
+ Requires-Python: >=3.10
34
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
35
+ Project-URL: Homepage, https://github.com/Apex-Engineers-Inc/rustest
36
+ Project-URL: Repository, https://github.com/Apex-Engineers-Inc/rustest
37
+ Project-URL: Documentation, https://apex-engineers-inc.github.io/rustest
38
+
39
+ # rustest
40
+
41
+ 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.
42
+
43
+ 📚 **[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/)
44
+
45
+ ## Why rustest?
46
+
47
+ - 🚀 **About 2x faster** than pytest on the rustest integration test suite
48
+ - ✅ Familiar `@fixture`, `@parametrize`, `@skip`, and `@mark` decorators
49
+ - 🔄 **Built-in async support** with `@mark.asyncio` (like pytest-asyncio)
50
+ - 🔍 Automatic test discovery (`test_*.py` and `*_test.py` files)
51
+ - 📝 **Built-in markdown code block testing** (like pytest-codeblocks, but faster)
52
+ - 🎯 Simple, clean API—if you know pytest, you already know rustest
53
+ - 🧮 Built-in `approx()` helper for tolerant numeric comparisons
54
+ - 🪤 `raises()` context manager for precise exception assertions
55
+ - 📦 Easy installation with pip or uv
56
+ - ⚡ Low-overhead execution keeps small suites feeling instant
57
+
58
+ ## Performance
59
+
60
+ 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:
61
+
62
+ | Test Runner | Wall Clock | Speedup | Command |
63
+ |-------------|------------|---------|---------|
64
+ | pytest | 1.33–1.59s | 1.0x (baseline) | `pytest tests/ examples/tests/ -q` |
65
+ | rustest | 0.69–0.70s | **~2.1x faster** | `python -m rustest tests/ examples/tests/` |
66
+
67
+ ### Large Parametrized Stress Test
68
+
69
+ With **10,000 parametrized invocations**:
70
+
71
+ | Test Runner | Avg. Wall Clock | Speedup | Command |
72
+ |-------------|-----------------|---------|---------|
73
+ | pytest | 9.72s | 1.0x | `pytest benchmarks/test_large_parametrize.py -q` |
74
+ | rustest | 0.41s | **~24x faster** | `python -m rustest benchmarks/test_large_parametrize.py` |
75
+
76
+ **[📊 View Detailed Performance Analysis →](https://apex-engineers-inc.github.io/rustest/advanced/performance/)**
77
+
78
+ ## Installation
79
+
80
+ Rustest supports Python **3.10 through 3.14**.
81
+
82
+ <!--pytest.mark.skip-->
83
+ ```bash
84
+ # Using pip
85
+ pip install rustest
86
+
87
+ # Using uv
88
+ uv add rustest
89
+ ```
90
+
91
+ **[📖 Installation Guide →](https://apex-engineers-inc.github.io/rustest/getting-started/installation/)**
92
+
93
+ ## Quick Start
94
+
95
+ ### 1. Write Your Tests
96
+
97
+ Create a file `test_math.py`:
98
+
99
+ ```python
100
+ from rustest import fixture, parametrize, mark, approx, raises
101
+ import asyncio
102
+
103
+ @fixture
104
+ def numbers() -> list[int]:
105
+ return [1, 2, 3, 4, 5]
106
+
107
+ def test_sum(numbers: list[int]) -> None:
108
+ assert sum(numbers) == approx(15)
109
+
110
+ @parametrize("value,expected", [(2, 4), (3, 9), (4, 16)])
111
+ def test_square(value: int, expected: int) -> None:
112
+ assert value ** 2 == expected
113
+
114
+ @mark.slow
115
+ def test_expensive_operation() -> None:
116
+ result = sum(range(1000000))
117
+ assert result > 0
118
+
119
+ @mark.asyncio
120
+ async def test_async_operation() -> None:
121
+ # Example async operation
122
+ await asyncio.sleep(0.001)
123
+ result = 42
124
+ assert result == 42
125
+
126
+ def test_division_by_zero() -> None:
127
+ with raises(ZeroDivisionError, match="division by zero"):
128
+ 1 / 0
129
+ ```
130
+
131
+ ### 2. Run Your Tests
132
+
133
+ <!--pytest.mark.skip-->
134
+ ```bash
135
+ # Run all tests
136
+ rustest
137
+
138
+ # Run specific tests
139
+ rustest tests/
140
+
141
+ # Filter by test name pattern
142
+ rustest -k "test_sum"
143
+
144
+ # Filter by marks
145
+ rustest -m "slow" # Run only slow tests
146
+ rustest -m "not slow" # Skip slow tests
147
+ rustest -m "slow and integration" # Run tests with both marks
148
+
149
+ # Rerun only failed tests
150
+ rustest --lf # Last failed only
151
+ rustest --ff # Failed first, then all others
152
+
153
+ # Exit on first failure
154
+ rustest -x # Fail fast
155
+
156
+ # Combine options
157
+ rustest --ff -x # Run failed tests first, stop on first failure
158
+
159
+ # Show output during execution
160
+ rustest --no-capture
161
+ ```
162
+
163
+ **[📖 Full Quick Start Guide →](https://apex-engineers-inc.github.io/rustest/getting-started/quickstart/)**
164
+
165
+ ## Documentation
166
+
167
+ **[📚 Full Documentation](https://apex-engineers-inc.github.io/rustest)**
168
+
169
+ ### Getting Started
170
+ - [Installation](https://apex-engineers-inc.github.io/rustest/getting-started/installation/)
171
+ - [Quick Start](https://apex-engineers-inc.github.io/rustest/getting-started/quickstart/)
172
+
173
+ ### User Guide
174
+ - [Writing Tests](https://apex-engineers-inc.github.io/rustest/guide/writing-tests/)
175
+ - [Fixtures](https://apex-engineers-inc.github.io/rustest/guide/fixtures/)
176
+ - [Parametrization](https://apex-engineers-inc.github.io/rustest/guide/parametrization/)
177
+ - [Marks & Skipping](https://apex-engineers-inc.github.io/rustest/guide/marks/)
178
+ - [Test Classes](https://apex-engineers-inc.github.io/rustest/guide/test-classes/)
179
+ - [Assertion Helpers](https://apex-engineers-inc.github.io/rustest/guide/assertions/)
180
+ - [Markdown Testing](https://apex-engineers-inc.github.io/rustest/guide/markdown-testing/)
181
+ - [CLI Usage](https://apex-engineers-inc.github.io/rustest/guide/cli/)
182
+ - [Python API](https://apex-engineers-inc.github.io/rustest/guide/python-api/)
183
+
184
+ ### API Reference
185
+ - [API Overview](https://apex-engineers-inc.github.io/rustest/api/overview/)
186
+ - [Decorators](https://apex-engineers-inc.github.io/rustest/api/decorators/)
187
+ - [Test Execution](https://apex-engineers-inc.github.io/rustest/api/core/)
188
+ - [Reporting](https://apex-engineers-inc.github.io/rustest/api/reporting/)
189
+ - [Assertion Utilities](https://apex-engineers-inc.github.io/rustest/api/approx/)
190
+
191
+ ### Advanced Topics
192
+ - [Performance](https://apex-engineers-inc.github.io/rustest/advanced/performance/)
193
+ - [Comparison with pytest](https://apex-engineers-inc.github.io/rustest/advanced/comparison/)
194
+ - [Development Guide](https://apex-engineers-inc.github.io/rustest/advanced/development/)
195
+
196
+ ## Feature Comparison with pytest
197
+
198
+ Rustest implements the 20% of pytest features that cover 80% of use cases, with a focus on raw speed and simplicity.
199
+
200
+ **[📋 View Full Feature Comparison →](https://apex-engineers-inc.github.io/rustest/advanced/comparison/)**
201
+
202
+ ✅ **Supported:** Fixtures, parametrization, marks, test classes, conftest.py, markdown testing
203
+ 🚧 **Planned:** Parallel execution, mark filtering, JUnit XML output
204
+ ❌ **Not Planned:** Plugins, hooks, custom collectors (keeps rustest simple)
205
+
206
+ ## Contributing
207
+
208
+ We welcome contributions! See the [Development Guide](https://apex-engineers-inc.github.io/rustest/advanced/development/) for setup instructions.
209
+
210
+ Quick reference:
211
+
212
+ <!--pytest.mark.skip-->
213
+ ```bash
214
+ # Setup
215
+ git clone https://github.com/Apex-Engineers-Inc/rustest.git
216
+ cd rustest
217
+ uv sync --all-extras
218
+ uv run maturin develop
219
+
220
+ # Run tests
221
+ uv run poe pytests # Python tests
222
+ cargo test # Rust tests
223
+
224
+ # Format and lint
225
+ uv run pre-commit install # One-time setup
226
+ git commit -m "message" # Pre-commit hooks run automatically
227
+ ```
228
+
229
+ ## License
230
+
231
+ rustest is distributed under the terms of the MIT license. See [LICENSE](LICENSE).
232
+
@@ -0,0 +1,16 @@
1
+ rustest-0.7.0.dist-info/METADATA,sha256=4oreZ3cw8nBFQuC5UMDhGkci1x-9p5KT6sz0YXkuhoA,8818
2
+ rustest-0.7.0.dist-info/WHEEL,sha256=Vw5ZLWgt_2Ah16i4JL1BF7IIc3gqArKE4ITLavudIhM,107
3
+ rustest-0.7.0.dist-info/entry_points.txt,sha256=7fUa3LO8vudQ4dKG1sTRaDnxcMdBSZsWs9EyuxFQ7Lk,48
4
+ rustest-0.7.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=26zaX635WqW0q8B-alb0Kk0wzIDZVkF-y9ZqzKtHDbI,10185
9
+ rustest/core.py,sha256=FBnr3yzHQequ83R-pRlwlCdbcYlkjjucwtHpp0gnqdQ,1461
10
+ rustest/decorators.py,sha256=mZDieFSaUx5FIeDqsWO07H6E0urk70cOYtQIuCIjKzY,18703
11
+ rustest/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ rustest/reporting.py,sha256=3-R8aljv2ZbmjOa9Q9KZeCPsMaitm8nZ96LoJS_NnUQ,1623
13
+ rustest/rust.cpython-313-darwin.so,sha256=6khWVzzAZDRmo6b8c2vlFC0T9JunQjVMLnZ8HnGE7pU,1668920
14
+ rustest/rust.py,sha256=tCIvjYd06VxoT_rKvv2o8CpXW_pFNua5VgcRDjLgU78,659
15
+ rustest/rust.pyi,sha256=bJDdaokbRZWPtTZ1bwsXWb43L9jwOm2gLkk7LzNlQcw,810
16
+ rustest-0.7.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: maturin (1.9.6)
2
+ Generator: maturin (1.10.0)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp313-cp313-macosx_10_12_x86_64
Binary file
@@ -1,521 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: rustest
3
- Version: 0.2.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 Output
445
-
446
- When you run rustest, you'll see clean, informative output:
447
-
448
- ```
449
- PASSED 0.001s test_simple_assertion
450
- PASSED 0.002s test_string_operations
451
- PASSED 0.001s test_database_query
452
- PASSED 0.003s test_square[two]
453
- PASSED 0.001s test_square[three]
454
- PASSED 0.002s test_square[four]
455
- SKIPPED 0.000s test_future_feature
456
- FAILED 0.005s test_broken_feature
457
- ----------------------------------------
458
- AssertionError: Expected 5, got 4
459
- at test_example.py:42
460
-
461
- 8 tests: 6 passed, 1 failed, 1 skipped in 0.015s
462
- ```
463
-
464
- ## Feature Comparison with pytest
465
-
466
- Rustest aims to provide the most commonly-used pytest features with dramatically better performance. Here's how the two compare:
467
-
468
- | Feature | pytest | rustest | Notes |
469
- |---------|--------|---------|-------|
470
- | **Core Test Discovery** |
471
- | `test_*.py` / `*_test.py` files | ✅ | ✅ | Rustest uses Rust for dramatically faster discovery |
472
- | Test function detection (`test_*`) | ✅ | ✅ | |
473
- | Test class detection (`Test*`) | ✅ | ✅ | via `unittest.TestCase` support |
474
- | Pattern-based filtering | ✅ | ✅ | `-k` pattern matching |
475
- | **Fixtures** |
476
- | `@fixture` decorator | ✅ | ✅ | Rust-based dependency resolution |
477
- | Fixture dependency injection | ✅ | ✅ | Much faster in rustest |
478
- | Fixture scopes (function/class/module/session) | ✅ | ✅ | Full support for all scopes |
479
- | Yield fixtures (setup/teardown) | ✅ | ✅ | Full support with cleanup |
480
- | Fixture parametrization | ✅ | 🚧 | Planned |
481
- | **Parametrization** |
482
- | `@parametrize` decorator | ✅ | ✅ | Full support with custom IDs |
483
- | Multiple parameter sets | ✅ | ✅ | |
484
- | Parametrize with fixtures | ✅ | ✅ | |
485
- | **Marks** |
486
- | `@mark.skip` / `@skip` | ✅ | ✅ | Skip tests with reasons |
487
- | Custom marks (`@mark.slow`, etc.) | ✅ | ✅ | Just added! |
488
- | Mark with arguments | ✅ | ✅ | `@mark.timeout(30)` |
489
- | Selecting tests by mark (`-m`) | ✅ | 🚧 | Mark metadata collected, filtering planned |
490
- | **Test Execution** |
491
- | Detailed assertion introspection | ✅ | ❌ | Uses standard Python assertions |
492
- | Parallel execution | ✅ (`pytest-xdist`) | 🚧 | Planned (Rust makes this easier) |
493
- | Test isolation | ✅ | ✅ | |
494
- | Stdout/stderr capture | ✅ | ✅ | |
495
- | **Reporting** |
496
- | Pass/fail/skip summary | ✅ | ✅ | |
497
- | Failure tracebacks | ✅ | ✅ | Full Python traceback support |
498
- | Duration reporting | ✅ | ✅ | Per-test timing |
499
- | JUnit XML output | ✅ | 🚧 | Planned |
500
- | HTML reports | ✅ (`pytest-html`) | 🚧 | Planned |
501
- | **Advanced Features** |
502
- | Plugins | ✅ | ❌ | Not planned (keeps rustest simple) |
503
- | Hooks | ✅ | ❌ | Not planned |
504
- | Custom collectors | ✅ | ❌ | Not planned |
505
- | `conftest.py` | ✅ | ✅ | Shared fixtures across test files |
506
- | **Developer Experience** |
507
- | Fully typed Python API | ⚠️ | ✅ | rustest uses `basedpyright` strict mode |
508
- | Fast CI/CD runs | ⚠️ | ✅ | 78x faster = dramatically shorter feedback loops |
509
-
510
- **Legend:**
511
- - ✅ Fully supported
512
- - 🚧 Planned or in progress
513
- - ⚠️ Partial support
514
- - ❌ Not planned
515
-
516
- **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.
517
-
518
- ## License
519
-
520
- rustest is distributed under the terms of the MIT license. See [LICENSE](LICENSE).
521
-
@@ -1,15 +0,0 @@
1
- rustest-0.2.0.dist-info/METADATA,sha256=zNdhsqtUfKsGrhvlI7csbjpmT6FFsb3T3kaE2a5Yx5c,16497
2
- rustest-0.2.0.dist-info/WHEEL,sha256=73R9gF1Kx45mnq358tC7C3GKhsVvyIHwOBt6LvI35MM,106
3
- rustest-0.2.0.dist-info/entry_points.txt,sha256=7fUa3LO8vudQ4dKG1sTRaDnxcMdBSZsWs9EyuxFQ7Lk,48
4
- rustest-0.2.0.dist-info/licenses/LICENSE,sha256=s64ibUGtb6jEDBsYuxUFtMr_c4PaqYP-vj3YY6QtTGw,1075
5
- rustest/__init__.py,sha256=lXDAo5Ygm_sk-1Qu0M8kZnx3BR5AgSVDJWeb8IX2vYM,523
6
- rustest/__main__.py,sha256=nqdz6DhrDze715SXxtzAYV2sie3CPoy7IvWCdcyHJEM,179
7
- rustest/_approx.py,sha256=MKmuorBBHqpH0h0QaIMVjbm3-mXJ0E90limEgSHHVfw,5744
8
- rustest/_cli.py,sha256=kq9LAwHaJmZ-gnAlTsz7Ov8r1fiDvNoLf4hEI3sxhng,8700
9
- rustest/_decorators.py,sha256=nijzNG8NQXZd8kEfMjhSB-85gLoIjaIMxwZQNUNWgrE,11373
10
- rustest/_reporting.py,sha256=6nVcccX1dgEBW72wCOeOIl5I-OE-ukjJD0VQs56pwjo,1626
11
- rustest/_rust.cpython-313-darwin.so,sha256=htKOwWu0GWLOq_YHMlcMp32Qh2FQ9d_boi9Nm579y7Q,1540452
12
- rustest/_rust.py,sha256=k3nXhGiehOVY_S6w28rIdrc0CEc3gFLgwWVOEMcPOZo,660
13
- rustest/_rust.pyi,sha256=fDFLX0qj4G_bV1sHmTtRPI26grTDG_LFzPFEqp5vFGk,671
14
- rustest/core.py,sha256=xmBUpuPs0r0HQthc9J5dCQYkZnXqfxqIfSGkHeoqQS4,488
15
- rustest-0.2.0.dist-info/RECORD,,
File without changes