pytest-isolated 0.5.0__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 (27) hide show
  1. pytest_isolated-0.5.0/LICENSE +21 -0
  2. pytest_isolated-0.5.0/PKG-INFO +391 -0
  3. pytest_isolated-0.5.0/README.md +352 -0
  4. pytest_isolated-0.5.0/pyproject.toml +109 -0
  5. pytest_isolated-0.5.0/setup.cfg +4 -0
  6. pytest_isolated-0.5.0/src/pytest_isolated/__init__.py +3 -0
  7. pytest_isolated-0.5.0/src/pytest_isolated/config.py +183 -0
  8. pytest_isolated-0.5.0/src/pytest_isolated/execution.py +588 -0
  9. pytest_isolated-0.5.0/src/pytest_isolated/grouping.py +157 -0
  10. pytest_isolated-0.5.0/src/pytest_isolated/plugin.py +19 -0
  11. pytest_isolated-0.5.0/src/pytest_isolated/py.typed +0 -0
  12. pytest_isolated-0.5.0/src/pytest_isolated/reporting.py +210 -0
  13. pytest_isolated-0.5.0/src/pytest_isolated.egg-info/PKG-INFO +391 -0
  14. pytest_isolated-0.5.0/src/pytest_isolated.egg-info/SOURCES.txt +25 -0
  15. pytest_isolated-0.5.0/src/pytest_isolated.egg-info/dependency_links.txt +1 -0
  16. pytest_isolated-0.5.0/src/pytest_isolated.egg-info/entry_points.txt +2 -0
  17. pytest_isolated-0.5.0/src/pytest_isolated.egg-info/requires.txt +11 -0
  18. pytest_isolated-0.5.0/src/pytest_isolated.egg-info/top_level.txt +1 -0
  19. pytest_isolated-0.5.0/tests/test_app_isolation.py +65 -0
  20. pytest_isolated-0.5.0/tests/test_basic.py +113 -0
  21. pytest_isolated-0.5.0/tests/test_execution.py +364 -0
  22. pytest_isolated-0.5.0/tests/test_execution_units.py +435 -0
  23. pytest_isolated-0.5.0/tests/test_filtering.py +236 -0
  24. pytest_isolated-0.5.0/tests/test_grouping.py +577 -0
  25. pytest_isolated-0.5.0/tests/test_options.py +505 -0
  26. pytest_isolated-0.5.0/tests/test_plugin_compatibility.py +176 -0
  27. pytest_isolated-0.5.0/tests/test_reporting.py +285 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bryn Lloyd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,391 @@
1
+ Metadata-Version: 2.4
2
+ Name: pytest-isolated
3
+ Version: 0.5.0
4
+ Summary: Run marked pytest tests in grouped subprocesses (cross-platform).
5
+ Author: pytest-isolated contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/dyollb/pytest-isolated
8
+ Project-URL: Repository, https://github.com/dyollb/pytest-isolated
9
+ Project-URL: Issues, https://github.com/dyollb/pytest-isolated/issues
10
+ Keywords: testing,cross-platform,pytest,process-isolation,pytest-plugin,subprocess,crash-handling,python-testing,test-isolation
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Framework :: Pytest
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Operating System :: MacOS
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Topic :: Software Development :: Testing
25
+ Classifier: Topic :: Software Development :: Quality Assurance
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.10
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: pytest>=7.0
31
+ Provides-Extra: dev
32
+ Requires-Dist: build; extra == "dev"
33
+ Requires-Dist: mypy; extra == "dev"
34
+ Requires-Dist: pre-commit; extra == "dev"
35
+ Requires-Dist: pytest-forked; sys_platform != "win32" and extra == "dev"
36
+ Requires-Dist: pytest-timeout; extra == "dev"
37
+ Requires-Dist: ruff; extra == "dev"
38
+ Dynamic: license-file
39
+
40
+ # pytest-isolated
41
+
42
+ [![Tests](https://github.com/dyollb/pytest-isolated/actions/workflows/test.yml/badge.svg)](https://github.com/dyollb/pytest-isolated/actions/workflows/test.yml)
43
+ [![PyPI](https://img.shields.io/pypi/v/pytest-isolated.svg)](https://pypi.org/project/pytest-isolated/)
44
+ [![Python Version](https://img.shields.io/pypi/pyversions/pytest-isolated.svg)](https://pypi.org/project/pytest-isolated/)
45
+ [![Downloads](https://static.pepy.tech/badge/pytest-isolated/month)](https://pepy.tech/project/pytest-isolated)
46
+ [![License](https://img.shields.io/pypi/l/pytest-isolated.svg)](https://github.com/dyollb/pytest-isolated/blob/main/LICENSE)
47
+ [![Platform](https://img.shields.io/badge/platform-windows%20%7C%20linux%20%7C%20macos-lightgrey.svg)](https://github.com/dyollb/pytest-isolated)
48
+
49
+ A cross-platform pytest plugin that runs marked tests in isolated subprocesses with intelligent grouping.
50
+
51
+ ## Why pytest-isolated?
52
+
53
+ Ever had a test that passes alone but fails in the suite? Or tests that mysteriously hang on CI? pytest-isolated solves this by running tests in completely isolated subprocesses.
54
+
55
+ **Common problems it solves:**
56
+
57
+ - ✅ Segfaults and crashes don't kill your entire test suite
58
+ - ✅ Tests modifying global state don't affect each other
59
+ - ✅ Hanging tests timeout without blocking other tests
60
+ - ✅ C extension crashes are contained
61
+ - ✅ Resource leaks are isolated per test
62
+
63
+ ## Cheatsheet for pytest-forked users
64
+
65
+ This plugin is inspired by [pytest-forked](https://github.com/pytest-dev/pytest-forked). See [pytest-forked migration guide](docs/pytest-forked-migration.md) for a quick reference comparing features.
66
+
67
+ ## Quick Start
68
+
69
+ **1. Install:**
70
+
71
+ ```bash
72
+ pip install pytest-isolated
73
+ ```
74
+
75
+ **2. Mark a flaky test:**
76
+
77
+ ```py
78
+ # Example 1: Simple isolation
79
+ @pytest.mark.isolated
80
+ def test_with_clean_state():
81
+ import os
82
+ os.environ["DEBUG"] = "true"
83
+ # Other tests won't see this change
84
+
85
+ # Example 2: Crash protection
86
+ @pytest.mark.isolated
87
+ def test_that_crashes():
88
+ import ctypes
89
+ ctypes.string_at(0) # Crash is contained!
90
+ ```
91
+
92
+ **3. Run pytest normally:**
93
+
94
+ ```bash
95
+ pytest # Crash is isolated, suite continues
96
+ ```
97
+
98
+ ## Features
99
+
100
+ - Run tests in fresh Python subprocesses to prevent state pollution
101
+ - Group related tests to run together in the same subprocess
102
+ - Handles crashes, timeouts, and setup/teardown failures
103
+ - Respects pytest's standard output capture settings (`-s`, `--capture`)
104
+ - Works with pytest reporters (JUnit XML, etc.)
105
+ - Configurable timeouts to prevent hanging subprocesses
106
+ - Cross-platform: Linux, macOS, Windows
107
+
108
+ ## Basic Usage
109
+
110
+ Mark tests to run in isolated subprocesses:
111
+
112
+ ```python
113
+ import pytest
114
+
115
+ @pytest.mark.isolated
116
+ def test_isolated():
117
+ # Runs in a fresh subprocess
118
+ assert True
119
+ ```
120
+
121
+ Tests with the same group run together in one subprocess:
122
+
123
+ ```python
124
+ # Using keyword argument
125
+ @pytest.mark.isolated(group="mygroup")
126
+ def test_one():
127
+ shared_state.append(1)
128
+
129
+ @pytest.mark.isolated(group="mygroup")
130
+ def test_two():
131
+ # Sees state from test_one
132
+ assert len(shared_state) == 2
133
+
134
+ # Or using positional argument
135
+ @pytest.mark.isolated("mygroup")
136
+ def test_three():
137
+ shared_state.append(3)
138
+ ```
139
+
140
+ Set timeout per test group:
141
+
142
+ ```python
143
+ @pytest.mark.isolated(timeout=30)
144
+ def test_with_timeout():
145
+ # This group gets 30 second timeout (overrides global setting)
146
+ expensive_operation()
147
+ ```
148
+
149
+ **Note:** Tests without an explicit `group` parameter each run in their own unique subprocess for maximum isolation.
150
+
151
+ ### Class and Module Markers
152
+
153
+ Apply to entire classes to share state between methods:
154
+
155
+ ```python
156
+ @pytest.mark.isolated
157
+ class TestDatabase:
158
+ def test_setup(self):
159
+ self.db = create_database()
160
+
161
+ def test_query(self):
162
+ # Shares state with test_setup
163
+ result = self.db.query("SELECT 1")
164
+ assert result
165
+ ```
166
+
167
+ Apply to entire modules using `pytestmark`:
168
+
169
+ ```python
170
+ import pytest
171
+
172
+ pytestmark = pytest.mark.isolated
173
+
174
+ def test_one():
175
+ # Runs in isolated subprocess
176
+ pass
177
+
178
+ def test_two():
179
+ # Shares subprocess with test_one
180
+ pass
181
+ ```
182
+
183
+ ### Marker Precedence
184
+
185
+ When `@pytest.mark.isolated` appears at multiple scopes, the **closest marker wins** (following pytest's standard `get_closest_marker` convention):
186
+
187
+ 1. **Explicit `group` always wins.** `@pytest.mark.isolated(group="name")` uses that group regardless of scope.
188
+ 1. **Function > class > module.** A function-level `@pytest.mark.isolated` breaks out of any class or module group into its own subprocess.
189
+ 1. **Class > module.** A class marker groups its methods together, even inside a module with `pytestmark`.
190
+
191
+ ## Configuration
192
+
193
+ ### Command Line
194
+
195
+ ```bash
196
+ # Run all tests in isolation (even without @pytest.mark.isolated)
197
+ pytest --isolated
198
+
199
+ # Set isolated test timeout (seconds)
200
+ pytest --isolated-timeout=60
201
+
202
+ # Disable subprocess isolation for debugging
203
+ pytest --no-isolation
204
+
205
+ # Control output capture (standard pytest flags work with isolated tests)
206
+ pytest -s # Disable capture, show all output
207
+ pytest --capture=sys # Capture at sys.stdout/stderr level
208
+
209
+ # Combine with pytest debugger
210
+ pytest --no-isolation --pdb
211
+ ```
212
+
213
+ ### pytest.ini / pyproject.toml
214
+
215
+ ```ini
216
+ [pytest]
217
+ isolated_timeout = 300
218
+ ```
219
+
220
+ Or in `pyproject.toml`:
221
+
222
+ ```toml
223
+ [tool.pytest.ini_options]
224
+ isolated_timeout = "300"
225
+ ```
226
+
227
+ ## Real-World Use Cases
228
+
229
+ ### 🌍 Isolating Global State
230
+
231
+ Prevent environment variable and configuration changes from leaking between tests:
232
+
233
+ ```python
234
+ @pytest.mark.isolated
235
+ def test_modifies_environ():
236
+ import os
237
+ os.environ["MY_VAR"] = "value"
238
+ # Won't affect other tests
239
+
240
+ @pytest.mark.isolated
241
+ def test_clean_environ():
242
+ import os
243
+ assert "MY_VAR" not in os.environ # Fresh environment
244
+ ```
245
+
246
+ ### 🔄 Testing Singletons
247
+
248
+ Group tests that share singleton state, or isolate them completely:
249
+
250
+ ```python
251
+ @pytest.mark.isolated(group="singleton_tests")
252
+ def test_singleton_init():
253
+ from myapp import DatabaseConnection
254
+ db = DatabaseConnection.get_instance()
255
+ assert db is not None
256
+
257
+ @pytest.mark.isolated(group="singleton_tests")
258
+ def test_singleton_reuse():
259
+ db = DatabaseConnection.get_instance()
260
+ # Same instance as previous test in group
261
+ ```
262
+
263
+ ### ⚙️ Testing Process Resources
264
+
265
+ Safely modify signal handlers and other process-level settings:
266
+
267
+ ```python
268
+ @pytest.mark.isolated
269
+ def test_signal_handlers():
270
+ import signal
271
+ signal.signal(signal.SIGTERM, custom_handler)
272
+ # Won't interfere with pytest or other tests
273
+ ```
274
+
275
+ ### 💾 Testing Database Migrations
276
+
277
+ Group expensive operations while maintaining isolation from other tests:
278
+
279
+ ```python
280
+ @pytest.mark.isolated(group="database")
281
+ class TestDatabase:
282
+ def test_migration(self):
283
+ db.migrate() # Expensive operation, runs once
284
+
285
+ def test_query(self):
286
+ # Reuses same DB from test_migration
287
+ result = db.query("SELECT 1")
288
+ assert result == [(1,)]
289
+ ```
290
+
291
+ ### 🔧 Testing C Extensions
292
+
293
+ Isolate tests that might crash from C code:
294
+
295
+ ```python
296
+ @pytest.mark.isolated
297
+ def test_numpy_operation():
298
+ import numpy as np
299
+ # If this segfaults, other tests still run
300
+ result = np.array([1, 2, 3])
301
+ assert len(result) == 3
302
+ ```
303
+
304
+ ## Output and Reporting
305
+
306
+ Failed tests automatically capture and display stdout/stderr:
307
+
308
+ ```python
309
+ @pytest.mark.isolated
310
+ def test_failing():
311
+ print("Debug info")
312
+ assert False
313
+ ```
314
+
315
+ Works with standard pytest reporters:
316
+
317
+ ```bash
318
+ pytest --junitxml=report.xml --durations=10
319
+ ```
320
+
321
+ ## Limitations
322
+
323
+ **Fixtures**: Module/session fixtures run in each subprocess group. Cannot share fixture objects between parent and subprocess.
324
+
325
+ **Debugging**: Use `--no-isolation` to run all tests in the main process for easier debugging with `pdb` or IDE debuggers.
326
+
327
+ **Performance**: Subprocess creation adds ~100-500ms per group. Group related tests to minimize overhead. Only mark tests that need isolation.
328
+
329
+ ### CLI Option Compatibility
330
+
331
+ pytest-isolated uses a forward-by-default (blacklist) model:
332
+
333
+ - Some options are blocked with a clear `UsageError` when isolation is active.
334
+ - Some options are supported but handled in the parent process (not forwarded).
335
+ - All remaining options (including custom plugin options) are forwarded to children.
336
+
337
+ See [Incompatible options reference](docs/incompatible-options.md) for the full list and workarounds.
338
+
339
+ ## Advanced
340
+
341
+ ### Coverage Integration
342
+
343
+ To collect coverage from isolated tests, enable subprocess tracking in `pyproject.toml`:
344
+
345
+ ```toml
346
+ [tool.coverage.run]
347
+ parallel = true
348
+ concurrency = ["subprocess"]
349
+ ```
350
+
351
+ See the [coverage.py subprocess documentation](https://coverage.readthedocs.io/en/latest/subprocess.html) for details.
352
+
353
+ ### Timeout Handling
354
+
355
+ ```bash
356
+ pytest --isolated-timeout=30
357
+ ```
358
+
359
+ Timeout errors are clearly reported with the group name and timeout duration.
360
+
361
+ ### Crash Detection
362
+
363
+ If a subprocess crashes, tests in that group are marked as failed with exit code information.
364
+
365
+ ### Subprocess Detection
366
+
367
+ ```python
368
+ import os
369
+
370
+ if os.environ.get("PYTEST_RUNNING_IN_SUBPROCESS") == "1":
371
+ # Running in subprocess
372
+ pass
373
+ ```
374
+
375
+ ## Troubleshooting
376
+
377
+ **Tests timing out**: Increase timeout with `--isolated-timeout=600`
378
+
379
+ **Missing output**: Use `-s` or `--capture=no` to see output from passing tests, or `-v` for verbose output. pytest-isolated respects pytest's standard capture settings.
380
+
381
+ **Subprocess crashes**: Check for segfaults, OOM, or signal issues. Run with `-v` for details.
382
+
383
+ ## Contributing
384
+
385
+ 1. Install pre-commit: `pip install pre-commit && pre-commit install`
386
+ 1. Run tests: `pytest tests/ -v`
387
+ 1. Open an issue before submitting PRs for new features
388
+
389
+ ## License
390
+
391
+ MIT License - see LICENSE file for details.