2018-11-16 07:36:18 +03:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
#
|
|
|
|
# Copyright (c) 2016-present, Facebook, Inc.
|
|
|
|
# All rights reserved.
|
|
|
|
#
|
|
|
|
# This source code is licensed under the BSD-style license found in the
|
|
|
|
# LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
# of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
|
2018-12-04 01:39:00 +03:00
|
|
|
import abc
|
2018-11-16 07:36:18 +03:00
|
|
|
import logging
|
|
|
|
import os
|
2018-12-04 01:39:00 +03:00
|
|
|
import pathlib
|
2018-11-16 07:36:18 +03:00
|
|
|
import shutil
|
2018-12-04 01:39:00 +03:00
|
|
|
import tempfile
|
2018-11-16 07:36:18 +03:00
|
|
|
import types
|
2018-12-04 01:39:00 +03:00
|
|
|
import typing
|
2018-11-16 07:36:18 +03:00
|
|
|
from pathlib import Path
|
|
|
|
from typing import Any, Callable, Tuple, Type, Union
|
|
|
|
|
|
|
|
|
|
|
|
def cleanup_tmp_dir(tmp_dir: Path) -> None:
|
|
|
|
"""Clean up a temporary directory.
|
|
|
|
|
|
|
|
This is similar to shutil.rmtree() but also handles removing read-only files and
|
|
|
|
directories. This function changes the permissions on files and directories if this
|
|
|
|
is necessary to remove them.
|
|
|
|
|
|
|
|
This is necessary for removing Eden checkout directories since "eden clone" makes
|
|
|
|
the original mount point directory read-only.
|
|
|
|
"""
|
|
|
|
# If we encounter an EPERM or EACCESS error removing a file try making its parent
|
|
|
|
# directory writable and then retry the removal.
|
|
|
|
def _remove_readonly(
|
|
|
|
func: Callable[[Union[os.PathLike, str]], Any],
|
|
|
|
path: Union[os.PathLike, str],
|
|
|
|
exc_info: Tuple[Type, BaseException, types.TracebackType],
|
|
|
|
) -> None:
|
|
|
|
_ex_type, ex, _traceback = exc_info
|
2019-06-07 22:22:21 +03:00
|
|
|
# pyre-fixme[29]: `Union[Callable[[object], bool], Callable[[object], bool],
|
|
|
|
# Callable[[object], bool], Callable[[object], bool]]` is not a function.
|
2018-11-16 07:36:18 +03:00
|
|
|
if path == tmp_dir:
|
|
|
|
logging.warning(
|
|
|
|
f"failed to remove temporary test directory {tmp_dir}: {ex}"
|
|
|
|
)
|
|
|
|
return
|
|
|
|
if not isinstance(ex, PermissionError):
|
|
|
|
logging.warning(f"error removing file in temporary directory {path}: {ex}")
|
|
|
|
return
|
|
|
|
|
|
|
|
try:
|
2019-05-10 04:12:03 +03:00
|
|
|
# pyre-fixme[6]: Expected `_PathLike[AnyStr]` for 1st param but got
|
|
|
|
# `Union[_PathLike[Any], str]`.
|
2018-11-16 07:36:18 +03:00
|
|
|
parent_dir = os.path.dirname(path)
|
|
|
|
os.chmod(parent_dir, 0o755)
|
|
|
|
# func() is the function that failed.
|
|
|
|
# This is usually os.unlink() or os.rmdir().
|
|
|
|
func(path)
|
|
|
|
except OSError as ex:
|
|
|
|
logging.warning(f"error removing file in temporary directory {path}: {ex}")
|
|
|
|
return
|
|
|
|
|
|
|
|
shutil.rmtree(tmp_dir, onerror=_remove_readonly)
|
2018-12-04 01:39:00 +03:00
|
|
|
|
|
|
|
|
|
|
|
class TemporaryDirectoryMixin(metaclass=abc.ABCMeta):
|
|
|
|
def make_temporary_directory(self) -> str:
|
|
|
|
def clean_up(path_str: str) -> None:
|
|
|
|
if os.environ.get("EDEN_TEST_NO_CLEANUP"):
|
|
|
|
print("Leaving behind eden test directory %r" % path_str)
|
|
|
|
else:
|
|
|
|
cleanup_tmp_dir(pathlib.Path(path_str))
|
|
|
|
|
|
|
|
path_str = tempfile.mkdtemp(prefix="eden_test.")
|
|
|
|
self.addCleanup(lambda: clean_up(path_str))
|
|
|
|
return path_str
|
|
|
|
|
|
|
|
def addCleanup(
|
|
|
|
self,
|
|
|
|
function: typing.Callable[..., typing.Any],
|
|
|
|
*args: typing.Any,
|
|
|
|
**kwargs: typing.Any,
|
|
|
|
) -> None:
|
|
|
|
raise NotImplementedError()
|