Skip to content

Commit 9cf6e5d

Browse files
committed
Add new fixture container_image
1 parent c28ba15 commit 9cf6e5d

File tree

4 files changed

+120
-18
lines changed

4 files changed

+120
-18
lines changed

CHANGELOG.rst

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,12 @@ Improvements and new features:
225225
Container Images exposing the same ports in parallel without marking them as
226226
``singleton=True``.
227227

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

235235
- Add support to add tags to container images via
236236
``DerivedContainer.add_build_tags`` (is now called

pytest_container/container.py

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -526,20 +526,38 @@ def get_launch_cmd(
526526
self,
527527
container_runtime: OciRuntimeBase,
528528
extra_run_args: Optional[List[str]] = None,
529+
in_background: bool = True,
530+
interactive_tty: bool = True,
531+
remove: bool = False,
529532
) -> List[str]:
530533
"""Returns the command to launch this container image.
531534
532535
Args:
536+
container_runtime: The container runtime to be used to launch this
537+
container
538+
533539
extra_run_args: optional list of arguments that are added to the
534540
launch command directly after the ``run -d``.
535541
542+
in_background: flag whether to launch the container with ``-d``
543+
(i.e. in the background). Defaults to ``True``.
544+
545+
interactive: flag whether to launch the container with ``--it``
546+
(i.e. in interactive mode and attach a pseudo TTY). Defaults to
547+
``True``.
548+
549+
remove: flag whether to launch the container with the ``--rm`` flag
550+
(i.e. it will be auto-removed on after stopping)
551+
536552
Returns:
537553
The command to launch the container image described by this class
538554
instance as a list of strings that can be fed directly to
539555
:py:class:`subprocess.Popen` as the ``args`` parameter.
540556
"""
541557
cmd = (
542-
[container_runtime.runner_binary, "run", "-d"]
558+
[container_runtime.runner_binary, "run"]
559+
+ (["-d"] if in_background else [])
560+
+ (["--rm"] if remove else [])
543561
+ (extra_run_args or [])
544562
+ self.extra_launch_args
545563
+ (
@@ -558,7 +576,9 @@ def get_launch_cmd(
558576
)
559577

560578
id_or_url = self.container_id or self.url
561-
container_launch = ("-it", id_or_url)
579+
container_launch: Tuple[str, ...] = (
580+
("-it", id_or_url) if interactive_tty else (id_or_url,)
581+
)
562582
bash_launch_end = (
563583
*_CONTAINER_STOPSIGNAL,
564584
*container_launch,
@@ -994,7 +1014,29 @@ def prepare_container(
9941014

9951015

9961016
@dataclass(frozen=True)
997-
class ContainerData:
1017+
class ContainerImageData:
1018+
#: the container data class that has been used in this test
1019+
container: Union[Container, DerivedContainer, MultiStageContainer]
1020+
1021+
_container_runtime: OciRuntimeBase
1022+
1023+
@property
1024+
def run_command_list(self) -> List[str]:
1025+
return self.container.get_launch_cmd(
1026+
self._container_runtime,
1027+
in_background=False,
1028+
remove=True,
1029+
# -it breaks testinfra.host.check_output() with docker
1030+
interactive_tty=self._container_runtime.runner_binary == "podman",
1031+
)
1032+
1033+
@property
1034+
def run_command(self) -> str:
1035+
return " ".join(self.run_command_list)
1036+
1037+
1038+
@dataclass(frozen=True)
1039+
class ContainerData(ContainerImageData):
9981040
"""Class returned by the ``*container*`` fixtures to the test function. It
9991041
contains information about the launched container and the testinfra
10001042
:py:attr:`connection` to the running container.
@@ -1008,13 +1050,9 @@ class ContainerData:
10081050
container_id: str
10091051
#: the testinfra connection to the running container
10101052
connection: Any
1011-
#: the container data class that has been used in this test
1012-
container: Union[Container, DerivedContainer, MultiStageContainer]
10131053
#: any ports that are exposed by this container
10141054
forwarded_ports: List[PortForwarding]
10151055

1016-
_container_runtime: OciRuntimeBase
1017-
10181056
@property
10191057
def inspect(self) -> ContainerInspect:
10201058
"""Inspect the launched container and return the result of
@@ -1205,11 +1243,7 @@ def from_pytestconfig(
12051243
def __enter__(self) -> "ContainerLauncher":
12061244
return self
12071245

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

1288+
def launch_container(self) -> None:
1289+
"""This function performs the actual heavy lifting of launching the
1290+
container, creating all the volumes, port bindings, etc.pp.
1291+
1292+
"""
1293+
self.prepare_container()
1294+
12541295
forwarded_ports = self.container.forwarded_ports
12551296

12561297
extra_run_args = self.extra_run_args
@@ -1290,6 +1331,13 @@ def release_lock() -> None:
12901331

12911332
self._wait_for_container_to_become_healthy()
12921333

1334+
@property
1335+
def container_image_data(self) -> ContainerImageData:
1336+
# FIXME: check if container is prepared
1337+
return ContainerImageData(
1338+
container=self.container, _container_runtime=self.container_runtime
1339+
)
1340+
12931341
@property
12941342
def container_data(self) -> ContainerData:
12951343
"""The :py:class:`ContainerData` instance corresponding to the running

pytest_container/plugin.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from pytest_container.container import container_and_marks_from_pytest_param
1212
from pytest_container.container import ContainerData
13+
from pytest_container.container import ContainerImageData
1314
from pytest_container.container import ContainerLauncher
1415
from pytest_container.logging import _logger
1516
from pytest_container.pod import pod_from_pytest_param
@@ -173,3 +174,20 @@ def fixture_funct(
173174

174175
#: Same as :py:func:`pod`, except that it creates a pod for each test function.
175176
pod_per_test = _create_auto_pod_fixture("function")
177+
178+
179+
@fixture(scope="session")
180+
def container_image(
181+
request: SubRequest,
182+
# we must call this parameter container runtime, so that pytest will
183+
# treat it as a fixture, but that causes pylint to complain…
184+
# pylint: disable=redefined-outer-name
185+
container_runtime: OciRuntimeBase,
186+
pytestconfig: Config,
187+
) -> Generator[ContainerImageData, None, None]:
188+
container, _ = container_and_marks_from_pytest_param(request.param)
189+
with ContainerLauncher.from_pytestconfig(
190+
container, container_runtime, pytestconfig
191+
) as launcher:
192+
launcher.prepare_container()
193+
yield launcher.container_image_data

tests/test_container_run.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import pytest
2+
from pytest_container.container import ContainerImageData
3+
from pytest_container.container import DerivedContainer
4+
5+
from tests.images import LEAP
6+
7+
8+
@pytest.mark.parametrize("container_image", [LEAP], indirect=True)
9+
def test_run_leap(container_image: ContainerImageData, host) -> None:
10+
assert 'NAME="openSUSE Leap"' in host.check_output(
11+
f"{container_image.run_command} cat /etc/os-release"
12+
)
13+
14+
15+
CTR_WITH_ENTRYPOINT_ADDING_PATH = DerivedContainer(
16+
base=LEAP,
17+
containerfile="""RUN mkdir -p /usr/test/; echo 'echo "foobar"' > /usr/test/foobar; chmod +x /usr/test/foobar
18+
RUN echo '#!/bin/sh' > /entrypoint.sh; \
19+
echo "export PATH=/usr/test/:$PATH" >> /entrypoint.sh; \
20+
echo 'exec "$@"' >> /entrypoint.sh; \
21+
chmod +x /entrypoint.sh
22+
23+
ENTRYPOINT ["/entrypoint.sh"]
24+
""",
25+
)
26+
27+
28+
@pytest.mark.parametrize(
29+
"container_image", [CTR_WITH_ENTRYPOINT_ADDING_PATH], indirect=True
30+
)
31+
def test_entrypoint_respected_in_run(
32+
container_image: ContainerImageData, host
33+
) -> None:
34+
assert "foobar" in host.check_output(
35+
f"{container_image.run_command} foobar"
36+
)

0 commit comments

Comments
 (0)