provide-testkit 0.5.2__tar.gz → 0.5.3__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.
- {provide_testkit-0.5.2/src/provide_testkit.egg-info → provide_testkit-0.5.3}/PKG-INFO +1 -1
- provide_testkit-0.5.3/VERSION +1 -0
- provide_testkit-0.5.3/src/provide/testkit/_capture.py +144 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3/src/provide_testkit.egg-info}/PKG-INFO +1 -1
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/tests/test_capture_stream_swap.py +87 -0
- provide_testkit-0.5.2/VERSION +0 -1
- provide_testkit-0.5.2/src/provide/testkit/_capture.py +0 -81
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/LICENSE +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/README.md +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/pyproject.toml +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/setup.cfg +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/__main__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/_blocker.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/_early_init.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/_install_blocker.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/_install_pth.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/_streams.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/base/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/base/foundation.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/base/harness.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/base/minimal.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/chaos/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/chaos/concurrency_strategies.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/chaos/fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/chaos/io_strategies.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/chaos/strategies.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/chaos/time_strategies.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/cli/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/cli/runner.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/cli/testing.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/common/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/common/fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/conftest.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/crypto/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/crypto/fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/file/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/file/content_fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/file/directory_fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/file/fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/file/special_fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/file/temp.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/hub/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/hub/fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/logger/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/logger/hooks.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/logger/mocks.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/logger/reset.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/main.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/mocking/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/mocking/fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/mocking/time.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/process/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/process/async_fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/process/fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/process/script_assertions.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/process/script_fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/process/subprocess_fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/process/system_fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/provide_testkit_init.pth +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/py.typed +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/pytest_plugin.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/artifacts.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/base.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/cli.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/complexity/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/complexity/analyzer.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/complexity/fixture.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/coverage/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/coverage/fixture.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/coverage/reporter.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/coverage/tracker.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/decorators.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/documentation/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/documentation/checker.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/documentation/fixture.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/profiling/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/profiling/fixture.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/profiling/profiler.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/report.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/runner.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/security/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/security/fixture.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/security/gitleaks_scanner.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/security/pip_audit_scanner.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/security/safety_scanner.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/security/scanner.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/security/semgrep_scanner.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/security/trufflehog_scanner.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/streams/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/streams/testing.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/threading/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/threading/basic_fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/threading/data_fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/threading/execution_fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/threading/fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/threading/sync_fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/time/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/time/classes.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/time/controlled.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/time/fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/time/freezing.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/time/measurement.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/time/mocking.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/time/rate_limiting.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/transport/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/transport/fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/utils/__init__.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/utils/environment.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/utils/temp_env.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide_testkit.egg-info/SOURCES.txt +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide_testkit.egg-info/dependency_links.txt +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide_testkit.egg-info/entry_points.txt +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide_testkit.egg-info/requires.txt +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide_testkit.egg-info/top_level.txt +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/tests/test_async_fixtures.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/tests/test_early_init.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/tests/test_placeholder.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/tests/test_pth_installation.py +0 -0
- {provide_testkit-0.5.2 → provide_testkit-0.5.3}/tests/test_stream_encoding.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.5.3
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#
|
|
2
|
+
# SPDX-FileCopyrightText: Copyright (c) provide.io llc. All rights reserved.
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
#
|
|
5
|
+
|
|
6
|
+
"""Capture that gives back the stream it took, not the one it remembers.
|
|
7
|
+
|
|
8
|
+
``SysCaptureBase.suspend`` hands ``sys.stdout`` back to the stream pytest saved
|
|
9
|
+
before capture started, and ``resume`` reinstates ``tmpfile`` -- the stream
|
|
10
|
+
capture installed at ``start``. Neither looks at what is actually there. A test
|
|
11
|
+
that swaps the stream after capture started owns something pytest has no record
|
|
12
|
+
of, so the swap is discarded on the first suspend and every write after it lands
|
|
13
|
+
in pytest's buffer instead of the caller's.
|
|
14
|
+
|
|
15
|
+
``log_cli = true`` makes that ordinary: pytest's live-log handler sits on the
|
|
16
|
+
root logger and suspends global capture around each record. One log line emitted
|
|
17
|
+
from inside ``CliRunner.invoke`` -- from any library, at any depth -- leaves
|
|
18
|
+
``result.output`` empty while the text itself is real and lands in pytest's own
|
|
19
|
+
capture. ``contextlib.redirect_stdout`` and hand-rolled stream fixtures lose
|
|
20
|
+
their swap the same way.
|
|
21
|
+
|
|
22
|
+
The pair below remembers the stream that was in place at suspend and returns it
|
|
23
|
+
at resume, which is what the two methods already claim to do. Teardown is
|
|
24
|
+
untouched: ``done`` still restores the stream capture replaced, so a swap left
|
|
25
|
+
behind by a test cannot outlive it.
|
|
26
|
+
|
|
27
|
+
This belongs in pytest, so the module is built to retire itself rather than to
|
|
28
|
+
be remembered. Installing drives a real capture through swap, suspend and
|
|
29
|
+
resume, and patches only a pytest that loses the swap -- one that keeps it is
|
|
30
|
+
left alone, by whatever route it came to keep it. A pytest that no longer has
|
|
31
|
+
the class costs the fix and nothing else: the import is asked for inside the
|
|
32
|
+
call rather than at module scope, so a reshaped capture module cannot take the
|
|
33
|
+
plugin, and every suite that loads it, down with it.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import io
|
|
39
|
+
import sys
|
|
40
|
+
from typing import TYPE_CHECKING, Any
|
|
41
|
+
|
|
42
|
+
if TYPE_CHECKING:
|
|
43
|
+
from _pytest.capture import SysCaptureBase
|
|
44
|
+
|
|
45
|
+
__all__ = ["install_capture_swap_fix"]
|
|
46
|
+
|
|
47
|
+
_INSTALLED_MARKER = "_provide_testkit_swap_safe"
|
|
48
|
+
# The stream is parked on pytest's object, so the name says whose it is.
|
|
49
|
+
_SWAPPED_IN = "_provide_testkit_swapped_in"
|
|
50
|
+
_STDOUT_FD = 1
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _suspend(self: SysCaptureBase[Any]) -> None:
|
|
54
|
+
self._assert_state("suspend", ("started", "suspended"))
|
|
55
|
+
# Suspending while suspended is legal, and the stream sitting there then is
|
|
56
|
+
# the one suspend itself installed -- not a swap worth remembering.
|
|
57
|
+
if self._state == "started":
|
|
58
|
+
setattr(self, _SWAPPED_IN, getattr(sys, self.name))
|
|
59
|
+
setattr(sys, self.name, self._old)
|
|
60
|
+
self._state = "suspended"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _resume(self: SysCaptureBase[Any]) -> None:
|
|
64
|
+
self._assert_state("resume", ("started", "suspended"))
|
|
65
|
+
if self._state == "started":
|
|
66
|
+
return
|
|
67
|
+
# A capture resumed without ever suspending has nothing recorded, and the
|
|
68
|
+
# stream capture installed is the right answer for it.
|
|
69
|
+
setattr(sys, self.name, getattr(self, _SWAPPED_IN, self.tmpfile))
|
|
70
|
+
self._state = "started"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _capture_classes() -> tuple[Any, type] | None:
|
|
74
|
+
"""The class to patch and the one to probe, or ``None`` from a pytest without them.
|
|
75
|
+
|
|
76
|
+
``SysCaptureBase`` carries the two methods for every capture that reaches
|
|
77
|
+
``sys``; ``SysCapture`` is the one that can be driven. Asking for them here
|
|
78
|
+
rather than at import is what keeps a renamed or removed class a lost fix
|
|
79
|
+
instead of an ``ImportError`` raised while the plugin is loading.
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
from _pytest.capture import SysCapture, SysCaptureBase
|
|
83
|
+
except (ImportError, AttributeError):
|
|
84
|
+
return None
|
|
85
|
+
return SysCaptureBase, SysCapture
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _drops_a_swap(probe_class: type) -> bool:
|
|
89
|
+
"""Whether this pytest loses a stream installed after capturing started.
|
|
90
|
+
|
|
91
|
+
The question is behavioural, so the answer is measured rather than read off
|
|
92
|
+
a version: start a capture, swap the stream, cycle suspend and resume, and
|
|
93
|
+
see which stream is left standing. A pytest that keeps the swap wants
|
|
94
|
+
nothing from this module, and a version number cannot say which pytest that
|
|
95
|
+
is -- a backport, a fork or a vendored copy all answer here for themselves.
|
|
96
|
+
|
|
97
|
+
A probe that cannot be driven at all -- a capture reshaped past recognition
|
|
98
|
+
-- reports no defect. Patching a class whose behaviour could not be
|
|
99
|
+
established is how a fix becomes the outage.
|
|
100
|
+
"""
|
|
101
|
+
saved = sys.stdout
|
|
102
|
+
try:
|
|
103
|
+
probe = probe_class(_STDOUT_FD)
|
|
104
|
+
probe.start()
|
|
105
|
+
try:
|
|
106
|
+
swapped_in = io.StringIO()
|
|
107
|
+
sys.stdout = swapped_in
|
|
108
|
+
probe.suspend()
|
|
109
|
+
probe.resume()
|
|
110
|
+
return sys.stdout is not swapped_in
|
|
111
|
+
finally:
|
|
112
|
+
probe.done()
|
|
113
|
+
except Exception:
|
|
114
|
+
return False
|
|
115
|
+
finally:
|
|
116
|
+
sys.stdout = saved
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def install_capture_swap_fix() -> bool:
|
|
120
|
+
"""Make pytest's stream capture preserve a swap made after it started.
|
|
121
|
+
|
|
122
|
+
Returns whether this call is the one that installed it. Declining leaves
|
|
123
|
+
pytest exactly as found -- a pytest that already keeps the swap, one whose
|
|
124
|
+
methods belong to another patcher, and one this module cannot recognise are
|
|
125
|
+
all cases where doing nothing is the whole job.
|
|
126
|
+
"""
|
|
127
|
+
classes = _capture_classes()
|
|
128
|
+
if classes is None:
|
|
129
|
+
return False
|
|
130
|
+
patch_target, probe_class = classes
|
|
131
|
+
|
|
132
|
+
# Read the marker off the class itself. A subclass inherits it, and asking
|
|
133
|
+
# through inheritance would report a fix that is not on the class in hand.
|
|
134
|
+
if _INSTALLED_MARKER in patch_target.__dict__:
|
|
135
|
+
return False
|
|
136
|
+
if any(getattr(patch_target, name).__module__ != "_pytest.capture" for name in ("suspend", "resume")):
|
|
137
|
+
return False
|
|
138
|
+
if not _drops_a_swap(probe_class):
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
patch_target.suspend = _suspend
|
|
142
|
+
patch_target.resume = _resume
|
|
143
|
+
setattr(patch_target, _INSTALLED_MARKER, True)
|
|
144
|
+
return True
|
|
@@ -22,6 +22,7 @@ import sys
|
|
|
22
22
|
from _pytest.capture import SysCapture
|
|
23
23
|
import pytest
|
|
24
24
|
|
|
25
|
+
from provide.testkit import _capture
|
|
25
26
|
from provide.testkit._capture import install_capture_swap_fix
|
|
26
27
|
|
|
27
28
|
STDOUT_FD = 1
|
|
@@ -140,3 +141,89 @@ def test_the_same_run_loses_its_output_without_this_plugin(pytester: pytest.Pyte
|
|
|
140
141
|
result = pytester.runpytest_subprocess("-o", "log_cli=true", "-p", "no:provide_testkit")
|
|
141
142
|
|
|
142
143
|
result.assert_outcomes(failed=1)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class _FixedSysCapture(SysCapture):
|
|
147
|
+
"""A pytest whose capture hands back the stream that was in place."""
|
|
148
|
+
|
|
149
|
+
def suspend(self) -> None:
|
|
150
|
+
self._assert_state("suspend", ("started", "suspended"))
|
|
151
|
+
if self._state == "started":
|
|
152
|
+
self._seen = getattr(sys, self.name)
|
|
153
|
+
setattr(sys, self.name, self._old)
|
|
154
|
+
self._state = "suspended"
|
|
155
|
+
|
|
156
|
+
def resume(self) -> None:
|
|
157
|
+
self._assert_state("resume", ("started", "suspended"))
|
|
158
|
+
if self._state == "started":
|
|
159
|
+
return
|
|
160
|
+
setattr(sys, self.name, getattr(self, "_seen", self.tmpfile))
|
|
161
|
+
self._state = "started"
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class _UnfixedSysCapture(SysCapture):
|
|
165
|
+
"""Pytest as shipped: resume reinstates the stream capture installed."""
|
|
166
|
+
|
|
167
|
+
def suspend(self) -> None:
|
|
168
|
+
self._assert_state("suspend", ("started", "suspended"))
|
|
169
|
+
setattr(sys, self.name, self._old)
|
|
170
|
+
self._state = "suspended"
|
|
171
|
+
|
|
172
|
+
def resume(self) -> None:
|
|
173
|
+
self._assert_state("resume", ("started", "suspended"))
|
|
174
|
+
if self._state == "started":
|
|
175
|
+
return
|
|
176
|
+
setattr(sys, self.name, self.tmpfile)
|
|
177
|
+
self._state = "started"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# Both stand-ins answer for pytest itself, so they must claim the origin the
|
|
181
|
+
# install check reads -- a class whose methods came from anywhere else is a
|
|
182
|
+
# different patcher, and declining that is a separate rule.
|
|
183
|
+
for _stand_in in (_FixedSysCapture, _UnfixedSysCapture):
|
|
184
|
+
_stand_in.suspend.__module__ = "_pytest.capture"
|
|
185
|
+
_stand_in.resume.__module__ = "_pytest.capture"
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_the_fix_is_declined_when_pytest_already_preserves_the_swap(
|
|
189
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
190
|
+
) -> None:
|
|
191
|
+
"""The exit route: a pytest that fixed this upstream is left alone."""
|
|
192
|
+
monkeypatch.setattr(_capture, "_capture_classes", lambda: (_FixedSysCapture, _FixedSysCapture))
|
|
193
|
+
|
|
194
|
+
installed = install_capture_swap_fix()
|
|
195
|
+
|
|
196
|
+
assert installed is False
|
|
197
|
+
assert _FixedSysCapture.suspend.__qualname__ == "_FixedSysCapture.suspend"
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def test_the_fix_is_installed_when_pytest_still_drops_the_swap(
|
|
201
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
202
|
+
) -> None:
|
|
203
|
+
"""The probe answers by behaviour, so it must still say yes to today's pytest."""
|
|
204
|
+
monkeypatch.setattr(_capture, "_capture_classes", lambda: (_UnfixedSysCapture, _UnfixedSysCapture))
|
|
205
|
+
|
|
206
|
+
installed = install_capture_swap_fix()
|
|
207
|
+
|
|
208
|
+
assert installed is True
|
|
209
|
+
assert _UnfixedSysCapture.suspend is _capture._suspend
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def test_a_pytest_without_the_class_leaves_the_plugin_importable(
|
|
213
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
214
|
+
) -> None:
|
|
215
|
+
"""A reshaped pytest must cost the fix, not every suite that loads the plugin."""
|
|
216
|
+
monkeypatch.setattr(_capture, "_capture_classes", lambda: None)
|
|
217
|
+
|
|
218
|
+
assert install_capture_swap_fix() is False
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@pytest.mark.usefixtures("stdout_restored")
|
|
222
|
+
def test_the_probe_gives_back_the_stream_it_found(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
223
|
+
"""Deciding whether to patch runs a real capture; it must leave no trace."""
|
|
224
|
+
monkeypatch.setattr(_capture, "_capture_classes", lambda: (_UnfixedSysCapture, _UnfixedSysCapture))
|
|
225
|
+
before = sys.stdout
|
|
226
|
+
|
|
227
|
+
install_capture_swap_fix()
|
|
228
|
+
|
|
229
|
+
assert sys.stdout is before
|
provide_testkit-0.5.2/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.5.2
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
#
|
|
2
|
-
# SPDX-FileCopyrightText: Copyright (c) provide.io llc. All rights reserved.
|
|
3
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
"""Capture that gives back the stream it took, not the one it remembers.
|
|
7
|
-
|
|
8
|
-
``SysCaptureBase.suspend`` hands ``sys.stdout`` back to the stream pytest saved
|
|
9
|
-
before capture started, and ``resume`` reinstates ``tmpfile`` -- the stream
|
|
10
|
-
capture installed at ``start``. Neither looks at what is actually there. A test
|
|
11
|
-
that swaps the stream after capture started owns something pytest has no record
|
|
12
|
-
of, so the swap is discarded on the first suspend and every write after it lands
|
|
13
|
-
in pytest's buffer instead of the caller's.
|
|
14
|
-
|
|
15
|
-
``log_cli = true`` makes that ordinary: pytest's live-log handler sits on the
|
|
16
|
-
root logger and suspends global capture around each record. One log line emitted
|
|
17
|
-
from inside ``CliRunner.invoke`` -- from any library, at any depth -- leaves
|
|
18
|
-
``result.output`` empty while the text itself is real and lands in pytest's own
|
|
19
|
-
capture. ``contextlib.redirect_stdout`` and hand-rolled stream fixtures lose
|
|
20
|
-
their swap the same way.
|
|
21
|
-
|
|
22
|
-
The pair below remembers the stream that was in place at suspend and returns it
|
|
23
|
-
at resume, which is what the two methods already claim to do. Teardown is
|
|
24
|
-
untouched: ``done`` still restores the stream capture replaced, so a swap left
|
|
25
|
-
behind by a test cannot outlive it.
|
|
26
|
-
|
|
27
|
-
This belongs in pytest, and the tests in ``tests/test_capture_stream_swap.py``
|
|
28
|
-
drive the real ``SysCapture``, so they fail loudly once pytest changes the
|
|
29
|
-
methods underneath -- whether it fixes this or reshapes the class. Drop this
|
|
30
|
-
module when the pytest floor carries the fix.
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
from __future__ import annotations
|
|
34
|
-
|
|
35
|
-
import sys
|
|
36
|
-
from typing import Any
|
|
37
|
-
|
|
38
|
-
from _pytest.capture import SysCaptureBase
|
|
39
|
-
|
|
40
|
-
__all__ = ["install_capture_swap_fix"]
|
|
41
|
-
|
|
42
|
-
_INSTALLED_MARKER = "_provide_testkit_swap_safe"
|
|
43
|
-
# The stream is parked on pytest's object, so the name says whose it is.
|
|
44
|
-
_SWAPPED_IN = "_provide_testkit_swapped_in"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _suspend(self: SysCaptureBase[Any]) -> None:
|
|
48
|
-
self._assert_state("suspend", ("started", "suspended"))
|
|
49
|
-
# Suspending while suspended is legal, and the stream sitting there then is
|
|
50
|
-
# the one suspend itself installed -- not a swap worth remembering.
|
|
51
|
-
if self._state == "started":
|
|
52
|
-
setattr(self, _SWAPPED_IN, getattr(sys, self.name))
|
|
53
|
-
setattr(sys, self.name, self._old)
|
|
54
|
-
self._state = "suspended"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def _resume(self: SysCaptureBase[Any]) -> None:
|
|
58
|
-
self._assert_state("resume", ("started", "suspended"))
|
|
59
|
-
if self._state == "started":
|
|
60
|
-
return
|
|
61
|
-
# A capture resumed without ever suspending has nothing recorded, and the
|
|
62
|
-
# stream capture installed is the right answer for it.
|
|
63
|
-
setattr(sys, self.name, getattr(self, _SWAPPED_IN, self.tmpfile))
|
|
64
|
-
self._state = "started"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def install_capture_swap_fix() -> bool:
|
|
68
|
-
"""Make pytest's stream capture preserve a swap made after it started.
|
|
69
|
-
|
|
70
|
-
Returns whether this call is the one that installed it. Declining leaves
|
|
71
|
-
pytest exactly as found: the fix is worth having, and silently layering it
|
|
72
|
-
over a patch someone else installed is not.
|
|
73
|
-
"""
|
|
74
|
-
if getattr(SysCaptureBase, _INSTALLED_MARKER, False):
|
|
75
|
-
return False
|
|
76
|
-
if any(getattr(SysCaptureBase, name).__module__ != "_pytest.capture" for name in ("suspend", "resume")):
|
|
77
|
-
return False
|
|
78
|
-
SysCaptureBase.suspend = _suspend # type: ignore[method-assign]
|
|
79
|
-
SysCaptureBase.resume = _resume # type: ignore[method-assign]
|
|
80
|
-
setattr(SysCaptureBase, _INSTALLED_MARKER, True)
|
|
81
|
-
return True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/chaos/concurrency_strategies.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/chaos/time_strategies.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/file/content_fixtures.py
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/file/directory_fixtures.py
RENAMED
|
File without changes
|
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/file/special_fixtures.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/process/async_fixtures.py
RENAMED
|
File without changes
|
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/process/script_assertions.py
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/process/script_fixtures.py
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/process/subprocess_fixtures.py
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/process/system_fixtures.py
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/provide_testkit_init.pth
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/complexity/__init__.py
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/complexity/analyzer.py
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/complexity/fixture.py
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/coverage/__init__.py
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/coverage/fixture.py
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/coverage/reporter.py
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/coverage/tracker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/documentation/checker.py
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/documentation/fixture.py
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/profiling/__init__.py
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/profiling/fixture.py
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/profiling/profiler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/security/__init__.py
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/security/fixture.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/quality/security/scanner.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/threading/basic_fixtures.py
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/threading/data_fixtures.py
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/threading/execution_fixtures.py
RENAMED
|
File without changes
|
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide/testkit/threading/sync_fixtures.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide_testkit.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{provide_testkit-0.5.2 → provide_testkit-0.5.3}/src/provide_testkit.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|