Skip to content

Commit d71adfe

Browse files
authored
Support empty scies with no files or interpreters. (#182)
This started as a cleanup of `test_custom_jump_invalid` to use `itertools.product`, then expanded to speed up the test by avoiding a Python interpreter download. This latter improvement required handling the empty scie case.
1 parent 7fa0d84 commit d71adfe

File tree

4 files changed

+116
-52
lines changed

4 files changed

+116
-52
lines changed

CHANGES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Release Notes
22

3+
## 0.15.3
4+
5+
This release fixes handling of the case of a scie with no files. These would build previously, but
6+
then fail to run. This is a decidedly unlikely corner-case usage in the real world, but can be a
7+
valid use.
8+
39
## 0.15.2
410

511
This release fixes validation of custom `scie_jump`s with some portion of their digest filled out.

science/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33

44
from packaging.version import Version
55

6-
__version__ = "0.15.2"
6+
__version__ = "0.15.3"
77

88
VERSION = Version(__version__)

science/commands/lift.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33

44
from __future__ import annotations
55

6+
import atexit
67
import dataclasses
78
import json
9+
import shutil
10+
import zipfile
811
from collections import deque
912
from dataclasses import dataclass
1013
from pathlib import Path
@@ -14,6 +17,7 @@
1417
from science.build_info import BuildInfo
1518
from science.errors import InputError
1619
from science.fetcher import fetch_and_verify
20+
from science.fs import temporary_directory
1721
from science.model import (
1822
Application,
1923
Binding,
@@ -174,6 +178,15 @@ def maybe_invert_lazy(file: File) -> File:
174178
bindings.append(Fetch.create_binding(fetch_exe=ptex_file, argv1=argv1))
175179
bindings.extend(application.bindings)
176180

181+
if not requested_files:
182+
empty_scie_tote = File(name="empty-scie-tote")
183+
with temporary_directory(empty_scie_tote.name, delete=False) as td:
184+
empty_zip = td / "empty.zip"
185+
zipfile.ZipFile(empty_zip, "w").close()
186+
atexit.register(shutil.rmtree, td, ignore_errors=True)
187+
file_paths_by_id[empty_scie_tote.id] = empty_zip
188+
requested_files.append(empty_scie_tote)
189+
177190
files = list[File]()
178191
fetch_urls = dict[str, str]()
179192
for requested_file in requested_files:

tests/test_exe.py

Lines changed: 96 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,7 +1300,7 @@ def test_custom_jump_nominal(tmp_path: Path, science_exe: Path, version: str) ->
13001300
env={**os.environ, "SCIENCE_CACHE_DIR": str(cache_dir)},
13011301
check=True,
13021302
)
1303-
exe = dest / "exe"
1303+
exe = dest / Platform.current().binary_name("exe")
13041304

13051305
split_dir = tmp_path / "split"
13061306
subprocess.run(args=[exe, split_dir], env={**os.environ, "SCIE": "split"}, check=True)
@@ -1326,41 +1326,94 @@ def test_custom_jump_nominal(tmp_path: Path, science_exe: Path, version: str) ->
13261326
assert os.path.getsize(load_result.path) == manifest["scie"]["jump"]["size"]
13271327

13281328

1329-
GOOD_SIZE = 2223910
1330-
GOOD_FINGERPRINT = Fingerprint("e7ebc56578041eb5c92d819f948f9c8d5a671afaa337720d7d310f5311a2c5c3")
1329+
VERSION = "1.8.2"
1330+
DOWNLOAD_URL = (
1331+
f"https://github.com/a-scie/jump/releases/download/v{VERSION}/"
1332+
f"{CURRENT_PLATFORM.qualified_binary_name('scie-jump')}"
1333+
)
13311334

13321335
BAD_SIZE = -1
13331336
BAD_FINGERPRINT = Fingerprint("bad")
13341337

1335-
1336-
def digest_id(digest: Digest) -> str:
1337-
if digest.size and digest.fingerprint:
1338+
GOOD_SIZE = {
1339+
Platform.Linux_aarch64: 1899710,
1340+
Platform.Linux_armv7l: 1808106,
1341+
Platform.Linux_powerpc64le: 2236262,
1342+
Platform.Linux_riscv64: 1881230,
1343+
Platform.Linux_s390x: 2269030,
1344+
Platform.Linux_x86_64: 2223910,
1345+
Platform.Macos_aarch64: 1866014,
1346+
Platform.Macos_x86_64: 2031962,
1347+
Platform.Windows_aarch64: 1828366,
1348+
Platform.Windows_x86_64: 2203150,
1349+
}[CURRENT_PLATFORM]
1350+
1351+
GOOD_FINGERPRINT = {
1352+
Platform.Linux_aarch64: Fingerprint(
1353+
"74951ec0b3820664fbdd0fa15f5057932ac3d92d4d37d5f8aadfe70220c9e80f"
1354+
),
1355+
Platform.Linux_armv7l: Fingerprint(
1356+
"3e288aacccb0f8c63dcbdf585afb6e214e3832b4dda4a98da4ed2c6767578fb4"
1357+
),
1358+
Platform.Linux_powerpc64le: Fingerprint(
1359+
"d30e9a0bf779d382a8186c9a139dadf664cad5fc42598d5f5826b2ffb1162503"
1360+
),
1361+
Platform.Linux_riscv64: Fingerprint(
1362+
"4b466ad4171b090d7abacb6dba08e0fb76db43a67880700bcfe33e811ab3d6bc"
1363+
),
1364+
Platform.Linux_s390x: Fingerprint(
1365+
"c6b6915ea3d420a138e8cf334cd2b45032728ceb56d4304f0e5b4d5533adef20"
1366+
),
1367+
Platform.Linux_x86_64: Fingerprint(
1368+
"e7ebc56578041eb5c92d819f948f9c8d5a671afaa337720d7d310f5311a2c5c3"
1369+
),
1370+
Platform.Macos_aarch64: Fingerprint(
1371+
"e886e4e85c82d484e083d0426dcba236311d10a796b1387ee070aa82752efea1"
1372+
),
1373+
Platform.Macos_x86_64: Fingerprint(
1374+
"74f05a39eedb61615c68b07b551c7fce2a08d3196fb1c692a7163f6d3d59fd32"
1375+
),
1376+
Platform.Windows_aarch64: Fingerprint(
1377+
"14fdd2a13164e282f7670f30e0a9e7bccaf34affbcfeff9e86415eaa22c34ae3"
1378+
),
1379+
Platform.Windows_x86_64: Fingerprint(
1380+
"1e34ec9abc06ec75727f9cc89df3c23f783dda4e68a176481d447cace60bafb4"
1381+
),
1382+
}[CURRENT_PLATFORM]
1383+
1384+
1385+
def digest_id(size: int | None, fingerprint: str | None) -> str:
1386+
if size and fingerprint:
13381387
components = ["digest"]
1339-
if digest.size == BAD_SIZE:
1388+
if size == BAD_SIZE:
13401389
components.append("bad-size")
1341-
if digest.fingerprint == BAD_FINGERPRINT:
1390+
if fingerprint == BAD_FINGERPRINT:
13421391
components.append("bad-fingerprint")
13431392
return "-".join(components)
1393+
if size:
1394+
return "bad-size" if size == BAD_SIZE else "size"
1395+
if fingerprint:
1396+
return "bad-fingerprint" if fingerprint == BAD_FINGERPRINT else "fingerprint"
1397+
return "no-digest"
1398+
1399+
1400+
def as_toml_line(digest: Digest) -> str:
1401+
digest_fields = []
13441402
if digest.size:
1345-
return "bad-size" if digest.size == BAD_SIZE else "size"
1403+
digest_fields.append(f"size = {digest.size}")
13461404
if digest.fingerprint:
1347-
return "bad-fingerprint" if digest.fingerprint == BAD_FINGERPRINT else "fingerprint"
1348-
raise AssertionError(f"Expected digest to have at least one field set: {digest}")
1405+
digest_fields.append(f'fingerprint = "{digest.fingerprint}"')
1406+
if not digest_fields:
1407+
return ""
1408+
return f"digest = {{ {', '.join(digest_fields)} }}"
13491409

13501410

13511411
@pytest.mark.parametrize(
13521412
"digest",
13531413
[
1354-
pytest.param(digest, id=digest_id(digest))
1355-
for digest in (
1356-
Digest(size=GOOD_SIZE, fingerprint=GOOD_FINGERPRINT),
1357-
Digest(size=BAD_SIZE, fingerprint=BAD_FINGERPRINT),
1358-
Digest(size=BAD_SIZE, fingerprint=GOOD_FINGERPRINT),
1359-
Digest(size=GOOD_SIZE, fingerprint=BAD_FINGERPRINT),
1360-
Digest(size=GOOD_SIZE),
1361-
Digest(size=BAD_SIZE),
1362-
Digest(fingerprint=GOOD_FINGERPRINT),
1363-
Digest(fingerprint=BAD_FINGERPRINT),
1414+
pytest.param(Digest(size=size, fingerprint=fingerprint), id=digest_id(size, fingerprint))
1415+
for size, fingerprint in itertools.product(
1416+
(GOOD_SIZE, BAD_SIZE, None), (GOOD_FINGERPRINT, BAD_FINGERPRINT, None)
13641417
)
13651418
],
13661419
)
@@ -1369,64 +1422,56 @@ def test_custom_jump_invalid(tmp_path: Path, science_exe: Path, digest: Digest)
13691422
chroot = tmp_path / "chroot"
13701423
chroot.mkdir(parents=True, exist_ok=True)
13711424

1372-
digest_fields = []
1373-
if digest.size:
1374-
digest_fields.append(f"size = {digest.size}")
1375-
if digest.fingerprint:
1376-
digest_fields.append(f'fingerprint = "{digest.fingerprint}"')
1377-
assert digest_fields, f"Expected digest to have at least one field set; given: {digest}"
1378-
13791425
lift_manifest = chroot / "lift.toml"
13801426
lift_manifest.write_text(
13811427
dedent(
1382-
f"""\
1428+
"""\
13831429
[lift]
1384-
name = "exe"
1385-
platforms = [{{ platform = "linux-x86_64", libc = "gnu" }}]
1430+
name = "inspect"
13861431
13871432
[lift.scie_jump]
1388-
version = "1.8.2"
1389-
digest = {{ {", ".join(digest_fields)} }}
1390-
1391-
[[lift.interpreters]]
1392-
id = "cpython"
1393-
provider = "PythonBuildStandalone"
1394-
release = "20251120"
1395-
version = "3.14"
1396-
flavor = "install_only_stripped"
1433+
version = "{version}"
1434+
{digest}
13971435
13981436
[[lift.commands]]
1399-
exe = "#{{cpython:python}}"
1400-
args = ["-V"]
1437+
exe = "{{scie}}"
1438+
[lift.commands.env.replace]
1439+
SCIE = "inspect"
14011440
"""
1402-
)
1441+
).format(version=VERSION, digest=as_toml_line(digest))
14031442
)
14041443

14051444
cache_dir = tmp_path / "cache"
14061445
result = subprocess.run(
1407-
args=[str(science_exe), "lift", "build", "--dest-dir", str(dest), lift_manifest],
1446+
args=[str(science_exe), "lift", "build", "--dest-dir", str(dest)],
14081447
env={**os.environ, "SCIENCE_CACHE_DIR": str(cache_dir)},
1448+
cwd=chroot,
14091449
stderr=subprocess.PIPE,
14101450
text=True,
14111451
)
14121452
if digest.size == BAD_SIZE:
14131453
assert result.returncode != 0
14141454
assert (
1415-
"The content at "
1416-
"https://github.com/a-scie/jump/releases/download/v1.8.2/scie-jump-linux-x86_64 is "
1417-
f"expected to be {BAD_SIZE} bytes, but advertises a Content-Length of {GOOD_SIZE} "
1418-
"bytes."
1455+
f"The content at {DOWNLOAD_URL} is expected to be {BAD_SIZE} bytes, but advertises a "
1456+
f"Content-Length of {GOOD_SIZE} bytes."
14191457
) in result.stderr
14201458
elif digest.fingerprint == BAD_FINGERPRINT:
14211459
assert result.returncode != 0
14221460
assert (
1423-
"The download from "
1424-
"https://github.com/a-scie/jump/releases/download/v1.8.2/scie-jump-linux-x86_64 has "
1425-
"unexpected contents.\n"
1461+
f"The download from {DOWNLOAD_URL} has unexpected contents.\n"
14261462
"Expected sha256 digest:\n"
14271463
f" {BAD_FINGERPRINT}\n"
14281464
"Actual sha256 digest:\n"
14291465
f" {GOOD_FINGERPRINT}"
14301466
) in result.stderr
14311467
else:
14321468
assert 0 == result.returncode
1469+
manifest = json.loads(
1470+
subprocess.run(
1471+
args=[dest / Platform.current().binary_name("inspect")],
1472+
stdout=subprocess.PIPE,
1473+
check=True,
1474+
).stdout
1475+
)
1476+
assert VERSION == manifest["scie"]["jump"]["version"]
1477+
assert (digest.size or GOOD_SIZE) == manifest["scie"]["jump"]["size"]

0 commit comments

Comments
 (0)