Skip to content

Commit 011c3dd

Browse files
committed
singularity/apptainer: simplify version checks & strengthen tests
Previously they were at risk of leaking the changes to other tests
1 parent a921f67 commit 011c3dd

File tree

3 files changed

+153
-139
lines changed

3 files changed

+153
-139
lines changed

cwltool/singularity.py

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from subprocess import check_call, check_output # nosec
1414
from typing import cast
1515

16+
from packaging.version import Version
1617
from schema_salad.sourceline import SourceLine
1718
from schema_salad.utils import json_dumps
1819
from spython.main import Client
@@ -33,7 +34,7 @@
3334
# This is a list containing major and minor versions as integer.
3435
# (The number of minor version digits can vary among different distributions,
3536
# therefore we need a list here.)
36-
_SINGULARITY_VERSION: list[int] | None = None
37+
_SINGULARITY_VERSION: Version | None = None
3738
# Cached flavor / distribution of singularity
3839
# Can be singularity, singularity-ce or apptainer
3940
_SINGULARITY_FLAVOR: str = ""
@@ -43,14 +44,14 @@
4344
_IMAGES_LOCK = threading.Lock()
4445

4546

46-
def get_version() -> tuple[list[int], str]:
47+
def get_version() -> tuple[Version, str]:
4748
"""
4849
Parse the output of 'singularity --version' to determine the flavor and version.
4950
5051
Both pieces of information will be cached.
5152
5253
:returns: A tuple containing:
53-
- A tuple with major and minor version numbers as integer.
54+
- A parsed Version object.
5455
- A string with the name of the singularity flavor.
5556
"""
5657
global _SINGULARITY_VERSION # pylint: disable=global-statement
@@ -63,7 +64,7 @@ def get_version() -> tuple[list[int], str]:
6364
raise RuntimeError("Output of 'singularity --version' not recognized.")
6465

6566
version_string = version_match.group(2)
66-
_SINGULARITY_VERSION = [int(i) for i in version_string.split(".")]
67+
_SINGULARITY_VERSION = Version(version_string)
6768
_SINGULARITY_FLAVOR = version_match.group(1)
6869

6970
_logger.debug(f"Singularity version: {version_string}" " ({_SINGULARITY_FLAVOR}.")
@@ -77,18 +78,18 @@ def is_apptainer_1_or_newer() -> bool:
7778
Apptainer v1.0.0 is compatible with SingularityCE 3.9.5.
7879
See: https://github.com/apptainer/apptainer/releases
7980
"""
80-
v = get_version()
81-
if v[1] != "apptainer":
81+
version, flavor = get_version()
82+
if flavor != "apptainer":
8283
return False
83-
return v[0][0] >= 1
84+
return version >= Version("1")
8485

8586

8687
def is_apptainer_1_1_or_newer() -> bool:
8788
"""Check if apptainer singularity distribution is version 1.1 or higher."""
88-
v = get_version()
89-
if v[1] != "apptainer":
89+
version, flavor = get_version()
90+
if flavor != "apptainer":
9091
return False
91-
return v[0][0] >= 2 or (v[0][0] >= 1 and v[0][1] >= 1)
92+
return version >= Version("1.1")
9293

9394

9495
def is_version_2_6() -> bool:
@@ -97,48 +98,58 @@ def is_version_2_6() -> bool:
9798
9899
Also returns False if the flavor is not singularity or singularity-ce.
99100
"""
100-
v = get_version()
101-
if v[1] != "singularity" and v[1] != "singularity-ce":
101+
version, flavor = get_version()
102+
if flavor not in ("singularity", "singularity-ce"):
102103
return False
103-
return v[0][0] == 2 and v[0][1] == 6
104+
return version >= Version("2.6") and version < Version("2.7")
104105

105106

106107
def is_version_3_or_newer() -> bool:
107108
"""Check if this version is singularity version 3 or newer or equivalent."""
108109
if is_apptainer_1_or_newer():
109110
return True # this is equivalent to singularity-ce > 3.9.5
110-
v = get_version()
111-
return v[0][0] >= 3
111+
version, flavor = get_version()
112+
if flavor == "apptainer":
113+
return False
114+
return version >= Version("3")
112115

113116

114117
def is_version_3_1_or_newer() -> bool:
115118
"""Check if this version is singularity version 3.1 or newer or equivalent."""
116119
if is_apptainer_1_or_newer():
117120
return True # this is equivalent to singularity-ce > 3.9.5
118-
v = get_version()
119-
return v[0][0] >= 4 or (v[0][0] == 3 and v[0][1] >= 1)
121+
version, flavor = get_version()
122+
if flavor == "apptainer":
123+
return False
124+
return version >= Version("3.1")
120125

121126

122127
def is_version_3_4_or_newer() -> bool:
123128
"""Detect if Singularity v3.4+ is available."""
124129
if is_apptainer_1_or_newer():
125130
return True # this is equivalent to singularity-ce > 3.9.5
126-
v = get_version()
127-
return v[0][0] >= 4 or (v[0][0] == 3 and v[0][1] >= 4)
131+
version, flavor = get_version()
132+
if flavor == "apptainer":
133+
return False
134+
return version >= Version("3.4")
128135

129136

130137
def is_version_3_9_or_newer() -> bool:
131138
"""Detect if Singularity v3.9+ is available."""
132139
if is_apptainer_1_or_newer():
133140
return True # this is equivalent to singularity-ce > 3.9.5
134-
v = get_version()
135-
return v[0][0] >= 4 or (v[0][0] == 3 and v[0][1] >= 9)
141+
version, flavor = get_version()
142+
if flavor == "apptainer":
143+
return False
144+
return version >= Version("3.9")
136145

137146

138147
def is_version_3_10_or_newer() -> bool:
139148
"""Detect if Singularity v3.10+ is available."""
140-
v = get_version()
141-
return v[0][0] >= 4 or (v[0][0] == 3 and v[0][1] >= 10)
149+
version, flavor = get_version()
150+
if flavor not in ("singularity", "singularity-ce"):
151+
return False
152+
return version >= Version("3.10")
142153

143154

144155
def _normalize_image_id(string: str) -> str:

tests/test_environment.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ def assert_env_matches(
5656
if not env_accepts_null():
5757
e.pop("LC_CTYPE", None)
5858
e.pop("__CF_USER_TEXT_ENCODING", None)
59-
assert len(e) == 0, f"Unexpected environment variable(s): {', '.join(e.keys())}"
59+
assert (
60+
len(e) == 0
61+
), f"Unexpected environment variable(s): {', '.join(e.keys())} from env {env}."
6062

6163

6264
class CheckHolder(ABC):
@@ -133,7 +135,7 @@ def PWD(v: str, env: Env) -> bool:
133135
}
134136

135137
# Singularity variables appear to be in flux somewhat.
136-
version = Version(".".join(map(str, get_version()[0])))
138+
version, _ = get_version()
137139
assert version >= Version("3"), "Tests only work for Singularity 3+"
138140
sing_vars: EnvChecks = {
139141
"SINGULARITY_CONTAINER": None,
@@ -296,6 +298,7 @@ def test_preserve_all(
296298

297299
for vname, val in env.items():
298300
try:
301+
assert vname in checks, env
299302
assert_envvar_matches(checks[vname], vname, env)
300303
except KeyError:
301304
assert val == os.environ[vname]

0 commit comments

Comments
 (0)