|
21 | 21 |
|
22 | 22 | import pytest |
23 | 23 | import toml |
| 24 | +from packaging.version import Version |
24 | 25 | from pytest import TempPathFactory |
25 | 26 | from testing import issue |
26 | 27 |
|
27 | | -from science import __version__ |
| 28 | +from science import __version__, a_scie |
28 | 29 | from science.config import parse_config_file |
| 30 | +from science.hashing import Digest, Fingerprint |
| 31 | +from science.model import ScieJump |
29 | 32 | from science.os import IS_WINDOWS |
30 | 33 | from science.platform import CURRENT_PLATFORM, CURRENT_PLATFORM_SPEC, LibC, Os, Platform |
31 | 34 | from science.providers import PyPy |
@@ -1211,3 +1214,219 @@ def scie_select(python) -> dict[str, Any]: |
1211 | 1214 | "debug": 1, |
1212 | 1215 | "free-threaded": 0, |
1213 | 1216 | } == scie_select("python3.14d") |
| 1217 | + |
| 1218 | + |
| 1219 | +def test_load_dotenv(tmp_path: Path, science_exe: Path) -> None: |
| 1220 | + dest = tmp_path / "dest" |
| 1221 | + chroot = tmp_path / "chroot" |
| 1222 | + chroot.mkdir(parents=True, exist_ok=True) |
| 1223 | + |
| 1224 | + subprocess.run( |
| 1225 | + args=[str(science_exe), "lift", "build", "--dest-dir", str(dest), "-"], |
| 1226 | + input=dedent( |
| 1227 | + """\ |
| 1228 | + [lift] |
| 1229 | + name = "exe" |
| 1230 | + load_dotenv = true |
| 1231 | +
|
| 1232 | + [[lift.interpreters]] |
| 1233 | + id = "cpython" |
| 1234 | + provider = "PythonBuildStandalone" |
| 1235 | + release = "20251120" |
| 1236 | + version = "3.14" |
| 1237 | + flavor = "install_only_stripped" |
| 1238 | +
|
| 1239 | + [[lift.commands]] |
| 1240 | + exe = "#{cpython:python}" |
| 1241 | + args = ["-c", "import os; print(os.environ.get('SLARTIBARTFAST', '<unset>'))"] |
| 1242 | + """ |
| 1243 | + ), |
| 1244 | + cwd=chroot, |
| 1245 | + stdout=subprocess.PIPE, |
| 1246 | + text=True, |
| 1247 | + check=True, |
| 1248 | + ) |
| 1249 | + exe = dest / "exe" |
| 1250 | + assert ( |
| 1251 | + "<unset>" |
| 1252 | + == subprocess.run(args=[exe], stdout=subprocess.PIPE, text=True, check=True).stdout.strip() |
| 1253 | + ) |
| 1254 | + (dest / ".env").write_text("SLARTIBARTFAST=42") |
| 1255 | + assert ( |
| 1256 | + "<unset>" |
| 1257 | + == subprocess.run(args=[exe], stdout=subprocess.PIPE, text=True, check=True).stdout.strip() |
| 1258 | + ) |
| 1259 | + assert ( |
| 1260 | + "42" |
| 1261 | + == subprocess.run( |
| 1262 | + args=[exe], stdout=subprocess.PIPE, text=True, check=True, cwd=dest |
| 1263 | + ).stdout.strip() |
| 1264 | + ) |
| 1265 | + |
| 1266 | + |
| 1267 | +@pytest.mark.parametrize("version", ["1.8.0", "1.8.1", "1.8.2"]) |
| 1268 | +def test_custom_jump_nominal(tmp_path: Path, science_exe: Path, version: str) -> None: |
| 1269 | + dest = tmp_path / "dest" |
| 1270 | + chroot = tmp_path / "chroot" |
| 1271 | + chroot.mkdir(parents=True, exist_ok=True) |
| 1272 | + |
| 1273 | + lift_manifest = chroot / "lift.toml" |
| 1274 | + lift_manifest.write_text( |
| 1275 | + dedent( |
| 1276 | + f"""\ |
| 1277 | + [lift] |
| 1278 | + name = "exe" |
| 1279 | +
|
| 1280 | + [lift.scie_jump] |
| 1281 | + version = "{version}" |
| 1282 | +
|
| 1283 | + [[lift.interpreters]] |
| 1284 | + id = "cpython" |
| 1285 | + provider = "PythonBuildStandalone" |
| 1286 | + release = "20251120" |
| 1287 | + version = "3.14" |
| 1288 | + flavor = "install_only_stripped" |
| 1289 | +
|
| 1290 | + [[lift.commands]] |
| 1291 | + exe = "#{{cpython:python}}" |
| 1292 | + args = ["-V"] |
| 1293 | + """ |
| 1294 | + ) |
| 1295 | + ) |
| 1296 | + |
| 1297 | + cache_dir = tmp_path / "cache" |
| 1298 | + subprocess.run( |
| 1299 | + args=[str(science_exe), "lift", "build", "--dest-dir", str(dest), lift_manifest], |
| 1300 | + env={**os.environ, "SCIENCE_CACHE_DIR": str(cache_dir)}, |
| 1301 | + check=True, |
| 1302 | + ) |
| 1303 | + exe = dest / "exe" |
| 1304 | + |
| 1305 | + split_dir = tmp_path / "split" |
| 1306 | + subprocess.run(args=[exe, split_dir], env={**os.environ, "SCIE": "split"}, check=True) |
| 1307 | + assert ( |
| 1308 | + version |
| 1309 | + == subprocess.run( |
| 1310 | + args=[split_dir / "scie-jump", "-V"], stdout=subprocess.PIPE, text=True, check=True |
| 1311 | + ).stdout.strip() |
| 1312 | + ) |
| 1313 | + |
| 1314 | + result = subprocess.run( |
| 1315 | + args=[exe], |
| 1316 | + env={**os.environ, "SCIE": "inspect"}, |
| 1317 | + stdout=subprocess.PIPE, |
| 1318 | + text=True, |
| 1319 | + check=True, |
| 1320 | + ) |
| 1321 | + manifest = json.loads(result.stdout) |
| 1322 | + assert version == manifest["scie"]["jump"]["version"] |
| 1323 | + |
| 1324 | + load_result = a_scie.jump(ScieJump(version=Version(version))) |
| 1325 | + assert load_result.digest.size == manifest["scie"]["jump"]["size"] |
| 1326 | + assert os.path.getsize(load_result.path) == manifest["scie"]["jump"]["size"] |
| 1327 | + |
| 1328 | + |
| 1329 | +GOOD_SIZE = 2223910 |
| 1330 | +GOOD_FINGERPRINT = Fingerprint("e7ebc56578041eb5c92d819f948f9c8d5a671afaa337720d7d310f5311a2c5c3") |
| 1331 | + |
| 1332 | +BAD_SIZE = -1 |
| 1333 | +BAD_FINGERPRINT = Fingerprint("bad") |
| 1334 | + |
| 1335 | + |
| 1336 | +def digest_id(digest: Digest) -> str: |
| 1337 | + if digest.size and digest.fingerprint: |
| 1338 | + components = ["digest"] |
| 1339 | + if digest.size == BAD_SIZE: |
| 1340 | + components.append("bad-size") |
| 1341 | + if digest.fingerprint == BAD_FINGERPRINT: |
| 1342 | + components.append("bad-fingerprint") |
| 1343 | + return "-".join(components) |
| 1344 | + if digest.size: |
| 1345 | + return "bad-size" if digest.size == BAD_SIZE else "size" |
| 1346 | + 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}") |
| 1349 | + |
| 1350 | + |
| 1351 | +@pytest.mark.parametrize( |
| 1352 | + "digest", |
| 1353 | + [ |
| 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), |
| 1364 | + ) |
| 1365 | + ], |
| 1366 | +) |
| 1367 | +def test_custom_jump_invalid(tmp_path: Path, science_exe: Path, digest: Digest) -> None: |
| 1368 | + dest = tmp_path / "dest" |
| 1369 | + chroot = tmp_path / "chroot" |
| 1370 | + chroot.mkdir(parents=True, exist_ok=True) |
| 1371 | + |
| 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 | + |
| 1379 | + lift_manifest = chroot / "lift.toml" |
| 1380 | + lift_manifest.write_text( |
| 1381 | + dedent( |
| 1382 | + f"""\ |
| 1383 | + [lift] |
| 1384 | + name = "exe" |
| 1385 | + platforms = [{{ platform = "linux-x86_64", libc = "gnu" }}] |
| 1386 | +
|
| 1387 | + [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" |
| 1397 | +
|
| 1398 | + [[lift.commands]] |
| 1399 | + exe = "#{{cpython:python}}" |
| 1400 | + args = ["-V"] |
| 1401 | + """ |
| 1402 | + ) |
| 1403 | + ) |
| 1404 | + |
| 1405 | + cache_dir = tmp_path / "cache" |
| 1406 | + result = subprocess.run( |
| 1407 | + args=[str(science_exe), "lift", "build", "--dest-dir", str(dest), lift_manifest], |
| 1408 | + env={**os.environ, "SCIENCE_CACHE_DIR": str(cache_dir)}, |
| 1409 | + stderr=subprocess.PIPE, |
| 1410 | + text=True, |
| 1411 | + ) |
| 1412 | + if digest.size == BAD_SIZE: |
| 1413 | + assert result.returncode != 0 |
| 1414 | + 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." |
| 1419 | + ) in result.stderr |
| 1420 | + elif digest.fingerprint == BAD_FINGERPRINT: |
| 1421 | + assert result.returncode != 0 |
| 1422 | + 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" |
| 1426 | + "Expected sha256 digest:\n" |
| 1427 | + f" {BAD_FINGERPRINT}\n" |
| 1428 | + "Actual sha256 digest:\n" |
| 1429 | + f" {GOOD_FINGERPRINT}" |
| 1430 | + ) in result.stderr |
| 1431 | + else: |
| 1432 | + assert 0 == result.returncode |
0 commit comments