rustest 0.8.0__cp312-cp312-win_amd64.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 +28 -0
- rustest/__main__.py +10 -0
- rustest/approx.py +176 -0
- rustest/builtin_fixtures.py +309 -0
- rustest/cli.py +311 -0
- rustest/core.py +44 -0
- rustest/decorators.py +549 -0
- rustest/py.typed +0 -0
- rustest/reporting.py +63 -0
- rustest/rust.cp312-win_amd64.pyd +0 -0
- rustest/rust.py +23 -0
- rustest/rust.pyi +39 -0
- rustest-0.8.0.dist-info/METADATA +270 -0
- rustest-0.8.0.dist-info/RECORD +17 -0
- rustest-0.8.0.dist-info/WHEEL +4 -0
- rustest-0.8.0.dist-info/entry_points.txt +2 -0
- rustest-0.8.0.dist-info/licenses/LICENSE +21 -0
rustest/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
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
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"RunReport",
|
|
19
|
+
"TestResult",
|
|
20
|
+
"approx",
|
|
21
|
+
"fixture",
|
|
22
|
+
"main",
|
|
23
|
+
"mark",
|
|
24
|
+
"parametrize",
|
|
25
|
+
"raises",
|
|
26
|
+
"run",
|
|
27
|
+
"skip",
|
|
28
|
+
]
|
rustest/__main__.py
ADDED
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)
|
|
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
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""Builtin fixtures that mirror a subset of pytest's default fixtures."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import itertools
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
import sys
|
|
10
|
+
import tempfile
|
|
11
|
+
from collections.abc import Generator, MutableMapping
|
|
12
|
+
from contextlib import contextmanager
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from types import ModuleType
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Iterator, cast
|
|
16
|
+
|
|
17
|
+
from .decorators import fixture
|
|
18
|
+
|
|
19
|
+
py: ModuleType | None
|
|
20
|
+
try: # pragma: no cover - optional dependency at runtime
|
|
21
|
+
import py as _py_module
|
|
22
|
+
except Exception: # pragma: no cover - import error reported at fixture usage time
|
|
23
|
+
py = None
|
|
24
|
+
else:
|
|
25
|
+
py = _py_module
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
try: # pragma: no cover - typing-only import
|
|
29
|
+
from py import path as _py_path
|
|
30
|
+
except ImportError:
|
|
31
|
+
PyPathLocal = Any
|
|
32
|
+
else:
|
|
33
|
+
PyPathLocal = _py_path.local
|
|
34
|
+
|
|
35
|
+
else: # pragma: no cover - imported only for typing
|
|
36
|
+
PyPathLocal = Any
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class _NotSet:
|
|
40
|
+
"""Sentinel value for tracking missing attributes/items."""
|
|
41
|
+
|
|
42
|
+
def __repr__(self) -> str: # pragma: no cover - debug helper
|
|
43
|
+
return "<NOTSET>"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_NOT_SET = _NotSet()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class MonkeyPatch:
|
|
50
|
+
"""Lightweight re-implementation of :class:`pytest.MonkeyPatch`."""
|
|
51
|
+
|
|
52
|
+
def __init__(self) -> None:
|
|
53
|
+
super().__init__()
|
|
54
|
+
self._setattrs: list[tuple[object, str, object | _NotSet]] = []
|
|
55
|
+
self._setitems: list[tuple[MutableMapping[Any, Any], Any, object | _NotSet]] = []
|
|
56
|
+
self._environ: list[tuple[str, str | _NotSet]] = []
|
|
57
|
+
self._syspath_prepend: list[str] = []
|
|
58
|
+
self._cwd_original: str | None = None
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
@contextmanager
|
|
62
|
+
def context(cls) -> Generator[MonkeyPatch, None, None]:
|
|
63
|
+
patch = cls()
|
|
64
|
+
try:
|
|
65
|
+
yield patch
|
|
66
|
+
finally:
|
|
67
|
+
patch.undo()
|
|
68
|
+
|
|
69
|
+
def setattr(
|
|
70
|
+
self,
|
|
71
|
+
target: object | str,
|
|
72
|
+
name: object | str = _NOT_SET,
|
|
73
|
+
value: object = _NOT_SET,
|
|
74
|
+
*,
|
|
75
|
+
raising: bool = True,
|
|
76
|
+
) -> None:
|
|
77
|
+
if value is _NOT_SET:
|
|
78
|
+
if not isinstance(target, str):
|
|
79
|
+
raise TypeError("use setattr(target, name, value) or setattr('module.attr', value)")
|
|
80
|
+
module_path, attr_name = target.rsplit(".", 1)
|
|
81
|
+
module = importlib.import_module(module_path)
|
|
82
|
+
obj = module
|
|
83
|
+
attr_value = name
|
|
84
|
+
if attr_value is _NOT_SET:
|
|
85
|
+
raise TypeError("value must be provided when using dotted path syntax")
|
|
86
|
+
attr_name = attr_name
|
|
87
|
+
else:
|
|
88
|
+
if not isinstance(name, str):
|
|
89
|
+
raise TypeError("attribute name must be a string")
|
|
90
|
+
obj = target
|
|
91
|
+
attr_name = name
|
|
92
|
+
attr_value = value
|
|
93
|
+
|
|
94
|
+
original = getattr(obj, attr_name, _NOT_SET)
|
|
95
|
+
if original is _NOT_SET and raising:
|
|
96
|
+
raise AttributeError(f"{attr_name!r} not found for patching")
|
|
97
|
+
|
|
98
|
+
setattr(obj, attr_name, attr_value)
|
|
99
|
+
self._setattrs.append((obj, attr_name, original))
|
|
100
|
+
|
|
101
|
+
def delattr(
|
|
102
|
+
self, target: object | str, name: str | _NotSet = _NOT_SET, *, raising: bool = True
|
|
103
|
+
) -> None:
|
|
104
|
+
if isinstance(target, str) and name is _NOT_SET:
|
|
105
|
+
module_path, attr_name = target.rsplit(".", 1)
|
|
106
|
+
module = importlib.import_module(module_path)
|
|
107
|
+
obj = module
|
|
108
|
+
attr_name = attr_name
|
|
109
|
+
else:
|
|
110
|
+
if not isinstance(name, str):
|
|
111
|
+
raise TypeError("attribute name must be a string")
|
|
112
|
+
obj = target
|
|
113
|
+
attr_name = name
|
|
114
|
+
|
|
115
|
+
original = getattr(obj, attr_name, _NOT_SET)
|
|
116
|
+
if original is _NOT_SET:
|
|
117
|
+
if raising:
|
|
118
|
+
raise AttributeError(f"{attr_name!r} not found for deletion")
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
delattr(obj, attr_name)
|
|
122
|
+
self._setattrs.append((obj, attr_name, original))
|
|
123
|
+
|
|
124
|
+
def setitem(self, mapping: MutableMapping[Any, Any], key: Any, value: Any) -> None:
|
|
125
|
+
original = mapping.get(key, _NOT_SET)
|
|
126
|
+
mapping[key] = value
|
|
127
|
+
self._setitems.append((mapping, key, original))
|
|
128
|
+
|
|
129
|
+
def delitem(self, mapping: MutableMapping[Any, Any], key: Any, *, raising: bool = True) -> None:
|
|
130
|
+
if key not in mapping:
|
|
131
|
+
if raising:
|
|
132
|
+
raise KeyError(key)
|
|
133
|
+
self._setitems.append((mapping, key, _NOT_SET))
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
original = mapping[key]
|
|
137
|
+
del mapping[key]
|
|
138
|
+
self._setitems.append((mapping, key, original))
|
|
139
|
+
|
|
140
|
+
def setenv(self, name: str, value: Any, prepend: str | None = None) -> None:
|
|
141
|
+
str_value = str(value)
|
|
142
|
+
if prepend and name in os.environ:
|
|
143
|
+
str_value = f"{str_value}{prepend}{os.environ[name]}"
|
|
144
|
+
original = os.environ.get(name)
|
|
145
|
+
os.environ[name] = str_value
|
|
146
|
+
stored_original: str | _NotSet = original if original is not None else _NOT_SET
|
|
147
|
+
self._environ.append((name, stored_original))
|
|
148
|
+
|
|
149
|
+
def delenv(self, name: str, *, raising: bool = True) -> None:
|
|
150
|
+
if name not in os.environ:
|
|
151
|
+
if raising:
|
|
152
|
+
raise KeyError(name)
|
|
153
|
+
self._environ.append((name, _NOT_SET))
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
original = os.environ.pop(name)
|
|
157
|
+
self._environ.append((name, original))
|
|
158
|
+
|
|
159
|
+
def syspath_prepend(self, path: os.PathLike[str] | str) -> None:
|
|
160
|
+
str_path = os.fspath(path)
|
|
161
|
+
if str_path in sys.path:
|
|
162
|
+
return
|
|
163
|
+
sys.path.insert(0, str_path)
|
|
164
|
+
self._syspath_prepend.append(str_path)
|
|
165
|
+
|
|
166
|
+
def chdir(self, path: os.PathLike[str] | str) -> None:
|
|
167
|
+
if self._cwd_original is None:
|
|
168
|
+
self._cwd_original = os.getcwd()
|
|
169
|
+
os.chdir(os.fspath(path))
|
|
170
|
+
|
|
171
|
+
def undo(self) -> None:
|
|
172
|
+
for obj, attr_name, original in reversed(self._setattrs):
|
|
173
|
+
if original is _NOT_SET:
|
|
174
|
+
try:
|
|
175
|
+
delattr(obj, attr_name)
|
|
176
|
+
except AttributeError: # pragma: no cover - defensive
|
|
177
|
+
pass
|
|
178
|
+
else:
|
|
179
|
+
setattr(obj, attr_name, original)
|
|
180
|
+
self._setattrs.clear()
|
|
181
|
+
|
|
182
|
+
for mapping, key, original in reversed(self._setitems):
|
|
183
|
+
if original is _NOT_SET:
|
|
184
|
+
mapping.pop(key, None)
|
|
185
|
+
else:
|
|
186
|
+
mapping[key] = original
|
|
187
|
+
self._setitems.clear()
|
|
188
|
+
|
|
189
|
+
for name, original in reversed(self._environ):
|
|
190
|
+
if original is _NOT_SET:
|
|
191
|
+
os.environ.pop(name, None)
|
|
192
|
+
else:
|
|
193
|
+
os.environ[name] = cast(str, original)
|
|
194
|
+
self._environ.clear()
|
|
195
|
+
|
|
196
|
+
while self._syspath_prepend:
|
|
197
|
+
str_path = self._syspath_prepend.pop()
|
|
198
|
+
try:
|
|
199
|
+
sys.path.remove(str_path)
|
|
200
|
+
except ValueError: # pragma: no cover - path already removed externally
|
|
201
|
+
pass
|
|
202
|
+
|
|
203
|
+
if self._cwd_original is not None:
|
|
204
|
+
os.chdir(self._cwd_original)
|
|
205
|
+
self._cwd_original = None
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class TmpPathFactory:
|
|
209
|
+
"""Create temporary directories using :class:`pathlib.Path`."""
|
|
210
|
+
|
|
211
|
+
def __init__(self, prefix: str = "tmp_path") -> None:
|
|
212
|
+
super().__init__()
|
|
213
|
+
self._base = Path(tempfile.mkdtemp(prefix=f"rustest-{prefix}-"))
|
|
214
|
+
self._counter = itertools.count()
|
|
215
|
+
self._created: list[Path] = []
|
|
216
|
+
|
|
217
|
+
def mktemp(self, basename: str, *, numbered: bool = True) -> Path:
|
|
218
|
+
if not basename:
|
|
219
|
+
raise ValueError("basename must be a non-empty string")
|
|
220
|
+
if numbered:
|
|
221
|
+
suffix = next(self._counter)
|
|
222
|
+
name = f"{basename}{suffix}"
|
|
223
|
+
else:
|
|
224
|
+
name = basename
|
|
225
|
+
path = self._base / name
|
|
226
|
+
path.mkdir(parents=True, exist_ok=False)
|
|
227
|
+
self._created.append(path)
|
|
228
|
+
return path
|
|
229
|
+
|
|
230
|
+
def getbasetemp(self) -> Path:
|
|
231
|
+
return self._base
|
|
232
|
+
|
|
233
|
+
def cleanup(self) -> None:
|
|
234
|
+
for path in reversed(self._created):
|
|
235
|
+
shutil.rmtree(path, ignore_errors=True)
|
|
236
|
+
shutil.rmtree(self._base, ignore_errors=True)
|
|
237
|
+
self._created.clear()
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
class TmpDirFactory:
|
|
241
|
+
"""Wrapper that exposes ``py.path.local`` directories."""
|
|
242
|
+
|
|
243
|
+
def __init__(self, path_factory: TmpPathFactory) -> None:
|
|
244
|
+
super().__init__()
|
|
245
|
+
self._factory = path_factory
|
|
246
|
+
|
|
247
|
+
def mktemp(self, basename: str, *, numbered: bool = True) -> Any:
|
|
248
|
+
if py is None: # pragma: no cover - exercised only when dependency missing
|
|
249
|
+
raise RuntimeError("py library is required for tmpdir fixtures")
|
|
250
|
+
path = self._factory.mktemp(basename, numbered=numbered)
|
|
251
|
+
return py.path.local(path)
|
|
252
|
+
|
|
253
|
+
def getbasetemp(self) -> Any:
|
|
254
|
+
if py is None: # pragma: no cover - exercised only when dependency missing
|
|
255
|
+
raise RuntimeError("py library is required for tmpdir fixtures")
|
|
256
|
+
return py.path.local(self._factory.getbasetemp())
|
|
257
|
+
|
|
258
|
+
def cleanup(self) -> None:
|
|
259
|
+
self._factory.cleanup()
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@fixture(scope="session")
|
|
263
|
+
def tmp_path_factory() -> Iterator[TmpPathFactory]:
|
|
264
|
+
factory = TmpPathFactory("tmp_path")
|
|
265
|
+
try:
|
|
266
|
+
yield factory
|
|
267
|
+
finally:
|
|
268
|
+
factory.cleanup()
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@fixture(scope="function")
|
|
272
|
+
def tmp_path(tmp_path_factory: TmpPathFactory) -> Iterator[Path]:
|
|
273
|
+
path = tmp_path_factory.mktemp("tmp_path")
|
|
274
|
+
yield path
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@fixture(scope="session")
|
|
278
|
+
def tmpdir_factory() -> Iterator[TmpDirFactory]:
|
|
279
|
+
factory = TmpDirFactory(TmpPathFactory("tmpdir"))
|
|
280
|
+
try:
|
|
281
|
+
yield factory
|
|
282
|
+
finally:
|
|
283
|
+
factory.cleanup()
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@fixture(scope="function")
|
|
287
|
+
def tmpdir(tmpdir_factory: TmpDirFactory) -> Iterator[Any]:
|
|
288
|
+
yield tmpdir_factory.mktemp("tmpdir")
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@fixture(scope="function")
|
|
292
|
+
def monkeypatch() -> Iterator[MonkeyPatch]:
|
|
293
|
+
patch = MonkeyPatch()
|
|
294
|
+
try:
|
|
295
|
+
yield patch
|
|
296
|
+
finally:
|
|
297
|
+
patch.undo()
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
__all__ = [
|
|
301
|
+
"MonkeyPatch",
|
|
302
|
+
"TmpDirFactory",
|
|
303
|
+
"TmpPathFactory",
|
|
304
|
+
"monkeypatch",
|
|
305
|
+
"tmpdir",
|
|
306
|
+
"tmpdir_factory",
|
|
307
|
+
"tmp_path",
|
|
308
|
+
"tmp_path_factory",
|
|
309
|
+
]
|