Skip to content

Commit 7ff0d6a

Browse files
committed
Fix end to end tests
Handle empty docker result Fix linter Fixed end to end tests except one volume test Put the skip mounts back add arg to build tesseract Add short id to container Fix volume args
1 parent 10e47d2 commit 7ff0d6a

File tree

7 files changed

+62
-39
lines changed

7 files changed

+62
-39
lines changed

tesseract_core/sdk/cli.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -754,20 +754,19 @@ def run_container(
754754
)
755755

756756
except RuntimeError as e:
757+
if "repository" in str(e) or "Repository" in str(e):
758+
raise UserError(
759+
"Tesseract image not found. "
760+
f"Are you sure your tesseract image name is {tesseract_image}?\n\n{e}"
761+
) from e
762+
757763
if "No such command" in str(e):
758764
error_string = f"Error running Tesseract '{tesseract_image}' \n\n Error: Unimplemented command '{cmd}'. "
759765
else:
760766
error_string = _sanitize_error_output(
761767
f"Error running Tesseract. \n\n{e}", tesseract_image
762768
)
763769

764-
765-
if "repository" in str(e) or "Repository" in str(e):
766-
raise UserError(
767-
"Tesseract image not found. "
768-
f"Are you sure your tesseract image name is {tesseract_image}?\n\n{e}"
769-
) from e
770-
771770
raise UserError(error_string) from e
772771

773772
if invoke_help:

tesseract_core/sdk/docker_cli_wrapper.py

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class Container:
2727

2828
def __init__(self, json_dict: dict) -> None:
2929
self.id = json_dict.get("Id", None)
30+
self.short_id = self.id[:12] if self.id else None
3031
self.name = json_dict.get("Name", None)
3132
ports = json_dict.get("NetworkSettings", None)
3233
if ports:
@@ -48,18 +49,17 @@ def __init__(self, json_dict: dict) -> None:
4849
self.short_id = self.id[:12] if self.id else None
4950
self.attrs = json_dict
5051
self.tags = json_dict.get("RepoTags", None)
51-
self.name = self.tags[0] if self.tags else None
52+
# Docker images may be prefixed with the registry URL
53+
self.name = self.tags[0].split("/")[-1] if self.tags else None
5254

5355
def get_all_containers(self) -> dict:
5456
"""Returns the current list of containers."""
55-
if not self.containers:
56-
self._update_containers()
57+
self._update_containers()
5758
return self.containers
5859

5960
def get_all_images(self) -> dict:
6061
"""Returns the current list of images."""
61-
if not self.images:
62-
self._update_images()
62+
self._update_images()
6363
return self.images
6464

6565
def get_projects(self) -> dict:
@@ -182,14 +182,12 @@ def exec_run(self, container_id: str, command: str) -> tuple:
182182
Return exit code and stdout.
183183
"""
184184
try:
185-
print("AKOAKO === ENTERING EXEC_RUN")
186185
result = subprocess.run(
187186
["docker", "exec", container_id, *command],
188187
check=True,
189188
capture_output=True,
190189
text=True,
191190
)
192-
print("AKOAKO === EXITING EXEC_RUN")
193191
return result.returncode, result.stdout
194192
except subprocess.CalledProcessError as ex:
195193
raise RuntimeError(
@@ -199,20 +197,22 @@ def exec_run(self, container_id: str, command: str) -> tuple:
199197
def run_container(
200198
self,
201199
image_id: str,
202-
command: str,
200+
command: list[str],
203201
parsed_volumes: dict,
204202
gpus: list[int | str] | None = None,
205203
) -> bool:
206204
"""Run a command in a container from an image."""
207205
# Convert the parsed_volumes into a list of strings in proper argument format,
208206
# `-v host_path:container_path:mode`.
209207
if not parsed_volumes:
210-
parsed_volumes = []
208+
volume_args = []
211209
else:
212-
parsed_volumes = [
213-
f"-v {host_path}:{volume_info['bind']}:{volume_info['mode']}"
214-
for host_path, volume_info in parsed_volumes.items()
215-
]
210+
volume_args = []
211+
for host_path, volume_info in parsed_volumes.items():
212+
volume_args.append("-v")
213+
volume_args.append(
214+
f"{host_path}:{volume_info['bind']}:{volume_info['mode']}"
215+
)
216216

217217
if gpus:
218218
gpus_str = ",".join(gpus)
@@ -225,7 +225,7 @@ def run_container(
225225
"docker",
226226
"run",
227227
"--rm",
228-
*parsed_volumes,
228+
*volume_args,
229229
*([gpus_option] if gpus_option else []),
230230
image_id,
231231
*command,
@@ -245,7 +245,9 @@ def run_container(
245245
return result.stdout, result.stderr
246246

247247
except subprocess.CalledProcessError as ex:
248-
raise RuntimeError(f"{ex.stderr}") from ex
248+
raise RuntimeError(
249+
f"Error running command: `{' '.join(cmd_list)}`. \n\n{ex.stderr}"
250+
) from ex
249251

250252
def docker_buildx(
251253
self,
@@ -303,6 +305,8 @@ def docker_buildx(
303305
if return_code != 0:
304306
raise RuntimeError("Error while building Docker image", logs)
305307

308+
# Update self.images
309+
self._update_images()
306310
# Get image object
307311
image = self.get_image(tag)
308312
return image
@@ -316,7 +320,10 @@ def _update_containers(self) -> None:
316320
text=True,
317321
check=True,
318322
)
319-
container_ids = result.stdout.strip().split("\n")
323+
if not result.stdout:
324+
container_ids = []
325+
else:
326+
container_ids = result.stdout.strip().split("\n")
320327

321328
# Check if theres any cleaned up containers.
322329
for container_id in self.containers:
@@ -347,7 +354,10 @@ def _update_images(self) -> None:
347354
text=True,
348355
check=True,
349356
)
350-
images = image_ids.stdout.strip().split("\n")
357+
if not image_ids.stdout:
358+
images = []
359+
else:
360+
images = image_ids.stdout.strip().split("\n")
351361

352362
# Clean up deleted images.
353363
for image in self.images:

tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,16 +281,16 @@ def mocked_subprocess_run(*args, **kwargs):
281281
if "run" in args[0]:
282282
# Return args to `docker run` as json.
283283
args = args[0]
284-
285284
return_dict = {"volumes": {}, "device_requests": None}
286285
volume_arg = args[3]
287286
while "-v" in volume_arg:
288287
# Add all the volumes and remove them from arg list.
289-
volume = args[3].split(" ")[1].split(":")
288+
volume = args[4].split(":")
290289
return_dict["volumes"].update(
291290
{volume[0]: {"bind": volume[1], "mode": volume[2]}}
292291
)
293292
# Remove this argument to fix indexing for none volume cases.
293+
args.pop(4)
294294
args.pop(3)
295295
volume_arg = args[3]
296296

tests/endtoend_tests/common.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,9 @@
99

1010

1111
def image_exists(docker_wrapper, image_name):
12-
# Docker images may be prefixed with the registry URL
13-
return any(
14-
tag.split("/")[-1] == image_name
15-
for img in docker_wrapper.get_all_images()
16-
for tag in img.tags
17-
)
12+
images = docker_wrapper.get_all_images()
13+
# If any image.name starts with image_name, we consider it exists
14+
return any(image_name in image.name for image in images)
1815

1916

2017
def print_debug_info(result):
@@ -26,7 +23,7 @@ def print_debug_info(result):
2623
traceback.print_exception(*result.exc_info)
2724

2825

29-
def build_tesseract(sourcedir, image_name, tag=None, build_retries=3):
26+
def build_tesseract(docker_wrapper, sourcedir, image_name, tag=None, build_retries=3):
3027
cli_runner = CliRunner()
3128

3229
build_args = [
@@ -58,4 +55,5 @@ def build_tesseract(sourcedir, image_name, tag=None, build_retries=3):
5855

5956
print_debug_info(result)
6057
assert result.exit_code == 0, result.exception
58+
assert docker_wrapper.get_image(image_name)
6159
return image_name

tests/endtoend_tests/test_endtoend.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
@pytest.fixture(scope="module")
1919
def built_image_name(docker_wrapper, shared_dummy_image_name, dummy_tesseract_location):
2020
"""Build the dummy Tesseract image for the tests."""
21-
image_name = build_tesseract(dummy_tesseract_location, shared_dummy_image_name)
21+
image_name = build_tesseract(
22+
docker_wrapper, dummy_tesseract_location, shared_dummy_image_name
23+
)
2224
assert image_exists(docker_wrapper, image_name)
2325
yield image_name
2426

@@ -42,7 +44,9 @@ def test_build_from_init_endtoend(
4244
assert yaml.safe_load(config_yaml)["name"] == dummy_image_name
4345

4446
img_tag = "foo" if tag else None
45-
image_name = build_tesseract(tmp_path, dummy_image_name, tag=img_tag)
47+
image_name = build_tesseract(
48+
docker_wrapper, tmp_path, dummy_image_name, tag=img_tag
49+
)
4650
assert image_exists(docker_wrapper, image_name)
4751

4852
# Test that the image can be run and that --help is forwarded correctly
@@ -131,7 +135,6 @@ def test_tesseract_run_stdout(built_image_name):
131135
def test_tesseract_serve_pipeline(docker_wrapper, built_image_name):
132136
cli_runner = CliRunner(mix_stderr=False)
133137
try:
134-
135138
# Serve
136139
run_res = cli_runner.invoke(
137140
app,
@@ -185,6 +188,7 @@ def test_tesseract_serve_pipeline(docker_wrapper, built_image_name):
185188
)
186189
assert run_res.exit_code == 0, run_res.stderr
187190

191+
188192
@pytest.mark.parametrize("tear_all", [True, False])
189193
def test_tesseract_teardown_multiple(built_image_name, tear_all):
190194
"""Teardown multiple projects."""
@@ -382,6 +386,7 @@ def test_tesseract_serve_with_volumes(built_image_name, tmp_path, docker_wrapper
382386
assert run_res.exit_code == 0, run_res.stderr
383387
assert run_res.stdout
384388
project_meta = json.loads(run_res.stdout)
389+
project_id = project_meta["project_id"]
385390
tesseract0_id = project_meta["containers"][0]["name"]
386391
tesseract0 = docker_wrapper.get_container(tesseract0_id)
387392
assert tesseract0 is not None
@@ -393,7 +398,9 @@ def test_tesseract_serve_with_volumes(built_image_name, tmp_path, docker_wrapper
393398
with open(tmpfile, "w") as hello:
394399
hello.write("world")
395400
hello.flush()
396-
exit_code, output = docker_wrapper.exec_run(tesseract0.name, ["cat", f"{dest}/hi"])
401+
exit_code, output = docker_wrapper.exec_run(
402+
tesseract0.name, ["cat", f"{dest}/hi"]
403+
)
397404
assert exit_code == 0
398405
assert output == "world"
399406

@@ -403,7 +410,9 @@ def test_tesseract_serve_with_volumes(built_image_name, tmp_path, docker_wrapper
403410
tesseract0.name, ["touch", f"{bar_file}"]
404411
)
405412
assert exit_code == 0
406-
exit_code, output = docker_wrapper.exec_run(tesseract1.name, ["cat", f"{bar_file}"])
413+
exit_code, output = docker_wrapper.exec_run(
414+
tesseract1.name, ["cat", f"{bar_file}"]
415+
)
407416
assert exit_code == 0
408417

409418
# The file should exist outside the container

tests/endtoend_tests/test_examples.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,7 @@ def test_unit_tesseract_endtoend(
566566

567567
# Stage 1: Build
568568
img_name = build_tesseract(
569+
docker_wrapper,
569570
unit_tesseract_path,
570571
dummy_image_name,
571572
tag="sometag",
@@ -651,6 +652,10 @@ def test_unit_tesseract_endtoend(
651652
assert_contains_array_allclose(output_json, array)
652653

653654
# Stage 3: Test HTTP server
655+
if unit_tesseract_config.volume_mounts is not None:
656+
# TODO: Mounts are not supported in HTTP mode yet, skip rest of the test for now
657+
return
658+
654659
try:
655660
result = cli_runner.invoke(
656661
app,

tests/endtoend_tests/test_tesseract_sdk.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
@pytest.fixture(scope="module")
1212
def built_image_name(docker_wrapper, shared_dummy_image_name, dummy_tesseract_location):
1313
"""Build the dummy Tesseract image for the tests."""
14-
image_name = build_tesseract(dummy_tesseract_location, shared_dummy_image_name)
14+
image_name = build_tesseract(
15+
docker_wrapper, dummy_tesseract_location, shared_dummy_image_name
16+
)
1517
assert image_exists(docker_wrapper, image_name)
1618
yield image_name
1719

0 commit comments

Comments
 (0)