update the usage of pexpect to work on Windows

Summary:
Update the integration test code that uses pexpect to use the more modern
pexpect APIs.  The top-level `pexpect.spawn()` function is considered legacy
now, and is only provided for backwards compatibility on Unix platforms.

Reviewed By: wez

Differential Revision: D21214640

fbshipit-source-id: 941da5435c4f8afaf22e8053f4c344175d7b1a7f
This commit is contained in:
Adam Simpkins 2020-04-24 14:45:37 -07:00 committed by Facebook GitHub Bot
parent cadcd0ab39
commit c13f0ac5f7
5 changed files with 54 additions and 28 deletions

View File

@ -15,7 +15,7 @@ from eden.fs.cli.daemon import wait_for_shutdown
from .lib import edenclient, testcase
from .lib.find_executables import FindExe
from .lib.pexpect import PexpectAssertionMixin
from .lib.pexpect import PexpectAssertionMixin, PexpectSpawnType, pexpect_spawn
from .lib.service_test_case import ServiceTestCaseBase, service_test
@ -36,7 +36,7 @@ class HealthTest(testcase.EdenTestCase):
class HealthOfFakeEdenFSTest(ServiceTestCaseBase, PexpectAssertionMixin):
def setUp(self) -> None:
super().setUp()
self.temp_dir = pathlib.Path(self.make_temporary_directory())
self.temp_dir = self.make_temp_dir()
def test_healthy_daemon_is_healthy(self) -> None:
with self.spawn_fake_edenfs(self.temp_dir):
@ -68,9 +68,8 @@ class HealthOfFakeEdenFSTest(ServiceTestCaseBase, PexpectAssertionMixin):
status_process.expect_exact("eden running normally")
self.assert_process_succeeds(status_process)
def spawn_status(self, extra_args: typing.List[str]) -> "pexpect.spawn[str]":
return pexpect.spawn(
# pyre-ignore[6]: T38947910
def spawn_status(self, extra_args: typing.List[str]) -> PexpectSpawnType:
return pexpect_spawn(
FindExe.EDEN_CLI,
["--config-dir", str(self.temp_dir)]
+ self.get_required_eden_cli_args()

View File

@ -6,13 +6,28 @@
import abc
import shlex
import sys
from typing import Any, Optional, Union
import pexpect
if sys.platform == "win32":
import pexpect.popen_spawn
PexpectSpawnType = pexpect.popen_spawn.PopenSpawn
pexpect_spawn = pexpect.popen_spawn.PopenSpawn
else:
import pexpect.pty_spawn
PexpectSpawnType = pexpect.pty_spawn.spawn
pexpect_spawn = pexpect.pty_spawn.spawn
class PexpectAssertionMixin(metaclass=abc.ABCMeta):
def assert_process_succeeds(self, process: pexpect.spawn):
def assert_process_succeeds(self, process: PexpectSpawnType):
actual_exit_code = wait_for_pexpect_process(process)
self.assertEqual(
actual_exit_code,
@ -21,7 +36,7 @@ class PexpectAssertionMixin(metaclass=abc.ABCMeta):
)
def assert_process_fails(
self, process: pexpect.spawn, exit_code: Optional[int] = None
self, process: PexpectSpawnType, exit_code: Optional[int] = None
):
if exit_code is None:
actual_exit_code = wait_for_pexpect_process(process)
@ -34,7 +49,7 @@ class PexpectAssertionMixin(metaclass=abc.ABCMeta):
else:
self.assert_process_exit_code(process, exit_code)
def assert_process_exit_code(self, process: pexpect.spawn, exit_code: int):
def assert_process_exit_code(self, process: PexpectSpawnType, exit_code: int):
actual_exit_code = wait_for_pexpect_process(process)
self.assertEqual(
actual_exit_code,
@ -52,17 +67,23 @@ class PexpectAssertionMixin(metaclass=abc.ABCMeta):
raise NotImplementedError()
def pexpect_process_shell_command(process: pexpect.spawn) -> str:
def pexpect_process_shell_command(process: PexpectSpawnType) -> 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))
command = process.command
args = process.args
if command is None:
return "<no pexpect command set>"
else:
assert args is not None
command_parts = [command] + [str_from_strlike(arg) for arg in args]
return " ".join(map(shlex.quote, command_parts))
def wait_for_pexpect_process(process: pexpect.spawn) -> int:
def wait_for_pexpect_process(process: PexpectSpawnType) -> int:
process.expect_exact(pexpect.EOF)
return process.wait()

View File

@ -6,6 +6,7 @@
import subprocess
import sys
import typing
from typing import Optional
import eden.thrift
@ -15,7 +16,7 @@ from eden.fs.cli.config import EdenInstance
from eden.fs.cli.util import HealthStatus
from .lib.find_executables import FindExe
from .lib.pexpect import PexpectAssertionMixin
from .lib.pexpect import PexpectAssertionMixin, PexpectSpawnType, pexpect_spawn
from .lib.service_test_case import (
ServiceTestCaseBase,
SystemdServiceTest,
@ -40,7 +41,7 @@ class RestartTestBase(ServiceTestCaseBase):
self.addCleanup(ensure_stopped)
def _spawn_restart(self, *args: str) -> "pexpect.spawn[bytes]":
def _spawn_restart(self, *args: str) -> PexpectSpawnType:
restart_cmd = (
[FindExe.EDEN_CLI, "--config-dir", str(self.eden_dir)]
+ self.get_required_eden_cli_args()
@ -50,7 +51,7 @@ class RestartTestBase(ServiceTestCaseBase):
restart_cmd.extend(args)
print("Retarting eden: %r" % (restart_cmd,))
return pexpect.spawn(
return pexpect_spawn(
restart_cmd[0], restart_cmd[1:], logfile=sys.stdout.buffer, timeout=5
)
@ -74,7 +75,6 @@ class RestartTest(RestartTestBase, PexpectAssertionMixin):
p.expect_exact("Eden is not currently running. Starting it...")
p.expect_exact("Starting fake edenfs daemon")
p.expect(r"Started edenfs \(pid ([0-9]+)\)")
int(p.match.group(1))
p.wait()
self.assertEqual(p.exitstatus, 0)
@ -109,7 +109,9 @@ class RestartTest(RestartTestBase, PexpectAssertionMixin):
p = self._spawn_restart("--force")
p.expect(r"Started edenfs \(pid (?P<pid>\d+)\)")
new_pid_from_restart: int = int(p.match.group("pid"))
match = typing.cast(Optional[typing.Match], p.match)
assert match is not None
new_pid_from_restart: int = int(match.group("pid"))
new_pid_from_health_check: Optional[int] = self._check_edenfs_health().pid
self.assertIsNotNone(new_pid_from_health_check, "EdenFS should be alive")

View File

@ -18,7 +18,12 @@ from eden.fs.cli.daemon import wait_for_process_exit
from eden.fs.cli.util import poll_until
from .lib.find_executables import FindExe
from .lib.pexpect import PexpectAssertionMixin, wait_for_pexpect_process
from .lib.pexpect import (
PexpectAssertionMixin,
PexpectSpawnType,
pexpect_spawn,
wait_for_pexpect_process,
)
from .lib.service_test_case import (
ServiceTestCaseBase,
SystemdServiceTest,
@ -42,9 +47,8 @@ class StopTestBase(ServiceTestCaseBase):
self.eden_dir = self.tmp_dir / "eden"
self.eden_dir.mkdir()
def spawn_stop(self, extra_args: List[str]) -> "pexpect.spawn[str]":
return pexpect.spawn(
# pyre-ignore[6]: T38947910
def spawn_stop(self, extra_args: List[str]) -> PexpectSpawnType:
return pexpect_spawn(
FindExe.EDEN_CLI,
["--config-dir", str(self.eden_dir)]
+ self.get_required_eden_cli_args()

View File

@ -11,13 +11,14 @@ import subprocess
import sys
import typing
import unittest
from typing import Optional
import pexpect
import toml
from eden.test_support.testcase import EdenTestCaseBase
from .lib.find_executables import FindExe
from .lib.pexpect import PexpectAssertionMixin
from .lib.pexpect import PexpectAssertionMixin, PexpectSpawnType, pexpect_spawn
from .lib.service_test_case import SystemdServiceTest, systemd_test
@ -93,10 +94,10 @@ class SystemdTest(SystemdServiceTest, PexpectAssertionMixin):
r"error: Starting the fb-edenfs@.+?\.service systemd service "
r"failed \(reason: exit-code\)"
)
before = typing.cast(Optional[str], start_process.before)
assert before is not None
self.assertNotIn(
"journalctl",
start_process.before,
"journalctl doesn't work and should not be mentioned",
"journalctl", before, "journalctl doesn't work and should not be mentioned"
)
remaining_output = start_process.read()
self.assertNotIn(
@ -161,9 +162,8 @@ class SystemdTest(SystemdServiceTest, PexpectAssertionMixin):
def spawn_start_with_fake_edenfs(
self, extra_args: typing.Sequence[str] = ()
) -> "pexpect.spawn[str]":
return pexpect.spawn(
# pyre-ignore[6]: T38947910
) -> PexpectSpawnType:
return pexpect_spawn(
FindExe.EDEN_CLI,
self.get_required_eden_cli_args()
# pyre-ignore[6]: T38947910