rustest 0.14.0__cp313-cp313-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.
@@ -0,0 +1,130 @@
1
+ """Global fixture registry for runtime fixture resolution.
2
+
3
+ This module provides a thread-safe global registry that stores fixture information
4
+ and enables dynamic fixture resolution via request.getfixturevalue().
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import inspect
10
+ import threading
11
+ from typing import Any
12
+
13
+ _registry_lock = threading.Lock()
14
+ _fixture_registry: dict[str, Any] = {}
15
+ _fixture_cache: dict[str, Any] = {}
16
+
17
+
18
+ def register_fixtures(fixtures: dict[str, Any]) -> None:
19
+ """Register fixtures for the current test context.
20
+
21
+ Args:
22
+ fixtures: Dictionary mapping fixture names to fixture callables
23
+ """
24
+ with _registry_lock:
25
+ _fixture_registry.clear()
26
+ _fixture_registry.update(fixtures)
27
+
28
+
29
+ def clear_registry() -> None:
30
+ """Clear the fixture registry and cache."""
31
+ with _registry_lock:
32
+ _fixture_registry.clear()
33
+ _fixture_cache.clear()
34
+
35
+
36
+ def get_fixture(name: str) -> Any:
37
+ """Get a fixture callable by name.
38
+
39
+ Args:
40
+ name: Name of the fixture
41
+
42
+ Returns:
43
+ The fixture callable
44
+
45
+ Raises:
46
+ ValueError: If the fixture is not found
47
+ """
48
+ with _registry_lock:
49
+ if name not in _fixture_registry:
50
+ raise ValueError(f"fixture '{name}' not found")
51
+ return _fixture_registry[name]
52
+
53
+
54
+ def resolve_fixture(name: str, _executed_fixtures: dict[str, Any] | None = None) -> Any:
55
+ """Resolve and execute a fixture by name.
56
+
57
+ This handles fixture dependencies recursively and caches results per test.
58
+
59
+ Args:
60
+ name: Name of the fixture to resolve
61
+ _executed_fixtures: Internal cache of already-executed fixtures for this test
62
+
63
+ Returns:
64
+ The fixture value
65
+
66
+ Raises:
67
+ ValueError: If the fixture is not found
68
+ NotImplementedError: If the fixture is async (not yet supported)
69
+ """
70
+ if _executed_fixtures is None:
71
+ _executed_fixtures = {}
72
+
73
+ # Check if already executed for this test
74
+ if name in _executed_fixtures:
75
+ return _executed_fixtures[name]
76
+
77
+ # Get the fixture callable
78
+ fixture_func = get_fixture(name)
79
+
80
+ # Check if it's async (either async function or async generator)
81
+ if inspect.iscoroutinefunction(fixture_func) or inspect.isasyncgenfunction(fixture_func):
82
+ # Raise a clear, helpful error explaining the issue and how to fix it
83
+ raise NotImplementedError(
84
+ f"\nCannot use async fixture '{name}' with request.getfixturevalue().\n\n"
85
+ + "Why this fails:\n"
86
+ + " • getfixturevalue() is a synchronous function that returns values immediately\n"
87
+ + " • Async fixtures must be awaited, but we can't await in a sync context\n"
88
+ + " • Calling the async fixture returns a coroutine object, not the actual value\n\n"
89
+ + "Good news: Async fixtures work perfectly with normal injection!\n\n"
90
+ + "How to fix:\n"
91
+ + f" ❌ Don't use: request.getfixturevalue('{name}')\n"
92
+ + f" ✅ Instead use: def test_something({name}):\n\n"
93
+ + "Example:\n"
94
+ + " # This works perfectly:\n"
95
+ + f" async def test_my_feature({name}):\n"
96
+ + f" assert {name} is not None\n"
97
+ )
98
+
99
+ # Get fixture parameters
100
+ sig = inspect.signature(fixture_func)
101
+ params = sig.parameters
102
+
103
+ # Resolve dependencies recursively
104
+ resolved_args = {}
105
+ for param_name in params:
106
+ # Use get_fixture() which has lock protection, instead of checking registry directly
107
+ try:
108
+ # Try to get the fixture (thread-safe with lock)
109
+ get_fixture(param_name)
110
+ # It's a fixture - resolve it recursively
111
+ resolved_args[param_name] = resolve_fixture(param_name, _executed_fixtures)
112
+ except ValueError:
113
+ # Not a fixture - skip it
114
+ # Special handling for 'request' parameter
115
+ if param_name == "request":
116
+ # Skip 'request' parameter - will be handled by caller
117
+ resolved_args[param_name] = None
118
+
119
+ # Execute the fixture
120
+ result = fixture_func(**resolved_args)
121
+
122
+ # Handle generator fixtures
123
+ if inspect.isgenerator(result):
124
+ result = next(result)
125
+ # TODO: Store generator for teardown
126
+
127
+ # Cache the result
128
+ _executed_fixtures[name] = result
129
+
130
+ return result
rustest/py.typed ADDED
File without changes
rustest/reporting.py ADDED
@@ -0,0 +1,63 @@
1
+ """Utilities for converting raw results from the Rust layer."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable
6
+ from dataclasses import dataclass
7
+
8
+ from . import rust
9
+
10
+
11
+ @dataclass(slots=True)
12
+ class TestResult:
13
+ """Structured view of a single test outcome."""
14
+
15
+ __test__ = False # Tell pytest this is not a test class
16
+
17
+ name: str
18
+ path: str
19
+ status: str
20
+ duration: float
21
+ message: str | None
22
+ stdout: str | None
23
+ stderr: str | None
24
+
25
+ @classmethod
26
+ def from_py(cls, result: rust.PyTestResult) -> "TestResult":
27
+ return cls(
28
+ name=result.name,
29
+ path=result.path,
30
+ status=result.status,
31
+ duration=result.duration,
32
+ message=result.message,
33
+ stdout=result.stdout,
34
+ stderr=result.stderr,
35
+ )
36
+
37
+
38
+ @dataclass(slots=True)
39
+ class RunReport:
40
+ """Aggregate statistics for an entire test session."""
41
+
42
+ total: int
43
+ passed: int
44
+ failed: int
45
+ skipped: int
46
+ duration: float
47
+ results: tuple[TestResult, ...]
48
+
49
+ @classmethod
50
+ def from_py(cls, report: rust.PyRunReport) -> "RunReport":
51
+ return cls(
52
+ total=report.total,
53
+ passed=report.passed,
54
+ failed=report.failed,
55
+ skipped=report.skipped,
56
+ duration=report.duration,
57
+ results=tuple(TestResult.from_py(result) for result in report.results),
58
+ )
59
+
60
+ def iter_status(self, status: str) -> Iterable[TestResult]:
61
+ """Yield results with the requested status."""
62
+
63
+ return (result for result in self.results if result.status == status)
Binary file
rustest/rust.py ADDED
@@ -0,0 +1,23 @@
1
+ """Fallback stub for the compiled rustest extension.
2
+
3
+ This module is packaged with the Python distribution so unit tests can import the
4
+ package without building the Rust extension. Individual tests are expected to
5
+ monkeypatch the functions they exercise.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Sequence
11
+
12
+
13
+ def run(
14
+ _paths: Sequence[str],
15
+ _pattern: str | None,
16
+ _workers: int | None,
17
+ _capture_output: bool,
18
+ ) -> Any:
19
+ """Placeholder implementation that mirrors the extension signature."""
20
+
21
+ raise NotImplementedError(
22
+ "The rustest native extension is unavailable. Tests must patch rustest.rust.run."
23
+ )
rustest/rust.pyi ADDED
@@ -0,0 +1,43 @@
1
+ """Type stubs for the rustest Rust extension module."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Sequence
6
+
7
+ class PyTestResult:
8
+ """Individual test result from the Rust extension."""
9
+
10
+ name: str
11
+ path: str
12
+ status: str
13
+ duration: float
14
+ message: str | None
15
+ stdout: str | None
16
+ stderr: str | None
17
+
18
+ class PyRunReport:
19
+ """Test run report from the Rust extension."""
20
+
21
+ total: int
22
+ passed: int
23
+ failed: int
24
+ skipped: int
25
+ duration: float
26
+ results: list[PyTestResult]
27
+
28
+ def run(
29
+ paths: Sequence[str],
30
+ pattern: str | None,
31
+ mark_expr: str | None,
32
+ workers: int | None,
33
+ capture_output: bool,
34
+ enable_codeblocks: bool,
35
+ last_failed_mode: str,
36
+ fail_fast: bool,
37
+ pytest_compat: bool,
38
+ verbose: bool,
39
+ ascii: bool,
40
+ no_color: bool,
41
+ ) -> PyRunReport:
42
+ """Execute tests and return a report."""
43
+ ...
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: rustest
3
+ Version: 0.14.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: packaging>=20.0 ; extra == 'dev'
19
+ Requires-Dist: poethepoet>=0.22 ; extra == 'dev'
20
+ Requires-Dist: pre-commit>=3.5 ; extra == 'dev'
21
+ Requires-Dist: py>=1.11 ; extra == 'dev'
22
+ Requires-Dist: pytest>=7.0 ; extra == 'dev'
23
+ Requires-Dist: pytest-asyncio>=1.2.0 ; extra == 'dev'
24
+ Requires-Dist: pytest-codeblocks>=0.17.0 ; extra == 'dev'
25
+ Requires-Dist: ruff>=0.1.9 ; extra == 'dev'
26
+ Requires-Dist: zensical ; extra == 'docs'
27
+ Provides-Extra: dev
28
+ Provides-Extra: docs
29
+ License-File: LICENSE
30
+ Summary: Rust powered pytest-compatible runner
31
+ Author: rustest contributors
32
+ Requires-Python: >=3.10
33
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
34
+ Project-URL: Homepage, https://github.com/Apex-Engineers-Inc/rustest
35
+ Project-URL: Repository, https://github.com/Apex-Engineers-Inc/rustest
36
+ Project-URL: Documentation, https://apex-engineers-inc.github.io/rustest
37
+
38
+ <div align="center">
39
+
40
+ ![rustest logo](assets/logo.svg)
41
+
42
+ </div>
43
+
44
+ Rustest is a Rust-powered pytest-compatible test runner delivering **8.5× average speedup** with familiar pytest syntax and zero setup.
45
+
46
+ 📚 **[Full Documentation](https://apex-engineers-inc.github.io/rustest)** | [Getting Started](https://apex-engineers-inc.github.io/rustest/getting-started/quickstart/) | [Migration Guide](https://apex-engineers-inc.github.io/rustest/from-pytest/migration/)
47
+
48
+ ## 🚀 Try It Now
49
+
50
+ Run your existing pytest tests with rustest — no code changes required:
51
+
52
+ <!--pytest.mark.skip-->
53
+ ```bash
54
+ pip install rustest
55
+ rustest --pytest-compat tests/
56
+ ```
57
+
58
+ See the speedup immediately, then migrate to native rustest for full features.
59
+
60
+ ## Why Rustest?
61
+
62
+ - 🚀 **8.5× average speedup** over pytest (up to 19× on large suites)
63
+ - 🧪 **pytest-compatible** — Run existing tests with `--pytest-compat`
64
+ - ✅ **Familiar API** — Same `@fixture`, `@parametrize`, `@mark` decorators
65
+ - 🔄 **Built-in async & mocking** — No pytest-asyncio or pytest-mock plugins needed
66
+ - 🐛 **Clear error messages** — Vitest-style output with Expected/Received diffs
67
+ - 📝 **Markdown testing** — Test code blocks in documentation
68
+ - 🛠️ **Rich fixtures** — `tmp_path`, `monkeypatch`, `mocker`, `capsys`, `caplog`, `cache`, and more
69
+
70
+ ## Performance
71
+
72
+ Rustest delivers consistent speedups across test suites of all sizes:
73
+
74
+ | Test Count | pytest | rustest | Speedup |
75
+ |-----------:|-------:|--------:|--------:|
76
+ | 20 | 0.45s | 0.12s | 3.8× |
77
+ | 500 | 1.21s | 0.15s | 8.3× |
78
+ | 5,000 | 7.81s | 0.40s | 19.4× |
79
+
80
+ **Expected speedups:** 3-4× for small suites, 5-8× for medium suites, 11-19× for large suites.
81
+
82
+ **[📊 Full Performance Analysis →](https://apex-engineers-inc.github.io/rustest/advanced/performance/)**
83
+
84
+ ## Installation
85
+
86
+ <!--pytest.mark.skip-->
87
+ ```bash
88
+ pip install rustest
89
+ # or
90
+ uv add rustest
91
+ ```
92
+
93
+ **Python 3.10-3.14 supported.** [📖 Installation Guide →](https://apex-engineers-inc.github.io/rustest/getting-started/installation/)
94
+
95
+ ## Quick Start
96
+
97
+ Write a test in `test_example.py`:
98
+
99
+ ```python
100
+ from rustest import fixture, parametrize, mark, raises
101
+
102
+ @fixture
103
+ def numbers():
104
+ return [1, 2, 3, 4, 5]
105
+
106
+ def test_sum(numbers):
107
+ assert sum(numbers) == 15
108
+
109
+ @parametrize("value,expected", [(2, 4), (3, 9)])
110
+ def test_square(value, expected):
111
+ assert value ** 2 == expected
112
+
113
+ @mark.asyncio
114
+ async def test_async():
115
+ result = 42
116
+ assert result == 42
117
+
118
+ def test_exception():
119
+ with raises(ZeroDivisionError):
120
+ 1 / 0
121
+ ```
122
+
123
+ Run your tests:
124
+
125
+ <!--pytest.mark.skip-->
126
+ ```bash
127
+ rustest # Run all tests
128
+ rustest tests/ # Run specific directory
129
+ rustest -k "test_sum" # Filter by name
130
+ rustest -m "slow" # Filter by mark
131
+ rustest --lf # Rerun last failed
132
+ rustest -x # Exit on first failure
133
+ ```
134
+
135
+ **[📖 Full Documentation →](https://apex-engineers-inc.github.io/rustest)**
136
+
137
+ ## Learn More
138
+
139
+ - **[Getting Started](https://apex-engineers-inc.github.io/rustest/getting-started/quickstart/)** — Complete quickstart guide
140
+ - **[Migration from pytest](https://apex-engineers-inc.github.io/rustest/from-pytest/migration/)** — 5-minute migration guide
141
+ - **[User Guide](https://apex-engineers-inc.github.io/rustest/guide/writing-tests/)** — Fixtures, parametrization, marks, assertions
142
+ - **[API Reference](https://apex-engineers-inc.github.io/rustest/api/overview/)** — Complete API documentation
143
+
144
+ ## Contributing
145
+
146
+ Contributions welcome! See the [Development Guide](https://apex-engineers-inc.github.io/rustest/advanced/development/) for setup instructions.
147
+
148
+ ## License
149
+
150
+ MIT License. See [LICENSE](LICENSE) for details.
151
+
@@ -0,0 +1,20 @@
1
+ rustest-0.14.0.dist-info/METADATA,sha256=8xmM0nheAoAgm-JHbIe5mRPVjEmrAGwyZuXcVAfP9KE,5338
2
+ rustest-0.14.0.dist-info/WHEEL,sha256=WjzAfvqut_Q5Xaf14OBgb9ZsnYHdVRd7o0GDAayVL0I,105
3
+ rustest-0.14.0.dist-info/entry_points.txt,sha256=7fUa3LO8vudQ4dKG1sTRaDnxcMdBSZsWs9EyuxFQ7Lk,48
4
+ rustest-0.14.0.dist-info/licenses/LICENSE,sha256=s64ibUGtb6jEDBsYuxUFtMr_c4PaqYP-vj3YY6QtTGw,1075
5
+ rustest/__init__.py,sha256=eg_pai6AXfe-F-GVaSPcbBVfW0yyJ7NKBLK1Sy5yTLQ,841
6
+ rustest/__main__.py,sha256=bBvo5gsSluUzlDTDvn5bP_gZZEXMwJQZMqVA5W1M1v8,178
7
+ rustest/approx.py,sha256=tHDkWqNQAj94eXsBclFcOukc169qiWuZTSh2-rCjz9c,6455
8
+ rustest/builtin_fixtures.py,sha256=3wNdhSV_LjlroCgMV0VKDN6ZpbWiUlDFh6M6umqUcuU,36322
9
+ rustest/cli.py,sha256=ZYBxpimBZEaBhmL-PI1XFz50FWPvqxaYslbhETrQpns,3870
10
+ rustest/compat/__init__.py,sha256=seRdlvStizCfkarfll8aIS_eikEK2V9kjlnJY6iiWA4,70
11
+ rustest/compat/pytest.py,sha256=UgNNEDj9Amhl-M8J74XSth6k6es8kKyC3uUMA8QobzM,37214
12
+ rustest/core.py,sha256=IJHo-z60MPnJtCZS1ha1yjRN0XJ9IPvnGlnKC5Ai50Y,1953
13
+ rustest/decorators.py,sha256=mxZAYHsBeZfpsDVCd65sPrYzjurvf32eFBKjPMy9De4,34416
14
+ rustest/fixture_registry.py,sha256=Rs7_wxBSox5XUWW5Mpb84Lcy3tjZuGxzsOJkYOxPmHI,4339
15
+ rustest/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ rustest/reporting.py,sha256=3-R8aljv2ZbmjOa9Q9KZeCPsMaitm8nZ96LoJS_NnUQ,1623
17
+ rustest/rust.cpython-313-darwin.so,sha256=8PhJ2VJlQyljyXOPQzefgBL3oYA9mUdEHsyI22mz6FA,2413712
18
+ rustest/rust.py,sha256=tCIvjYd06VxoT_rKvv2o8CpXW_pFNua5VgcRDjLgU78,659
19
+ rustest/rust.pyi,sha256=QCEy3Qc8pZqOSUR7cgsWnTQThgQVcsKrM1skpGVMCgc,891
20
+ rustest-0.14.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.10.2)
3
+ Root-Is-Purelib: false
4
+ Tag: cp313-cp313-macosx_11_0_arm64
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ rustest=rustest.__main__:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Apex Engineers Inc
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.