orionis 0.371.0__py3-none-any.whl → 0.373.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/__init__.py +9 -0
- orionis/container/resolver/resolver.py +3 -3
- orionis/foundation/application.py +66 -31
- orionis/foundation/contracts/application.py +1 -1
- orionis/metadata/framework.py +1 -1
- orionis/services/introspection/dependencies/entities/callable_dependencies.py +6 -6
- orionis/services/introspection/dependencies/entities/class_dependencies.py +7 -7
- orionis/services/introspection/dependencies/entities/{resolved_dependencies.py → known_dependencies.py} +1 -1
- orionis/services/introspection/dependencies/entities/method_dependencies.py +6 -6
- orionis/services/introspection/dependencies/reflection.py +4 -4
- orionis/test/arguments/parser.py +197 -0
- orionis/test/cases/asynchronous.py +145 -0
- orionis/test/cases/synchronous.py +135 -0
- orionis/test/contracts/kernel.py +99 -0
- orionis/test/contracts/logs.py +125 -0
- orionis/test/contracts/parser.py +37 -0
- orionis/test/contracts/render.py +30 -0
- orionis/test/contracts/unit_test.py +21 -0
- orionis/test/core/unit_test.py +104 -23
- orionis/test/entities/arguments.py +38 -0
- orionis/test/kernel.py +276 -0
- orionis/test/output/dumper.py +3 -4
- orionis/test/output/printer.py +22 -0
- orionis/test/{logs → record}/history.py +25 -9
- orionis/test/records/__init__.py +0 -0
- orionis/test/records/logs.py +385 -0
- orionis/test/view/render.py +8 -5
- {orionis-0.371.0.dist-info → orionis-0.373.0.dist-info}/METADATA +1 -1
- {orionis-0.371.0.dist-info → orionis-0.373.0.dist-info}/RECORD +93 -88
- tests/example/test_example.py +4 -3
- tests/foundation/config/app/test_foundation_config_app.py +2 -2
- tests/foundation/config/auth/test_foundation_config_auth.py +2 -2
- tests/foundation/config/cache/test_foundation_config_cache.py +2 -2
- tests/foundation/config/cache/test_foundation_config_cache_file.py +2 -2
- tests/foundation/config/cache/test_foundation_config_cache_stores.py +2 -2
- tests/foundation/config/cors/test_foundation_config_cors.py +2 -2
- tests/foundation/config/database/test_foundation_config_database.py +2 -2
- tests/foundation/config/database/test_foundation_config_database_connections.py +2 -2
- tests/foundation/config/database/test_foundation_config_database_mysql.py +2 -2
- tests/foundation/config/database/test_foundation_config_database_oracle.py +2 -2
- tests/foundation/config/database/test_foundation_config_database_pgsql.py +2 -2
- tests/foundation/config/database/test_foundation_config_database_sqlite.py +2 -2
- tests/foundation/config/filesystems/test_foundation_config_filesystems.py +2 -2
- tests/foundation/config/filesystems/test_foundation_config_filesystems_aws.py +2 -2
- tests/foundation/config/filesystems/test_foundation_config_filesystems_disks.py +2 -2
- tests/foundation/config/filesystems/test_foundation_config_filesystems_local.py +2 -2
- tests/foundation/config/filesystems/test_foundation_config_filesystems_public.py +2 -2
- tests/foundation/config/logging/test_foundation_config_logging.py +2 -2
- tests/foundation/config/logging/test_foundation_config_logging_channels.py +2 -2
- tests/foundation/config/logging/test_foundation_config_logging_chunked.py +2 -2
- tests/foundation/config/logging/test_foundation_config_logging_daily.py +2 -2
- tests/foundation/config/logging/test_foundation_config_logging_hourly.py +2 -2
- tests/foundation/config/logging/test_foundation_config_logging_monthly.py +2 -2
- tests/foundation/config/logging/test_foundation_config_logging_stack.py +2 -2
- tests/foundation/config/logging/test_foundation_config_logging_weekly.py +2 -2
- tests/foundation/config/mail/test_foundation_config_mail.py +2 -2
- tests/foundation/config/mail/test_foundation_config_mail_file.py +2 -2
- tests/foundation/config/mail/test_foundation_config_mail_mailers.py +2 -2
- tests/foundation/config/mail/test_foundation_config_mail_smtp.py +2 -2
- tests/foundation/config/queue/test_foundation_config_queue.py +2 -5
- tests/foundation/config/queue/test_foundation_config_queue_brokers.py +2 -2
- tests/foundation/config/queue/test_foundation_config_queue_database.py +3 -2
- tests/foundation/config/root/test_foundation_config_root_paths.py +3 -3
- tests/foundation/config/session/test_foundation_config_session.py +4 -3
- tests/foundation/config/startup/test_foundation_config_startup.py +4 -3
- tests/foundation/config/testing/test_foundation_config_testing.py +3 -3
- tests/foundation/exceptions/test_foundation_config_exceptions.py +3 -3
- tests/metadata/test_metadata_framework.py +2 -2
- tests/metadata/test_metadata_package.py +3 -2
- tests/services/asynchrony/test_services_asynchrony_coroutine.py +2 -2
- tests/services/environment/test_services_environment.py +2 -2
- tests/services/inspection/dependencies/test_reflect_dependencies.py +22 -22
- tests/services/inspection/reflection/test_reflection_abstract.py +2 -2
- tests/services/inspection/reflection/test_reflection_callable.py +2 -2
- tests/services/inspection/reflection/test_reflection_concrete.py +2 -2
- tests/services/inspection/reflection/test_reflection_instance.py +2 -2
- tests/services/inspection/reflection/test_reflection_module.py +2 -2
- tests/services/inspection/test_reflection.py +2 -2
- tests/services/path/test_services_resolver.py +2 -2
- tests/services/system/test_services_system_imports.py +2 -2
- tests/services/system/test_services_system_workers.py +3 -2
- tests/support/parsers/test_services_parser_exceptions.py +2 -2
- tests/support/patterns/singleton/test_patterns_singleton.py +2 -2
- tests/support/standard/test_services_std.py +2 -2
- tests/support/wrapper/test_services_wrapper_docdict.py +2 -2
- tests/testing/test_testing_result.py +4 -6
- tests/testing/test_testing_unit.py +9 -10
- orionis/test/cases/test_async.py +0 -55
- orionis/test/cases/test_case.py +0 -42
- orionis/test/cases/test_sync.py +0 -33
- orionis/test/contracts/history.py +0 -54
- orionis/test/test_suite.py +0 -142
- orionis/unittesting.py +0 -64
- /orionis/test/{logs → arguments}/__init__.py +0 -0
- /orionis/test/entities/{test_result.py → result.py} +0 -0
- {orionis-0.371.0.dist-info → orionis-0.373.0.dist-info}/WHEEL +0 -0
- {orionis-0.371.0.dist-info → orionis-0.373.0.dist-info}/licenses/LICENCE +0 -0
- {orionis-0.371.0.dist-info → orionis-0.373.0.dist-info}/top_level.txt +0 -0
- {orionis-0.371.0.dist-info → orionis-0.373.0.dist-info}/zip-safe +0 -0
orionis/test/core/unit_test.py
CHANGED
|
@@ -10,9 +10,10 @@ from contextlib import redirect_stdout, redirect_stderr
|
|
|
10
10
|
from datetime import datetime
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import Any, Dict, List, Optional, Tuple
|
|
13
|
+
from orionis.foundation.contracts.application import IApplication
|
|
13
14
|
from orionis.services.introspection.instances.reflection import ReflectionInstance
|
|
14
15
|
from orionis.services.system.workers import Workers
|
|
15
|
-
from orionis.test.entities.
|
|
16
|
+
from orionis.test.entities.result import TestResult
|
|
16
17
|
from orionis.test.enums import (
|
|
17
18
|
ExecutionMode,
|
|
18
19
|
TestStatus
|
|
@@ -22,7 +23,7 @@ from orionis.test.exceptions import (
|
|
|
22
23
|
OrionisTestPersistenceError,
|
|
23
24
|
OrionisTestValueError
|
|
24
25
|
)
|
|
25
|
-
from orionis.test.logs
|
|
26
|
+
from orionis.test.records.logs import TestLogs
|
|
26
27
|
from orionis.test.contracts.unit_test import IUnitTest
|
|
27
28
|
from orionis.test.output.printer import TestPrinter
|
|
28
29
|
from orionis.test.view.render import TestingResultRender
|
|
@@ -42,7 +43,9 @@ class UnitTest(IUnitTest):
|
|
|
42
43
|
This is an especially suitable choice for those seeking greater robustness, traceability, and visibility in their automated testing processes, offering advantages often missing from other alternatives.
|
|
43
44
|
"""
|
|
44
45
|
|
|
45
|
-
def __init__(
|
|
46
|
+
def __init__(
|
|
47
|
+
self
|
|
48
|
+
) -> None:
|
|
46
49
|
"""
|
|
47
50
|
Initializes the test suite configuration and supporting components.
|
|
48
51
|
|
|
@@ -100,28 +103,30 @@ class UnitTest(IUnitTest):
|
|
|
100
103
|
Result of the test execution.
|
|
101
104
|
"""
|
|
102
105
|
|
|
106
|
+
# Value for application instance
|
|
107
|
+
self.app: Optional[IApplication] = None
|
|
108
|
+
|
|
103
109
|
# Values for configuration
|
|
104
|
-
self.verbosity: int
|
|
105
|
-
self.execution_mode: str
|
|
106
|
-
self.max_workers: int
|
|
107
|
-
self.fail_fast: bool
|
|
108
|
-
self.print_result: bool
|
|
109
|
-
self.throw_exception: bool
|
|
110
|
-
self.persistent: bool
|
|
111
|
-
self.persistent_driver: str
|
|
112
|
-
self.web_report: bool
|
|
110
|
+
self.verbosity: Optional[int] = None
|
|
111
|
+
self.execution_mode: Optional[str] = None
|
|
112
|
+
self.max_workers: Optional[int] = None
|
|
113
|
+
self.fail_fast: Optional[bool] = None
|
|
114
|
+
self.print_result: Optional[bool] = None
|
|
115
|
+
self.throw_exception: Optional[bool] = None
|
|
116
|
+
self.persistent: Optional[bool] = None
|
|
117
|
+
self.persistent_driver: Optional[str] = None
|
|
118
|
+
self.web_report: Optional[bool] = None
|
|
113
119
|
|
|
114
120
|
# Values for discovering tests in folders
|
|
115
|
-
self.full_path: Optional[str]
|
|
116
|
-
self.folder_path: str
|
|
117
|
-
self.base_path: str
|
|
118
|
-
self.pattern: str
|
|
119
|
-
self.test_name_pattern: Optional[str]
|
|
120
|
-
self.tags: Optional[List[str]]
|
|
121
|
+
self.full_path: Optional[str] = None
|
|
122
|
+
self.folder_path: Optional[str] = None
|
|
123
|
+
self.base_path: Optional[str] = None
|
|
124
|
+
self.pattern: Optional[str] = None
|
|
125
|
+
self.test_name_pattern: Optional[str] = None
|
|
126
|
+
self.tags: Optional[List[str]] = None
|
|
121
127
|
|
|
122
128
|
# Values for discovering tests in modules
|
|
123
|
-
self.module_name: str
|
|
124
|
-
self.test_name_pattern: Optional[str]
|
|
129
|
+
self.module_name: Optional[str] = None
|
|
125
130
|
|
|
126
131
|
# Initialize the test loader and suite
|
|
127
132
|
self.loader = unittest.TestLoader()
|
|
@@ -138,6 +143,33 @@ class UnitTest(IUnitTest):
|
|
|
138
143
|
# Result of the test execution
|
|
139
144
|
self.__result = None
|
|
140
145
|
|
|
146
|
+
def setApplication(
|
|
147
|
+
self,
|
|
148
|
+
app: 'IApplication'
|
|
149
|
+
) -> 'UnitTest':
|
|
150
|
+
"""
|
|
151
|
+
Set the application instance for the UnitTest.
|
|
152
|
+
This method allows the UnitTest to access the application instance, which is necessary for resolving dependencies and executing tests.
|
|
153
|
+
|
|
154
|
+
Parameters
|
|
155
|
+
----------
|
|
156
|
+
app : IApplication
|
|
157
|
+
The application instance to be set for the UnitTest.
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
UnitTest
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
# Validate the provided application instance
|
|
165
|
+
if not isinstance(app, IApplication):
|
|
166
|
+
raise OrionisTestValueError(
|
|
167
|
+
f"The provided application is not a valid instance of IApplication: {type(app).__name__}."
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# Set the application instance
|
|
171
|
+
self.app = app
|
|
172
|
+
|
|
141
173
|
def configure(
|
|
142
174
|
self,
|
|
143
175
|
*,
|
|
@@ -657,6 +689,55 @@ class UnitTest(IUnitTest):
|
|
|
657
689
|
# Flatten the suite to avoid duplicate tests
|
|
658
690
|
flattened_suite = unittest.TestSuite(self.__flattenTestSuite(self.suite))
|
|
659
691
|
|
|
692
|
+
# Create a new test suite with tests that have their dependencies resolved
|
|
693
|
+
flattened_suite = unittest.TestSuite()
|
|
694
|
+
app = self.app
|
|
695
|
+
|
|
696
|
+
# Iterate through all test cases
|
|
697
|
+
for test_case in self.__flattenTestSuite(self.suite):
|
|
698
|
+
# Get the test method name
|
|
699
|
+
method_name = getattr(test_case, '_testMethodName', None)
|
|
700
|
+
|
|
701
|
+
# Skip if we can't identify the test method
|
|
702
|
+
if not method_name:
|
|
703
|
+
flattened_suite.addTest(test_case)
|
|
704
|
+
continue
|
|
705
|
+
|
|
706
|
+
# Extract dependencies for the test method
|
|
707
|
+
rsv = ReflectionInstance(test_case).getMethodDependencies(method_name)
|
|
708
|
+
|
|
709
|
+
# If no dependencies to resolve, just add the original test
|
|
710
|
+
if not rsv.resolved and not rsv.unresolved:
|
|
711
|
+
flattened_suite.addTest(test_case)
|
|
712
|
+
continue
|
|
713
|
+
|
|
714
|
+
# Create a specialized test case with resolved dependencies
|
|
715
|
+
test_class = test_case.__class__
|
|
716
|
+
original_method = getattr(test_class, method_name)
|
|
717
|
+
|
|
718
|
+
# Create a dict of resolved dependencies
|
|
719
|
+
args_ = {}
|
|
720
|
+
for k, v in rsv.resolved.items():
|
|
721
|
+
from orionis.services.introspection.dependencies.entities.known_dependencies import KnownDependency
|
|
722
|
+
if isinstance(v, KnownDependency):
|
|
723
|
+
args_[k] = app.make(v.type)
|
|
724
|
+
|
|
725
|
+
# Create a wrapper method that injects dependencies
|
|
726
|
+
def create_test_wrapper(original_test, resolved_args, unresolved_args):
|
|
727
|
+
def wrapper(self_instance):
|
|
728
|
+
args_list = list(resolved_args.values())
|
|
729
|
+
args_list.extend(unresolved_args)
|
|
730
|
+
return original_test(self_instance, *args_list)
|
|
731
|
+
return wrapper
|
|
732
|
+
|
|
733
|
+
# Create a new test case with the wrapped method
|
|
734
|
+
setattr(test_class, f"_wrapped_{method_name}", create_test_wrapper(original_method, args_, rsv.unresolved))
|
|
735
|
+
setattr(test_case, '_testMethodName', f"_wrapped_{method_name}")
|
|
736
|
+
|
|
737
|
+
# Add the modified test case to the suite
|
|
738
|
+
flattened_suite.addTest(test_case)
|
|
739
|
+
# sys.exit(0)
|
|
740
|
+
|
|
660
741
|
# Create a custom result class to capture detailed test results
|
|
661
742
|
with redirect_stdout(output_buffer), redirect_stderr(error_buffer):
|
|
662
743
|
runner = unittest.TextTestRunner(
|
|
@@ -1108,7 +1189,7 @@ class UnitTest(IUnitTest):
|
|
|
1108
1189
|
Notes
|
|
1109
1190
|
-----
|
|
1110
1191
|
Depending on the value of `self.persistent_driver`, the summary is either:
|
|
1111
|
-
- Stored in an SQLite database (using the
|
|
1192
|
+
- Stored in an SQLite database (using the TestLogs class), or
|
|
1112
1193
|
- Written to a timestamped JSON file in the specified base path.
|
|
1113
1194
|
|
|
1114
1195
|
Raises
|
|
@@ -1129,8 +1210,8 @@ class UnitTest(IUnitTest):
|
|
|
1129
1210
|
|
|
1130
1211
|
if self.persistent_driver == 'sqlite':
|
|
1131
1212
|
|
|
1132
|
-
# Initialize the
|
|
1133
|
-
history =
|
|
1213
|
+
# Initialize the TestLogs class for database operations
|
|
1214
|
+
history = TestLogs(
|
|
1134
1215
|
storage_path=storage_path,
|
|
1135
1216
|
db_name='tests.sqlite',
|
|
1136
1217
|
table_name='reports'
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Literal, Optional
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class TestArguments:
|
|
6
|
+
"""
|
|
7
|
+
Parameters for Orionis test execution.
|
|
8
|
+
|
|
9
|
+
Parameters
|
|
10
|
+
----------
|
|
11
|
+
verbosity : int, default=2
|
|
12
|
+
Level of test output verbosity.
|
|
13
|
+
mode : {'parallel', 'sequential'}, default='parallel'
|
|
14
|
+
Test execution mode. Whether to run tests in parallel or sequentially.
|
|
15
|
+
fail_fast : bool, default=False
|
|
16
|
+
If True, stop execution upon first test failure.
|
|
17
|
+
print_result : bool, default=True
|
|
18
|
+
If True, print test results to the console.
|
|
19
|
+
throw_exception : bool, default=False
|
|
20
|
+
If True, raise exceptions during test execution.
|
|
21
|
+
persistent : bool, default=False
|
|
22
|
+
If True, maintain state between test runs.
|
|
23
|
+
persistent_driver : str, optional
|
|
24
|
+
Driver to use for persistent test execution.
|
|
25
|
+
web_report : bool, default=False
|
|
26
|
+
If True, generate a web-based test report.
|
|
27
|
+
print_output_buffer : bool, default=False
|
|
28
|
+
If True, print the test output buffer.
|
|
29
|
+
"""
|
|
30
|
+
verbosity: int = 2
|
|
31
|
+
mode: Literal['parallel', 'sequential'] = 'parallel'
|
|
32
|
+
fail_fast: bool = False
|
|
33
|
+
print_result: bool = True
|
|
34
|
+
throw_exception: bool = False
|
|
35
|
+
persistent: bool = False
|
|
36
|
+
persistent_driver: Optional[str] = None
|
|
37
|
+
web_report: bool = False
|
|
38
|
+
print_output_buffer: bool = False
|
orionis/test/kernel.py
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import gc
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
from os import walk
|
|
5
|
+
from orionis.foundation.config.testing.entities.testing import Testing as Configuration
|
|
6
|
+
from orionis.foundation.contracts.application import IApplication
|
|
7
|
+
from orionis.test.contracts.kernel import ITestKernel
|
|
8
|
+
from orionis.test.core.unit_test import UnitTest
|
|
9
|
+
from orionis.test.exceptions import OrionisTestConfigException
|
|
10
|
+
|
|
11
|
+
class TestKernel(ITestKernel):
|
|
12
|
+
"""
|
|
13
|
+
Core test kernel for the Orionis testing framework.
|
|
14
|
+
|
|
15
|
+
This class provides the main interface for discovering, configuring, and executing
|
|
16
|
+
test suites within the Orionis framework. It handles test configuration validation,
|
|
17
|
+
test discovery across multiple directories, and orchestrates the execution of
|
|
18
|
+
discovered tests.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
app : IApplication
|
|
23
|
+
The Orionis application instance that provides the testing context.
|
|
24
|
+
|
|
25
|
+
Attributes
|
|
26
|
+
----------
|
|
27
|
+
__app : IApplication
|
|
28
|
+
Private reference to the application instance.
|
|
29
|
+
__config : Configuration
|
|
30
|
+
Private reference to the testing configuration.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
app: IApplication
|
|
36
|
+
) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Initialize the Orionis test kernel.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
app : IApplication
|
|
43
|
+
The application instance that implements the IApplication interface.
|
|
44
|
+
This provides the context and services needed for test execution.
|
|
45
|
+
|
|
46
|
+
Raises
|
|
47
|
+
------
|
|
48
|
+
ValueError
|
|
49
|
+
If the provided app is None or not an instance of IApplication.
|
|
50
|
+
"""
|
|
51
|
+
# Validate application instance
|
|
52
|
+
if app is None or not isinstance(app, IApplication):
|
|
53
|
+
raise ValueError("The provided application is not a valid instance of IApplication.")
|
|
54
|
+
|
|
55
|
+
# Set the application instance
|
|
56
|
+
self.__app = app
|
|
57
|
+
|
|
58
|
+
def __checkConfiguration(
|
|
59
|
+
self,
|
|
60
|
+
config: Configuration = None,
|
|
61
|
+
**kwargs
|
|
62
|
+
) -> Configuration:
|
|
63
|
+
"""
|
|
64
|
+
Validate and initialize the testing configuration.
|
|
65
|
+
|
|
66
|
+
This method validates the provided configuration or creates a new one from
|
|
67
|
+
keyword arguments. It ensures that the configuration is properly set up
|
|
68
|
+
before test execution begins.
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
config : Configuration, optional
|
|
73
|
+
A pre-configured Testing configuration instance. If None, attempts to
|
|
74
|
+
create one from kwargs.
|
|
75
|
+
**kwargs : dict
|
|
76
|
+
Keyword arguments to create a Configuration instance if config is None.
|
|
77
|
+
Must match the Configuration class constructor parameters.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
bool
|
|
82
|
+
True if configuration validation succeeds.
|
|
83
|
+
|
|
84
|
+
Raises
|
|
85
|
+
------
|
|
86
|
+
OrionisTestConfigException
|
|
87
|
+
If the configuration is invalid or required fields are missing.
|
|
88
|
+
The exception message includes details about required fields and their types.
|
|
89
|
+
"""
|
|
90
|
+
# Check if config is None and kwargs are provided
|
|
91
|
+
if config is None:
|
|
92
|
+
try:
|
|
93
|
+
# Attempt to create a Configuration instance with provided keyword arguments
|
|
94
|
+
config = Configuration(**kwargs)
|
|
95
|
+
except TypeError:
|
|
96
|
+
# If a TypeError occurs, it indicates that the provided arguments do not match the Configuration class
|
|
97
|
+
required_fields = []
|
|
98
|
+
for field in Configuration().getFields():
|
|
99
|
+
required_fields.append(f"{field.get('name')} = (Type: {field.get('type')}, Default: {field.get('default')})")
|
|
100
|
+
|
|
101
|
+
# Raise an exception with a detailed message about the required fields
|
|
102
|
+
raise OrionisTestConfigException(f"The provided configuration is not valid. Please ensure it is an instance of the Configuration class or provide valid keyword arguments. \n{str('\n').join(required_fields)}]")
|
|
103
|
+
|
|
104
|
+
# Assign the configuration to the instance variable
|
|
105
|
+
return config or Configuration()
|
|
106
|
+
|
|
107
|
+
def handle(
|
|
108
|
+
self,
|
|
109
|
+
config: Configuration = None,
|
|
110
|
+
**kwargs
|
|
111
|
+
) -> UnitTest:
|
|
112
|
+
"""
|
|
113
|
+
Execute the complete test discovery and execution pipeline.
|
|
114
|
+
|
|
115
|
+
This is the main entry point for running tests. It validates the configuration,
|
|
116
|
+
discovers test files based on specified patterns and paths, configures the
|
|
117
|
+
test suite, and executes all discovered tests.
|
|
118
|
+
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
config : Configuration, optional
|
|
122
|
+
A pre-configured Testing configuration instance. If None, attempts to
|
|
123
|
+
create one from kwargs.
|
|
124
|
+
**kwargs : dict
|
|
125
|
+
Keyword arguments to create a Configuration instance if config is None.
|
|
126
|
+
Common parameters include:
|
|
127
|
+
- base_path : str, base directory for test discovery
|
|
128
|
+
- folder_path : str or list, specific folders to search
|
|
129
|
+
- pattern : str, file pattern for test discovery
|
|
130
|
+
- verbosity : int, output verbosity level
|
|
131
|
+
- execution_mode : str, test execution mode
|
|
132
|
+
- max_workers : int, maximum number of worker threads
|
|
133
|
+
- fail_fast : bool, stop on first failure
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
-------
|
|
137
|
+
UnitTest
|
|
138
|
+
The configured and executed test suite instance containing all results.
|
|
139
|
+
|
|
140
|
+
Raises
|
|
141
|
+
------
|
|
142
|
+
OrionisTestConfigException
|
|
143
|
+
If the configuration validation fails.
|
|
144
|
+
"""
|
|
145
|
+
# Validate and set configuration
|
|
146
|
+
config = self.__checkConfiguration(config, **kwargs)
|
|
147
|
+
|
|
148
|
+
# Initialize the test suite
|
|
149
|
+
tests = UnitTest()
|
|
150
|
+
|
|
151
|
+
# Assign the application instance to the test suite
|
|
152
|
+
tests.setApplication(self.__app)
|
|
153
|
+
|
|
154
|
+
# Configure the test suite with validated configuration values
|
|
155
|
+
tests.configure(
|
|
156
|
+
verbosity=config.verbosity,
|
|
157
|
+
execution_mode=config.execution_mode,
|
|
158
|
+
max_workers=config.max_workers,
|
|
159
|
+
fail_fast=config.fail_fast,
|
|
160
|
+
print_result=config.print_result,
|
|
161
|
+
throw_exception=config.throw_exception,
|
|
162
|
+
persistent=config.persistent,
|
|
163
|
+
persistent_driver=config.persistent_driver,
|
|
164
|
+
web_report=config.web_report
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Extract configuration values for test discovery
|
|
168
|
+
base_path = config.base_path
|
|
169
|
+
folder_path = config.folder_path
|
|
170
|
+
pattern = config.pattern
|
|
171
|
+
|
|
172
|
+
def list_matching_folders(custom_path: str, pattern: str):
|
|
173
|
+
"""
|
|
174
|
+
Discover folders containing files that match the specified pattern.
|
|
175
|
+
|
|
176
|
+
This helper function walks through the directory tree starting from
|
|
177
|
+
custom_path and identifies folders that contain files matching the
|
|
178
|
+
given pattern.
|
|
179
|
+
|
|
180
|
+
Parameters
|
|
181
|
+
----------
|
|
182
|
+
custom_path : str
|
|
183
|
+
The root path to start the search from.
|
|
184
|
+
pattern : str
|
|
185
|
+
The file pattern to match (supports wildcards * and ?).
|
|
186
|
+
|
|
187
|
+
Returns
|
|
188
|
+
-------
|
|
189
|
+
list of str
|
|
190
|
+
List of relative folder paths containing matching files.
|
|
191
|
+
"""
|
|
192
|
+
matched_folders = []
|
|
193
|
+
for root, _, files in walk(custom_path):
|
|
194
|
+
for file in files:
|
|
195
|
+
if re.fullmatch(pattern.replace('*', '.*').replace('?', '.'), file):
|
|
196
|
+
relative_path = root.replace(base_path, '').replace('\\', '/').lstrip('/')
|
|
197
|
+
if relative_path not in matched_folders:
|
|
198
|
+
matched_folders.append(relative_path)
|
|
199
|
+
return matched_folders
|
|
200
|
+
|
|
201
|
+
# Discover test folders based on configuration
|
|
202
|
+
discovered_folders = []
|
|
203
|
+
if folder_path == '*':
|
|
204
|
+
# Search all folders under base_path
|
|
205
|
+
discovered_folders.extend(list_matching_folders(base_path, pattern))
|
|
206
|
+
elif isinstance(folder_path, list):
|
|
207
|
+
# Search specific folders provided in the list
|
|
208
|
+
for custom_path in folder_path:
|
|
209
|
+
discovered_folders.extend(list_matching_folders(f"{base_path}/{custom_path}", pattern))
|
|
210
|
+
else:
|
|
211
|
+
# Search single specified folder
|
|
212
|
+
discovered_folders.extend(list_matching_folders(folder_path, pattern))
|
|
213
|
+
|
|
214
|
+
# Add discovered folders to the test suite for execution
|
|
215
|
+
for folder in discovered_folders:
|
|
216
|
+
tests.discoverTestsInFolder(
|
|
217
|
+
folder_path=folder,
|
|
218
|
+
base_path=base_path,
|
|
219
|
+
pattern=pattern,
|
|
220
|
+
test_name_pattern=config.test_name_pattern if config.test_name_pattern else None,
|
|
221
|
+
tags=config.tags if config.tags else None
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Execute the test suite and return the results
|
|
225
|
+
tests.run()
|
|
226
|
+
|
|
227
|
+
# Return the test suite instance containing all results
|
|
228
|
+
return tests
|
|
229
|
+
|
|
230
|
+
def exit(
|
|
231
|
+
self,
|
|
232
|
+
code: int = 0
|
|
233
|
+
) -> None:
|
|
234
|
+
"""
|
|
235
|
+
Terminate the test execution process and free associated resources.
|
|
236
|
+
|
|
237
|
+
This method performs a clean shutdown of the test kernel by explicitly
|
|
238
|
+
triggering garbage collection to release memory resources and then
|
|
239
|
+
terminating the process with the provided exit code. It ensures that any
|
|
240
|
+
remaining file handles, threads, or other resources are properly released.
|
|
241
|
+
|
|
242
|
+
Parameters
|
|
243
|
+
----------
|
|
244
|
+
code : int
|
|
245
|
+
The exit code to return to the operating system. Should be 0 for
|
|
246
|
+
successful execution or a non-zero value to indicate an error.
|
|
247
|
+
|
|
248
|
+
Returns
|
|
249
|
+
-------
|
|
250
|
+
None
|
|
251
|
+
This method does not return as it terminates the process.
|
|
252
|
+
|
|
253
|
+
Raises
|
|
254
|
+
------
|
|
255
|
+
ValueError
|
|
256
|
+
If the provided code is not a valid integer or outside the allowed range.
|
|
257
|
+
|
|
258
|
+
Notes
|
|
259
|
+
-----
|
|
260
|
+
Using os._exit() bypasses normal Python cleanup mechanisms and
|
|
261
|
+
immediately terminates the process. This can be necessary when
|
|
262
|
+
normal sys.exit() would be caught by exception handlers.
|
|
263
|
+
"""
|
|
264
|
+
# Validate the exit code
|
|
265
|
+
if not isinstance(code, int):
|
|
266
|
+
raise ValueError("Exit code must be an integer")
|
|
267
|
+
|
|
268
|
+
# Check if the code is within the allowed range (typically 0-255)
|
|
269
|
+
if code < 0 or code > 255:
|
|
270
|
+
raise ValueError("Exit code must be between 0 and 255")
|
|
271
|
+
|
|
272
|
+
# Force garbage collection to free memory
|
|
273
|
+
gc.collect()
|
|
274
|
+
|
|
275
|
+
# Terminate the process immediately without running cleanup handlers
|
|
276
|
+
os._exit(code)
|
orionis/test/output/dumper.py
CHANGED
|
@@ -45,10 +45,9 @@ class TestDumper(ITestDumper):
|
|
|
45
45
|
try:
|
|
46
46
|
if value is None:
|
|
47
47
|
return False
|
|
48
|
-
from orionis.test.cases.
|
|
49
|
-
from orionis.test.cases.
|
|
50
|
-
|
|
51
|
-
return isinstance(value, (AsyncTestCase, TestCase, SyncTestCase))
|
|
48
|
+
from orionis.test.cases.asynchronous import AsyncTestCase
|
|
49
|
+
from orionis.test.cases.synchronous import SyncTestCase
|
|
50
|
+
return isinstance(value, (AsyncTestCase, SyncTestCase))
|
|
52
51
|
except Exception:
|
|
53
52
|
return False
|
|
54
53
|
|
orionis/test/output/printer.py
CHANGED
|
@@ -16,6 +16,28 @@ class TestPrinter(ITestPrinter):
|
|
|
16
16
|
def __init__(
|
|
17
17
|
self
|
|
18
18
|
) -> None:
|
|
19
|
+
"""
|
|
20
|
+
Initialize the test output printer.
|
|
21
|
+
|
|
22
|
+
This initializes a Rich Console for output rendering, setting up panel
|
|
23
|
+
parameters and debug keywords for test result display.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
None
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
None
|
|
32
|
+
|
|
33
|
+
Notes
|
|
34
|
+
-----
|
|
35
|
+
Sets up the following attributes:
|
|
36
|
+
- __rich_console: Rich Console instance for formatted terminal output
|
|
37
|
+
- __panel_title: Title string for the output panel
|
|
38
|
+
- __panel_width: Width of the output panel (75% of console width)
|
|
39
|
+
- __debbug_keywords: List of keywords for identifying debug calls
|
|
40
|
+
"""
|
|
19
41
|
self.__rich_console = Console()
|
|
20
42
|
self.__panel_title: str = "🧪 Orionis Framework - Component Test Suite"
|
|
21
43
|
self.__panel_width: int = int(self.__rich_console.width * 0.75)
|
|
@@ -5,9 +5,9 @@ from pathlib import Path
|
|
|
5
5
|
from typing import Dict, List, Optional, Tuple
|
|
6
6
|
from orionis.services.environment.env import Env
|
|
7
7
|
from orionis.test.exceptions import OrionisTestPersistenceError, OrionisTestValueError
|
|
8
|
-
from orionis.test.contracts.
|
|
8
|
+
from orionis.test.contracts.logs import ITestLogs
|
|
9
9
|
|
|
10
|
-
class
|
|
10
|
+
class TestLogs(ITestLogs):
|
|
11
11
|
|
|
12
12
|
def __init__(
|
|
13
13
|
self,
|
|
@@ -71,7 +71,9 @@ class TestHistory(ITestHistory):
|
|
|
71
71
|
# Create a connection to the database, initially set to None
|
|
72
72
|
self._conn: Optional[sqlite3.Connection] = None
|
|
73
73
|
|
|
74
|
-
def __connect(
|
|
74
|
+
def __connect(
|
|
75
|
+
self
|
|
76
|
+
) -> None:
|
|
75
77
|
"""
|
|
76
78
|
Establishes a connection to the SQLite database if not already connected.
|
|
77
79
|
|
|
@@ -89,7 +91,9 @@ class TestHistory(ITestHistory):
|
|
|
89
91
|
except (sqlite3.Error, Exception) as e:
|
|
90
92
|
raise OrionisTestPersistenceError(f"Database connection error: {e}")
|
|
91
93
|
|
|
92
|
-
def __createTableIfNotExists(
|
|
94
|
+
def __createTableIfNotExists(
|
|
95
|
+
self
|
|
96
|
+
) -> bool:
|
|
93
97
|
"""
|
|
94
98
|
Ensures that the test history table exists in the database.
|
|
95
99
|
|
|
@@ -135,7 +139,10 @@ class TestHistory(ITestHistory):
|
|
|
135
139
|
self.__close()
|
|
136
140
|
self._conn = None
|
|
137
141
|
|
|
138
|
-
def __insertReport(
|
|
142
|
+
def __insertReport(
|
|
143
|
+
self,
|
|
144
|
+
report: Dict
|
|
145
|
+
) -> bool:
|
|
139
146
|
"""
|
|
140
147
|
Inserts a test report into the history database table.
|
|
141
148
|
|
|
@@ -272,7 +279,9 @@ class TestHistory(ITestHistory):
|
|
|
272
279
|
self.__close()
|
|
273
280
|
self._conn = None
|
|
274
281
|
|
|
275
|
-
def __resetDatabase(
|
|
282
|
+
def __resetDatabase(
|
|
283
|
+
self
|
|
284
|
+
) -> bool:
|
|
276
285
|
"""
|
|
277
286
|
Resets the database by dropping the existing table.
|
|
278
287
|
This method connects to the database, drops the table specified by
|
|
@@ -304,7 +313,9 @@ class TestHistory(ITestHistory):
|
|
|
304
313
|
self.__close()
|
|
305
314
|
self._conn = None
|
|
306
315
|
|
|
307
|
-
def __close(
|
|
316
|
+
def __close(
|
|
317
|
+
self
|
|
318
|
+
) -> None:
|
|
308
319
|
"""
|
|
309
320
|
Closes the current database connection.
|
|
310
321
|
This method checks if a database connection exists. If so, it closes the connection and sets the connection attribute to None.
|
|
@@ -318,7 +329,10 @@ class TestHistory(ITestHistory):
|
|
|
318
329
|
self._conn.close()
|
|
319
330
|
self._conn = None
|
|
320
331
|
|
|
321
|
-
def create(
|
|
332
|
+
def create(
|
|
333
|
+
self,
|
|
334
|
+
report: Dict
|
|
335
|
+
) -> bool:
|
|
322
336
|
"""
|
|
323
337
|
Create a new test report in the history database.
|
|
324
338
|
|
|
@@ -335,7 +349,9 @@ class TestHistory(ITestHistory):
|
|
|
335
349
|
self.__createTableIfNotExists()
|
|
336
350
|
return self.__insertReport(report)
|
|
337
351
|
|
|
338
|
-
def reset(
|
|
352
|
+
def reset(
|
|
353
|
+
self
|
|
354
|
+
) -> bool:
|
|
339
355
|
"""
|
|
340
356
|
Reset the history database by dropping the existing table.
|
|
341
357
|
|
|
File without changes
|