pUnit 1.7.2__tar.gz → 1.7.3__tar.gz
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.
- {punit-1.7.2/src/pUnit.egg-info → punit-1.7.3}/PKG-INFO +1 -1
- {punit-1.7.2 → punit-1.7.3}/pyproject.toml +1 -1
- {punit-1.7.2 → punit-1.7.3/src/pUnit.egg-info}/PKG-INFO +1 -1
- {punit-1.7.2 → punit-1.7.3}/src/punit/TestResult.py +17 -1
- {punit-1.7.2 → punit-1.7.3}/src/punit/__init__.py +2 -2
- {punit-1.7.2 → punit-1.7.3}/src/punit/__main__.py +14 -14
- {punit-1.7.2 → punit-1.7.3}/src/punit/cli.py +26 -7
- {punit-1.7.2 → punit-1.7.3}/src/punit/filters/Filter.py +13 -2
- {punit-1.7.2 → punit-1.7.3}/src/punit/reports/JUnitReportGenerator.py +2 -2
- {punit-1.7.2 → punit-1.7.3}/src/punit/runner.py +7 -6
- {punit-1.7.2 → punit-1.7.3}/src/punit/theories/Theory.py +17 -1
- {punit-1.7.2 → punit-1.7.3}/LICENSE +0 -0
- {punit-1.7.2 → punit-1.7.3}/README.md +0 -0
- {punit-1.7.2 → punit-1.7.3}/setup.cfg +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/pUnit.egg-info/SOURCES.txt +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/pUnit.egg-info/dependency_links.txt +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/pUnit.egg-info/entry_points.txt +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/pUnit.egg-info/requires.txt +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/pUnit.egg-info/top_level.txt +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/assertions/__init__.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/assertions/collections.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/assertions/exceptions.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/assertions/numeric.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/assertions/strings.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/discovery/TestModuleDiscovery.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/discovery/__init__.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/facts/Fact.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/facts/FactManager.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/facts/__init__.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/filters/FilterManager.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/filters/__init__.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/metadata/CallableMetadata.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/metadata/__init__.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/py.typed +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/reports/HtmlReportGenerator.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/reports/JsonReportGenerator.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/reports/__init__.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/setups/Setup.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/setups/SetupManager.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/setups/__init__.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/teardowns/Teardown.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/teardowns/TeardownManager.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/teardowns/__init__.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/theories/TheoryManager.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/theories/__init__.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/traits/Trait.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/traits/TraitManager.py +0 -0
- {punit-1.7.2 → punit-1.7.3}/src/punit/traits/__init__.py +0 -0
|
@@ -27,6 +27,22 @@ class TextIOCapture:
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class TestResult:
|
|
30
|
+
"""
|
|
31
|
+
Accumulates the outcome of a single test execution.
|
|
32
|
+
|
|
33
|
+
Example
|
|
34
|
+
-------
|
|
35
|
+
|
|
36
|
+
Create and populate a result manually:
|
|
37
|
+
|
|
38
|
+
.. code-block:: python
|
|
39
|
+
|
|
40
|
+
result = TestResult()
|
|
41
|
+
result.module_name = 'tests.example'
|
|
42
|
+
result.test_name = 'test_something'
|
|
43
|
+
result.is_success = True
|
|
44
|
+
|
|
45
|
+
"""
|
|
30
46
|
|
|
31
47
|
__class_name: Optional[str]
|
|
32
48
|
__exception: Exception | None
|
|
@@ -175,7 +191,7 @@ class TestResult:
|
|
|
175
191
|
elif took >= 0.000000001:
|
|
176
192
|
return f'{(took*1000000):.3f}'.rstrip('0').rstrip('.') + 'μs'
|
|
177
193
|
else:
|
|
178
|
-
return f'{(took*
|
|
194
|
+
return f'{(took*1000000):.3f}'.rstrip('0').rstrip('.') + 'ns'
|
|
179
195
|
|
|
180
196
|
def capture_output(self, quiet: bool = False) -> None:
|
|
181
197
|
self.__stdout_capture = TextIOCapture(sys.stdout, quiet)
|
|
@@ -16,7 +16,7 @@ from .teardowns.TeardownManager import TeardownManager
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
async def async_main() -> None:
|
|
19
|
-
|
|
19
|
+
start_time = time.time()
|
|
20
20
|
cli = CommandLineInterface.parse()
|
|
21
21
|
if cli.help: # pragma: no cover
|
|
22
22
|
cli.print_help()
|
|
@@ -26,27 +26,27 @@ async def async_main() -> None:
|
|
|
26
26
|
cli.print_version()
|
|
27
27
|
os.chdir(cli.workdir)
|
|
28
28
|
if cli.no_pathfix is not True:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if os.path.exists(
|
|
32
|
-
sys.path.append(
|
|
33
|
-
if
|
|
34
|
-
sys.path.append(
|
|
29
|
+
cwd_path = str(Path.cwd())
|
|
30
|
+
src_path = os.path.join(cwd_path, 'src')
|
|
31
|
+
if os.path.exists(src_path) and src_path not in sys.path:
|
|
32
|
+
sys.path.append(src_path)
|
|
33
|
+
if cwd_path not in sys.path:
|
|
34
|
+
sys.path.append(cwd_path)
|
|
35
35
|
test_module_discovery = TestModuleDiscovery(
|
|
36
36
|
os.path.join(cli.workdir, cli.test_package_name),
|
|
37
37
|
cli.includePatterns,
|
|
38
38
|
cli.excludePatterns,
|
|
39
39
|
cli)
|
|
40
40
|
test_module_discovery.discover()
|
|
41
|
-
|
|
42
|
-
results = await
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
test_runner = TestRunner(cli.test_package_name, test_module_discovery.filenames, cli)
|
|
42
|
+
results = await test_runner.run()
|
|
43
|
+
total_time = time.time() - start_time
|
|
44
|
+
failure_count = 0
|
|
45
45
|
for result in results:
|
|
46
46
|
if not result.is_success:
|
|
47
|
-
|
|
47
|
+
failure_count += 1 # pragma: no cover
|
|
48
48
|
if not cli.quiet:
|
|
49
|
-
print(f'Total: {len(results)}, Failures: {
|
|
49
|
+
print(f'Total: {len(results)}, Failures: {failure_count}, Took: {total_time:.3f}s')
|
|
50
50
|
if cli.reportFormat is not None: # pragma: no cover
|
|
51
51
|
report: str = ''
|
|
52
52
|
match cli.reportFormat:
|
|
@@ -74,7 +74,7 @@ async def async_main() -> None:
|
|
|
74
74
|
pass
|
|
75
75
|
|
|
76
76
|
if cli.no_exitcode is not True:
|
|
77
|
-
if
|
|
77
|
+
if failure_count:
|
|
78
78
|
# test failures trigger exit code 119 (for automation gating)
|
|
79
79
|
sys.exit(119)
|
|
80
80
|
if TeardownManager.instance().teardown_error_count > 0:
|
|
@@ -12,6 +12,20 @@ from .traits import Trait
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class CommandLineInterface:
|
|
15
|
+
"""
|
|
16
|
+
Command-line argument parser and display helper for pUnit.
|
|
17
|
+
|
|
18
|
+
Example
|
|
19
|
+
-------
|
|
20
|
+
|
|
21
|
+
Parse CLI arguments from ``sys.argv``:
|
|
22
|
+
|
|
23
|
+
.. code-block:: python
|
|
24
|
+
|
|
25
|
+
cli = CommandLineInterface.parse()
|
|
26
|
+
print(cli.test_package_name)
|
|
27
|
+
|
|
28
|
+
"""
|
|
15
29
|
|
|
16
30
|
__aliases: dict[str, str]
|
|
17
31
|
__excludePatterns: list[str]
|
|
@@ -49,6 +63,12 @@ class CommandLineInterface:
|
|
|
49
63
|
self.__verbose = False
|
|
50
64
|
|
|
51
65
|
def __parse(self, argv: list[str]) -> CommandLineInterface:
|
|
66
|
+
"""
|
|
67
|
+
Parse command-line arguments using a finite-state machine.
|
|
68
|
+
|
|
69
|
+
:param argv: List of argument strings (typically ``sys.argv``).
|
|
70
|
+
:returns: ``self`` for method chaining.
|
|
71
|
+
"""
|
|
52
72
|
aliasName: str | None = None
|
|
53
73
|
extractFilter: bool = False
|
|
54
74
|
extractExcludePattern: bool = False
|
|
@@ -160,7 +180,7 @@ class CommandLineInterface:
|
|
|
160
180
|
FilterManager.instance().add(arg)
|
|
161
181
|
|
|
162
182
|
@property
|
|
163
|
-
def aliases(self) -> dict:
|
|
183
|
+
def aliases(self) -> dict[str, str]:
|
|
164
184
|
return self.__aliases
|
|
165
185
|
|
|
166
186
|
@property
|
|
@@ -213,16 +233,15 @@ class CommandLineInterface:
|
|
|
213
233
|
|
|
214
234
|
@property
|
|
215
235
|
def no_exitcode(self) -> bool:
|
|
216
|
-
return self.__no_exitcode
|
|
236
|
+
return self.__no_exitcode
|
|
217
237
|
|
|
218
238
|
@property
|
|
219
239
|
def no_pathfix(self) -> bool:
|
|
220
|
-
return self.__no_pathfix
|
|
240
|
+
return self.__no_pathfix
|
|
221
241
|
|
|
222
242
|
def print_help(self) -> None:
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
print("""
|
|
243
|
+
self.print_version()
|
|
244
|
+
print("""
|
|
226
245
|
Usage: python3 -m punit [-h|--help]
|
|
227
246
|
[-q|--quiet] [-v|--verbose]
|
|
228
247
|
[-z|--failfast]
|
|
@@ -281,7 +300,7 @@ Options:
|
|
|
281
300
|
write to FILENAME. In this case `--report` does not
|
|
282
301
|
suppress any program output.
|
|
283
302
|
""")
|
|
284
|
-
|
|
303
|
+
os._exit(0)
|
|
285
304
|
|
|
286
305
|
def print_summary(self) -> None:
|
|
287
306
|
self.print_version()
|
|
@@ -5,17 +5,28 @@ import re
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class Filter:
|
|
8
|
+
"""
|
|
9
|
+
Wraps a glob-style filter pattern and compiles it to a case-insensitive regex.
|
|
10
|
+
|
|
11
|
+
Patterns use ``*`` for multi-character wildcards and ``?`` for single characters.
|
|
12
|
+
Prefix with ``!`` to mark the filter as an exclusion rule.
|
|
13
|
+
"""
|
|
8
14
|
|
|
9
15
|
__isExclude: bool
|
|
10
16
|
__pattern: str
|
|
11
|
-
__re: re.Pattern
|
|
17
|
+
__re: re.Pattern[str]
|
|
12
18
|
|
|
13
19
|
def __init__(self, pattern: str) -> None:
|
|
14
20
|
self.__isExclude = pattern.startswith('!')
|
|
15
21
|
self.__pattern = pattern
|
|
16
22
|
self.__re = self.__toRegex(pattern)
|
|
17
23
|
|
|
18
|
-
def __toRegex(self, pattern: str) -> re.Pattern:
|
|
24
|
+
def __toRegex(self, pattern: str) -> re.Pattern[str]:
|
|
25
|
+
"""Convert a glob-style pattern to a compiled regex.
|
|
26
|
+
|
|
27
|
+
Supports ``*`` (multi-char wildcard), ``?`` (single-char wildcard),
|
|
28
|
+
and ``!`` prefix (exclusion). Matching is case-insensitive.
|
|
29
|
+
"""
|
|
19
30
|
if self.__isExclude:
|
|
20
31
|
pattern = pattern[1:]
|
|
21
32
|
pattern = re.escape(pattern)\
|
|
@@ -65,7 +65,7 @@ class JUnitTestSuite:
|
|
|
65
65
|
result: int = 0
|
|
66
66
|
if self.testCases is not None:
|
|
67
67
|
for testCase in self.testCases:
|
|
68
|
-
if testCase.disabled
|
|
68
|
+
if testCase.disabled is True:
|
|
69
69
|
result += 1
|
|
70
70
|
return result
|
|
71
71
|
|
|
@@ -187,7 +187,7 @@ class JUnitReportGenerator:
|
|
|
187
187
|
ele.text = testCase.stderr
|
|
188
188
|
testSuitesEle.attrib['disabled'] = str(totalDisabledCount)
|
|
189
189
|
testSuitesEle.attrib['errors'] = str(totalErrorCount)
|
|
190
|
-
testSuitesEle.attrib['failures'] = str(
|
|
190
|
+
testSuitesEle.attrib['failures'] = str(totalFailureCount)
|
|
191
191
|
testSuitesEle.attrib['tests'] = str(totalTestCount)
|
|
192
192
|
testSuitesEle.attrib['time'] = f'{totalTime:.6f}'.rstrip('0').rstrip('.')
|
|
193
193
|
et.indent(testSuitesEle, space=" ")
|
|
@@ -113,7 +113,8 @@ class TestRunner:
|
|
|
113
113
|
)
|
|
114
114
|
print(f'Teardown Error ({target_desc}): {ex}')
|
|
115
115
|
|
|
116
|
-
def print_test_result(self, test_result: TestResult):
|
|
116
|
+
def print_test_result(self, test_result: TestResult) -> None:
|
|
117
|
+
"""Print the test result using a coloured emoji and timing."""
|
|
117
118
|
if self.__cli.quiet:
|
|
118
119
|
return
|
|
119
120
|
glyph = '🟩' if test_result.is_success else '🟥'
|
|
@@ -204,13 +205,13 @@ class TestRunner:
|
|
|
204
205
|
test_package_path = os.path.join(os.path.abspath(os.curdir), self.__test_package_name).replace('\\', '/')
|
|
205
206
|
for filename in self.__filenames:
|
|
206
207
|
ts = time.time()
|
|
207
|
-
|
|
208
|
-
if
|
|
209
|
-
|
|
210
|
-
module_report_name =
|
|
208
|
+
module_import_name = filename.replace(test_package_path, '').replace('/', '.')
|
|
209
|
+
if module_import_name.endswith('.py'):
|
|
210
|
+
module_import_name = module_import_name[:-3]
|
|
211
|
+
module_report_name = module_import_name.lstrip('.')
|
|
211
212
|
try:
|
|
212
213
|
host_name: str = socket.gethostname()
|
|
213
|
-
test_module = importlib.import_module(
|
|
214
|
+
test_module = importlib.import_module(module_import_name, self.__test_package_name)
|
|
214
215
|
|
|
215
216
|
# execute all facts
|
|
216
217
|
await self.__run_facts(host_name, test_module, filename, module_report_name, results)
|
|
@@ -10,6 +10,23 @@ from ..metadata import CallableMetadata
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class Theory:
|
|
13
|
+
"""
|
|
14
|
+
Wraps a parameterised test (``@theory``) and its collected data points.
|
|
15
|
+
|
|
16
|
+
Example
|
|
17
|
+
-------
|
|
18
|
+
|
|
19
|
+
.. code-block:: python
|
|
20
|
+
|
|
21
|
+
@theory
|
|
22
|
+
def add_correct(x, y): ...
|
|
23
|
+
|
|
24
|
+
@inlinedata((1, 2), (3, 4))
|
|
25
|
+
@theory
|
|
26
|
+
def add_correct(x, y):
|
|
27
|
+
assert x + y == x + y
|
|
28
|
+
|
|
29
|
+
"""
|
|
13
30
|
|
|
14
31
|
__datas: list[tuple]
|
|
15
32
|
__target: Union[FunctionType, MethodType, BuiltinFunctionType, BuiltinMethodType, Callable]
|
|
@@ -52,7 +69,6 @@ class Theory:
|
|
|
52
69
|
args = (class_instance,) + data
|
|
53
70
|
coro = self.__target(*args)
|
|
54
71
|
else:
|
|
55
|
-
self.__test_name = self.__target.__name__
|
|
56
72
|
coro = self.__target(*data)
|
|
57
73
|
if inspect.iscoroutine(coro):
|
|
58
74
|
await coro
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|