Repository navigation
Expand file tree
/
Copy pathfixtures.py
More file actions
113 lines (93 loc) · 3.87 KB
/
Copy pathfixtures.py
File metadata and controls
113 lines (93 loc) · 3.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
"""Scripted initial states shared by the CLI and Gix test runs."""
from contextlib import contextmanager
import logging
from pathlib import Path
import shutil
from zlib import crc32
from gix import Error, testtools
from git import Repo
from git.util import cwd
from test.cleanup import cleanup_directory
ROOT = Path(__file__).resolve().parents[2]
_logger = logging.getLogger(__name__)
class _WritableFixture:
"""A native fixture whose disposal preserves the suite's cleanup contract."""
def __init__(self, native):
self._native = native
def path(self):
if self._native is None:
raise RuntimeError("temporary fixture is closed")
return self._native.path()
def close(self, *, keep=False):
if self._native is None:
return
native, self._native = self._native, None
path = native.path()
retained = path.with_name(path.name + "-retained")
try:
if not keep:
cleanup_directory(path)
if path.exists():
# Release the native owner without deleting retained files.
try:
path.rename(retained)
except OSError:
if keep:
try:
shutil.copytree(path, retained, symlinks=True)
except OSError as error:
_logger.warning("Could not copy failed fixture artifacts to %r: %s", str(retained), error)
else:
_logger.warning("Retained failed fixture artifacts at %r", str(retained))
raise
path.mkdir()
try:
native.close()
finally:
if retained.exists():
retained.rename(path)
except (OSError, Error) as error:
_logger.warning("Could not dispose of fixture %r: %s", str(path), error)
def __enter__(self):
self.path()
return self
def __exit__(self, *args):
self.close()
@contextmanager
def _script_directory(script):
with cwd(ROOT):
try:
yield
except Error:
# Native errors include the whole environment; captured script output is safe.
raise RuntimeError(f"Fixture script {script!r} failed; see its captured output") from None
def read_only_fixture(script, *args):
"""Return cached script output which callers must not modify."""
args = list(map(str, args))
with _script_directory(script):
# An absolute argument resets the native memoization path; version the full argument list.
path, _ = testtools.scripted_fixture_read_only_with_args_with_post(
script, args, crc32(repr((str(script), args)).encode()), lambda state: None
)
return path.resolve()
def writable_fixture(script, *args, execute=False):
"""Copy cached output, or execute a declared location-dependent fixture."""
args = list(map(str, args))
mode = testtools.Creation.Execute if execute else testtools.Creation.CopyFromReadOnly
with _script_directory(script):
temporary, _ = testtools.scripted_fixture_writable_with_args_with_post(
script, args, mode, crc32(repr((str(script), args)).encode()), lambda state: None
)
return _WritableFixture(temporary)
@contextmanager
def fixture_repo(script, *args, write=False, execute=False, subdir="repo"):
"""Open a scripted repository, closing handles before its temporary owner."""
if write:
with writable_fixture(script, *args, execute=execute) as temporary:
with Repo(temporary.path().resolve() / subdir) as repo:
yield repo
else:
if execute:
raise ValueError("execute requires a writable fixture")
with Repo(read_only_fixture(script, *args) / subdir) as repo:
yield repo