Skip to content

Commit 192a1f4

Browse files
committed
tests: extend docker e2e tests to actually test the resulting container
Uses testinfra to check the requested changes are actually applied to the running container.
1 parent 9bc54b8 commit 192a1f4

File tree

11 files changed

+120
-29
lines changed

11 files changed

+120
-29
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ jobs:
7373
strategy:
7474
matrix:
7575
os: [ubuntu-22.04, ubuntu-24.04]
76-
test-name: [local, ssh, docker]
76+
test-name: [local, ssh, docker, podman]
7777
python-version: ["3.10", "3.11", "3.12"]
7878
include:
7979
- os: macos-13

pyinfra/api/util.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from __future__ import annotations
22

3+
import hashlib
34
from functools import wraps
4-
from hashlib import sha1
5+
from hashlib import sha1, sha256
56
from inspect import getframeinfo, stack
67
from io import BytesIO, StringIO
78
from os import getcwd, path, stat
@@ -341,7 +342,7 @@ class get_file_io:
341342
_close: bool = False
342343
_file_io: IO[Any]
343344

344-
def __init__(self, filename_or_io, mode="rb"):
345+
def __init__(self, filename_or_io: str | IO, mode: str = "rb"):
345346
if not (
346347
# Check we can be read
347348
hasattr(filename_or_io, "read")
@@ -390,6 +391,14 @@ def cache_key(self):
390391

391392

392393
def get_file_sha1(filename_or_io):
394+
return _get_file_digest(filename_or_io, sha1())
395+
396+
397+
def get_file_sha256(filename_or_io):
398+
return _get_file_digest(filename_or_io, sha256())
399+
400+
401+
def _get_file_digest(filename_or_io: str | IO, hasher: hashlib._Hash):
393402
"""
394403
Calculates the SHA1 of a file or file object using a buffer to handle larger files.
395404
"""
@@ -401,7 +410,6 @@ def get_file_sha1(filename_or_io):
401410
return FILE_SHAS[cache_key]
402411

403412
with file_data as file_io:
404-
hasher = sha1()
405413
buff = file_io.read(BLOCKSIZE)
406414

407415
while len(buff) > 0:

pyinfra/facts/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ def command(self):
529529
PASSWORD=`(grep ^$i: /etc/shadow || grep ^$i: /etc/master.passwd) 2> /dev/null | cut -d: -f2`;
530530
echo "$ENTRY|`id -gn $i`|`id -Gn $i`|$LASTLOG|$PASSWORD";
531531
done
532-
""".strip() # noqa
532+
""".strip() # noqa
533533

534534
default = dict
535535

pyinfra_cli/main.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,10 +515,12 @@ def _set_verbosity(state, verbosity):
515515
state.print_noop_info = True
516516

517517
if verbosity > 1:
518-
state.print_input = state.print_fact_input = True
518+
state.print_input = True
519+
state.print_fact_input = True
519520

520521
if verbosity > 2:
521-
state.print_output = state.print_fact_output = True
522+
state.print_output = True
523+
state.print_fact_output = True
522524

523525
return state
524526

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,6 @@ addopts = -m'not end_to_end'
3737
markers =
3838
end_to_end
3939
end_to_end_docker
40+
end_to_end_podman
4041
end_to_end_local
4142
end_to_end_ssh

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"pytest==8.3.5",
4444
"coverage==7.7.1",
4545
"pytest-cov==6.0.0",
46+
"pytest-testinfra==10.2.2",
4647
# Formatting & linting
4748
"black==25.1.0",
4849
"isort==6.0.1",

tests/end-to-end/conftest.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import re
22
import subprocess
3+
from typing import Callable
34

45
import pytest
6+
import testinfra.host
57

68

79
class Helpers:
@@ -38,6 +40,28 @@ def run_check_output(command, expected_lines=None, **kwargs):
3840
line,
3941
)
4042

43+
@staticmethod
44+
def run_container_test_host(
45+
container: str,
46+
cmd: str,
47+
callback: Callable[[testinfra.host.Host], None],
48+
) -> None:
49+
stdout, _ = Helpers.run(
50+
f"docker run -d {container} tail -f /dev/null",
51+
)
52+
cid = stdout.strip()
53+
54+
try:
55+
Helpers.run_check_output(
56+
f"pyinfra -y @docker/{cid} {cmd}",
57+
expected_lines=["docker build complete"],
58+
)
59+
60+
host = testinfra.get_host(f"docker://{cid}")
61+
callback(host)
62+
finally:
63+
Helpers.run(f"docker rm -f {cid}")
64+
4165

4266
@pytest.fixture(scope="module")
4367
def helpers():

tests/end-to-end/test_e2e_docker.py

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,56 @@
33
"""
44

55
import pytest
6+
import testinfra
7+
import testinfra.host
68

79

810
@pytest.mark.end_to_end
911
@pytest.mark.end_to_end_docker
1012
def test_int_docker_install_package_ubuntu(helpers):
11-
helpers.run_check_output(
12-
"pyinfra -y --chdir examples @docker/ubuntu:22.04 apt.packages iftop update=true",
13-
expected_lines=["docker build complete"],
13+
def check(host: testinfra.host.Host):
14+
assert host.package("iftop").is_installed
15+
16+
helpers.run_container_test_host(
17+
"ubuntu:22.04",
18+
"apt.packages packages=iftop update=true",
19+
check,
20+
)
21+
22+
23+
@pytest.mark.end_to_end
24+
@pytest.mark.end_to_end_docker
25+
def test_int_docker_file(helpers):
26+
def check(host: testinfra.host.Host):
27+
file = host.file("/testfile")
28+
assert file.is_file
29+
assert file.user == "nobody"
30+
assert file.group == "root"
31+
assert file.mode == 0o755
32+
33+
helpers.run_container_test_host(
34+
"ubuntu:22.04",
35+
"files.file path=/testfile mode=755 user=nobody",
36+
check,
1437
)
1538

1639

1740
@pytest.mark.end_to_end
1841
@pytest.mark.end_to_end_docker
19-
def test_int_podman_install_package_ubuntu(helpers):
20-
helpers.run_check_output(
21-
"pyinfra -y --chdir examples @podman/ubuntu:22.04 apt.packages iftop update=true",
22-
expected_lines=["podman build complete"],
42+
def test_int_docker_put_file(helpers):
43+
# TODO: why does importing this at top level break things?
44+
from pyinfra.api.util import get_file_sha256
45+
46+
with open("README.md", "r") as f:
47+
expected_sum = get_file_sha256(f)
48+
49+
def check(host: testinfra.host.Host):
50+
file = host.file("/README.md")
51+
assert file.is_file
52+
assert file.sha256sum == expected_sum
53+
54+
helpers.run_container_test_host(
55+
"ubuntu:22.04",
56+
"files.put src=README.md dest=README.md mode=755 user=nobody",
57+
check,
2358
)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""
2+
Podman based integration tests.
3+
"""
4+
5+
import pytest
6+
7+
8+
@pytest.mark.end_to_end
9+
@pytest.mark.end_to_end_podman
10+
def test_int_podman_install_package_ubuntu(helpers):
11+
helpers.run_check_output(
12+
"pyinfra -y --chdir examples @podman/ubuntu:22.04 apt.packages iftop update=true",
13+
expected_lines=["podman build complete"],
14+
)

tests/paramiko_util.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ def __iter__(self):
6060

6161

6262
class FakeSSHClient:
63+
def close(self):
64+
pass
65+
6366
def load_system_host_keys(self):
6467
pass
6568

@@ -90,6 +93,9 @@ class FakeSFTPClient:
9093
def from_transport(cls, transport):
9194
return cls()
9295

96+
def close(self):
97+
pass
98+
9399
def putfo(self, file_io, remote_location):
94100
pass
95101

0 commit comments

Comments
 (0)