Skip to content

Commit

Permalink
Add new fixture container_image
Browse files Browse the repository at this point in the history
  • Loading branch information
dcermak committed Aug 26, 2024
1 parent c28ba15 commit 9cf6e5d
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 18 deletions.
12 changes: 6 additions & 6 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -225,12 +225,12 @@ Improvements and new features:
Container Images exposing the same ports in parallel without marking them as
``singleton=True``.

- The attribute :py:attr:`~pytest_container.container.ContainerData.container`
was added to :py:class:`~pytest_container.container.ContainerData` (the
datastructure that is passed to test functions via the ``*container*``
fixtures). This attribute contains the
:py:class:`~pytest_container.container.ContainerBase` that was used to
parametrize this test run.
- The attribute ``ContainerData.container`` (is now
:py:attr:`~pytest_container.container.ContainerImageData.container`) was added
to :py:class:`~pytest_container.container.ContainerData` (the datastructure
that is passed to test functions via the ``*container*`` fixtures). This
attribute contains the :py:class:`~pytest_container.container.ContainerBase`
that was used to parametrize this test run.

- Add support to add tags to container images via
``DerivedContainer.add_build_tags`` (is now called
Expand Down
72 changes: 60 additions & 12 deletions pytest_container/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -526,20 +526,38 @@ def get_launch_cmd(
self,
container_runtime: OciRuntimeBase,
extra_run_args: Optional[List[str]] = None,
in_background: bool = True,
interactive_tty: bool = True,
remove: bool = False,
) -> List[str]:
"""Returns the command to launch this container image.
Args:
container_runtime: The container runtime to be used to launch this
container
extra_run_args: optional list of arguments that are added to the
launch command directly after the ``run -d``.
in_background: flag whether to launch the container with ``-d``
(i.e. in the background). Defaults to ``True``.
interactive: flag whether to launch the container with ``--it``
(i.e. in interactive mode and attach a pseudo TTY). Defaults to
``True``.
remove: flag whether to launch the container with the ``--rm`` flag
(i.e. it will be auto-removed on after stopping)
Returns:
The command to launch the container image described by this class
instance as a list of strings that can be fed directly to
:py:class:`subprocess.Popen` as the ``args`` parameter.
"""
cmd = (
[container_runtime.runner_binary, "run", "-d"]
[container_runtime.runner_binary, "run"]
+ (["-d"] if in_background else [])
+ (["--rm"] if remove else [])
+ (extra_run_args or [])
+ self.extra_launch_args
+ (
Expand All @@ -558,7 +576,9 @@ def get_launch_cmd(
)

id_or_url = self.container_id or self.url
container_launch = ("-it", id_or_url)
container_launch: Tuple[str, ...] = (
("-it", id_or_url) if interactive_tty else (id_or_url,)
)
bash_launch_end = (
*_CONTAINER_STOPSIGNAL,
*container_launch,
Expand Down Expand Up @@ -994,7 +1014,29 @@ def prepare_container(


@dataclass(frozen=True)
class ContainerData:
class ContainerImageData:
#: the container data class that has been used in this test
container: Union[Container, DerivedContainer, MultiStageContainer]

_container_runtime: OciRuntimeBase

@property
def run_command_list(self) -> List[str]:
return self.container.get_launch_cmd(
self._container_runtime,
in_background=False,
remove=True,
# -it breaks testinfra.host.check_output() with docker
interactive_tty=self._container_runtime.runner_binary == "podman",
)

@property
def run_command(self) -> str:
return " ".join(self.run_command_list)


@dataclass(frozen=True)
class ContainerData(ContainerImageData):
"""Class returned by the ``*container*`` fixtures to the test function. It
contains information about the launched container and the testinfra
:py:attr:`connection` to the running container.
Expand All @@ -1008,13 +1050,9 @@ class ContainerData:
container_id: str
#: the testinfra connection to the running container
connection: Any
#: the container data class that has been used in this test
container: Union[Container, DerivedContainer, MultiStageContainer]
#: any ports that are exposed by this container
forwarded_ports: List[PortForwarding]

_container_runtime: OciRuntimeBase

@property
def inspect(self) -> ContainerInspect:
"""Inspect the launched container and return the result of
Expand Down Expand Up @@ -1205,11 +1243,7 @@ def from_pytestconfig(
def __enter__(self) -> "ContainerLauncher":
return self

def launch_container(self) -> None:
"""This function performs the actual heavy lifting of launching the
container, creating all the volumes, port bindings, etc.pp.
"""
def prepare_container(self) -> None:
# Lock guarding the container preparation, so that only one process
# tries to pull/build it at the same time.
# If this container is a singleton, then we use it as a lock until
Expand Down Expand Up @@ -1251,6 +1285,13 @@ def release_lock() -> None:
get_volume_creator(cont_vol, self.container_runtime)
)

def launch_container(self) -> None:
"""This function performs the actual heavy lifting of launching the
container, creating all the volumes, port bindings, etc.pp.
"""
self.prepare_container()

forwarded_ports = self.container.forwarded_ports

extra_run_args = self.extra_run_args
Expand Down Expand Up @@ -1290,6 +1331,13 @@ def release_lock() -> None:

self._wait_for_container_to_become_healthy()

@property
def container_image_data(self) -> ContainerImageData:
# FIXME: check if container is prepared
return ContainerImageData(
container=self.container, _container_runtime=self.container_runtime
)

@property
def container_data(self) -> ContainerData:
"""The :py:class:`ContainerData` instance corresponding to the running
Expand Down
18 changes: 18 additions & 0 deletions pytest_container/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from pytest_container.container import container_and_marks_from_pytest_param
from pytest_container.container import ContainerData
from pytest_container.container import ContainerImageData
from pytest_container.container import ContainerLauncher
from pytest_container.logging import _logger
from pytest_container.pod import pod_from_pytest_param
Expand Down Expand Up @@ -173,3 +174,20 @@ def fixture_funct(

#: Same as :py:func:`pod`, except that it creates a pod for each test function.
pod_per_test = _create_auto_pod_fixture("function")


@fixture(scope="session")
def container_image(
request: SubRequest,
# we must call this parameter container runtime, so that pytest will
# treat it as a fixture, but that causes pylint to complain…
# pylint: disable=redefined-outer-name
container_runtime: OciRuntimeBase,
pytestconfig: Config,
) -> Generator[ContainerImageData, None, None]:
container, _ = container_and_marks_from_pytest_param(request.param)
with ContainerLauncher.from_pytestconfig(
container, container_runtime, pytestconfig
) as launcher:
launcher.prepare_container()
yield launcher.container_image_data
36 changes: 36 additions & 0 deletions tests/test_container_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import pytest
from pytest_container.container import ContainerImageData
from pytest_container.container import DerivedContainer

from tests.images import LEAP


@pytest.mark.parametrize("container_image", [LEAP], indirect=True)
def test_run_leap(container_image: ContainerImageData, host) -> None:
assert 'NAME="openSUSE Leap"' in host.check_output(
f"{container_image.run_command} cat /etc/os-release"
)


CTR_WITH_ENTRYPOINT_ADDING_PATH = DerivedContainer(
base=LEAP,
containerfile="""RUN mkdir -p /usr/test/; echo 'echo "foobar"' > /usr/test/foobar; chmod +x /usr/test/foobar
RUN echo '#!/bin/sh' > /entrypoint.sh; \
echo "export PATH=/usr/test/:$PATH" >> /entrypoint.sh; \
echo 'exec "$@"' >> /entrypoint.sh; \
chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
""",
)


@pytest.mark.parametrize(
"container_image", [CTR_WITH_ENTRYPOINT_ADDING_PATH], indirect=True
)
def test_entrypoint_respected_in_run(
container_image: ContainerImageData, host
) -> None:
assert "foobar" in host.check_output(
f"{container_image.run_command} foobar"
)

0 comments on commit 9cf6e5d

Please sign in to comment.