punit 1.4.9__tar.gz → 1.5.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.
Files changed (48) hide show
  1. {punit-1.4.9/src/pUnit.egg-info → punit-1.5.3}/PKG-INFO +2 -2
  2. {punit-1.4.9 → punit-1.5.3}/README.md +1 -1
  3. {punit-1.4.9 → punit-1.5.3}/pyproject.toml +1 -1
  4. {punit-1.4.9 → punit-1.5.3/src/pUnit.egg-info}/PKG-INFO +2 -2
  5. {punit-1.4.9 → punit-1.5.3}/src/pUnit.egg-info/SOURCES.txt +1 -0
  6. punit-1.5.3/src/punit/TestResult.py +186 -0
  7. punit-1.5.3/src/punit/__init__.py +26 -0
  8. {punit-1.4.9 → punit-1.5.3}/src/punit/__main__.py +3 -3
  9. {punit-1.4.9 → punit-1.5.3}/src/punit/assertions/__init__.py +2 -1
  10. {punit-1.4.9 → punit-1.5.3}/src/punit/assertions/collections.py +44 -21
  11. {punit-1.4.9 → punit-1.5.3}/src/punit/assertions/exceptions.py +7 -7
  12. punit-1.5.3/src/punit/assertions/numeric.py +651 -0
  13. punit-1.5.3/src/punit/assertions/strings.py +98 -0
  14. {punit-1.4.9 → punit-1.5.3}/src/punit/cli.py +43 -45
  15. {punit-1.4.9 → punit-1.5.3}/src/punit/discovery/TestModuleDiscovery.py +13 -14
  16. {punit-1.4.9 → punit-1.5.3}/src/punit/facts/Fact.py +8 -9
  17. {punit-1.4.9 → punit-1.5.3}/src/punit/facts/FactManager.py +17 -17
  18. {punit-1.4.9 → punit-1.5.3}/src/punit/filters/Filter.py +7 -7
  19. {punit-1.4.9 → punit-1.5.3}/src/punit/filters/FilterManager.py +7 -7
  20. punit-1.5.3/src/punit/metadata/CallableMetadata.py +38 -0
  21. {punit-1.4.9 → punit-1.5.3}/src/punit/reports/HtmlReportGenerator.py +26 -26
  22. {punit-1.4.9 → punit-1.5.3}/src/punit/reports/JUnitReportGenerator.py +49 -48
  23. punit-1.5.3/src/punit/reports/JsonReportGenerator.py +40 -0
  24. punit-1.5.3/src/punit/runner.py +124 -0
  25. {punit-1.4.9 → punit-1.5.3}/src/punit/theories/Theory.py +10 -10
  26. {punit-1.4.9 → punit-1.5.3}/src/punit/theories/TheoryManager.py +20 -20
  27. {punit-1.4.9 → punit-1.5.3}/src/punit/traits/Trait.py +7 -7
  28. {punit-1.4.9 → punit-1.5.3}/src/punit/traits/TraitManager.py +9 -9
  29. punit-1.4.9/src/punit/TestResult.py +0 -188
  30. punit-1.4.9/src/punit/__init__.py +0 -23
  31. punit-1.4.9/src/punit/assertions/strings.py +0 -55
  32. punit-1.4.9/src/punit/metadata/CallableMetadata.py +0 -38
  33. punit-1.4.9/src/punit/reports/JsonReportGenerator.py +0 -41
  34. punit-1.4.9/src/punit/runner.py +0 -121
  35. {punit-1.4.9 → punit-1.5.3}/LICENSE +0 -0
  36. {punit-1.4.9 → punit-1.5.3}/setup.cfg +0 -0
  37. {punit-1.4.9 → punit-1.5.3}/src/pUnit.egg-info/dependency_links.txt +0 -0
  38. {punit-1.4.9 → punit-1.5.3}/src/pUnit.egg-info/entry_points.txt +0 -0
  39. {punit-1.4.9 → punit-1.5.3}/src/pUnit.egg-info/requires.txt +0 -0
  40. {punit-1.4.9 → punit-1.5.3}/src/pUnit.egg-info/top_level.txt +0 -0
  41. {punit-1.4.9 → punit-1.5.3}/src/punit/discovery/__init__.py +0 -0
  42. {punit-1.4.9 → punit-1.5.3}/src/punit/facts/__init__.py +0 -0
  43. {punit-1.4.9 → punit-1.5.3}/src/punit/filters/__init__.py +0 -0
  44. {punit-1.4.9 → punit-1.5.3}/src/punit/metadata/__init__.py +0 -0
  45. {punit-1.4.9 → punit-1.5.3}/src/punit/py.typed +0 -0
  46. {punit-1.4.9 → punit-1.5.3}/src/punit/reports/__init__.py +0 -0
  47. {punit-1.4.9 → punit-1.5.3}/src/punit/theories/__init__.py +0 -0
  48. {punit-1.4.9 → punit-1.5.3}/src/punit/traits/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pUnit
3
- Version: 1.4.9
3
+ Version: 1.5.3
4
4
  Summary: A modernized unit-test framework for Python.
5
5
  Author-email: Shaun Wilson <mrshaunwilson@msn.com>
6
6
  License: MIT License
@@ -114,7 +114,7 @@ Options:
114
114
  Default: '*.py'
115
115
  -e, --exclude PATTERN
116
116
  Exclude test files matching PATTERN, overriding --include
117
- Default: '__*__' (dunder files), '/.*/' (dot-directories)
117
+ Default: '__*__' (dunders)
118
118
  -f, --filter PATTERN|@FILEPATH
119
119
  Only execute tests matching PATTERN
120
120
  Default: '*'
@@ -70,7 +70,7 @@ Options:
70
70
  Default: '*.py'
71
71
  -e, --exclude PATTERN
72
72
  Exclude test files matching PATTERN, overriding --include
73
- Default: '__*__' (dunder files), '/.*/' (dot-directories)
73
+ Default: '__*__' (dunders)
74
74
  -f, --filter PATTERN|@FILEPATH
75
75
  Only execute tests matching PATTERN
76
76
  Default: '*'
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pUnit"
3
- version = "1.4.9"
3
+ version = "1.5.3"
4
4
  description = "A modernized unit-test framework for Python."
5
5
  keywords = ["test", "unittest", "unit-test", "xUnit", "nUnit", "pytest"]
6
6
  authors = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pUnit
3
- Version: 1.4.9
3
+ Version: 1.5.3
4
4
  Summary: A modernized unit-test framework for Python.
5
5
  Author-email: Shaun Wilson <mrshaunwilson@msn.com>
6
6
  License: MIT License
@@ -114,7 +114,7 @@ Options:
114
114
  Default: '*.py'
115
115
  -e, --exclude PATTERN
116
116
  Exclude test files matching PATTERN, overriding --include
117
- Default: '__*__' (dunder files), '/.*/' (dot-directories)
117
+ Default: '__*__' (dunders)
118
118
  -f, --filter PATTERN|@FILEPATH
119
119
  Only execute tests matching PATTERN
120
120
  Default: '*'
@@ -16,6 +16,7 @@ src/punit/runner.py
16
16
  src/punit/assertions/__init__.py
17
17
  src/punit/assertions/collections.py
18
18
  src/punit/assertions/exceptions.py
19
+ src/punit/assertions/numeric.py
19
20
  src/punit/assertions/strings.py
20
21
  src/punit/discovery/TestModuleDiscovery.py
21
22
  src/punit/discovery/__init__.py
@@ -0,0 +1,186 @@
1
+ # SPDX-FileCopyrightText: © 2024 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) -> int:
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
+ return len(text)
27
+
28
+
29
+ class TestResult:
30
+
31
+ __class_name: Optional[str]
32
+ __exception: Exception | None
33
+ __file_name: str | None
34
+ __host_name: str | None
35
+ __is_success: bool | None
36
+ __module_name: str | None
37
+ __package_name: str | None
38
+ __properties: dict[str, Any]
39
+ __start_time: float | None
40
+ __stderr_capture: TextIOCapture | None
41
+ __stdout_capture: TextIOCapture | None
42
+ __stop_time: float | None
43
+ __test_name: str | None
44
+
45
+ def __init__(self) -> None:
46
+ self.__class_name = None
47
+ self.__exception = None
48
+ self.__file_name = None
49
+ self.__host_name = None
50
+ self.__is_success = None
51
+ self.__module_name = None
52
+ self.__package_name = None
53
+ self.__properties = dict[str, Any]()
54
+ self.__start_time = None
55
+ self.__stderr_capture = None
56
+ self.__stdout_capture = None
57
+ self.__stop_time = None
58
+ self.__test_name = None
59
+
60
+ @property
61
+ def class_name(self) -> Optional[str]:
62
+ return self.__class_name if self.__class_name is not None and len(self.__class_name) > 0 else None
63
+
64
+ @class_name.setter
65
+ def class_name(self, value: Optional[str]) -> None:
66
+ self.__class_name = value
67
+
68
+ @property
69
+ def exception(self) -> Exception | None:
70
+ return self.__exception
71
+
72
+ @exception.setter
73
+ def exception(self, value: Exception) -> None:
74
+ self.__exception = value
75
+
76
+ @property
77
+ def file_name(self) -> str | None:
78
+ return self.__file_name
79
+
80
+ @file_name.setter
81
+ def file_name(self, value: str) -> None:
82
+ self.__file_name = value
83
+
84
+ @property
85
+ def host_name(self) -> str | None:
86
+ return self.__host_name
87
+
88
+ @host_name.setter
89
+ def host_name(self, value: str) -> None:
90
+ self.__host_name = value
91
+
92
+ @property
93
+ def is_success(self) -> bool:
94
+ return False if self.__is_success is None else self.__is_success
95
+
96
+ @is_success.setter
97
+ def is_success(self, value: bool) -> None:
98
+ self.__is_success = value
99
+
100
+ @property
101
+ def module_name(self) -> str:
102
+ return cast(str, self.__module_name)
103
+
104
+ @module_name.setter
105
+ def module_name(self, value: str) -> None:
106
+ self.__module_name = value
107
+
108
+ @property
109
+ def package_name(self) -> str | None:
110
+ return self.__package_name
111
+
112
+ @package_name.setter
113
+ def package_name(self, value: str) -> None:
114
+ self.__package_name = value
115
+
116
+ @property
117
+ def properties(self) -> dict[str, Any]:
118
+ return self.__properties
119
+
120
+ @properties.setter
121
+ def properties(self, value: dict[str, str]) -> None:
122
+ self.__properties = value
123
+
124
+ @property
125
+ def start_time(self) -> float | None:
126
+ return self.__start_time
127
+
128
+ @start_time.setter
129
+ def start_time(self, value: float) -> None:
130
+ self.__start_time = value
131
+
132
+ @property
133
+ def stderr(self) -> str | None:
134
+ return None if self.__stderr_capture is None else self.__stderr_capture.output
135
+
136
+ @property
137
+ def stdout(self) -> str | None:
138
+ return None if self.__stdout_capture is None else self.__stdout_capture.output
139
+
140
+ @property
141
+ def stop_time(self) -> float | None:
142
+ return self.__stop_time
143
+
144
+ @stop_time.setter
145
+ def stop_time(self, value: float) -> None:
146
+ self.__stop_time = value
147
+
148
+ @property
149
+ def test_name(self) -> str | None:
150
+ return self.__test_name
151
+
152
+ @test_name.setter
153
+ def test_name(self, value: str) -> None:
154
+ self.__test_name = value
155
+
156
+ @property
157
+ def took(self) -> float | None:
158
+ return None if self.__stop_time is None or self.__start_time is None else self.__stop_time - self.__start_time
159
+
160
+ @property
161
+ def tookPretty(self) -> str:
162
+ took = self.took
163
+ if took is None:
164
+ return 'N/A'
165
+ elif took >= 1:
166
+ return f'{took:.1f}'.rstrip('0').rstrip('.') + 's'
167
+ elif took >= 0.001:
168
+ return f'{(took*1000):.0f}ms'
169
+ elif took >= 0.000001:
170
+ return f'{(took*1000):.3f}'.rstrip('0').rstrip('.') + 'ms'
171
+ elif took >= 0.000000001:
172
+ return f'{(took*1000000):.3f}'.rstrip('0').rstrip('.') + 'μs'
173
+ else:
174
+ return f'{(took*1000):.3f}'.rstrip('0').rstrip('.') + 'ms'
175
+
176
+ def captureOutput(self, quiet: bool = False) -> None:
177
+ self.__stdout_capture = TextIOCapture(sys.stdout, quiet)
178
+ self.__stderr_capture = TextIOCapture(sys.stderr, quiet)
179
+ sys.stdout = self.__stdout_capture
180
+ sys.stderr = self.__stderr_capture
181
+
182
+ def releaseOutput(self) -> None:
183
+ if self.__stdout_capture is not None and self.__stdout_capture.target is not None:
184
+ sys.stdout = self.__stdout_capture.target
185
+ if self.__stderr_capture is not None and self.__stderr_capture.target is not None:
186
+ sys.stderr = self.__stderr_capture.target
@@ -0,0 +1,26 @@
1
+ # SPDX-FileCopyrightText: © 2024 Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ from .assertions import collections, exceptions, numeric, strings
5
+ from .facts import fact
6
+ from .theories import theory, inlinedata
7
+ from .traits import trait
8
+
9
+ from .assertions.exceptions import raises
10
+ from .assertions.numeric import approx
11
+
12
+
13
+ __version__ = '1.5.3'
14
+ __commit__ = '34475af'
15
+ __all__ = [
16
+ '__version__', '__commit__',
17
+ 'assertions',
18
+ 'collections',
19
+ 'exceptions', 'raises',
20
+ 'numeric', 'approx',
21
+ 'strings',
22
+ 'fact',
23
+ 'metadata',
24
+ 'theory', 'inlinedata',
25
+ 'trait'
26
+ ]
@@ -31,17 +31,17 @@ async def async_main() -> None:
31
31
  if pathbase not in sys.path:
32
32
  sys.path.append(pathbase)
33
33
  testModuleDiscovery = TestModuleDiscovery(
34
- os.path.join(cli.workdir, cli.testPackageName),
34
+ os.path.join(cli.workdir, cli.test_package_name),
35
35
  cli.includePatterns,
36
36
  cli.excludePatterns,
37
37
  cli)
38
38
  testModuleDiscovery.discover()
39
- testRunner = TestRunner(cli.testPackageName, testModuleDiscovery.filenames, cli)
39
+ testRunner = TestRunner(cli.test_package_name, testModuleDiscovery.filenames, cli)
40
40
  results = await testRunner.run()
41
41
  totalTime = time.time() - ts
42
42
  failureCount = 0
43
43
  for result in results:
44
- if not result.isSuccess:
44
+ if not result.is_success:
45
45
  failureCount += 1 # pragma: no cover
46
46
  if not cli.quiet:
47
47
  print(f'Total: {len(results)}, Failures: {failureCount}, Took: {totalTime:.3f}s')
@@ -1,11 +1,12 @@
1
1
  # SPDX-FileCopyrightText: © 2024 Shaun Wilson
2
2
  # SPDX-License-Identifier: MIT
3
3
 
4
- from . import collections, exceptions, strings
4
+ from . import collections, exceptions, numeric, strings
5
5
 
6
6
 
7
7
  __all__ = [
8
8
  'collections',
9
9
  'exceptions',
10
+ 'numeric',
10
11
  'strings'
11
12
  ]
@@ -4,12 +4,14 @@
4
4
  from typing import Any, Callable, Optional, Sequence, cast
5
5
 
6
6
 
7
- def areSame(actual: Sequence[Any] | set[Any] | dict[Any, Any] | None, expected: Sequence[Any] | set[Any] | dict[Any, Any] | None, sort: bool = False, sort_function: Optional[Callable[[Any], Any]] = None) -> bool:
7
+ def are_same(actual: Sequence[Any] | set[Any] | dict[Any, Any] | None, expected: Sequence[Any] | set[Any] | dict[Any, Any] | None, sort: bool = False, sort_function: Optional[Callable[[Any], Any]] = None) -> bool:
8
8
  """
9
9
  Check if two sequences contain the same elements in the same order.
10
-
11
- :param actual: The sequence to check
12
- :param expected: The sequence to compare against
10
+
11
+ Can optionally specify *sort* so that differences in order do not cause failure.
12
+
13
+ :param actual: The sequence to check.
14
+ :param expected: The sequence to compare against.
13
15
  :param sort: Sort sequences before performing comparisons.
14
16
  :param sort_function: Custom function to use when sorting.
15
17
  :returns bool: True if the sequences contain the same elements in the same order, False otherwise.
@@ -27,44 +29,48 @@ def areSame(actual: Sequence[Any] | set[Any] | dict[Any, Any] | None, expected:
27
29
  if sort:
28
30
  if isinstance(actual, dict) or isinstance(expected, dict):
29
31
  sort_function = sort_function if sort_function is not None else lambda e: e[0]
30
- actual = sorted(cast(dict, actual).items(), key=sort_function)
31
- expected = sorted(cast(dict, expected).items(), key=sort_function)
32
+ actual = sorted(cast(dict[Any, Any], actual).items(), key=sort_function)
33
+ expected = sorted(cast(dict[Any, Any], expected).items(), key=sort_function)
32
34
  else:
33
35
  sort_function = sort_function if sort_function is not None else lambda e: e
34
36
  actual = sorted(actual, key=sort_function)
35
37
  expected = sorted(expected, key=sort_function)
36
38
 
37
39
  if isinstance(actual, dict) or isinstance(expected, dict):
38
- for pairs in zip(cast(dict, actual).items(), cast(dict, expected).items()):
40
+ for pairs in zip(cast(dict[Any, Any], actual).items(), cast(dict[Any, Any], expected).items()):
39
41
  if not areSame(pairs[0], pairs[1]):
40
- return False
42
+ return False
41
43
  else:
42
44
  for pairs in zip(actual, expected):
43
45
  if isinstance(pairs[0], dict) or isinstance(pairs[1], dict):
44
46
  if not areSame(pairs[0], pairs[1]):
45
- return False
46
- elif pairs[0] != pairs[1]:
47
47
  return False
48
+ elif pairs[0] != pairs[1]:
49
+ return False
48
50
  return True
49
51
 
50
- def hasLength(actual: Sequence[Any] | set[Any] | dict[Any, Any] | None, expected: int | None) -> bool:
52
+
53
+ def has_length(actual: Sequence[Any] | set[Any] | dict[Any, Any] | None, min: Optional[int] = None, max: Optional[int] = None) -> bool:
51
54
  """
52
55
  Check if actual value has the expected number of elements.
53
-
56
+
54
57
  :param actual: The actual value to check.
55
58
  :param int|None expected: The expected number of elements
56
-
59
+
57
60
  :returns bool: True if the sequence has exactly the expected number of elements, False otherwise
58
61
  """
59
- if actual is None and (expected is None or expected == 0):
60
- return True
61
- elif actual is None and (expected is not None and expected != 0):
62
- return False
63
- elif actual is not None and expected is None:
64
- return False
65
- return actual is not None and len(actual) == expected
62
+ if actual is None:
63
+ if ((min is None or min == 0) and (max is None or max == 0)):
64
+ return True
65
+ return not ((min is not None and min != 0) or (max is not None or max != 0))
66
+ else:
67
+ if ((min is None) and (max is None)):
68
+ return False
69
+ len_actual = len(actual)
70
+ return (min is None or len_actual >= min) and (max is None or len_actual <= max)
71
+
66
72
 
67
- def isNoneOrEmpty(actual: Sequence[Any] | set[Any] | dict[Any, Any] | None) -> bool:
73
+ def is_none_or_empty(actual: Sequence[Any] | set[Any] | dict[Any, Any] | None) -> bool:
68
74
  """
69
75
  Check if actual value is None or empty.
70
76
 
@@ -74,3 +80,20 @@ def isNoneOrEmpty(actual: Sequence[Any] | set[Any] | dict[Any, Any] | None) -> b
74
80
  if actual is None:
75
81
  return True
76
82
  return len(actual) == 0
83
+
84
+
85
+ # DEPRECATIONS
86
+ areSame = are_same
87
+ hasLength = has_length
88
+ isNoneOrEmpty = is_none_or_empty
89
+
90
+
91
+ __all__ = [
92
+ 'are_same',
93
+ 'has_length',
94
+ 'is_none_or_empty',
95
+ # deprecated (do not use):
96
+ 'areSame',
97
+ 'hasLength',
98
+ 'isNoneOrEmpty'
99
+ ]
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: © 2026 Shaun Wilson
1
+ # SPDX-FileCopyrightText: © 2024 Shaun Wilson
2
2
  # SPDX-License-Identifier: MIT
3
3
 
4
4
  from typing import Any, Callable, Generic, Optional, TypeVar, cast, get_args
@@ -8,12 +8,12 @@ TError = TypeVar('TError', bound=Exception)
8
8
 
9
9
 
10
10
  class raises(Generic[TError]):
11
-
12
- __action:Callable
13
- __exact:bool
14
- __expect:Optional[TError|type]
15
11
 
16
- def __init__(self, action:Callable, *, exact:bool = False, expect:Optional[TError|type] = None) -> None:
12
+ __action: Callable[[], Any]
13
+ __exact: bool
14
+ __expect: Optional[TError | type]
15
+
16
+ def __init__(self, action: Callable[[], Any], *, exact: bool = False, expect: Optional[TError | type] = None) -> None:
17
17
  self.__action = action
18
18
  self.__exact = exact
19
19
  self.__expect = expect
@@ -28,7 +28,7 @@ class raises(Generic[TError]):
28
28
  if self.__exact:
29
29
  return type(ex) is expected
30
30
  elif expected is not None:
31
- return issubclass(type(ex), cast(Any,expected))
31
+ return issubclass(type(ex), cast(Any, expected))
32
32
  return False
33
33
 
34
34