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.
Files changed (175) hide show
  1. orionis/console/base/command.py +57 -50
  2. orionis/console/base/contracts/command.py +68 -0
  3. orionis/console/dynamic/contracts/progress_bar.py +3 -3
  4. orionis/console/dynamic/progress_bar.py +8 -8
  5. orionis/console/output/console.py +8 -2
  6. orionis/console/output/contracts/console.py +1 -1
  7. orionis/container/container.py +2 -2
  8. orionis/container/context/scope.py +4 -1
  9. orionis/container/contracts/service_provider.py +2 -2
  10. orionis/container/entities/binding.py +31 -44
  11. orionis/container/enums/lifetimes.py +22 -1
  12. orionis/container/facades/facade.py +1 -2
  13. orionis/container/providers/service_provider.py +2 -2
  14. orionis/foundation/application.py +542 -248
  15. orionis/foundation/config/app/entities/app.py +107 -90
  16. orionis/foundation/config/auth/entities/auth.py +4 -33
  17. orionis/foundation/config/cache/entities/cache.py +18 -41
  18. orionis/foundation/config/cache/entities/file.py +8 -35
  19. orionis/foundation/config/cache/entities/stores.py +17 -38
  20. orionis/foundation/config/cors/entities/cors.py +41 -54
  21. orionis/foundation/config/database/entities/connections.py +40 -56
  22. orionis/foundation/config/database/entities/database.py +11 -38
  23. orionis/foundation/config/database/entities/mysql.py +48 -76
  24. orionis/foundation/config/database/entities/oracle.py +30 -57
  25. orionis/foundation/config/database/entities/pgsql.py +45 -61
  26. orionis/foundation/config/database/entities/sqlite.py +26 -53
  27. orionis/foundation/config/filesystems/entitites/aws.py +28 -49
  28. orionis/foundation/config/filesystems/entitites/disks.py +27 -47
  29. orionis/foundation/config/filesystems/entitites/filesystems.py +15 -37
  30. orionis/foundation/config/filesystems/entitites/local.py +9 -35
  31. orionis/foundation/config/filesystems/entitites/public.py +14 -41
  32. orionis/foundation/config/logging/entities/channels.py +56 -86
  33. orionis/foundation/config/logging/entities/chunked.py +9 -9
  34. orionis/foundation/config/logging/entities/daily.py +8 -8
  35. orionis/foundation/config/logging/entities/hourly.py +6 -6
  36. orionis/foundation/config/logging/entities/logging.py +12 -18
  37. orionis/foundation/config/logging/entities/monthly.py +7 -7
  38. orionis/foundation/config/logging/entities/stack.py +5 -5
  39. orionis/foundation/config/logging/entities/weekly.py +6 -6
  40. orionis/foundation/config/mail/entities/file.py +9 -36
  41. orionis/foundation/config/mail/entities/mail.py +22 -40
  42. orionis/foundation/config/mail/entities/mailers.py +29 -44
  43. orionis/foundation/config/mail/entities/smtp.py +47 -48
  44. orionis/foundation/config/queue/entities/brokers.py +19 -41
  45. orionis/foundation/config/queue/entities/database.py +24 -46
  46. orionis/foundation/config/queue/entities/queue.py +28 -40
  47. orionis/foundation/config/roots/paths.py +272 -468
  48. orionis/foundation/config/session/entities/session.py +23 -53
  49. orionis/foundation/config/startup.py +165 -135
  50. orionis/foundation/config/testing/entities/testing.py +137 -122
  51. orionis/foundation/config/testing/enums/__init__.py +6 -2
  52. orionis/foundation/config/testing/enums/drivers.py +16 -0
  53. orionis/foundation/config/testing/enums/verbosity.py +18 -0
  54. orionis/foundation/contracts/application.py +152 -362
  55. orionis/foundation/providers/console_provider.py +24 -2
  56. orionis/foundation/providers/dumper_provider.py +24 -2
  57. orionis/foundation/providers/logger_provider.py +24 -2
  58. orionis/foundation/providers/path_resolver_provider.py +25 -2
  59. orionis/foundation/providers/progress_bar_provider.py +24 -2
  60. orionis/foundation/providers/testing_provider.py +39 -0
  61. orionis/foundation/providers/workers_provider.py +24 -2
  62. orionis/metadata/framework.py +1 -1
  63. orionis/services/asynchrony/contracts/coroutines.py +13 -5
  64. orionis/services/asynchrony/coroutines.py +33 -29
  65. orionis/services/asynchrony/exceptions/exception.py +9 -1
  66. orionis/services/environment/core/dot_env.py +46 -34
  67. orionis/services/environment/enums/__init__.py +0 -0
  68. orionis/services/environment/enums/cast_type.py +42 -0
  69. orionis/services/environment/helpers/functions.py +1 -2
  70. orionis/services/environment/key/__init__.py +0 -0
  71. orionis/services/environment/key/key_generator.py +37 -0
  72. orionis/services/environment/serializer/__init__.py +0 -0
  73. orionis/services/environment/serializer/values.py +21 -0
  74. orionis/services/environment/validators/__init__.py +0 -0
  75. orionis/services/environment/validators/key_name.py +46 -0
  76. orionis/services/environment/validators/types.py +45 -0
  77. orionis/services/system/contracts/imports.py +38 -18
  78. orionis/services/system/contracts/workers.py +29 -12
  79. orionis/services/system/imports.py +65 -25
  80. orionis/services/system/runtime/imports.py +18 -9
  81. orionis/services/system/workers.py +49 -16
  82. orionis/support/entities/__init__.py +0 -0
  83. orionis/support/entities/base.py +104 -0
  84. orionis/support/facades/testing.py +15 -0
  85. orionis/support/facades/workers.py +1 -1
  86. orionis/test/cases/asynchronous.py +0 -11
  87. orionis/test/cases/synchronous.py +0 -9
  88. orionis/test/contracts/dumper.py +11 -4
  89. orionis/test/contracts/kernel.py +5 -110
  90. orionis/test/contracts/logs.py +27 -65
  91. orionis/test/contracts/printer.py +16 -128
  92. orionis/test/contracts/test_result.py +100 -0
  93. orionis/test/contracts/unit_test.py +87 -150
  94. orionis/test/core/unit_test.py +608 -554
  95. orionis/test/entities/result.py +22 -2
  96. orionis/test/enums/__init__.py +0 -2
  97. orionis/test/enums/status.py +14 -9
  98. orionis/test/exceptions/config.py +9 -1
  99. orionis/test/exceptions/failure.py +34 -11
  100. orionis/test/exceptions/persistence.py +10 -2
  101. orionis/test/exceptions/runtime.py +9 -1
  102. orionis/test/exceptions/value.py +13 -1
  103. orionis/test/kernel.py +87 -289
  104. orionis/test/output/dumper.py +83 -18
  105. orionis/test/output/printer.py +399 -156
  106. orionis/test/records/logs.py +203 -82
  107. orionis/test/validators/__init__.py +33 -0
  108. orionis/test/validators/base_path.py +45 -0
  109. orionis/test/validators/execution_mode.py +45 -0
  110. orionis/test/validators/fail_fast.py +37 -0
  111. orionis/test/validators/folder_path.py +34 -0
  112. orionis/test/validators/module_name.py +31 -0
  113. orionis/test/validators/name_pattern.py +40 -0
  114. orionis/test/validators/pattern.py +36 -0
  115. orionis/test/validators/persistent.py +42 -0
  116. orionis/test/validators/persistent_driver.py +43 -0
  117. orionis/test/validators/print_result.py +37 -0
  118. orionis/test/validators/tags.py +37 -0
  119. orionis/test/validators/throw_exception.py +39 -0
  120. orionis/test/validators/verbosity.py +37 -0
  121. orionis/test/validators/web_report.py +35 -0
  122. orionis/test/validators/workers.py +31 -0
  123. orionis/test/view/render.py +48 -54
  124. {orionis-0.405.0.dist-info → orionis-0.407.0.dist-info}/METADATA +1 -1
  125. {orionis-0.405.0.dist-info → orionis-0.407.0.dist-info}/RECORD +170 -112
  126. tests/container/__init__.py +0 -0
  127. tests/container/context/__init__.py +0 -0
  128. tests/container/context/test_manager.py +27 -0
  129. tests/container/context/test_scope.py +23 -0
  130. tests/container/entities/__init__.py +0 -0
  131. tests/container/entities/test_binding.py +133 -0
  132. tests/container/enums/__init__.py +0 -0
  133. tests/container/enums/test_lifetimes.py +63 -0
  134. tests/container/facades/__init__.py +0 -0
  135. tests/container/facades/test_facade.py +61 -0
  136. tests/container/mocks/__init__.py +0 -0
  137. tests/container/mocks/mock_complex_classes.py +482 -0
  138. tests/container/mocks/mock_simple_classes.py +32 -0
  139. tests/container/providers/__init__.py +0 -0
  140. tests/container/providers/test_providers.py +48 -0
  141. tests/container/resolver/__init__.py +0 -0
  142. tests/container/resolver/test_resolver.py +55 -0
  143. tests/container/test_container.py +254 -0
  144. tests/container/test_singleton.py +98 -0
  145. tests/container/test_thread_safety.py +217 -0
  146. tests/container/validators/__init__.py +0 -0
  147. tests/container/validators/test_implements.py +140 -0
  148. tests/container/validators/test_is_abstract_class.py +99 -0
  149. tests/container/validators/test_is_callable.py +73 -0
  150. tests/container/validators/test_is_concrete_class.py +97 -0
  151. tests/container/validators/test_is_instance.py +105 -0
  152. tests/container/validators/test_is_not_subclass.py +117 -0
  153. tests/container/validators/test_is_subclass.py +115 -0
  154. tests/container/validators/test_is_valid_alias.py +113 -0
  155. tests/container/validators/test_lifetime.py +75 -0
  156. tests/example/test_example.py +2 -2
  157. tests/foundation/config/testing/test_foundation_config_testing.py +1 -1
  158. tests/metadata/test_metadata_framework.py +89 -24
  159. tests/metadata/test_metadata_package.py +55 -10
  160. tests/services/asynchrony/test_services_asynchrony_coroutine.py +52 -7
  161. tests/services/system/test_services_system_imports.py +119 -16
  162. tests/services/system/test_services_system_workers.py +71 -30
  163. tests/testing/test_testing_result.py +117 -117
  164. tests/testing/test_testing_unit.py +209 -209
  165. orionis/foundation/config/base.py +0 -112
  166. orionis/test/arguments/parser.py +0 -187
  167. orionis/test/contracts/parser.py +0 -43
  168. orionis/test/entities/arguments.py +0 -38
  169. orionis/test/enums/execution_mode.py +0 -16
  170. /orionis/{test/arguments → console/base/contracts}/__init__.py +0 -0
  171. /orionis/foundation/config/testing/enums/{test_mode.py → mode.py} +0 -0
  172. {orionis-0.405.0.dist-info → orionis-0.407.0.dist-info}/WHEEL +0 -0
  173. {orionis-0.405.0.dist-info → orionis-0.407.0.dist-info}/licenses/LICENCE +0 -0
  174. {orionis-0.405.0.dist-info → orionis-0.407.0.dist-info}/top_level.txt +0 -0
  175. {orionis-0.405.0.dist-info → orionis-0.407.0.dist-info}/zip-safe +0 -0
@@ -0,0 +1,254 @@
1
+ from orionis.container.container import Container
2
+ from orionis.container.facades.facade import Facade
3
+ from orionis.foundation.application import Application
4
+ from orionis.test.cases.asynchronous import AsyncTestCase
5
+ from tests.container.mocks.mock_simple_classes import Car, ICar
6
+
7
+ class TestContainer(AsyncTestCase):
8
+ """Test suite for the Container class functionality."""
9
+
10
+ async def testTransientRegistration(self) -> None:
11
+ """
12
+ Tests the transient registration of a service in the container.
13
+ It verifies that:
14
+ 1. The container.transient() method correctly registers a type (ICar) to be resolved as another type (Car)
15
+ 2. The resolved instances are of the correct type (Car)
16
+ 3. Each resolution returns a new instance (instance1 is not the same object as instance2)
17
+ """
18
+ container = Container()
19
+ container.transient(ICar, Car)
20
+ instance1 = container.make(ICar)
21
+ instance2 = container.make(ICar)
22
+
23
+ self.assertIsInstance(instance1, Car)
24
+ self.assertIsInstance(instance2, Car)
25
+ self.assertIsNot(instance1, instance2)
26
+
27
+ container.drop(abstract=ICar)
28
+
29
+ async def testSingletonRegistration(self) -> None:
30
+ """
31
+ Test that verifies singleton registration and resolution from the container.
32
+ This test ensures that:
33
+ 1. A class can be registered as a singleton implementation of an interface
34
+ 2. The container correctly returns an instance of the registered implementation
35
+ 3. Multiple requests for the same interface return the same instance
36
+ The test binds the ICar interface to the Car implementation as a singleton,
37
+ then verifies that two requests for ICar:
38
+ - Both return instances of Car
39
+ - Return the exact same instance (object identity)
40
+ """
41
+ container = Container()
42
+ container.singleton(ICar, Car)
43
+ instance1 = container.make(ICar)
44
+ instance2 = container.make(ICar)
45
+
46
+ self.assertIsInstance(instance1, Car)
47
+ self.assertIsInstance(instance2, Car)
48
+ self.assertIs(instance1, instance2)
49
+
50
+ container.drop(abstract=ICar)
51
+
52
+ async def testScopedRegistration(self) -> None:
53
+ """
54
+ Tests the scoped registration functionality of the container.
55
+ This test verifies that:
56
+ 1. Within a single context, scoped registrations return the same instance
57
+ when the same interface is requested multiple times
58
+ 2. Different contexts produce different instances of the same registration
59
+ The test creates two separate contexts and confirms that instances obtained
60
+ within a single context are identical, but instances from different contexts
61
+ are distinct.
62
+ """
63
+
64
+ container = Container()
65
+ with container.createContext():
66
+ container.scoped(ICar, Car)
67
+ instance1 = container.make(ICar)
68
+ instance2 = container.make(ICar)
69
+
70
+ self.assertIsInstance(instance1, Car)
71
+ self.assertIsInstance(instance2, Car)
72
+ self.assertIs(instance1, instance2)
73
+
74
+ with container.createContext():
75
+ container.scoped(ICar, Car)
76
+ instance3 = container.make(ICar)
77
+ self.assertIsNot(instance1, instance3)
78
+
79
+ container.drop(abstract=ICar)
80
+
81
+ async def testInstanceRegistration(self) -> None:
82
+ """
83
+ Test case for instance registration in the container.
84
+ This test ensures that when an instance is registered to a service in the container,
85
+ the container returns exactly the same instance when resolving that service.
86
+ The test:
87
+ 1. Creates an instance of Car
88
+ 2. Registers this instance to the ICar interface in the container
89
+ 3. Resolves the ICar interface from the container
90
+ 4. Asserts that the resolved instance is exactly the same object as the one registered
91
+ """
92
+ car_instance = Car()
93
+ container = Container()
94
+ container.instance(ICar, car_instance)
95
+ resolved = container.make(ICar)
96
+
97
+ self.assertIs(resolved, car_instance)
98
+
99
+ container.drop(abstract=ICar)
100
+
101
+ async def testCallableRegistration(self) -> None:
102
+ """
103
+ Test that callables can be registered and resolved from the container.
104
+ This test verifies that:
105
+ 1. Functions can be registered in the container using the callable() method
106
+ 2. Registered functions can be resolved and executed using the make() method
107
+ 3. Arguments can be passed to the resolved functions as positional arguments
108
+ 4. Arguments can be passed to the resolved functions as keyword arguments
109
+ The test registers two simple functions (add and multiply) and verifies
110
+ they can be correctly resolved and executed with the expected results.
111
+ """
112
+ def add(a: int, b: int) -> int:
113
+ return a + b
114
+
115
+ def multiply(a: int, b: int) -> int:
116
+ return a * b
117
+
118
+ container = Container()
119
+ container.callable('add', add)
120
+ container.callable('multiply', multiply)
121
+
122
+ self.assertEqual(container.make('add', 1, 2), 3)
123
+ self.assertEqual(container.make('multiply', 3, 4), 12)
124
+ self.assertEqual(container.make('add', a=5, b=7), 12)
125
+
126
+ container.drop(alias='add')
127
+ container.drop(alias='multiply')
128
+
129
+ async def testTransientFacade(self) -> None:
130
+ """
131
+ Test case for transient instance resolution using a Facade pattern.
132
+ This test validates that:
133
+ 1. The container can register a transient binding between an interface and a class
134
+ 2. The Facade pattern correctly resolves instances of the registered interface
135
+ 3. Multiple calls to the Facade's resolve() method return different instances
136
+ when the binding is transient
137
+ The test creates a CarFacade that accesses ICar interface which is bound to the Car
138
+ implementation in a transient manner, ensuring each resolution yields a new instance.
139
+ """
140
+ container = Application()
141
+ container.transient(ICar, Car)
142
+
143
+ class CarFacade(Facade):
144
+ @classmethod
145
+ def getFacadeAccessor(cls):
146
+ return ICar
147
+
148
+ instance1 = CarFacade.resolve()
149
+ instance2 = CarFacade.resolve()
150
+
151
+ self.assertIsInstance(instance1, Car)
152
+ self.assertIsInstance(instance2, Car)
153
+ self.assertIsNot(instance1, instance2)
154
+
155
+ container.drop(abstract=ICar)
156
+
157
+ async def testSingletonFacade(self) -> None:
158
+ """
159
+ Tests if the Facade pattern correctly resolves singleton instances.
160
+ This test verifies that:
161
+ 1. A singleton binding can be registered in the container
162
+ 2. A Facade class can be created to access this binding
163
+ 3. Multiple resolutions through the Facade return the same instance
164
+ 4. The resolved instances are of the correct type (Car)
165
+ The test demonstrates how Facades act as static proxies to container bindings,
166
+ maintaining the singleton behavior defined in the container.
167
+ """
168
+ container = Application()
169
+ container.singleton(ICar, Car)
170
+
171
+ class CarFacade(Facade):
172
+ @classmethod
173
+ def getFacadeAccessor(cls):
174
+ return ICar
175
+
176
+ instance1 = CarFacade.resolve()
177
+ instance2 = CarFacade.resolve()
178
+
179
+ self.assertIsInstance(instance1, Car)
180
+ self.assertIsInstance(instance2, Car)
181
+ self.assertIs(instance1, instance2)
182
+
183
+ container.drop(abstract=ICar)
184
+
185
+ async def testScopedFacade(self) -> None:
186
+ """
187
+ Tests the functionality of a Facade accessing a scoped service within a container context.
188
+ This test verifies that:
189
+ 1. The Facade can properly resolve a scoped service
190
+ 2. Multiple resolves within the same scope return the same instance
191
+ 3. The resolved instance is of the correct type
192
+ The test creates a scoped registration for ICar interface to Car implementation,
193
+ defines a CarFacade class that accesses ICar, and then confirms that:
194
+ - Resolved instances are of Car type
195
+ - Multiple resolutions return the same instance (scoped behavior)
196
+ """
197
+ container = Application()
198
+ with container.createContext():
199
+ container.scoped(ICar, Car)
200
+
201
+ class CarFacade(Facade):
202
+ @classmethod
203
+ def getFacadeAccessor(cls):
204
+ return ICar
205
+
206
+ instance1 = CarFacade.resolve()
207
+ instance2 = CarFacade.resolve()
208
+
209
+ self.assertIsInstance(instance1, Car)
210
+ self.assertIsInstance(instance2, Car)
211
+ self.assertIs(instance1, instance2)
212
+
213
+ container.drop(abstract=ICar)
214
+
215
+ async def testResolvingUnregisteredType(self) -> None:
216
+ """
217
+ Tests that attempting to resolve an unregistered type from the container raises an exception.
218
+ This test ensures that the container correctly validates that a type is registered before attempting to resolve it.
219
+
220
+ Raises:
221
+ Exception: Expected to be raised when attempting to resolve an unregistered type.
222
+ """
223
+ container = Container()
224
+ with self.assertRaises(Exception):
225
+ container.make('ICar')
226
+
227
+ async def testOverridingRegistration(self) -> None:
228
+ """
229
+ Tests the ability of the container to override existing registrations.
230
+ This test verifies that:
231
+ 1. When a class is registered as a singleton for an interface
232
+ 2. And later a different class is registered for the same interface
233
+ 3. The container returns the new class when resolving the interface
234
+ 4. The new instance is different from the previous instance
235
+ This demonstrates the container's support for overriding service implementations.
236
+ """
237
+ class SportsCar(Car):
238
+ def start(self):
239
+ return f"{self.brand} {self.model} is starting."
240
+ def stop(self):
241
+ return f"{self.brand} {self.model} is stopping."
242
+
243
+ container = Container()
244
+ container.singleton(ICar, Car)
245
+ first = container.make(ICar)
246
+ self.assertIsInstance(first, Car)
247
+ self.assertNotIsInstance(first, SportsCar)
248
+
249
+ container.singleton(ICar, SportsCar)
250
+ second = container.make(ICar)
251
+ self.assertIsInstance(second, SportsCar)
252
+ self.assertIsNot(first, second)
253
+
254
+ container.drop(abstract=ICar)
@@ -0,0 +1,98 @@
1
+ import threading
2
+ import time
3
+ from orionis.foundation.application import Application as Orionis
4
+ from orionis.container.container import Container
5
+ from orionis.test.cases.asynchronous import AsyncTestCase
6
+
7
+ class TestSingleton(AsyncTestCase):
8
+ """Test suite for singleton pattern implementation."""
9
+
10
+ async def testSingletonBasicFunctionality(self) -> None:
11
+ """
12
+ Test basic singleton functionality.
13
+
14
+ This test verifies that:
15
+ 1. Multiple Container instances are the same object
16
+ 2. Multiple Orionis instances are the same object
17
+ 3. Container and Orionis are different singletons
18
+ """
19
+ # Create multiple instances
20
+ container1 = Container()
21
+ container2 = Container()
22
+ orionis1 = Orionis()
23
+ orionis2 = Orionis()
24
+
25
+ # Test that Container instances are the same
26
+ self.assertIs(container1, container2)
27
+ self.assertEqual(id(container1), id(container2))
28
+
29
+ # Test that Orionis instances are the same
30
+ self.assertIs(orionis1, orionis2)
31
+ self.assertEqual(id(orionis1), id(orionis2))
32
+
33
+ # Test that Container and Orionis are different singletons
34
+ self.assertIsNot(container1, orionis1)
35
+
36
+ async def testSingletonThreadingSafety(self) -> None:
37
+ """
38
+ Test singleton in multi-threaded environment.
39
+
40
+ This test verifies that singleton pattern works correctly
41
+ when instances are created from multiple threads simultaneously.
42
+ """
43
+ container_instances = []
44
+ orionis_instances = []
45
+
46
+ def create_container():
47
+ """Create container instance in thread."""
48
+ time.sleep(0.01) # Small delay to increase chance of race condition
49
+ container_instances.append(Container())
50
+
51
+ def create_orionis():
52
+ """Create orionis instance in thread."""
53
+ time.sleep(0.01) # Small delay to increase chance of race condition
54
+ orionis_instances.append(Orionis())
55
+
56
+ # Create multiple threads
57
+ threads = []
58
+ for i in range(10):
59
+ t1 = threading.Thread(target=create_container)
60
+ t2 = threading.Thread(target=create_orionis)
61
+ threads.extend([t1, t2])
62
+
63
+ # Start all threads
64
+ for t in threads:
65
+ t.start()
66
+
67
+ # Wait for all threads to complete
68
+ for t in threads:
69
+ t.join()
70
+
71
+ # Check that all instances are the same
72
+ container_ids = [id(c) for c in container_instances]
73
+ orionis_ids = [id(o) for o in orionis_instances]
74
+
75
+ self.assertEqual(len(set(container_ids)), 1)
76
+ self.assertEqual(len(set(orionis_ids)), 1)
77
+ self.assertEqual(len(container_instances), 10)
78
+ self.assertEqual(len(orionis_instances), 10)
79
+
80
+ async def testInheritanceSeparation(self) -> None:
81
+ """
82
+ Test that Container and Orionis maintain separate singleton instances.
83
+
84
+ This test verifies that different singleton classes maintain
85
+ their own separate instances while both implementing singleton pattern.
86
+ """
87
+ container = Container()
88
+ orionis = Orionis()
89
+
90
+ # Add some data to each to verify they're separate
91
+ container.callable("test_container", lambda: "container_value")
92
+
93
+ # Check that they're different instances but both are singletons
94
+ self.assertEqual(type(container).__name__, "Container")
95
+ self.assertEqual(type(orionis).__name__, "Application")
96
+ self.assertIsNot(container, orionis)
97
+ self.assertTrue(container.bound('test_container'))
98
+ self.assertFalse(orionis.bound('test_container'))
@@ -0,0 +1,217 @@
1
+ import threading
2
+ import time
3
+ import random
4
+ from concurrent.futures import ThreadPoolExecutor, as_completed
5
+ from orionis.foundation.application import Application as Orionis
6
+ from orionis.container.container import Container
7
+ from orionis.test.cases.asynchronous import AsyncTestCase
8
+
9
+ class TestThreadSafety(AsyncTestCase):
10
+
11
+ async def testStressSingleton(self) -> None:
12
+ """
13
+ Test singleton under extreme concurrent conditions.
14
+
15
+ This test creates a large number of threads that simultaneously
16
+ attempt to create singleton instances to verify thread-safety.
17
+ """
18
+ container_instances = []
19
+ orionis_instances = []
20
+
21
+ def create_container_with_delay():
22
+ """Create container with random delay to simulate real conditions."""
23
+ # Random delay to increase chance of race conditions
24
+ time.sleep(random.uniform(0.001, 0.01))
25
+ container = Container()
26
+ container_instances.append(container)
27
+ return id(container)
28
+
29
+ def create_orionis_with_delay():
30
+ """Create orionis with random delay to simulate real conditions."""
31
+ # Random delay to increase chance of race conditions
32
+ time.sleep(random.uniform(0.001, 0.01))
33
+ orionis = Orionis()
34
+ orionis_instances.append(orionis)
35
+ return id(orionis)
36
+
37
+ # Create a large number of threads
38
+ num_threads = 100
39
+
40
+ with ThreadPoolExecutor(max_workers=50) as executor:
41
+ # Submit container creation tasks
42
+ container_futures = [
43
+ executor.submit(create_container_with_delay)
44
+ for _ in range(num_threads)
45
+ ]
46
+
47
+ # Submit orionis creation tasks
48
+ orionis_futures = [
49
+ executor.submit(create_orionis_with_delay)
50
+ for _ in range(num_threads)
51
+ ]
52
+
53
+ # Wait for all tasks to complete
54
+ container_ids = [future.result() for future in as_completed(container_futures)]
55
+ orionis_ids = [future.result() for future in as_completed(orionis_futures)]
56
+
57
+ # Verify all instances are the same
58
+ unique_container_ids = set(container_ids)
59
+ unique_orionis_ids = set(orionis_ids)
60
+
61
+ self.assertEqual(len(container_instances), num_threads)
62
+ self.assertEqual(len(unique_container_ids), 1)
63
+ self.assertEqual(len(orionis_instances), num_threads)
64
+ self.assertEqual(len(unique_orionis_ids), 1)
65
+
66
+ # Verify that Container and Orionis are different singletons
67
+ container_id = list(unique_container_ids)[0] if unique_container_ids else None
68
+ orionis_id = list(unique_orionis_ids)[0] if unique_orionis_ids else None
69
+
70
+ self.assertNotEqual(container_id, orionis_id)
71
+
72
+ async def testRapidAccess(self) -> None:
73
+ """
74
+ Test rapid concurrent access to existing singleton instances.
75
+
76
+ This test verifies that rapid concurrent access to already
77
+ created singleton instances maintains consistency.
78
+ """
79
+ # Create initial instances
80
+ initial_container = Container()
81
+ initial_orionis = Orionis()
82
+
83
+ container_ids = []
84
+ orionis_ids = []
85
+
86
+ def rapid_container_access():
87
+ """Rapidly access container singleton."""
88
+ for _ in range(100):
89
+ container = Container()
90
+ container_ids.append(id(container))
91
+
92
+ def rapid_orionis_access():
93
+ """Rapidly access orionis singleton."""
94
+ for _ in range(100):
95
+ orionis = Orionis()
96
+ orionis_ids.append(id(orionis))
97
+
98
+ # Create threads for rapid access
99
+ threads = []
100
+ for _ in range(20):
101
+ t1 = threading.Thread(target=rapid_container_access)
102
+ t2 = threading.Thread(target=rapid_orionis_access)
103
+ threads.extend([t1, t2])
104
+
105
+ # Start all threads simultaneously
106
+ start_time = time.time()
107
+ for t in threads:
108
+ t.start()
109
+
110
+ # Wait for all threads to complete
111
+ for t in threads:
112
+ t.join()
113
+
114
+ end_time = time.time()
115
+
116
+ # Verify consistency
117
+ unique_container_ids = set(container_ids)
118
+ unique_orionis_ids = set(orionis_ids)
119
+
120
+ self.assertEqual(len(container_ids), 20 * 100) # 20 threads * 100 accesses each
121
+ self.assertEqual(len(unique_container_ids), 1)
122
+ self.assertEqual(len(orionis_ids), 20 * 100)
123
+ self.assertEqual(len(unique_orionis_ids), 1)
124
+
125
+ # Verify performance is reasonable (should complete quickly)
126
+ self.assertLess(end_time - start_time, 10.0) # Should complete in less than 10 seconds
127
+
128
+ async def testMixedOperations(self) -> None:
129
+ """
130
+ Test mixed read/write operations on singletons.
131
+
132
+ This test verifies that concurrent read/write operations
133
+ maintain singleton consistency and data integrity.
134
+ """
135
+ errors = []
136
+
137
+ def mixed_operations():
138
+ """Perform mixed operations on containers."""
139
+ try:
140
+ # Get instances
141
+ container = Container()
142
+ orionis = Orionis()
143
+
144
+ # Perform some operations
145
+ test_key = f"test_func_{threading.current_thread().ident}"
146
+ container.callable(test_key, lambda: "test_value")
147
+
148
+ # Verify the same instance
149
+ container2 = Container()
150
+ orionis2 = Orionis()
151
+
152
+ if container is not container2:
153
+ errors.append("Container singleton violated")
154
+
155
+ if orionis is not orionis2:
156
+ errors.append("Orionis singleton violated")
157
+
158
+ # Check bindings consistency
159
+ if not container2.bound(test_key):
160
+ errors.append("Binding not consistent across instances")
161
+
162
+ except Exception as e:
163
+ errors.append(f"Exception in mixed operations: {e}")
164
+
165
+ # Run mixed operations in multiple threads
166
+ threads = []
167
+ for _ in range(50):
168
+ t = threading.Thread(target=mixed_operations)
169
+ threads.append(t)
170
+
171
+ for t in threads:
172
+ t.start()
173
+
174
+ for t in threads:
175
+ t.join()
176
+
177
+ # Assert no errors occurred
178
+ self.assertEqual(len(errors), 0, f"Errors found: {errors[:5]}")
179
+
180
+ async def testMemoryConsistency(self) -> None:
181
+ """
182
+ Test memory consistency across threads.
183
+
184
+ This test verifies that changes made in one thread
185
+ are visible in other threads due to singleton behavior.
186
+ """
187
+ results = []
188
+
189
+ def thread_a():
190
+ """Thread A - modifies the container."""
191
+ container = Container()
192
+ container.callable("thread_a_marker", lambda: "from_thread_a")
193
+ results.append("A_completed")
194
+
195
+ def thread_b():
196
+ """Thread B - reads from the container."""
197
+ # Small delay to ensure thread A runs first
198
+ time.sleep(0.01)
199
+ container = Container()
200
+ has_marker = container.bound("thread_a_marker")
201
+ results.append(f"B_sees_marker: {has_marker}")
202
+
203
+ t1 = threading.Thread(target=thread_a)
204
+ t2 = threading.Thread(target=thread_b)
205
+
206
+ t1.start()
207
+ t2.start()
208
+
209
+ t1.join()
210
+ t2.join()
211
+
212
+ # Verify that thread B saw the changes made by thread A
213
+ a_completed = "A_completed" in results
214
+ b_saw_marker = any("B_sees_marker: True" in r for r in results)
215
+
216
+ self.assertTrue(a_completed, "Thread A should have completed")
217
+ self.assertTrue(b_saw_marker, "Thread B should see Thread A's changes")
File without changes