Skip to content

Commit 0337802

Browse files
move all the things into _pytest.pathlib
1 parent e7a2b10 commit 0337802

File tree

8 files changed

+202
-195
lines changed

8 files changed

+202
-195
lines changed

src/_pytest/assertion/rewrite.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
import py
1818

1919
from _pytest.assertion import util
20-
from _pytest.compat import PurePath, spec_from_file_location
20+
from _pytest.pathlib import PurePath
21+
from _pytest.compat import spec_from_file_location
2122
from _pytest.paths import fnmatch_ex
2223

2324
# pytest caches rewritten pycs in __pycache__.

src/_pytest/cacheprovider.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
import shutil
1717

1818
from . import paths
19-
from .compat import _PY2 as PY2, Path
19+
from .compat import _PY2 as PY2
20+
from .pathlib import Path
2021

2122
README_CONTENT = u"""\
2223
# pytest cache directory #

src/_pytest/compat.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323
# Only available in Python 3.4+ or as a backport
2424
enum = None
2525

26-
__all__ = ["Path", "PurePath"]
27-
2826
_PY3 = sys.version_info > (3, 0)
2927
_PY2 = not _PY3
3028

@@ -41,11 +39,6 @@
4139
PY36 = sys.version_info[:2] >= (3, 6)
4240
MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
4341

44-
if PY36:
45-
from pathlib import Path, PurePath
46-
else:
47-
from pathlib2 import Path, PurePath
48-
4942

5043
if _PY3:
5144
from collections.abc import MutableMapping as MappingMixin

src/_pytest/pathlib.py

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
2+
import os
3+
import errno
4+
import atexit
5+
import operator
6+
import six
7+
from functools import reduce
8+
import uuid
9+
from six.moves import map
10+
import itertools
11+
import shutil
12+
13+
from .compat import PY36
14+
15+
if PY36:
16+
from pathlib import Path, PurePath
17+
else:
18+
from pathlib2 import Path, PurePath
19+
20+
__all__ = ["Path", "PurePath"]
21+
22+
23+
LOCK_TIMEOUT = 60 * 60 * 3
24+
25+
get_lock_path = operator.methodcaller("joinpath", ".lock")
26+
27+
28+
def find_prefixed(root, prefix):
29+
l_prefix = prefix.lower()
30+
for x in root.iterdir():
31+
if x.name.lower().startswith(l_prefix):
32+
yield x
33+
34+
35+
def extract_suffixes(iter, prefix):
36+
p_len = len(prefix)
37+
for p in iter:
38+
yield p.name[p_len:]
39+
40+
41+
def find_suffixes(root, prefix):
42+
return extract_suffixes(find_prefixed(root, prefix), prefix)
43+
44+
45+
def parse_num(maybe_num):
46+
try:
47+
return int(maybe_num)
48+
except ValueError:
49+
return -1
50+
51+
52+
def _max(iterable, default):
53+
# needed due to python2.7 lacking the default argument for max
54+
return reduce(max, iterable, default)
55+
56+
57+
def make_numbered_dir(root, prefix):
58+
for i in range(10):
59+
# try up to 10 times to create the folder
60+
max_existing = _max(map(parse_num, find_suffixes(root, prefix)), -1)
61+
new_number = max_existing + 1
62+
new_path = root.joinpath("{}{}".format(prefix, new_number))
63+
try:
64+
new_path.mkdir()
65+
except Exception:
66+
pass
67+
else:
68+
return new_path
69+
else:
70+
raise EnvironmentError(
71+
"could not create numbered dir with prefix {prefix} in {root})".format(
72+
prefix=prefix, root=root
73+
)
74+
)
75+
76+
77+
def create_cleanup_lock(p):
78+
lock_path = get_lock_path(p)
79+
try:
80+
fd = os.open(str(lock_path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
81+
except OSError as e:
82+
if e.errno == errno.EEXIST:
83+
six.raise_from(
84+
EnvironmentError("cannot create lockfile in {path}".format(path=p)), e
85+
)
86+
else:
87+
raise
88+
else:
89+
pid = os.getpid()
90+
spid = str(pid)
91+
if not isinstance(spid, six.binary_type):
92+
spid = spid.encode("ascii")
93+
os.write(fd, spid)
94+
os.close(fd)
95+
if not lock_path.is_file():
96+
raise EnvironmentError("lock path got renamed after sucessfull creation")
97+
return lock_path
98+
99+
100+
def register_cleanup_lock_removal(lock_path, register=atexit.register):
101+
pid = os.getpid()
102+
103+
def cleanup_on_exit(lock_path=lock_path, original_pid=pid):
104+
current_pid = os.getpid()
105+
if current_pid != original_pid:
106+
# fork
107+
return
108+
try:
109+
lock_path.unlink()
110+
except (OSError, IOError):
111+
pass
112+
113+
return register(cleanup_on_exit)
114+
115+
116+
def delete_a_numbered_dir(path):
117+
create_cleanup_lock(path)
118+
parent = path.parent
119+
120+
garbage = parent.joinpath("garbage-{}".format(uuid.uuid4()))
121+
path.rename(garbage)
122+
shutil.rmtree(str(garbage), ignore_errors=True)
123+
124+
125+
def ensure_deletable(path, consider_lock_dead_if_created_before):
126+
lock = get_lock_path(path)
127+
if not lock.exists():
128+
return True
129+
try:
130+
lock_time = lock.stat().st_mtime
131+
except Exception:
132+
return False
133+
else:
134+
if lock_time < consider_lock_dead_if_created_before:
135+
lock.unlink()
136+
return True
137+
else:
138+
return False
139+
140+
141+
def try_cleanup(path, consider_lock_dead_if_created_before):
142+
if ensure_deletable(path, consider_lock_dead_if_created_before):
143+
delete_a_numbered_dir(path)
144+
145+
146+
def cleanup_candidates(root, prefix, keep):
147+
max_existing = _max(map(parse_num, find_suffixes(root, prefix)), -1)
148+
max_delete = max_existing - keep
149+
paths = find_prefixed(root, prefix)
150+
paths, paths2 = itertools.tee(paths)
151+
numbers = map(parse_num, extract_suffixes(paths2, prefix))
152+
for path, number in zip(paths, numbers):
153+
if number <= max_delete:
154+
yield path
155+
156+
157+
def cleanup_numbered_dir(root, prefix, keep, consider_lock_dead_if_created_before):
158+
for path in cleanup_candidates(root, prefix, keep):
159+
try_cleanup(path, consider_lock_dead_if_created_before)
160+
for path in root.glob("garbage-*"):
161+
try_cleanup(path, consider_lock_dead_if_created_before)
162+
163+
164+
def make_numbered_dir_with_cleanup(root, prefix, keep, lock_timeout):
165+
e = None
166+
for i in range(10):
167+
try:
168+
p = make_numbered_dir(root, prefix)
169+
lock_path = create_cleanup_lock(p)
170+
register_cleanup_lock_removal(lock_path)
171+
except Exception as e:
172+
pass
173+
else:
174+
consider_lock_dead_if_created_before = p.stat().st_mtime - lock_timeout
175+
cleanup_numbered_dir(
176+
root=root,
177+
prefix=prefix,
178+
keep=keep,
179+
consider_lock_dead_if_created_before=consider_lock_dead_if_created_before,
180+
)
181+
return p
182+
assert e is not None
183+
raise e

src/_pytest/paths.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import six
77

8-
from .compat import Path, PurePath
8+
from .pathlib import Path, PurePath
99

1010

1111
def resolve_from_str(input, root):

src/_pytest/pytester.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717

1818
from _pytest.capture import MultiCapture, SysCapture
1919
from _pytest._code import Source
20-
import py
21-
import pytest
2220
from _pytest.main import Session, EXIT_OK
2321
from _pytest.assertion.rewrite import AssertionRewritingHook
24-
from _pytest.compat import Path
22+
from _pytest.pathlib import Path
2523
from _pytest.compat import safe_str
2624

25+
import py
26+
import pytest
27+
2728
IGNORE_PAM = [ # filenames added when obtaining details about the current user
2829
u"/var/lib/sss/mc/passwd"
2930
]

0 commit comments

Comments
 (0)