orionis 0.405.0__py3-none-any.whl → 0.407.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.
- orionis/console/base/command.py +57 -50
- orionis/console/base/contracts/command.py +68 -0
- orionis/console/dynamic/contracts/progress_bar.py +3 -3
- orionis/console/dynamic/progress_bar.py +8 -8
- orionis/console/output/console.py +8 -2
- orionis/console/output/contracts/console.py +1 -1
- orionis/container/container.py +2 -2
- orionis/container/context/scope.py +4 -1
- orionis/container/contracts/service_provider.py +2 -2
- orionis/container/entities/binding.py +31 -44
- orionis/container/enums/lifetimes.py +22 -1
- orionis/container/facades/facade.py +1 -2
- orionis/container/providers/service_provider.py +2 -2
- orionis/foundation/application.py +542 -248
- orionis/foundation/config/app/entities/app.py +107 -90
- orionis/foundation/config/auth/entities/auth.py +4 -33
- orionis/foundation/config/cache/entities/cache.py +18 -41
- orionis/foundation/config/cache/entities/file.py +8 -35
- orionis/foundation/config/cache/entities/stores.py +17 -38
- orionis/foundation/config/cors/entities/cors.py +41 -54
- orionis/foundation/config/database/entities/connections.py +40 -56
- orionis/foundation/config/database/entities/database.py +11 -38
- orionis/foundation/config/database/entities/mysql.py +48 -76
- orionis/foundation/config/database/entities/oracle.py +30 -57
- orionis/foundation/config/database/entities/pgsql.py +45 -61
- orionis/foundation/config/database/entities/sqlite.py +26 -53
- orionis/foundation/config/filesystems/entitites/aws.py +28 -49
- orionis/foundation/config/filesystems/entitites/disks.py +27 -47
- orionis/foundation/config/filesystems/entitites/filesystems.py +15 -37
- orionis/foundation/config/filesystems/entitites/local.py +9 -35
- orionis/foundation/config/filesystems/entitites/public.py +14 -41
- orionis/foundation/config/logging/entities/channels.py +56 -86
- orionis/foundation/config/logging/entities/chunked.py +9 -9
- orionis/foundation/config/logging/entities/daily.py +8 -8
- orionis/foundation/config/logging/entities/hourly.py +6 -6
- orionis/foundation/config/logging/entities/logging.py +12 -18
- orionis/foundation/config/logging/entities/monthly.py +7 -7
- orionis/foundation/config/logging/entities/stack.py +5 -5
- orionis/foundation/config/logging/entities/weekly.py +6 -6
- orionis/foundation/config/mail/entities/file.py +9 -36
- orionis/foundation/config/mail/entities/mail.py +22 -40
- orionis/foundation/config/mail/entities/mailers.py +29 -44
- orionis/foundation/config/mail/entities/smtp.py +47 -48
- orionis/foundation/config/queue/entities/brokers.py +19 -41
- orionis/foundation/config/queue/entities/database.py +24 -46
- orionis/foundation/config/queue/entities/queue.py +28 -40
- orionis/foundation/config/roots/paths.py +272 -468
- orionis/foundation/config/session/entities/session.py +23 -53
- orionis/foundation/config/startup.py +165 -135
- orionis/foundation/config/testing/entities/testing.py +137 -122
- orionis/foundation/config/testing/enums/__init__.py +6 -2
- orionis/foundation/config/testing/enums/drivers.py +16 -0
- orionis/foundation/config/testing/enums/verbosity.py +18 -0
- orionis/foundation/contracts/application.py +152 -362
- orionis/foundation/providers/console_provider.py +24 -2
- orionis/foundation/providers/dumper_provider.py +24 -2
- orionis/foundation/providers/logger_provider.py +24 -2
- orionis/foundation/providers/path_resolver_provider.py +25 -2
- orionis/foundation/providers/progress_bar_provider.py +24 -2
- orionis/foundation/providers/testing_provider.py +39 -0
- orionis/foundation/providers/workers_provider.py +24 -2
- orionis/metadata/framework.py +1 -1
- orionis/services/asynchrony/contracts/coroutines.py +13 -5
- orionis/services/asynchrony/coroutines.py +33 -29
- orionis/services/asynchrony/exceptions/exception.py +9 -1
- orionis/services/environment/core/dot_env.py +46 -34
- orionis/services/environment/enums/__init__.py +0 -0
- orionis/services/environment/enums/cast_type.py +42 -0
- orionis/services/environment/helpers/functions.py +1 -2
- orionis/services/environment/key/__init__.py +0 -0
- orionis/services/environment/key/key_generator.py +37 -0
- orionis/services/environment/serializer/__init__.py +0 -0
- orionis/services/environment/serializer/values.py +21 -0
- orionis/services/environment/validators/__init__.py +0 -0
- orionis/services/environment/validators/key_name.py +46 -0
- orionis/services/environment/validators/types.py +45 -0
- orionis/services/system/contracts/imports.py +38 -18
- orionis/services/system/contracts/workers.py +29 -12
- orionis/services/system/imports.py +65 -25
- orionis/services/system/runtime/imports.py +18 -9
- orionis/services/system/workers.py +49 -16
- orionis/support/entities/__init__.py +0 -0
- orionis/support/entities/base.py +104 -0
- orionis/support/facades/testing.py +15 -0
- orionis/support/facades/workers.py +1 -1
- orionis/test/cases/asynchronous.py +0 -11
- orionis/test/cases/synchronous.py +0 -9
- orionis/test/contracts/dumper.py +11 -4
- orionis/test/contracts/kernel.py +5 -110
- orionis/test/contracts/logs.py +27 -65
- orionis/test/contracts/printer.py +16 -128
- orionis/test/contracts/test_result.py +100 -0
- orionis/test/contracts/unit_test.py +87 -150
- orionis/test/core/unit_test.py +608 -554
- orionis/test/entities/result.py +22 -2
- orionis/test/enums/__init__.py +0 -2
- orionis/test/enums/status.py +14 -9
- orionis/test/exceptions/config.py +9 -1
- orionis/test/exceptions/failure.py +34 -11
- orionis/test/exceptions/persistence.py +10 -2
- orionis/test/exceptions/runtime.py +9 -1
- orionis/test/exceptions/value.py +13 -1
- orionis/test/kernel.py +87 -289
- orionis/test/output/dumper.py +83 -18
- orionis/test/output/printer.py +399 -156
- orionis/test/records/logs.py +203 -82
- orionis/test/validators/__init__.py +33 -0
- orionis/test/validators/base_path.py +45 -0
- orionis/test/validators/execution_mode.py +45 -0
- orionis/test/validators/fail_fast.py +37 -0
- orionis/test/validators/folder_path.py +34 -0
- orionis/test/validators/module_name.py +31 -0
- orionis/test/validators/name_pattern.py +40 -0
- orionis/test/validators/pattern.py +36 -0
- orionis/test/validators/persistent.py +42 -0
- orionis/test/validators/persistent_driver.py +43 -0
- orionis/test/validators/print_result.py +37 -0
- orionis/test/validators/tags.py +37 -0
- orionis/test/validators/throw_exception.py +39 -0
- orionis/test/validators/verbosity.py +37 -0
- orionis/test/validators/web_report.py +35 -0
- orionis/test/validators/workers.py +31 -0
- orionis/test/view/render.py +48 -54
- {orionis-0.405.0.dist-info → orionis-0.407.0.dist-info}/METADATA +1 -1
- {orionis-0.405.0.dist-info → orionis-0.407.0.dist-info}/RECORD +170 -112
- tests/container/__init__.py +0 -0
- tests/container/context/__init__.py +0 -0
- tests/container/context/test_manager.py +27 -0
- tests/container/context/test_scope.py +23 -0
- tests/container/entities/__init__.py +0 -0
- tests/container/entities/test_binding.py +133 -0
- tests/container/enums/__init__.py +0 -0
- tests/container/enums/test_lifetimes.py +63 -0
- tests/container/facades/__init__.py +0 -0
- tests/container/facades/test_facade.py +61 -0
- tests/container/mocks/__init__.py +0 -0
- tests/container/mocks/mock_complex_classes.py +482 -0
- tests/container/mocks/mock_simple_classes.py +32 -0
- tests/container/providers/__init__.py +0 -0
- tests/container/providers/test_providers.py +48 -0
- tests/container/resolver/__init__.py +0 -0
- tests/container/resolver/test_resolver.py +55 -0
- tests/container/test_container.py +254 -0
- tests/container/test_singleton.py +98 -0
- tests/container/test_thread_safety.py +217 -0
- tests/container/validators/__init__.py +0 -0
- tests/container/validators/test_implements.py +140 -0
- tests/container/validators/test_is_abstract_class.py +99 -0
- tests/container/validators/test_is_callable.py +73 -0
- tests/container/validators/test_is_concrete_class.py +97 -0
- tests/container/validators/test_is_instance.py +105 -0
- tests/container/validators/test_is_not_subclass.py +117 -0
- tests/container/validators/test_is_subclass.py +115 -0
- tests/container/validators/test_is_valid_alias.py +113 -0
- tests/container/validators/test_lifetime.py +75 -0
- tests/example/test_example.py +2 -2
- tests/foundation/config/testing/test_foundation_config_testing.py +1 -1
- tests/metadata/test_metadata_framework.py +89 -24
- tests/metadata/test_metadata_package.py +55 -10
- tests/services/asynchrony/test_services_asynchrony_coroutine.py +52 -7
- tests/services/system/test_services_system_imports.py +119 -16
- tests/services/system/test_services_system_workers.py +71 -30
- tests/testing/test_testing_result.py +117 -117
- tests/testing/test_testing_unit.py +209 -209
- orionis/foundation/config/base.py +0 -112
- orionis/test/arguments/parser.py +0 -187
- orionis/test/contracts/parser.py +0 -43
- orionis/test/entities/arguments.py +0 -38
- orionis/test/enums/execution_mode.py +0 -16
- /orionis/{test/arguments → console/base/contracts}/__init__.py +0 -0
- /orionis/foundation/config/testing/enums/{test_mode.py → mode.py} +0 -0
- {orionis-0.405.0.dist-info → orionis-0.407.0.dist-info}/WHEEL +0 -0
- {orionis-0.405.0.dist-info → orionis-0.407.0.dist-info}/licenses/LICENCE +0 -0
- {orionis-0.405.0.dist-info → orionis-0.407.0.dist-info}/top_level.txt +0 -0
- {orionis-0.405.0.dist-info → orionis-0.407.0.dist-info}/zip-safe +0 -0
orionis/test/core/unit_test.py
CHANGED
|
@@ -11,376 +11,311 @@ from datetime import datetime
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import Any, Dict, List, Optional, Tuple
|
|
13
13
|
from orionis.container.resolver.resolver import Resolver
|
|
14
|
+
from orionis.foundation.config.testing.enums.drivers import PersistentDrivers
|
|
15
|
+
from orionis.foundation.config.testing.enums.mode import ExecutionMode
|
|
16
|
+
from orionis.foundation.config.testing.enums.verbosity import VerbosityMode
|
|
14
17
|
from orionis.foundation.contracts.application import IApplication
|
|
15
18
|
from orionis.services.introspection.instances.reflection import ReflectionInstance
|
|
16
|
-
from orionis.
|
|
19
|
+
from orionis.test.contracts.test_result import IOrionisTestResult
|
|
20
|
+
from orionis.test.contracts.unit_test import IUnitTest
|
|
17
21
|
from orionis.test.entities.result import TestResult
|
|
18
|
-
from orionis.test.enums import
|
|
19
|
-
ExecutionMode,
|
|
20
|
-
TestStatus
|
|
21
|
-
)
|
|
22
|
+
from orionis.test.enums import TestStatus
|
|
22
23
|
from orionis.test.exceptions import (
|
|
23
24
|
OrionisTestFailureException,
|
|
24
25
|
OrionisTestPersistenceError,
|
|
25
|
-
OrionisTestValueError
|
|
26
|
+
OrionisTestValueError,
|
|
26
27
|
)
|
|
27
|
-
from orionis.test.records.logs import TestLogs
|
|
28
|
-
from orionis.test.contracts.unit_test import IUnitTest
|
|
29
28
|
from orionis.test.output.printer import TestPrinter
|
|
29
|
+
from orionis.test.records.logs import TestLogs
|
|
30
|
+
from orionis.test.validators import (
|
|
31
|
+
ValidExecutionMode,
|
|
32
|
+
ValidFailFast,
|
|
33
|
+
ValidPersistent,
|
|
34
|
+
ValidPersistentDriver,
|
|
35
|
+
ValidPrintResult,
|
|
36
|
+
ValidThrowException,
|
|
37
|
+
ValidVerbosity,
|
|
38
|
+
ValidWebReport,
|
|
39
|
+
ValidWorkers,
|
|
40
|
+
ValidBasePath,
|
|
41
|
+
ValidFolderPath,
|
|
42
|
+
ValidNamePattern,
|
|
43
|
+
ValidPattern,
|
|
44
|
+
ValidTags,
|
|
45
|
+
ValidModuleName,
|
|
46
|
+
)
|
|
30
47
|
from orionis.test.view.render import TestingResultRender
|
|
31
48
|
|
|
32
49
|
class UnitTest(IUnitTest):
|
|
33
50
|
"""
|
|
34
51
|
Orionis UnitTest
|
|
35
52
|
|
|
36
|
-
|
|
53
|
+
Advanced unit testing manager for the Orionis framework.
|
|
37
54
|
|
|
38
|
-
This class
|
|
39
|
-
It supports sequential or parallel execution, filtering by name or tags, and detailed result capture, including timings, errors, and tracebacks.
|
|
55
|
+
This class offers a robust and extensible solution for discovering, executing, and reporting unit tests with high configurability. It supports both sequential and parallel execution modes, filtering by test name or tags, and provides detailed result tracking including execution times, error messages, and tracebacks.
|
|
40
56
|
|
|
41
|
-
|
|
42
|
-
|
|
57
|
+
Key features:
|
|
58
|
+
- Flexible test discovery from folders or modules, with pattern and tag filtering.
|
|
59
|
+
- Rich result reporting: console output, persistent storage (SQLite or JSON), and web-based reports.
|
|
60
|
+
- Dependency injection for test methods via the application context.
|
|
61
|
+
- Customizable verbosity, fail-fast, and exception handling options.
|
|
62
|
+
- Designed for easy integration into CI/CD pipelines and adaptable to diverse project requirements.
|
|
43
63
|
|
|
44
|
-
|
|
64
|
+
Orionis UnitTest is ideal for teams seeking enhanced traceability, reliability, and visibility in automated testing, with capabilities that go beyond standard unittest frameworks.
|
|
45
65
|
"""
|
|
46
66
|
|
|
47
67
|
def __init__(
|
|
48
68
|
self
|
|
49
69
|
) -> None:
|
|
50
70
|
"""
|
|
51
|
-
|
|
71
|
+
Initialize a new UnitTest instance with default configuration and internal state.
|
|
52
72
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
This constructor sets up all internal attributes required for test discovery, execution,
|
|
74
|
+
result reporting, and configuration management. It prepares the instance for further
|
|
75
|
+
configuration and use, but does not perform any test discovery or execution itself.
|
|
56
76
|
|
|
57
77
|
Attributes
|
|
58
78
|
----------
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
Pattern to
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
Name of the module for test discovery.
|
|
91
|
-
loader : unittest.TestLoader
|
|
79
|
+
__app : Optional[IApplication]
|
|
80
|
+
The application instance used for dependency injection in test cases.
|
|
81
|
+
__verbosity : Optional[int]
|
|
82
|
+
Verbosity level for test output (None until configured).
|
|
83
|
+
__execution_mode : Optional[str]
|
|
84
|
+
Test execution mode, e.g., 'SEQUENTIAL' or 'PARALLEL' (None until configured).
|
|
85
|
+
__max_workers : Optional[int]
|
|
86
|
+
Maximum number of worker threads/processes for parallel execution (None until configured).
|
|
87
|
+
__fail_fast : Optional[bool]
|
|
88
|
+
If True, stops execution upon the first test failure (None until configured).
|
|
89
|
+
__throw_exception : Optional[bool]
|
|
90
|
+
If True, raises exceptions on test failures (None until configured).
|
|
91
|
+
__persistent : Optional[bool]
|
|
92
|
+
If True, enables persistent storage for test results (None until configured).
|
|
93
|
+
__persistent_driver : Optional[str]
|
|
94
|
+
The driver to use for persistence, e.g., 'sqlite' or 'json' (None until configured).
|
|
95
|
+
__web_report : Optional[bool]
|
|
96
|
+
If True, enables web-based reporting of test results (None until configured).
|
|
97
|
+
__folder_path : Optional[str]
|
|
98
|
+
Relative folder path for test discovery (None until set).
|
|
99
|
+
__base_path : Optional[str]
|
|
100
|
+
Base directory for test discovery (None until set).
|
|
101
|
+
__pattern : Optional[str]
|
|
102
|
+
File name pattern to match test files (None until set).
|
|
103
|
+
__test_name_pattern : Optional[str]
|
|
104
|
+
Pattern to filter test names (None until set).
|
|
105
|
+
__tags : Optional[List[str]]
|
|
106
|
+
List of tags to filter tests (None until set).
|
|
107
|
+
__module_name : Optional[str]
|
|
108
|
+
Name of the module for test discovery (None until set).
|
|
109
|
+
__loader : unittest.TestLoader
|
|
92
110
|
Loader for discovering tests.
|
|
93
|
-
|
|
94
|
-
Test suite
|
|
95
|
-
|
|
96
|
-
List of discovered
|
|
97
|
-
|
|
111
|
+
__suite : unittest.TestSuite
|
|
112
|
+
Test suite containing discovered tests.
|
|
113
|
+
__discovered_tests : List
|
|
114
|
+
List of discovered test metadata.
|
|
115
|
+
__printer : Optional[TestPrinter]
|
|
98
116
|
Utility for printing test results to the console.
|
|
99
|
-
__output_buffer
|
|
117
|
+
__output_buffer : Optional[str]
|
|
100
118
|
Buffer for capturing standard output during tests.
|
|
101
|
-
__error_buffer
|
|
119
|
+
__error_buffer : Optional[str]
|
|
102
120
|
Buffer for capturing error output during tests.
|
|
103
|
-
__result
|
|
104
|
-
Result of the test execution.
|
|
121
|
+
__result : Optional[dict]
|
|
122
|
+
Result summary of the test execution.
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
None
|
|
127
|
+
This constructor does not return a value.
|
|
105
128
|
"""
|
|
106
129
|
|
|
107
|
-
#
|
|
108
|
-
self.
|
|
109
|
-
|
|
110
|
-
#
|
|
111
|
-
self.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
self.
|
|
115
|
-
self.
|
|
116
|
-
self.
|
|
117
|
-
self.
|
|
118
|
-
self.
|
|
119
|
-
self.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
self.
|
|
125
|
-
self.
|
|
126
|
-
self.
|
|
127
|
-
self.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
self.
|
|
135
|
-
self.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
130
|
+
# Application instance for dependency injection (set via __setApp)
|
|
131
|
+
self.__app: Optional[IApplication] = None
|
|
132
|
+
|
|
133
|
+
# Storage path for test results (set via __setApp)
|
|
134
|
+
self.__storage: Optional[str] = None
|
|
135
|
+
|
|
136
|
+
# Configuration values (set via configure)
|
|
137
|
+
self.__verbosity: Optional[int] = None
|
|
138
|
+
self.__execution_mode: Optional[str] = None
|
|
139
|
+
self.__max_workers: Optional[int] = None
|
|
140
|
+
self.__fail_fast: Optional[bool] = None
|
|
141
|
+
self.__throw_exception: Optional[bool] = None
|
|
142
|
+
self.__persistent: Optional[bool] = None
|
|
143
|
+
self.__persistent_driver: Optional[str] = None
|
|
144
|
+
self.__web_report: Optional[bool] = None
|
|
145
|
+
|
|
146
|
+
# Test discovery parameters for folders
|
|
147
|
+
self.__folder_path: Optional[str] = None
|
|
148
|
+
self.__base_path: Optional[str] = None
|
|
149
|
+
self.__pattern: Optional[str] = None
|
|
150
|
+
self.__test_name_pattern: Optional[str] = None
|
|
151
|
+
self.__tags: Optional[List[str]] = None
|
|
152
|
+
|
|
153
|
+
# Test discovery parameter for modules
|
|
154
|
+
self.__module_name: Optional[str] = None
|
|
155
|
+
|
|
156
|
+
# Initialize the unittest loader and suite for test discovery and execution
|
|
157
|
+
self.__loader = unittest.TestLoader()
|
|
158
|
+
self.__suite = unittest.TestSuite()
|
|
159
|
+
self.__discovered_tests: List = []
|
|
160
|
+
|
|
161
|
+
# Printer for console output (set during configuration)
|
|
162
|
+
self.__printer: TestPrinter = None
|
|
163
|
+
|
|
164
|
+
# Buffers for capturing standard output and error during test execution
|
|
141
165
|
self.__output_buffer = None
|
|
142
166
|
self.__error_buffer = None
|
|
143
167
|
|
|
144
|
-
#
|
|
168
|
+
# Stores the result summary after test execution
|
|
145
169
|
self.__result = None
|
|
146
170
|
|
|
147
|
-
def
|
|
171
|
+
def configure(
|
|
148
172
|
self,
|
|
149
|
-
|
|
173
|
+
*,
|
|
174
|
+
verbosity: int | VerbosityMode,
|
|
175
|
+
execution_mode: str | ExecutionMode,
|
|
176
|
+
max_workers: int,
|
|
177
|
+
fail_fast: bool,
|
|
178
|
+
print_result: bool,
|
|
179
|
+
throw_exception: bool,
|
|
180
|
+
persistent: bool,
|
|
181
|
+
persistent_driver: str | PersistentDrivers,
|
|
182
|
+
web_report: bool
|
|
150
183
|
) -> 'UnitTest':
|
|
151
184
|
"""
|
|
152
|
-
|
|
153
|
-
This method allows the UnitTest to access the application instance, which is necessary for resolving dependencies and executing tests.
|
|
154
|
-
|
|
155
|
-
Parameters
|
|
156
|
-
----------
|
|
157
|
-
app : IApplication
|
|
158
|
-
The application instance to be set for the UnitTest.
|
|
159
|
-
|
|
160
|
-
Returns
|
|
161
|
-
-------
|
|
162
|
-
UnitTest
|
|
163
|
-
"""
|
|
164
|
-
|
|
165
|
-
# Validate the provided application instance
|
|
166
|
-
if not isinstance(app, IApplication):
|
|
167
|
-
raise OrionisTestValueError(
|
|
168
|
-
f"The provided application is not a valid instance of IApplication: {type(app).__name__}."
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
# Set the application instance
|
|
172
|
-
self.app = app
|
|
185
|
+
Configures the UnitTest instance with the main execution and reporting parameters.
|
|
173
186
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
verbosity: int = 2,
|
|
178
|
-
execution_mode: str | ExecutionMode = ExecutionMode.SEQUENTIAL,
|
|
179
|
-
max_workers: int = Workers().calculate(),
|
|
180
|
-
fail_fast: bool = False,
|
|
181
|
-
print_result: bool = True,
|
|
182
|
-
throw_exception: bool = False,
|
|
183
|
-
persistent: bool = False,
|
|
184
|
-
persistent_driver: str = 'sqlite',
|
|
185
|
-
web_report: bool = False
|
|
186
|
-
) -> 'UnitTest':
|
|
187
|
-
"""
|
|
188
|
-
Configure the UnitTest instance with various execution and reporting options.
|
|
187
|
+
This method sets all relevant options for running unit tests in Orionis, including execution mode
|
|
188
|
+
(sequential or parallel), verbosity level, maximum number of workers, result persistence, exception
|
|
189
|
+
handling, and web report generation.
|
|
189
190
|
|
|
190
191
|
Parameters
|
|
191
192
|
----------
|
|
192
|
-
verbosity : int
|
|
193
|
-
|
|
194
|
-
execution_mode : str
|
|
195
|
-
Test execution mode.
|
|
196
|
-
max_workers : int
|
|
197
|
-
Maximum number of
|
|
198
|
-
fail_fast : bool
|
|
199
|
-
If True,
|
|
200
|
-
print_result : bool
|
|
201
|
-
If True,
|
|
202
|
-
throw_exception : bool
|
|
203
|
-
If True,
|
|
204
|
-
persistent : bool
|
|
205
|
-
If True,
|
|
206
|
-
persistent_driver : str
|
|
207
|
-
|
|
208
|
-
web_report : bool
|
|
209
|
-
If True,
|
|
193
|
+
verbosity : int | VerbosityMode
|
|
194
|
+
Verbosity level for test output. Can be an integer or a VerbosityMode enum member.
|
|
195
|
+
execution_mode : str | ExecutionMode
|
|
196
|
+
Test execution mode ('SEQUENTIAL' or 'PARALLEL'), as a string or ExecutionMode enum.
|
|
197
|
+
max_workers : int
|
|
198
|
+
Maximum number of threads/processes for parallel execution. Must be between 1 and the maximum allowed by Workers.
|
|
199
|
+
fail_fast : bool
|
|
200
|
+
If True, stops execution on the first failure.
|
|
201
|
+
print_result : bool
|
|
202
|
+
If True, prints results to the console.
|
|
203
|
+
throw_exception : bool
|
|
204
|
+
If True, raises exceptions on test failures.
|
|
205
|
+
persistent : bool
|
|
206
|
+
If True, enables result persistence.
|
|
207
|
+
persistent_driver : str or PersistentDrivers
|
|
208
|
+
Persistence driver to use ('sqlite' or 'json').
|
|
209
|
+
web_report : bool
|
|
210
|
+
If True, enables web report generation.
|
|
210
211
|
|
|
211
212
|
Returns
|
|
212
213
|
-------
|
|
213
214
|
UnitTest
|
|
214
|
-
The configured UnitTest instance.
|
|
215
|
+
The configured UnitTest instance, allowing method chaining.
|
|
215
216
|
|
|
216
217
|
Raises
|
|
217
218
|
------
|
|
218
219
|
OrionisTestValueError
|
|
219
|
-
If any parameter
|
|
220
|
+
If any parameter is invalid or does not meet the expected requirements.
|
|
220
221
|
"""
|
|
221
222
|
|
|
222
|
-
# Validate and
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
raise OrionisTestValueError("Execution mode must be 'SEQUENTIAL' or 'PARALLEL'.")
|
|
237
|
-
|
|
238
|
-
# Validate and set max_workers
|
|
239
|
-
if max_workers is not None:
|
|
240
|
-
if isinstance(max_workers, int) and max_workers > 0:
|
|
241
|
-
self.max_workers = max_workers
|
|
242
|
-
else:
|
|
243
|
-
raise OrionisTestValueError("Max workers must be a positive integer.")
|
|
244
|
-
|
|
245
|
-
# Validate and set other parameters
|
|
246
|
-
if fail_fast is not None:
|
|
247
|
-
if isinstance(fail_fast, bool):
|
|
248
|
-
self.fail_fast = fail_fast
|
|
249
|
-
else:
|
|
250
|
-
raise OrionisTestValueError("Fail fast must be a boolean value.")
|
|
251
|
-
|
|
252
|
-
# Validate and set print_result
|
|
253
|
-
if print_result is not None:
|
|
254
|
-
if isinstance(print_result, bool):
|
|
255
|
-
self.print_result = print_result
|
|
256
|
-
else:
|
|
257
|
-
raise OrionisTestValueError("Print result must be a boolean value.")
|
|
258
|
-
|
|
259
|
-
# Validate and set throw_exception
|
|
260
|
-
if throw_exception is not None:
|
|
261
|
-
if isinstance(throw_exception, bool):
|
|
262
|
-
self.throw_exception = throw_exception
|
|
263
|
-
else:
|
|
264
|
-
raise OrionisTestValueError("Throw exception must be a boolean value.")
|
|
265
|
-
|
|
266
|
-
# Validate and set persistent and persistent_driver
|
|
267
|
-
if persistent is not None:
|
|
268
|
-
if isinstance(persistent, bool):
|
|
269
|
-
self.persistent = persistent
|
|
270
|
-
else:
|
|
271
|
-
raise OrionisTestValueError("Persistent must be a boolean value.")
|
|
272
|
-
|
|
273
|
-
# Validate and set persistent_driver
|
|
274
|
-
if persistent_driver is not None:
|
|
275
|
-
if isinstance(persistent_driver, str) and persistent_driver in ['sqlite', 'json']:
|
|
276
|
-
self.persistent_driver = persistent_driver
|
|
277
|
-
else:
|
|
278
|
-
raise OrionisTestValueError("Persistent driver must be 'sqlite' or 'json'.")
|
|
279
|
-
|
|
280
|
-
# Validate and set web_report
|
|
281
|
-
if web_report is not None:
|
|
282
|
-
if isinstance(web_report, bool):
|
|
283
|
-
self.web_report = web_report
|
|
284
|
-
else:
|
|
285
|
-
raise OrionisTestValueError("Web report must be a boolean value.")
|
|
223
|
+
# Validate and assign parameters using specialized validators
|
|
224
|
+
self.__verbosity = ValidVerbosity(verbosity)
|
|
225
|
+
self.__execution_mode = ValidExecutionMode(execution_mode)
|
|
226
|
+
self.__max_workers = ValidWorkers(max_workers)
|
|
227
|
+
self.__fail_fast = ValidFailFast(fail_fast)
|
|
228
|
+
self.__throw_exception = ValidThrowException(throw_exception)
|
|
229
|
+
self.__persistent = ValidPersistent(persistent)
|
|
230
|
+
self.__persistent_driver = ValidPersistentDriver(persistent_driver)
|
|
231
|
+
self.__web_report = ValidWebReport(web_report)
|
|
232
|
+
|
|
233
|
+
# Initialize the result printer with the current configuration
|
|
234
|
+
self.__printer = TestPrinter(
|
|
235
|
+
print_result = ValidPrintResult(print_result)
|
|
236
|
+
)
|
|
286
237
|
|
|
287
|
-
# Return the
|
|
238
|
+
# Return the instance to allow method chaining
|
|
288
239
|
return self
|
|
289
240
|
|
|
290
241
|
def discoverTestsInFolder(
|
|
291
242
|
self,
|
|
292
243
|
*,
|
|
293
|
-
base_path: str
|
|
244
|
+
base_path: str | Path,
|
|
294
245
|
folder_path: str,
|
|
295
|
-
pattern: str
|
|
246
|
+
pattern: str,
|
|
296
247
|
test_name_pattern: Optional[str] = None,
|
|
297
248
|
tags: Optional[List[str]] = None
|
|
298
249
|
) -> 'UnitTest':
|
|
299
250
|
"""
|
|
300
251
|
Discover and add unit tests from a specified folder to the test suite.
|
|
301
252
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
the
|
|
253
|
+
This method searches for test files within a given folder, using a file name pattern,
|
|
254
|
+
and optionally filters discovered tests by test name pattern and tags. All matching
|
|
255
|
+
tests are added to the internal test suite. The method also records metadata about
|
|
256
|
+
the discovery process, such as the folder path and the number of tests found.
|
|
305
257
|
|
|
306
258
|
Parameters
|
|
307
259
|
----------
|
|
308
|
-
base_path : str
|
|
309
|
-
The base directory
|
|
260
|
+
base_path : str or Path
|
|
261
|
+
The base directory from which the folder path is resolved.
|
|
310
262
|
folder_path : str
|
|
311
|
-
The relative path to the folder containing test files
|
|
312
|
-
pattern : str
|
|
313
|
-
The file name pattern to match test files.
|
|
314
|
-
test_name_pattern :
|
|
315
|
-
A pattern to filter test names.
|
|
316
|
-
|
|
317
|
-
|
|
263
|
+
The relative path to the folder containing test files, relative to `base_path`.
|
|
264
|
+
pattern : str
|
|
265
|
+
The file name pattern to match test files (e.g., 'test_*.py').
|
|
266
|
+
test_name_pattern : str, optional
|
|
267
|
+
A regular expression pattern to filter test names. Only tests whose names match
|
|
268
|
+
this pattern will be included. If None, all test names are included.
|
|
269
|
+
tags : list of str, optional
|
|
270
|
+
A list of tags to filter tests. Only tests decorated or marked with any of these
|
|
271
|
+
tags will be included. If None, no tag filtering is applied.
|
|
318
272
|
|
|
319
273
|
Returns
|
|
320
274
|
-------
|
|
321
275
|
UnitTest
|
|
322
|
-
The current instance with discovered tests added to the suite.
|
|
276
|
+
The current instance with the discovered tests added to the suite.
|
|
323
277
|
|
|
324
278
|
Raises
|
|
325
279
|
------
|
|
326
280
|
OrionisTestValueError
|
|
327
281
|
If any argument is invalid, the folder does not exist, no tests are found,
|
|
328
282
|
or if there are import or discovery errors.
|
|
329
|
-
"""
|
|
330
283
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
f"Invalid base_path: Expected a non-empty string, got '{base_path}' ({type(base_path).__name__})."
|
|
342
|
-
)
|
|
343
|
-
self.base_path = base_path
|
|
344
|
-
|
|
345
|
-
# Validate pattern
|
|
346
|
-
if pattern is None or not isinstance(pattern, str):
|
|
347
|
-
raise OrionisTestValueError(
|
|
348
|
-
f"Invalid pattern: Expected a non-empty string, got '{pattern}' ({type(pattern).__name__})."
|
|
349
|
-
)
|
|
350
|
-
self.pattern = pattern
|
|
351
|
-
|
|
352
|
-
# Validate test_name_pattern
|
|
353
|
-
if test_name_pattern is not None:
|
|
354
|
-
if not isinstance(test_name_pattern, str):
|
|
355
|
-
raise OrionisTestValueError(
|
|
356
|
-
f"Invalid test_name_pattern: Expected a string, got '{test_name_pattern}' ({type(test_name_pattern).__name__})."
|
|
357
|
-
)
|
|
358
|
-
self.test_name_pattern = test_name_pattern
|
|
284
|
+
Notes
|
|
285
|
+
-----
|
|
286
|
+
- The method validates all input parameters using Orionis validators.
|
|
287
|
+
- The folder path is resolved relative to the provided base path.
|
|
288
|
+
- Test discovery uses Python's unittest loader.
|
|
289
|
+
- If `test_name_pattern` is provided, only tests whose names match the pattern are included.
|
|
290
|
+
- If `tags` are provided, only tests with matching tags are included.
|
|
291
|
+
- If no tests are found after filtering, an exception is raised.
|
|
292
|
+
- Metadata about the discovery (folder and test count) is appended to the internal record.
|
|
293
|
+
"""
|
|
359
294
|
|
|
360
|
-
# Validate
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
self.tags = tags
|
|
295
|
+
# Validate Parameters
|
|
296
|
+
self.__base_path = ValidBasePath(base_path)
|
|
297
|
+
self.__folder_path = ValidFolderPath(folder_path)
|
|
298
|
+
self.__pattern = ValidPattern(pattern)
|
|
299
|
+
self.__test_name_pattern = ValidNamePattern(test_name_pattern)
|
|
300
|
+
self.__tags = ValidTags(tags)
|
|
367
301
|
|
|
368
302
|
# Try to discover tests in the specified folder
|
|
369
303
|
try:
|
|
370
304
|
|
|
371
305
|
# Ensure the folder path is absolute
|
|
372
|
-
full_path = Path(self.
|
|
306
|
+
full_path = Path(self.__base_path / self.__folder_path).resolve()
|
|
307
|
+
|
|
308
|
+
# Validate the full path
|
|
373
309
|
if not full_path.exists():
|
|
374
310
|
raise OrionisTestValueError(
|
|
375
|
-
f"Test folder not found at the specified path: '{full_path}'. "
|
|
311
|
+
f"Test folder not found at the specified path: '{str(full_path)}'. "
|
|
376
312
|
"Please verify that the path is correct and the folder exists."
|
|
377
313
|
)
|
|
378
|
-
self.full_path = str(full_path.resolve())
|
|
379
314
|
|
|
380
315
|
# Discover tests using the unittest TestLoader
|
|
381
|
-
tests = self.
|
|
316
|
+
tests = self.__loader.discover(
|
|
382
317
|
start_dir=str(full_path),
|
|
383
|
-
pattern=
|
|
318
|
+
pattern=self.__pattern,
|
|
384
319
|
top_level_dir=None
|
|
385
320
|
)
|
|
386
321
|
|
|
@@ -388,53 +323,53 @@ class UnitTest(IUnitTest):
|
|
|
388
323
|
if test_name_pattern:
|
|
389
324
|
tests = self.__filterTestsByName(
|
|
390
325
|
suite=tests,
|
|
391
|
-
pattern=
|
|
326
|
+
pattern=self.__test_name_pattern
|
|
392
327
|
)
|
|
393
328
|
|
|
394
329
|
# If tags are provided, filter tests by tags
|
|
395
330
|
if tags:
|
|
396
331
|
tests = self.__filterTestsByTags(
|
|
397
332
|
suite=tests,
|
|
398
|
-
tags=
|
|
333
|
+
tags=self.__tags
|
|
399
334
|
)
|
|
400
335
|
|
|
401
336
|
# If no tests are found, raise an error
|
|
402
337
|
if not list(tests):
|
|
403
338
|
raise OrionisTestValueError(
|
|
404
|
-
f"No tests
|
|
405
|
-
+ (f"
|
|
406
|
-
+ (f" and
|
|
407
|
-
"
|
|
339
|
+
f"No tests found in '{str(full_path)}' matching file pattern '{pattern}'"
|
|
340
|
+
+ (f", test name pattern '{test_name_pattern}'" if test_name_pattern else "")
|
|
341
|
+
+ (f", and tags {tags}" if tags else "") +
|
|
342
|
+
". Please check your patterns, tags, and test files."
|
|
408
343
|
)
|
|
409
344
|
|
|
410
345
|
# Add discovered tests to the suite
|
|
411
|
-
self.
|
|
346
|
+
self.__suite.addTests(tests)
|
|
412
347
|
|
|
413
348
|
# Count the number of tests discovered
|
|
414
349
|
# Using __flattenTestSuite to ensure we count all individual test cases
|
|
415
350
|
test_count = len(list(self.__flattenTestSuite(tests)))
|
|
416
351
|
|
|
417
352
|
# Append the discovered tests information
|
|
418
|
-
self.
|
|
353
|
+
self.__discovered_tests.append({
|
|
419
354
|
"folder": str(full_path),
|
|
420
355
|
"test_count": test_count,
|
|
421
356
|
})
|
|
422
357
|
|
|
423
|
-
#
|
|
358
|
+
# Return the current instance
|
|
424
359
|
return self
|
|
425
360
|
|
|
426
361
|
except ImportError as e:
|
|
427
362
|
|
|
428
363
|
# Raise a specific error if the import fails
|
|
429
364
|
raise OrionisTestValueError(
|
|
430
|
-
f"Error importing tests from path '{full_path}': {str(e)}.\n"
|
|
365
|
+
f"Error importing tests from path '{str(full_path)}': {str(e)}.\n"
|
|
431
366
|
"Please verify that the directory and test modules are accessible and correct."
|
|
432
367
|
)
|
|
433
368
|
except Exception as e:
|
|
434
369
|
|
|
435
370
|
# Raise a general error for unexpected issues
|
|
436
371
|
raise OrionisTestValueError(
|
|
437
|
-
f"Unexpected error while discovering tests in '{full_path}': {str(e)}.\n"
|
|
372
|
+
f"Unexpected error while discovering tests in '{str(full_path)}': {str(e)}.\n"
|
|
438
373
|
"Ensure that the test files are valid and that there are no syntax errors or missing dependencies."
|
|
439
374
|
)
|
|
440
375
|
|
|
@@ -445,137 +380,168 @@ class UnitTest(IUnitTest):
|
|
|
445
380
|
test_name_pattern: Optional[str] = None
|
|
446
381
|
) -> 'UnitTest':
|
|
447
382
|
"""
|
|
448
|
-
Discover and add unit tests from a specified module to the test suite.
|
|
383
|
+
Discover and add unit tests from a specified Python module to the test suite.
|
|
384
|
+
|
|
385
|
+
This method loads all unit tests defined within the given module and adds them to the internal test suite.
|
|
386
|
+
Optionally, it can filter discovered tests by a regular expression pattern applied to test names.
|
|
449
387
|
|
|
450
388
|
Parameters
|
|
451
389
|
----------
|
|
452
390
|
module_name : str
|
|
453
|
-
The name of the module from which to discover tests.
|
|
454
|
-
|
|
455
|
-
|
|
391
|
+
The fully qualified name of the module from which to discover tests (e.g., 'myproject.tests.test_example').
|
|
392
|
+
Must be a non-empty string and importable from the current environment.
|
|
393
|
+
test_name_pattern : str or None, optional
|
|
394
|
+
A regular expression pattern to filter test names. Only tests whose names match this pattern
|
|
395
|
+
will be included in the suite. If None, all discovered tests are included.
|
|
456
396
|
|
|
457
397
|
Returns
|
|
458
398
|
-------
|
|
459
399
|
UnitTest
|
|
460
|
-
The current instance with the discovered tests added
|
|
400
|
+
The current UnitTest instance with the discovered tests added, allowing method chaining.
|
|
461
401
|
|
|
462
402
|
Raises
|
|
463
403
|
------
|
|
464
404
|
OrionisTestValueError
|
|
465
|
-
If
|
|
466
|
-
or
|
|
405
|
+
If `module_name` is invalid, `test_name_pattern` is not a valid regex, the module cannot be imported,
|
|
406
|
+
or if no tests are found after filtering.
|
|
407
|
+
OrionisTestValueError
|
|
408
|
+
For any unexpected error during test discovery, with details about the failure.
|
|
467
409
|
|
|
468
410
|
Notes
|
|
469
411
|
-----
|
|
470
|
-
-
|
|
471
|
-
- If
|
|
472
|
-
-
|
|
412
|
+
- Input parameters are validated using Orionis validators before discovery.
|
|
413
|
+
- If `test_name_pattern` is provided, only tests matching the pattern are included.
|
|
414
|
+
- Metadata about the discovery (module name and test count) is appended to the internal `__discovered_tests` list.
|
|
415
|
+
- This method is useful for dynamically loading tests from specific modules, such as in plugin architectures or
|
|
416
|
+
when tests are not organized in standard file patterns.
|
|
473
417
|
"""
|
|
474
418
|
|
|
475
|
-
# Validate
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
f"Invalid module_name: Expected a non-empty string, got '{module_name}' ({type(module_name).__name__})."
|
|
479
|
-
)
|
|
480
|
-
self.module_name = module_name
|
|
481
|
-
|
|
482
|
-
# Validate test_name_pattern
|
|
483
|
-
if test_name_pattern is not None and not isinstance(test_name_pattern, str):
|
|
484
|
-
raise OrionisTestValueError(
|
|
485
|
-
f"Invalid test_name_pattern: Expected a string, got '{test_name_pattern}' ({type(test_name_pattern).__name__})."
|
|
486
|
-
)
|
|
487
|
-
self.test_name_pattern = test_name_pattern
|
|
419
|
+
# Validate input parameters
|
|
420
|
+
self.__module_name = ValidModuleName(module_name)
|
|
421
|
+
self.__test_name_pattern = ValidNamePattern(test_name_pattern)
|
|
488
422
|
|
|
489
|
-
# Try to load tests from the specified module
|
|
490
423
|
try:
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
name=module_name
|
|
424
|
+
# Load all tests from the specified module
|
|
425
|
+
tests = self.__loader.loadTestsFromName(
|
|
426
|
+
name=self.__module_name
|
|
495
427
|
)
|
|
496
428
|
|
|
497
|
-
# If
|
|
429
|
+
# If a test name pattern is provided, filter the discovered tests
|
|
498
430
|
if test_name_pattern:
|
|
499
431
|
tests = self.__filterTestsByName(
|
|
500
432
|
suite=tests,
|
|
501
|
-
pattern=
|
|
433
|
+
pattern=self.__test_name_pattern
|
|
502
434
|
)
|
|
503
435
|
|
|
504
|
-
# Add the
|
|
505
|
-
self.
|
|
436
|
+
# Add the filtered (or all) tests to the suite
|
|
437
|
+
self.__suite.addTests(tests)
|
|
506
438
|
|
|
507
|
-
# Count the number of tests
|
|
439
|
+
# Count the number of discovered tests
|
|
508
440
|
test_count = len(list(self.__flattenTestSuite(tests)))
|
|
509
441
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
442
|
+
if test_count == 0:
|
|
443
|
+
raise OrionisTestValueError(
|
|
444
|
+
f"No tests found in module '{self.__module_name}'"
|
|
445
|
+
+ (f" matching test name pattern '{test_name_pattern}'." if test_name_pattern else ".")
|
|
446
|
+
+ " Please ensure the module contains valid test cases and the pattern is correct."
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
# Record discovery metadata
|
|
450
|
+
self.__discovered_tests.append({
|
|
451
|
+
"module": self.__module_name,
|
|
452
|
+
"test_count": test_count
|
|
514
453
|
})
|
|
515
454
|
|
|
516
|
-
# Return the current instance
|
|
455
|
+
# Return the current instance for method chaining
|
|
517
456
|
return self
|
|
518
457
|
|
|
519
458
|
except ImportError as e:
|
|
520
459
|
|
|
521
|
-
# Raise
|
|
460
|
+
# Raise an error if the module cannot be imported
|
|
461
|
+
raise OrionisTestValueError(
|
|
462
|
+
f"Failed to import tests from module '{self.__module_name}': {str(e)}. "
|
|
463
|
+
"Ensure the module exists, is importable, and contains valid test cases."
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
except re.error as e:
|
|
467
|
+
|
|
468
|
+
# Raise an error if the test name pattern is not a valid regex
|
|
522
469
|
raise OrionisTestValueError(
|
|
523
|
-
f"
|
|
524
|
-
"
|
|
470
|
+
f"Invalid regular expression for test_name_pattern: '{test_name_pattern}'. "
|
|
471
|
+
f"Regex compilation error: {str(e)}. Please check the pattern syntax."
|
|
525
472
|
)
|
|
473
|
+
|
|
526
474
|
except Exception as e:
|
|
527
475
|
|
|
528
476
|
# Raise a general error for unexpected issues
|
|
529
477
|
raise OrionisTestValueError(
|
|
530
|
-
f"
|
|
531
|
-
"
|
|
478
|
+
f"An unexpected error occurred while discovering tests in module '{self.__module_name}': {str(e)}. "
|
|
479
|
+
"Verify that the module name is correct, test methods are valid, and there are no syntax errors or missing dependencies."
|
|
532
480
|
)
|
|
533
481
|
|
|
534
482
|
def run(
|
|
535
483
|
self
|
|
536
484
|
) -> Dict[str, Any]:
|
|
485
|
+
"""
|
|
486
|
+
Execute the test suite and return a summary of the results.
|
|
537
487
|
|
|
538
|
-
|
|
539
|
-
|
|
488
|
+
This method manages the full test execution lifecycle: it prints start and finish messages,
|
|
489
|
+
executes the test suite, captures output and error buffers, processes the results, and
|
|
490
|
+
optionally raises an exception if failures occur and exception throwing is enabled.
|
|
540
491
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
492
|
+
Returns
|
|
493
|
+
-------
|
|
494
|
+
Dict[str, Any]
|
|
495
|
+
A dictionary summarizing the test results, including statistics and execution time.
|
|
496
|
+
|
|
497
|
+
Raises
|
|
498
|
+
------
|
|
499
|
+
OrionisTestFailureException
|
|
500
|
+
If the test suite execution fails and `throw_exception` is set to True.
|
|
501
|
+
|
|
502
|
+
Notes
|
|
503
|
+
-----
|
|
504
|
+
- Measures total execution time in milliseconds.
|
|
505
|
+
- Uses the configured printer to display start, result, and finish messages.
|
|
506
|
+
- Captures and stores output and error buffers.
|
|
507
|
+
- Raises an exception if tests fail and exception throwing is enabled.
|
|
508
|
+
"""
|
|
509
|
+
|
|
510
|
+
# Record the start time in nanoseconds
|
|
511
|
+
start_time = time.time_ns()
|
|
512
|
+
|
|
513
|
+
# Print the start message with test suite details
|
|
514
|
+
self.__printer.startMessage(
|
|
515
|
+
length_tests=len(list(self.__flattenTestSuite(self.__suite))),
|
|
516
|
+
execution_mode=self.__execution_mode,
|
|
517
|
+
max_workers=self.__max_workers
|
|
547
518
|
)
|
|
548
519
|
|
|
549
|
-
# Execute the test suite and capture
|
|
550
|
-
result, output_buffer, error_buffer = self.
|
|
551
|
-
|
|
552
|
-
flatten_test_suite= self.__flattenTestSuite(self.suite),
|
|
520
|
+
# Execute the test suite and capture result, output, and error buffers
|
|
521
|
+
result, output_buffer, error_buffer = self.__printer.executePanel(
|
|
522
|
+
flatten_test_suite=self.__flattenTestSuite(self.__suite),
|
|
553
523
|
callable=self.__runSuite
|
|
554
524
|
)
|
|
555
525
|
|
|
556
|
-
#
|
|
526
|
+
# Store the captured output and error buffers as strings
|
|
557
527
|
self.__output_buffer = output_buffer.getvalue()
|
|
558
528
|
self.__error_buffer = error_buffer.getvalue()
|
|
559
529
|
|
|
560
|
-
#
|
|
561
|
-
execution_time = time.
|
|
530
|
+
# Calculate execution time in milliseconds
|
|
531
|
+
execution_time = (time.time_ns() - start_time) / 1_000_000_000
|
|
532
|
+
|
|
533
|
+
# Generate a summary of the test results
|
|
562
534
|
summary = self.__generateSummary(result, execution_time)
|
|
563
535
|
|
|
564
|
-
#
|
|
565
|
-
self.
|
|
566
|
-
print_result=self.print_result,
|
|
567
|
-
summary=summary
|
|
568
|
-
)
|
|
536
|
+
# Display the test results using the printer
|
|
537
|
+
self.__printer.displayResults(summary=summary)
|
|
569
538
|
|
|
570
|
-
#
|
|
571
|
-
if not result.wasSuccessful() and self.
|
|
539
|
+
# Raise an exception if tests failed and exception throwing is enabled
|
|
540
|
+
if not result.wasSuccessful() and self.__throw_exception:
|
|
572
541
|
raise OrionisTestFailureException(result)
|
|
573
542
|
|
|
574
543
|
# Print the final summary message
|
|
575
|
-
self.
|
|
576
|
-
print_result=self.print_result,
|
|
577
|
-
summary=summary
|
|
578
|
-
)
|
|
544
|
+
self.__printer.finishMessage(summary=summary)
|
|
579
545
|
|
|
580
546
|
# Return the summary of the test results
|
|
581
547
|
return summary
|
|
@@ -585,95 +551,125 @@ class UnitTest(IUnitTest):
|
|
|
585
551
|
suite: unittest.TestSuite
|
|
586
552
|
) -> List[unittest.TestCase]:
|
|
587
553
|
"""
|
|
588
|
-
Recursively flattens a nested unittest.TestSuite into a list of unique unittest.TestCase instances.
|
|
554
|
+
Recursively flattens a (potentially nested) unittest.TestSuite into a list of unique unittest.TestCase instances.
|
|
555
|
+
|
|
556
|
+
This method traverses the provided test suite, which may contain nested suites or individual test cases,
|
|
557
|
+
and collects all unique TestCase instances into a flat list. It ensures that each test case appears only once
|
|
558
|
+
in the resulting list, based on a short identifier derived from the test's id. This is particularly useful
|
|
559
|
+
for operations that require direct access to all test cases, such as filtering, counting, or custom execution.
|
|
589
560
|
|
|
590
561
|
Parameters
|
|
591
562
|
----------
|
|
592
563
|
suite : unittest.TestSuite
|
|
593
|
-
The test suite to flatten
|
|
564
|
+
The test suite to flatten. This can be a single suite, a nested suite, or a suite containing test cases.
|
|
594
565
|
|
|
595
566
|
Returns
|
|
596
567
|
-------
|
|
597
568
|
List[unittest.TestCase]
|
|
598
|
-
A list containing all unique TestCase instances
|
|
569
|
+
A flat list containing all unique unittest.TestCase instances found within the input suite.
|
|
599
570
|
|
|
600
571
|
Notes
|
|
601
572
|
-----
|
|
602
|
-
|
|
603
|
-
|
|
573
|
+
- The uniqueness of test cases is determined by a "short id", which is composed of the last two segments
|
|
574
|
+
of the test's full id (typically "ClassName.methodName"). This helps avoid duplicate test cases in the result.
|
|
575
|
+
- The method uses recursion to traverse all levels of nested suites.
|
|
576
|
+
- Only objects with an 'id' attribute (i.e., test cases) are included in the result.
|
|
604
577
|
"""
|
|
605
578
|
tests = []
|
|
606
579
|
seen_ids = set()
|
|
607
580
|
|
|
608
581
|
def _flatten(item):
|
|
582
|
+
"""
|
|
583
|
+
Recursively process a TestSuite or test case, collecting unique test cases.
|
|
584
|
+
|
|
585
|
+
- If the item is a TestSuite, recursively process its children.
|
|
586
|
+
- If the item is a test case (has 'id'), generate a short id and add it if not already seen.
|
|
587
|
+
"""
|
|
609
588
|
if isinstance(item, unittest.TestSuite):
|
|
589
|
+
# Recursively flatten all sub-items in the suite
|
|
610
590
|
for sub_item in item:
|
|
611
591
|
_flatten(sub_item)
|
|
612
592
|
elif hasattr(item, "id"):
|
|
593
|
+
# Generate a short id for uniqueness (e.g., "ClassName.methodName")
|
|
613
594
|
test_id = item.id()
|
|
614
595
|
parts = test_id.split('.')
|
|
615
596
|
if len(parts) >= 2:
|
|
616
597
|
short_id = '.'.join(parts[-2:])
|
|
617
598
|
else:
|
|
618
599
|
short_id = test_id
|
|
600
|
+
# Add the test case only if its short id has not been seen
|
|
619
601
|
if short_id not in seen_ids:
|
|
620
602
|
seen_ids.add(short_id)
|
|
621
603
|
tests.append(item)
|
|
622
604
|
|
|
605
|
+
# Start flattening from the root suite
|
|
623
606
|
_flatten(suite)
|
|
607
|
+
|
|
608
|
+
# Return a flat list of unique unittest.TestCase instances
|
|
624
609
|
return tests
|
|
625
610
|
|
|
626
611
|
def __runSuite(
|
|
627
612
|
self
|
|
628
|
-
):
|
|
613
|
+
) -> Tuple[unittest.TestResult, io.StringIO, io.StringIO]:
|
|
629
614
|
"""
|
|
630
|
-
|
|
631
|
-
capturing standard output and error streams during
|
|
615
|
+
Executes the test suite using the configured execution mode (sequential or parallel),
|
|
616
|
+
while capturing both standard output and error streams during the test run.
|
|
617
|
+
|
|
618
|
+
This method determines the execution mode (sequential or parallel) based on the current
|
|
619
|
+
configuration and delegates the actual test execution to the appropriate internal method.
|
|
620
|
+
It ensures that all output and error messages generated during the test run are captured
|
|
621
|
+
in dedicated buffers for later inspection or reporting.
|
|
632
622
|
|
|
633
623
|
Returns
|
|
634
624
|
-------
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
625
|
+
Tuple[unittest.TestResult, io.StringIO, io.StringIO]
|
|
626
|
+
A tuple containing:
|
|
627
|
+
- result: The unittest.TestResult object with detailed information about the test run,
|
|
628
|
+
including passed, failed, errored, and skipped tests.
|
|
629
|
+
- output_buffer: An io.StringIO object containing all captured standard output produced
|
|
630
|
+
during the test execution.
|
|
631
|
+
- error_buffer: An io.StringIO object containing all captured standard error output
|
|
632
|
+
produced during the test execution.
|
|
633
|
+
|
|
634
|
+
Notes
|
|
635
|
+
-----
|
|
636
|
+
- The execution mode is determined by the value of self.__execution_mode.
|
|
637
|
+
- Output and error streams are always captured, regardless of execution mode.
|
|
638
|
+
- The returned buffers can be used for further processing, logging, or displaying test output.
|
|
642
639
|
"""
|
|
643
640
|
|
|
644
|
-
#
|
|
641
|
+
# Create buffers to capture standard output and error during test execution
|
|
645
642
|
output_buffer = io.StringIO()
|
|
646
643
|
error_buffer = io.StringIO()
|
|
647
644
|
|
|
648
|
-
#
|
|
649
|
-
if self.
|
|
650
|
-
|
|
651
|
-
# Run tests in parallel
|
|
645
|
+
# Determine execution mode and run tests accordingly
|
|
646
|
+
if self.__execution_mode == ExecutionMode.PARALLEL.value:
|
|
647
|
+
# Run tests in parallel mode
|
|
652
648
|
result = self.__runTestsInParallel(
|
|
653
649
|
output_buffer,
|
|
654
650
|
error_buffer
|
|
655
651
|
)
|
|
656
|
-
|
|
657
652
|
else:
|
|
658
|
-
|
|
659
|
-
# Run tests sequentially
|
|
653
|
+
# Run tests sequentially (default)
|
|
660
654
|
result = self.__runTestsSequentially(
|
|
661
655
|
output_buffer,
|
|
662
656
|
error_buffer
|
|
663
657
|
)
|
|
664
658
|
|
|
665
|
-
# Return the result along with captured output and error
|
|
659
|
+
# Return the test result along with the captured output and error buffers
|
|
666
660
|
return result, output_buffer, error_buffer
|
|
667
661
|
|
|
668
662
|
def __resolveFlattenedTestSuite(
|
|
669
663
|
self
|
|
670
664
|
) -> unittest.TestSuite:
|
|
671
665
|
"""
|
|
672
|
-
Resolves dependencies for all test cases in the suite
|
|
666
|
+
Resolves and injects dependencies for all test cases in the suite, returning a flattened TestSuite.
|
|
673
667
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
668
|
+
This method processes each test case in the internal suite, inspects the test method signatures,
|
|
669
|
+
and uses the application's dependency resolver to inject any required dependencies. It handles
|
|
670
|
+
decorated methods, methods without dependencies, and raises errors for unresolved dependencies.
|
|
671
|
+
The result is a new, flat unittest.TestSuite containing test cases with all dependencies resolved
|
|
672
|
+
and injected, ready for execution.
|
|
677
673
|
|
|
678
674
|
Parameters
|
|
679
675
|
----------
|
|
@@ -682,7 +678,8 @@ class UnitTest(IUnitTest):
|
|
|
682
678
|
Returns
|
|
683
679
|
-------
|
|
684
680
|
unittest.TestSuite
|
|
685
|
-
A new test
|
|
681
|
+
A new TestSuite containing all test cases from the original suite, with dependencies injected
|
|
682
|
+
where required. Test cases with unresolved dependencies will cause an exception to be raised.
|
|
686
683
|
|
|
687
684
|
Raises
|
|
688
685
|
------
|
|
@@ -691,53 +688,59 @@ class UnitTest(IUnitTest):
|
|
|
691
688
|
|
|
692
689
|
Notes
|
|
693
690
|
-----
|
|
694
|
-
-
|
|
695
|
-
- Test methods without dependencies are added directly
|
|
696
|
-
- Test methods with unresolved dependencies trigger an error
|
|
691
|
+
- Decorated test methods are left unchanged and added as-is.
|
|
692
|
+
- Test methods without dependencies are added directly.
|
|
693
|
+
- Test methods with unresolved dependencies will trigger an error.
|
|
694
|
+
- The returned TestSuite is flat and contains all processed test cases.
|
|
697
695
|
"""
|
|
698
696
|
|
|
699
|
-
# Create a new test suite
|
|
697
|
+
# Create a new test suite to hold test cases with dependencies resolved
|
|
700
698
|
flattened_suite = unittest.TestSuite()
|
|
701
699
|
|
|
702
|
-
# Iterate through all test cases
|
|
703
|
-
for test_case in self.__flattenTestSuite(self.
|
|
700
|
+
# Iterate through all test cases in the original (possibly nested) suite
|
|
701
|
+
for test_case in self.__flattenTestSuite(self.__suite):
|
|
704
702
|
|
|
705
|
-
# Get the test method name
|
|
706
|
-
|
|
703
|
+
# Get the test method name using reflection
|
|
704
|
+
rf_instance = ReflectionInstance(test_case)
|
|
705
|
+
method_name = rf_instance.getAttribute("_testMethodName")
|
|
707
706
|
|
|
708
|
-
#
|
|
707
|
+
# If no method name is found, add the test case as-is
|
|
709
708
|
if not method_name:
|
|
710
709
|
flattened_suite.addTest(test_case)
|
|
711
710
|
continue
|
|
712
711
|
|
|
713
|
-
#
|
|
712
|
+
# Retrieve the actual test method object from the class
|
|
714
713
|
test_method = getattr(test_case.__class__, method_name, None)
|
|
715
714
|
|
|
716
|
-
# Check
|
|
715
|
+
# Check if the test method is decorated by looking for __wrapped__ attributes
|
|
717
716
|
decorators = []
|
|
718
|
-
|
|
719
|
-
# Get decorators from the test method
|
|
720
717
|
if hasattr(test_method, '__wrapped__'):
|
|
721
718
|
original = test_method
|
|
722
719
|
while hasattr(original, '__wrapped__'):
|
|
723
|
-
#
|
|
720
|
+
# Collect decorator names for informational purposes
|
|
724
721
|
if hasattr(original, '__qualname__'):
|
|
725
722
|
decorators.append(original.__qualname__)
|
|
726
723
|
elif hasattr(original, '__name__'):
|
|
727
724
|
decorators.append(original.__name__)
|
|
728
725
|
original = original.__wrapped__
|
|
729
726
|
|
|
730
|
-
# If
|
|
727
|
+
# If decorators are present, add the test case as-is (do not inject dependencies)
|
|
731
728
|
if decorators:
|
|
732
729
|
flattened_suite.addTest(test_case)
|
|
733
730
|
continue
|
|
734
731
|
|
|
735
|
-
#
|
|
736
|
-
|
|
732
|
+
# Attempt to extract dependency information from the test method signature
|
|
733
|
+
try:
|
|
734
|
+
signature = rf_instance.getMethodDependencies(method_name)
|
|
735
|
+
except Exception as e:
|
|
736
|
+
raise OrionisTestValueError(
|
|
737
|
+
f"Failed to resolve test dependencies in '../{method_name}.py. "
|
|
738
|
+
f"This may be caused by incorrect imports, missing modules, or undefined classes. "
|
|
739
|
+
f"Please verify that all test methods and their dependencies are correctly declared and accessible."
|
|
740
|
+
)
|
|
737
741
|
|
|
738
|
-
# If no dependencies to resolve,
|
|
739
|
-
if (not signature.resolved and not signature.unresolved) or
|
|
740
|
-
(not signature.resolved and len(signature.unresolved) > 0):
|
|
742
|
+
# If there are no dependencies to resolve, or unresolved dependencies exist, add as-is
|
|
743
|
+
if ((not signature.resolved and not signature.unresolved) or (not signature.resolved and len(signature.unresolved) > 0)):
|
|
741
744
|
flattened_suite.addTest(test_case)
|
|
742
745
|
continue
|
|
743
746
|
|
|
@@ -748,32 +751,32 @@ class UnitTest(IUnitTest):
|
|
|
748
751
|
"Please ensure all dependencies are correctly defined and available."
|
|
749
752
|
)
|
|
750
753
|
|
|
751
|
-
#
|
|
754
|
+
# All dependencies are resolved; prepare to inject them into the test method
|
|
752
755
|
test_class = ReflectionInstance(test_case).getClass()
|
|
753
756
|
original_method = getattr(test_class, method_name)
|
|
754
757
|
|
|
755
|
-
#
|
|
756
|
-
params = Resolver(self.
|
|
758
|
+
# Resolve dependencies using the application's resolver
|
|
759
|
+
params = Resolver(self.__app).resolveSignature(signature)
|
|
757
760
|
|
|
758
|
-
# Create a wrapper
|
|
759
|
-
def create_test_wrapper(original_test, resolved_args:dict):
|
|
761
|
+
# Create a wrapper function that injects resolved dependencies into the test method
|
|
762
|
+
def create_test_wrapper(original_test, resolved_args: dict):
|
|
760
763
|
def wrapper(self_instance):
|
|
761
764
|
return original_test(self_instance, **resolved_args)
|
|
762
765
|
return wrapper
|
|
763
766
|
|
|
764
|
-
#
|
|
767
|
+
# Wrap the original test method with the dependency-injecting wrapper
|
|
765
768
|
wrapped_method = create_test_wrapper(original_method, params)
|
|
766
769
|
|
|
767
770
|
# Bind the wrapped method to the test case instance
|
|
768
771
|
bound_method = wrapped_method.__get__(test_case, test_case.__class__)
|
|
769
772
|
|
|
770
|
-
# Replace the original test method with the wrapped
|
|
773
|
+
# Replace the original test method on the test case with the wrapped version
|
|
771
774
|
setattr(test_case, method_name, bound_method)
|
|
772
775
|
|
|
773
|
-
# Add the modified test case to the suite
|
|
776
|
+
# Add the modified test case to the flattened suite
|
|
774
777
|
flattened_suite.addTest(test_case)
|
|
775
778
|
|
|
776
|
-
# Return the flattened suite with resolved
|
|
779
|
+
# Return the new flattened suite with all dependencies resolved and injected
|
|
777
780
|
return flattened_suite
|
|
778
781
|
|
|
779
782
|
def __runTestsSequentially(
|
|
@@ -799,14 +802,31 @@ class UnitTest(IUnitTest):
|
|
|
799
802
|
"""
|
|
800
803
|
|
|
801
804
|
# Create a custom result class to capture detailed test results
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
805
|
+
result = None
|
|
806
|
+
for case in self.__resolveFlattenedTestSuite():
|
|
807
|
+
|
|
808
|
+
if not isinstance(case, unittest.TestCase):
|
|
809
|
+
raise OrionisTestValueError(
|
|
810
|
+
f"Invalid test case type: Expected unittest.TestCase, got {type(case).__name__}."
|
|
811
|
+
)
|
|
812
|
+
|
|
813
|
+
with redirect_stdout(output_buffer), redirect_stderr(error_buffer):
|
|
814
|
+
runner = unittest.TextTestRunner(
|
|
815
|
+
stream=output_buffer,
|
|
816
|
+
verbosity=self.__verbosity,
|
|
817
|
+
failfast=self.__fail_fast,
|
|
818
|
+
resultclass=self.__customResultClass()
|
|
819
|
+
)
|
|
820
|
+
single_result: IOrionisTestResult = runner.run(unittest.TestSuite([case]))
|
|
821
|
+
|
|
822
|
+
# Print a concise summary for each test.
|
|
823
|
+
self.__printer.unittestResult(single_result.test_results[0])
|
|
824
|
+
|
|
825
|
+
# Merge results
|
|
826
|
+
if result is None:
|
|
827
|
+
result = single_result
|
|
828
|
+
else:
|
|
829
|
+
self.__mergeTestResults(result, single_result)
|
|
810
830
|
|
|
811
831
|
# Return the result object containing test outcomes
|
|
812
832
|
return result
|
|
@@ -817,26 +837,35 @@ class UnitTest(IUnitTest):
|
|
|
817
837
|
error_buffer: io.StringIO
|
|
818
838
|
) -> unittest.TestResult:
|
|
819
839
|
"""
|
|
820
|
-
|
|
821
|
-
aggregating
|
|
822
|
-
are redirected to the provided buffers during execution.
|
|
840
|
+
Executes all test cases in the test suite concurrently using a thread pool,
|
|
841
|
+
aggregating their results into a single result object. Standard output and error
|
|
842
|
+
streams are redirected to the provided buffers during execution.
|
|
843
|
+
|
|
844
|
+
This method is designed to speed up test execution by running multiple test cases
|
|
845
|
+
in parallel threads, making use of the configured maximum number of workers. Each
|
|
846
|
+
test case is executed in isolation, and their results are merged into a combined
|
|
847
|
+
result object. If the `fail_fast` option is enabled and a test fails, remaining
|
|
848
|
+
tests are canceled as soon as possible.
|
|
823
849
|
|
|
824
850
|
Parameters
|
|
825
851
|
----------
|
|
826
852
|
output_buffer : io.StringIO
|
|
827
|
-
Buffer to capture standard output during test execution.
|
|
853
|
+
Buffer to capture standard output produced during test execution.
|
|
828
854
|
error_buffer : io.StringIO
|
|
829
|
-
Buffer to capture standard error during test execution.
|
|
855
|
+
Buffer to capture standard error produced during test execution.
|
|
830
856
|
|
|
831
857
|
Returns
|
|
832
858
|
-------
|
|
833
859
|
unittest.TestResult
|
|
834
|
-
|
|
860
|
+
A combined result object (instance of the custom result class) containing
|
|
861
|
+
the aggregated outcomes of all executed tests, including detailed information
|
|
862
|
+
about passed, failed, errored, and skipped tests.
|
|
835
863
|
|
|
836
864
|
Notes
|
|
837
865
|
-----
|
|
838
|
-
- Uses a custom result class to
|
|
866
|
+
- Uses a custom result class to collect detailed test outcomes.
|
|
839
867
|
- If `fail_fast` is enabled and a test fails, remaining tests are canceled.
|
|
868
|
+
- Output and error streams are captured for the entire parallel execution.
|
|
840
869
|
"""
|
|
841
870
|
|
|
842
871
|
# Flatten the test suite to get individual test cases
|
|
@@ -844,24 +873,25 @@ class UnitTest(IUnitTest):
|
|
|
844
873
|
|
|
845
874
|
# Create a custom result instance to collect all results
|
|
846
875
|
result_class = self.__customResultClass()
|
|
847
|
-
combined_result = result_class(io.StringIO(), descriptions=True, verbosity=self.
|
|
876
|
+
combined_result = result_class(io.StringIO(), descriptions=True, verbosity=self.__verbosity)
|
|
848
877
|
|
|
849
878
|
# Helper function to run a single test and return its result.
|
|
850
|
-
#
|
|
879
|
+
# Each test runs in its own thread with minimal output.
|
|
851
880
|
def run_single_test(test):
|
|
852
881
|
runner = unittest.TextTestRunner(
|
|
853
|
-
stream=io.StringIO(),
|
|
882
|
+
stream=io.StringIO(), # Use a dummy stream for individual test output
|
|
854
883
|
verbosity=0,
|
|
855
884
|
failfast=False,
|
|
856
885
|
resultclass=result_class
|
|
857
886
|
)
|
|
887
|
+
# Run the test and return its result object
|
|
858
888
|
return runner.run(unittest.TestSuite([test]))
|
|
859
889
|
|
|
860
|
-
#
|
|
890
|
+
# Redirect stdout and stderr to the provided buffers during parallel execution
|
|
861
891
|
with redirect_stdout(output_buffer), redirect_stderr(error_buffer):
|
|
862
892
|
|
|
863
893
|
# Create a ThreadPoolExecutor to run tests in parallel
|
|
864
|
-
with ThreadPoolExecutor(max_workers=self.
|
|
894
|
+
with ThreadPoolExecutor(max_workers=self.__max_workers) as executor:
|
|
865
895
|
|
|
866
896
|
# Submit all test cases to the executor
|
|
867
897
|
futures = [executor.submit(run_single_test, test) for test in test_cases]
|
|
@@ -869,15 +899,20 @@ class UnitTest(IUnitTest):
|
|
|
869
899
|
# Process the results as they complete
|
|
870
900
|
for future in as_completed(futures):
|
|
871
901
|
test_result = future.result()
|
|
902
|
+
# Merge each individual test result into the combined result
|
|
872
903
|
self.__mergeTestResults(combined_result, test_result)
|
|
873
904
|
|
|
874
905
|
# If fail_fast is enabled and a test failed, cancel remaining futures
|
|
875
|
-
if self.
|
|
906
|
+
if self.__fail_fast and not combined_result.wasSuccessful():
|
|
876
907
|
for f in futures:
|
|
877
908
|
f.cancel()
|
|
878
909
|
break
|
|
879
910
|
|
|
880
|
-
#
|
|
911
|
+
# Print a concise summary for each test in the combined result
|
|
912
|
+
for test_result in combined_result.test_results:
|
|
913
|
+
self.__printer.unittestResult(test_result)
|
|
914
|
+
|
|
915
|
+
# Return the combined result object containing all test outcomes
|
|
881
916
|
return combined_result
|
|
882
917
|
|
|
883
918
|
def __mergeTestResults(
|
|
@@ -886,34 +921,44 @@ class UnitTest(IUnitTest):
|
|
|
886
921
|
individual_result: unittest.TestResult
|
|
887
922
|
) -> None:
|
|
888
923
|
"""
|
|
889
|
-
Merge the results of two unittest.TestResult objects.
|
|
924
|
+
Merge the results of two unittest.TestResult objects into a single result.
|
|
890
925
|
|
|
891
|
-
This method updates the `combined_result` object by
|
|
892
|
-
failures, errors, skipped tests, expected failures, and unexpected successes
|
|
893
|
-
|
|
894
|
-
|
|
926
|
+
This method updates the `combined_result` object by aggregating the test run counts,
|
|
927
|
+
failures, errors, skipped tests, expected failures, and unexpected successes from the
|
|
928
|
+
`individual_result` object. It also merges any custom test results stored in the
|
|
929
|
+
`test_results` attribute, if present, ensuring that all detailed test outcomes are
|
|
930
|
+
included in the combined result.
|
|
895
931
|
|
|
896
932
|
Parameters
|
|
897
933
|
----------
|
|
898
934
|
combined_result : unittest.TestResult
|
|
899
|
-
The TestResult object to
|
|
935
|
+
The TestResult object that will be updated to include the results from `individual_result`.
|
|
900
936
|
individual_result : unittest.TestResult
|
|
901
|
-
The TestResult object
|
|
937
|
+
The TestResult object whose results will be merged into `combined_result`.
|
|
902
938
|
|
|
903
939
|
Returns
|
|
904
940
|
-------
|
|
905
941
|
None
|
|
942
|
+
This method does not return a value. It modifies `combined_result` in place.
|
|
943
|
+
|
|
944
|
+
Notes
|
|
945
|
+
-----
|
|
946
|
+
- The method aggregates all relevant test outcome lists and counters.
|
|
947
|
+
- If the `test_results` attribute exists (for custom result classes), it is also merged.
|
|
948
|
+
- This is useful for combining results from parallel or sequential test executions.
|
|
906
949
|
"""
|
|
907
950
|
|
|
908
|
-
#
|
|
951
|
+
# Aggregate the number of tests run
|
|
909
952
|
combined_result.testsRun += individual_result.testsRun
|
|
953
|
+
|
|
954
|
+
# Extend the lists of failures, errors, skipped, expected failures, and unexpected successes
|
|
910
955
|
combined_result.failures.extend(individual_result.failures)
|
|
911
956
|
combined_result.errors.extend(individual_result.errors)
|
|
912
957
|
combined_result.skipped.extend(individual_result.skipped)
|
|
913
958
|
combined_result.expectedFailures.extend(individual_result.expectedFailures)
|
|
914
959
|
combined_result.unexpectedSuccesses.extend(individual_result.unexpectedSuccesses)
|
|
915
960
|
|
|
916
|
-
# Merge
|
|
961
|
+
# Merge custom test results if present (for enhanced result tracking)
|
|
917
962
|
if hasattr(individual_result, 'test_results'):
|
|
918
963
|
if not hasattr(combined_result, 'test_results'):
|
|
919
964
|
combined_result.test_results = []
|
|
@@ -1048,30 +1093,38 @@ class UnitTest(IUnitTest):
|
|
|
1048
1093
|
traceback_str: str
|
|
1049
1094
|
) -> Tuple[Optional[str], Optional[str]]:
|
|
1050
1095
|
"""
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1096
|
+
Extracts the file path and a cleaned traceback from a given traceback string.
|
|
1097
|
+
|
|
1098
|
+
This method analyzes a Python traceback string to determine the file path of the Python file
|
|
1099
|
+
where the error occurred (typically the last file in the traceback). It also removes lines
|
|
1100
|
+
related to framework internals and irrelevant noise, such as those containing 'unittest/',
|
|
1101
|
+
'lib/python', or 'site-packages', to produce a more concise and relevant traceback for reporting.
|
|
1054
1102
|
|
|
1055
1103
|
Parameters
|
|
1056
1104
|
----------
|
|
1057
1105
|
traceback_str : str
|
|
1058
|
-
The traceback string to process.
|
|
1106
|
+
The full traceback string to process.
|
|
1059
1107
|
|
|
1060
1108
|
Returns
|
|
1061
1109
|
-------
|
|
1062
1110
|
Tuple[Optional[str], Optional[str]]
|
|
1063
1111
|
A tuple containing:
|
|
1112
|
+
- file_path (Optional[str]): The path to the Python file where the error occurred, or None if not found.
|
|
1113
|
+
- clean_tb (Optional[str]): The cleaned traceback string, with framework internals and unrelated lines removed.
|
|
1064
1114
|
|
|
1065
1115
|
Notes
|
|
1066
1116
|
-----
|
|
1067
|
-
|
|
1068
|
-
|
|
1117
|
+
The cleaned traceback starts from the first occurrence of the test file path and omits lines
|
|
1118
|
+
that are part of the Python standard library or third-party packages, focusing on user code.
|
|
1069
1119
|
"""
|
|
1070
|
-
|
|
1120
|
+
|
|
1121
|
+
# Extract all Python file paths from the traceback string
|
|
1071
1122
|
file_matches = re.findall(r'File ["\'](.*?.py)["\']', traceback_str)
|
|
1123
|
+
|
|
1124
|
+
# Use the last file in the traceback as the most relevant (where the error occurred)
|
|
1072
1125
|
file_path = file_matches[-1] if file_matches else None
|
|
1073
1126
|
|
|
1074
|
-
#
|
|
1127
|
+
# Split the traceback into individual lines for processing
|
|
1075
1128
|
tb_lines = traceback_str.split('\n')
|
|
1076
1129
|
clean_lines = []
|
|
1077
1130
|
relevant_lines_started = False
|
|
@@ -1079,19 +1132,22 @@ class UnitTest(IUnitTest):
|
|
|
1079
1132
|
# Iterate through each line in the traceback
|
|
1080
1133
|
for line in tb_lines:
|
|
1081
1134
|
|
|
1082
|
-
# Skip framework
|
|
1135
|
+
# Skip lines that are part of framework internals or third-party libraries
|
|
1083
1136
|
if any(s in line for s in ['unittest/', 'lib/python', 'site-packages']):
|
|
1084
1137
|
continue
|
|
1085
1138
|
|
|
1086
|
-
# Start
|
|
1139
|
+
# Start including lines once the relevant file path is found
|
|
1087
1140
|
if file_path and file_path in line and not relevant_lines_started:
|
|
1088
1141
|
relevant_lines_started = True
|
|
1089
1142
|
|
|
1143
|
+
# If we've started collecting relevant lines, add them to the cleaned traceback
|
|
1090
1144
|
if relevant_lines_started:
|
|
1091
1145
|
clean_lines.append(line)
|
|
1092
1146
|
|
|
1147
|
+
# Join the cleaned lines into a single string; if none, return the original traceback
|
|
1093
1148
|
clean_tb = str('\n').join(clean_lines) if clean_lines else traceback_str
|
|
1094
1149
|
|
|
1150
|
+
# Return the file path and cleaned traceback
|
|
1095
1151
|
return file_path, clean_tb
|
|
1096
1152
|
|
|
1097
1153
|
def __generateSummary(
|
|
@@ -1100,56 +1156,51 @@ class UnitTest(IUnitTest):
|
|
|
1100
1156
|
execution_time: float
|
|
1101
1157
|
) -> Dict[str, Any]:
|
|
1102
1158
|
"""
|
|
1103
|
-
|
|
1159
|
+
Generates a comprehensive summary of the test suite execution.
|
|
1160
|
+
|
|
1161
|
+
This method processes the provided unittest.TestResult object and aggregates
|
|
1162
|
+
statistics such as the total number of tests, counts of passed, failed, errored,
|
|
1163
|
+
and skipped tests, as well as the overall execution time and success rate.
|
|
1164
|
+
It also collects detailed information for each individual test, including
|
|
1165
|
+
identifiers, class and method names, status, execution time, error messages,
|
|
1166
|
+
tracebacks, file paths, and docstrings.
|
|
1167
|
+
|
|
1168
|
+
If result persistence is enabled, the summary is saved using the configured
|
|
1169
|
+
persistence driver (e.g., SQLite or JSON). If web reporting is enabled, a
|
|
1170
|
+
web report is generated and linked.
|
|
1104
1171
|
|
|
1105
1172
|
Parameters
|
|
1106
1173
|
----------
|
|
1107
1174
|
result : unittest.TestResult
|
|
1108
1175
|
The result object containing details of the test execution.
|
|
1109
1176
|
execution_time : float
|
|
1110
|
-
The total execution time of the test suite in
|
|
1177
|
+
The total execution time of the test suite in milliseconds.
|
|
1111
1178
|
|
|
1112
1179
|
Returns
|
|
1113
1180
|
-------
|
|
1114
1181
|
Dict[str, Any]
|
|
1115
|
-
A dictionary containing
|
|
1116
|
-
total_tests :
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
id : str
|
|
1133
|
-
The unique identifier of the test.
|
|
1134
|
-
class : str
|
|
1135
|
-
The class name of the test.
|
|
1136
|
-
method : str
|
|
1137
|
-
The method name of the test.
|
|
1138
|
-
status : str
|
|
1139
|
-
The status of the test (e.g., "PASSED", "FAILED").
|
|
1140
|
-
execution_time : float
|
|
1141
|
-
The execution time of the test in seconds.
|
|
1142
|
-
error_message : str
|
|
1143
|
-
The error message if the test failed or errored.
|
|
1144
|
-
traceback : str
|
|
1145
|
-
The traceback information if the test failed or errored.
|
|
1146
|
-
file_path : str
|
|
1147
|
-
The file path of the test.
|
|
1148
|
-
doc_string : str
|
|
1149
|
-
The docstring of the test method, if available.
|
|
1182
|
+
A dictionary containing:
|
|
1183
|
+
- total_tests (int): Total number of tests executed.
|
|
1184
|
+
- passed (int): Number of tests that passed.
|
|
1185
|
+
- failed (int): Number of tests that failed.
|
|
1186
|
+
- errors (int): Number of tests that encountered errors.
|
|
1187
|
+
- skipped (int): Number of tests that were skipped.
|
|
1188
|
+
- total_time (float): Total execution time in milliseconds.
|
|
1189
|
+
- success_rate (float): Percentage of tests that passed.
|
|
1190
|
+
- test_details (List[Dict[str, Any]]): List of dictionaries with details for each test,
|
|
1191
|
+
including id, class, method, status, execution_time, error_message, traceback,
|
|
1192
|
+
file_path, and doc_string.
|
|
1193
|
+
- timestamp (str): ISO-formatted timestamp of when the summary was generated.
|
|
1194
|
+
|
|
1195
|
+
Side Effects
|
|
1196
|
+
------------
|
|
1197
|
+
- If persistence is enabled, the summary is persisted to storage.
|
|
1198
|
+
- If web reporting is enabled, a web report is generated.
|
|
1150
1199
|
"""
|
|
1200
|
+
|
|
1151
1201
|
test_details = []
|
|
1152
1202
|
|
|
1203
|
+
# Collect detailed information for each test result
|
|
1153
1204
|
for test_result in result.test_results:
|
|
1154
1205
|
rst: TestResult = test_result
|
|
1155
1206
|
test_details.append({
|
|
@@ -1164,10 +1215,12 @@ class UnitTest(IUnitTest):
|
|
|
1164
1215
|
'doc_string': rst.doc_string
|
|
1165
1216
|
})
|
|
1166
1217
|
|
|
1218
|
+
# Calculate the number of passed tests
|
|
1167
1219
|
passed = result.testsRun - len(result.failures) - len(result.errors) - len(result.skipped)
|
|
1220
|
+
# Calculate the success rate as a percentage
|
|
1168
1221
|
success_rate = (passed / result.testsRun * 100) if result.testsRun > 0 else 100.0
|
|
1169
1222
|
|
|
1170
|
-
#
|
|
1223
|
+
# Build the summary dictionary
|
|
1171
1224
|
self.__result = {
|
|
1172
1225
|
"total_tests": result.testsRun,
|
|
1173
1226
|
"passed": passed,
|
|
@@ -1180,15 +1233,15 @@ class UnitTest(IUnitTest):
|
|
|
1180
1233
|
"timestamp": datetime.now().isoformat()
|
|
1181
1234
|
}
|
|
1182
1235
|
|
|
1183
|
-
#
|
|
1184
|
-
if self.
|
|
1236
|
+
# Persist the summary if persistence is enabled
|
|
1237
|
+
if self.__persistent:
|
|
1185
1238
|
self.__handlePersistResults(self.__result)
|
|
1186
1239
|
|
|
1187
|
-
#
|
|
1188
|
-
if self.
|
|
1240
|
+
# Generate a web report if web reporting is enabled
|
|
1241
|
+
if self.__web_report:
|
|
1189
1242
|
self.__handleWebReport(self.__result)
|
|
1190
1243
|
|
|
1191
|
-
# Return the summary
|
|
1244
|
+
# Return the summary dictionary
|
|
1192
1245
|
return self.__result
|
|
1193
1246
|
|
|
1194
1247
|
def __handleWebReport(
|
|
@@ -1196,102 +1249,103 @@ class UnitTest(IUnitTest):
|
|
|
1196
1249
|
summary: Dict[str, Any]
|
|
1197
1250
|
) -> None:
|
|
1198
1251
|
"""
|
|
1199
|
-
|
|
1252
|
+
Generate a web-based report for the provided test results summary.
|
|
1253
|
+
|
|
1254
|
+
This method creates a web report for the test execution summary using the `TestingResultRender` class.
|
|
1255
|
+
It determines the appropriate storage path for the report, configures persistence options based on the
|
|
1256
|
+
current settings, and invokes the rendering process. After generating the report, it prints a link to
|
|
1257
|
+
the web report using the configured printer.
|
|
1200
1258
|
|
|
1201
1259
|
Parameters
|
|
1202
1260
|
----------
|
|
1203
1261
|
summary : dict
|
|
1204
|
-
The summary of test results
|
|
1262
|
+
The summary of test results for which the web report will be generated.
|
|
1205
1263
|
|
|
1206
1264
|
Returns
|
|
1207
1265
|
-------
|
|
1208
|
-
|
|
1209
|
-
|
|
1266
|
+
None
|
|
1267
|
+
This method does not return any value. The generated web report is rendered and a link to it is printed
|
|
1268
|
+
to the console via the printer.
|
|
1210
1269
|
|
|
1211
1270
|
Notes
|
|
1212
1271
|
-----
|
|
1213
|
-
-
|
|
1214
|
-
-
|
|
1215
|
-
-
|
|
1216
|
-
-
|
|
1272
|
+
- The storage path for the report is determined by `self.__base_path`.
|
|
1273
|
+
- If result persistence is enabled and the driver is set to 'sqlite', the report is marked as persistent.
|
|
1274
|
+
- The web report is generated using the `TestingResultRender` class.
|
|
1275
|
+
- The method prints the link to the generated web report using the printer.
|
|
1217
1276
|
"""
|
|
1218
1277
|
|
|
1219
|
-
#
|
|
1220
|
-
project = os.path.basename(os.getcwd())
|
|
1221
|
-
storage_path = os.path.abspath(os.path.join(os.getcwd(), self.base_path))
|
|
1222
|
-
|
|
1223
|
-
# Only use storage_path if project is recognized
|
|
1224
|
-
if project not in ['framework', 'orionis']:
|
|
1225
|
-
storage_path = None
|
|
1226
|
-
|
|
1227
|
-
# Create the TestingResultRender instance with the storage path and summary
|
|
1278
|
+
# Create the TestingResultRender instance with the storage path and summary.
|
|
1228
1279
|
render = TestingResultRender(
|
|
1229
|
-
storage_path=
|
|
1280
|
+
storage_path=self.__storage,
|
|
1230
1281
|
result=summary,
|
|
1231
|
-
persist=self.
|
|
1282
|
+
persist=self.__persistent and self.__persistent_driver == 'sqlite'
|
|
1232
1283
|
)
|
|
1233
1284
|
|
|
1234
|
-
# Render the report and print the
|
|
1235
|
-
self.
|
|
1285
|
+
# Render the web report and print the link using the printer.
|
|
1286
|
+
self.__printer.linkWebReport(render.render())
|
|
1236
1287
|
|
|
1237
1288
|
def __handlePersistResults(
|
|
1238
1289
|
self,
|
|
1239
1290
|
summary: Dict[str, Any]
|
|
1240
1291
|
) -> None:
|
|
1241
1292
|
"""
|
|
1242
|
-
Persist the test results summary using the configured
|
|
1293
|
+
Persist the test results summary using the configured persistence driver.
|
|
1294
|
+
|
|
1295
|
+
This method saves the provided test results summary to persistent storage, based on the
|
|
1296
|
+
current configuration. Supported drivers include SQLite (using the TestLogs class) and
|
|
1297
|
+
JSON file output. The storage location is determined by the configured base path.
|
|
1243
1298
|
|
|
1244
1299
|
Parameters
|
|
1245
1300
|
----------
|
|
1246
1301
|
summary : dict
|
|
1247
|
-
The summary of test results to persist.
|
|
1302
|
+
The summary of test results to persist. This should include all relevant test execution
|
|
1303
|
+
details, such as test counts, statuses, execution times, and individual test results.
|
|
1248
1304
|
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
- Written to a timestamped JSON file in the specified base path.
|
|
1305
|
+
Returns
|
|
1306
|
+
-------
|
|
1307
|
+
None
|
|
1308
|
+
This method does not return any value. It performs persistence as a side effect.
|
|
1254
1309
|
|
|
1255
1310
|
Raises
|
|
1256
1311
|
------
|
|
1257
1312
|
OSError
|
|
1258
|
-
If there is an error creating directories or writing files.
|
|
1259
|
-
|
|
1260
|
-
If database operations fail.
|
|
1313
|
+
If there is an error creating directories or writing files to disk.
|
|
1314
|
+
OrionisTestPersistenceError
|
|
1315
|
+
If database operations fail or any other error occurs during persistence.
|
|
1316
|
+
|
|
1317
|
+
Notes
|
|
1318
|
+
-----
|
|
1319
|
+
- If `self.__persistent_driver` is set to 'sqlite', the summary is stored in an SQLite database
|
|
1320
|
+
using the TestLogs class.
|
|
1321
|
+
- If `self.__persistent_driver` is set to 'json', the summary is written to a timestamped JSON
|
|
1322
|
+
file in the specified base path.
|
|
1323
|
+
- The method ensures that the target directory exists before writing files.
|
|
1324
|
+
- Any errors encountered during persistence are raised as exceptions for the caller to handle.
|
|
1261
1325
|
"""
|
|
1262
1326
|
|
|
1263
1327
|
try:
|
|
1264
1328
|
|
|
1265
|
-
|
|
1266
|
-
project = os.getcwd().split(os.sep)[-1]
|
|
1267
|
-
storage_path = None
|
|
1268
|
-
if project in ['framework', 'orionis']:
|
|
1269
|
-
storage_path = os.path.abspath(os.path.join(os.getcwd(), self.base_path))
|
|
1270
|
-
|
|
1271
|
-
if self.persistent_driver == 'sqlite':
|
|
1329
|
+
if self.__persistent_driver == PersistentDrivers.SQLITE.value:
|
|
1272
1330
|
|
|
1273
|
-
#
|
|
1274
|
-
history = TestLogs(
|
|
1275
|
-
storage_path=storage_path,
|
|
1276
|
-
db_name='tests.sqlite',
|
|
1277
|
-
table_name='reports'
|
|
1278
|
-
)
|
|
1331
|
+
# Persist results to SQLite database using TestLogs
|
|
1332
|
+
history = TestLogs(self.__storage)
|
|
1279
1333
|
|
|
1280
1334
|
# Insert the summary into the database
|
|
1281
1335
|
history.create(summary)
|
|
1282
1336
|
|
|
1283
|
-
elif self.
|
|
1337
|
+
elif self.__persistent_driver == PersistentDrivers.JSON.value:
|
|
1284
1338
|
|
|
1285
|
-
#
|
|
1286
|
-
os.makedirs(storage_path, exist_ok=True)
|
|
1287
|
-
|
|
1288
|
-
# Get the current timestamp for the log file name
|
|
1339
|
+
# Generate a timestamp for the log file name
|
|
1289
1340
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
1290
1341
|
|
|
1291
|
-
#
|
|
1292
|
-
log_path =
|
|
1342
|
+
# Construct the log file path with the timestamp
|
|
1343
|
+
log_path = Path(self.__storage) / f"{timestamp}_test_results.json"
|
|
1344
|
+
|
|
1345
|
+
# Ensure the directory exists
|
|
1346
|
+
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1293
1347
|
|
|
1294
|
-
# Write the summary to the JSON file
|
|
1348
|
+
# Write the summary dictionary to the JSON file
|
|
1295
1349
|
with open(log_path, 'w', encoding='utf-8') as log:
|
|
1296
1350
|
json.dump(summary, log, indent=4)
|
|
1297
1351
|
|
|
@@ -1302,7 +1356,7 @@ class UnitTest(IUnitTest):
|
|
|
1302
1356
|
|
|
1303
1357
|
except Exception as e:
|
|
1304
1358
|
|
|
1305
|
-
# Raise a
|
|
1359
|
+
# Raise a custom exception for any other issues during persistence
|
|
1306
1360
|
raise OrionisTestPersistenceError(f"Error persisting test results: {str(e)}")
|
|
1307
1361
|
|
|
1308
1362
|
def __filterTestsByName(
|
|
@@ -1411,7 +1465,7 @@ class UnitTest(IUnitTest):
|
|
|
1411
1465
|
List[str]
|
|
1412
1466
|
List of test names (unique identifiers) from the test suite.
|
|
1413
1467
|
"""
|
|
1414
|
-
return [test.id() for test in self.__flattenTestSuite(self.
|
|
1468
|
+
return [test.id() for test in self.__flattenTestSuite(self.__suite)]
|
|
1415
1469
|
|
|
1416
1470
|
def getTestCount(
|
|
1417
1471
|
self
|
|
@@ -1424,7 +1478,7 @@ class UnitTest(IUnitTest):
|
|
|
1424
1478
|
int
|
|
1425
1479
|
The total number of individual test cases in the suite.
|
|
1426
1480
|
"""
|
|
1427
|
-
return len(list(self.__flattenTestSuite(self.
|
|
1481
|
+
return len(list(self.__flattenTestSuite(self.__suite)))
|
|
1428
1482
|
|
|
1429
1483
|
def clearTests(
|
|
1430
1484
|
self
|
|
@@ -1434,7 +1488,7 @@ class UnitTest(IUnitTest):
|
|
|
1434
1488
|
|
|
1435
1489
|
Resets the internal test suite to an empty `unittest.TestSuite`, removing any previously added tests.
|
|
1436
1490
|
"""
|
|
1437
|
-
self.
|
|
1491
|
+
self.__suite = unittest.TestSuite()
|
|
1438
1492
|
|
|
1439
1493
|
def getResult(
|
|
1440
1494
|
self
|
|
@@ -1469,7 +1523,7 @@ class UnitTest(IUnitTest):
|
|
|
1469
1523
|
Prints the contents of the output buffer to the console.
|
|
1470
1524
|
This method retrieves the output buffer and prints its contents using the rich console.
|
|
1471
1525
|
"""
|
|
1472
|
-
self.
|
|
1526
|
+
self.__printer.print(self.__output_buffer)
|
|
1473
1527
|
|
|
1474
1528
|
def getErrorBuffer(
|
|
1475
1529
|
self
|
|
@@ -1491,4 +1545,4 @@ class UnitTest(IUnitTest):
|
|
|
1491
1545
|
Prints the contents of the error buffer to the console.
|
|
1492
1546
|
This method retrieves the error buffer and prints its contents using the rich console.
|
|
1493
1547
|
"""
|
|
1494
|
-
self.
|
|
1548
|
+
self.__printer.print(self.__error_buffer)
|