provide-testkit 0.0.0.dev6__tar.gz → 0.0.31__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/PKG-INFO +3 -1
  2. provide_testkit-0.0.31/VERSION +1 -0
  3. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/pyproject.toml +2 -0
  4. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/__init__.py +104 -65
  5. provide_testkit-0.0.31/src/provide/testkit/base/__init__.py +0 -0
  6. provide_testkit-0.0.31/src/provide/testkit/base/foundation.py +90 -0
  7. provide_testkit-0.0.31/src/provide/testkit/base/minimal.py +122 -0
  8. provide_testkit-0.0.31/src/provide/testkit/chaos/__init__.py +103 -0
  9. provide_testkit-0.0.31/src/provide/testkit/chaos/concurrency_strategies.py +396 -0
  10. provide_testkit-0.0.31/src/provide/testkit/chaos/fixtures.py +265 -0
  11. provide_testkit-0.0.31/src/provide/testkit/chaos/io_strategies.py +374 -0
  12. provide_testkit-0.0.31/src/provide/testkit/chaos/strategies.py +340 -0
  13. provide_testkit-0.0.31/src/provide/testkit/chaos/time_strategies.py +299 -0
  14. provide_testkit-0.0.31/src/provide/testkit/cli/__init__.py +0 -0
  15. provide_testkit-0.0.31/src/provide/testkit/cli/runner.py +215 -0
  16. provide_testkit-0.0.0.dev6/src/provide/testkit/cli.py → provide_testkit-0.0.31/src/provide/testkit/cli/testing.py +28 -28
  17. provide_testkit-0.0.31/src/provide/testkit/common/fixtures.py +164 -0
  18. provide_testkit-0.0.31/src/provide/testkit/crypto/__init__.py +37 -0
  19. provide_testkit-0.0.31/src/provide/testkit/file/temp.py +292 -0
  20. provide_testkit-0.0.31/src/provide/testkit/hub/__init__.py +0 -0
  21. provide_testkit-0.0.31/src/provide/testkit/logger/mocks.py +92 -0
  22. provide_testkit-0.0.31/src/provide/testkit/logger/reset.py +47 -0
  23. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/mocking/__init__.py +2 -0
  24. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/mocking/fixtures.py +7 -6
  25. provide_testkit-0.0.31/src/provide/testkit/mocking/time.py +183 -0
  26. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/process/__init__.py +0 -2
  27. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/process/async_fixtures.py +1 -21
  28. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/process/fixtures.py +0 -2
  29. provide_testkit-0.0.31/src/provide/testkit/streams/__init__.py +19 -0
  30. provide_testkit-0.0.31/src/provide/testkit/streams/testing.py +96 -0
  31. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/time/__init__.py +17 -2
  32. provide_testkit-0.0.31/src/provide/testkit/time/classes.py +320 -0
  33. provide_testkit-0.0.31/src/provide/testkit/time/controlled.py +81 -0
  34. provide_testkit-0.0.31/src/provide/testkit/time/fixtures.py +76 -0
  35. provide_testkit-0.0.31/src/provide/testkit/time/freezing.py +103 -0
  36. provide_testkit-0.0.31/src/provide/testkit/time/measurement.py +48 -0
  37. provide_testkit-0.0.31/src/provide/testkit/time/mocking.py +132 -0
  38. provide_testkit-0.0.31/src/provide/testkit/time/rate_limiting.py +33 -0
  39. provide_testkit-0.0.31/src/provide/testkit/utils/__init__.py +0 -0
  40. provide_testkit-0.0.31/src/provide/testkit/utils/temp_env.py +186 -0
  41. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide_testkit.egg-info/PKG-INFO +3 -1
  42. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide_testkit.egg-info/SOURCES.txt +31 -7
  43. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide_testkit.egg-info/requires.txt +2 -0
  44. provide_testkit-0.0.0.dev6/VERSION +0 -1
  45. provide_testkit-0.0.0.dev6/src/provide/testkit/common/fixtures.py +0 -234
  46. provide_testkit-0.0.0.dev6/src/provide/testkit/fixtures.py +0 -52
  47. provide_testkit-0.0.0.dev6/src/provide/testkit/logger/reset.py +0 -230
  48. provide_testkit-0.0.0.dev6/src/provide/testkit/streams.py +0 -54
  49. provide_testkit-0.0.0.dev6/src/provide/testkit/time/fixtures.py +0 -416
  50. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/README.md +0 -0
  51. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/setup.cfg +0 -0
  52. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/__init__.py +0 -0
  53. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/archive/__init__.py +0 -0
  54. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/archive/fixtures.py +0 -0
  55. {provide_testkit-0.0.0.dev6/src/provide/testkit → provide_testkit-0.0.31/src/provide/testkit/base}/harness.py +0 -0
  56. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/common/__init__.py +0 -0
  57. /provide_testkit-0.0.0.dev6/src/provide/testkit/crypto.py → /provide_testkit-0.0.31/src/provide/testkit/crypto/fixtures.py +0 -0
  58. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/file/__init__.py +0 -0
  59. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/file/content_fixtures.py +0 -0
  60. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/file/directory_fixtures.py +0 -0
  61. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/file/fixtures.py +0 -0
  62. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/file/special_fixtures.py +0 -0
  63. /provide_testkit-0.0.0.dev6/src/provide/testkit/hub.py → /provide_testkit-0.0.31/src/provide/testkit/hub/fixtures.py +0 -0
  64. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/logger/__init__.py +0 -0
  65. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/logger/hooks.py +0 -0
  66. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/main.py +0 -0
  67. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/process/subprocess_fixtures.py +0 -0
  68. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/__init__.py +0 -0
  69. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/artifacts.py +0 -0
  70. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/base.py +0 -0
  71. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/cli.py +0 -0
  72. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/complexity/__init__.py +0 -0
  73. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/complexity/analyzer.py +0 -0
  74. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/complexity/fixture.py +0 -0
  75. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/coverage/__init__.py +0 -0
  76. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/coverage/fixture.py +0 -0
  77. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/coverage/reporter.py +0 -0
  78. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/coverage/tracker.py +0 -0
  79. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/decorators.py +0 -0
  80. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/documentation/__init__.py +0 -0
  81. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/documentation/checker.py +0 -0
  82. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/documentation/fixture.py +0 -0
  83. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/profiling/__init__.py +0 -0
  84. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/profiling/fixture.py +0 -0
  85. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/profiling/profiler.py +0 -0
  86. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/report.py +0 -0
  87. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/runner.py +0 -0
  88. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/security/__init__.py +0 -0
  89. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/security/fixture.py +0 -0
  90. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/quality/security/scanner.py +0 -0
  91. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/threading/__init__.py +0 -0
  92. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/threading/basic_fixtures.py +0 -0
  93. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/threading/data_fixtures.py +0 -0
  94. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/threading/execution_fixtures.py +0 -0
  95. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/threading/fixtures.py +0 -0
  96. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/threading/sync_fixtures.py +0 -0
  97. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/transport/__init__.py +0 -0
  98. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide/testkit/transport/fixtures.py +0 -0
  99. {provide_testkit-0.0.0.dev6/src/provide/testkit → provide_testkit-0.0.31/src/provide/testkit/utils}/environment.py +0 -0
  100. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide_testkit.egg-info/dependency_links.txt +0 -0
  101. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide_testkit.egg-info/entry_points.txt +0 -0
  102. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/src/provide_testkit.egg-info/top_level.txt +0 -0
  103. {provide_testkit-0.0.0.dev6 → provide_testkit-0.0.31}/tests/test_placeholder.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: provide-testkit
3
- Version: 0.0.0.dev6
3
+ Version: 0.0.31
4
4
  Summary: Testing utilities and fixtures for the provide ecosystem.
5
5
  Author-email: Tim Perkins <code@tim.life>
6
6
  Maintainer-email: "provide.io" <code@provide.io>
@@ -50,6 +50,8 @@ Requires-Dist: types-grpcio>=1.0.0.20250603; extra == "typecheck"
50
50
  Requires-Dist: types-toml>=0.10.8.20240310; extra == "typecheck"
51
51
  Requires-Dist: types-protobuf>=6.30.2.20250516; extra == "typecheck"
52
52
  Requires-Dist: types-click>=7.1.8; extra == "typecheck"
53
+ Requires-Dist: types-pyyaml>=6.0.12.20250915; extra == "typecheck"
54
+ Requires-Dist: types-psutil>=7.0.0.20250822; extra == "typecheck"
53
55
  Provides-Extra: advanced-testing
54
56
  Requires-Dist: hypothesis>=6.131.28; extra == "advanced-testing"
55
57
  Requires-Dist: freezegun>=1.5.1; extra == "advanced-testing"
@@ -0,0 +1 @@
1
+ 0.0.31
@@ -75,6 +75,8 @@ typecheck = [
75
75
  "types-toml>=0.10.8.20240310",
76
76
  "types-protobuf>=6.30.2.20250516",
77
77
  "types-click>=7.1.8",
78
+ "types-pyyaml>=6.0.12.20250915",
79
+ "types-psutil>=7.0.0.20250822",
78
80
  ]
79
81
 
80
82
  # Property-based and advanced testing
@@ -12,10 +12,10 @@ Note: Testing information is displayed via pytest hooks in conftest.py
12
12
 
13
13
  from typing import Any
14
14
 
15
- # Mapping of attribute names to their modules
15
+ # Mapping of attribute names to their modules for lazy loading.
16
16
  _LAZY_IMPORTS = {
17
17
  # CLI testing utilities
18
- "cli": [
18
+ "cli.testing": [
19
19
  "MockContext",
20
20
  "isolated_cli_runner",
21
21
  "temp_config_file",
@@ -23,6 +23,12 @@ _LAZY_IMPORTS = {
23
23
  "CliTestCase",
24
24
  "click_testing_mode",
25
25
  ],
26
+ # CLI runner utilities
27
+ "cli.runner": [
28
+ "CliTestRunner",
29
+ "assert_cli_success",
30
+ "assert_cli_error",
31
+ ],
26
32
  # Logger testing utilities
27
33
  "logger": [
28
34
  "reset_foundation_setup_for_testing",
@@ -36,11 +42,21 @@ _LAZY_IMPORTS = {
36
42
  "suppress_loggers",
37
43
  ],
38
44
  # Stream testing utilities
39
- "streams": ["set_log_stream_for_testing"],
40
- # Fixture utilities
41
- "fixtures": [
45
+ "streams.testing": ["set_log_stream_for_testing", "enable_file_logging_for_testing"],
46
+ # Common fixture utilities
47
+ "common.fixtures": [
42
48
  "captured_stderr_for_foundation",
43
49
  "setup_foundation_telemetry_for_test",
50
+ "mock_http_config",
51
+ "mock_telemetry_config",
52
+ "mock_config_source",
53
+ "mock_event_emitter",
54
+ "mock_transport",
55
+ "mock_metrics_collector",
56
+ "mock_cache",
57
+ "mock_database",
58
+ "mock_file_system",
59
+ "mock_subprocess",
44
60
  ],
45
61
  # File testing utilities
46
62
  "file.fixtures": [
@@ -63,20 +79,6 @@ _LAZY_IMPORTS = {
63
79
  "async_iterator",
64
80
  "async_queue",
65
81
  "async_lock",
66
- "mock_async_sleep",
67
- ],
68
- # Common mock utilities
69
- "common.fixtures": [
70
- "mock_http_config",
71
- "mock_telemetry_config",
72
- "mock_config_source",
73
- "mock_event_emitter",
74
- "mock_transport",
75
- "mock_metrics_collector",
76
- "mock_cache",
77
- "mock_database",
78
- "mock_file_system",
79
- "mock_subprocess",
80
82
  ],
81
83
  # Transport/network testing utilities
82
84
  "transport.fixtures": [
@@ -100,7 +102,7 @@ _LAZY_IMPORTS = {
100
102
  "archive_stress_test_files",
101
103
  ],
102
104
  # Crypto fixtures
103
- "crypto": [
105
+ "crypto.fixtures": [
104
106
  "client_cert",
105
107
  "server_cert",
106
108
  "ca_cert",
@@ -118,26 +120,95 @@ _LAZY_IMPORTS = {
118
120
  "external_ca_pem",
119
121
  ],
120
122
  # Hub fixtures
121
- "hub": ["default_container_directory"],
123
+ "hub.fixtures": ["default_container_directory"],
122
124
  # Environment utilities
123
- "environment": [
125
+ "utils.environment": [
124
126
  "TestEnvironment",
125
127
  "get_example_dir",
126
128
  "add_src_to_path",
127
129
  "reset_test_environment",
128
130
  ],
131
+ # Temp environment utilities
132
+ "utils.temp_env": [
133
+ "temp_env",
134
+ "temp_env_from_dict",
135
+ "isolated_env",
136
+ "EnvContext",
137
+ ],
138
+ # Base test classes
139
+ "base.foundation": [
140
+ "FoundationTestCase",
141
+ ],
142
+ "base.minimal": [
143
+ "MinimalTestCase",
144
+ ],
145
+ # Base harness utilities
146
+ "base.harness": [
147
+ "HarnessRunner",
148
+ ],
149
+ # Mocking utilities
150
+ "mocking.fixtures": [
151
+ "ANY",
152
+ "AsyncMock",
153
+ "MagicMock",
154
+ "Mock",
155
+ "PropertyMock",
156
+ "assert_mock_calls",
157
+ "async_mock_factory",
158
+ "auto_patch",
159
+ "call",
160
+ "magic_mock_factory",
161
+ "mock_factory",
162
+ "mock_open_fixture",
163
+ "patch",
164
+ "patch_fixture",
165
+ "patch_multiple_fixture",
166
+ "property_mock_factory",
167
+ "spy_fixture",
168
+ ],
169
+ # Time mocking utilities
170
+ "mocking.time": [
171
+ "SleepTracker",
172
+ "mock_sleep",
173
+ "mock_time_sleep",
174
+ "mock_asyncio_sleep",
175
+ "create_sleep_mock",
176
+ ],
177
+ # Time fixture utilities
178
+ "time.fixtures": [
179
+ "time_machine",
180
+ "freeze_time",
181
+ "mock_datetime",
182
+ "timer",
183
+ "time_travel",
184
+ "rate_limiter_mock",
185
+ "benchmark_timer",
186
+ "advance_time",
187
+ ],
188
+ # File temp utilities
189
+ "file.temp": [
190
+ "TempFileManager",
191
+ "create_temp_file",
192
+ "create_temp_dir",
193
+ ],
129
194
  }
130
195
 
131
196
  # Submodules that can be imported directly
132
197
  _DIRECT_SUBMODULES = [
133
198
  "archive",
199
+ "base",
200
+ "cli",
134
201
  "common",
202
+ "crypto",
135
203
  "file",
136
- "process",
137
- "transport",
204
+ "hub",
138
205
  "mocking",
139
- "time",
206
+ "process",
207
+ "streams",
140
208
  "threading",
209
+ "time",
210
+ "transport",
211
+ "utils",
141
212
  ]
142
213
 
143
214
 
@@ -174,43 +245,11 @@ def __getattr__(name: str) -> Any:
174
245
  raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
175
246
 
176
247
 
177
- # Public API - these will be available for import but loaded lazily
178
- __all__ = [
179
- "DEFAULT_NOISY_LOGGERS",
180
- "CliTestCase",
181
- "MockContext",
182
- "TestEnvironment",
183
- "_is_testing_context",
184
- "add_src_to_path",
185
- "ca_cert",
186
- "captured_stderr_for_foundation",
187
- "cert_with_extra_whitespace",
188
- "cert_with_utf8_bom",
189
- "cert_with_windows_line_endings",
190
- "client_cert",
191
- "create_test_cli",
192
- "default_container_directory",
193
- "empty_cert",
194
- "external_ca_pem",
195
- "get_example_dir",
196
- "get_log_level_for_noisy_loggers",
197
- "get_noisy_loggers",
198
- "invalid_cert_pem",
199
- "invalid_key_pem",
200
- "isolated_cli_runner",
201
- "malformed_cert_pem",
202
- "mock_logger",
203
- "pytest_runtest_setup",
204
- "reset_foundation_setup_for_testing",
205
- "reset_foundation_state",
206
- "reset_test_environment",
207
- "server_cert",
208
- "set_log_stream_for_testing",
209
- "setup_foundation_telemetry_for_test",
210
- "suppress_loggers",
211
- "temp_config_file",
212
- "temporary_cert_file",
213
- "temporary_key_file",
214
- "valid_cert_pem",
215
- "valid_key_pem",
216
- ]
248
+ # --- Public API ---
249
+ # This list is dynamically generated to be comprehensive and stay in sync
250
+ # with the lazy loader, ensuring a complete and correct public API.
251
+ _all_lazy_attributes: list[str] = []
252
+ for _attributes in _LAZY_IMPORTS.values():
253
+ _all_lazy_attributes.extend(_attributes)
254
+
255
+ __all__ = sorted(list(set(_all_lazy_attributes)))
@@ -0,0 +1,90 @@
1
+ """
2
+ FoundationTestCase Base Class.
3
+
4
+ Provides a common base class for all Foundation-based tests with
5
+ standard utilities, automatic setup/cleanup, and common assertions.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from provide.testkit.base.minimal import MinimalTestCase
11
+ from provide.testkit.logger import reset_foundation_setup_for_testing
12
+
13
+
14
+ class FoundationTestCase(MinimalTestCase):
15
+ """Base test case for Foundation-based tests.
16
+
17
+ Provides common utilities for testing Foundation applications:
18
+ - Automatic Foundation reset between tests
19
+ - Temporary file/directory tracking and cleanup
20
+ - Mock tracking utilities
21
+ - Common assertion methods
22
+ - Output capture helpers
23
+ """
24
+
25
+ def setup_method(self) -> None:
26
+ """Set up test case with Foundation reset."""
27
+ # Check if this test/class is marked as timing_sensitive
28
+ if self._needs_full_reset():
29
+ reset_foundation_setup_for_testing()
30
+ else:
31
+ # For timing-sensitive tests, do minimal reset to prevent test interference
32
+ # but avoid full setup that could affect timing
33
+ self._minimal_state_reset()
34
+
35
+ # Always call parent setup for basic utilities
36
+ super().setup_method()
37
+
38
+ def _needs_full_reset(self) -> bool:
39
+ """Check if this test needs full Foundation reset.
40
+
41
+ Returns False if test/class is marked with timing_sensitive marker.
42
+ """
43
+ # Get the current test item from pytest's request fixture
44
+ # We need to be careful here as this runs during setup
45
+ try:
46
+ import inspect
47
+
48
+ # Get the test method
49
+ frame = inspect.currentframe()
50
+ while frame:
51
+ if frame.f_code.co_name.startswith("test_"):
52
+ test_method = getattr(self, frame.f_code.co_name, None)
53
+ if test_method and hasattr(test_method, "pytestmark"):
54
+ marks = getattr(test_method, "pytestmark", [])
55
+ for mark in marks:
56
+ if mark.name == "timing_sensitive":
57
+ return False
58
+ break
59
+ frame = frame.f_back
60
+
61
+ # Check class-level markers
62
+ if hasattr(self.__class__, "pytestmark"):
63
+ marks = getattr(self.__class__, "pytestmark", [])
64
+ for mark in marks:
65
+ if mark.name == "timing_sensitive":
66
+ return False
67
+ except Exception:
68
+ # If we can't determine, default to full reset for safety
69
+ pass
70
+
71
+ return True
72
+
73
+ def _minimal_state_reset(self) -> None:
74
+ """Perform minimal state reset for timing-sensitive tests.
75
+
76
+ This resets only the most critical global state that can cause
77
+ test interference, while avoiding expensive operations that could
78
+ affect timing.
79
+ """
80
+ try:
81
+ # Reset Hub state which includes component registry
82
+ from provide.foundation.testmode.internal import reset_hub_state
83
+
84
+ reset_hub_state()
85
+ except ImportError:
86
+ # Testmode module not available, skip
87
+ pass
88
+
89
+
90
+ __all__ = ["FoundationTestCase"]
@@ -0,0 +1,122 @@
1
+ """
2
+ MinimalTestCase Base Class.
3
+
4
+ Provides a lightweight base class for tests that need common utilities
5
+ but don't require Foundation state reset. Ideal for timing-sensitive
6
+ tests or tests that don't use Foundation components.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ from pathlib import Path
13
+ from typing import Any
14
+ from unittest.mock import Mock
15
+
16
+
17
+ class MinimalTestCase:
18
+ """Minimal test case base class without Foundation reset overhead.
19
+
20
+ Provides common test utilities without the heavyweight Foundation reset:
21
+ - Temporary file/directory tracking and cleanup
22
+ - Mock tracking utilities
23
+ - Common assertion methods
24
+ - Output capture helpers
25
+
26
+ Use this for timing-sensitive tests or tests that don't need
27
+ Foundation state isolation.
28
+ """
29
+
30
+ def setup_method(self) -> None:
31
+ """Set up test case with minimal overhead."""
32
+ self._temp_files: list[Path] = []
33
+ self._mocks: list[Mock] = []
34
+
35
+ def teardown_method(self) -> None:
36
+ """Clean up test case."""
37
+ # Clean up temporary files
38
+ for path in self._temp_files:
39
+ if path.exists():
40
+ if path.is_file():
41
+ path.unlink()
42
+ elif path.is_dir():
43
+ import shutil
44
+
45
+ shutil.rmtree(path)
46
+
47
+ # Reset mocks
48
+ for mock in self._mocks:
49
+ mock.reset_mock()
50
+
51
+ def create_temp_file(self, content: str = "", suffix: str = "") -> Path:
52
+ """Create a temporary file that will be cleaned up."""
53
+ from provide.foundation.file import temp_file
54
+
55
+ with temp_file(suffix=suffix, text=True, cleanup=False) as path:
56
+ if content:
57
+ path.write_text(content)
58
+ self._temp_files.append(path)
59
+ return path
60
+
61
+ def create_temp_dir(self) -> Path:
62
+ """Create a temporary directory that will be cleaned up."""
63
+ import tempfile
64
+
65
+ temp_dir = Path(tempfile.mkdtemp())
66
+ self._temp_files.append(temp_dir)
67
+ return temp_dir
68
+
69
+ def track_mock(self, mock: Mock) -> Mock:
70
+ """Track a mock for automatic reset during teardown."""
71
+ self._mocks.append(mock)
72
+ return mock
73
+
74
+ def assert_json_output(self, output: str, expected: dict[str, Any]) -> None:
75
+ """Assert that output is valid JSON matching expected values."""
76
+ try:
77
+ actual = json.loads(output)
78
+ except json.JSONDecodeError as e:
79
+ raise AssertionError(f"Output is not valid JSON: {e}\n{output}") from e
80
+
81
+ for key, value in expected.items():
82
+ assert key in actual, f"Key '{key}' not in output"
83
+ assert actual[key] == value, f"Value mismatch for '{key}': {actual[key]} != {value}"
84
+
85
+ def assert_contains_error(
86
+ self, output: str, error_type: type[Exception], message: str | None = None
87
+ ) -> None:
88
+ """Assert that output contains an error of the specified type."""
89
+ error_name = error_type.__name__
90
+ assert error_name in output, f"Error type '{error_name}' not found in output: {output}"
91
+
92
+ if message:
93
+ assert message in output, f"Error message '{message}' not found in output: {output}"
94
+
95
+ def assert_log_contains(self, captured_logs: str, level: str, message: str) -> None:
96
+ """Assert that captured logs contain a message at the specified level."""
97
+ log_line = f"[{level.lower()}]"
98
+ assert log_line in captured_logs, f"Log level '{level}' not found in logs"
99
+ assert message in captured_logs, f"Log message '{message}' not found in logs"
100
+
101
+ def assert_file_exists(self, path: str | Path) -> None:
102
+ """Assert that a file exists."""
103
+ file_path = Path(path)
104
+ assert file_path.exists(), f"File does not exist: {file_path}"
105
+ assert file_path.is_file(), f"Path exists but is not a file: {file_path}"
106
+
107
+ def assert_dir_exists(self, path: str | Path) -> None:
108
+ """Assert that a directory exists."""
109
+ dir_path = Path(path)
110
+ assert dir_path.exists(), f"Directory does not exist: {dir_path}"
111
+ assert dir_path.is_dir(), f"Path exists but is not a directory: {dir_path}"
112
+
113
+ def assert_output_contains(self, output: str, expected: str) -> None:
114
+ """Assert that output contains expected string."""
115
+ assert expected in output, f"Expected '{expected}' not found in output: {output}"
116
+
117
+ def assert_output_not_contains(self, output: str, unexpected: str) -> None:
118
+ """Assert that output does not contain unexpected string."""
119
+ assert unexpected not in output, f"Unexpected '{unexpected}' found in output: {output}"
120
+
121
+
122
+ __all__ = ["MinimalTestCase"]
@@ -0,0 +1,103 @@
1
+ """Chaos testing utilities for property-based testing with Hypothesis.
2
+
3
+ This module provides reusable Hypothesis strategies and fixtures for chaos engineering
4
+ in tests. It enables systematic exploration of edge cases, race conditions, and failure
5
+ scenarios that are difficult to test with traditional methods.
6
+
7
+ Key Features:
8
+ - Property-based testing strategies for common chaos patterns
9
+ - Time manipulation and clock skew simulation
10
+ - Concurrency and race condition triggers
11
+ - I/O failure injection patterns
12
+ - Reusable pytest fixtures for chaos testing
13
+
14
+ Example:
15
+ ```python
16
+ from hypothesis import given
17
+ from provide.testkit.chaos import chaos_timings, failure_patterns
18
+
19
+ @given(
20
+ timing=chaos_timings(),
21
+ failures=failure_patterns()
22
+ )
23
+ async def test_with_chaos(timing, failures):
24
+ # Your chaos test here
25
+ pass
26
+ ```
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ from provide.testkit.chaos.concurrency_strategies import (
32
+ async_event_patterns,
33
+ deadlock_scenarios,
34
+ lock_contention_patterns,
35
+ pid_recycling_scenarios,
36
+ process_pool_patterns,
37
+ race_condition_triggers,
38
+ task_cancellation_patterns,
39
+ thread_counts,
40
+ )
41
+ from provide.testkit.chaos.io_strategies import (
42
+ buffer_overflow_patterns,
43
+ disk_full_scenarios,
44
+ file_corruption_patterns,
45
+ file_sizes,
46
+ lock_file_scenarios,
47
+ network_error_patterns,
48
+ path_traversal_patterns,
49
+ permission_patterns,
50
+ )
51
+ from provide.testkit.chaos.strategies import (
52
+ chaos_timings,
53
+ edge_values,
54
+ failure_patterns,
55
+ malformed_inputs,
56
+ resource_limits,
57
+ unicode_chaos,
58
+ )
59
+ from provide.testkit.chaos.time_strategies import (
60
+ clock_skew,
61
+ deadline_scenarios,
62
+ jitter_patterns,
63
+ rate_burst_patterns,
64
+ retry_backoff_patterns,
65
+ time_advances,
66
+ timeout_patterns,
67
+ )
68
+
69
+ __all__ = [
70
+ # Concurrency strategies
71
+ "async_event_patterns",
72
+ # I/O strategies
73
+ "buffer_overflow_patterns",
74
+ # Core strategies
75
+ "chaos_timings",
76
+ # Time strategies
77
+ "clock_skew",
78
+ "deadline_scenarios",
79
+ "deadlock_scenarios",
80
+ "disk_full_scenarios",
81
+ "edge_values",
82
+ "failure_patterns",
83
+ "file_corruption_patterns",
84
+ "file_sizes",
85
+ "jitter_patterns",
86
+ "lock_contention_patterns",
87
+ "lock_file_scenarios",
88
+ "malformed_inputs",
89
+ "network_error_patterns",
90
+ "path_traversal_patterns",
91
+ "permission_patterns",
92
+ "pid_recycling_scenarios",
93
+ "process_pool_patterns",
94
+ "race_condition_triggers",
95
+ "rate_burst_patterns",
96
+ "resource_limits",
97
+ "retry_backoff_patterns",
98
+ "task_cancellation_patterns",
99
+ "thread_counts",
100
+ "time_advances",
101
+ "timeout_patterns",
102
+ "unicode_chaos",
103
+ ]