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.
rustest/__init__.py ADDED
@@ -0,0 +1,39 @@
1
+ """Public Python API for rustest."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from . import decorators
6
+ from .approx import approx
7
+ from .cli import main
8
+ from .reporting import RunReport, TestResult
9
+ from .core import run
10
+
11
+ fixture = decorators.fixture
12
+ mark = decorators.mark
13
+ parametrize = decorators.parametrize
14
+ raises = decorators.raises
15
+ skip = decorators.skip # Function version that raises Skipped
16
+ skip_decorator = decorators.skip_decorator # Decorator version (use via @mark.skip)
17
+ fail = decorators.fail
18
+ Failed = decorators.Failed
19
+ Skipped = decorators.Skipped
20
+ XFailed = decorators.XFailed
21
+ xfail = decorators.xfail
22
+
23
+ __all__ = [
24
+ "Failed",
25
+ "RunReport",
26
+ "Skipped",
27
+ "TestResult",
28
+ "XFailed",
29
+ "approx",
30
+ "fail",
31
+ "fixture",
32
+ "main",
33
+ "mark",
34
+ "parametrize",
35
+ "raises",
36
+ "run",
37
+ "skip",
38
+ "xfail",
39
+ ]
rustest/__main__.py ADDED
@@ -0,0 +1,10 @@
1
+ """Module executed when running ``python -m rustest``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+ from .cli import main
8
+
9
+ if __name__ == "__main__":
10
+ sys.exit(main())
rustest/approx.py ADDED
@@ -0,0 +1,176 @@
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 __future__ import annotations
8
+
9
+ from collections.abc import Mapping, Sequence
10
+ from typing import Any, Union, cast
11
+
12
+ ApproxScalar = Union[float, int, complex]
13
+ ApproxValue = Union[ApproxScalar, Sequence["ApproxValue"], Mapping[str, "ApproxValue"]]
14
+
15
+
16
+ class approx:
17
+ """Assert that two numbers (or collections of numbers) are equal to each other
18
+ within some tolerance.
19
+
20
+ This is similar to pytest.approx and is useful for comparing floating-point
21
+ numbers that may have small rounding errors.
22
+
23
+ Usage:
24
+ assert 0.1 + 0.2 == approx(0.3)
25
+ assert 0.1 + 0.2 == approx(0.3, rel=1e-6)
26
+ assert 0.1 + 0.2 == approx(0.3, abs=1e-9)
27
+ assert [0.1 + 0.2, 0.3] == approx([0.3, 0.3])
28
+ assert {"a": 0.1 + 0.2} == approx({"a": 0.3})
29
+
30
+ Args:
31
+ expected: The expected value to compare against
32
+ rel: The relative tolerance (default: 1e-6)
33
+ abs: The absolute tolerance (default: 1e-12)
34
+
35
+ By default, numbers are considered close if the difference between them is
36
+ less than or equal to:
37
+ abs(expected * rel) + abs_tolerance
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ expected: ApproxValue,
43
+ rel: float = 1e-6,
44
+ abs: float = 1e-12,
45
+ ) -> None:
46
+ """Initialize approx with expected value and tolerances.
47
+
48
+ Args:
49
+ expected: The expected value to compare against
50
+ rel: The relative tolerance (default: 1e-6)
51
+ abs: The absolute tolerance (default: 1e-12)
52
+ """
53
+ super().__init__()
54
+ self.expected = expected
55
+ self.rel = rel
56
+ self.abs = abs
57
+
58
+ def __repr__(self) -> str:
59
+ """Return a string representation of the approx object."""
60
+ return f"approx({self.expected!r}, rel={self.rel}, abs={self.abs})"
61
+
62
+ def __eq__(self, actual: Any) -> bool:
63
+ """Compare actual value with expected value within tolerance.
64
+
65
+ Args:
66
+ actual: The actual value to compare
67
+
68
+ Returns:
69
+ True if the values are approximately equal, False otherwise
70
+ """
71
+ return self._approx_compare(actual, self.expected)
72
+
73
+ def _approx_compare(self, actual: Any, expected: Any) -> bool:
74
+ """Recursively compare actual and expected values.
75
+
76
+ Args:
77
+ actual: The actual value
78
+ expected: The expected value
79
+
80
+ Returns:
81
+ True if values are approximately equal, False otherwise
82
+ """
83
+ # Handle None
84
+ if actual is None or expected is None:
85
+ return actual == expected
86
+
87
+ # Handle dictionaries
88
+ if isinstance(expected, Mapping):
89
+ expected_mapping = cast(Mapping[str, ApproxValue], expected)
90
+ if not isinstance(actual, Mapping):
91
+ return False
92
+ actual_mapping = cast(Mapping[str, ApproxValue], actual)
93
+ if set(actual_mapping.keys()) != set(expected_mapping.keys()):
94
+ return False
95
+ return all(
96
+ self._approx_compare(actual_mapping[key], expected_mapping[key])
97
+ for key in expected_mapping
98
+ )
99
+
100
+ # Handle sequences (lists, tuples, etc.) but not strings
101
+ if isinstance(expected, Sequence) and not isinstance(expected, (str, bytes, bytearray)):
102
+ expected_sequence = cast(Sequence[ApproxValue], expected)
103
+ if not (
104
+ isinstance(actual, Sequence)
105
+ and not isinstance(actual, (str, bytes, bytearray))
106
+ and type(actual) is type(expected) # pyright: ignore[reportUnknownArgumentType]
107
+ ):
108
+ return False
109
+ actual_sequence = cast(Sequence[ApproxValue], actual)
110
+ if len(actual_sequence) != len(expected_sequence):
111
+ return False
112
+ return all(
113
+ self._approx_compare(actual_item, expected_item)
114
+ for actual_item, expected_item in zip(actual_sequence, expected_sequence)
115
+ )
116
+
117
+ # Handle numbers (float, int, complex)
118
+ if isinstance(expected, (float, int, complex)) and isinstance(
119
+ actual, (float, int, complex)
120
+ ):
121
+ return self._is_close(actual, expected)
122
+
123
+ # For other types, use exact equality
124
+ return actual == expected
125
+
126
+ def _is_close(
127
+ self, actual: Union[float, int, complex], expected: Union[float, int, complex]
128
+ ) -> bool:
129
+ """Check if two numbers are close within tolerance.
130
+
131
+ Uses the formula: |actual - expected| <= max(rel * max(|actual|, |expected|), abs)
132
+
133
+ Args:
134
+ actual: The actual number
135
+ expected: The expected number
136
+
137
+ Returns:
138
+ True if numbers are close, False otherwise
139
+ """
140
+ # Handle infinities and NaN
141
+ if isinstance(actual, complex) or isinstance(expected, complex):
142
+ # For complex numbers, compare real and imaginary parts separately
143
+ if isinstance(actual, complex) and isinstance(expected, complex):
144
+ return self._is_close(actual.real, expected.real) and self._is_close(
145
+ actual.imag, expected.imag
146
+ )
147
+ # One is complex, the other is not
148
+ if isinstance(actual, complex):
149
+ return self._is_close(actual.real, expected) and abs(actual.imag) <= self.abs
150
+ else: # expected is complex
151
+ return self._is_close(actual, expected.real) and abs(expected.imag) <= self.abs
152
+
153
+ # Convert to float for comparison
154
+ actual_float = float(actual)
155
+ expected_float = float(expected)
156
+
157
+ # Handle special float values
158
+ if actual_float == expected_float:
159
+ # This handles infinities and zeros
160
+ return True
161
+
162
+ # Check for NaN - NaN should never be equal to anything
163
+ import math
164
+
165
+ if math.isnan(actual_float) or math.isnan(expected_float):
166
+ return False
167
+
168
+ # Check for infinities
169
+ if math.isinf(actual_float) or math.isinf(expected_float):
170
+ return actual_float == expected_float
171
+
172
+ # Calculate tolerance
173
+ abs_diff = abs(actual_float - expected_float)
174
+ tolerance = max(self.rel * max(abs(actual_float), abs(expected_float)), self.abs)
175
+
176
+ return abs_diff <= tolerance