provide-testkit 0.0.0.dev0__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 (66) hide show
  1. provide/__init__.py +3 -0
  2. provide/testkit/__init__.py +248 -0
  3. provide/testkit/archive/__init__.py +24 -0
  4. provide/testkit/archive/fixtures.py +217 -0
  5. provide/testkit/cli.py +229 -0
  6. provide/testkit/common/__init__.py +32 -0
  7. provide/testkit/common/fixtures.py +234 -0
  8. provide/testkit/crypto.py +163 -0
  9. provide/testkit/environment.py +79 -0
  10. provide/testkit/file/__init__.py +40 -0
  11. provide/testkit/file/content_fixtures.py +275 -0
  12. provide/testkit/file/directory_fixtures.py +105 -0
  13. provide/testkit/file/fixtures.py +49 -0
  14. provide/testkit/file/special_fixtures.py +141 -0
  15. provide/testkit/fixtures.py +52 -0
  16. provide/testkit/harness.py +122 -0
  17. provide/testkit/hub.py +22 -0
  18. provide/testkit/logger/__init__.py +39 -0
  19. provide/testkit/logger/hooks.py +100 -0
  20. provide/testkit/logger/reset.py +230 -0
  21. provide/testkit/main.py +22 -0
  22. provide/testkit/mocking/__init__.py +46 -0
  23. provide/testkit/mocking/fixtures.py +340 -0
  24. provide/testkit/process/__init__.py +48 -0
  25. provide/testkit/process/async_fixtures.py +410 -0
  26. provide/testkit/process/fixtures.py +54 -0
  27. provide/testkit/process/subprocess_fixtures.py +208 -0
  28. provide/testkit/quality/__init__.py +101 -0
  29. provide/testkit/quality/artifacts.py +360 -0
  30. provide/testkit/quality/base.py +158 -0
  31. provide/testkit/quality/cli.py +394 -0
  32. provide/testkit/quality/complexity/__init__.py +30 -0
  33. provide/testkit/quality/complexity/analyzer.py +392 -0
  34. provide/testkit/quality/complexity/fixture.py +196 -0
  35. provide/testkit/quality/coverage/__init__.py +36 -0
  36. provide/testkit/quality/coverage/fixture.py +236 -0
  37. provide/testkit/quality/coverage/reporter.py +150 -0
  38. provide/testkit/quality/coverage/tracker.py +313 -0
  39. provide/testkit/quality/decorators.py +380 -0
  40. provide/testkit/quality/documentation/__init__.py +29 -0
  41. provide/testkit/quality/documentation/checker.py +361 -0
  42. provide/testkit/quality/documentation/fixture.py +187 -0
  43. provide/testkit/quality/profiling/__init__.py +30 -0
  44. provide/testkit/quality/profiling/fixture.py +332 -0
  45. provide/testkit/quality/profiling/profiler.py +428 -0
  46. provide/testkit/quality/report.py +266 -0
  47. provide/testkit/quality/runner.py +319 -0
  48. provide/testkit/quality/security/__init__.py +29 -0
  49. provide/testkit/quality/security/fixture.py +196 -0
  50. provide/testkit/quality/security/scanner.py +338 -0
  51. provide/testkit/streams.py +54 -0
  52. provide/testkit/threading/__init__.py +38 -0
  53. provide/testkit/threading/basic_fixtures.py +103 -0
  54. provide/testkit/threading/data_fixtures.py +101 -0
  55. provide/testkit/threading/execution_fixtures.py +268 -0
  56. provide/testkit/threading/fixtures.py +50 -0
  57. provide/testkit/threading/sync_fixtures.py +98 -0
  58. provide/testkit/time/__init__.py +32 -0
  59. provide/testkit/time/fixtures.py +416 -0
  60. provide/testkit/transport/__init__.py +30 -0
  61. provide/testkit/transport/fixtures.py +278 -0
  62. provide_testkit-0.0.0.dev0.dist-info/METADATA +145 -0
  63. provide_testkit-0.0.0.dev0.dist-info/RECORD +66 -0
  64. provide_testkit-0.0.0.dev0.dist-info/WHEEL +5 -0
  65. provide_testkit-0.0.0.dev0.dist-info/entry_points.txt +2 -0
  66. provide_testkit-0.0.0.dev0.dist-info/top_level.txt +1 -0
provide/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Provide namespace package."""
2
+
3
+ __path__ = __import__("pkgutil").extend_path(__path__, __name__)
@@ -0,0 +1,248 @@
1
+ #
2
+ # __init__.py
3
+ #
4
+ """
5
+ Provide TestKit.
6
+
7
+ Unified testing utilities for the provide ecosystem with automatic context detection.
8
+ Comprehensive fixtures and utilities for testing Foundation-based applications.
9
+
10
+ Note: Testing information is displayed via pytest hooks in conftest.py
11
+ """
12
+
13
+ from typing import Any
14
+
15
+
16
+ # Lazy imports to avoid importing testing utilities in production
17
+ def __getattr__(name: str) -> Any:
18
+ """Lazy import testing utilities only when accessed."""
19
+
20
+ # CLI testing utilities
21
+ if name in [
22
+ "MockContext",
23
+ "isolated_cli_runner",
24
+ "temp_config_file",
25
+ "create_test_cli",
26
+ "CliTestCase",
27
+ "click_testing_mode",
28
+ ]:
29
+ import provide.testkit.cli as cli_module
30
+
31
+ return getattr(cli_module, name)
32
+
33
+ # Logger testing utilities
34
+ elif name in [
35
+ "reset_foundation_setup_for_testing",
36
+ "reset_foundation_state",
37
+ "mock_logger",
38
+ "mock_logger_factory",
39
+ # New hook utilities
40
+ "DEFAULT_NOISY_LOGGERS",
41
+ "get_noisy_loggers",
42
+ "get_log_level_for_noisy_loggers",
43
+ "pytest_runtest_setup",
44
+ "suppress_loggers",
45
+ ]:
46
+ import provide.testkit.logger as logger_module
47
+
48
+ return getattr(logger_module, name)
49
+
50
+ # Stream testing utilities
51
+ elif name in ["set_log_stream_for_testing"]:
52
+ import provide.testkit.streams as streams_module
53
+
54
+ return getattr(streams_module, name)
55
+
56
+ # Fixture utilities
57
+ elif name in [
58
+ "captured_stderr_for_foundation",
59
+ "setup_foundation_telemetry_for_test",
60
+ ]:
61
+ import provide.testkit.fixtures as fixtures_module
62
+
63
+ return getattr(fixtures_module, name)
64
+
65
+ # Import submodules directly
66
+ elif name in [
67
+ "archive",
68
+ "common",
69
+ "file",
70
+ "process",
71
+ "transport",
72
+ "mocking",
73
+ "time",
74
+ "threading",
75
+ ]:
76
+ import importlib
77
+
78
+ return importlib.import_module(f"provide.testkit.{name}")
79
+
80
+ # File testing utilities (backward compatibility)
81
+ elif name in [
82
+ "temp_directory",
83
+ "test_files_structure",
84
+ "temp_file",
85
+ "binary_file",
86
+ "nested_directory_structure",
87
+ "empty_directory",
88
+ "readonly_file",
89
+ ]:
90
+ import provide.testkit.file.fixtures as file_module
91
+
92
+ return getattr(file_module, name)
93
+
94
+ # Process/async testing utilities (backward compatibility)
95
+ elif name in [
96
+ "clean_event_loop",
97
+ "async_timeout",
98
+ "mock_async_process",
99
+ "async_stream_reader",
100
+ "event_loop_policy",
101
+ "async_context_manager",
102
+ "async_iterator",
103
+ "async_queue",
104
+ "async_lock",
105
+ "mock_async_sleep",
106
+ ]:
107
+ import provide.testkit.process.fixtures as process_module
108
+
109
+ return getattr(process_module, name)
110
+
111
+ # Common mock utilities (backward compatibility)
112
+ elif name in [
113
+ "mock_http_config",
114
+ "mock_telemetry_config",
115
+ "mock_config_source",
116
+ "mock_event_emitter",
117
+ "mock_transport",
118
+ "mock_metrics_collector",
119
+ "mock_cache",
120
+ "mock_database",
121
+ "mock_file_system",
122
+ "mock_subprocess",
123
+ ]:
124
+ import provide.testkit.common.fixtures as common_module
125
+
126
+ return getattr(common_module, name)
127
+
128
+ # Transport/network testing utilities (backward compatibility)
129
+ elif name in [
130
+ "free_port",
131
+ "mock_server",
132
+ "httpx_mock_responses",
133
+ "mock_websocket",
134
+ "mock_dns_resolver",
135
+ "tcp_client_server",
136
+ "mock_ssl_context",
137
+ "network_timeout",
138
+ "mock_http_headers",
139
+ ]:
140
+ import provide.testkit.transport.fixtures as transport_module
141
+
142
+ return getattr(transport_module, name)
143
+
144
+ # Archive testing utilities
145
+ elif name in [
146
+ "archive_test_content",
147
+ "large_file_for_compression",
148
+ "multi_format_archives",
149
+ "archive_with_permissions",
150
+ "corrupted_archives",
151
+ "archive_stress_test_files",
152
+ ]:
153
+ import provide.testkit.archive.fixtures as archive_module
154
+
155
+ return getattr(archive_module, name)
156
+
157
+ # Crypto fixtures (many fixtures)
158
+ elif name in [
159
+ "client_cert",
160
+ "server_cert",
161
+ "ca_cert",
162
+ "valid_cert_pem",
163
+ "valid_key_pem",
164
+ "invalid_cert_pem",
165
+ "invalid_key_pem",
166
+ "malformed_cert_pem",
167
+ "empty_cert",
168
+ "temporary_cert_file",
169
+ "temporary_key_file",
170
+ "cert_with_windows_line_endings",
171
+ "cert_with_utf8_bom",
172
+ "cert_with_extra_whitespace",
173
+ "external_ca_pem",
174
+ ]:
175
+ import provide.testkit.crypto as crypto_module
176
+
177
+ return getattr(crypto_module, name)
178
+
179
+ # Hub fixtures
180
+ elif name in ["default_container_directory"]:
181
+ import provide.testkit.hub as hub_module
182
+
183
+ return getattr(hub_module, name)
184
+
185
+ # Environment utilities
186
+ elif name in [
187
+ "TestEnvironment",
188
+ "get_example_dir",
189
+ "add_src_to_path",
190
+ "reset_test_environment",
191
+ ]:
192
+ import provide.testkit.environment as environment_module
193
+
194
+ return getattr(environment_module, name)
195
+
196
+ else:
197
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
198
+
199
+
200
+ # Public API - these will be available for import but loaded lazily
201
+ __all__ = [
202
+ # Context detection
203
+ "_is_testing_context",
204
+ # CLI testing
205
+ "MockContext",
206
+ "isolated_cli_runner",
207
+ "temp_config_file",
208
+ "create_test_cli",
209
+ "mock_logger",
210
+ "CliTestCase",
211
+ # Logger testing
212
+ "reset_foundation_setup_for_testing",
213
+ "reset_foundation_state",
214
+ # Logger hook utilities
215
+ "DEFAULT_NOISY_LOGGERS",
216
+ "get_noisy_loggers",
217
+ "get_log_level_for_noisy_loggers",
218
+ "pytest_runtest_setup",
219
+ "suppress_loggers",
220
+ # Stream testing
221
+ "set_log_stream_for_testing",
222
+ # Common fixtures
223
+ "captured_stderr_for_foundation",
224
+ "setup_foundation_telemetry_for_test",
225
+ # Crypto fixtures
226
+ "client_cert",
227
+ "server_cert",
228
+ "ca_cert",
229
+ "valid_cert_pem",
230
+ "valid_key_pem",
231
+ "invalid_cert_pem",
232
+ "invalid_key_pem",
233
+ "malformed_cert_pem",
234
+ "empty_cert",
235
+ "temporary_cert_file",
236
+ "temporary_key_file",
237
+ "cert_with_windows_line_endings",
238
+ "cert_with_utf8_bom",
239
+ "cert_with_extra_whitespace",
240
+ "external_ca_pem",
241
+ # Hub fixtures
242
+ "default_container_directory",
243
+ # Environment utilities
244
+ "TestEnvironment",
245
+ "get_example_dir",
246
+ "add_src_to_path",
247
+ "reset_test_environment",
248
+ ]
@@ -0,0 +1,24 @@
1
+ """
2
+ Archive testing fixtures for the provide-io ecosystem.
3
+
4
+ Standard fixtures for testing archive operations (tar, zip, gzip, bzip2)
5
+ across any project that depends on provide.foundation.
6
+ """
7
+
8
+ from provide.testkit.archive.fixtures import (
9
+ archive_stress_test_files,
10
+ archive_test_content,
11
+ archive_with_permissions,
12
+ corrupted_archives,
13
+ large_file_for_compression,
14
+ multi_format_archives,
15
+ )
16
+
17
+ __all__ = [
18
+ "archive_stress_test_files",
19
+ "archive_test_content",
20
+ "archive_with_permissions",
21
+ "corrupted_archives",
22
+ "large_file_for_compression",
23
+ "multi_format_archives",
24
+ ]
@@ -0,0 +1,217 @@
1
+ """
2
+ Archive Testing Fixtures.
3
+
4
+ Fixtures specific to testing archive operations like tar, zip, gzip, bzip2.
5
+ Builds on top of file fixtures for archive-specific test scenarios.
6
+ """
7
+
8
+ from collections.abc import Generator
9
+ from pathlib import Path
10
+
11
+ import pytest
12
+
13
+ from provide.testkit.file.fixtures import temp_directory
14
+
15
+
16
+ @pytest.fixture
17
+ def archive_test_content() -> Generator[tuple[Path, dict[str, str]], None, None]:
18
+ """
19
+ Create a standard set of files for archive testing.
20
+
21
+ Creates multiple files with different types of content to ensure
22
+ proper compression and extraction testing.
23
+
24
+ Yields:
25
+ Tuple of (source_dir, content_map) where content_map maps
26
+ relative paths to their expected content.
27
+ """
28
+ with temp_directory() as temp_dir:
29
+ source = temp_dir / "archive_source"
30
+ source.mkdir()
31
+
32
+ content_map = {
33
+ "text_file.txt": "This is a text file for archive testing.\n" * 10,
34
+ "data.json": '{"test": "data", "array": [1, 2, 3]}',
35
+ "script.py": "#!/usr/bin/env python\nprint('Hello from archive')\n",
36
+ "nested/dir/file.md": "# Nested File\nContent in nested directory",
37
+ "binary.dat": "Binary\x00\x01\x02\x03\xff\xfe data",
38
+ "empty.txt": "",
39
+ }
40
+
41
+ # Create all files
42
+ for rel_path, content in content_map.items():
43
+ file_path = source / rel_path
44
+ file_path.parent.mkdir(parents=True, exist_ok=True)
45
+
46
+ if isinstance(content, str):
47
+ file_path.write_text(content)
48
+ else:
49
+ file_path.write_bytes(content.encode() if isinstance(content, str) else content)
50
+
51
+ yield source, content_map
52
+
53
+
54
+ @pytest.fixture
55
+ def large_file_for_compression() -> Generator[Path, None, None]:
56
+ """
57
+ Create a large file suitable for compression testing.
58
+
59
+ The file contains repetitive content that compresses well.
60
+
61
+ Yields:
62
+ Path to a large file with compressible content.
63
+ """
64
+ with temp_directory() as temp_dir:
65
+ large_file = temp_dir / "large_compressible.txt"
66
+
67
+ # Create 10MB of highly compressible content
68
+ content = "This is a line of text that will be repeated many times.\n" * 100
69
+ large_content = content * 1000 # ~6MB of repetitive text
70
+
71
+ large_file.write_text(large_content)
72
+ yield large_file
73
+
74
+
75
+ @pytest.fixture
76
+ def multi_format_archives() -> Generator[dict[str, Path], None, None]:
77
+ """
78
+ Create sample archives in different formats for format detection testing.
79
+
80
+ Yields:
81
+ Dict mapping format names to paths of sample archives.
82
+ """
83
+ with temp_directory() as temp_dir:
84
+ archives = {}
85
+
86
+ # Create minimal valid archives in different formats
87
+ # Note: These are minimal headers, not full valid archives
88
+
89
+ # GZIP file (magic: 1f 8b)
90
+ gzip_file = temp_dir / "sample.gz"
91
+ gzip_file.write_bytes(b"\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x03" + b"compressed data")
92
+ archives["gzip"] = gzip_file
93
+
94
+ # BZIP2 file (magic: BZh)
95
+ bzip2_file = temp_dir / "sample.bz2"
96
+ bzip2_file.write_bytes(b"BZh91AY&SY" + b"compressed data")
97
+ archives["bzip2"] = bzip2_file
98
+
99
+ # ZIP file (magic: PK\x03\x04)
100
+ zip_file = temp_dir / "sample.zip"
101
+ zip_file.write_bytes(b"PK\x03\x04" + b"\x00" * 16 + b"zipfile")
102
+ archives["zip"] = zip_file
103
+
104
+ # TAR file (has specific header structure)
105
+ tar_file = temp_dir / "sample.tar"
106
+ # Minimal tar header (512 bytes)
107
+ tar_header = b"testfile.txt" + b"\x00" * 88 # name
108
+ tar_header += b"0000644\x00" # mode
109
+ tar_header += b"0000000\x00" # uid
110
+ tar_header += b"0000000\x00" # gid
111
+ tar_header += b"00000000000\x00" # size
112
+ tar_header += b"00000000000\x00" # mtime
113
+ tar_header += b" " # checksum placeholder
114
+ tar_header += b"0" # typeflag
115
+ tar_header += b"\x00" * 355 # padding to 512 bytes
116
+ tar_file.write_bytes(tar_header[:512])
117
+ archives["tar"] = tar_file
118
+
119
+ yield archives
120
+
121
+
122
+ @pytest.fixture
123
+ def archive_with_permissions() -> Generator[Path, None, None]:
124
+ """
125
+ Create files with specific permissions for archive permission testing.
126
+
127
+ Yields:
128
+ Path to directory containing files with various permission modes.
129
+ """
130
+ with temp_directory() as temp_dir:
131
+ source = temp_dir / "permissions_test"
132
+ source.mkdir()
133
+
134
+ # Regular file
135
+ regular = source / "regular.txt"
136
+ regular.write_text("Regular file")
137
+ regular.chmod(0o644)
138
+
139
+ # Executable file
140
+ executable = source / "script.sh"
141
+ executable.write_text("#!/bin/bash\necho 'Hello'")
142
+ executable.chmod(0o755)
143
+
144
+ # Read-only file
145
+ readonly = source / "readonly.txt"
146
+ readonly.write_text("Read only content")
147
+ readonly.chmod(0o444)
148
+
149
+ # Directory with specific permissions
150
+ special_dir = source / "special"
151
+ special_dir.mkdir()
152
+ special_dir.chmod(0o700)
153
+
154
+ yield source
155
+
156
+
157
+ @pytest.fixture
158
+ def corrupted_archives() -> Generator[dict[str, Path], None, None]:
159
+ """
160
+ Create corrupted archive files for error handling testing.
161
+
162
+ Yields:
163
+ Dict mapping format names to paths of corrupted archives.
164
+ """
165
+ with temp_directory() as temp_dir:
166
+ corrupted = {}
167
+
168
+ # Corrupted GZIP (invalid header)
169
+ bad_gzip = temp_dir / "corrupted.gz"
170
+ bad_gzip.write_bytes(b"\x1f\x8c" + b"not really gzip data")
171
+ corrupted["gzip"] = bad_gzip
172
+
173
+ # Corrupted ZIP (incomplete header)
174
+ bad_zip = temp_dir / "corrupted.zip"
175
+ bad_zip.write_bytes(b"PK\x03") # Incomplete magic
176
+ corrupted["zip"] = bad_zip
177
+
178
+ # Corrupted BZIP2 (wrong magic)
179
+ bad_bzip2 = temp_dir / "corrupted.bz2"
180
+ bad_bzip2.write_bytes(b"BZX" + b"not bzip2")
181
+ corrupted["bzip2"] = bad_bzip2
182
+
183
+ # Empty file claiming to be archive
184
+ empty_archive = temp_dir / "empty.tar.gz"
185
+ empty_archive.write_bytes(b"")
186
+ corrupted["empty"] = empty_archive
187
+
188
+ yield corrupted
189
+
190
+
191
+ @pytest.fixture
192
+ def archive_stress_test_files() -> Generator[Path, None, None]:
193
+ """
194
+ Create a large number of files for stress testing archive operations.
195
+
196
+ Yields:
197
+ Path to directory with many files for stress testing.
198
+ """
199
+ with temp_directory() as temp_dir:
200
+ stress_dir = temp_dir / "stress_test"
201
+ stress_dir.mkdir()
202
+
203
+ # Create 100 files in various subdirectories
204
+ for i in range(10):
205
+ subdir = stress_dir / f"subdir_{i}"
206
+ subdir.mkdir()
207
+
208
+ for j in range(10):
209
+ file_path = subdir / f"file_{j}.txt"
210
+ file_path.write_text(f"Content of file {i}_{j}\n" * 10)
211
+
212
+ # Add some binary files
213
+ for i in range(5):
214
+ bin_file = stress_dir / f"binary_{i}.dat"
215
+ bin_file.write_bytes(bytes(range(256)) * 10)
216
+
217
+ yield stress_dir