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
@@ -0,0 +1,79 @@
1
+ """
2
+ Environment management utilities for testing.
3
+
4
+ Provides context managers and utilities for managing test environments,
5
+ environment variables, and foundation setup/cleanup.
6
+ """
7
+
8
+ import os
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from provide.testkit.logger import reset_foundation_setup_for_testing
13
+
14
+
15
+ class TestEnvironment:
16
+ """Context manager for test environment setup with proper cleanup."""
17
+
18
+ def __init__(self, env_vars: dict[str, str] | None = None):
19
+ """
20
+ Initialize test environment manager.
21
+
22
+ Args:
23
+ env_vars: Dictionary of environment variables to set during the test
24
+ """
25
+ self.env_vars = env_vars or {}
26
+ self.original_env: dict[str, str | None] = {}
27
+
28
+ def __enter__(self) -> "TestEnvironment":
29
+ """Enter the test environment context."""
30
+ # Save original environment variables
31
+ for key in self.env_vars:
32
+ self.original_env[key] = os.environ.get(key)
33
+ os.environ[key] = self.env_vars[key]
34
+
35
+ # Reset foundation setup
36
+ reset_foundation_setup_for_testing()
37
+ return self
38
+
39
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
40
+ """Exit the test environment context and restore original state."""
41
+ # Restore original environment variables
42
+ for key, original_value in self.original_env.items():
43
+ if original_value is None:
44
+ os.environ.pop(key, None)
45
+ else:
46
+ os.environ[key] = original_value
47
+
48
+ # Reset foundation setup again for cleanup
49
+ reset_foundation_setup_for_testing()
50
+
51
+
52
+ def get_example_dir() -> Path:
53
+ """Get the examples directory path consistently across examples."""
54
+ # Note: This assumes foundation project structure
55
+ # In testkit context, this should point to foundation's examples
56
+ current_file = Path(__file__).resolve()
57
+ # Go up from testkit/src/provide/testkit/environment.py to find foundation
58
+ testkit_root = current_file.parent.parent.parent.parent
59
+ foundation_root = testkit_root.parent / "provide-foundation"
60
+ return foundation_root / "examples"
61
+
62
+
63
+ def add_src_to_path() -> Path:
64
+ """Add src directory to Python path for examples. Returns project root path."""
65
+ import sys
66
+
67
+ example_dir = get_example_dir()
68
+ project_root = example_dir.parent
69
+ src_path = project_root / "src"
70
+
71
+ if src_path.exists() and str(src_path) not in sys.path:
72
+ sys.path.insert(0, str(src_path))
73
+
74
+ return project_root
75
+
76
+
77
+ def reset_test_environment() -> None:
78
+ """Reset the test environment to a clean state."""
79
+ reset_foundation_setup_for_testing()
@@ -0,0 +1,40 @@
1
+ """
2
+ File testing fixtures for the provide-io ecosystem.
3
+
4
+ Standard fixtures for file and directory operations that can be used
5
+ across any project that depends on provide.foundation.
6
+ """
7
+
8
+ from provide.testkit.file.fixtures import (
9
+ binary_file,
10
+ empty_directory,
11
+ nested_directory_structure,
12
+ readonly_file,
13
+ temp_binary_file,
14
+ temp_csv_file,
15
+ temp_directory,
16
+ temp_executable_file,
17
+ temp_file,
18
+ temp_file_with_content,
19
+ temp_json_file,
20
+ temp_named_file,
21
+ temp_symlink,
22
+ test_files_structure,
23
+ )
24
+
25
+ __all__ = [
26
+ "binary_file",
27
+ "empty_directory",
28
+ "nested_directory_structure",
29
+ "readonly_file",
30
+ "temp_binary_file",
31
+ "temp_csv_file",
32
+ "temp_directory",
33
+ "temp_executable_file",
34
+ "temp_file",
35
+ "temp_file_with_content",
36
+ "temp_json_file",
37
+ "temp_named_file",
38
+ "temp_symlink",
39
+ "test_files_structure",
40
+ ]
@@ -0,0 +1,275 @@
1
+ """
2
+ Content-based file test fixtures.
3
+
4
+ Fixtures for creating files with specific content types like text, binary,
5
+ CSV, JSON, and other structured data.
6
+ """
7
+
8
+ import csv
9
+ import json
10
+ from pathlib import Path
11
+ import random
12
+
13
+ import pytest
14
+
15
+ from provide.foundation.file import temp_file as foundation_temp_file
16
+ from provide.foundation.file.safe import safe_delete
17
+
18
+
19
+ @pytest.fixture
20
+ def temp_file():
21
+ """
22
+ Create a temporary file factory with optional content.
23
+
24
+ Returns:
25
+ A function that creates temporary files with specified content and suffix.
26
+ """
27
+ created_files = []
28
+
29
+ def _make_temp_file(content: str = "test content", suffix: str = ".txt") -> Path:
30
+ """
31
+ Create a temporary file.
32
+
33
+ Args:
34
+ content: Content to write to the file
35
+ suffix: File suffix/extension
36
+
37
+ Returns:
38
+ Path to the created temporary file
39
+ """
40
+ with foundation_temp_file(suffix=suffix, text=True, cleanup=False) as path:
41
+ path.write_text(content)
42
+ created_files.append(path)
43
+ return path
44
+
45
+ yield _make_temp_file
46
+
47
+ # Cleanup all created files
48
+ for path in created_files:
49
+ safe_delete(path, missing_ok=True)
50
+
51
+
52
+ @pytest.fixture
53
+ def temp_named_file():
54
+ """
55
+ Create a named temporary file factory.
56
+
57
+ Returns:
58
+ Function that creates named temporary files.
59
+ """
60
+ created_files = []
61
+
62
+ def _make_named_file(
63
+ content: bytes | str = None,
64
+ suffix: str = "",
65
+ prefix: str = "tmp",
66
+ dir: Path | str = None,
67
+ mode: str = "w+b",
68
+ delete: bool = False,
69
+ ) -> Path:
70
+ """
71
+ Create a named temporary file.
72
+
73
+ Args:
74
+ content: Optional content to write
75
+ suffix: File suffix
76
+ prefix: File prefix
77
+ dir: Directory for the file
78
+ mode: File mode
79
+ delete: Whether to delete on close
80
+
81
+ Returns:
82
+ Path to the created file
83
+ """
84
+ if isinstance(dir, Path):
85
+ dir = str(dir)
86
+
87
+ # Use Foundation's temp_file with cleanup=False since we manage cleanup
88
+ with foundation_temp_file(
89
+ suffix=suffix, prefix=prefix, dir=dir, text="b" not in mode, cleanup=False
90
+ ) as path:
91
+ if content is not None:
92
+ if isinstance(content, str):
93
+ if "b" in mode:
94
+ path.write_bytes(content.encode())
95
+ else:
96
+ path.write_text(content)
97
+ else:
98
+ path.write_bytes(content)
99
+
100
+ if not delete:
101
+ created_files.append(path)
102
+
103
+ return path
104
+
105
+ yield _make_named_file
106
+
107
+ # Cleanup
108
+ for path in created_files:
109
+ safe_delete(path, missing_ok=True)
110
+
111
+
112
+ @pytest.fixture
113
+ def temp_file_with_content():
114
+ """
115
+ Create temporary files with specific content.
116
+
117
+ Returns:
118
+ Function that creates files with content.
119
+ """
120
+ created_files = []
121
+
122
+ def _make_file(content: str | bytes, suffix: str = ".txt", encoding: str = "utf-8") -> Path:
123
+ """
124
+ Create a temporary file with content.
125
+
126
+ Args:
127
+ content: Content to write
128
+ suffix: File suffix
129
+ encoding: Text encoding (for str content)
130
+
131
+ Returns:
132
+ Path to created file
133
+ """
134
+ # Use Foundation's temp_file
135
+ with foundation_temp_file(suffix=suffix, text=not isinstance(content, bytes), cleanup=False) as path:
136
+ if isinstance(content, bytes):
137
+ path.write_bytes(content)
138
+ else:
139
+ path.write_text(content, encoding=encoding)
140
+
141
+ created_files.append(path)
142
+ return path
143
+
144
+ yield _make_file
145
+
146
+ # Cleanup
147
+ for path in created_files:
148
+ safe_delete(path, missing_ok=True)
149
+
150
+
151
+ @pytest.fixture
152
+ def temp_binary_file():
153
+ """
154
+ Create temporary binary files.
155
+
156
+ Returns:
157
+ Function that creates binary files.
158
+ """
159
+ created_files = []
160
+
161
+ def _make_binary(size: int = 1024, pattern: bytes = None, suffix: str = ".bin") -> Path:
162
+ """
163
+ Create a temporary binary file.
164
+
165
+ Args:
166
+ size: File size in bytes
167
+ pattern: Optional byte pattern to repeat
168
+ suffix: File suffix
169
+
170
+ Returns:
171
+ Path to created binary file
172
+ """
173
+ if pattern is None:
174
+ # Create pseudo-random binary data
175
+ content = bytes(random.randint(0, 255) for _ in range(size))
176
+ else:
177
+ # Repeat pattern to reach size
178
+ repetitions = size // len(pattern) + 1
179
+ content = (pattern * repetitions)[:size]
180
+
181
+ with foundation_temp_file(suffix=suffix, text=False, cleanup=False) as path:
182
+ path.write_bytes(content)
183
+
184
+ created_files.append(path)
185
+ return path
186
+
187
+ yield _make_binary
188
+
189
+ # Cleanup
190
+ for path in created_files:
191
+ safe_delete(path, missing_ok=True)
192
+
193
+
194
+ @pytest.fixture
195
+ def temp_csv_file():
196
+ """
197
+ Create temporary CSV files for testing.
198
+
199
+ Returns:
200
+ Function that creates CSV files.
201
+ """
202
+ created_files = []
203
+
204
+ def _make_csv(headers: list[str], rows: list[list], suffix: str = ".csv") -> Path:
205
+ """
206
+ Create a temporary CSV file.
207
+
208
+ Args:
209
+ headers: Column headers
210
+ rows: Data rows
211
+ suffix: File suffix
212
+
213
+ Returns:
214
+ Path to created CSV file
215
+ """
216
+ with foundation_temp_file(suffix=suffix, text=True, cleanup=False) as path:
217
+ with open(path, "w", newline="") as f:
218
+ writer = csv.writer(f)
219
+ writer.writerow(headers)
220
+ writer.writerows(rows)
221
+
222
+ created_files.append(path)
223
+ return path
224
+
225
+ yield _make_csv
226
+
227
+ # Cleanup
228
+ for path in created_files:
229
+ safe_delete(path, missing_ok=True)
230
+
231
+
232
+ @pytest.fixture
233
+ def temp_json_file():
234
+ """
235
+ Create temporary JSON files for testing.
236
+
237
+ Returns:
238
+ Function that creates JSON files.
239
+ """
240
+ created_files = []
241
+
242
+ def _make_json(data: dict | list, suffix: str = ".json", indent: int = 2) -> Path:
243
+ """
244
+ Create a temporary JSON file.
245
+
246
+ Args:
247
+ data: JSON data to write
248
+ suffix: File suffix
249
+ indent: JSON indentation
250
+
251
+ Returns:
252
+ Path to created JSON file
253
+ """
254
+ with foundation_temp_file(suffix=suffix, text=True, cleanup=False) as path:
255
+ with open(path, "w") as f:
256
+ json.dump(data, f, indent=indent)
257
+
258
+ created_files.append(path)
259
+ return path
260
+
261
+ yield _make_json
262
+
263
+ # Cleanup
264
+ for path in created_files:
265
+ safe_delete(path, missing_ok=True)
266
+
267
+
268
+ __all__ = [
269
+ "temp_binary_file",
270
+ "temp_csv_file",
271
+ "temp_file",
272
+ "temp_file_with_content",
273
+ "temp_json_file",
274
+ "temp_named_file",
275
+ ]
@@ -0,0 +1,105 @@
1
+ """
2
+ Directory-specific test fixtures.
3
+
4
+ Fixtures for creating temporary directories, nested structures,
5
+ and standard test directory layouts.
6
+ """
7
+
8
+ from collections.abc import Generator
9
+ from pathlib import Path
10
+
11
+ import pytest
12
+
13
+ from provide.foundation.file import temp_dir as foundation_temp_dir
14
+
15
+
16
+ @pytest.fixture
17
+ def temp_directory() -> Generator[Path, None, None]:
18
+ """
19
+ Create a temporary directory that's cleaned up after test.
20
+
21
+ Yields:
22
+ Path to the temporary directory.
23
+ """
24
+ with foundation_temp_dir() as temp_dir:
25
+ yield temp_dir
26
+
27
+
28
+ @pytest.fixture
29
+ def test_files_structure() -> Generator[tuple[Path, Path], None, None]:
30
+ """
31
+ Create standard test file structure with files and subdirectories.
32
+
33
+ Creates:
34
+ - source/
35
+ - file1.txt (contains "Content 1")
36
+ - file2.txt (contains "Content 2")
37
+ - subdir/
38
+ - file3.txt (contains "Content 3")
39
+
40
+ Yields:
41
+ Tuple of (temp_path, source_path)
42
+ """
43
+ with foundation_temp_dir() as path:
44
+ source = path / "source"
45
+ source.mkdir()
46
+
47
+ # Create test files
48
+ (source / "file1.txt").write_text("Content 1")
49
+ (source / "file2.txt").write_text("Content 2")
50
+
51
+ # Create subdirectory with files
52
+ subdir = source / "subdir"
53
+ subdir.mkdir()
54
+ (subdir / "file3.txt").write_text("Content 3")
55
+
56
+ yield path, source
57
+
58
+
59
+ @pytest.fixture
60
+ def nested_directory_structure() -> Generator[Path, None, None]:
61
+ """
62
+ Create a deeply nested directory structure for testing.
63
+
64
+ Creates:
65
+ - level1/
66
+ - level2/
67
+ - level3/
68
+ - deep_file.txt
69
+ - file_l2.txt
70
+ - file_l1.txt
71
+
72
+ Yields:
73
+ Path to the root of the structure.
74
+ """
75
+ with foundation_temp_dir() as root:
76
+ # Create nested structure
77
+ deep_dir = root / "level1" / "level2" / "level3"
78
+ deep_dir.mkdir(parents=True)
79
+
80
+ # Add files at different levels
81
+ (root / "file_l1.txt").write_text("Level 1 file")
82
+ (root / "level1" / "file_l2.txt").write_text("Level 2 file")
83
+ (deep_dir / "deep_file.txt").write_text("Deep file")
84
+
85
+ yield root
86
+
87
+
88
+ @pytest.fixture
89
+ def empty_directory() -> Generator[Path, None, None]:
90
+ """
91
+ Create an empty temporary directory.
92
+
93
+ Yields:
94
+ Path to an empty directory.
95
+ """
96
+ with foundation_temp_dir() as temp_dir:
97
+ yield temp_dir
98
+
99
+
100
+ __all__ = [
101
+ "empty_directory",
102
+ "nested_directory_structure",
103
+ "temp_directory",
104
+ "test_files_structure",
105
+ ]
@@ -0,0 +1,49 @@
1
+ """
2
+ File and Directory Test Fixtures.
3
+
4
+ Core file testing fixtures with re-exports from specialized modules.
5
+ Common fixtures for testing file operations, creating temporary directories,
6
+ and standard test file structures used across the provide-io ecosystem.
7
+ """
8
+
9
+ # Re-export all fixtures from specialized modules
10
+ from provide.testkit.file.content_fixtures import (
11
+ temp_binary_file,
12
+ temp_csv_file,
13
+ temp_file,
14
+ temp_file_with_content,
15
+ temp_json_file,
16
+ temp_named_file,
17
+ )
18
+ from provide.testkit.file.directory_fixtures import (
19
+ empty_directory,
20
+ nested_directory_structure,
21
+ temp_directory,
22
+ test_files_structure,
23
+ )
24
+ from provide.testkit.file.special_fixtures import (
25
+ binary_file,
26
+ readonly_file,
27
+ temp_executable_file,
28
+ temp_symlink,
29
+ )
30
+
31
+ __all__ = [
32
+ # Directory fixtures
33
+ "temp_directory",
34
+ "test_files_structure",
35
+ "nested_directory_structure",
36
+ "empty_directory",
37
+ # Special file fixtures
38
+ "binary_file",
39
+ "readonly_file",
40
+ "temp_symlink",
41
+ "temp_executable_file",
42
+ # Content-based fixtures
43
+ "temp_file",
44
+ "temp_named_file",
45
+ "temp_file_with_content",
46
+ "temp_binary_file",
47
+ "temp_csv_file",
48
+ "temp_json_file",
49
+ ]
@@ -0,0 +1,141 @@
1
+ """
2
+ Special file test fixtures.
3
+
4
+ Fixtures for creating specialized files like binary files, read-only files,
5
+ symbolic links, and executable files.
6
+ """
7
+
8
+ from collections.abc import Generator
9
+ from pathlib import Path
10
+ import stat
11
+
12
+ import pytest
13
+
14
+ from provide.foundation.file import temp_file as foundation_temp_file
15
+ from provide.foundation.file.safe import safe_delete
16
+
17
+
18
+ @pytest.fixture
19
+ def binary_file() -> Generator[Path, None, None]:
20
+ """
21
+ Create a temporary binary file for testing.
22
+
23
+ Yields:
24
+ Path to a binary file containing sample binary data.
25
+ """
26
+ with foundation_temp_file(suffix=".bin", text=False, cleanup=False) as path:
27
+ # Write some binary data
28
+ path.write_bytes(
29
+ b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09" + b"\xff\xfe\xfd\xfc\xfb\xfa\xf9\xf8\xf7\xf6"
30
+ )
31
+
32
+ yield path
33
+ safe_delete(path, missing_ok=True)
34
+
35
+
36
+ @pytest.fixture
37
+ def readonly_file() -> Generator[Path, None, None]:
38
+ """
39
+ Create a read-only file for permission testing.
40
+
41
+ Yields:
42
+ Path to a read-only file.
43
+ """
44
+ with foundation_temp_file(suffix=".txt", text=True, cleanup=False) as path:
45
+ path.write_text("Read-only content")
46
+
47
+ # Make file read-only
48
+ path.chmod(0o444)
49
+
50
+ yield path
51
+
52
+ # Restore write permission for cleanup
53
+ path.chmod(0o644)
54
+ safe_delete(path, missing_ok=True)
55
+
56
+
57
+ @pytest.fixture
58
+ def temp_symlink():
59
+ """
60
+ Create temporary symbolic links for testing.
61
+
62
+ Returns:
63
+ Function that creates symbolic links.
64
+ """
65
+ created_links = []
66
+
67
+ def _make_symlink(target: Path | str, link_name: Path | str = None) -> Path:
68
+ """
69
+ Create a temporary symbolic link.
70
+
71
+ Args:
72
+ target: Target path for the symlink
73
+ link_name: Optional link name (auto-generated if None)
74
+
75
+ Returns:
76
+ Path to created symlink
77
+ """
78
+ target = Path(target)
79
+
80
+ if link_name is None:
81
+ with foundation_temp_file(cleanup=True) as temp_path:
82
+ link_name = Path(str(temp_path) + "_link")
83
+ else:
84
+ link_name = Path(link_name)
85
+
86
+ link_name.symlink_to(target)
87
+ created_links.append(link_name)
88
+
89
+ return link_name
90
+
91
+ yield _make_symlink
92
+
93
+ # Cleanup
94
+ for link in created_links:
95
+ safe_delete(link, missing_ok=True)
96
+
97
+
98
+ @pytest.fixture
99
+ def temp_executable_file():
100
+ """
101
+ Create temporary executable files for testing.
102
+
103
+ Returns:
104
+ Function that creates executable files.
105
+ """
106
+ created_files = []
107
+
108
+ def _make_executable(content: str = "#!/bin/sh\necho 'test'\n", suffix: str = ".sh") -> Path:
109
+ """
110
+ Create a temporary executable file.
111
+
112
+ Args:
113
+ content: Script content
114
+ suffix: File suffix
115
+
116
+ Returns:
117
+ Path to created executable file
118
+ """
119
+ with foundation_temp_file(suffix=suffix, text=True, cleanup=False) as path:
120
+ path.write_text(content)
121
+
122
+ # Make executable
123
+ current = path.stat().st_mode
124
+ path.chmod(current | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
125
+
126
+ created_files.append(path)
127
+ return path
128
+
129
+ yield _make_executable
130
+
131
+ # Cleanup
132
+ for path in created_files:
133
+ safe_delete(path, missing_ok=True)
134
+
135
+
136
+ __all__ = [
137
+ "binary_file",
138
+ "readonly_file",
139
+ "temp_executable_file",
140
+ "temp_symlink",
141
+ ]