From 7729afe9793632673c978b347ccbd999b5152042 Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Mon, 28 Feb 2022 16:26:43 -0700 Subject: Refactor docker-py compatibility tests * Add which python client is being used to run tests, see "python client" below. * Remove redundate code from test classes * Update/Add comments to modules and classes ======================================================= test session starts ======================================================== platform linux -- Python 3.10.0, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 python client -- DockerClient rootdir: /home/jhonce/Projects/go/src/github.com/containers/podman plugins: requests-mock-1.8.0 collected 33 items test/python/docker/compat/test_containers.py ...s.............. [ 54%] test/python/docker/compat/test_images.py ............ [ 90%] test/python/docker/compat/test_system.py ... [100%] Note: Follow-up PRs will verify the test results and expand the tests. Signed-off-by: Jhon Honce --- Makefile | 2 +- pyproject.toml | 15 ++ test/python/docker/__init__.py | 30 ++-- test/python/docker/compat/common.py | 77 ++++++++- test/python/docker/compat/constant.py | 4 +- test/python/docker/compat/test_containers.py | 232 +++++++++++---------------- test/python/docker/compat/test_images.py | 131 ++++++--------- test/python/docker/compat/test_system.py | 75 +++------ test/python/docker/conftest.py | 8 + 9 files changed, 275 insertions(+), 299 deletions(-) create mode 100644 pyproject.toml create mode 100644 test/python/docker/conftest.py diff --git a/Makefile b/Makefile index cb230d8e9..611a1c568 100644 --- a/Makefile +++ b/Makefile @@ -540,7 +540,7 @@ validate.completions: .PHONY: run-docker-py-tests run-docker-py-tests: touch test/__init__.py - env CONTAINERS_CONF=$(CURDIR)/test/apiv2/containers.conf pytest test/python/docker/ + env CONTAINERS_CONF=$(CURDIR)/test/apiv2/containers.conf pytest --disable-warnings test/python/docker/ -rm test/__init__.py .PHONY: localunit diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..5a31279c6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,15 @@ +[tool.black] +line-length = 100 +target-version = ['py36'] +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.tox + | \.venv + | build + | dist + | docs + | hack +)/ +''' diff --git a/test/python/docker/__init__.py b/test/python/docker/__init__.py index 816667b82..d46f465f7 100644 --- a/test/python/docker/__init__.py +++ b/test/python/docker/__init__.py @@ -1,4 +1,6 @@ -import configparser +""" +Helpers for integration tests using DockerClient +""" import json import os import pathlib @@ -11,7 +13,7 @@ from docker import DockerClient from .compat import constant -class Podman(object): +class PodmanAPI: """ Instances hold the configuration and setup for running podman commands """ @@ -53,17 +55,13 @@ location = "mirror.localhost:5000" """ - with open(os.environ["CONTAINERS_REGISTRIES_CONF"], "w") as w: - w.write(conf) + with open(os.environ["CONTAINERS_REGISTRIES_CONF"], "w") as file: + file.write(conf) - os.environ["CNI_CONFIG_PATH"] = os.path.join( - self.anchor_directory, "cni", "net.d" - ) + os.environ["CNI_CONFIG_PATH"] = os.path.join(self.anchor_directory, "cni", "net.d") os.makedirs(os.environ["CNI_CONFIG_PATH"], exist_ok=True) self.cmd.append("--network-config-dir=" + os.environ["CNI_CONFIG_PATH"]) - cni_cfg = os.path.join( - os.environ["CNI_CONFIG_PATH"], "87-podman-bridge.conflist" - ) + cni_cfg = os.path.join(os.environ["CNI_CONFIG_PATH"], "87-podman-bridge.conflist") # json decoded and encoded to ensure legal json buf = json.loads( """ @@ -93,8 +91,8 @@ location = "mirror.localhost:5000" } """ ) - with open(cni_cfg, "w") as w: - json.dump(buf, w) + with open(cni_cfg, "w") as file: + json.dump(buf, file) def open(self, command, *args, **kwargs): """Podman initialized instance to run a given command @@ -111,6 +109,7 @@ location = "mirror.localhost:5000" shell = kwargs.get("shell", False) + # pylint: disable=consider-using-with return subprocess.Popen( cmd, shell=shell, @@ -144,9 +143,11 @@ location = "mirror.localhost:5000" ) def tear_down(self): + """Delete test environment.""" shutil.rmtree(self.anchor_directory, ignore_errors=True) def restore_image_from_cache(self, client: DockerClient): + """Populate images from cache.""" path = os.path.join(self.image_cache, constant.ALPINE_TARBALL) if not os.path.exists(path): img = client.images.pull(constant.ALPINE) @@ -157,5 +158,6 @@ location = "mirror.localhost:5000" self.run("load", "-i", path, check=True) def flush_image_cache(self): - for f in pathlib.Path(self.image_cache).glob("*.tar"): - f.unlink(f) + """Delete image cache.""" + for file in pathlib.Path(self.image_cache).glob("*.tar"): + file.unlink(missing_ok=True) diff --git a/test/python/docker/compat/common.py b/test/python/docker/compat/common.py index bdc67c287..218ac9872 100644 --- a/test/python/docker/compat/common.py +++ b/test/python/docker/compat/common.py @@ -1,23 +1,92 @@ +""" +Fixtures and Helpers for unittests. +""" +import subprocess +import sys +import time +import unittest + +# pylint: disable=no-name-in-module,import-error,wrong-import-order from docker import DockerClient +from test.python.docker import PodmanAPI from test.python.docker.compat import constant def run_top_container(client: DockerClient): - c = client.containers.create( - constant.ALPINE, command="top", detach=True, tty=True, name="top" + """Run top command in a alpine container.""" + ctnr = client.containers.create( + constant.ALPINE, + command="top", + detach=True, + tty=True, + name="top", ) - c.start() - return c.id + ctnr.start() + return ctnr.id def remove_all_containers(client: DockerClient): + """Delete all containers from the Podman service.""" for ctnr in client.containers.list(all=True): ctnr.remove(force=True) def remove_all_images(client: DockerClient): + """Delete all images from the Podman service.""" for img in client.images.list(): # FIXME should DELETE /images accept the sha256: prefix? id_ = img.id.removeprefix("sha256:") client.images.remove(id_, force=True) + + +class DockerTestCase(unittest.TestCase): + """Specialized TestCase class for testing against Podman service.""" + + podman: PodmanAPI = None # initialized podman configuration for tests + service: subprocess.Popen = None # podman service instance + + top_container_id: str = None + docker: DockerClient = None + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + + cls.podman = PodmanAPI() + super().addClassCleanup(cls.podman.tear_down) + + cls.service = cls.podman.open("system", "service", "tcp:127.0.0.1:8080", "--time=0") + # give the service some time to be ready... + time.sleep(2) + + return_code = cls.service.poll() + if return_code is not None: + raise subprocess.CalledProcessError(return_code, "podman system service") + + @classmethod + def tearDownClass(cls) -> None: + cls.service.terminate() + stdout, stderr = cls.service.communicate(timeout=0.5) + if stdout: + sys.stdout.write("\ndocker-py -- Service Stdout:\n" + stdout.decode("utf-8")) + if stderr: + sys.stderr.write("\ndocker-py -- Service Stderr:\n" + stderr.decode("utf-8")) + + return super().tearDownClass() + + def setUp(self) -> None: + super().setUp() + + self.docker = DockerClient(base_url="tcp://127.0.0.1:8080", timeout=15) + self.addCleanup(self.docker.close) + + self.podman.restore_image_from_cache(self.docker) + self.top_container_id = run_top_container(self.docker) + self.assertIsNotNone(self.top_container_id, "Failed to create 'top' container") + + def tearDown(self) -> None: + remove_all_containers(self.docker) + remove_all_images(self.docker) + + super().tearDown() diff --git a/test/python/docker/compat/constant.py b/test/python/docker/compat/constant.py index 892293c97..2a0046daf 100644 --- a/test/python/docker/compat/constant.py +++ b/test/python/docker/compat/constant.py @@ -1,6 +1,8 @@ +""" +Constants to use in writing unittests. +""" ALPINE = "quay.io/libpod/alpine:latest" ALPINE_SHORTNAME = "alpine" ALPINE_TARBALL = "alpine.tar" BB = "quay.io/libpod/busybox:latest" NGINX = "quay.io/libpod/alpine_nginx:latest" -infra = "k8s.gcr.io/pause:3.2" diff --git a/test/python/docker/compat/test_containers.py b/test/python/docker/compat/test_containers.py index d14c09fc1..c0b6c9abe 100644 --- a/test/python/docker/compat/test_containers.py +++ b/test/python/docker/compat/test_containers.py @@ -1,111 +1,56 @@ +""" +Integration tests for exercising docker-py against Podman Service. +""" import io -import subprocess -import sys -import time -import unittest -from typing import IO, Optional, List +import tarfile +from typing import IO, List, Optional -from docker import DockerClient, errors +from docker import errors from docker.models.containers import Container from docker.models.images import Image from docker.models.volumes import Volume from docker.types import Mount -from test.python.docker import Podman +# pylint: disable=no-name-in-module,import-error,wrong-import-order from test.python.docker.compat import common, constant -import tarfile - - -class TestContainers(unittest.TestCase): - podman = None # initialized podman configuration for tests - service = None # podman service instance - topContainerId = "" - - def setUp(self): - super().setUp() - self.client = DockerClient(base_url="tcp://127.0.0.1:8080", timeout=15) - TestContainers.podman.restore_image_from_cache(self.client) - TestContainers.topContainerId = common.run_top_container(self.client) - self.assertIsNotNone(TestContainers.topContainerId) - - def tearDown(self): - common.remove_all_containers(self.client) - common.remove_all_images(self.client) - self.client.close() - return super().tearDown() - - @classmethod - def setUpClass(cls): - super().setUpClass() - TestContainers.podman = Podman() - TestContainers.service = TestContainers.podman.open( - "system", "service", "tcp:127.0.0.1:8080", "--time=0" - ) - # give the service some time to be ready... - time.sleep(2) - - rc = TestContainers.service.poll() - if rc is not None: - raise subprocess.CalledProcessError(rc, "podman system service") - @classmethod - def tearDownClass(cls): - TestContainers.service.terminate() - stdout, stderr = TestContainers.service.communicate(timeout=0.5) - if stdout: - sys.stdout.write("\nContainers Service Stdout:\n" + stdout.decode("utf-8")) - if stderr: - sys.stderr.write("\nContainers Service Stderr:\n" + stderr.decode("utf-8")) - - TestContainers.podman.tear_down() - return super().tearDownClass() +# pylint: disable=missing-function-docstring +class TestContainers(common.DockerTestCase): + """TestCase for exercising containers.""" def test_create_container(self): - # Run a container with detach mode - self.client.containers.create(image="alpine", detach=True) - self.assertEqual(len(self.client.containers.list(all=True)), 2) + """Run a container with detach mode.""" + self.docker.containers.create(image="alpine", detach=True) + self.assertEqual(len(self.docker.containers.list(all=True)), 2) def test_create_network(self): - net = self.client.networks.create("testNetwork", driver="bridge") - ctnr = self.client.containers.create(image="alpine", detach=True) - - # TODO fix when ready - # This test will not work until all connect|disconnect - # code is fixed. - # net.connect(ctnr) - - # nets = self.client.networks.list(greedy=True) - # self.assertGreaterEqual(len(nets), 1) - - # TODO fix endpoint to include containers - # for n in nets: - # if n.id == "testNetwork": - # self.assertEqual(ctnr.id, n.containers) - # self.assertTrue(False, "testNetwork not found") + """Add network to a container.""" + self.docker.networks.create("testNetwork", driver="bridge") + self.docker.containers.create(image="alpine", detach=True) def test_start_container(self): # Podman docs says it should give a 304 but returns with no response # # Start a already started container should return 304 - # response = self.client.api.start(container=TestContainers.topContainerId) + # response = self.docker.api.start(container=self.top_container_id) # self.assertEqual(error.exception.response.status_code, 304) # Create a new container and validate the count - self.client.containers.create(image=constant.ALPINE, name="container2") - containers = self.client.containers.list(all=True) + self.docker.containers.create(image=constant.ALPINE, name="container2") + containers = self.docker.containers.list(all=True) self.assertEqual(len(containers), 2) def test_start_container_with_random_port_bind(self): - container = self.client.containers.create( + container = self.docker.containers.create( image=constant.ALPINE, name="containerWithRandomBind", ports={"1234/tcp": None}, ) - containers = self.client.containers.list(all=True) + containers = self.docker.containers.list(all=True) self.assertTrue(container in containers) def test_stop_container(self): - top = self.client.containers.get(TestContainers.topContainerId) + top = self.docker.containers.get(self.top_container_id) self.assertEqual(top.status, "running") # Stop a running container and validate the state @@ -114,7 +59,7 @@ class TestContainers(unittest.TestCase): self.assertIn(top.status, ("stopped", "exited")) def test_kill_container(self): - top = self.client.containers.get(TestContainers.topContainerId) + top = self.docker.containers.get(self.top_container_id) self.assertEqual(top.status, "running") # Kill a running container and validate the state @@ -124,7 +69,7 @@ class TestContainers(unittest.TestCase): def test_restart_container(self): # Validate the container state - top = self.client.containers.get(TestContainers.topContainerId) + top = self.docker.containers.get(self.top_container_id) top.stop() top.reload() self.assertIn(top.status, ("stopped", "exited")) @@ -136,16 +81,16 @@ class TestContainers(unittest.TestCase): def test_remove_container(self): # Remove container by ID with force - top = self.client.containers.get(TestContainers.topContainerId) + top = self.docker.containers.get(self.top_container_id) top.remove(force=True) - self.assertEqual(len(self.client.containers.list()), 0) + self.assertEqual(len(self.docker.containers.list()), 0) def test_remove_container_without_force(self): # Validate current container count - self.assertEqual(len(self.client.containers.list()), 1) + self.assertEqual(len(self.docker.containers.list()), 1) # Remove running container should throw error - top = self.client.containers.get(TestContainers.topContainerId) + top = self.docker.containers.get(self.top_container_id) with self.assertRaises(errors.APIError) as error: top.remove() self.assertEqual(error.exception.response.status_code, 500) @@ -153,11 +98,11 @@ class TestContainers(unittest.TestCase): # Remove container by ID without force top.stop() top.remove() - self.assertEqual(len(self.client.containers.list()), 0) + self.assertEqual(len(self.docker.containers.list()), 0) def test_pause_container(self): # Validate the container state - top = self.client.containers.get(TestContainers.topContainerId) + top = self.docker.containers.get(self.top_container_id) self.assertEqual(top.status, "running") # Pause a running container and validate the state @@ -167,7 +112,7 @@ class TestContainers(unittest.TestCase): def test_pause_stopped_container(self): # Stop the container - top = self.client.containers.get(TestContainers.topContainerId) + top = self.docker.containers.get(self.top_container_id) top.stop() # Pause exited container should throw error @@ -176,7 +121,7 @@ class TestContainers(unittest.TestCase): self.assertEqual(error.exception.response.status_code, 500) def test_unpause_container(self): - top = self.client.containers.get(TestContainers.topContainerId) + top = self.docker.containers.get(self.top_container_id) # Validate the container state top.pause() @@ -190,21 +135,21 @@ class TestContainers(unittest.TestCase): def test_list_container(self): # Add container and validate the count - self.client.containers.create(image="alpine", detach=True) - containers = self.client.containers.list(all=True) + self.docker.containers.create(image="alpine", detach=True) + containers = self.docker.containers.list(all=True) self.assertEqual(len(containers), 2) def test_filters(self): self.skipTest("TODO Endpoint does not yet support filters") # List container with filter by id - filters = {"id": TestContainers.topContainerId} - ctnrs = self.client.containers.list(all=True, filters=filters) + filters = {"id": self.top_container_id} + ctnrs = self.docker.containers.list(all=True, filters=filters) self.assertEqual(len(ctnrs), 1) # List container with filter by name filters = {"name": "top"} - ctnrs = self.client.containers.list(all=True, filters=filters) + ctnrs = self.docker.containers.list(all=True, filters=filters) self.assertEqual(len(ctnrs), 1) def test_copy_to_container(self): @@ -212,31 +157,33 @@ class TestContainers(unittest.TestCase): vol: Optional[Volume] = None try: test_file_content = b"Hello World!" - vol = self.client.volumes.create("test-volume") - ctr = self.client.containers.create(image="alpine", - detach=True, - command="top", - volumes=["test-volume:/test-volume-read-only:ro"]) + vol = self.docker.volumes.create("test-volume") + ctr = self.docker.containers.create( + image="alpine", + detach=True, + command="top", + volumes=["test-volume:/test-volume-read-only:ro"], + ) ctr.start() buff: IO[bytes] = io.BytesIO() - with tarfile.open(fileobj=buff, mode="w:") as tf: - ti: tarfile.TarInfo = tarfile.TarInfo() - ti.uid = 1042 - ti.gid = 1043 - ti.name = "a.txt" - ti.path = "a.txt" - ti.mode = 0o644 - ti.type = tarfile.REGTYPE - ti.size = len(test_file_content) - tf.addfile(ti, fileobj=io.BytesIO(test_file_content)) + with tarfile.open(fileobj=buff, mode="w:") as file: + info: tarfile.TarInfo = tarfile.TarInfo() + info.uid = 1042 + info.gid = 1043 + info.name = "a.txt" + info.path = "a.txt" + info.mode = 0o644 + info.type = tarfile.REGTYPE + info.size = len(test_file_content) + file.addfile(info, fileobj=io.BytesIO(test_file_content)) buff.seek(0) ctr.put_archive("/tmp/", buff) ret, out = ctr.exec_run(["stat", "-c", "%u:%g", "/tmp/a.txt"]) self.assertEqual(ret, 0) - self.assertEqual(out.rstrip(), b'1042:1043', "UID/GID of copied file") + self.assertEqual(out.rstrip(), b"1042:1043", "UID/GID of copied file") ret, out = ctr.exec_run(["cat", "/tmp/a.txt"]) self.assertEqual(ret, 0) @@ -253,48 +200,61 @@ class TestContainers(unittest.TestCase): vol.remove(force=True) def test_mount_preexisting_dir(self): - dockerfile = (B'FROM quay.io/libpod/alpine:latest\n' - B'USER root\n' - B'RUN mkdir -p /workspace\n' - B'RUN chown 1042:1043 /workspace') + dockerfile = ( + b"FROM quay.io/libpod/alpine:latest\n" + b"USER root\n" + b"RUN mkdir -p /workspace\n" + b"RUN chown 1042:1043 /workspace" + ) img: Image - img, out = self.client.images.build(fileobj=io.BytesIO(dockerfile)) - ctr: Container = self.client.containers.create(image=img.id, detach=True, command="top", - volumes=["test_mount_preexisting_dir_vol:/workspace"]) + img, out = self.docker.images.build(fileobj=io.BytesIO(dockerfile)) + ctr: Container = self.docker.containers.create( + image=img.id, + detach=True, + command="top", + volumes=["test_mount_preexisting_dir_vol:/workspace"], + ) ctr.start() - ret, out = ctr.exec_run(["stat", "-c", "%u:%g", "/workspace"]) - self.assertEqual(out.rstrip(), b'1042:1043', "UID/GID set in dockerfile") - + _, out = ctr.exec_run(["stat", "-c", "%u:%g", "/workspace"]) + self.assertEqual(out.rstrip(), b"1042:1043", "UID/GID set in dockerfile") def test_non_existant_workdir(self): - dockerfile = (B'FROM quay.io/libpod/alpine:latest\n' - B'USER root\n' - B'WORKDIR /workspace/scratch\n' - B'RUN touch test') + dockerfile = ( + b"FROM quay.io/libpod/alpine:latest\n" + b"USER root\n" + b"WORKDIR /workspace/scratch\n" + b"RUN touch test" + ) img: Image - img, out = self.client.images.build(fileobj=io.BytesIO(dockerfile)) - ctr: Container = self.client.containers.create(image=img.id, detach=True, command="top", - volumes=["test_non_existant_workdir:/workspace"]) + img, _ = self.docker.images.build(fileobj=io.BytesIO(dockerfile)) + ctr: Container = self.docker.containers.create( + image=img.id, + detach=True, + command="top", + volumes=["test_non_existant_workdir:/workspace"], + ) ctr.start() - ret, out = ctr.exec_run(["stat", "/workspace/scratch/test"]) + ret, _ = ctr.exec_run(["stat", "/workspace/scratch/test"]) self.assertEqual(ret, 0, "Working directory created if it doesn't exist") def test_mount_rw_by_default(self): ctr: Optional[Container] = None vol: Optional[Volume] = None + try: - vol = self.client.volumes.create("test-volume") - ctr = self.client.containers.create(image="alpine", - detach=True, - command="top", - mounts=[Mount(target="/vol-mnt", - source="test-volume", - type='volume', - read_only=False)]) - ctr_inspect = self.client.api.inspect_container(ctr.id) + vol = self.docker.volumes.create("test-volume") + ctr = self.docker.containers.create( + image="alpine", + detach=True, + command="top", + mounts=[ + Mount(target="/vol-mnt", source="test-volume", type="volume", read_only=False) + ], + ) + ctr_inspect = self.docker.api.inspect_container(ctr.id) binds: List[str] = ctr_inspect["HostConfig"]["Binds"] self.assertEqual(len(binds), 1) - self.assertEqual(binds[0], 'test-volume:/vol-mnt:rw,rprivate,nosuid,nodev,rbind') + self.assertEqual(binds[0], "test-volume:/vol-mnt:rw,rprivate,nosuid,nodev,rbind") finally: if ctr is not None: ctr.remove() diff --git a/test/python/docker/compat/test_images.py b/test/python/docker/compat/test_images.py index 05d0e3e12..ac9a8d671 100644 --- a/test/python/docker/compat/test_images.py +++ b/test/python/docker/compat/test_images.py @@ -1,116 +1,73 @@ -import collections +""" +Integration tests for exercising docker-py against Podman Service. +""" import io import os -import subprocess -import sys -import time import unittest -from docker import DockerClient, errors -from docker.errors import APIError +from docker import errors -from test.python.docker import Podman +# pylint: disable=no-name-in-module,import-error,wrong-import-order from test.python.docker.compat import common, constant -class TestImages(unittest.TestCase): - podman = None # initialized podman configuration for tests - service = None # podman service instance - - def setUp(self): - super().setUp() - self.client = DockerClient(base_url="tcp://127.0.0.1:8080", timeout=15) - - TestImages.podman.restore_image_from_cache(self.client) - - def tearDown(self): - common.remove_all_images(self.client) - self.client.close() - return super().tearDown() - - @classmethod - def setUpClass(cls): - super().setUpClass() - TestImages.podman = Podman() - TestImages.service = TestImages.podman.open( - "system", "service", "tcp:127.0.0.1:8080", "--time=0" - ) - # give the service some time to be ready... - time.sleep(2) - - returncode = TestImages.service.poll() - if returncode is not None: - raise subprocess.CalledProcessError(returncode, "podman system service") - - @classmethod - def tearDownClass(cls): - TestImages.service.terminate() - stdout, stderr = TestImages.service.communicate(timeout=0.5) - if stdout: - sys.stdout.write("\nImages Service Stdout:\n" + stdout.decode("utf-8")) - if stderr: - sys.stderr.write("\nImAges Service Stderr:\n" + stderr.decode("utf-8")) - - TestImages.podman.tear_down() - return super().tearDownClass() +class TestImages(common.DockerTestCase): + """TestCase for exercising images.""" def test_tag_valid_image(self): """Validates if the image is tagged successfully""" - alpine = self.client.images.get(constant.ALPINE) + alpine = self.docker.images.get(constant.ALPINE) self.assertTrue(alpine.tag("demo", constant.ALPINE_SHORTNAME)) - alpine = self.client.images.get(constant.ALPINE) - for t in alpine.tags: - self.assertIn("alpine", t) + alpine = self.docker.images.get(constant.ALPINE) + for tag in alpine.tags: + self.assertIn("alpine", tag) - # @unittest.skip("doesn't work now") def test_retag_valid_image(self): - """Validates if name updates when the image is retagged""" - alpine = self.client.images.get(constant.ALPINE) + """Validates if name updates when the image is re-tagged.""" + alpine = self.docker.images.get(constant.ALPINE) self.assertTrue(alpine.tag("demo", "rename")) - alpine = self.client.images.get(constant.ALPINE) + alpine = self.docker.images.get(constant.ALPINE) self.assertNotIn("demo:test", alpine.tags) def test_list_images(self): """List images""" - self.assertEqual(len(self.client.images.list()), 1) + self.assertEqual(len(self.docker.images.list()), 1) # Add more images - self.client.images.pull(constant.BB) - self.assertEqual(len(self.client.images.list()), 2) - self.assertEqual(len(self.client.images.list(all=True)), 2) + self.docker.images.pull(constant.BB) + self.assertEqual(len(self.docker.images.list()), 2) + self.assertEqual(len(self.docker.images.list(all=True)), 2) # List images with filter - self.assertEqual(len(self.client.images.list(filters={"reference": "alpine"})), 1) + self.assertEqual(len(self.docker.images.list(filters={"reference": "alpine"})), 1) def test_search_image(self): """Search for image""" - for r in self.client.images.search("alpine"): + for registry in self.docker.images.search("alpine"): # registry matches if string is in either one - self.assertIn("alpine", r["Name"]+" "+r["Description"].lower()) + self.assertIn("alpine", registry["Name"] + " " + registry["Description"].lower()) def test_search_bogus_image(self): """Search for bogus image should throw exception""" - try: - r = self.client.images.search("bogus/bogus") - except: - return - self.assertTrue(len(r) == 0) + with self.assertRaises(errors.APIError): + self.docker.images.search("bogus/bogus") def test_remove_image(self): """Remove image""" # Check for error with wrong image name with self.assertRaises(errors.NotFound): - self.client.images.remove("dummy") - self.assertEqual(len(self.client.images.list()), 1) + self.docker.images.remove("dummy") - self.client.images.remove(constant.ALPINE) - self.assertEqual(len(self.client.images.list()), 0) + common.remove_all_containers(self.docker) + self.assertEqual(len(self.docker.images.list()), 1) + self.docker.images.remove(constant.ALPINE) + self.assertEqual(len(self.docker.images.list()), 0) def test_image_history(self): """Image history""" - img = self.client.images.get(constant.ALPINE) + img = self.docker.images.get(constant.ALPINE) history = img.history() image_id = img.id[7:] if img.id.startswith("sha256:") else img.id @@ -122,47 +79,49 @@ class TestImages(unittest.TestCase): def test_get_image_exists_not(self): """Negative test for get image""" with self.assertRaises(errors.NotFound): - response = self.client.images.get("image_does_not_exists") - collections.deque(response) + self.docker.images.get("image_does_not_exists") def test_save_image(self): """Export Image""" - image = self.client.images.pull(constant.BB) + image = self.docker.images.pull(constant.BB) file = os.path.join(TestImages.podman.image_cache, "busybox.tar") with open(file, mode="wb") as tarball: for frame in image.save(named=True): tarball.write(frame) - sz = os.path.getsize(file) - self.assertGreater(sz, 0) + self.assertGreater(os.path.getsize(file), 0) def test_load_image(self): """Import|Load Image""" - self.assertEqual(len(self.client.images.list()), 1) + self.assertEqual(len(self.docker.images.list()), 1) - image = self.client.images.pull(constant.BB) + image = self.docker.images.pull(constant.BB) file = os.path.join(TestImages.podman.image_cache, "busybox.tar") with open(file, mode="wb") as tarball: for frame in image.save(): tarball.write(frame) with open(file, mode="rb") as saved: - _ = self.client.images.load(saved) + self.docker.images.load(saved) - self.assertEqual(len(self.client.images.list()), 2) + self.assertEqual(len(self.docker.images.list()), 2) def test_load_corrupt_image(self): """Import|Load Image failure""" tarball = io.BytesIO("This is a corrupt tarball".encode("utf-8")) - with self.assertRaises(APIError): - self.client.images.load(tarball) + with self.assertRaises(errors.APIError): + self.docker.images.load(tarball) def test_build_image(self): + """Build Image with custom labels.""" labels = {"apple": "red", "grape": "green"} - _ = self.client.images.build( - path="test/python/docker/build_labels", labels=labels, tag="labels", isolation="default" + self.docker.images.build( + path="test/python/docker/build_labels", + labels=labels, + tag="labels", + isolation="default", ) - image = self.client.images.get("labels") + image = self.docker.images.get("labels") self.assertEqual(image.labels["apple"], labels["apple"]) self.assertEqual(image.labels["grape"], labels["grape"]) diff --git a/test/python/docker/compat/test_system.py b/test/python/docker/compat/test_system.py index a928de0ee..f6092996b 100644 --- a/test/python/docker/compat/test_system.py +++ b/test/python/docker/compat/test_system.py @@ -1,71 +1,32 @@ -import subprocess -import sys -import time -import unittest +""" +Integration tests for exercising docker-py against Podman Service. +""" -from docker import DockerClient - -from test.python.docker import Podman, constant +# pylint: disable=no-name-in-module,import-error,wrong-import-order +from test.python.docker import constant from test.python.docker.compat import common -class TestSystem(unittest.TestCase): - podman = None # initialized podman configuration for tests - service = None # podman service instance - topContainerId = "" - - def setUp(self): - super().setUp() - self.client = DockerClient(base_url="tcp://127.0.0.1:8080", timeout=15) - - TestSystem.podman.restore_image_from_cache(self.client) - TestSystem.topContainerId = common.run_top_container(self.client) - - def tearDown(self): - common.remove_all_containers(self.client) - common.remove_all_images(self.client) - self.client.close() - return super().tearDown() - - @classmethod - def setUpClass(cls): - super().setUpClass() - TestSystem.podman = Podman() - TestSystem.service = TestSystem.podman.open( - "system", "service", "tcp:127.0.0.1:8080", "--time=0" - ) - # give the service some time to be ready... - time.sleep(2) - - returncode = TestSystem.service.poll() - if returncode is not None: - raise subprocess.CalledProcessError(returncode, "podman system service") - - @classmethod - def tearDownClass(cls): - TestSystem.service.terminate() - stdout, stderr = TestSystem.service.communicate(timeout=0.5) - if stdout: - sys.stdout.write("\nImages Service Stdout:\n" + stdout.decode("utf-8")) - if stderr: - sys.stderr.write("\nImAges Service Stderr:\n" + stderr.decode("utf-8")) +# pylint: disable=missing-function-docstring +class TestSystem(common.DockerTestCase): + """TestCase for exercising Podman system services.""" - TestSystem.podman.tear_down() - return super().tearDownClass() - - def test_Info(self): - info = self.client.info() + def test_info(self): + info = self.docker.info() self.assertIsNotNone(info) self.assertEqual(info["RegistryConfig"]["IndexConfigs"]["localhost:5000"]["Secure"], False) - self.assertEqual(info["RegistryConfig"]["IndexConfigs"]["localhost:5000"]["Mirrors"], ["mirror.localhost:5000"]) + self.assertEqual( + info["RegistryConfig"]["IndexConfigs"]["localhost:5000"]["Mirrors"], + ["mirror.localhost:5000"], + ) def test_info_container_details(self): - info = self.client.info() + info = self.docker.info() self.assertEqual(info["Containers"], 1) - self.client.containers.create(image=constant.ALPINE) - info = self.client.info() + self.docker.containers.create(image=constant.ALPINE) + info = self.docker.info() self.assertEqual(info["Containers"], 2) def test_version(self): - version = self.client.version() + version = self.docker.version() self.assertIsNotNone(version["Platform"]["Name"]) diff --git a/test/python/docker/conftest.py b/test/python/docker/conftest.py new file mode 100644 index 000000000..c63780fa8 --- /dev/null +++ b/test/python/docker/conftest.py @@ -0,0 +1,8 @@ +""" +Configure pytest +""" + + +def pytest_report_header(config): + """Add header to report.""" + return "python client -- DockerClient" -- cgit v1.2.3-54-g00ecf