Skip to content

Commit c51d312

Browse files
test: improve test and linting
1 parent dba9616 commit c51d312

File tree

3 files changed

+142
-12
lines changed

3 files changed

+142
-12
lines changed

cwltool/singularity.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@
66
import os.path
77
import re
88
import shutil
9-
import subprocess
109
import sys
1110
from collections.abc import Callable, MutableMapping
12-
from subprocess import DEVNULL, check_call, check_output, run # nosec
11+
from subprocess import check_call, check_output, run # nosec
1312
from typing import cast
1413

1514
from schema_salad.sourceline import SourceLine
@@ -146,6 +145,7 @@ def _normalize_sif_id(string: str) -> str:
146145
string += "_latest"
147146
return string.replace("/", "_") + ".sif"
148147

148+
149149
def _inspect_singularity_image(path: str) -> bool:
150150
"""Inspect singularity image to be sure it is not an empty directory."""
151151
cmd = [
@@ -158,16 +158,17 @@ def _inspect_singularity_image(path: str) -> bool:
158158
result = run(cmd, capture_output=True, text=True)
159159
except Exception:
160160
return False
161-
161+
162162
if result.returncode == 0:
163163
try:
164164
output = json.loads(result.stdout)
165165
except json.JSONDecodeError:
166166
return False
167-
if output.get('data', {}).get('attributes', {}):
167+
if output.get("data", {}).get("attributes", {}):
168168
return True
169169
return False
170170

171+
171172
class SingularityCommandLineJob(ContainerCommandLineJob):
172173
def __init__(
173174
self,
@@ -253,7 +254,9 @@ def get_image(
253254
found = True
254255
elif "dockerImageId" not in dockerRequirement and "dockerPull" in dockerRequirement:
255256
# looking for local singularity sandbox image and handle it as a local image
256-
if os.path.isdir(dockerRequirement["dockerPull"]) and _inspect_singularity_image(dockerRequirement["dockerPull"]):
257+
if os.path.isdir(dockerRequirement["dockerPull"]) and _inspect_singularity_image(
258+
dockerRequirement["dockerPull"]
259+
):
257260
dockerRequirement["dockerImageId"] = dockerRequirement["dockerPull"]
258261
_logger.info(
259262
"Using local Singularity sandbox image found in %s",
@@ -280,7 +283,9 @@ def get_image(
280283
if is_version_3_or_newer():
281284
candidates.append(_normalize_sif_id(dockerRequirement["dockerImageId"]))
282285
# handling local singularity sandbox image
283-
elif os.path.isdir(dockerRequirement["dockerImageId"]) and _inspect_singularity_image(dockerRequirement["dockerImageId"]):
286+
elif os.path.isdir(dockerRequirement["dockerImageId"]) and _inspect_singularity_image(
287+
dockerRequirement["dockerImageId"]
288+
):
284289
_logger.info(
285290
"Using local Singularity sandbox image found in %s",
286291
dockerRequirement["dockerImageId"],

tests/test_singularity.py

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
"""Tests to find local Singularity image."""
22

33
import shutil
4+
import subprocess
45
from pathlib import Path
56

67
import pytest
78

89
from cwltool.main import main
10+
from cwltool.singularity import _inspect_singularity_image
911

1012
from .util import (
1113
get_data,
@@ -160,22 +162,23 @@ def test_singularity3_docker_image_id_in_tool(tmp_path: Path) -> None:
160162
)
161163
assert result_code1 == 0
162164

165+
163166
@needs_singularity
164167
def test_singularity_local_sandbox_image(tmp_path: Path):
165-
import subprocess
166168
workdir = tmp_path / "working_dir"
167169
workdir.mkdir()
168170
with working_directory(workdir):
169171
# build a sandbox image
170-
container_path = Path(f"{workdir}/container_repo/")
172+
container_path = workdir / "container_repo"
171173
container_path.mkdir()
172174
cmd = [
173-
"apptainer",
175+
"singularity",
174176
"build",
175177
"--sandbox",
176-
container_path / "alpine",
177-
"docker://alpine:latest"
178+
str(container_path / "alpine"),
179+
"docker://alpine:latest",
178180
]
181+
179182
build = subprocess.run(cmd, capture_output=True, text=True)
180183
if build.returncode == 0:
181184
result_code, stdout, stderr = get_main_output(
@@ -189,4 +192,39 @@ def test_singularity_local_sandbox_image(tmp_path: Path):
189192
)
190193
assert result_code == 0
191194
else:
192-
pytest.skip(f"Failed to build the Singularity image: {build.stderr}")
195+
pytest.skip(f"Failed to build the singularity image: {build.stderr}")
196+
197+
198+
@needs_singularity
199+
def test_singularity_inspect_image(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
200+
workdir = tmp_path / "working_dir"
201+
workdir.mkdir()
202+
repo_path = workdir / "container_repo"
203+
image_path = repo_path / "alpine"
204+
205+
# test path does not exists
206+
res_inspect = _inspect_singularity_image(str(image_path))
207+
assert res_inspect is False
208+
209+
# test image exists
210+
repo_path.mkdir()
211+
cmd = [
212+
"singularity",
213+
"build",
214+
"--sandbox",
215+
str(image_path),
216+
"docker://alpine:latest",
217+
]
218+
build = subprocess.run(cmd, capture_output=True, text=True)
219+
if build.returncode == 0:
220+
# Verify the path is a container image
221+
res_inspect = _inspect_singularity_image(image_path)
222+
assert res_inspect is True
223+
224+
# test wrong subprocess call
225+
def mock_failed_subprocess(*args, **kwargs):
226+
raise subprocess.CalledProcessError(returncode=1, cmd=args[0])
227+
228+
monkeypatch.setattr("cwltool.singularity.run", mock_failed_subprocess)
229+
res_inspect = _inspect_singularity_image(str(image_path))
230+
assert res_inspect is False

tests/test_tmpdir.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,93 @@ def test_dockerfile_singularity_build(monkeypatch: pytest.MonkeyPatch, tmp_path:
285285
shutil.rmtree(subdir)
286286

287287

288+
@needs_singularity
289+
def test_singularity_get_image_from_sandbox(monkeypatch: pytest.MonkeyPatch, tmp_path: Path):
290+
"""Test that SingularityCommandLineJob.get_image correctly handle sandbox image."""
291+
292+
(tmp_path / "out").mkdir(exist_ok=True)
293+
tmp_outdir_prefix = tmp_path / "out"
294+
(tmp_path / "3").mkdir(exist_ok=True)
295+
tmpdir_prefix = str(tmp_path / "tmp")
296+
runtime_context = RuntimeContext(
297+
{"tmpdir_prefix": tmpdir_prefix, "user_space_docker_cmd": None}
298+
)
299+
builder = Builder(
300+
{},
301+
[],
302+
[],
303+
{},
304+
schema.Names(),
305+
[],
306+
[],
307+
{},
308+
None,
309+
None,
310+
StdFsAccess,
311+
StdFsAccess(""),
312+
None,
313+
0.1,
314+
True,
315+
False,
316+
False,
317+
"no_listing",
318+
runtime_context.get_outdir(),
319+
runtime_context.get_tmpdir(),
320+
runtime_context.get_stagedir(),
321+
INTERNAL_VERSION,
322+
"singularity",
323+
)
324+
325+
workdir = tmp_path / "working_dir"
326+
workdir.mkdir()
327+
repo_path = workdir / "container_repo"
328+
repo_path.mkdir()
329+
image_path = repo_path / "alpine"
330+
image_path.mkdir()
331+
332+
# directory exists but is not an image
333+
monkeypatch.setattr("cwltool.singularity._inspect_singularity_image", lambda *args, **kwargs: False)
334+
req = {"class": "DockerRequirement", "dockerPull": f"{image_path}"}
335+
res = SingularityCommandLineJob(
336+
builder, {}, CommandLineTool.make_path_mapper, [], [], ""
337+
).get_image(
338+
req,
339+
pull_image=False,
340+
tmp_outdir_prefix=str(tmp_outdir_prefix),
341+
force_pull=False,
342+
)
343+
assert req["dockerPull"].startswith("docker://")
344+
assert res is False
345+
346+
# directory exists and is an image:
347+
monkeypatch.setattr("cwltool.singularity._inspect_singularity_image", lambda *args, **kwargs: True)
348+
req = {"class": "DockerRequirement", "dockerPull": f"{image_path}"}
349+
res = SingularityCommandLineJob(
350+
builder, {}, CommandLineTool.make_path_mapper, [], [], ""
351+
).get_image(
352+
req,
353+
pull_image=False,
354+
tmp_outdir_prefix=str(tmp_outdir_prefix),
355+
force_pull=False,
356+
)
357+
assert req["dockerPull"] == str(image_path)
358+
assert req["dockerImageId"] == str(image_path)
359+
assert res
360+
361+
# test that dockerImageId is set and image exists:
362+
req = {"class": "DockerRequirement", "dockerImageId": f"{image_path}"}
363+
res = SingularityCommandLineJob(
364+
builder, {}, CommandLineTool.make_path_mapper, [], [], ""
365+
).get_image(
366+
req,
367+
pull_image=False,
368+
tmp_outdir_prefix=str(tmp_outdir_prefix),
369+
force_pull=False,
370+
)
371+
assert req["dockerImageId"] == str(image_path)
372+
assert res
373+
374+
288375
def test_docker_tmpdir_prefix(tmp_path: Path) -> None:
289376
"""Test that DockerCommandLineJob respects temp directory directives."""
290377
(tmp_path / "3").mkdir()

0 commit comments

Comments
 (0)