mirror of
https://github.com/facebook/sapling.git
synced 2024-10-08 07:49:11 +03:00
7a0dec91ec
Summary: While I was fixing the cli_test I realized that the rest of the tests didn't enforce strict type checking, let's make sure it is enabled so `pyre -l eden` works for all the tests now and in the future. Reviewed By: chadaustin Differential Revision: D26356267 fbshipit-source-id: 4f026b6f96c410115a6a38d772f8e06ab977293b
121 lines
3.7 KiB
Python
121 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
|
# Copyright (c) Facebook, Inc. and its affiliates.
|
|
#
|
|
# This software may be used and distributed according to the terms of the
|
|
# GNU General Public License version 2.
|
|
|
|
# pyre-strict
|
|
|
|
import contextlib
|
|
import signal
|
|
import subprocess
|
|
import threading
|
|
import time
|
|
import typing
|
|
import unittest
|
|
|
|
from eden.fs.cli.daemon import wait_for_shutdown
|
|
|
|
|
|
class WaitForShutdownTest(unittest.TestCase):
|
|
def test_waiting_for_exited_process_finishes_immediately(self) -> None:
|
|
process = AutoReapingChildProcess(["true"])
|
|
process.wait()
|
|
|
|
stop_watch = StopWatch()
|
|
with stop_watch.measure():
|
|
wait_for_shutdown(process.pid, timeout=5)
|
|
self.assertLessEqual(stop_watch.elapsed, 3)
|
|
|
|
def test_waiting_for_exiting_process_finishes_without_sigkill(self) -> None:
|
|
process = AutoReapingChildProcess(["sleep", "1"])
|
|
wait_for_shutdown(process.pid, timeout=5)
|
|
returncode = process.wait()
|
|
self.assertEqual(returncode, 0, "Process should have exited cleanly")
|
|
|
|
def test_waiting_for_alive_process_kills_with_sigkill(self) -> None:
|
|
process = AutoReapingChildProcess(["sleep", "30"])
|
|
wait_for_shutdown(process.pid, timeout=1)
|
|
returncode = process.wait()
|
|
self.assertEqual(
|
|
returncode, -signal.SIGKILL, "Process should have exited with SIGKILL"
|
|
)
|
|
|
|
|
|
class AutoReapingChildProcess:
|
|
"""A child process (subprocess.Popen) which is promptly reaped."""
|
|
|
|
def __init__(self, args: typing.List[str]) -> None:
|
|
super().__init__()
|
|
|
|
self.__condition = threading.Condition()
|
|
self.__error: typing.Optional[BaseException] = None
|
|
self.__pid: typing.Optional[int] = None
|
|
self.__returncode: typing.Optional[int] = None
|
|
|
|
self.__start_thread(args)
|
|
self.__wait_for_process_start()
|
|
|
|
@property
|
|
def pid(self) -> int:
|
|
with self.__condition:
|
|
pid = self.__pid
|
|
assert pid is not None
|
|
return pid
|
|
|
|
def wait(self) -> int:
|
|
with self.__condition:
|
|
while self.__returncode is None:
|
|
self.__condition.wait()
|
|
returncode = self.__returncode
|
|
assert returncode is not None
|
|
return returncode
|
|
|
|
def __wait_for_process_start(self) -> None:
|
|
with self.__condition:
|
|
while self.__pid is None and self.__error is None:
|
|
self.__condition.wait()
|
|
error = self.__error
|
|
if error is not None:
|
|
raise error
|
|
assert self.__pid is not None
|
|
|
|
def __start_thread(self, *args: typing.List[str]) -> None:
|
|
thread = threading.Thread(target=self.__run_thread, args=(args), daemon=True)
|
|
thread.start()
|
|
|
|
def __run_thread(self, popen_args: typing.List[str]) -> None:
|
|
try:
|
|
with subprocess.Popen(popen_args) as process:
|
|
with self.__condition:
|
|
self.__pid = process.pid
|
|
self.__condition.notify_all()
|
|
process.wait()
|
|
with self.__condition:
|
|
self.__returncode = process.returncode
|
|
self.__condition.notify_all()
|
|
except BaseException as e:
|
|
with self.__condition:
|
|
self.__error = e
|
|
self.__condition.notify_all()
|
|
|
|
|
|
class StopWatch:
|
|
__elapsed: float = 0
|
|
|
|
@property
|
|
def elapsed(self) -> float:
|
|
return self.__elapsed
|
|
|
|
@contextlib.contextmanager
|
|
def measure(self) -> typing.Iterator[None]:
|
|
start_time = self.__time()
|
|
try:
|
|
yield
|
|
finally:
|
|
end_time = self.__time()
|
|
self.__elapsed += end_time - start_time
|
|
|
|
def __time(self) -> float:
|
|
return time.monotonic()
|