rustest 0.4.0__cp310-cp310-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.

Potentially problematic release.


This version of rustest might be problematic. Click here for more details.

@@ -0,0 +1,713 @@
1
+ Metadata-Version: 2.4
2
+ Name: rustest
3
+ Version: 0.4.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
+ - ๐Ÿ“ **Built-in markdown code block testing** (like pytest-codeblocks, but faster)
41
+ - ๐ŸŽฏ Simple, clean APIโ€”if you know pytest, you already know rustest
42
+ - ๐Ÿงฎ Built-in `approx()` helper for tolerant numeric comparisons across scalars, collections, and complex numbers
43
+ - ๐Ÿชค `raises()` context manager for precise exception assertions with optional message matching
44
+ - ๐Ÿ“ฆ Easy installation with pip or uv
45
+ - โšก Low-overhead execution keeps small suites feeling instant
46
+
47
+ ## Performance
48
+
49
+ 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:
50
+
51
+ | Test Runner | Reported Runtimeโ€  | Wall Clockโ€ก | Speedup (wall) | Command |
52
+ |-------------|------------------|-------------|----------------|---------|
53
+ | pytest | 0.43โ€“0.59s | 1.33โ€“1.59s | 1.0x (baseline) | `pytest tests/ examples/tests/ -q`
54
+ | rustest | 0.003s | 0.69โ€“0.70s | **~2.1x faster** | `python -m rustest tests/ examples/tests/`ยง
55
+
56
+ ### Large parametrized stress test
57
+
58
+ 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:
59
+
60
+ | Test Runner | Avg. Wall Clock (3 runs) | Speedup | Command |
61
+ |-------------|--------------------------|---------|---------|
62
+ | pytest | 9.72s | 1.0x | `pytest benchmarks/test_large_parametrize.py -q`ยง
63
+ | rustest | 0.41s | **~24x** | `python -m rustest benchmarks/test_large_parametrize.py`ยง
64
+
65
+ โ€  pytest and rustest both report only active test execution time; rustest's figure omits Python interpreter start-up overhead.
66
+
67
+ โ€ก Integration-suite wall-clock timing measured with the shell `time` builtin across two consecutive runs in the same environment.
68
+
69
+ ยง 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`.
70
+
71
+ 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.
72
+
73
+ **Why is rustest faster?**
74
+ - **Near-zero startup time**: Native Rust binary minimizes overhead before Python code starts running.
75
+ - **Rust-native test discovery**: Minimal imports until test execution keeps collection quick.
76
+ - **Optimized fixture resolution**: Efficient dependency graph resolution reduces per-test work.
77
+ - **Lean orchestration**: Rust handles scheduling and reporting so the Python interpreter focuses on running test bodies.
78
+
79
+ **Real-world impact:**
80
+ - **200 tests** (this repository): 1.46s โ†’ 0.70s (average wall-clock, ~0.76s saved per run)
81
+ - **1,000 tests** (projected): ~7.3s โ†’ ~3.4s assuming similar scaling
82
+ - **10,000 tests** (projected): ~73s โ†’ ~34sโ€”minutes saved across CI runs
83
+
84
+ See [BENCHMARKS.md](BENCHMARKS.md) for detailed performance analysis and methodology.
85
+
86
+ ## Installation
87
+
88
+ Rustest supports Python **3.10 through 3.14**.
89
+
90
+ ### Using pip
91
+ ```bash
92
+ pip install rustest
93
+ ```
94
+
95
+ ### Using uv
96
+ ```bash
97
+ uv add rustest
98
+ ```
99
+
100
+ ### For Development
101
+ If you want to contribute to rustest, see [DEVELOPMENT.md](DEVELOPMENT.md) for setup instructions.
102
+
103
+ ## Testing Markdown Code Blocks
104
+
105
+ Rustest can automatically discover and test Python code blocks in your markdown files, similar to pytest-codeblocks. This is perfect for ensuring your documentation examples stay up-to-date and functional.
106
+
107
+ ### Enabling Code Block Tests
108
+
109
+ By default, rustest will automatically discover and test Python code blocks in markdown files (`.md`). Each Python code block is treated as a separate test case.
110
+
111
+ ```bash
112
+ # Run tests including markdown code blocks
113
+ rustest
114
+
115
+ # Disable code block tests
116
+ rustest --no-codeblocks
117
+ ```
118
+
119
+ ### Example Markdown File
120
+
121
+ ```markdown
122
+ # Example Documentation
123
+
124
+ ## Basic Addition
125
+
126
+ \```python
127
+ x = 1 + 1
128
+ assert x == 2
129
+ \```
130
+
131
+ ## String Operations
132
+
133
+ \```python
134
+ text = "hello world"
135
+ assert text.startswith("hello")
136
+ \```
137
+ ```
138
+
139
+ Each Python code block will be executed as a test. Code blocks with other language tags (like `javascript`, `bash`, etc.) are ignored.
140
+
141
+ ### Features
142
+
143
+ - **Automatic Discovery**: All `.md` files are scanned for Python code blocks
144
+ - **Simple Testing**: Each `\```python` code block is executed as a test
145
+ - **CLI Control**: Use `--no-codeblocks` to disable code block testing
146
+ - **Fast Execution**: Rust-powered parsing and execution keeps tests fast
147
+
148
+ ## Quick Start
149
+
150
+ ### 1. Write Your Tests
151
+
152
+ Create a file `test_math.py`:
153
+
154
+ ```python
155
+ from rustest import fixture, parametrize, mark, approx, raises
156
+
157
+ @fixture
158
+ def numbers() -> list[int]:
159
+ return [1, 2, 3, 4, 5]
160
+
161
+ def test_sum(numbers: list[int]) -> None:
162
+ assert sum(numbers) == approx(15)
163
+
164
+ @parametrize("value,expected", [(2, 4), (3, 9), (4, 16)])
165
+ def test_square(value: int, expected: int) -> None:
166
+ assert value ** 2 == expected
167
+
168
+ @mark.slow
169
+ def test_expensive_operation() -> None:
170
+ # This test is marked as slow for filtering
171
+ result = sum(range(1000000))
172
+ assert result > 0
173
+
174
+ def test_division_by_zero_is_reported() -> None:
175
+ with raises(ZeroDivisionError, match="division by zero"):
176
+ 1 / 0
177
+ ```
178
+
179
+ ### 2. Run Your Tests
180
+
181
+ ```bash
182
+ # Run all tests in the current directory
183
+ rustest
184
+
185
+ # Run tests in a specific directory
186
+ rustest tests/
187
+
188
+ # Run tests matching a pattern
189
+ rustest -k "test_sum"
190
+
191
+ # Show output during test execution
192
+ rustest --no-capture
193
+ ```
194
+
195
+ ## Usage Examples
196
+
197
+ ### CLI Usage
198
+
199
+ ```bash
200
+ # Run all tests in current directory (including markdown code blocks)
201
+ rustest
202
+
203
+ # Run tests in specific paths
204
+ rustest tests/ integration/
205
+
206
+ # Filter tests by name pattern
207
+ rustest -k "user" # Runs test_user_login, test_user_signup, etc.
208
+ rustest -k "auth" # Runs all tests with "auth" in the name
209
+
210
+ # Control output capture
211
+ rustest --no-capture # See print statements during test execution
212
+
213
+ # Disable markdown code block tests
214
+ rustest --no-codeblocks # Only run Python test files, skip .md files
215
+ ```
216
+
217
+ ### Python API Usage
218
+
219
+ You can also run rustest programmatically from Python:
220
+
221
+ ```python
222
+ from rustest import run
223
+ import os
224
+
225
+ # Basic usage (specify your test directory)
226
+ if os.path.exists("tests"):
227
+ report = run(paths=["tests"])
228
+ print(f"Passed: {report.passed}, Failed: {report.failed}")
229
+
230
+ # With pattern filtering
231
+ report = run(paths=["tests"], pattern="user")
232
+
233
+ # Without output capture (see print statements)
234
+ report = run(paths=["tests"], capture_output=False)
235
+
236
+ # Disable markdown code block tests
237
+ report = run(paths=["tests"], enable_codeblocks=False)
238
+
239
+ # Access individual test results
240
+ for result in report.results:
241
+ print(f"{result.name}: {result.status} ({result.duration:.3f}s)")
242
+ if result.status == "failed":
243
+ print(f" Error: {result.message}")
244
+ ```
245
+
246
+ ### Writing Tests
247
+
248
+ #### Basic Test Functions
249
+
250
+ ```python
251
+ def test_simple_assertion() -> None:
252
+ assert 1 + 1 == 2
253
+
254
+ def test_string_operations() -> None:
255
+ text = "hello world"
256
+ assert text.startswith("hello")
257
+ assert "world" in text
258
+ ```
259
+
260
+ #### Using Fixtures
261
+
262
+ Fixtures provide reusable test data and setup:
263
+
264
+ ```python
265
+ from rustest import fixture
266
+
267
+ @fixture
268
+ def database_connection() -> dict:
269
+ # Setup: create a connection
270
+ conn = {"host": "localhost", "port": 5432}
271
+ return conn
272
+ # Teardown happens automatically
273
+
274
+ @fixture
275
+ def sample_user() -> dict:
276
+ return {"id": 1, "name": "Alice", "email": "alice@example.com"}
277
+
278
+ def test_database_query(database_connection: dict) -> None:
279
+ assert database_connection["host"] == "localhost"
280
+
281
+ def test_user_email(sample_user: dict) -> None:
282
+ assert "@" in sample_user["email"]
283
+ ```
284
+
285
+ #### Fixtures with Dependencies
286
+
287
+ Fixtures can depend on other fixtures:
288
+
289
+ ```python
290
+ from rustest import fixture
291
+
292
+ @fixture
293
+ def api_url() -> str:
294
+ return "https://api.example.com"
295
+
296
+ @fixture
297
+ def api_client(api_url: str) -> dict:
298
+ return {"base_url": api_url, "timeout": 30}
299
+
300
+ def test_api_configuration(api_client: dict) -> None:
301
+ assert api_client["base_url"].startswith("https://")
302
+ assert api_client["timeout"] == 30
303
+ ```
304
+
305
+ #### Assertion Helpers
306
+
307
+ Rustest ships helpers for expressive assertions:
308
+
309
+ ```python
310
+ from rustest import approx, raises
311
+
312
+ def test_nearly_equal() -> None:
313
+ assert 0.1 + 0.2 == approx(0.3, rel=1e-9)
314
+
315
+ def test_raises_with_message() -> None:
316
+ with raises(ValueError, match="invalid configuration"):
317
+ raise ValueError("invalid configuration")
318
+ ```
319
+
320
+ #### Yield Fixtures with Setup/Teardown
321
+
322
+ Fixtures can use `yield` to perform cleanup after tests:
323
+
324
+ ```python
325
+ from rustest import fixture
326
+
327
+ @fixture
328
+ def database_connection():
329
+ # Setup: create connection
330
+ conn = create_db_connection()
331
+ print("Database connected")
332
+
333
+ yield conn
334
+
335
+ # Teardown: close connection
336
+ conn.close()
337
+ print("Database connection closed")
338
+
339
+ @fixture
340
+ def temp_file():
341
+ # Setup
342
+ file = open("temp.txt", "w")
343
+ file.write("test data")
344
+
345
+ yield file
346
+
347
+ # Teardown
348
+ file.close()
349
+ os.remove("temp.txt")
350
+
351
+ def test_database_query(database_connection):
352
+ result = database_connection.query("SELECT 1")
353
+ assert result is not None
354
+ ```
355
+
356
+ #### Fixture Scopes
357
+
358
+ Fixtures support different scopes to control when they are created and destroyed:
359
+
360
+ ```python
361
+ from rustest import fixture
362
+
363
+ @fixture # Default: function scope - new instance per test
364
+ def function_fixture() -> dict:
365
+ return {"value": "reset each test"}
366
+
367
+ @fixture(scope="class") # Shared across all tests in a class
368
+ def class_database() -> dict:
369
+ return {"connection": "db://test", "shared": True}
370
+
371
+ @fixture(scope="module") # Shared across all tests in a module
372
+ def module_config() -> dict:
373
+ return {"env": "test", "timeout": 30}
374
+
375
+ @fixture(scope="session") # Shared across entire test session
376
+ def session_cache() -> dict:
377
+ return {"global_cache": {}}
378
+
379
+ # Fixtures can depend on fixtures with different scopes
380
+ @fixture(scope="function")
381
+ def request_handler(module_config: dict, session_cache: dict) -> dict:
382
+ return {
383
+ "config": module_config, # module-scoped
384
+ "cache": session_cache, # session-scoped
385
+ "request_id": id(object()) # unique per test
386
+ }
387
+ ```
388
+
389
+ **Scope Behavior:**
390
+ - `function` (default): New instance for each test function
391
+ - `class`: Shared across all test methods in a test class
392
+ - `module`: Shared across all tests in a Python module
393
+ - `session`: Shared across the entire test session
394
+
395
+ Scoped fixtures are especially useful for expensive setup operations like database connections, API clients, or configuration loading.
396
+
397
+ **Using conftest.py for Shared Fixtures:**
398
+
399
+ You can define fixtures in a `conftest.py` file to share them across multiple test files:
400
+
401
+ ```python
402
+ # conftest.py
403
+ from rustest import fixture
404
+
405
+ @fixture(scope="session")
406
+ def database():
407
+ """Shared database connection for all tests."""
408
+ db = setup_database()
409
+ yield db
410
+ db.cleanup()
411
+
412
+ @fixture(scope="module")
413
+ def api_client():
414
+ """API client shared across a module."""
415
+ return create_api_client()
416
+ ```
417
+
418
+ All test files in the same directory (and subdirectories) can use these fixtures automatically.
419
+
420
+ #### Parametrized Tests
421
+
422
+ Run the same test with different inputs:
423
+
424
+ ```python
425
+ from rustest import parametrize
426
+
427
+ @parametrize("input,expected", [
428
+ (1, 2),
429
+ (2, 4),
430
+ (3, 6),
431
+ ])
432
+ def test_double(input: int, expected: int) -> None:
433
+ assert input * 2 == expected
434
+
435
+ # With custom test IDs for better output
436
+ @parametrize("value,expected", [
437
+ (2, 4),
438
+ (3, 9),
439
+ (4, 16),
440
+ ], ids=["two", "three", "four"])
441
+ def test_square(value: int, expected: int) -> None:
442
+ assert value ** 2 == expected
443
+ ```
444
+
445
+ #### Combining Fixtures and Parameters
446
+
447
+ ```python
448
+ from rustest import fixture, parametrize
449
+
450
+ @fixture
451
+ def multiplier() -> int:
452
+ return 10
453
+
454
+ @parametrize("value,expected", [
455
+ (1, 10),
456
+ (2, 20),
457
+ (3, 30),
458
+ ])
459
+ def test_multiply(multiplier: int, value: int, expected: int) -> None:
460
+ assert multiplier * value == expected
461
+ ```
462
+
463
+ #### Skipping Tests
464
+
465
+ ```python
466
+ from rustest import skip, mark
467
+
468
+ @skip("Not implemented yet")
469
+ def test_future_feature() -> None:
470
+ assert False
471
+
472
+ @mark.skip(reason="Waiting for API update")
473
+ def test_deprecated_api() -> None:
474
+ assert False
475
+ ```
476
+
477
+ #### Using Marks to Organize Tests
478
+
479
+ ```python
480
+ from rustest import mark
481
+
482
+ @mark.unit
483
+ def test_calculation() -> None:
484
+ assert 2 + 2 == 4
485
+
486
+ @mark.integration
487
+ def test_database_integration() -> None:
488
+ # Integration test
489
+ pass
490
+
491
+ @mark.slow
492
+ @mark.integration
493
+ def test_full_workflow() -> None:
494
+ # This test has multiple marks
495
+ pass
496
+ ```
497
+
498
+ #### Test Classes
499
+
500
+ Rustest supports pytest-style test classes, allowing you to organize related tests together:
501
+
502
+ ```python
503
+ from rustest import fixture, parametrize, mark
504
+
505
+ class TestBasicMath:
506
+ """Group related tests in a class."""
507
+
508
+ def test_addition(self):
509
+ assert 1 + 1 == 2
510
+
511
+ def test_subtraction(self):
512
+ assert 5 - 3 == 2
513
+
514
+ def test_multiplication(self):
515
+ assert 3 * 4 == 12
516
+ ```
517
+
518
+ **Using Fixtures in Test Classes:**
519
+
520
+ Test methods can inject fixtures just like standalone test functions:
521
+
522
+ ```python
523
+ from rustest import fixture
524
+
525
+ @fixture
526
+ def calculator():
527
+ return {"add": lambda x, y: x + y, "multiply": lambda x, y: x * y}
528
+
529
+ class TestCalculator:
530
+ """Test class using fixtures."""
531
+
532
+ def test_addition(self, calculator):
533
+ assert calculator["add"](2, 3) == 5
534
+
535
+ def test_multiplication(self, calculator):
536
+ assert calculator["multiply"](4, 5) == 20
537
+ ```
538
+
539
+ **Class-Scoped Fixtures:**
540
+
541
+ Class-scoped fixtures are shared across all test methods in the same class, perfect for expensive setup operations:
542
+
543
+ ```python
544
+ from rustest import fixture
545
+
546
+ @fixture(scope="class")
547
+ def database():
548
+ """Expensive setup shared across all tests in a class."""
549
+ db = {"connection": "db://test", "data": []}
550
+ return db
551
+
552
+ class TestDatabase:
553
+ """All tests share the same database fixture instance."""
554
+
555
+ def test_connection(self, database):
556
+ assert database["connection"] == "db://test"
557
+
558
+ def test_add_data(self, database):
559
+ database["data"].append("item1")
560
+ assert len(database["data"]) >= 1
561
+
562
+ def test_data_persists(self, database):
563
+ # Same database instance, so previous test's data is still there
564
+ assert len(database["data"]) >= 1
565
+ ```
566
+
567
+ **Fixture Methods Within Test Classes:**
568
+
569
+ You can define fixtures as methods inside test classes, providing class-specific setup:
570
+
571
+ ```python
572
+ from rustest import fixture
573
+
574
+ class TestWithFixtureMethod:
575
+ """Test class with its own fixture methods."""
576
+
577
+ @fixture(scope="class")
578
+ def class_resource(self):
579
+ """Fixture method shared across tests in this class."""
580
+ resource = {"value": 42, "name": "test_resource"}
581
+ yield resource
582
+ # Teardown happens after all tests in class
583
+ resource["closed"] = True
584
+
585
+ @fixture
586
+ def per_test_data(self, class_resource):
587
+ """Fixture method that depends on another fixture."""
588
+ return {"id": id(self), "resource": class_resource}
589
+
590
+ def test_uses_class_resource(self, class_resource):
591
+ assert class_resource["value"] == 42
592
+
593
+ def test_uses_per_test_data(self, per_test_data):
594
+ assert "resource" in per_test_data
595
+ assert per_test_data["resource"]["value"] == 42
596
+ ```
597
+
598
+ **Class Variables and Instance Variables:**
599
+
600
+ Test classes can use class variables for shared state and instance variables for per-test isolation:
601
+
602
+ ```python
603
+ class TestWithVariables:
604
+ """Test class with class and instance variables."""
605
+
606
+ class_variable = "shared_data" # Shared across all tests
607
+
608
+ def test_class_variable(self):
609
+ # Access class variable
610
+ assert self.class_variable == "shared_data"
611
+ assert TestWithVariables.class_variable == "shared_data"
612
+
613
+ def test_instance_variable(self):
614
+ # Each test gets a fresh instance
615
+ self.instance_var = "test_specific"
616
+ assert self.instance_var == "test_specific"
617
+ ```
618
+
619
+ **Parametrized Test Methods:**
620
+
621
+ Use `@parametrize` on class methods just like regular test functions:
622
+
623
+ ```python
624
+ from rustest import parametrize
625
+
626
+ class TestParametrized:
627
+ """Test class with parametrized methods."""
628
+
629
+ @parametrize("value,expected", [(2, 4), (3, 9), (4, 16)])
630
+ def test_square(self, value, expected):
631
+ assert value ** 2 == expected
632
+ ```
633
+
634
+ ### Test Output
635
+
636
+ When you run rustest, you'll see clean, informative output:
637
+
638
+ ```
639
+ PASSED 0.001s test_simple_assertion
640
+ PASSED 0.002s test_string_operations
641
+ PASSED 0.001s test_database_query
642
+ PASSED 0.003s test_square[two]
643
+ PASSED 0.001s test_square[three]
644
+ PASSED 0.002s test_square[four]
645
+ SKIPPED 0.000s test_future_feature
646
+ FAILED 0.005s test_broken_feature
647
+ ----------------------------------------
648
+ AssertionError: Expected 5, got 4
649
+ at test_example.py:42
650
+
651
+ 8 tests: 6 passed, 1 failed, 1 skipped in 0.015s
652
+ ```
653
+
654
+ ## Feature Comparison with pytest
655
+
656
+ Rustest aims to provide the most commonly-used pytest features with dramatically better performance. Here's how the two compare:
657
+
658
+ | Feature | pytest | rustest | Notes |
659
+ |---------|--------|---------|-------|
660
+ | **Core Test Discovery** |
661
+ | `test_*.py` / `*_test.py` files | โœ… | โœ… | Rustest uses Rust for dramatically faster discovery |
662
+ | Test function detection (`test_*`) | โœ… | โœ… | |
663
+ | Test class detection (`Test*`) | โœ… | โœ… | Full pytest-style class support with fixture methods |
664
+ | Pattern-based filtering | โœ… | โœ… | `-k` pattern matching |
665
+ | Markdown code block testing | โœ… (`pytest-codeblocks`) | โœ… | Built-in support for testing Python blocks in `.md` files |
666
+ | **Fixtures** |
667
+ | `@fixture` decorator | โœ… | โœ… | Rust-based dependency resolution |
668
+ | Fixture dependency injection | โœ… | โœ… | Much faster in rustest |
669
+ | Fixture scopes (function/class/module/session) | โœ… | โœ… | Full support for all scopes |
670
+ | Yield fixtures (setup/teardown) | โœ… | โœ… | Full support with cleanup |
671
+ | Fixture methods within test classes | โœ… | โœ… | Define fixtures as class methods |
672
+ | Fixture parametrization | โœ… | ๐Ÿšง | Planned |
673
+ | **Parametrization** |
674
+ | `@parametrize` decorator | โœ… | โœ… | Full support with custom IDs |
675
+ | Multiple parameter sets | โœ… | โœ… | |
676
+ | Parametrize with fixtures | โœ… | โœ… | |
677
+ | **Marks** |
678
+ | `@mark.skip` / `@skip` | โœ… | โœ… | Skip tests with reasons |
679
+ | Custom marks (`@mark.slow`, etc.) | โœ… | โœ… | Just added! |
680
+ | Mark with arguments | โœ… | โœ… | `@mark.timeout(30)` |
681
+ | Selecting tests by mark (`-m`) | โœ… | ๐Ÿšง | Mark metadata collected, filtering planned |
682
+ | **Test Execution** |
683
+ | Detailed assertion introspection | โœ… | โŒ | Uses standard Python assertions |
684
+ | Parallel execution | โœ… (`pytest-xdist`) | ๐Ÿšง | Planned (Rust makes this easier) |
685
+ | Test isolation | โœ… | โœ… | |
686
+ | Stdout/stderr capture | โœ… | โœ… | |
687
+ | **Reporting** |
688
+ | Pass/fail/skip summary | โœ… | โœ… | |
689
+ | Failure tracebacks | โœ… | โœ… | Full Python traceback support |
690
+ | Duration reporting | โœ… | โœ… | Per-test timing |
691
+ | JUnit XML output | โœ… | ๐Ÿšง | Planned |
692
+ | HTML reports | โœ… (`pytest-html`) | ๐Ÿšง | Planned |
693
+ | **Advanced Features** |
694
+ | Plugins | โœ… | โŒ | Not planned (keeps rustest simple) |
695
+ | Hooks | โœ… | โŒ | Not planned |
696
+ | Custom collectors | โœ… | โŒ | Not planned |
697
+ | `conftest.py` | โœ… | โœ… | Shared fixtures across test files |
698
+ | **Developer Experience** |
699
+ | Fully typed Python API | โš ๏ธ | โœ… | rustest uses `basedpyright` strict mode |
700
+ | Fast CI/CD runs | โš ๏ธ | โœ… | 78x faster = dramatically shorter feedback loops |
701
+
702
+ **Legend:**
703
+ - โœ… Fully supported
704
+ - ๐Ÿšง Planned or in progress
705
+ - โš ๏ธ Partial support
706
+ - โŒ Not planned
707
+
708
+ **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.
709
+
710
+ ## License
711
+
712
+ rustest is distributed under the terms of the MIT license. See [LICENSE](LICENSE).
713
+