punit 1.12.2__tar.gz → 1.12.4__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.12.2/src/pUnit.egg-info → punit-1.12.4}/PKG-INFO +30 -4
- {punit-1.12.2 → punit-1.12.4}/README.md +28 -2
- {punit-1.12.2 → punit-1.12.4}/pyproject.toml +2 -2
- {punit-1.12.2 → punit-1.12.4/src/pUnit.egg-info}/PKG-INFO +30 -4
- {punit-1.12.2 → punit-1.12.4}/src/pUnit.egg-info/SOURCES.txt +1 -2
- {punit-1.12.2 → punit-1.12.4}/src/punit/__init__.py +4 -3
- {punit-1.12.2 → punit-1.12.4}/src/punit/cli.py +1 -1
- punit-1.12.2/src/punit/parallel.py → punit-1.12.4/src/punit/parallelism.py +230 -9
- {punit-1.12.2 → punit-1.12.4}/src/punit/runner.py +163 -6
- punit-1.12.2/src/punit/sequential.py +0 -63
- {punit-1.12.2 → punit-1.12.4}/LICENSE +0 -0
- {punit-1.12.2 → punit-1.12.4}/setup.cfg +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/pUnit.egg-info/dependency_links.txt +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/pUnit.egg-info/entry_points.txt +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/pUnit.egg-info/requires.txt +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/pUnit.egg-info/top_level.txt +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/SKILL.md +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/TestResult.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/TextIOCapture.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/__main__.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/assertions/__init__.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/assertions/collections.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/assertions/exceptions.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/assertions/numeric.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/assertions/strings.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/conditions/__init__.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/conditions/skip.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/discovery/TestModuleDiscovery.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/discovery/__init__.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/facts/Fact.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/facts/FactManager.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/facts/__init__.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/filters/Filter.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/filters/FilterManager.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/filters/__init__.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/metadata/CallableMetadata.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/metadata/__init__.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/mocks/__init__.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/mocks/matcher.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/mocks/mock.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/mocks/patch.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/py.typed +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/reports/HtmlReportGenerator.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/reports/JUnitReportGenerator.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/reports/JsonReportGenerator.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/reports/__init__.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/results/__init__.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/results/fails.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/setups/Setup.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/setups/SetupManager.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/setups/__init__.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/teardowns/Teardown.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/teardowns/TeardownManager.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/teardowns/__init__.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/theories/Theory.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/theories/TheoryManager.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/theories/__init__.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/traits/Trait.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/traits/TraitManager.py +0 -0
- {punit-1.12.2 → punit-1.12.4}/src/punit/traits/__init__.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pUnit
|
|
3
|
-
Version: 1.12.
|
|
3
|
+
Version: 1.12.4
|
|
4
4
|
Summary: A modernized unit-test framework for Python.
|
|
5
5
|
Author-email: Shaun Wilson <mrshaunwilson@msn.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Documentation, https://punit.readthedocs.io/
|
|
8
8
|
Project-URL: Homepage, https://github.com/wilson0x4d/punit
|
|
9
9
|
Project-URL: Repository, https://github.com/wilson0x4d/punit.git
|
|
10
|
-
Keywords: test,unittest,unit-test,xUnit,nUnit,pytest
|
|
10
|
+
Keywords: test,unittest,unit-test,xUnit,nUnit,pytest,mock,patch,parallelism,vscode,vscodium
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
12
12
|
Classifier: Programming Language :: Python
|
|
13
13
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
@@ -25,6 +25,26 @@ Dynamic: license-file
|
|
|
25
25
|
|
|
26
26
|
This README is only a high-level introduction to **pUnit**. For more detailed documentation, please view the official docs at [https://pUnit.readthedocs.io](https://pUnit.readthedocs.io).
|
|
27
27
|
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- **IDE Support**; Run/Debug tests and review results with full error detail from within vscode using the [x4d.punit-testadapter](https://marketplace.visualstudio.com/items?itemName=x4d.punit-testadapter) extension ([also available for VSCodium via Open VSX](https://open-vsx.org/extension/x4d/punit-testadapter))
|
|
31
|
+
- **Fact/Theory Based TEsting**; Separate `@fact` and `@theory` decorators distinguish invariant tests from variant (parameterized) tests, allowing for more concise test permutation syntaxes.
|
|
32
|
+
- **Parallel Test Execution**; Optionally run tests concurrently with multiple worker threads, each with its own async event loop. Enable via `--parallel` CLI flag or `@parallel` decorator. Mix parallel and `@sequential`-marked tests in the same file.
|
|
33
|
+
- **Dual-Scope Setup/Teardown**; Module-scoped and class-scoped setup/teardown as bare decorated functions, no fixture plumbing or injection required. Configure with `@setup` and `@teardown`.
|
|
34
|
+
- **Per-Test Output Capture**; Captures each test's stdout/stderr output in isolation, included in the test results and all report formats.
|
|
35
|
+
- **Traits**; Categorical name/value test metadata queryable at discovery time via CLI flags. Unlike traditional marks, traits are filterable during discovery, not at decoration, and can also be used to filter in the IDE.
|
|
36
|
+
- **Expected Failures with Inversion**; Invert test results for known bugs with human-readable reason tracking. Mark with `@fails(reason)`, useful for TDD workflows.
|
|
37
|
+
- **Mock Objects**; Single-class mocking with fluent API (`.returns()`, `.side_effect()`), origin conformance, delegate/spy support, nested child mocks, and iterator protocol. Located in `punit.mocks`.
|
|
38
|
+
- **Module Patching**; Decorator and context-manager patching for arbitrary Python paths with automatic restoration, with support for complex dotted attribute paths.
|
|
39
|
+
- **Argument Matchers**; Lightweight and extensible matchers (`is_any`, `contains`, `is_gt`, `is_lt`, `in_range`, `neg`) for flexible mock call assertions.
|
|
40
|
+
- **Conditionals**; Skip tests using boolean, callback, or unconditional logic via `@skip()`. Evaluates at execution time, allowing for complex state mutation/evaluation in test suites.
|
|
41
|
+
- **Multi-Format Reports**; Built-in HTML, jUnit XML, and JSON reports which include captured output, precise timing, and full error detail. Enable with `--report` and combine with `--output` to write to a file, great for CI/CD integrations.
|
|
42
|
+
- **Multi-Level Test Filtering**; Unified test pipeline that combines filename globbing, case-insensitive regex on test names, trait queries, and `--filter @FILEPATH` filters (loaded from ``FILEPATH``), `--filter @stdin` allows piping lists of filters in via stdin.
|
|
43
|
+
- **Direct File Execution**; Run individual `.py` files without a `tests/` package or `__init__.py`. Pass file paths directly to the CLI.
|
|
44
|
+
- **Assertion Helpers for Exceptions**; Type-safe exception assertion via generic syntax `raises[MyException](action)`. Supports strict (`exact=True`) and subclass matching.
|
|
45
|
+
- **Assertion Helpers for Collections**; Assert sequences, lists, sets, dicts, and tuples using `are_same`, `has_length` and `is_none_or_empty` for comparing elements, length bounds, and emptiness, or ordering.
|
|
46
|
+
- **Assertion Helpers for Numericals**; Directional comparators using `> approx(threshold)` with tolerance below and `< approx(threshold)` with tolerance above, also supports fluent syntaxes for more complex numerical requirements such as invariant boundary control, mixtures, etc.
|
|
47
|
+
|
|
28
48
|
## Command-Line Usage
|
|
29
49
|
|
|
30
50
|
Running pUnit with no arguments will perform test auto-discovery and execution:
|
|
@@ -99,7 +119,7 @@ Options:
|
|
|
99
119
|
Default: '*.py'
|
|
100
120
|
-e, --exclude PATTERN
|
|
101
121
|
Exclude test files matching PATTERN, overriding --include
|
|
102
|
-
Default: '__*__' (
|
|
122
|
+
Default: '__*__' (dunder files)
|
|
103
123
|
-f, --filter PATTERN|@FILEPATH
|
|
104
124
|
Only execute tests matching PATTERN
|
|
105
125
|
Default: '*'
|
|
@@ -109,7 +129,7 @@ Options:
|
|
|
109
129
|
Execute tests with the specified trait, negated by prefixing with '!'.
|
|
110
130
|
If VALUE is specified, matches tests with the trait having specified value.
|
|
111
131
|
If VALUE is not specified, matches any test with the trait having any value.
|
|
112
|
-
Default: No filtering based on traits.
|
|
132
|
+
Default: No filtering based on traits.
|
|
113
133
|
-w, --working-directory PATH
|
|
114
134
|
Working directory (defaults to start directory)
|
|
115
135
|
-n, --no-default-patterns
|
|
@@ -127,6 +147,12 @@ Options:
|
|
|
127
147
|
If `--report` is used, instead of writing to stdout
|
|
128
148
|
write to FILENAME. In this case `--report` does not
|
|
129
149
|
suppress any program output.
|
|
150
|
+
FILE
|
|
151
|
+
One or more .py files to run directly.
|
|
152
|
+
When any FILE is specified, only those files are tested;
|
|
153
|
+
-p/--test-package, --include, and --exclude patterns on
|
|
154
|
+
discovered files are ignored (test filtering via -f/-t
|
|
155
|
+
still applies).
|
|
130
156
|
```
|
|
131
157
|
|
|
132
158
|
## Writing Tests
|
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
This README is only a high-level introduction to **pUnit**. For more detailed documentation, please view the official docs at [https://pUnit.readthedocs.io](https://pUnit.readthedocs.io).
|
|
4
4
|
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **IDE Support**; Run/Debug tests and review results with full error detail from within vscode using the [x4d.punit-testadapter](https://marketplace.visualstudio.com/items?itemName=x4d.punit-testadapter) extension ([also available for VSCodium via Open VSX](https://open-vsx.org/extension/x4d/punit-testadapter))
|
|
8
|
+
- **Fact/Theory Based TEsting**; Separate `@fact` and `@theory` decorators distinguish invariant tests from variant (parameterized) tests, allowing for more concise test permutation syntaxes.
|
|
9
|
+
- **Parallel Test Execution**; Optionally run tests concurrently with multiple worker threads, each with its own async event loop. Enable via `--parallel` CLI flag or `@parallel` decorator. Mix parallel and `@sequential`-marked tests in the same file.
|
|
10
|
+
- **Dual-Scope Setup/Teardown**; Module-scoped and class-scoped setup/teardown as bare decorated functions, no fixture plumbing or injection required. Configure with `@setup` and `@teardown`.
|
|
11
|
+
- **Per-Test Output Capture**; Captures each test's stdout/stderr output in isolation, included in the test results and all report formats.
|
|
12
|
+
- **Traits**; Categorical name/value test metadata queryable at discovery time via CLI flags. Unlike traditional marks, traits are filterable during discovery, not at decoration, and can also be used to filter in the IDE.
|
|
13
|
+
- **Expected Failures with Inversion**; Invert test results for known bugs with human-readable reason tracking. Mark with `@fails(reason)`, useful for TDD workflows.
|
|
14
|
+
- **Mock Objects**; Single-class mocking with fluent API (`.returns()`, `.side_effect()`), origin conformance, delegate/spy support, nested child mocks, and iterator protocol. Located in `punit.mocks`.
|
|
15
|
+
- **Module Patching**; Decorator and context-manager patching for arbitrary Python paths with automatic restoration, with support for complex dotted attribute paths.
|
|
16
|
+
- **Argument Matchers**; Lightweight and extensible matchers (`is_any`, `contains`, `is_gt`, `is_lt`, `in_range`, `neg`) for flexible mock call assertions.
|
|
17
|
+
- **Conditionals**; Skip tests using boolean, callback, or unconditional logic via `@skip()`. Evaluates at execution time, allowing for complex state mutation/evaluation in test suites.
|
|
18
|
+
- **Multi-Format Reports**; Built-in HTML, jUnit XML, and JSON reports which include captured output, precise timing, and full error detail. Enable with `--report` and combine with `--output` to write to a file, great for CI/CD integrations.
|
|
19
|
+
- **Multi-Level Test Filtering**; Unified test pipeline that combines filename globbing, case-insensitive regex on test names, trait queries, and `--filter @FILEPATH` filters (loaded from ``FILEPATH``), `--filter @stdin` allows piping lists of filters in via stdin.
|
|
20
|
+
- **Direct File Execution**; Run individual `.py` files without a `tests/` package or `__init__.py`. Pass file paths directly to the CLI.
|
|
21
|
+
- **Assertion Helpers for Exceptions**; Type-safe exception assertion via generic syntax `raises[MyException](action)`. Supports strict (`exact=True`) and subclass matching.
|
|
22
|
+
- **Assertion Helpers for Collections**; Assert sequences, lists, sets, dicts, and tuples using `are_same`, `has_length` and `is_none_or_empty` for comparing elements, length bounds, and emptiness, or ordering.
|
|
23
|
+
- **Assertion Helpers for Numericals**; Directional comparators using `> approx(threshold)` with tolerance below and `< approx(threshold)` with tolerance above, also supports fluent syntaxes for more complex numerical requirements such as invariant boundary control, mixtures, etc.
|
|
24
|
+
|
|
5
25
|
## Command-Line Usage
|
|
6
26
|
|
|
7
27
|
Running pUnit with no arguments will perform test auto-discovery and execution:
|
|
@@ -76,7 +96,7 @@ Options:
|
|
|
76
96
|
Default: '*.py'
|
|
77
97
|
-e, --exclude PATTERN
|
|
78
98
|
Exclude test files matching PATTERN, overriding --include
|
|
79
|
-
Default: '__*__' (
|
|
99
|
+
Default: '__*__' (dunder files)
|
|
80
100
|
-f, --filter PATTERN|@FILEPATH
|
|
81
101
|
Only execute tests matching PATTERN
|
|
82
102
|
Default: '*'
|
|
@@ -86,7 +106,7 @@ Options:
|
|
|
86
106
|
Execute tests with the specified trait, negated by prefixing with '!'.
|
|
87
107
|
If VALUE is specified, matches tests with the trait having specified value.
|
|
88
108
|
If VALUE is not specified, matches any test with the trait having any value.
|
|
89
|
-
Default: No filtering based on traits.
|
|
109
|
+
Default: No filtering based on traits.
|
|
90
110
|
-w, --working-directory PATH
|
|
91
111
|
Working directory (defaults to start directory)
|
|
92
112
|
-n, --no-default-patterns
|
|
@@ -104,6 +124,12 @@ Options:
|
|
|
104
124
|
If `--report` is used, instead of writing to stdout
|
|
105
125
|
write to FILENAME. In this case `--report` does not
|
|
106
126
|
suppress any program output.
|
|
127
|
+
FILE
|
|
128
|
+
One or more .py files to run directly.
|
|
129
|
+
When any FILE is specified, only those files are tested;
|
|
130
|
+
-p/--test-package, --include, and --exclude patterns on
|
|
131
|
+
discovered files are ignored (test filtering via -f/-t
|
|
132
|
+
still applies).
|
|
107
133
|
```
|
|
108
134
|
|
|
109
135
|
## Writing Tests
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "pUnit"
|
|
3
|
-
version = "1.12.
|
|
3
|
+
version = "1.12.4"
|
|
4
4
|
description = "A modernized unit-test framework for Python."
|
|
5
|
-
keywords = ["test", "unittest", "unit-test", "xUnit", "nUnit", "pytest"]
|
|
5
|
+
keywords = ["test", "unittest", "unit-test", "xUnit", "nUnit", "pytest", "mock", "patch", "parallelism", "vscode", "vscodium"]
|
|
6
6
|
authors = [
|
|
7
7
|
{ name="Shaun Wilson", email="mrshaunwilson@msn.com" }
|
|
8
8
|
]
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pUnit
|
|
3
|
-
Version: 1.12.
|
|
3
|
+
Version: 1.12.4
|
|
4
4
|
Summary: A modernized unit-test framework for Python.
|
|
5
5
|
Author-email: Shaun Wilson <mrshaunwilson@msn.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Documentation, https://punit.readthedocs.io/
|
|
8
8
|
Project-URL: Homepage, https://github.com/wilson0x4d/punit
|
|
9
9
|
Project-URL: Repository, https://github.com/wilson0x4d/punit.git
|
|
10
|
-
Keywords: test,unittest,unit-test,xUnit,nUnit,pytest
|
|
10
|
+
Keywords: test,unittest,unit-test,xUnit,nUnit,pytest,mock,patch,parallelism,vscode,vscodium
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
12
12
|
Classifier: Programming Language :: Python
|
|
13
13
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
@@ -25,6 +25,26 @@ Dynamic: license-file
|
|
|
25
25
|
|
|
26
26
|
This README is only a high-level introduction to **pUnit**. For more detailed documentation, please view the official docs at [https://pUnit.readthedocs.io](https://pUnit.readthedocs.io).
|
|
27
27
|
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- **IDE Support**; Run/Debug tests and review results with full error detail from within vscode using the [x4d.punit-testadapter](https://marketplace.visualstudio.com/items?itemName=x4d.punit-testadapter) extension ([also available for VSCodium via Open VSX](https://open-vsx.org/extension/x4d/punit-testadapter))
|
|
31
|
+
- **Fact/Theory Based TEsting**; Separate `@fact` and `@theory` decorators distinguish invariant tests from variant (parameterized) tests, allowing for more concise test permutation syntaxes.
|
|
32
|
+
- **Parallel Test Execution**; Optionally run tests concurrently with multiple worker threads, each with its own async event loop. Enable via `--parallel` CLI flag or `@parallel` decorator. Mix parallel and `@sequential`-marked tests in the same file.
|
|
33
|
+
- **Dual-Scope Setup/Teardown**; Module-scoped and class-scoped setup/teardown as bare decorated functions, no fixture plumbing or injection required. Configure with `@setup` and `@teardown`.
|
|
34
|
+
- **Per-Test Output Capture**; Captures each test's stdout/stderr output in isolation, included in the test results and all report formats.
|
|
35
|
+
- **Traits**; Categorical name/value test metadata queryable at discovery time via CLI flags. Unlike traditional marks, traits are filterable during discovery, not at decoration, and can also be used to filter in the IDE.
|
|
36
|
+
- **Expected Failures with Inversion**; Invert test results for known bugs with human-readable reason tracking. Mark with `@fails(reason)`, useful for TDD workflows.
|
|
37
|
+
- **Mock Objects**; Single-class mocking with fluent API (`.returns()`, `.side_effect()`), origin conformance, delegate/spy support, nested child mocks, and iterator protocol. Located in `punit.mocks`.
|
|
38
|
+
- **Module Patching**; Decorator and context-manager patching for arbitrary Python paths with automatic restoration, with support for complex dotted attribute paths.
|
|
39
|
+
- **Argument Matchers**; Lightweight and extensible matchers (`is_any`, `contains`, `is_gt`, `is_lt`, `in_range`, `neg`) for flexible mock call assertions.
|
|
40
|
+
- **Conditionals**; Skip tests using boolean, callback, or unconditional logic via `@skip()`. Evaluates at execution time, allowing for complex state mutation/evaluation in test suites.
|
|
41
|
+
- **Multi-Format Reports**; Built-in HTML, jUnit XML, and JSON reports which include captured output, precise timing, and full error detail. Enable with `--report` and combine with `--output` to write to a file, great for CI/CD integrations.
|
|
42
|
+
- **Multi-Level Test Filtering**; Unified test pipeline that combines filename globbing, case-insensitive regex on test names, trait queries, and `--filter @FILEPATH` filters (loaded from ``FILEPATH``), `--filter @stdin` allows piping lists of filters in via stdin.
|
|
43
|
+
- **Direct File Execution**; Run individual `.py` files without a `tests/` package or `__init__.py`. Pass file paths directly to the CLI.
|
|
44
|
+
- **Assertion Helpers for Exceptions**; Type-safe exception assertion via generic syntax `raises[MyException](action)`. Supports strict (`exact=True`) and subclass matching.
|
|
45
|
+
- **Assertion Helpers for Collections**; Assert sequences, lists, sets, dicts, and tuples using `are_same`, `has_length` and `is_none_or_empty` for comparing elements, length bounds, and emptiness, or ordering.
|
|
46
|
+
- **Assertion Helpers for Numericals**; Directional comparators using `> approx(threshold)` with tolerance below and `< approx(threshold)` with tolerance above, also supports fluent syntaxes for more complex numerical requirements such as invariant boundary control, mixtures, etc.
|
|
47
|
+
|
|
28
48
|
## Command-Line Usage
|
|
29
49
|
|
|
30
50
|
Running pUnit with no arguments will perform test auto-discovery and execution:
|
|
@@ -99,7 +119,7 @@ Options:
|
|
|
99
119
|
Default: '*.py'
|
|
100
120
|
-e, --exclude PATTERN
|
|
101
121
|
Exclude test files matching PATTERN, overriding --include
|
|
102
|
-
Default: '__*__' (
|
|
122
|
+
Default: '__*__' (dunder files)
|
|
103
123
|
-f, --filter PATTERN|@FILEPATH
|
|
104
124
|
Only execute tests matching PATTERN
|
|
105
125
|
Default: '*'
|
|
@@ -109,7 +129,7 @@ Options:
|
|
|
109
129
|
Execute tests with the specified trait, negated by prefixing with '!'.
|
|
110
130
|
If VALUE is specified, matches tests with the trait having specified value.
|
|
111
131
|
If VALUE is not specified, matches any test with the trait having any value.
|
|
112
|
-
Default: No filtering based on traits.
|
|
132
|
+
Default: No filtering based on traits.
|
|
113
133
|
-w, --working-directory PATH
|
|
114
134
|
Working directory (defaults to start directory)
|
|
115
135
|
-n, --no-default-patterns
|
|
@@ -127,6 +147,12 @@ Options:
|
|
|
127
147
|
If `--report` is used, instead of writing to stdout
|
|
128
148
|
write to FILENAME. In this case `--report` does not
|
|
129
149
|
suppress any program output.
|
|
150
|
+
FILE
|
|
151
|
+
One or more .py files to run directly.
|
|
152
|
+
When any FILE is specified, only those files are tested;
|
|
153
|
+
-p/--test-package, --include, and --exclude patterns on
|
|
154
|
+
discovered files are ignored (test filtering via -f/-t
|
|
155
|
+
still applies).
|
|
130
156
|
```
|
|
131
157
|
|
|
132
158
|
## Writing Tests
|
|
@@ -13,10 +13,9 @@ src/punit/TextIOCapture.py
|
|
|
13
13
|
src/punit/__init__.py
|
|
14
14
|
src/punit/__main__.py
|
|
15
15
|
src/punit/cli.py
|
|
16
|
-
src/punit/
|
|
16
|
+
src/punit/parallelism.py
|
|
17
17
|
src/punit/py.typed
|
|
18
18
|
src/punit/runner.py
|
|
19
|
-
src/punit/sequential.py
|
|
20
19
|
src/punit/assertions/__init__.py
|
|
21
20
|
src/punit/assertions/collections.py
|
|
22
21
|
src/punit/assertions/exceptions.py
|
|
@@ -14,12 +14,12 @@ from .conditions import skip
|
|
|
14
14
|
from .teardowns import Teardown, teardown
|
|
15
15
|
from .theories import theory, inlinedata
|
|
16
16
|
from .traits import trait
|
|
17
|
-
from .
|
|
17
|
+
from .parallelism import parallel, sequential
|
|
18
18
|
|
|
19
19
|
from . import mocks
|
|
20
20
|
|
|
21
|
-
__version__ = '1.12.
|
|
22
|
-
__commit__ = '
|
|
21
|
+
__version__ = '1.12.4'
|
|
22
|
+
__commit__ = '43fecfd'
|
|
23
23
|
__all__ = [
|
|
24
24
|
'__version__', '__commit__',
|
|
25
25
|
'assertions',
|
|
@@ -36,4 +36,5 @@ __all__ = [
|
|
|
36
36
|
'fails',
|
|
37
37
|
'skip',
|
|
38
38
|
'sequential',
|
|
39
|
+
'parallel',
|
|
39
40
|
]
|
|
@@ -2,14 +2,90 @@
|
|
|
2
2
|
# SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
4
|
"""
|
|
5
|
-
|
|
5
|
+
Parallelism for pUnit.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
pUnit provides three mechanisms for controlling test execution parallelism:
|
|
8
|
+
the **``--parallel [THREADS]``** CLI flag, the **``@parallel``** decorator,
|
|
9
|
+
and the **``@sequential``** decorator.
|
|
10
|
+
|
|
11
|
+
CLI flag
|
|
12
|
+
--------
|
|
13
|
+
The ``--parallel [THREADS]`` option enables multi-threaded parallel execution.
|
|
14
|
+
Each worker thread runs its own asyncio event loop for isolation.
|
|
15
|
+
|
|
16
|
+
* ``--parallel`` (bare flag, no number) runs tests using
|
|
17
|
+
``cpu_count // 2`` workers.
|
|
18
|
+
* ``--parallel N`` runs tests using *N* workers.
|
|
19
|
+
* No flag means tests run sequentially, one after another.
|
|
20
|
+
|
|
21
|
+
When parallel mode is active the runner processes each test file in four
|
|
22
|
+
batches: parallel facts, parallel theories, sequential facts, then
|
|
23
|
+
sequential theories.
|
|
24
|
+
|
|
25
|
+
@parallel decorator
|
|
26
|
+
-------------------
|
|
27
|
+
Marking a fact or theory with ``@parallel`` triggers **auto-enable** mode.
|
|
28
|
+
When pUnit scans the test package and finds at least one ``@parallel``
|
|
29
|
+
decorated test (and no ``--parallel`` flag was given on the CLI):
|
|
30
|
+
|
|
31
|
+
* Parallel execution is enabled automatically with
|
|
32
|
+
``cpu_count // 2`` workers.
|
|
33
|
+
* **Only** ``@parallel``-marked tests run in the concurrent batch.
|
|
34
|
+
* All other tests are treated as sequential (as if they carried ``@sequential``).
|
|
35
|
+
|
|
36
|
+
Applying ``@parallel`` to a **class** marks every method of that class.
|
|
37
|
+
|
|
38
|
+
``@parallel`` is a no-op when ``--parallel`` is specified on the CLI; the
|
|
39
|
+
CLI flag takes full priority.
|
|
40
|
+
|
|
41
|
+
@sequential decorator
|
|
42
|
+
---------------------
|
|
43
|
+
Marking a fact or theory with ``@sequential`` forces it to run as a
|
|
44
|
+
sequential test **after** all parralel tests for that file have
|
|
45
|
+
completed. This lets you co-run certain tests one-at-a-time inside
|
|
46
|
+
an otherwise parallel test suite (for example, tests that share mutable
|
|
47
|
+
state).
|
|
48
|
+
|
|
49
|
+
When no ``@parallel`` decorator is present and ``--parallel`` is not used
|
|
50
|
+
on the CLI, all tests run sequentially (the default).
|
|
51
|
+
|
|
52
|
+
Examples
|
|
53
|
+
--------
|
|
54
|
+
|
|
55
|
+
Enable parallel mode for the entire test package via CLI::
|
|
56
|
+
|
|
57
|
+
punit tests/ --parallel 4
|
|
58
|
+
|
|
59
|
+
Run only selected tests in parallel -- no CLI flag needed::
|
|
60
|
+
|
|
61
|
+
from punit import fact, parallel
|
|
62
|
+
|
|
63
|
+
@fact
|
|
64
|
+
@parallel
|
|
65
|
+
def test_network_io():
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
@fact
|
|
69
|
+
def test_database_setup():
|
|
70
|
+
# Runs sequentially alongside the parallel tests
|
|
71
|
+
...
|
|
72
|
+
|
|
73
|
+
Mix parallel and sequential tests inside a parallel suite::
|
|
74
|
+
|
|
75
|
+
from punit import fact, parallel, sequential
|
|
76
|
+
|
|
77
|
+
@parallel
|
|
78
|
+
class SlowTests:
|
|
79
|
+
@fact
|
|
80
|
+
def test_heavy_computation(self):
|
|
81
|
+
...
|
|
82
|
+
|
|
83
|
+
@fact
|
|
84
|
+
@sequential
|
|
85
|
+
def test_shared_file_handle(self):
|
|
86
|
+
# Runs one-at-a-time after all other SlowTests finish
|
|
87
|
+
...
|
|
9
88
|
|
|
10
|
-
Each test is wrapped in a coroutine that creates a ``TestResult``, runs
|
|
11
|
-
setup -> test -> teardown, and yields the populated result. The main thread
|
|
12
|
-
dispatches these coroutines to worker threads and collects the results.
|
|
13
89
|
"""
|
|
14
90
|
|
|
15
91
|
from __future__ import annotations
|
|
@@ -20,7 +96,7 @@ import queue
|
|
|
20
96
|
import threading
|
|
21
97
|
import time
|
|
22
98
|
from types import ModuleType
|
|
23
|
-
from typing import Any
|
|
99
|
+
from typing import Any, Callable
|
|
24
100
|
|
|
25
101
|
from .TestResult import TestResult
|
|
26
102
|
|
|
@@ -239,7 +315,7 @@ async def _execute_fact(
|
|
|
239
315
|
|
|
240
316
|
async def _execute_theory(
|
|
241
317
|
theory: Any,
|
|
242
|
-
data: tuple,
|
|
318
|
+
data: tuple[Any, ...],
|
|
243
319
|
module: ModuleType,
|
|
244
320
|
module_report_name: str,
|
|
245
321
|
filename: str,
|
|
@@ -330,6 +406,151 @@ async def _execute_theory(
|
|
|
330
406
|
return result
|
|
331
407
|
|
|
332
408
|
|
|
409
|
+
def sequential(target: Callable[..., Any] | type) -> Callable[..., Any]:
|
|
410
|
+
"""Mark a test function, method, or class for **sequential** execution.
|
|
411
|
+
|
|
412
|
+
When pUnit is started with ``--parallel THREADS`` (where *THREADS* > 1),
|
|
413
|
+
tests decorated without ``@sequential`` run in parallel up to *N* at
|
|
414
|
+
any given point. ``@sequential`` tests are **not** added to the
|
|
415
|
+
concurrent dispatch queue -- after all peers at the same scope finish
|
|
416
|
+
concurrently, the sequential tests run one after another in their
|
|
417
|
+
definition order.
|
|
418
|
+
|
|
419
|
+
When applied to a **class**, all methods of the class are automatically
|
|
420
|
+
marked as sequential.
|
|
421
|
+
|
|
422
|
+
The decorator returns the original ``target`` unchanged; it installs the
|
|
423
|
+
``__punit_sequential`` marker attribute on the unwrapped function (or the
|
|
424
|
+
class object itself for class targets).
|
|
425
|
+
|
|
426
|
+
Parameters
|
|
427
|
+
----------
|
|
428
|
+
target : Callable | type
|
|
429
|
+
The test function, method, or class to mark.
|
|
430
|
+
|
|
431
|
+
Returns
|
|
432
|
+
-------
|
|
433
|
+
Callable
|
|
434
|
+
The original, undecorated target.
|
|
435
|
+
|
|
436
|
+
Example
|
|
437
|
+
-------
|
|
438
|
+
|
|
439
|
+
.. code-block:: python
|
|
440
|
+
|
|
441
|
+
from punit import fact, sequential
|
|
442
|
+
|
|
443
|
+
@fact
|
|
444
|
+
@sequential
|
|
445
|
+
def my_test():
|
|
446
|
+
assert True
|
|
447
|
+
|
|
448
|
+
class MyTestCase:
|
|
449
|
+
@fact
|
|
450
|
+
@sequential
|
|
451
|
+
def test_one(self):
|
|
452
|
+
assert True
|
|
453
|
+
|
|
454
|
+
# Apply @sequential to an entire class -- all methods run sequentially
|
|
455
|
+
@sequential
|
|
456
|
+
class SequentialTestCase:
|
|
457
|
+
@fact
|
|
458
|
+
def test_one(self):
|
|
459
|
+
assert True
|
|
460
|
+
|
|
461
|
+
@fact
|
|
462
|
+
def test_two(self):
|
|
463
|
+
assert True
|
|
464
|
+
|
|
465
|
+
"""
|
|
466
|
+
import inspect
|
|
467
|
+
|
|
468
|
+
if isinstance(target, type):
|
|
469
|
+
setattr(target, "__punit_sequential", True)
|
|
470
|
+
for name, method in inspect.getmembers(target, predicate=inspect.isfunction):
|
|
471
|
+
setattr(method, "__punit_sequential", True)
|
|
472
|
+
return target
|
|
473
|
+
|
|
474
|
+
unwrapped = inspect.unwrap(target)
|
|
475
|
+
setattr(unwrapped, "__punit_sequential", True)
|
|
476
|
+
return target
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def parallel(target: Callable[..., Any]) -> Callable[..., Any]:
|
|
480
|
+
"""Mark a test function, method, or class for **parallel** execution.
|
|
481
|
+
|
|
482
|
+
When pUnit detects any ``@parallel``-decorated tests in a test package at
|
|
483
|
+
runtime, it automatically triggers (multi-threaded) parallel execution for
|
|
484
|
+
the entire test run -- no ``--parallel`` CLI flag is required.
|
|
485
|
+
|
|
486
|
+
When ``@parallel`` triggers auto-enable:
|
|
487
|
+
|
|
488
|
+
* Only tests marked with ``@parallel`` run in the parallel batch
|
|
489
|
+
* Tests not marked with ``@parallel`` are treated as sequential
|
|
490
|
+
(as if decorated with ``@sequential``)
|
|
491
|
+
* The thread pool is initialized with ``cpu_count // 2`` workers
|
|
492
|
+
* This auto-enable is superseded by ``--parallel`` on the CLI
|
|
493
|
+
|
|
494
|
+
When ``--parallel`` is specified on the CLI, this decorator has no effect;
|
|
495
|
+
the existing ``--parallel`` behavior takes priority and all
|
|
496
|
+
non-``@sequential`` tests run in parallel as before.
|
|
497
|
+
|
|
498
|
+
When applied to a class, all methods of the class are automatically marked
|
|
499
|
+
as parallel.
|
|
500
|
+
|
|
501
|
+
The decorator returns the original ``target`` unchanged; it installs the
|
|
502
|
+
``__punit_parallel`` marker attribute on the unwrapped function (or the
|
|
503
|
+
class object itself for class targets).
|
|
504
|
+
|
|
505
|
+
Parameters
|
|
506
|
+
----------
|
|
507
|
+
target : Callable | type
|
|
508
|
+
The test function, method, or class to mark.
|
|
509
|
+
|
|
510
|
+
Returns
|
|
511
|
+
-------
|
|
512
|
+
Callable
|
|
513
|
+
The original, undecorated target.
|
|
514
|
+
|
|
515
|
+
Example
|
|
516
|
+
-------
|
|
517
|
+
|
|
518
|
+
.. code-block:: python
|
|
519
|
+
|
|
520
|
+
from punit import fact, parallel
|
|
521
|
+
|
|
522
|
+
@fact
|
|
523
|
+
@parallel
|
|
524
|
+
def my_parallel_test():
|
|
525
|
+
assert True
|
|
526
|
+
|
|
527
|
+
class ParallelTestCase:
|
|
528
|
+
@fact
|
|
529
|
+
@parallel
|
|
530
|
+
def test_one(self):
|
|
531
|
+
assert True
|
|
532
|
+
|
|
533
|
+
@fact
|
|
534
|
+
def test_sequential(self):
|
|
535
|
+
# Runs sequentially (not in parallel batch)
|
|
536
|
+
assert True
|
|
537
|
+
|
|
538
|
+
"""
|
|
539
|
+
import inspect
|
|
540
|
+
|
|
541
|
+
if isinstance(target, type):
|
|
542
|
+
setattr(target, "__punit_parallel", True)
|
|
543
|
+
for name, method in inspect.getmembers(target, predicate=inspect.isfunction):
|
|
544
|
+
setattr(method, "__punit_parallel", True)
|
|
545
|
+
return target
|
|
546
|
+
|
|
547
|
+
unwrapped = inspect.unwrap(target)
|
|
548
|
+
setattr(unwrapped, "__punit_parallel", True)
|
|
549
|
+
return target
|
|
550
|
+
|
|
551
|
+
|
|
333
552
|
__all__ = [
|
|
334
|
-
'ThreadPool'
|
|
553
|
+
'ThreadPool',
|
|
554
|
+
'parallel',
|
|
555
|
+
'sequential'
|
|
335
556
|
]
|
|
@@ -13,7 +13,7 @@ from typing import Any, Callable, Optional
|
|
|
13
13
|
|
|
14
14
|
from .TextIOCapture import setup_global_text_io, _PERSISTENT_STDOUT, _PERSISTENT_STDERR
|
|
15
15
|
from .cli import CommandLineInterface
|
|
16
|
-
from .
|
|
16
|
+
from .parallelism import ThreadPool, _execute_fact, _execute_theory
|
|
17
17
|
from .facts.FactManager import FactManager
|
|
18
18
|
from .setups.SetupManager import SetupManager
|
|
19
19
|
from .teardowns.TeardownManager import TeardownManager
|
|
@@ -43,19 +43,25 @@ def _is_sequential(target: Any) -> bool:
|
|
|
43
43
|
return getattr(unwrapped, '__punit_sequential', None) is True
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
def _is_parallel(target: Any) -> bool:
|
|
47
|
+
"""Return True if *target* is marked with ``@parallel``."""
|
|
48
|
+
unwrapped = inspect.unwrap(target)
|
|
49
|
+
return getattr(unwrapped, '__punit_parallel', None) is True
|
|
50
|
+
|
|
51
|
+
|
|
46
52
|
def _get_parallelism(cli: 'CommandLineInterface') -> int:
|
|
47
53
|
"""Resolve the parallelism level from CLI.
|
|
48
54
|
|
|
49
|
-
Returns ``-1`` when
|
|
55
|
+
Returns ``-1`` when parallelism is disabled (use sequential flow).
|
|
50
56
|
Returns the worker count when enabled; ``0`` maps to ``cpu_count // 2``.
|
|
51
57
|
"""
|
|
52
|
-
|
|
53
|
-
if
|
|
58
|
+
parallelism = cli.parallelism
|
|
59
|
+
if parallelism is None:
|
|
54
60
|
return -1
|
|
55
|
-
if
|
|
61
|
+
if parallelism <= 0:
|
|
56
62
|
cores = os.cpu_count() or 1
|
|
57
63
|
return max(1, cores // 2)
|
|
58
|
-
return
|
|
64
|
+
return parallelism
|
|
59
65
|
|
|
60
66
|
|
|
61
67
|
class TestRunner:
|
|
@@ -167,6 +173,22 @@ class TestRunner:
|
|
|
167
173
|
if self.__cli.verbose and (not test_result.is_success) and test_result.exception is not None:
|
|
168
174
|
print(f'Test File:\n {test_result.file_name}\nError:\n {test_result.exception}\n Traceback:\n{"".join(traceback.format_tb(test_result.exception.__traceback__))}')
|
|
169
175
|
|
|
176
|
+
def __detect_parallel_tests(self) -> bool:
|
|
177
|
+
"""Return True if any registered fact or theory is marked with ``@parallel``."""
|
|
178
|
+
fm = FactManager.instance()
|
|
179
|
+
modules = getattr(fm, '_FactManager__modules', {})
|
|
180
|
+
for facts in modules.values():
|
|
181
|
+
for fact in facts:
|
|
182
|
+
if _is_parallel(fact.target):
|
|
183
|
+
return True
|
|
184
|
+
tm = TheoryManager.instance()
|
|
185
|
+
modules = getattr(tm, '_TheoryManager__modules', {})
|
|
186
|
+
for theories in modules.values():
|
|
187
|
+
for theory in theories:
|
|
188
|
+
if _is_parallel(theory.target):
|
|
189
|
+
return True
|
|
190
|
+
return False
|
|
191
|
+
|
|
170
192
|
async def __run_facts(self, host_name: str, test_module: ModuleType, filename: str, module_report_name: str, results: list[TestResult]) -> None:
|
|
171
193
|
"""Run all facts for a test module and return their results."""
|
|
172
194
|
facts = FactManager.instance().get(test_module.__name__)
|
|
@@ -288,6 +310,14 @@ class TestRunner:
|
|
|
288
310
|
if parallelism > 0:
|
|
289
311
|
return await self._run_parallel(results, test_package_path)
|
|
290
312
|
|
|
313
|
+
# Auto-enable parallel mode when @parallel-decorated tests are detected
|
|
314
|
+
has_parallel_tests = self.__detect_parallel_tests()
|
|
315
|
+
if has_parallel_tests:
|
|
316
|
+
cores = os.cpu_count() or 1
|
|
317
|
+
parallelism = max(1, cores // 2)
|
|
318
|
+
await self._run_parallel_parallel_only(results, test_package_path, parallelism)
|
|
319
|
+
return results
|
|
320
|
+
|
|
291
321
|
for filename in self.__filenames:
|
|
292
322
|
ts = time.time()
|
|
293
323
|
if not self.__test_package_name:
|
|
@@ -333,6 +363,82 @@ class TestRunner:
|
|
|
333
363
|
# Parallel execution
|
|
334
364
|
# ------------------------------------------------------------------
|
|
335
365
|
|
|
366
|
+
async def _run_parallel_parallel_only(
|
|
367
|
+
self,
|
|
368
|
+
results: list[TestResult],
|
|
369
|
+
test_package_path: str,
|
|
370
|
+
parallelism: int,
|
|
371
|
+
) -> None:
|
|
372
|
+
"""Execute ``@parallel``-decorated tests in a worker pool, rest sequentially.
|
|
373
|
+
|
|
374
|
+
This is used when ``@parallel`` decorators are detected at runtime and
|
|
375
|
+
no ``--parallel`` CLI flag was provided: only tests marked with
|
|
376
|
+
``@parallel`` go into the parallel batch, everything else runs one-by-one.
|
|
377
|
+
"""
|
|
378
|
+
try:
|
|
379
|
+
pool = ThreadPool(parallelism)
|
|
380
|
+
with pool:
|
|
381
|
+
module_report_name = ''
|
|
382
|
+
for filename in self.__filenames:
|
|
383
|
+
try:
|
|
384
|
+
if not self.__test_package_name:
|
|
385
|
+
module_import_name = filename
|
|
386
|
+
else:
|
|
387
|
+
module_import_name = filename.replace(test_package_path, '').replace('/', '.')
|
|
388
|
+
if module_import_name.endswith('.py'):
|
|
389
|
+
module_import_name = module_import_name[:-3]
|
|
390
|
+
module_report_name = module_import_name.lstrip('.')
|
|
391
|
+
if not self.__test_package_name:
|
|
392
|
+
test_module = importlib.import_module(module_import_name)
|
|
393
|
+
else:
|
|
394
|
+
test_module = importlib.import_module(module_import_name, self.__test_package_name)
|
|
395
|
+
host_name = socket.gethostname()
|
|
396
|
+
|
|
397
|
+
await self._run_parallel_facts_parallel_only(host_name, test_module, filename, module_report_name, results, pool)
|
|
398
|
+
if self.__cli.failfast and any(e.is_success is False for e in results):
|
|
399
|
+
break
|
|
400
|
+
|
|
401
|
+
await self._run_parallel_theories_parallel_only(host_name, test_module, filename, module_report_name, results, pool)
|
|
402
|
+
if self.__cli.failfast and any(e.is_success is False for e in results):
|
|
403
|
+
break
|
|
404
|
+
|
|
405
|
+
facts = FactManager.instance().get(test_module.__name__)
|
|
406
|
+
for fact in facts:
|
|
407
|
+
if _is_parallel(fact.target):
|
|
408
|
+
continue
|
|
409
|
+
test_result = await _execute_fact(fact, test_module, module_report_name, filename, host_name, self.__test_package_name)
|
|
410
|
+
results.append(test_result)
|
|
411
|
+
self.print_test_result(test_result)
|
|
412
|
+
if self.__cli.failfast and not test_result.is_success:
|
|
413
|
+
return
|
|
414
|
+
|
|
415
|
+
theories = TheoryManager.instance().get(test_module.__name__)
|
|
416
|
+
for theory in theories:
|
|
417
|
+
if _is_parallel(theory.target):
|
|
418
|
+
continue
|
|
419
|
+
for data in theory.datas:
|
|
420
|
+
test_result = await _execute_theory(theory, data, test_module, module_report_name, filename, host_name, self.__test_package_name)
|
|
421
|
+
results.append(test_result)
|
|
422
|
+
self.print_test_result(test_result)
|
|
423
|
+
if self.__cli.failfast and not test_result.is_success:
|
|
424
|
+
return
|
|
425
|
+
|
|
426
|
+
except Exception as ex:
|
|
427
|
+
result = TestResult()
|
|
428
|
+
result.class_name = '*'
|
|
429
|
+
result.module_name = module_report_name
|
|
430
|
+
result.test_name = '*'
|
|
431
|
+
result.start_time = time.time()
|
|
432
|
+
result.stop_time = time.time()
|
|
433
|
+
result.is_success = False
|
|
434
|
+
result.exception = ex
|
|
435
|
+
results.append(result)
|
|
436
|
+
self.print_test_result(result)
|
|
437
|
+
if self.__cli.failfast:
|
|
438
|
+
break
|
|
439
|
+
except Exception:
|
|
440
|
+
pass
|
|
441
|
+
|
|
336
442
|
async def _run_parallel(self, results: list[TestResult], test_package_path: str) -> list[TestResult]:
|
|
337
443
|
"""Execute all tests with a worker pool (each worker has its own event loop)."""
|
|
338
444
|
parallelism = _get_parallelism(self.__cli)
|
|
@@ -474,3 +580,54 @@ class TestRunner:
|
|
|
474
580
|
self.print_test_result(test_result)
|
|
475
581
|
if self.__cli.failfast and not test_result.is_success:
|
|
476
582
|
return
|
|
583
|
+
|
|
584
|
+
async def _run_parallel_facts_parallel_only(
|
|
585
|
+
self,
|
|
586
|
+
host_name: str,
|
|
587
|
+
test_module: ModuleType,
|
|
588
|
+
filename: str,
|
|
589
|
+
module_report_name: str,
|
|
590
|
+
results: list[TestResult],
|
|
591
|
+
pool: ThreadPool
|
|
592
|
+
) -> None:
|
|
593
|
+
"""Run @parallel facts in parallel (auto-enable mode)."""
|
|
594
|
+
facts = FactManager.instance().get(test_module.__name__)
|
|
595
|
+
coros: list[Any] = []
|
|
596
|
+
for fact in facts:
|
|
597
|
+
if not _is_parallel(fact.target):
|
|
598
|
+
continue
|
|
599
|
+
coro = _execute_fact(fact, test_module, module_report_name, filename, host_name, self.__test_package_name)
|
|
600
|
+
coros.append(coro)
|
|
601
|
+
if coros:
|
|
602
|
+
test_results = await asyncio.gather(*coros)
|
|
603
|
+
for test_result in test_results:
|
|
604
|
+
results.append(test_result)
|
|
605
|
+
self.print_test_result(test_result)
|
|
606
|
+
if self.__cli.failfast and not test_result.is_success:
|
|
607
|
+
return
|
|
608
|
+
|
|
609
|
+
async def _run_parallel_theories_parallel_only(
|
|
610
|
+
self,
|
|
611
|
+
host_name: str,
|
|
612
|
+
test_module: ModuleType,
|
|
613
|
+
filename: str,
|
|
614
|
+
module_report_name: str,
|
|
615
|
+
results: list[TestResult],
|
|
616
|
+
pool: ThreadPool
|
|
617
|
+
) -> None:
|
|
618
|
+
"""Run @parallel theory data-points in parallel (auto-enable mode)."""
|
|
619
|
+
theories = TheoryManager.instance().get(test_module.__name__)
|
|
620
|
+
coros: list[Any] = []
|
|
621
|
+
for theory in theories:
|
|
622
|
+
if not _is_parallel(theory.target):
|
|
623
|
+
continue
|
|
624
|
+
for data in theory.datas:
|
|
625
|
+
coro = _execute_theory(theory, data, test_module, module_report_name, filename, host_name, self.__test_package_name)
|
|
626
|
+
coros.append(coro)
|
|
627
|
+
if coros:
|
|
628
|
+
test_results = await asyncio.gather(*coros)
|
|
629
|
+
for test_result in test_results:
|
|
630
|
+
results.append(test_result)
|
|
631
|
+
self.print_test_result(test_result)
|
|
632
|
+
if self.__cli.failfast and not test_result.is_success:
|
|
633
|
+
return
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: © 2024 Shaun Wilson
|
|
2
|
-
# SPDX-License-Identifier: MIT
|
|
3
|
-
|
|
4
|
-
"""
|
|
5
|
-
``@sequential`` decorator for pUnit.
|
|
6
|
-
|
|
7
|
-
When running tests with ``--parallel``, any test decorated with
|
|
8
|
-
``@sequential`` is deferred to a sequential pass that runs after all
|
|
9
|
-
parallel peers at the same scope have finished.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
from typing import Any, Callable
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def sequential(target: Callable[..., Any]) -> Callable[..., Any]:
|
|
18
|
-
"""Mark a test function or method for **sequential** (sequential) execution.
|
|
19
|
-
|
|
20
|
-
When pUnit is started with ``--parralel THREADS`` (where *THREADS* > 1),
|
|
21
|
-
tests decorated without ``@sequential`` run in parallel up to *N* at
|
|
22
|
-
any given point. ``@sequential`` tests are **not** added to the
|
|
23
|
-
concurrent dispatch queue -- after all peers at the same scope finish
|
|
24
|
-
concurrently, the sequential tests run one after another in their
|
|
25
|
-
definition order.
|
|
26
|
-
|
|
27
|
-
The decorator returns the original ``target`` unchanged; it installs the
|
|
28
|
-
``__punit_sequential`` marker attribute on the unwrapped function.
|
|
29
|
-
|
|
30
|
-
Parameters
|
|
31
|
-
----------
|
|
32
|
-
target : Callable
|
|
33
|
-
The test function or method to mark.
|
|
34
|
-
|
|
35
|
-
Returns
|
|
36
|
-
-------
|
|
37
|
-
Callable
|
|
38
|
-
The original, undecorated target.
|
|
39
|
-
|
|
40
|
-
Example
|
|
41
|
-
-------
|
|
42
|
-
|
|
43
|
-
.. code-block:: python
|
|
44
|
-
|
|
45
|
-
from punit import fact, sequential
|
|
46
|
-
|
|
47
|
-
@fact
|
|
48
|
-
@sequential
|
|
49
|
-
def my_test():
|
|
50
|
-
assert True
|
|
51
|
-
|
|
52
|
-
class MyTestCase:
|
|
53
|
-
@fact
|
|
54
|
-
@sequential
|
|
55
|
-
def test_one(self):
|
|
56
|
-
assert True
|
|
57
|
-
|
|
58
|
-
"""
|
|
59
|
-
import inspect
|
|
60
|
-
|
|
61
|
-
unwrapped = inspect.unwrap(target)
|
|
62
|
-
setattr(unwrapped, "__punit_sequential", True)
|
|
63
|
-
return target
|
|
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
|
|
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
|