summaryrefslogtreecommitdiff
path: root/testing
diff options
context:
space:
mode:
authorArmin Rigo <arigo@tunes.org>2020-03-06 23:24:22 +0100
committerArmin Rigo <arigo@tunes.org>2020-03-06 23:24:22 +0100
commit39738b5be335e12afedbf39c5ef3c14ed1d50d35 (patch)
tree354075d09d9bac3b182ae17208a0bb05359ffa2e /testing
parent3d7cdd07ac7b93e983f865325acfb7ea23851c38 (diff)
downloadcffi-39738b5be335e12afedbf39c5ef3c14ed1d50d35.tar.gz
Vendor in pypy's version of py.path.local.make_numbered_dir(), which is more
resilient against random OS Errors like EACCES
Diffstat (limited to 'testing')
-rw-r--r--testing/udir.py131
1 files changed, 129 insertions, 2 deletions
diff --git a/testing/udir.py b/testing/udir.py
index 4dd0a11..59db1c4 100644
--- a/testing/udir.py
+++ b/testing/udir.py
@@ -1,7 +1,134 @@
import py
-import sys
+import sys, os, atexit
-udir = py.path.local.make_numbered_dir(prefix = 'ffi-')
+
+# This is copied from PyPy's vendored py lib. The latest py lib release
+# (1.8.1) contains a bug and crashes if it sees another temporary directory
+# in which we don't have write permission (e.g. because it's owned by someone
+# else).
+def make_numbered_dir(prefix='session-', rootdir=None, keep=3,
+ lock_timeout = 172800, # two days
+ min_timeout = 300): # five minutes
+ """ return unique directory with a number greater than the current
+ maximum one. The number is assumed to start directly after prefix.
+ if keep is true directories with a number less than (maxnum-keep)
+ will be removed.
+ """
+ if rootdir is None:
+ rootdir = py.path.local.get_temproot()
+
+ def parse_num(path):
+ """ parse the number out of a path (if it matches the prefix) """
+ bn = path.basename
+ if bn.startswith(prefix):
+ try:
+ return int(bn[len(prefix):])
+ except ValueError:
+ pass
+
+ # compute the maximum number currently in use with the
+ # prefix
+ lastmax = None
+ while True:
+ maxnum = -1
+ for path in rootdir.listdir():
+ num = parse_num(path)
+ if num is not None:
+ maxnum = max(maxnum, num)
+
+ # make the new directory
+ try:
+ udir = rootdir.mkdir(prefix + str(maxnum+1))
+ except py.error.EEXIST:
+ # race condition: another thread/process created the dir
+ # in the meantime. Try counting again
+ if lastmax == maxnum:
+ raise
+ lastmax = maxnum
+ continue
+ break
+
+ # put a .lock file in the new directory that will be removed at
+ # process exit
+ if lock_timeout:
+ lockfile = udir.join('.lock')
+ mypid = os.getpid()
+ if hasattr(lockfile, 'mksymlinkto'):
+ lockfile.mksymlinkto(str(mypid))
+ else:
+ lockfile.write(str(mypid))
+ def try_remove_lockfile():
+ # in a fork() situation, only the last process should
+ # remove the .lock, otherwise the other processes run the
+ # risk of seeing their temporary dir disappear. For now
+ # we remove the .lock in the parent only (i.e. we assume
+ # that the children finish before the parent).
+ if os.getpid() != mypid:
+ return
+ try:
+ lockfile.remove()
+ except py.error.Error:
+ pass
+ atexit.register(try_remove_lockfile)
+
+ # prune old directories
+ if keep:
+ for path in rootdir.listdir():
+ num = parse_num(path)
+ if num is not None and num <= (maxnum - keep):
+ if min_timeout:
+ # NB: doing this is needed to prevent (or reduce
+ # a lot the chance of) the following situation:
+ # 'keep+1' processes call make_numbered_dir() at
+ # the same time, they create dirs, but then the
+ # last process notices the first dir doesn't have
+ # (yet) a .lock in it and kills it.
+ try:
+ t1 = path.lstat().mtime
+ t2 = lockfile.lstat().mtime
+ if abs(t2-t1) < min_timeout:
+ continue # skip directories too recent
+ except py.error.Error:
+ continue # failure to get a time, better skip
+ lf = path.join('.lock')
+ try:
+ t1 = lf.lstat().mtime
+ t2 = lockfile.lstat().mtime
+ if not lock_timeout or abs(t2-t1) < lock_timeout:
+ continue # skip directories still locked
+ except py.error.Error:
+ pass # assume that it means that there is no 'lf'
+ try:
+ path.remove(rec=1)
+ except KeyboardInterrupt:
+ raise
+ except: # this might be py.error.Error, WindowsError ...
+ pass
+
+ # make link...
+ try:
+ username = os.environ['USER'] #linux, et al
+ except KeyError:
+ try:
+ username = os.environ['USERNAME'] #windows
+ except KeyError:
+ username = 'current'
+
+ src = str(udir)
+ dest = src[:src.rfind('-')] + '-' + username
+ try:
+ os.unlink(dest)
+ except OSError:
+ pass
+ try:
+ os.symlink(src, dest)
+ except (OSError, AttributeError, NotImplementedError):
+ pass
+
+ return udir
+
+
+udir = make_numbered_dir(prefix = 'ffi-')
# Windows-only workaround for some configurations: see