1 # Copyright (c) Meta Platforms, Inc. and affiliates.
3 # This source code is licensed under the MIT license found in the
4 # LICENSE file in the root directory of this source tree.
17 from . import path_utils
as path
20 global_temp_dir
= None
23 class TempDir(object):
25 This is a helper for locating a reasonable place for temporary files.
26 When run in the watchman test suite, we compute this up-front and then
27 store everything under that temporary directory.
28 When run under the FB internal test runner, we infer a reasonable grouped
29 location from the process group environmental variable exported by the
33 def __init__(self
, keepAtShutdown
: bool = False) -> None:
34 # We'll put all our temporary stuff under one dir so that we
35 # can clean it all up at the end.
37 parent_dir
= tempfile
.gettempdir()
40 self
.temp_dir
= path
.get_canonical_filesystem_path(
41 tempfile
.mkdtemp(dir=parent_dir
, prefix
=prefix
)
45 # On some platforms, setting the setgid bit on a directory doesn't
46 # work if the user isn't a member of the directory's group. Set the
47 # group explicitly to avoid this.
48 os
.chown(self
.temp_dir
, -1, os
.getegid())
49 # Some environments have a weird umask that can leave state
50 # directories too open and break tests.
52 # Redirect all temporary files to that location
53 tempfile
.tempdir
= os
.fsdecode(self
.temp_dir
)
55 self
.keep
= keepAtShutdown
59 sys
.stdout
.write("Preserving output in %s\n" % self
.temp_dir
)
61 self
._retry
_rmtree
(self
.temp_dir
)
63 atexit
.register(cleanup
)
68 def set_keep(self
, value
) -> None:
71 def _retry_rmtree(self
, top
) -> None:
72 # Keep trying to remove it; on Windows it may take a few moments
73 # for any outstanding locks/handles to be released
74 for _
in range(1, 10):
75 shutil
.rmtree(top
, onerror
=_remove_readonly
)
76 if not os
.path
.isdir(top
):
78 sys
.stdout
.write("Waiting to remove temp data under %s\n" % top
)
80 sys
.stdout
.write("Failed to completely remove %s\n" % top
)
83 def _remove_readonly(func
, path
, exc_info
) -> None:
84 # If we encounter an EPERM or EACCESS error removing a file try making its parent
85 # directory writable and then retry the removal. This is necessary to clean up
86 # eden mount point directories after the checkout is unmounted, as these directories
87 # are made read-only by "eden clone"
88 _ex_type
, ex
, _traceback
= exc_info
90 isinstance(ex
, EnvironmentError) and ex
.errno
in (errno
.EACCES
, errno
.EPERM
)
92 # Just ignore other errors. This will be retried by _retry_rmtree()
96 parent_dir
= os
.path
.dirname(path
)
97 os
.chmod(parent_dir
, 0o755)
98 # func() is the function that failed.
99 # This is usually os.unlink() or os.rmdir().
105 def get_temp_dir(keep
=None):
106 global global_temp_dir
108 return global_temp_dir
110 keep
= os
.environ
.get("WATCHMAN_TEST_KEEP", "0") == "1"
111 global_temp_dir
= TempDir(keep
)
112 return global_temp_dir