pytest_containerTesting Container Images with Python and Pytest
Dan Čermák
Dan Čermák
| Software Developer @SUSE | |
| i3 SIG, Package maintainer | |
| Developer Tools, Testing and Documentation, Home Automation | |
| https://dancermak.name | |
| dcermak | |
| @Defolos@mastodon.social | 
/bin/sh?When shell scripts are:
Until they aren't 💥
pytest_container?x86_64, aarch64, s390x, ppcle64TW = Container(
    url="registry.opensuse.org/opensuse/tumbleweed:latest"
)
@pytest.mark.parametrize("container", [TW], indirect=True)
def test_etc_os_release_present(container: ContainerData):
    assert container.connection.file(
        "/etc/os-release"
    ).exists
pytest fixturesdef test_ehlo(smtp_connection):
    response, msg = smtp_connection.ehlo()
    assert response == 250
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x: int, y: int):
    # do something with x & y here
WEB_SERVER = DerivedContainer(
    base="registry.opensuse.org/opensuse/leap:latest",
    containerfile="""RUN zypper -n in python3 \
    && echo "Hello Green World!" > index.html
ENTRYPOINT ["/usr/bin/python3", "-m", "http.server"]
"""
)
@pytest.mark.parametrize(
    "container", [WEB_SERVER], indirect=True
)
def test_python_installed(container: ContainerData):
    assert container.connection.package(
        "python3"
    ).is_installed
TW = Container(
    url="registry.opensuse.org/opensuse/tumbleweed:latest"
)
NGINX = DerivedContainer(
    base=TW,
    containerfile="RUN zypper -n in nginx",
)
NGINX_DEBUG = DerivedContainer(
    base=NGINX,
    containerfile="RUN zypper -n in gdb nginx-debuginfo"
)
CONTAINER_IMAGES=[NGINX_DEBUG]
def test_nginx(auto_container): ...
WEB_SERVER = DerivedContainer(
    # snip
    forwarded_ports=[PortForwarding(container_port=8000)],
)
@pytest.mark.parametrize(
    "container", [WEB_SERVER], indirect=True
)
def test_port_forward(container: ContainerData, host):
    cmd = (
        "curl --fail localhost:"
        + str(container.forwarded_ports[0].host_port)
    )
    host.check_output(cmd)
HEALTHCHECKWEB_SERVER = DerivedContainer(
    # snip
    containerfile="""
ENTRYPOINT ["/usr/bin/python3", "-m", "http.server"]
HEALTHCHECK CMD curl --fail http://0.0.0.0:8000""",
)
@pytest.mark.parametrize("container", [WEB_SERVER], indirect=True)
def test_server_up(container, container_runtime):
    assert (
        container_runtime.get_container_health(
            container.container_id
        ) == ContainerHealth.HEALTHY
    )
MEDIAWIKI_FPM_POD = Pod(
    containers=[MEDIAWIKI_FPM_CONTAINER, NGINX_FPM_PROXY],
    forwarded_ports=[PortForwarding(container_port=80)],
)
@pytest.mark.parametrize(
    "pod", [MEDIAWIKI_FPM_POD], indirect=True
)
def test_port_forward(pod: PodData, host):
    cmd = (
        "curl --fail localhost:"
        + str(pod.forwarded_ports[0].host_port)
    )
    host.check_output(cmd)
use the container_per_test fixture:
@pytest.mark.parametrize(
    "container_per_test", [TW], indirect=True
)
def test_rm_rf(container_per_test):
    container_per_test.connection.check_output("rm -rf /")
@pytest.mark.parametrize(
    "container_per_test", [TW], indirect=True
)
def test_uninstall_zypper(container_per_test):
    container_per_test.connection.check_output(
        "rpm -e --nodeps zypper"
    )
@pytest.mark.parametrize(
    "container", [MY_IMAGE], indirect=True
)
def test_inspect(container: ContainerData):
    inspect = container.inspect
    assert inspect.config.user == "me"
    assert inspect.config.cmd == ["/bin/sh"]
    assert (
        "HOME" in inspect.config.env
        and inspect.config.env["HOME"] == "/src/"
    )
Bind mounts
ROOTDIR_BIND_MOUNTED = DerivedContainer(
    base="registry.opensuse.org/opensuse/tumbleweed",
    volume_mounts=[
        BindMount("/src/", host_path=get_rootdir())
    ],
)
@pytest.mark.parametrize(
    "container", [ROOTDIR_BIND_MOUNTED], indirect=True
)
def test_bind_mount_cwd(container: ContainerData):
    vol = container.container.volume_mounts[0]
    assert container.connection.file("/src/").exists
Container volumes
WITH_VAR_LOG_VOLUME = DerivedContainer(
    base="registry.opensuse.org/opensuse/tumbleweed",
    volume_mounts=[ContainerVolume("/var/log/")],
)
use the auto_container / auto_container_per_test fixtures:
CONTAINER_IMAGES = [TW, LEAP, SLE]
def test_etc_os_release(auto_container): ...
def test_zypper_rm_works(auto_container_per_test): ...
export CONTAINER_RUNTIME=docker
pytest -vv
pip install pytest-xdist
# or
poetry add --group dev pytest-xdist
pytest -vv -- -n auto
Answers!
Suggestions?