Skip to content

Commit

Permalink
tests: Improve typing in test_main.py
Browse files Browse the repository at this point in the history
  • Loading branch information
JonathonReinhart committed Mar 25, 2024
1 parent 2f5c406 commit 3635f87
Showing 1 changed file with 63 additions and 52 deletions.
115 changes: 63 additions & 52 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import sys
from tempfile import TemporaryFile, NamedTemporaryFile
from textwrap import dedent
from typing import cast, IO, List, Optional, Sequence, TextIO, Tuple
from unittest import mock
import warnings

Expand All @@ -26,6 +27,8 @@
PseudoTTY,
)

ScubaResult = Tuple[str, str]


SCUBA_YML = Path(".scuba.yml")
SCUBAINIT_EXIT_FAIL = 99
Expand All @@ -36,7 +39,13 @@ def write_script(path: Path, text: str) -> None:
make_executable(path)


def run_scuba(args, *, expect_return=0, mock_isatty=False, stdin=None):
def run_scuba(
args: List[str],
*,
expect_return: int = 0,
mock_isatty: bool = False,
stdin: Optional[IO[str]] = None,
) -> ScubaResult:
"""Run scuba, checking its return value
Returns scuba/docker stdout data.
Expand All @@ -47,8 +56,8 @@ def run_scuba(args, *, expect_return=0, mock_isatty=False, stdin=None):
with TemporaryFile(prefix="scubatest-stdout", mode="w+t") as stdout:
with TemporaryFile(prefix="scubatest-stderr", mode="w+t") as stderr:
if mock_isatty:
stdout = PseudoTTY(stdout)
stderr = PseudoTTY(stderr)
stdout = PseudoTTY(stdout) # type: ignore[assignment]
stderr = PseudoTTY(stderr) # type: ignore[assignment]

old_stdin = sys.stdin
old_stdout = sys.stdout
Expand All @@ -57,9 +66,9 @@ def run_scuba(args, *, expect_return=0, mock_isatty=False, stdin=None):
if stdin is None:
sys.stdin = open(os.devnull, "w")
else:
sys.stdin = stdin
sys.stdout = stdout
sys.stderr = stderr
sys.stdin = cast(TextIO, stdin)
sys.stdout = cast(TextIO, stdout)
sys.stderr = cast(TextIO, stderr)

try:
"""
Expand Down Expand Up @@ -127,7 +136,7 @@ def test_handle_get_image_command_error(self) -> None:
"""Verify scuba handles a get_image_command error"""
SCUBA_YML.write_text("image: {DOCKER_IMAGE}")

def mocked_gic(*args, **kw):
def mocked_gic(image: str) -> Optional[Sequence[str]]:
raise scuba.dockerutil.DockerError("mock error")

# http://alexmarandon.com/articles/python_mock_gotchas/#patching-in-the-wrong-place
Expand Down Expand Up @@ -169,7 +178,7 @@ def test_version(self) -> None:
assert name == "scuba"
assert ver == scuba.__version__

def test_no_docker(self, monkeypatch) -> None:
def test_no_docker(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""Verify scuba gracefully handles docker not being installed"""
SCUBA_YML.write_text(f"image: {DOCKER_IMAGE}")

Expand All @@ -179,12 +188,12 @@ def test_no_docker(self, monkeypatch) -> None:
_, err = run_scuba(args, expect_return=2)

@mock.patch("subprocess.call")
def test_dry_run(self, subproc_call_mock):
def test_dry_run(self, subproc_call_mock: mock.Mock) -> None:
print(f"subproc_call_mock is a {type(subproc_call_mock)}")
"""Verify scuba handles --dry-run and --verbose"""
SCUBA_YML.write_text(f"image: {DOCKER_IMAGE}")

args = ["--dry-run", "--verbose", "/bin/false"]

_, err = run_scuba(args)

assert not subproc_call_mock.called
Expand Down Expand Up @@ -272,12 +281,12 @@ def test_redirect_stdin(self) -> None:
class TestMainUser(MainTest):
def _test_user(
self,
expected_uid,
expected_username,
expected_gid,
expected_groupname,
scuba_args=[],
):
expected_uid: int,
expected_username: str,
expected_gid: int,
expected_groupname: str,
scuba_args: List[str] = [],
) -> None:
SCUBA_YML.write_text(f"image: {DOCKER_IMAGE}")

args = scuba_args + [
Expand All @@ -287,15 +296,24 @@ def _test_user(
]
out, _ = run_scuba(args)

uid, username, gid, groupname = out.split()
uid = int(uid)
gid = int(gid)
uid_str, username, gid_str, groupname = out.split()
uid = int(uid_str)
gid = int(gid_str)

assert uid == expected_uid
assert username == expected_username
assert gid == expected_gid
assert groupname == expected_groupname

def _test_user_expect_root(self, scuba_args: List[str] = []) -> None:
return self._test_user(
expected_uid=0,
expected_username="root",
expected_gid=0,
expected_groupname="root",
scuba_args=scuba_args,
)

def test_user_scubauser(self) -> None:
"""Verify scuba runs container as the current (host) uid/gid"""
self._test_user(
Expand All @@ -305,27 +323,17 @@ def test_user_scubauser(self) -> None:
expected_groupname=getgrgid(os.getgid()).gr_name,
)

EXPECT_ROOT = dict(
expected_uid=0,
expected_username="root",
expected_gid=0,
expected_groupname="root",
)

def test_user_root(self) -> None:
"""Verify scuba -r runs container as root"""
self._test_user(
**self.EXPECT_ROOT,
scuba_args=["-r"],
)
self._test_user_expect_root(scuba_args=["-r"])

def test_user_run_as_root(self) -> None:
'''Verify running scuba as root is identical to "scuba -r"'''

with mock.patch("os.getuid", return_value=0) as getuid_mock, mock.patch(
"os.getgid", return_value=0
) as getgid_mock:
self._test_user(**self.EXPECT_ROOT)
self._test_user_expect_root()
assert getuid_mock.called
assert getgid_mock.called

Expand Down Expand Up @@ -373,7 +381,7 @@ def test_user_root_alias(self) -> None:


class TestMainHomedir(MainTest):
def _test_home_writable(self, scuba_args=[]):
def _test_home_writable(self, scuba_args: List[str] = []) -> None:
SCUBA_YML.write_text(f"image: {DOCKER_IMAGE}")

args = scuba_args + [
Expand Down Expand Up @@ -435,7 +443,7 @@ def test_arbitrary_docker_args_merge_config(self) -> None:
expfiles = set()
tgtdir = "/tgtdir"

def mount_dummy(name):
def mount_dummy(name: str) -> str:
assert name not in expfiles
expfiles.add(name)
return f'-v "{dummy.absolute()}:{tgtdir}/{name}"\n'
Expand Down Expand Up @@ -686,7 +694,13 @@ def test_yml_not_needed_with_image_override(self) -> None:


class TestMainHooks(MainTest):
def _test_one_hook(self, hookname, hookcmd, cmd, expect_return=0):
def _test_one_hook(
self,
hookname: str,
hookcmd: str,
cmd: str,
expect_return: int = 0,
) -> ScubaResult:
SCUBA_YML.write_text(
f"""
image: {DOCKER_IMAGE}
Expand All @@ -698,19 +712,19 @@ def _test_one_hook(self, hookname, hookcmd, cmd, expect_return=0):
args = ["/bin/sh", "-c", cmd]
return run_scuba(args, expect_return=expect_return)

def _test_hook_runs_as(self, hookname, exp_uid, exp_gid) -> None:
def _test_hook_runs_as(self, hookname: str, exp_uid: int, exp_gid: int) -> None:
out, _ = self._test_one_hook(
hookname,
"echo $(id -u) $(id -g)",
"echo success",
hookname=hookname,
hookcmd="echo $(id -u) $(id -g)",
cmd="echo success",
)
out = out.splitlines()
out_lines = out.splitlines()

uid, gid = map(int, out[0].split())
uid, gid = map(int, out_lines[0].split())
assert exp_uid == uid
assert exp_gid == gid

assert_str_equalish(out[1], "success")
assert_str_equalish(out_lines[1], "success")

def test_user_hook_runs_as_user(self) -> None:
"""Verify user hook executes as user"""
Expand Down Expand Up @@ -745,7 +759,7 @@ def test_env_var_keyval(self) -> None:
out, _ = run_scuba(args)
assert_str_equalish(out, "VAL")

def test_env_var_key_only(self, monkeypatch):
def test_env_var_key_only(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""Verify -e KEY works"""
SCUBA_YML.write_text(f"image: {DOCKER_IMAGE}")
args = [
Expand All @@ -759,7 +773,7 @@ def test_env_var_key_only(self, monkeypatch):
out, _ = run_scuba(args)
assert_str_equalish(out, "mockedvalue")

def test_env_var_sources(self, monkeypatch):
def test_env_var_sources(self, monkeypatch: pytest.MonkeyPatch) -> None:
"""Verify scuba handles all possible environment variable sources"""
SCUBA_YML.write_text(
rf"""
Expand Down Expand Up @@ -812,7 +826,7 @@ def test_env_var_sources(self, monkeypatch):
BAZ="From the command line",
)

def test_builtin_env__SCUBA_ROOT(self, in_tmp_path):
def test_builtin_env__SCUBA_ROOT(self, in_tmp_path: Path) -> None:
"""Verify SCUBA_ROOT is set in container"""
SCUBA_YML.write_text(f"image: {DOCKER_IMAGE}")

Expand Down Expand Up @@ -945,8 +959,7 @@ def test_volumes_basic(self) -> None:
)

out, _ = run_scuba(["doit"])
out = out.splitlines()
assert out == ["from the top", "from the alias"]
assert out.splitlines() == ["from the top", "from the alias"]

def test_volumes_alias_override(self) -> None:
"""Verify volumes can be overridden by an alias"""
Expand Down Expand Up @@ -975,13 +988,11 @@ def test_volumes_alias_override(self) -> None:

# Run a non-alias command
out, _ = run_scuba(["cat", "/data/thing"])
out = out.splitlines()
assert out == ["from the top"]
assert out.splitlines() == ["from the top"]

# Run the alias
out, _ = run_scuba(["doit"])
out = out.splitlines()
assert out == ["from the alias"]
assert out.splitlines() == ["from the alias"]

def test_volumes_host_path_create(self) -> None:
"""Missing host paths should be created before starting Docker"""
Expand Down Expand Up @@ -1136,10 +1147,10 @@ def _rm_volume(self) -> None:
return
result.check_returncode()

def setup_method(self, method) -> None:
def setup_method(self) -> None:
self._rm_volume()

def teardown_method(self, method) -> None:
def teardown_method(self) -> None:
self._rm_volume()

def test_volumes_named(self) -> None:
Expand Down

0 comments on commit 3635f87

Please sign in to comment.