rustest 0.2.0__cp311-cp311-macosx_10_12_x86_64.whl → 0.4.0__cp311-cp311-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
 
@@ -81,7 +81,13 @@ def build_parser() -> argparse.ArgumentParser:
81
81
  action="store_false",
82
82
  help="Disable colored output.",
83
83
  )
84
- _ = parser.set_defaults(capture_output=True, color=True)
84
+ _ = parser.add_argument(
85
+ "--no-codeblocks",
86
+ dest="enable_codeblocks",
87
+ action="store_false",
88
+ help="Disable code block tests from markdown files.",
89
+ )
90
+ _ = parser.set_defaults(capture_output=True, color=True, enable_codeblocks=True)
85
91
  return parser
86
92
 
87
93
 
@@ -98,6 +104,7 @@ def main(argv: Sequence[str] | None = None) -> int:
98
104
  pattern=args.pattern,
99
105
  workers=args.workers,
100
106
  capture_output=args.capture_output,
107
+ enable_codeblocks=args.enable_codeblocks,
101
108
  )
102
109
  _print_report(report, verbose=args.verbose, ascii_mode=args.ascii)
103
110
  return 0 if report.failed == 0 else 1
rustest/core.py CHANGED
@@ -4,18 +4,19 @@ 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
+ workers: int | None = None,
16
+ capture_output: bool = True,
17
+ enable_codeblocks: bool = True,
17
18
  ) -> RunReport:
18
19
  """Execute tests and return a rich report."""
19
20
 
20
- raw_report = _rust.run(list(paths), pattern, workers, capture_output)
21
+ raw_report = rust.run(list(paths), pattern, workers, capture_output, enable_codeblocks)
21
22
  return RunReport.from_py(raw_report)
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,21 @@ 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
31
  workers: int | None,
32
32
  capture_output: bool,
33
+ enable_codeblocks: bool,
33
34
  ) -> PyRunReport:
34
- """Run tests and return a report."""
35
+ """Execute tests and return a report."""
35
36
  ...
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rustest
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -37,6 +37,7 @@ Rustest (pronounced like Russ-Test) is a Rust-powered test runner that aims to p
37
37
  - 🚀 **About 2x faster** than pytest on the rustest integration test suite
38
38
  - ✅ Familiar `@fixture`, `@parametrize`, `@skip`, and `@mark` decorators
39
39
  - 🔍 Automatic test discovery (`test_*.py` and `*_test.py` files)
40
+ - 📝 **Built-in markdown code block testing** (like pytest-codeblocks, but faster)
40
41
  - 🎯 Simple, clean API—if you know pytest, you already know rustest
41
42
  - 🧮 Built-in `approx()` helper for tolerant numeric comparisons across scalars, collections, and complex numbers
42
43
  - 🪤 `raises()` context manager for precise exception assertions with optional message matching
@@ -99,6 +100,51 @@ uv add rustest
99
100
  ### For Development
100
101
  If you want to contribute to rustest, see [DEVELOPMENT.md](DEVELOPMENT.md) for setup instructions.
101
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
+
102
148
  ## Quick Start
103
149
 
104
150
  ### 1. Write Your Tests
@@ -151,7 +197,7 @@ rustest --no-capture
151
197
  ### CLI Usage
152
198
 
153
199
  ```bash
154
- # Run all tests in current directory
200
+ # Run all tests in current directory (including markdown code blocks)
155
201
  rustest
156
202
 
157
203
  # Run tests in specific paths
@@ -163,6 +209,9 @@ rustest -k "auth" # Runs all tests with "auth" in the name
163
209
 
164
210
  # Control output capture
165
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
166
215
  ```
167
216
 
168
217
  ### Python API Usage
@@ -171,22 +220,27 @@ You can also run rustest programmatically from Python:
171
220
 
172
221
  ```python
173
222
  from rustest import run
223
+ import os
174
224
 
175
- # Basic usage
176
- report = run(paths=["tests"])
177
- print(f"Passed: {report.passed}, Failed: {report.failed}")
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}")
178
229
 
179
- # With pattern filtering
180
- report = run(paths=["tests"], pattern="user")
230
+ # With pattern filtering
231
+ report = run(paths=["tests"], pattern="user")
181
232
 
182
- # Without output capture (see print statements)
183
- report = run(paths=["tests"], capture_output=False)
233
+ # Without output capture (see print statements)
234
+ report = run(paths=["tests"], capture_output=False)
184
235
 
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}")
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}")
190
244
  ```
191
245
 
192
246
  ### Writing Tests
@@ -441,6 +495,142 @@ def test_full_workflow() -> None:
441
495
  pass
442
496
  ```
443
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
+
444
634
  ### Test Output
445
635
 
446
636
  When you run rustest, you'll see clean, informative output:
@@ -470,13 +660,15 @@ Rustest aims to provide the most commonly-used pytest features with dramatically
470
660
  | **Core Test Discovery** |
471
661
  | `test_*.py` / `*_test.py` files | ✅ | ✅ | Rustest uses Rust for dramatically faster discovery |
472
662
  | Test function detection (`test_*`) | ✅ | ✅ | |
473
- | Test class detection (`Test*`) | ✅ | ✅ | via `unittest.TestCase` support |
663
+ | Test class detection (`Test*`) | ✅ | ✅ | Full pytest-style class support with fixture methods |
474
664
  | Pattern-based filtering | ✅ | ✅ | `-k` pattern matching |
665
+ | Markdown code block testing | ✅ (`pytest-codeblocks`) | ✅ | Built-in support for testing Python blocks in `.md` files |
475
666
  | **Fixtures** |
476
667
  | `@fixture` decorator | ✅ | ✅ | Rust-based dependency resolution |
477
668
  | Fixture dependency injection | ✅ | ✅ | Much faster in rustest |
478
669
  | Fixture scopes (function/class/module/session) | ✅ | ✅ | Full support for all scopes |
479
670
  | Yield fixtures (setup/teardown) | ✅ | ✅ | Full support with cleanup |
671
+ | Fixture methods within test classes | ✅ | ✅ | Define fixtures as class methods |
480
672
  | Fixture parametrization | ✅ | 🚧 | Planned |
481
673
  | **Parametrization** |
482
674
  | `@parametrize` decorator | ✅ | ✅ | Full support with custom IDs |
@@ -0,0 +1,16 @@
1
+ rustest-0.4.0.dist-info/METADATA,sha256=RUECmwFvTEGVbU8UXiT5aPgHXGSGBANBM39Tr-C1ITE,22132
2
+ rustest-0.4.0.dist-info/WHEEL,sha256=p_Yquq5Pk0aa0if4rX4yfMDTA_PSQbCXzFftM7T46yU,106
3
+ rustest-0.4.0.dist-info/entry_points.txt,sha256=7fUa3LO8vudQ4dKG1sTRaDnxcMdBSZsWs9EyuxFQ7Lk,48
4
+ rustest-0.4.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=VeV12EREHrBXva7hvUooEuePKVW6UgJuuaEowkwobgk,8961
9
+ rustest/core.py,sha256=IsRdEfnCuTj41uEsy3WF4g9xI_-pGJ7FLu1s5C-PeKA,561
10
+ rustest/decorators.py,sha256=nijzNG8NQXZd8kEfMjhSB-85gLoIjaIMxwZQNUNWgrE,11373
11
+ rustest/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ rustest/reporting.py,sha256=3-R8aljv2ZbmjOa9Q9KZeCPsMaitm8nZ96LoJS_NnUQ,1623
13
+ rustest/rust.cpython-311-darwin.so,sha256=RqScRyC5hDwjQUSWYFZ1w2xKMCv6w4Fe8ErhSJVWBwo,1591348
14
+ rustest/rust.py,sha256=tCIvjYd06VxoT_rKvv2o8CpXW_pFNua5VgcRDjLgU78,659
15
+ rustest/rust.pyi,sha256=bUx3229uh62MYJbBDGg1Vfnr1y_oyJnv_zK0g7vKe2s,735
16
+ rustest-0.4.0.dist-info/RECORD,,
Binary file
@@ -1,15 +0,0 @@
1
- rustest-0.2.0.dist-info/METADATA,sha256=zNdhsqtUfKsGrhvlI7csbjpmT6FFsb3T3kaE2a5Yx5c,16497
2
- rustest-0.2.0.dist-info/WHEEL,sha256=p_Yquq5Pk0aa0if4rX4yfMDTA_PSQbCXzFftM7T46yU,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-311-darwin.so,sha256=RKKbnOTFQHX1Xb4TcBjGFNgQbAvCvSHI48hLBGcdUdw,1540188
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
File without changes