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:
Matt Glazar 2018-10-09 14:31:21 -07:00 committed by Facebook Github Bot
parent e7ab071589
commit ada2bd6752
3 changed files with 106 additions and 79 deletions

View File

@ -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()

View 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()

View File

@ -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)