rustest 0.1.0__cp311-cp311-macosx_11_0_arm64.whl → 0.5.0__cp311-cp311-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.
- rustest/__init__.py +11 -7
- rustest/__main__.py +1 -1
- rustest/approx.py +160 -0
- rustest/{_cli.py → cli.py} +17 -3
- rustest/core.py +24 -7
- rustest/decorators.py +423 -0
- rustest/py.typed +0 -0
- rustest/{_reporting.py → reporting.py} +3 -3
- rustest/rust.cpython-311-darwin.so +0 -0
- rustest/{_rust.py → rust.py} +1 -1
- rustest/{_rust.pyi → rust.pyi} +9 -7
- rustest-0.5.0.dist-info/METADATA +208 -0
- rustest-0.5.0.dist-info/RECORD +16 -0
- rustest/_decorators.py +0 -194
- rustest/_rust.cpython-311-darwin.so +0 -0
- rustest-0.1.0.dist-info/METADATA +0 -487
- rustest-0.1.0.dist-info/RECORD +0 -14
- {rustest-0.1.0.dist-info → rustest-0.5.0.dist-info}/WHEEL +0 -0
- {rustest-0.1.0.dist-info → rustest-0.5.0.dist-info}/entry_points.txt +0 -0
- {rustest-0.1.0.dist-info → rustest-0.5.0.dist-info}/licenses/LICENSE +0 -0
rustest/__init__.py
CHANGED
|
@@ -2,23 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from . import
|
|
6
|
-
from .
|
|
7
|
-
from .
|
|
5
|
+
from . import decorators
|
|
6
|
+
from .approx import approx
|
|
7
|
+
from .cli import main
|
|
8
|
+
from .reporting import RunReport, TestResult
|
|
8
9
|
from .core import run
|
|
9
10
|
|
|
10
|
-
fixture =
|
|
11
|
-
mark =
|
|
12
|
-
parametrize =
|
|
13
|
-
|
|
11
|
+
fixture = decorators.fixture
|
|
12
|
+
mark = decorators.mark
|
|
13
|
+
parametrize = decorators.parametrize
|
|
14
|
+
raises = decorators.raises
|
|
15
|
+
skip = decorators.skip
|
|
14
16
|
|
|
15
17
|
__all__ = [
|
|
16
18
|
"RunReport",
|
|
17
19
|
"TestResult",
|
|
20
|
+
"approx",
|
|
18
21
|
"fixture",
|
|
19
22
|
"main",
|
|
20
23
|
"mark",
|
|
21
24
|
"parametrize",
|
|
25
|
+
"raises",
|
|
22
26
|
"run",
|
|
23
27
|
"skip",
|
|
24
28
|
]
|
rustest/__main__.py
CHANGED
rustest/approx.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Approximate comparison for floating-point numbers.
|
|
2
|
+
|
|
3
|
+
This module provides the `approx` class for comparing floating-point numbers
|
|
4
|
+
with a tolerance, similar to pytest.approx.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Mapping, Sequence, Union
|
|
8
|
+
|
|
9
|
+
# Type alias for values that can be approximated
|
|
10
|
+
ApproxValue = Union[float, int, complex, Sequence[Any], Mapping[str, Any]]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class approx:
|
|
14
|
+
"""Assert that two numbers (or collections of numbers) are equal to each other
|
|
15
|
+
within some tolerance.
|
|
16
|
+
|
|
17
|
+
This is similar to pytest.approx and is useful for comparing floating-point
|
|
18
|
+
numbers that may have small rounding errors.
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
assert 0.1 + 0.2 == approx(0.3)
|
|
22
|
+
assert 0.1 + 0.2 == approx(0.3, rel=1e-6)
|
|
23
|
+
assert 0.1 + 0.2 == approx(0.3, abs=1e-9)
|
|
24
|
+
assert [0.1 + 0.2, 0.3] == approx([0.3, 0.3])
|
|
25
|
+
assert {"a": 0.1 + 0.2} == approx({"a": 0.3})
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
expected: The expected value to compare against
|
|
29
|
+
rel: The relative tolerance (default: 1e-6)
|
|
30
|
+
abs: The absolute tolerance (default: 1e-12)
|
|
31
|
+
|
|
32
|
+
By default, numbers are considered close if the difference between them is
|
|
33
|
+
less than or equal to:
|
|
34
|
+
abs(expected * rel) + abs_tolerance
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
expected: ApproxValue,
|
|
40
|
+
rel: float = 1e-6,
|
|
41
|
+
abs: float = 1e-12,
|
|
42
|
+
) -> None:
|
|
43
|
+
"""Initialize approx with expected value and tolerances.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
expected: The expected value to compare against
|
|
47
|
+
rel: The relative tolerance (default: 1e-6)
|
|
48
|
+
abs: The absolute tolerance (default: 1e-12)
|
|
49
|
+
"""
|
|
50
|
+
super().__init__()
|
|
51
|
+
self.expected = expected
|
|
52
|
+
self.rel = rel
|
|
53
|
+
self.abs = abs
|
|
54
|
+
|
|
55
|
+
def __repr__(self) -> str:
|
|
56
|
+
"""Return a string representation of the approx object."""
|
|
57
|
+
return f"approx({self.expected!r}, rel={self.rel}, abs={self.abs})"
|
|
58
|
+
|
|
59
|
+
def __eq__(self, actual: Any) -> bool:
|
|
60
|
+
"""Compare actual value with expected value within tolerance.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
actual: The actual value to compare
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
True if the values are approximately equal, False otherwise
|
|
67
|
+
"""
|
|
68
|
+
return self._approx_compare(actual, self.expected)
|
|
69
|
+
|
|
70
|
+
def _approx_compare(self, actual: Any, expected: Any) -> bool:
|
|
71
|
+
"""Recursively compare actual and expected values.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
actual: The actual value
|
|
75
|
+
expected: The expected value
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
True if values are approximately equal, False otherwise
|
|
79
|
+
"""
|
|
80
|
+
# Handle None
|
|
81
|
+
if actual is None or expected is None:
|
|
82
|
+
return actual == expected
|
|
83
|
+
|
|
84
|
+
# Handle dictionaries
|
|
85
|
+
if isinstance(expected, dict):
|
|
86
|
+
if not isinstance(actual, dict):
|
|
87
|
+
return False
|
|
88
|
+
if set(actual.keys()) != set(expected.keys()):
|
|
89
|
+
return False
|
|
90
|
+
return all(self._approx_compare(actual[k], expected[k]) for k in expected.keys())
|
|
91
|
+
|
|
92
|
+
# Handle sequences (lists, tuples, etc.) but not strings
|
|
93
|
+
if isinstance(expected, (list, tuple)) and not isinstance(expected, str):
|
|
94
|
+
# Check that actual is the same type (list vs tuple matters)
|
|
95
|
+
if type(actual) is not type(expected):
|
|
96
|
+
return False
|
|
97
|
+
if len(actual) != len(expected):
|
|
98
|
+
return False
|
|
99
|
+
return all(self._approx_compare(a, e) for a, e in zip(actual, expected))
|
|
100
|
+
|
|
101
|
+
# Handle numbers (float, int, complex)
|
|
102
|
+
if isinstance(expected, (float, int, complex)) and isinstance(
|
|
103
|
+
actual, (float, int, complex)
|
|
104
|
+
):
|
|
105
|
+
return self._is_close(actual, expected)
|
|
106
|
+
|
|
107
|
+
# For other types, use exact equality
|
|
108
|
+
return actual == expected
|
|
109
|
+
|
|
110
|
+
def _is_close(
|
|
111
|
+
self, actual: Union[float, int, complex], expected: Union[float, int, complex]
|
|
112
|
+
) -> bool:
|
|
113
|
+
"""Check if two numbers are close within tolerance.
|
|
114
|
+
|
|
115
|
+
Uses the formula: |actual - expected| <= max(rel * max(|actual|, |expected|), abs)
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
actual: The actual number
|
|
119
|
+
expected: The expected number
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
True if numbers are close, False otherwise
|
|
123
|
+
"""
|
|
124
|
+
# Handle infinities and NaN
|
|
125
|
+
if isinstance(actual, complex) or isinstance(expected, complex):
|
|
126
|
+
# For complex numbers, compare real and imaginary parts separately
|
|
127
|
+
if isinstance(actual, complex) and isinstance(expected, complex):
|
|
128
|
+
return self._is_close(actual.real, expected.real) and self._is_close(
|
|
129
|
+
actual.imag, expected.imag
|
|
130
|
+
)
|
|
131
|
+
# One is complex, the other is not
|
|
132
|
+
if isinstance(actual, complex):
|
|
133
|
+
return self._is_close(actual.real, expected) and abs(actual.imag) <= self.abs
|
|
134
|
+
else: # expected is complex
|
|
135
|
+
return self._is_close(actual, expected.real) and abs(expected.imag) <= self.abs
|
|
136
|
+
|
|
137
|
+
# Convert to float for comparison
|
|
138
|
+
actual_float = float(actual)
|
|
139
|
+
expected_float = float(expected)
|
|
140
|
+
|
|
141
|
+
# Handle special float values
|
|
142
|
+
if actual_float == expected_float:
|
|
143
|
+
# This handles infinities and zeros
|
|
144
|
+
return True
|
|
145
|
+
|
|
146
|
+
# Check for NaN - NaN should never be equal to anything
|
|
147
|
+
import math
|
|
148
|
+
|
|
149
|
+
if math.isnan(actual_float) or math.isnan(expected_float):
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
# Check for infinities
|
|
153
|
+
if math.isinf(actual_float) or math.isinf(expected_float):
|
|
154
|
+
return actual_float == expected_float
|
|
155
|
+
|
|
156
|
+
# Calculate tolerance
|
|
157
|
+
abs_diff = abs(actual_float - expected_float)
|
|
158
|
+
tolerance = max(self.rel * max(abs(actual_float), abs(expected_float)), self.abs)
|
|
159
|
+
|
|
160
|
+
return abs_diff <= tolerance
|
rustest/{_cli.py → cli.py}
RENAMED
|
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
import argparse
|
|
6
6
|
from collections.abc import Sequence
|
|
7
7
|
|
|
8
|
-
from .
|
|
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,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
81
87
|
action="store_false",
|
|
82
88
|
help="Disable colored output.",
|
|
83
89
|
)
|
|
84
|
-
_ = parser.
|
|
90
|
+
_ = parser.add_argument(
|
|
91
|
+
"--no-codeblocks",
|
|
92
|
+
dest="enable_codeblocks",
|
|
93
|
+
action="store_false",
|
|
94
|
+
help="Disable code block tests from markdown files.",
|
|
95
|
+
)
|
|
96
|
+
_ = parser.set_defaults(capture_output=True, color=True, enable_codeblocks=True)
|
|
85
97
|
return parser
|
|
86
98
|
|
|
87
99
|
|
|
@@ -94,10 +106,12 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
94
106
|
Colors.disable()
|
|
95
107
|
|
|
96
108
|
report = run(
|
|
97
|
-
paths=
|
|
109
|
+
paths=list(args.paths),
|
|
98
110
|
pattern=args.pattern,
|
|
111
|
+
mark_expr=args.mark_expr,
|
|
99
112
|
workers=args.workers,
|
|
100
113
|
capture_output=args.capture_output,
|
|
114
|
+
enable_codeblocks=args.enable_codeblocks,
|
|
101
115
|
)
|
|
102
116
|
_print_report(report, verbose=args.verbose, ascii_mode=args.ascii)
|
|
103
117
|
return 0 if report.failed == 0 else 1
|
rustest/core.py
CHANGED
|
@@ -4,18 +4,35 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from collections.abc import Sequence
|
|
6
6
|
|
|
7
|
-
from . import
|
|
8
|
-
from .
|
|
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
|
-
|
|
16
|
-
|
|
14
|
+
pattern: str | None = None,
|
|
15
|
+
mark_expr: str | None = None,
|
|
16
|
+
workers: int | None = None,
|
|
17
|
+
capture_output: bool = True,
|
|
18
|
+
enable_codeblocks: bool = True,
|
|
17
19
|
) -> RunReport:
|
|
18
|
-
"""Execute tests and return a rich report.
|
|
20
|
+
"""Execute tests and return a rich report.
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
Args:
|
|
23
|
+
paths: Files or directories to collect tests from
|
|
24
|
+
pattern: Substring to filter tests by (case insensitive)
|
|
25
|
+
mark_expr: Mark expression to filter tests (e.g., "slow", "not slow", "slow and integration")
|
|
26
|
+
workers: Number of worker slots to use (experimental)
|
|
27
|
+
capture_output: Whether to capture stdout/stderr during test execution
|
|
28
|
+
enable_codeblocks: Whether to enable code block tests from markdown files
|
|
29
|
+
"""
|
|
30
|
+
raw_report = rust.run(
|
|
31
|
+
paths=list(paths),
|
|
32
|
+
pattern=pattern,
|
|
33
|
+
mark_expr=mark_expr,
|
|
34
|
+
workers=workers,
|
|
35
|
+
capture_output=capture_output,
|
|
36
|
+
enable_codeblocks=enable_codeblocks,
|
|
37
|
+
)
|
|
21
38
|
return RunReport.from_py(raw_report)
|