mirror of
https://github.com/facebook/sapling.git
synced 2024-10-06 23:07:18 +03:00
Refactor stop_test.py to use pexpect
Summary: pexpect prints program output while the program is running, which is very useful when debugging. Make the tests in stop_test.py use this feature. Also, add some assertions to some of the tests. Reviewed By: chadaustin Differential Revision: D10157354 fbshipit-source-id: 210fcde79d5d21c6ee34fad3813e7c56eb298b9b
This commit is contained in:
parent
e7ab071589
commit
ada2bd6752
@ -22,6 +22,7 @@ from eden.cli.daemon import wait_for_shutdown
|
||||
from .lib import edenclient, testcase
|
||||
from .lib.fake_edenfs import fake_eden_daemon
|
||||
from .lib.find_executables import FindExe
|
||||
from .lib.pexpect import PexpectAssertionMixin
|
||||
|
||||
|
||||
class HealthTest(testcase.EdenTestCase):
|
||||
@ -37,7 +38,7 @@ class HealthTest(testcase.EdenTestCase):
|
||||
self.assertFalse(client.is_healthy())
|
||||
|
||||
|
||||
class HealthOfFakeEdenFSTest(unittest.TestCase):
|
||||
class HealthOfFakeEdenFSTest(unittest.TestCase, PexpectAssertionMixin):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
@ -95,37 +96,3 @@ class HealthOfFakeEdenFSTest(unittest.TestCase):
|
||||
status_process.logfile = sys.stderr
|
||||
status_process.expect_exact("eden running normally")
|
||||
self.assert_process_succeeds(status_process)
|
||||
|
||||
def assert_process_succeeds(self, process: pexpect.spawn):
|
||||
actual_exit_code = wait_for_pexpect_process(process)
|
||||
self.assertEqual(
|
||||
actual_exit_code,
|
||||
0,
|
||||
f"Command should return success: {pexpect_process_shell_command(process)}",
|
||||
)
|
||||
|
||||
def assert_process_fails(self, process: pexpect.spawn, exit_code: int):
|
||||
assert exit_code != 0
|
||||
actual_exit_code = wait_for_pexpect_process(process)
|
||||
self.assertEqual(
|
||||
actual_exit_code,
|
||||
exit_code,
|
||||
f"Command should return an error code: "
|
||||
f"{pexpect_process_shell_command(process)}",
|
||||
)
|
||||
|
||||
|
||||
def pexpect_process_shell_command(process: pexpect.spawn) -> str:
|
||||
def str_from_strlike(s: typing.Union[bytes, str]) -> str:
|
||||
if isinstance(s, str):
|
||||
return s
|
||||
else:
|
||||
return s.decode("utf-8")
|
||||
|
||||
command_parts = [process.command] + [str_from_strlike(arg) for arg in process.args]
|
||||
return " ".join(map(shlex.quote, command_parts))
|
||||
|
||||
|
||||
def wait_for_pexpect_process(process: pexpect.spawn) -> int:
|
||||
process.expect_exact(pexpect.EOF)
|
||||
return process.wait()
|
||||
|
63
eden/integration/lib/pexpect.py
Normal file
63
eden/integration/lib/pexpect.py
Normal file
@ -0,0 +1,63 @@
|
||||
#!/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.
|
||||
|
||||
import abc
|
||||
import shlex
|
||||
from typing import Any, Union
|
||||
|
||||
import pexpect
|
||||
|
||||
|
||||
class PexpectAssertionMixin(metaclass=abc.ABCMeta):
|
||||
def assert_process_succeeds(self, process: pexpect.spawn):
|
||||
actual_exit_code = wait_for_pexpect_process(process)
|
||||
self.assertEqual(
|
||||
actual_exit_code,
|
||||
0,
|
||||
f"Command should return success: {pexpect_process_shell_command(process)}",
|
||||
)
|
||||
|
||||
def assert_process_fails(self, process: pexpect.spawn, exit_code: int):
|
||||
assert exit_code != 0
|
||||
actual_exit_code = wait_for_pexpect_process(process)
|
||||
self.assertEqual(
|
||||
actual_exit_code,
|
||||
exit_code,
|
||||
f"Command should return an error code: "
|
||||
f"{pexpect_process_shell_command(process)}",
|
||||
)
|
||||
|
||||
def assert_process_exit_code(self, process: pexpect.spawn, exit_code: int):
|
||||
actual_exit_code = wait_for_pexpect_process(process)
|
||||
self.assertEqual(
|
||||
actual_exit_code,
|
||||
exit_code,
|
||||
f"Command should exit with code {exit_code}: "
|
||||
f"{pexpect_process_shell_command(process)}",
|
||||
)
|
||||
|
||||
@abc.abstractmethod
|
||||
def assertEqual(self, first: Any, second: Any, msg: Any = ...) -> None:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def pexpect_process_shell_command(process: pexpect.spawn) -> str:
|
||||
def str_from_strlike(s: Union[bytes, str]) -> str:
|
||||
if isinstance(s, str):
|
||||
return s
|
||||
else:
|
||||
return s.decode("utf-8")
|
||||
|
||||
command_parts = [process.command] + [str_from_strlike(arg) for arg in process.args]
|
||||
return " ".join(map(shlex.quote, command_parts))
|
||||
|
||||
|
||||
def wait_for_pexpect_process(process: pexpect.spawn) -> int:
|
||||
process.expect_exact(pexpect.EOF)
|
||||
return process.wait()
|
@ -12,15 +12,18 @@ import pathlib
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import typing
|
||||
import unittest
|
||||
|
||||
import pexpect
|
||||
from eden.cli.daemon import did_process_exit
|
||||
from eden.cli.util import poll_until
|
||||
|
||||
from .lib.fake_edenfs import fake_eden_daemon, spawn_fake_eden_daemon
|
||||
from .lib.find_executables import FindExe
|
||||
from .lib.pexpect import PexpectAssertionMixin
|
||||
|
||||
|
||||
SHUTDOWN_EXIT_CODE_NORMAL = 0
|
||||
@ -29,7 +32,7 @@ SHUTDOWN_EXIT_CODE_NOT_RUNNING_ERROR = 2
|
||||
SHUTDOWN_EXIT_CODE_TERMINATED_VIA_SIGKILL = 3
|
||||
|
||||
|
||||
class StopTest(unittest.TestCase):
|
||||
class StopTest(unittest.TestCase, PexpectAssertionMixin):
|
||||
def setUp(self):
|
||||
def cleanup_tmp_dir() -> None:
|
||||
shutil.rmtree(self.tmp_dir, ignore_errors=True)
|
||||
@ -39,17 +42,14 @@ class StopTest(unittest.TestCase):
|
||||
|
||||
def test_stop_stops_running_daemon(self):
|
||||
with fake_eden_daemon(pathlib.Path(self.tmp_dir)) as daemon_pid:
|
||||
stop_result = subprocess.run(
|
||||
[
|
||||
FindExe.EDEN_CLI,
|
||||
"--config-dir",
|
||||
self.tmp_dir,
|
||||
"stop",
|
||||
"--timeout",
|
||||
"5",
|
||||
]
|
||||
stop_process = pexpect.spawn(
|
||||
FindExe.EDEN_CLI,
|
||||
["--config-dir", self.tmp_dir, "stop", "--timeout", "5"],
|
||||
encoding="utf-8",
|
||||
logfile=sys.stderr,
|
||||
)
|
||||
self.assertEqual(SHUTDOWN_EXIT_CODE_NORMAL, stop_result.returncode)
|
||||
stop_process.expect_exact("edenfs exited cleanly.")
|
||||
self.assert_process_exit_code(stop_process, SHUTDOWN_EXIT_CODE_NORMAL)
|
||||
self.assertTrue(
|
||||
did_process_exit(daemon_pid), f"Process {daemon_pid} should have died"
|
||||
)
|
||||
@ -82,28 +82,25 @@ class StopTest(unittest.TestCase):
|
||||
"1",
|
||||
]
|
||||
print("Stopping eden: %r" % (stop_cmd,))
|
||||
stop_result = subprocess.run(
|
||||
stop_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
stop_process = pexpect.spawn(
|
||||
stop_cmd[0], stop_cmd[1:], encoding="utf-8", logfile=sys.stderr
|
||||
)
|
||||
self.assertIn(b"Terminated edenfs with SIGKILL", stop_result.stderr)
|
||||
self.assertEqual(
|
||||
SHUTDOWN_EXIT_CODE_TERMINATED_VIA_SIGKILL, stop_result.returncode
|
||||
stop_process.expect_exact("Terminated edenfs with SIGKILL")
|
||||
self.assert_process_exit_code(
|
||||
stop_process, SHUTDOWN_EXIT_CODE_TERMINATED_VIA_SIGKILL
|
||||
)
|
||||
|
||||
def test_async_stop_stops_daemon_eventually(self):
|
||||
with fake_eden_daemon(pathlib.Path(self.tmp_dir)) as daemon_pid:
|
||||
stop_result = subprocess.run(
|
||||
[
|
||||
FindExe.EDEN_CLI,
|
||||
"--config-dir",
|
||||
self.tmp_dir,
|
||||
"stop",
|
||||
"--timeout",
|
||||
"0",
|
||||
]
|
||||
stop_process = pexpect.spawn(
|
||||
FindExe.EDEN_CLI,
|
||||
["--config-dir", self.tmp_dir, "stop", "--timeout", "0"],
|
||||
encoding="utf-8",
|
||||
logfile=sys.stderr,
|
||||
)
|
||||
self.assertEqual(
|
||||
SHUTDOWN_EXIT_CODE_REQUESTED_SHUTDOWN, stop_result.returncode
|
||||
stop_process.expect_exact("Sent async shutdown request to edenfs.")
|
||||
self.assert_process_exit_code(
|
||||
stop_process, SHUTDOWN_EXIT_CODE_REQUESTED_SHUTDOWN
|
||||
)
|
||||
|
||||
def daemon_exited() -> typing.Optional[bool]:
|
||||
@ -115,28 +112,28 @@ class StopTest(unittest.TestCase):
|
||||
poll_until(daemon_exited, timeout=10)
|
||||
|
||||
def test_stop_not_running(self):
|
||||
stop_cmd = [
|
||||
stop_process = pexpect.spawn(
|
||||
FindExe.EDEN_CLI,
|
||||
"--config-dir",
|
||||
self.tmp_dir,
|
||||
"stop",
|
||||
"--timeout",
|
||||
"1",
|
||||
]
|
||||
stop_result = subprocess.run(
|
||||
stop_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
["--config-dir", self.tmp_dir, "stop", "--timeout", "1"],
|
||||
encoding="utf-8",
|
||||
logfile=sys.stderr,
|
||||
)
|
||||
stop_process.expect_exact("edenfs is not running")
|
||||
self.assert_process_exit_code(
|
||||
stop_process, SHUTDOWN_EXIT_CODE_NOT_RUNNING_ERROR
|
||||
)
|
||||
self.assertIn(b"edenfs is not running", stop_result.stderr)
|
||||
self.assertEqual(SHUTDOWN_EXIT_CODE_NOT_RUNNING_ERROR, stop_result.returncode)
|
||||
|
||||
def test_stopping_killed_daemon_reports_not_running(self):
|
||||
daemon_pid = spawn_fake_eden_daemon(pathlib.Path(self.tmp_dir))
|
||||
os.kill(daemon_pid, signal.SIGKILL)
|
||||
|
||||
stop_result = subprocess.run(
|
||||
[FindExe.EDEN_CLI, "--config-dir", self.tmp_dir, "stop", "--timeout", "1"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
stop_process = pexpect.spawn(
|
||||
FindExe.EDEN_CLI,
|
||||
["--config-dir", self.tmp_dir, "stop", "--timeout", "1"],
|
||||
encoding="utf-8",
|
||||
logfile=sys.stderr,
|
||||
)
|
||||
stop_process.expect_exact("edenfs is not running")
|
||||
self.assert_process_exit_code(
|
||||
stop_process, SHUTDOWN_EXIT_CODE_NOT_RUNNING_ERROR
|
||||
)
|
||||
self.assertIn(b"edenfs is not running", stop_result.stderr)
|
||||
self.assertEqual(SHUTDOWN_EXIT_CODE_NOT_RUNNING_ERROR, stop_result.returncode)
|
||||
|
Loading…
Reference in New Issue
Block a user