agapsys-tests 0.0.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.
@@ -0,0 +1,4 @@
1
+ # Copyright (c) 2026 Leandro José Britto de Oliveira
2
+ # Licensed under the MIT License.
3
+
4
+ from . import assertions, console, os, time
@@ -0,0 +1,71 @@
1
+
2
+ # Copyright (c) 2026 Leandro José Britto de Oliveira
3
+ # Licensed under the MIT License.
4
+
5
+ # Copyright (c) 2026 Leandro José Britto de Oliveira
6
+ # Licensed under the MIT License.
7
+
8
+ from typing import Type, ContextManager
9
+
10
+ def raises(ex_type: Type[Exception] = Exception, msg: str | None = None, exact: bool = True) -> ContextManager:
11
+ """
12
+ Returns a context manager expecting an exception is raised.
13
+
14
+ Args:
15
+ ex_type (Type[Exception], optional):
16
+ Exception type to be raised. Defaults to `Exception`.
17
+
18
+ msg (str | None, optional):
19
+ Exception message that be detected or `None` is message is not
20
+ checked. Defaults to `None`.
21
+
22
+ exact (bool, optional):
23
+ When a message is specifed, defines if an exact match is required,
24
+ otherwise a a check if given messae is present in detected exception
25
+ message. Defaults to `True`.
26
+
27
+ Raises:
28
+ AssertionError:
29
+ If either an exception is not detected, its type does not match,
30
+ or exception message does not match with expected one.
31
+
32
+ Returns:
33
+ ContextManager
34
+ """
35
+ isinstance(ex_type, (type(None), type))
36
+ if ex_type is not None:
37
+ issubclass(ex_type, Exception)
38
+ isinstance(msg, (type(None), str))
39
+ isinstance(exact, bool)
40
+
41
+ class RaisesContext:
42
+ def __init__(self, ex_type: type | None, msg: str | None):
43
+ self.ex_type = ex_type
44
+ self.msg = msg
45
+ self.excinfo = None
46
+
47
+ def __enter__(self):
48
+ return self
49
+
50
+ def __exit__(self, exc_type, exc_value, traceback):
51
+ # No exception raised → fail
52
+ if exc_type is None:
53
+ raise AssertionError(f"No exception was raised")
54
+
55
+ # An exception was raised. Check type:
56
+ if self.ex_type is not None and not issubclass(exc_type, self.ex_type):
57
+ raise AssertionError(f"\nExpected type: {self.ex_type.__name__}\nGiven type: {exc_type.__name__}") from exc_value
58
+
59
+ # Correct exception, check message
60
+ if self.msg is not None:
61
+ ex_msg = str(exc_value)
62
+ if exact and msg != ex_msg:
63
+ raise AssertionError(f"\nExpected message: {repr(msg)}\nGiven message: {repr(ex_msg)}") from exc_value
64
+ if not exact and msg is not None and msg not in ex_msg:
65
+ raise AssertionError(f"\nNot found: {repr(msg)}\nIn: {repr(ex_msg)}") from exc_value
66
+
67
+ # store info and suppress it
68
+ self.excinfo = (exc_type, exc_value, traceback)
69
+ return True
70
+
71
+ return RaisesContext(ex_type, msg)
@@ -0,0 +1,174 @@
1
+ # Copyright (c) 2026 Leandro José Britto de Oliveira
2
+ # Licensed under the MIT License.
3
+
4
+ from pytest import fixture
5
+ from typing import Sequence
6
+
7
+ import sys
8
+ import io
9
+
10
+ class ConsoleTextOutputStream(io.StringIO):
11
+ """Console text output stream. """
12
+
13
+ def __init__(self, original_stream) -> None:
14
+ super().__init__()
15
+ self.__original_stream = original_stream
16
+
17
+ def clear(self):
18
+ """Clear stream data."""
19
+ self.truncate(0)
20
+ self.seek(0)
21
+
22
+ def dump(self):
23
+ """
24
+ Dumps stored data into original (wrapped) stream.
25
+ """
26
+ print(self.getvalue(), file=self.__original_stream)
27
+
28
+ def __str__(self):
29
+ return self.getvalue()
30
+
31
+ def __repr__(self) -> str:
32
+ return self.__str__()
33
+
34
+ def assert_lines(
35
+ self,
36
+ expected_lines: Sequence[str],
37
+ exact_lines: bool = False,
38
+ exact_len: bool = True
39
+ ):
40
+ """
41
+ Asserts contained data matches a given sequence of lines.
42
+
43
+ Args:
44
+ expected_lines (Sequence[str]):
45
+ Expected line sequence.
46
+
47
+ exact_lines (bool, optional):
48
+ Defines if every line should match exactly. Defaults to False.
49
+
50
+ exact_len (bool, optional):
51
+ Defines if the number of contained lines matches with given
52
+ expected ones. Defaults to True.
53
+
54
+ Raises:
55
+ AssertionError: If test fails.
56
+ """
57
+ out = self.getvalue().split('\n')
58
+ out = out[:-1] if out and out[-1] == "" else out
59
+
60
+ if exact_len:
61
+ match_length = (len(out) == len(expected_lines))
62
+ min_len = min(len(out), len(expected_lines))
63
+
64
+ for i in range(min_len):
65
+ if exact_lines:
66
+ assert expected_lines[i] == out[i], f"Difference at line {i + 1}:\nExpected: {repr(expected_lines[i])}\nGiven: {repr(out[i])}"
67
+ else:
68
+ assert expected_lines[i] in out[i], f"Substring not found at line {i + 1}:\nSearched: {repr(expected_lines[i])}\nBase: {repr(out[i])}"
69
+
70
+ if not match_length:
71
+ raise AssertionError(f"Unexpected line: {expected_lines[min_len] if len(expected_lines) > min_len else out[min_len]}")
72
+
73
+ self.clear()
74
+
75
+ class ConsoleTextInputStream(io.StringIO):
76
+ """Console text input stream. """
77
+ def __init__(self, initial_value: str | None = "", newline: str | None = "\n"):
78
+ super().__init__(initial_value, newline)
79
+
80
+ def set_inputs(self, *inputs):
81
+ """
82
+ Sets a sequence of inputs (for each input an implicit newline is added
83
+ automatically.
84
+ """
85
+ self.seek(0)
86
+ for input in inputs:
87
+ self.write(f"{input}\n")
88
+ self.seek(0)
89
+
90
+ @fixture
91
+ def text_stdout():
92
+ """
93
+ Replaces `sys.stdout` by a `ConsoleTextOutputStream upon every test.
94
+
95
+ Original stream is restored after test.
96
+ """
97
+ old = sys.stdout
98
+ sys.stdout = ConsoleTextOutputStream(old)
99
+ try:
100
+ yield sys.stdout
101
+ finally:
102
+ sys.stdout = old
103
+
104
+ @fixture
105
+ def binary_stdout():
106
+ """
107
+ Replaces `sys.stdout` by a byte buffer (`io.BytesIO`) upon every test.
108
+
109
+ Original stream is restored after test.
110
+ """
111
+ old = sys.stdout
112
+ sys.stdout = io.BytesIO()
113
+
114
+ try:
115
+ yield sys.stdout
116
+ finally:
117
+ sys.stdout = old
118
+
119
+ @fixture
120
+ def text_stderr():
121
+ """
122
+ Replaces `sys.stderr` by a `ConsoleTextOutputStream upon every test.
123
+
124
+ Original stream is restored after test.
125
+ """
126
+ old = sys.stderr
127
+ sys.stderr = ConsoleTextOutputStream(old)
128
+ try:
129
+ yield sys.stderr
130
+ finally:
131
+ sys.stderr = old
132
+
133
+ @fixture
134
+ def binary_stderr():
135
+ """
136
+ Replaces `sys.stderr` by a byte buffer (`io.BytesIO`) upon every test.
137
+
138
+ Original stream is restored after test.
139
+ """
140
+ old = sys.stderr
141
+ sys.stderr = io.BytesIO()
142
+
143
+ try:
144
+ yield sys.stderr
145
+ finally:
146
+ sys.stderr = old
147
+
148
+ @fixture
149
+ def text_stdin():
150
+ """
151
+ Replaces `sys.stdin` by a `ConsoleTextInputStream upon every test.
152
+
153
+ Original stream is restored after test.
154
+ """
155
+ old = sys.stdin
156
+ sys.stdin = ConsoleTextInputStream()
157
+ try:
158
+ yield sys.stdin
159
+ finally:
160
+ sys.stdin = old
161
+
162
+ @fixture
163
+ def binary_stdin():
164
+ """
165
+ Replaces `sys.stdin` by a byte buffer (`io.BytesIO`) upon every test.
166
+
167
+ Original stream is restored after test.
168
+ """
169
+ old = sys.stdin
170
+ sys.stdin = io.BytesIO()
171
+ try:
172
+ yield sys.stdin
173
+ finally:
174
+ sys.stdin = old
agapsys/tests/os.py ADDED
@@ -0,0 +1,71 @@
1
+ # Copyright (c) 2026 Leandro José Britto de Oliveira
2
+ # Licensed under the MIT License.
3
+
4
+ from pytest import fixture
5
+
6
+ import os
7
+ import sys
8
+
9
+ class Argv:
10
+ """Wrapper for `sys.argv`."""
11
+ def __init__(self):
12
+ """Create a wrapper for `sys.argv`."""
13
+ self.__argv = sys.argv
14
+ self.set()
15
+
16
+ def set(self, *args: str, launcher: str = 'test'):
17
+ """
18
+ Sets `sys.argv` args.
19
+
20
+ Args:
21
+ *args (str):
22
+ Arguments for `sys.argv` starting at index 1.
23
+
24
+ launcher (str, optional):
25
+ Value for `sys.argv[0]`. Defaults to 'test'.
26
+ """
27
+ assert isinstance(launcher, str) and launcher
28
+ self.__argv[:] = [launcher]
29
+ self.__argv.extend(args)
30
+
31
+ class Env:
32
+ """Wrapper for `os.environ`."""
33
+ def __init__(self):
34
+ """Create a wrapper for `os.environ`."""
35
+ self.__env = os.environ
36
+
37
+ def set(self, **vars: dict[str, str]):
38
+ """
39
+ Sets `os.environ` variables.
40
+
41
+ Args:
42
+ **vars (dict[str, str]):
43
+ Variables.
44
+ """
45
+ assert isinstance(vars, dict)
46
+ self.__env.clear()
47
+ for k, v in vars.items():
48
+ assert isinstance(k, str) and isinstance(v, str)
49
+ self.__env[k] = v
50
+
51
+ @fixture
52
+ def env():
53
+ """
54
+ Return test-specific environment.
55
+ """
56
+ original = os.environ.copy()
57
+ env = Env()
58
+ try:
59
+ yield env
60
+ finally:
61
+ os.environ.clear()
62
+ os.environ.update(original)
63
+
64
+ @fixture
65
+ def argv():
66
+ backup = sys.argv.copy()
67
+ argv = Argv()
68
+ try:
69
+ yield argv
70
+ finally:
71
+ sys.argv = backup
agapsys/tests/time.py ADDED
@@ -0,0 +1,39 @@
1
+ # Copyright (c) 2026 Leandro José Britto de Oliveira
2
+ # Licensed under the MIT License.
3
+
4
+ from pytest import fixture
5
+
6
+ import time
7
+
8
+ class FakePerformanceCounter:
9
+ def __init__(self) -> None:
10
+ self.__t = 0.0
11
+ self.__delay = None
12
+
13
+ def set(self, t: float):
14
+ assert isinstance(t, float)
15
+ self.__t = t
16
+ self.__delay = None
17
+
18
+ def delay(self, t: float):
19
+ assert isinstance(t, float) and t > 0.0
20
+ self.__delay = t
21
+
22
+ def __call__(self) -> float:
23
+ if self.__delay is not None:
24
+ t = self.__t
25
+ self.__t += self.__delay
26
+ self.__delay = None
27
+ return t
28
+
29
+ return self.__t
30
+
31
+ @fixture
32
+ def perf_counter():
33
+ backup = time.perf_counter
34
+ fake = FakePerformanceCounter()
35
+ time.perf_counter = fake
36
+ try:
37
+ yield fake
38
+ finally:
39
+ time.perf_counter = backup
@@ -0,0 +1,30 @@
1
+ Metadata-Version: 2.4
2
+ Name: agapsys-tests
3
+ Version: 0.0.0
4
+ Summary: Common test utilities
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: pytest>=7.0
10
+ Requires-Dist: agapsys-utils<2.0.0,>=1.0.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: build; extra == "dev"
13
+ Requires-Dist: twine; extra == "dev"
14
+ Dynamic: license-file
15
+
16
+ # agapsys-tests
17
+
18
+ This project provides testing general purpose utiltities that individually thet do not fit in a dedicated library.
19
+
20
+ ## License
21
+
22
+ This project is distributed under MIT License. Please see the [LICENSE](LICENSE) file for details on copying and distribution.
23
+
24
+ ## Basic usage
25
+
26
+ ```py
27
+ import agapsys.tests as tests
28
+
29
+ # That's it... use the available modules
30
+ ```
@@ -0,0 +1,10 @@
1
+ agapsys/tests/__init__.py,sha256=zasd3WW7rgjPISnSxs4_MYlTZuWUqCvpiNh2hBGkrxo,133
2
+ agapsys/tests/assertions.py,sha256=-nY0mhV2W2HvmhgoH2lraeQSoGoROY1nHRsQDkmjyas,2669
3
+ agapsys/tests/console.py,sha256=6DUXKR5jhrVBwGgYNILfj85IECfdGxuDzFZGe1V1n24,4486
4
+ agapsys/tests/os.py,sha256=q-_p8WvzegnSXNrmUK6ITrn1BuBlfnvfQH1AAOAyluU,1631
5
+ agapsys/tests/time.py,sha256=fLpw87fsZUHQsAi3I1II7BKW7mZrnRe8Va9buV_feoo,876
6
+ agapsys_tests-0.0.0.dist-info/licenses/LICENSE,sha256=yr_RkPiAq-n-SY5DlUgGF3NLyKJs7Fma2lNNbUmPSaI,1089
7
+ agapsys_tests-0.0.0.dist-info/METADATA,sha256=VjLXUAjNpIum9fE6LUZAxlJtocYkjMcu7BBGzdJk26c,764
8
+ agapsys_tests-0.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
9
+ agapsys_tests-0.0.0.dist-info/top_level.txt,sha256=jQtj8IXrsij7eYU7deXAYNMNiYvMvdhkfjud5AjBtOk,8
10
+ agapsys_tests-0.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Leandro José Britto de Oliveira
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ agapsys