Skip to content

Commit c15af88

Browse files
committed
Add unit test for docker_cli_wrapper.py
Fix generate only
1 parent 8293402 commit c15af88

File tree

4 files changed

+135
-30
lines changed

4 files changed

+135
-30
lines changed

tesseract_core/sdk/docker_cli_wrapper.py

+21-28
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,6 @@ def remove(self, image_id: str) -> None:
7878
capture_output=True,
7979
)
8080

81-
self._update_images()
82-
8381
except subprocess.CalledProcessError as ex:
8482
raise CLIDockerClient.Errors.ImageNotFound(
8583
f"Cannot remove image {image_id}: {ex}"
@@ -136,6 +134,7 @@ def buildx(
136134
return None
137135

138136
out_pipe = LogPipe(logging.DEBUG)
137+
139138
with out_pipe as out_pipe_fd:
140139
proc = subprocess.run(build_cmd, stdout=out_pipe_fd, stderr=out_pipe_fd)
141140

@@ -174,6 +173,8 @@ def buildx(
174173
def _update_images(self) -> None:
175174
"""Updates the list of images by querying Docker CLI."""
176175
try:
176+
# Reset self.images completely
177+
self.images = []
177178
image_ids = subprocess.run(
178179
["docker", "images", "-q"], # List only image IDs
179180
capture_output=True,
@@ -182,20 +183,11 @@ def _update_images(self) -> None:
182183
)
183184
if not image_ids.stdout:
184185
images = []
185-
else:
186-
images = image_ids.stdout.strip().split("\n")
187-
188-
# Clean up deleted images.
189-
for image in self.images:
190-
if image.id not in images:
191-
self.images.remove(image)
192-
193-
# Filter list to exclude image ids that are already in self.images.
194-
images = [
195-
image_id
196-
for image_id in images
197-
if image_id not in self.images and image_id
198-
]
186+
return
187+
188+
images = image_ids.stdout.strip().split("\n")
189+
# Filter list to exclude empty strings.
190+
images = [image_id for image_id in images if image_id]
199191
json_dicts = get_docker_metadata(images, is_image=True)
200192
for _, json_dict in json_dicts.items():
201193
image = self.Image(json_dict)
@@ -337,6 +329,11 @@ def get(self, container: str) -> Container:
337329
for _, container_obj in self.list().items():
338330
if container_obj.name == container:
339331
return container_obj
332+
333+
if container_obj is None:
334+
raise CLIDockerClient.Errors.ContainerError(
335+
f"Container {container} not found."
336+
)
340337
return container_obj
341338

342339
def run(
@@ -426,6 +423,8 @@ def run(
426423
def _update_containers(self) -> None:
427424
"""Update self.containers."""
428425
try:
426+
# Reset self.containers completely
427+
self.containers = {}
429428
result = subprocess.run(
430429
["docker", "ps", "-q"], # List only container IDs
431430
capture_output=True,
@@ -434,19 +433,13 @@ def _update_containers(self) -> None:
434433
)
435434
if not result.stdout:
436435
container_ids = []
437-
else:
438-
container_ids = result.stdout.strip().split("\n")
439-
440-
# Check if theres any cleaned up containers.
441-
for container_id in list(self.containers.keys()):
442-
if container_id not in container_ids:
443-
del self.containers[container_id]
444-
# Filter list to exclude container ids that are already in self.containers
445-
# also exclude empty strings.
436+
return
437+
438+
container_ids = result.stdout.strip().split("\n")
439+
440+
# Filter list to exclude empty strings.
446441
container_ids = [
447-
container_id
448-
for container_id in container_ids
449-
if container_id not in self.containers and container_id
442+
container_id for container_id in container_ids if container_id
450443
]
451444
json_dicts = get_docker_metadata(container_ids)
452445
for container_id, json_dict in json_dicts.items():

tests/conftest.py

+16
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,22 @@ def unit_tesseract_path(request) -> Path:
8383
return UNIT_TESSERACT_PATH / request.param
8484

8585

86+
@pytest.fixture()
87+
def dummy_docker_file(tmpdir):
88+
"""Create a dummy Dockerfile for testing."""
89+
dockerfile_path = tmpdir / "Dockerfile"
90+
dockerfile_content = """
91+
FROM alpine
92+
CMD echo "Hello, Tesseract!" && sleep infinity
93+
94+
# Set environment variables
95+
ENV TESSERACT_NAME="dummy-tesseract" \
96+
"""
97+
with open(dockerfile_path, "w") as f:
98+
f.write(dockerfile_content)
99+
return dockerfile_path
100+
101+
86102
@pytest.fixture(scope="session")
87103
def dummy_tesseract_location():
88104
"""Return the dummy tesseract location."""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""End to end tests for docker cli wrapper."""
2+
3+
from common import image_exists
4+
5+
from tesseract_core.sdk.docker_cli_wrapper import CLIDockerClient
6+
7+
8+
def test_create_image(docker_client, dummy_tesseract_location, dummy_docker_file):
9+
"""Test image building, getting, and removing."""
10+
image, image1 = None, None
11+
# Create an image
12+
try:
13+
image_name = "tesseract_create_image_test:dummy"
14+
15+
docker_client.images.buildx(
16+
dummy_tesseract_location, image_name, dummy_docker_file
17+
)
18+
image = docker_client.images.get(image_name)
19+
assert image is not None
20+
assert image.name == image_name
21+
22+
# Create a second image
23+
image1_name = "tesseract_core_test:dummy1"
24+
docker_client.images.buildx(
25+
dummy_tesseract_location, image1_name, dummy_docker_file
26+
)
27+
image1 = docker_client.images.get(image1_name)
28+
assert image1 is not None
29+
assert image1.name == image1_name
30+
31+
# Check that image and image1 both exist
32+
assert image_exists(docker_client, image.name)
33+
assert image_exists(docker_client, image1.name)
34+
35+
finally:
36+
# Clean up the images
37+
try:
38+
if image:
39+
docker_client.images.remove(image.name)
40+
if image1:
41+
docker_client.images.remove(image1.name)
42+
43+
except CLIDockerClient.Errors.ImageNotFound:
44+
pass
45+
46+
# Check that images are removed
47+
assert not image_exists(docker_client, image.name)
48+
assert not image_exists(docker_client, image1.name)
49+
50+
51+
def test_create_container(docker_client, dummy_tesseract_location, dummy_docker_file):
52+
"""Test container creation, run, logs, and remove."""
53+
# Create a container
54+
image, container = None, None
55+
try:
56+
image_name = "tesseract_create_container_test:dummy"
57+
58+
docker_client.images.buildx(
59+
dummy_tesseract_location, image_name, dummy_docker_file
60+
)
61+
image = docker_client.images.get(image_name)
62+
assert image is not None
63+
assert image.name == image_name
64+
65+
container = docker_client.containers.run(image_name, [], detach=True)
66+
assert container is not None
67+
container_id = container.id
68+
69+
container_get = docker_client.containers.get(container_id)
70+
assert container_get is not None
71+
assert container_get.id == container.id
72+
73+
logs = container.logs()
74+
assert logs == ("Hello, Tesseract!\n", "")
75+
76+
finally:
77+
try:
78+
# Clean up the container
79+
if container:
80+
container.remove(v=True, force=True)
81+
82+
# Check that the container is removed
83+
containers = docker_client.containers.list()
84+
assert container.id not in containers.keys()
85+
except CLIDockerClient.Errors.ContainerError:
86+
pass
87+
88+
# Image has to be removed after container is removed
89+
try:
90+
if image:
91+
docker_client.images.remove(image.id)
92+
except CLIDockerClient.Errors.ImageNotFound:
93+
pass

tests/sdk_tests/test_engine.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,11 @@ def test_build_image(
5050
if generate_only:
5151
# Check stdout if it contains the correct docker build command
5252
assert got is None
53-
command = f"docker buildx build --load --no-cache --tag {image_name} --file {dockerfile_path} {tmpdir}"
54-
assert command in caplog.text
53+
# Split into 2 parts because label is timestamp based
54+
command1 = f"docker buildx build --load --tag {image_name} --label"
55+
command2 = f"--file {dockerfile_path} {tmpdir}"
56+
assert command1 in caplog.text
57+
assert command2 in caplog.text
5558
else:
5659
assert got.attrs == mocked_docker.images.get(image_name).attrs
5760

0 commit comments

Comments
 (0)