punit 1.1.9__tar.gz → 1.2.0__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.1.9/punit.egg-info → punit-1.2.0}/PKG-INFO +31 -5
- {punit-1.1.9 → punit-1.2.0}/README.md +1 -1
- {punit-1.1.9 → punit-1.2.0}/pyproject.toml +15 -4
- {punit-1.1.9 → punit-1.2.0/src/pUnit.egg-info}/PKG-INFO +31 -5
- punit-1.2.0/src/pUnit.egg-info/SOURCES.txt +31 -0
- punit-1.2.0/src/pUnit.egg-info/requires.txt +5 -0
- punit-1.2.0/src/pUnit.egg-info/top_level.txt +1 -0
- {punit-1.1.9 → punit-1.2.0/src}/punit/TestResult.py +42 -36
- punit-1.2.0/src/punit/__init__.py +21 -0
- {punit-1.1.9 → punit-1.2.0/src}/punit/__main__.py +14 -10
- {punit-1.1.9 → punit-1.2.0/src}/punit/assertions/__init__.py +3 -0
- {punit-1.1.9 → punit-1.2.0/src}/punit/assertions/collections.py +24 -23
- {punit-1.1.9 → punit-1.2.0/src}/punit/assertions/exceptions.py +10 -7
- {punit-1.1.9 → punit-1.2.0/src}/punit/assertions/strings.py +6 -7
- {punit-1.1.9 → punit-1.2.0/src}/punit/cli.py +13 -13
- {punit-1.1.9 → punit-1.2.0/src}/punit/discovery/__init__.py +3 -2
- {punit-1.1.9 → punit-1.2.0/src}/punit/facts/Fact.py +9 -9
- {punit-1.1.9 → punit-1.2.0/src}/punit/facts/FactManager.py +7 -8
- {punit-1.1.9 → punit-1.2.0/src}/punit/facts/__init__.py +6 -0
- {punit-1.1.9 → punit-1.2.0/src}/punit/reports/HtmlReportGenerator.py +4 -3
- punit-1.2.0/src/punit/reports/JUnitReportGenerator.py +196 -0
- {punit-1.1.9 → punit-1.2.0/src}/punit/theories/Theory.py +10 -9
- {punit-1.1.9 → punit-1.2.0/src}/punit/theories/TheoryManager.py +7 -8
- {punit-1.1.9 → punit-1.2.0/src}/punit/theories/__init__.py +6 -0
- {punit-1.1.9 → punit-1.2.0/src}/punit/traits/Trait.py +4 -5
- {punit-1.1.9 → punit-1.2.0/src}/punit/traits/__init__.py +5 -0
- punit-1.1.9/punit/__init__.py +0 -9
- punit-1.1.9/punit/reports/JUnitReportGenerator.py +0 -185
- punit-1.1.9/punit.egg-info/SOURCES.txt +0 -30
- punit-1.1.9/punit.egg-info/top_level.txt +0 -4
- {punit-1.1.9 → punit-1.2.0}/.scripts/punit +0 -0
- {punit-1.1.9 → punit-1.2.0}/LICENSE +0 -0
- {punit-1.1.9 → punit-1.2.0}/setup.cfg +0 -0
- {punit-1.1.9/punit.egg-info → punit-1.2.0/src/pUnit.egg-info}/dependency_links.txt +0 -0
- {punit-1.1.9 → punit-1.2.0/src}/punit/discovery/TestModuleDiscovery.py +0 -0
- {punit-1.1.9 → punit-1.2.0/src}/punit/reports/__init__.py +0 -0
- {punit-1.1.9 → punit-1.2.0/src}/punit/runner.py +0 -0
|
@@ -1,21 +1,47 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
|
-
Name:
|
|
3
|
-
Version: 1.
|
|
2
|
+
Name: pUnit
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: A modernized unit-test framework for Python.
|
|
5
5
|
Author-email: Shaun Wilson <mrshaunwilson@msn.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) Shaun Wilson
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
6
28
|
Project-URL: Documentation, https://punit.readthedocs.io/
|
|
7
29
|
Project-URL: Homepage, https://github.com/wilson0x4d/punit
|
|
8
30
|
Project-URL: Repository, https://github.com/wilson0x4d/punit.git
|
|
9
|
-
Keywords: test,unittest,unit-test
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
31
|
+
Keywords: test,unittest,unit-test,xUnit,nUnit,pytest
|
|
11
32
|
Classifier: Operating System :: OS Independent
|
|
12
33
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
34
|
Classifier: Programming Language :: Python :: 3.13
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
36
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
37
|
Classifier: Intended Audience :: Developers
|
|
16
38
|
Requires-Python: >=3.12
|
|
17
39
|
Description-Content-Type: text/markdown
|
|
18
40
|
License-File: LICENSE
|
|
41
|
+
Provides-Extra: dev
|
|
42
|
+
Requires-Dist: build; extra == "dev"
|
|
43
|
+
Requires-Dist: setuptools; extra == "dev"
|
|
44
|
+
Requires-Dist: twine; extra == "dev"
|
|
19
45
|
Dynamic: license-file
|
|
20
46
|
|
|
21
47
|
`pUnit` is a modernized unit-testing framework for Python, inspired by xUnit.
|
|
@@ -120,7 +146,7 @@ You can write tests as functions, class methods, instance methods, or static met
|
|
|
120
146
|
|
|
121
147
|
```python
|
|
122
148
|
|
|
123
|
-
from punit import
|
|
149
|
+
from punit import fact, theory, inlinedata
|
|
124
150
|
|
|
125
151
|
@fact
|
|
126
152
|
async def MyLibrary_WhenInitialized_TouchMustReturnTrue:
|
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
[project]
|
|
2
|
-
name = "
|
|
3
|
-
version = "1.
|
|
2
|
+
name = "pUnit"
|
|
3
|
+
version = "1.2.0"
|
|
4
4
|
description = "A modernized unit-test framework for Python."
|
|
5
|
-
keywords = ["test", "unittest", "unit-test"]
|
|
5
|
+
keywords = ["test", "unittest", "unit-test", "xUnit", "nUnit", "pytest"]
|
|
6
6
|
authors = [
|
|
7
7
|
{ name="Shaun Wilson", email="mrshaunwilson@msn.com" }
|
|
8
8
|
]
|
|
9
9
|
readme = "README.md"
|
|
10
|
+
license = {file = "LICENSE"}
|
|
10
11
|
requires-python = ">=3.12"
|
|
11
12
|
classifiers = [
|
|
12
|
-
"License :: OSI Approved :: MIT License",
|
|
13
13
|
"Operating System :: OS Independent",
|
|
14
14
|
"Programming Language :: Python :: 3.12",
|
|
15
15
|
"Programming Language :: Python :: 3.13",
|
|
16
|
+
"Programming Language :: Python :: 3.14",
|
|
16
17
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
17
18
|
"Intended Audience :: Developers"
|
|
18
19
|
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
dev = [
|
|
25
|
+
"build",
|
|
26
|
+
"setuptools",
|
|
27
|
+
"twine"
|
|
28
|
+
]
|
|
19
29
|
|
|
20
30
|
[project.urls]
|
|
21
31
|
Documentation = "https://punit.readthedocs.io/"
|
|
@@ -27,6 +37,7 @@ requires = ["setuptools"]
|
|
|
27
37
|
build-backend = "setuptools.build_meta"
|
|
28
38
|
|
|
29
39
|
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["src"]
|
|
30
41
|
exclude = [
|
|
31
42
|
"docs",
|
|
32
43
|
"tests",
|
|
@@ -1,21 +1,47 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
|
-
Name:
|
|
3
|
-
Version: 1.
|
|
2
|
+
Name: pUnit
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: A modernized unit-test framework for Python.
|
|
5
5
|
Author-email: Shaun Wilson <mrshaunwilson@msn.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) Shaun Wilson
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
6
28
|
Project-URL: Documentation, https://punit.readthedocs.io/
|
|
7
29
|
Project-URL: Homepage, https://github.com/wilson0x4d/punit
|
|
8
30
|
Project-URL: Repository, https://github.com/wilson0x4d/punit.git
|
|
9
|
-
Keywords: test,unittest,unit-test
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
31
|
+
Keywords: test,unittest,unit-test,xUnit,nUnit,pytest
|
|
11
32
|
Classifier: Operating System :: OS Independent
|
|
12
33
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
34
|
Classifier: Programming Language :: Python :: 3.13
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
36
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
37
|
Classifier: Intended Audience :: Developers
|
|
16
38
|
Requires-Python: >=3.12
|
|
17
39
|
Description-Content-Type: text/markdown
|
|
18
40
|
License-File: LICENSE
|
|
41
|
+
Provides-Extra: dev
|
|
42
|
+
Requires-Dist: build; extra == "dev"
|
|
43
|
+
Requires-Dist: setuptools; extra == "dev"
|
|
44
|
+
Requires-Dist: twine; extra == "dev"
|
|
19
45
|
Dynamic: license-file
|
|
20
46
|
|
|
21
47
|
`pUnit` is a modernized unit-testing framework for Python, inspired by xUnit.
|
|
@@ -120,7 +146,7 @@ You can write tests as functions, class methods, instance methods, or static met
|
|
|
120
146
|
|
|
121
147
|
```python
|
|
122
148
|
|
|
123
|
-
from punit import
|
|
149
|
+
from punit import fact, theory, inlinedata
|
|
124
150
|
|
|
125
151
|
@fact
|
|
126
152
|
async def MyLibrary_WhenInitialized_TouchMustReturnTrue:
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
.scripts/punit
|
|
5
|
+
src/pUnit.egg-info/PKG-INFO
|
|
6
|
+
src/pUnit.egg-info/SOURCES.txt
|
|
7
|
+
src/pUnit.egg-info/dependency_links.txt
|
|
8
|
+
src/pUnit.egg-info/requires.txt
|
|
9
|
+
src/pUnit.egg-info/top_level.txt
|
|
10
|
+
src/punit/TestResult.py
|
|
11
|
+
src/punit/__init__.py
|
|
12
|
+
src/punit/__main__.py
|
|
13
|
+
src/punit/cli.py
|
|
14
|
+
src/punit/runner.py
|
|
15
|
+
src/punit/assertions/__init__.py
|
|
16
|
+
src/punit/assertions/collections.py
|
|
17
|
+
src/punit/assertions/exceptions.py
|
|
18
|
+
src/punit/assertions/strings.py
|
|
19
|
+
src/punit/discovery/TestModuleDiscovery.py
|
|
20
|
+
src/punit/discovery/__init__.py
|
|
21
|
+
src/punit/facts/Fact.py
|
|
22
|
+
src/punit/facts/FactManager.py
|
|
23
|
+
src/punit/facts/__init__.py
|
|
24
|
+
src/punit/reports/HtmlReportGenerator.py
|
|
25
|
+
src/punit/reports/JUnitReportGenerator.py
|
|
26
|
+
src/punit/reports/__init__.py
|
|
27
|
+
src/punit/theories/Theory.py
|
|
28
|
+
src/punit/theories/TheoryManager.py
|
|
29
|
+
src/punit/theories/__init__.py
|
|
30
|
+
src/punit/traits/Trait.py
|
|
31
|
+
src/punit/traits/__init__.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
punit
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
# SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
4
|
import sys
|
|
5
|
-
from typing import TextIO
|
|
5
|
+
from typing import Any, TextIO, Optional, cast
|
|
6
|
+
|
|
6
7
|
|
|
7
8
|
class TextIOCapture:
|
|
8
9
|
|
|
9
10
|
__quiet:bool
|
|
10
|
-
output:str = None
|
|
11
|
+
output:str|None = None
|
|
11
12
|
target:TextIO
|
|
12
13
|
|
|
13
14
|
def __init__(self, target:TextIO, quiet:bool = False) -> None:
|
|
@@ -23,21 +24,22 @@ class TextIOCapture:
|
|
|
23
24
|
if not self.__quiet:
|
|
24
25
|
self.target.write(text)
|
|
25
26
|
|
|
27
|
+
|
|
26
28
|
class TestResult:
|
|
27
29
|
|
|
28
|
-
__className:str
|
|
29
|
-
__exception:Exception
|
|
30
|
-
__fileName:str
|
|
31
|
-
__hostName:str
|
|
32
|
-
__isSuccess:bool
|
|
33
|
-
__moduleName:str
|
|
34
|
-
__packageName:str
|
|
35
|
-
__properties:dict[str,
|
|
36
|
-
__startTime:float
|
|
37
|
-
__stderrCapture:TextIOCapture
|
|
38
|
-
__stdoutCapture:TextIOCapture
|
|
39
|
-
__stopTime:float
|
|
40
|
-
__testName:str
|
|
30
|
+
__className:Optional[str]
|
|
31
|
+
__exception:Exception|None
|
|
32
|
+
__fileName:str|None
|
|
33
|
+
__hostName:str|None
|
|
34
|
+
__isSuccess:bool|None
|
|
35
|
+
__moduleName:str|None
|
|
36
|
+
__packageName:str|None
|
|
37
|
+
__properties:dict[str, Any]
|
|
38
|
+
__startTime:float|None
|
|
39
|
+
__stderrCapture:TextIOCapture|None
|
|
40
|
+
__stdoutCapture:TextIOCapture|None
|
|
41
|
+
__stopTime:float|None
|
|
42
|
+
__testName:str|None
|
|
41
43
|
|
|
42
44
|
def __init__(self):
|
|
43
45
|
self.__className = None
|
|
@@ -47,7 +49,7 @@ class TestResult:
|
|
|
47
49
|
self.__isSuccess = None
|
|
48
50
|
self.__moduleName = None
|
|
49
51
|
self.__packageName = None
|
|
50
|
-
self.__properties =
|
|
52
|
+
self.__properties = dict[str,Any]()
|
|
51
53
|
self.__startTime = None
|
|
52
54
|
self.__stderrCapture = None
|
|
53
55
|
self.__stdoutCapture = None
|
|
@@ -55,15 +57,15 @@ class TestResult:
|
|
|
55
57
|
self.__testName = None
|
|
56
58
|
|
|
57
59
|
@property
|
|
58
|
-
def className(self) -> str:
|
|
60
|
+
def className(self) -> Optional[str]:
|
|
59
61
|
return self.__className
|
|
60
62
|
|
|
61
63
|
@className.setter
|
|
62
|
-
def className(self, value:str) -> None:
|
|
64
|
+
def className(self, value:Optional[str]) -> None:
|
|
63
65
|
self.__className = value
|
|
64
66
|
|
|
65
67
|
@property
|
|
66
|
-
def exception(self) -> Exception:
|
|
68
|
+
def exception(self) -> Exception|None:
|
|
67
69
|
return self.__exception
|
|
68
70
|
|
|
69
71
|
@exception.setter
|
|
@@ -71,7 +73,7 @@ class TestResult:
|
|
|
71
73
|
self.__exception = value
|
|
72
74
|
|
|
73
75
|
@property
|
|
74
|
-
def fileName(self) -> str:
|
|
76
|
+
def fileName(self) -> str|None:
|
|
75
77
|
return self.__fileName
|
|
76
78
|
|
|
77
79
|
@fileName.setter
|
|
@@ -79,7 +81,7 @@ class TestResult:
|
|
|
79
81
|
self.__fileName = value
|
|
80
82
|
|
|
81
83
|
@property
|
|
82
|
-
def hostName(self) -> str:
|
|
84
|
+
def hostName(self) -> str|None:
|
|
83
85
|
return self.__hostName
|
|
84
86
|
|
|
85
87
|
@hostName.setter
|
|
@@ -88,7 +90,7 @@ class TestResult:
|
|
|
88
90
|
|
|
89
91
|
@property
|
|
90
92
|
def isSuccess(self) -> bool:
|
|
91
|
-
return self.__isSuccess
|
|
93
|
+
return False if self.__isSuccess is None else self.__isSuccess
|
|
92
94
|
|
|
93
95
|
@isSuccess.setter
|
|
94
96
|
def isSuccess(self, value:bool) -> None:
|
|
@@ -96,14 +98,14 @@ class TestResult:
|
|
|
96
98
|
|
|
97
99
|
@property
|
|
98
100
|
def moduleName(self) -> str:
|
|
99
|
-
return self.__moduleName
|
|
101
|
+
return cast(str,self.__moduleName)
|
|
100
102
|
|
|
101
103
|
@moduleName.setter
|
|
102
104
|
def moduleName(self, value:str) -> None:
|
|
103
105
|
self.__moduleName = value
|
|
104
106
|
|
|
105
107
|
@property
|
|
106
|
-
def packageName(self) -> str:
|
|
108
|
+
def packageName(self) -> str|None:
|
|
107
109
|
return self.__packageName
|
|
108
110
|
|
|
109
111
|
@packageName.setter
|
|
@@ -111,7 +113,7 @@ class TestResult:
|
|
|
111
113
|
self.__packageName = value
|
|
112
114
|
|
|
113
115
|
@property
|
|
114
|
-
def properties(self) -> dict[str,
|
|
116
|
+
def properties(self) -> dict[str, Any]:
|
|
115
117
|
return self.__properties
|
|
116
118
|
|
|
117
119
|
@properties.setter
|
|
@@ -119,7 +121,7 @@ class TestResult:
|
|
|
119
121
|
self.__properties = value
|
|
120
122
|
|
|
121
123
|
@property
|
|
122
|
-
def startTime(self) -> float:
|
|
124
|
+
def startTime(self) -> float|None:
|
|
123
125
|
return self.__startTime
|
|
124
126
|
|
|
125
127
|
@startTime.setter
|
|
@@ -127,15 +129,15 @@ class TestResult:
|
|
|
127
129
|
self.__startTime = value
|
|
128
130
|
|
|
129
131
|
@property
|
|
130
|
-
def stderr(self) -> str:
|
|
132
|
+
def stderr(self) -> str|None:
|
|
131
133
|
return None if self.__stderrCapture is None else self.__stderrCapture.output
|
|
132
134
|
|
|
133
135
|
@property
|
|
134
|
-
def stdout(self) -> str:
|
|
136
|
+
def stdout(self) -> str|None:
|
|
135
137
|
return None if self.__stdoutCapture is None else self.__stdoutCapture.output
|
|
136
138
|
|
|
137
139
|
@property
|
|
138
|
-
def stopTime(self) -> float:
|
|
140
|
+
def stopTime(self) -> float|None:
|
|
139
141
|
return self.__stopTime
|
|
140
142
|
|
|
141
143
|
@stopTime.setter
|
|
@@ -143,7 +145,7 @@ class TestResult:
|
|
|
143
145
|
self.__stopTime = value
|
|
144
146
|
|
|
145
147
|
@property
|
|
146
|
-
def testName(self) -> str:
|
|
148
|
+
def testName(self) -> str|None:
|
|
147
149
|
return self.__testName
|
|
148
150
|
|
|
149
151
|
@testName.setter
|
|
@@ -151,13 +153,15 @@ class TestResult:
|
|
|
151
153
|
self.__testName = value
|
|
152
154
|
|
|
153
155
|
@property
|
|
154
|
-
def took(self) -> float:
|
|
155
|
-
return self.__stopTime - self.__startTime
|
|
156
|
+
def took(self) -> float|None:
|
|
157
|
+
return None if self.__stopTime is None or self.__startTime is None else self.__stopTime - self.__startTime
|
|
156
158
|
|
|
157
159
|
@property
|
|
158
160
|
def tookPretty(self) -> str:
|
|
159
161
|
took = self.took
|
|
160
|
-
if took
|
|
162
|
+
if took is None:
|
|
163
|
+
return 'N/A'
|
|
164
|
+
elif took >= 1:
|
|
161
165
|
return f'{took:.1f}'.rstrip('0').rstrip('.') + 's'
|
|
162
166
|
elif took >= 0.001:
|
|
163
167
|
return f'{(took*1000):.0f}ms'
|
|
@@ -169,12 +173,14 @@ class TestResult:
|
|
|
169
173
|
return f'{(took*1000):.3f}'.rstrip('0').rstrip('.') + 'ms'
|
|
170
174
|
|
|
171
175
|
|
|
172
|
-
def captureOutput(self, quiet:bool = False) ->
|
|
176
|
+
def captureOutput(self, quiet:bool = False) -> None:
|
|
173
177
|
self.__stdoutCapture = TextIOCapture(sys.stdout, quiet)
|
|
174
178
|
self.__stderrCapture = TextIOCapture(sys.stderr, quiet)
|
|
175
179
|
sys.stdout = self.__stdoutCapture
|
|
176
180
|
sys.stderr = self.__stderrCapture
|
|
177
181
|
|
|
178
182
|
def releaseOutput(self) -> None:
|
|
179
|
-
|
|
180
|
-
|
|
183
|
+
if self.__stdoutCapture is not None and self.__stdoutCapture.target is not None:
|
|
184
|
+
sys.stdout = self.__stdoutCapture.target
|
|
185
|
+
if self.__stderrCapture is not None and self.__stderrCapture.target is not None:
|
|
186
|
+
sys.stderr = self.__stderrCapture.target
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: © Shaun Wilson
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
__version__ = '1.2.0'
|
|
5
|
+
|
|
6
|
+
from .assertions import *
|
|
7
|
+
from .facts import *
|
|
8
|
+
from .theories import *
|
|
9
|
+
from .traits import *
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
'assertions',
|
|
14
|
+
'collections', 'exceptions', 'strings',
|
|
15
|
+
'facts',
|
|
16
|
+
'fact',
|
|
17
|
+
'theories',
|
|
18
|
+
'theory', 'inlinedata',
|
|
19
|
+
'traits',
|
|
20
|
+
'trait'
|
|
21
|
+
]
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
# SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
4
|
import asyncio
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import os
|
|
6
|
+
import time
|
|
7
|
+
from .cli import CommandLineInterface
|
|
8
|
+
from .discovery import TestModuleDiscovery
|
|
7
9
|
from .reports import HtmlReportGenerator, JUnitReportGenerator
|
|
8
|
-
from .runner import
|
|
10
|
+
from .runner import TestRunner
|
|
11
|
+
|
|
9
12
|
|
|
10
13
|
async def async_main():
|
|
11
14
|
ts = time.time()
|
|
@@ -33,18 +36,19 @@ async def async_main():
|
|
|
33
36
|
if not cli.quiet:
|
|
34
37
|
print(f'Total: {len(results)}, Failures: {failureCount}, Took: {totalTime:.3f}s')
|
|
35
38
|
if cli.reportFormat is not None:
|
|
36
|
-
report:str = None
|
|
39
|
+
report:str|None = None
|
|
37
40
|
match cli.reportFormat:
|
|
38
41
|
case 'html':
|
|
39
42
|
report = HtmlReportGenerator().generate(results)
|
|
40
43
|
case 'junit':
|
|
41
44
|
report = JUnitReportGenerator().generate(results)
|
|
42
|
-
if
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
if report is not None:
|
|
46
|
+
if cli.outputFilename is None:
|
|
47
|
+
print(report)
|
|
48
|
+
else:
|
|
49
|
+
with open(cli.outputFilename, 'wb') as file:
|
|
50
|
+
file.write(report.encode())
|
|
51
|
+
print(f'\n("{cli.reportFormat}" report written to: {cli.outputFilename})')
|
|
48
52
|
if failureCount > 0:
|
|
49
53
|
exit(119)
|
|
50
54
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: © Shaun Wilson
|
|
2
2
|
# SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
-
from typing import Any, Callable, Sequence
|
|
4
|
+
from typing import Any, Callable, Sequence, Optional, cast
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
def areSame(a:Sequence[Any]|None, b:Sequence[Any]|None, sort:bool=False, sortFunction:Callable[[Any], Any]=None) -> bool:
|
|
7
|
+
def areSame(a:Sequence[Any]|None, b:Sequence[Any]|None, sort:bool=False, sortFunction:Optional[Callable[[Any], Any]]=None) -> bool:
|
|
8
8
|
"""
|
|
9
9
|
Check if two sequences contain the same elements in the same order.
|
|
10
10
|
|
|
@@ -20,30 +20,31 @@ def areSame(a:Sequence[Any]|None, b:Sequence[Any]|None, sort:bool=False, sortFun
|
|
|
20
20
|
return False
|
|
21
21
|
elif a is not None and b is None:
|
|
22
22
|
return False
|
|
23
|
-
elif
|
|
24
|
-
|
|
23
|
+
elif a is not None and b is not None:
|
|
24
|
+
if len(a) != len(b):
|
|
25
|
+
return False
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
if sort:
|
|
28
|
+
if isinstance(a, dict) or isinstance(b, dict):
|
|
29
|
+
sortFunction = sortFunction if sortFunction is not None else lambda e: e[0]
|
|
30
|
+
a = sorted(cast(dict,a).items(), key=sortFunction)
|
|
31
|
+
b = sorted(cast(dict,b).items(), key=sortFunction)
|
|
32
|
+
else:
|
|
33
|
+
sortFunction = sortFunction if sortFunction is not None else lambda e: e
|
|
34
|
+
a = sorted(a, key=sortFunction)
|
|
35
|
+
b = sorted(b, key=sortFunction)
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if not areSame(pairs[0], pairs[1]):
|
|
39
|
-
return False
|
|
40
|
-
else:
|
|
41
|
-
for pairs in zip(a, b):
|
|
42
|
-
if isinstance(pairs[0], dict) or isinstance(pairs[1], dict):
|
|
37
|
+
if isinstance(a, dict) or isinstance(b, dict):
|
|
38
|
+
for pairs in zip(cast(dict,a).items(), cast(dict,b).items()):
|
|
43
39
|
if not areSame(pairs[0], pairs[1]):
|
|
44
40
|
return False
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
else:
|
|
42
|
+
for pairs in zip(a, b):
|
|
43
|
+
if isinstance(pairs[0], dict) or isinstance(pairs[1], dict):
|
|
44
|
+
if not areSame(pairs[0], pairs[1]):
|
|
45
|
+
return False
|
|
46
|
+
elif pairs[0] != pairs[1]:
|
|
47
|
+
return False
|
|
47
48
|
return True
|
|
48
49
|
|
|
49
50
|
def hasLength(sequence:Sequence[Any]|None, expected:int|None) -> bool:
|
|
@@ -61,7 +62,7 @@ def hasLength(sequence:Sequence[Any]|None, expected:int|None) -> bool:
|
|
|
61
62
|
return False
|
|
62
63
|
elif sequence is not None and expected is None:
|
|
63
64
|
return False
|
|
64
|
-
return len(sequence) == expected
|
|
65
|
+
return sequence is not None and len(sequence) == expected
|
|
65
66
|
|
|
66
67
|
def isNoneOrEmpty(sequence:Sequence[Any]|None) -> bool:
|
|
67
68
|
"""
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: © Shaun Wilson
|
|
2
2
|
# SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
-
from typing import Callable, get_args
|
|
4
|
+
from typing import Any, Callable, Optional, cast, get_args
|
|
5
|
+
|
|
5
6
|
|
|
6
7
|
def isNoneOrWhiteSpace(input:str):
|
|
7
8
|
return input is None or len(input.lstrip()) == 0
|
|
8
9
|
|
|
10
|
+
|
|
9
11
|
def isNoneOrEmpty(input:str):
|
|
10
12
|
return input is None or len(input) == 0
|
|
11
13
|
|
|
14
|
+
|
|
12
15
|
class raises[TError:Exception]:
|
|
13
16
|
|
|
14
17
|
__action:Callable
|
|
15
18
|
__exact:bool
|
|
16
|
-
__expect:TError
|
|
19
|
+
__expect:Optional[TError|type]
|
|
17
20
|
|
|
18
|
-
def __init__(self, action:Callable, *, exact:bool = False, expect:TError = None) -> None:
|
|
21
|
+
def __init__(self, action:Callable, *, exact:bool = False, expect:Optional[TError|type] = None) -> None:
|
|
19
22
|
self.__action = action
|
|
20
23
|
self.__exact = exact
|
|
21
24
|
self.__expect = expect
|
|
@@ -25,10 +28,10 @@ class raises[TError:Exception]:
|
|
|
25
28
|
self.__action()
|
|
26
29
|
except BaseException as ex:
|
|
27
30
|
expected = self.__expect
|
|
28
|
-
if expected is None:
|
|
29
|
-
expected = get_args(self
|
|
31
|
+
if expected is None and hasattr(self, '__orig_class__') is not None:
|
|
32
|
+
expected = get_args(getattr(self, '__orig_class__'))[0]
|
|
30
33
|
if self.__exact:
|
|
31
34
|
return type(ex) is expected
|
|
32
|
-
|
|
33
|
-
return issubclass(type(ex), expected)
|
|
35
|
+
elif expected is not None:
|
|
36
|
+
return issubclass(type(ex), cast(Any,expected))
|
|
34
37
|
return False
|
|
@@ -14,17 +14,16 @@ def areSame(a:str|None, b:str|None) -> bool:
|
|
|
14
14
|
Returns:
|
|
15
15
|
True if the strings contain the same characters in the same order, False otherwise
|
|
16
16
|
"""
|
|
17
|
-
if a is b:
|
|
18
|
-
return True
|
|
19
|
-
elif a is None and b is not None:
|
|
17
|
+
if a is None and b is not None:
|
|
20
18
|
return False
|
|
21
19
|
elif a is not None and b is None:
|
|
22
20
|
return False
|
|
23
|
-
elif
|
|
24
|
-
|
|
25
|
-
for i in range(len(a)):
|
|
26
|
-
if a[i] != b[i]:
|
|
21
|
+
elif a is not None and b is not None:
|
|
22
|
+
if len(a) != len(b):
|
|
27
23
|
return False
|
|
24
|
+
for i in range(len(a)):
|
|
25
|
+
if a[i] != b[i]:
|
|
26
|
+
return False
|
|
28
27
|
return True
|
|
29
28
|
|
|
30
29
|
def isNoneOrEmpty(string:str|None) -> bool:
|