pUnit 1.2.0__py3-none-any.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.
punit/TestResult.py ADDED
@@ -0,0 +1,186 @@
1
+ # SPDX-FileCopyrightText: © Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ import sys
5
+ from typing import Any, TextIO, Optional, cast
6
+
7
+
8
+ class TextIOCapture:
9
+
10
+ __quiet:bool
11
+ output:str|None = None
12
+ target:TextIO
13
+
14
+ def __init__(self, target:TextIO, quiet:bool = False) -> None:
15
+ self.__quiet = quiet
16
+ self.output = None
17
+ self.target = target
18
+
19
+ def write(self, text:str):
20
+ if self.output is None:
21
+ self.output = text
22
+ else:
23
+ self.output += text
24
+ if not self.__quiet:
25
+ self.target.write(text)
26
+
27
+
28
+ class TestResult:
29
+
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
43
+
44
+ def __init__(self):
45
+ self.__className = None
46
+ self.__exception = None
47
+ self.__fileName = None
48
+ self.__hostName = None
49
+ self.__isSuccess = None
50
+ self.__moduleName = None
51
+ self.__packageName = None
52
+ self.__properties = dict[str,Any]()
53
+ self.__startTime = None
54
+ self.__stderrCapture = None
55
+ self.__stdoutCapture = None
56
+ self.__stopTime = None
57
+ self.__testName = None
58
+
59
+ @property
60
+ def className(self) -> Optional[str]:
61
+ return self.__className
62
+
63
+ @className.setter
64
+ def className(self, value:Optional[str]) -> None:
65
+ self.__className = value
66
+
67
+ @property
68
+ def exception(self) -> Exception|None:
69
+ return self.__exception
70
+
71
+ @exception.setter
72
+ def exception(self, value:Exception) -> None:
73
+ self.__exception = value
74
+
75
+ @property
76
+ def fileName(self) -> str|None:
77
+ return self.__fileName
78
+
79
+ @fileName.setter
80
+ def fileName(self, value:str) -> None:
81
+ self.__fileName = value
82
+
83
+ @property
84
+ def hostName(self) -> str|None:
85
+ return self.__hostName
86
+
87
+ @hostName.setter
88
+ def hostName(self, value:str) -> None:
89
+ self.__hostName = value
90
+
91
+ @property
92
+ def isSuccess(self) -> bool:
93
+ return False if self.__isSuccess is None else self.__isSuccess
94
+
95
+ @isSuccess.setter
96
+ def isSuccess(self, value:bool) -> None:
97
+ self.__isSuccess = value
98
+
99
+ @property
100
+ def moduleName(self) -> str:
101
+ return cast(str,self.__moduleName)
102
+
103
+ @moduleName.setter
104
+ def moduleName(self, value:str) -> None:
105
+ self.__moduleName = value
106
+
107
+ @property
108
+ def packageName(self) -> str|None:
109
+ return self.__packageName
110
+
111
+ @packageName.setter
112
+ def packageName(self, value:str) -> None:
113
+ self.__packageName = value
114
+
115
+ @property
116
+ def properties(self) -> dict[str, Any]:
117
+ return self.__properties
118
+
119
+ @properties.setter
120
+ def properties(self, value:dict[str, str]) -> None:
121
+ self.__properties = value
122
+
123
+ @property
124
+ def startTime(self) -> float|None:
125
+ return self.__startTime
126
+
127
+ @startTime.setter
128
+ def startTime(self, value:float) -> None:
129
+ self.__startTime = value
130
+
131
+ @property
132
+ def stderr(self) -> str|None:
133
+ return None if self.__stderrCapture is None else self.__stderrCapture.output
134
+
135
+ @property
136
+ def stdout(self) -> str|None:
137
+ return None if self.__stdoutCapture is None else self.__stdoutCapture.output
138
+
139
+ @property
140
+ def stopTime(self) -> float|None:
141
+ return self.__stopTime
142
+
143
+ @stopTime.setter
144
+ def stopTime(self, value:float) -> None:
145
+ self.__stopTime = value
146
+
147
+ @property
148
+ def testName(self) -> str|None:
149
+ return self.__testName
150
+
151
+ @testName.setter
152
+ def testName(self, value:str) -> None:
153
+ self.__testName = value
154
+
155
+ @property
156
+ def took(self) -> float|None:
157
+ return None if self.__stopTime is None or self.__startTime is None else self.__stopTime - self.__startTime
158
+
159
+ @property
160
+ def tookPretty(self) -> str:
161
+ took = self.took
162
+ if took is None:
163
+ return 'N/A'
164
+ elif took >= 1:
165
+ return f'{took:.1f}'.rstrip('0').rstrip('.') + 's'
166
+ elif took >= 0.001:
167
+ return f'{(took*1000):.0f}ms'
168
+ elif took >= 0.000001:
169
+ return f'{(took*1000):.3f}'.rstrip('0').rstrip('.') + 'ms'
170
+ elif took >= 0.000000001:
171
+ return f'{(took*1000000):.3f}'.rstrip('0').rstrip('.') + 'μs'
172
+ else:
173
+ return f'{(took*1000):.3f}'.rstrip('0').rstrip('.') + 'ms'
174
+
175
+
176
+ def captureOutput(self, quiet:bool = False) -> None:
177
+ self.__stdoutCapture = TextIOCapture(sys.stdout, quiet)
178
+ self.__stderrCapture = TextIOCapture(sys.stderr, quiet)
179
+ sys.stdout = self.__stdoutCapture
180
+ sys.stderr = self.__stderrCapture
181
+
182
+ def releaseOutput(self) -> None:
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
punit/__init__.py ADDED
@@ -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
+ ]
punit/__main__.py ADDED
@@ -0,0 +1,59 @@
1
+ # SPDX-FileCopyrightText: © Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ import asyncio
5
+ import os
6
+ import time
7
+ from .cli import CommandLineInterface
8
+ from .discovery import TestModuleDiscovery
9
+ from .reports import HtmlReportGenerator, JUnitReportGenerator
10
+ from .runner import TestRunner
11
+
12
+
13
+ async def async_main():
14
+ ts = time.time()
15
+ cli = CommandLineInterface.parse()
16
+ if cli.help:
17
+ cli.printHelp()
18
+ elif cli.verbose and not cli.quiet:
19
+ cli.printSummary()
20
+ elif not cli.quiet:
21
+ cli.printVersion()
22
+ os.chdir(cli.workdir)
23
+ testModuleDiscovery = TestModuleDiscovery(
24
+ os.path.join(cli.workdir, cli.testPackageName),
25
+ cli.includePatterns,
26
+ cli.excludePatterns,
27
+ cli)
28
+ testModuleDiscovery.discover()
29
+ testRunner = TestRunner(cli.testPackageName, testModuleDiscovery.filenames, cli)
30
+ results = await testRunner.run()
31
+ totalTime = time.time() - ts
32
+ failureCount = 0
33
+ for result in results:
34
+ if not result.isSuccess:
35
+ failureCount += 1
36
+ if not cli.quiet:
37
+ print(f'Total: {len(results)}, Failures: {failureCount}, Took: {totalTime:.3f}s')
38
+ if cli.reportFormat is not None:
39
+ report:str|None = None
40
+ match cli.reportFormat:
41
+ case 'html':
42
+ report = HtmlReportGenerator().generate(results)
43
+ case 'junit':
44
+ report = JUnitReportGenerator().generate(results)
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})')
52
+ if failureCount > 0:
53
+ exit(119)
54
+
55
+ def main():
56
+ asyncio.run(async_main())
57
+
58
+ if (__name__ == '__main__'):
59
+ main()
@@ -0,0 +1,11 @@
1
+ # SPDX-FileCopyrightText: © Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ from . import collections, exceptions, strings
5
+
6
+
7
+ __all__ = [
8
+ 'collections',
9
+ 'exceptions',
10
+ 'strings'
11
+ ]
@@ -0,0 +1,76 @@
1
+ # SPDX-FileCopyrightText: © Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ from typing import Any, Callable, Sequence, Optional, cast
5
+
6
+
7
+ def areSame(a:Sequence[Any]|None, b:Sequence[Any]|None, sort:bool=False, sortFunction:Optional[Callable[[Any], Any]]=None) -> bool:
8
+ """
9
+ Check if two sequences contain the same elements in the same order.
10
+
11
+ :param Sequence[Any]|None a: The sequence to check
12
+ :param Sequence[Any]|None b: The sequence to compare against
13
+ :param Optional[bool] sort: Sort sequences before performing comparisons.
14
+ :param Optional[Callable[[Any], Any]] sortFunction: Custom function to use when sorting.
15
+ :returns bool: True if the sequences contain the same elements in the same order, False otherwise.
16
+ """
17
+ if a is b:
18
+ return True
19
+ elif a is None and b is not None:
20
+ return False
21
+ elif a is not None and b is None:
22
+ return False
23
+ elif a is not None and b is not None:
24
+ if len(a) != len(b):
25
+ return False
26
+
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)
36
+
37
+ if isinstance(a, dict) or isinstance(b, dict):
38
+ for pairs in zip(cast(dict,a).items(), cast(dict,b).items()):
39
+ if not areSame(pairs[0], pairs[1]):
40
+ return False
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
48
+ return True
49
+
50
+ def hasLength(sequence:Sequence[Any]|None, expected:int|None) -> bool:
51
+ """
52
+ Check if a sequence has the expected number of elements.
53
+
54
+ :param Sequence[Any]|None sequence: The sequence to check
55
+ :param int|None expected: The expected number of elements
56
+
57
+ :returns bool: True if the sequence has exactly the expected number of elements, False otherwise
58
+ """
59
+ if sequence is None and (expected is None or expected == 0):
60
+ return True
61
+ elif sequence is None and (expected is not None and expected != 0):
62
+ return False
63
+ elif sequence is not None and expected is None:
64
+ return False
65
+ return sequence is not None and len(sequence) == expected
66
+
67
+ def isNoneOrEmpty(sequence:Sequence[Any]|None) -> bool:
68
+ """
69
+ Check if a sequence is None or empty.
70
+
71
+ :param Sequence[Any]|None sequence: The sequence to check
72
+ :returns bool: True if the sequence is None or empty, False otherwise
73
+ """
74
+ if sequence is None:
75
+ return True
76
+ return len(sequence) == 0
@@ -0,0 +1,37 @@
1
+ # SPDX-FileCopyrightText: © Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ from typing import Any, Callable, Optional, cast, get_args
5
+
6
+
7
+ def isNoneOrWhiteSpace(input:str):
8
+ return input is None or len(input.lstrip()) == 0
9
+
10
+
11
+ def isNoneOrEmpty(input:str):
12
+ return input is None or len(input) == 0
13
+
14
+
15
+ class raises[TError:Exception]:
16
+
17
+ __action:Callable
18
+ __exact:bool
19
+ __expect:Optional[TError|type]
20
+
21
+ def __init__(self, action:Callable, *, exact:bool = False, expect:Optional[TError|type] = None) -> None:
22
+ self.__action = action
23
+ self.__exact = exact
24
+ self.__expect = expect
25
+
26
+ def __bool__(self) -> bool:
27
+ try:
28
+ self.__action()
29
+ except BaseException as ex:
30
+ expected = self.__expect
31
+ if expected is None and hasattr(self, '__orig_class__') is not None:
32
+ expected = get_args(getattr(self, '__orig_class__'))[0]
33
+ if self.__exact:
34
+ return type(ex) is expected
35
+ elif expected is not None:
36
+ return issubclass(type(ex), cast(Any,expected))
37
+ return False
@@ -0,0 +1,55 @@
1
+ # SPDX-FileCopyrightText: © Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ from typing import Any
5
+
6
+ def areSame(a:str|None, b:str|None) -> bool:
7
+ """
8
+ Check if two strings contain the same characters in the same order.
9
+
10
+ Args:
11
+ a: The first string
12
+ b: The second string
13
+
14
+ Returns:
15
+ True if the strings contain the same characters in the same order, False otherwise
16
+ """
17
+ if a is None and b is not None:
18
+ return False
19
+ elif a is not None and b is None:
20
+ return False
21
+ elif a is not None and b is not None:
22
+ if len(a) != len(b):
23
+ return False
24
+ for i in range(len(a)):
25
+ if a[i] != b[i]:
26
+ return False
27
+ return True
28
+
29
+ def isNoneOrEmpty(string:str|None) -> bool:
30
+ """
31
+ Check if a string is None or empty.
32
+
33
+ Args:
34
+ string: The string to check
35
+
36
+ Returns:
37
+ True if the string is None or empty, False otherwise
38
+ """
39
+ if string is None:
40
+ return True
41
+ return len(string) == 0
42
+
43
+ def isNoneOrWhitespace(string:str|None) -> bool:
44
+ """
45
+ Check if a string is None or whitespace.
46
+
47
+ Args:
48
+ string: The string to check
49
+
50
+ Returns:
51
+ True if the string is None or whitespace, False otherwise
52
+ """
53
+ if string is None:
54
+ return True
55
+ return len(string.strip()) == 0