tests/functional2: migrate tarball.sh

Change-Id: I8fb889a5723e34453597fbce2e027fb49a859319
This commit is contained in:
Commentator2.0
2025-12-19 20:44:25 +01:00
parent 605de55fed
commit c1debca36b
6 changed files with 227 additions and 73 deletions
-1
View File
@@ -72,7 +72,6 @@ functional_tests_scripts = [
'export-graph.sh',
'fetchGitRefs.sh',
'gc-runtime.sh',
'tarball.sh',
'fetchers.sh',
'fetchGit.sh',
'fetchGitLocked.sh',
-72
View File
@@ -1,72 +0,0 @@
source common.sh
clearStore
rm -rf $TEST_HOME
tarroot=$TEST_ROOT/tarball
rm -rf $tarroot
mkdir -p $tarroot
cp dependencies.nix $tarroot/default.nix
cp config.nix dependencies.builder*.sh $tarroot/
touch -d '@1000000000' $tarroot $tarroot/*
hash=$(nix hash path $tarroot)
test_tarball() {
local ext="$1"
local compressor="$2"
tarball=$TEST_ROOT/tarball.tar$ext
(cd $TEST_ROOT && GNUTAR_REPRODUCIBLE= tar --mtime=$tarroot/default.nix --owner=0 --group=0 --numeric-owner --sort=name -c -f - tarball) | $compressor > $tarball
nix-env -f file://$tarball -qa --out-path | grepQuiet dependencies
nix-build -o $TEST_ROOT/result file://$tarball
nix-build -o $TEST_ROOT/result '<foo>' -I foo=file://$tarball
nix-build -o $TEST_ROOT/result -E "import (fetchTarball \"file://$tarball\")"
# Do not re-fetch paths already present
nix-build -o $TEST_ROOT/result -E "import (fetchTarball { url = \"file:///does-not-exist/must-remain-unused/$tarball\"; sha256 = \"$hash\"; })"
# Regression test: Ensure tarballs can be unpacked when the
# temporary directory is a symbolic link.
(
export HOME=$(mktemp -d)
ln -sf "$TMPDIR" "$HOME/tmp"
nix-build -o "$TEST_ROOT/result" --temp-dir "$HOME/tmp" -E "import (fetchTarball \"file://$tarball\")"
)
nix-build -o $TEST_ROOT/result -E "import (fetchTree \"file://$tarball\")"
nix-build -o $TEST_ROOT/result -E "import (fetchTree { type = \"tarball\"; url = \"file://$tarball\"; })"
nix-build -o $TEST_ROOT/result -E "import (fetchTree { type = \"tarball\"; url = \"file://$tarball\"; narHash = \"$hash\"; })"
# Do not re-fetch paths already present
nix-build -o $TEST_ROOT/result -E "import (fetchTree { type = \"tarball\"; url = \"file:///does-not-exist/must-remain-unused/$tarball\"; narHash = \"$hash\"; })"
expectStderr 102 nix-build -o $TEST_ROOT/result -E "import (fetchTree { type = \"tarball\"; url = \"file://$tarball\"; narHash = \"sha256-xdKv2pq/IiwLSnBBJXW8hNowI4MrdZfW+SYqDQs7Tzc=\"; })" | grep 'NAR hash mismatch in input'
[[ $(nix eval --impure --expr "(fetchTree \"file://$tarball\").lastModified") = 1000000000 ]]
nix-instantiate --strict --eval -E "!((import (fetchTree { type = \"tarball\"; url = \"file://$tarball\"; narHash = \"$hash\"; })) ? submodules)" >&2
nix-instantiate --strict --eval -E "!((import (fetchTree { type = \"tarball\"; url = \"file://$tarball\"; narHash = \"$hash\"; })) ? submodules)" 2>&1 | grep 'true'
nix-instantiate --eval -E '1 + 2' -I fnord=file://no-such-tarball.tar$ext
nix-instantiate --eval -E 'with <fnord/xyzzy>; 1 + 2' -I fnord=file://no-such-tarball$ext
(! nix-instantiate --eval -E '<fnord/xyzzy> 1' -I fnord=file://no-such-tarball$ext)
nix-instantiate --eval -E '<fnord/config.nix>' -I fnord=file://no-such-tarball$ext -I fnord=.
# Ensure that the `name` attribute isnt accepted as that would mess
# with the content-addressing
(! nix-instantiate --eval -E "fetchTree { type = \"tarball\"; url = \"file://$tarball\"; narHash = \"$hash\"; name = \"foo\"; }")
}
test_tarball '' cat
test_tarball .xz xz
test_tarball .gz gzip
rm -rf $TEST_ROOT/tmp
mkdir -p $TEST_ROOT/tmp
(! TMPDIR=$TEST_ROOT/tmp XDG_RUNTIME_DIR=$TEST_ROOT/tmp nix-env -f file://$(pwd)/bad.tar.xz -qa --out-path)
(! [ -e $TEST_ROOT/tmp/bad ])
@@ -0,0 +1,224 @@
import os
import shutil
from pathlib import Path
import pytest
from _pytest.fixtures import FixtureRequest
from functional2.testlib.fixtures.command import Command
from functional2.testlib.fixtures.file_helper import with_files, Symlink, CopyFile
from functional2.testlib.fixtures.nix import Nix
from functional2.testlib.utils import get_global_asset_pack, get_global_asset
TAR_FILES = {
"tarball": get_global_asset_pack("dependencies")
| {"default.nix": get_global_asset("dependencies/dependencies.nix")}
}
def _set_mtime_of_folder(path: Path, mtime: int = 1000000000):
for file in [path] + list(path.iterdir()) if path.is_dir() else []:
os.utime(file, (mtime, mtime))
@pytest.fixture(params=[("", "cat"), (".xz", "xz"), (".gz", "gzip")])
def tarball(request: FixtureRequest, nix: Nix, files: Path) -> Path:
ext, compressor = request.param
# setup
_set_mtime_of_folder(files / "tarball")
env = nix.env
env["GNUTAR_REPRODUCIBLE"] = ""
tar_exe = shutil.which("tar")
env.path.add_program("tar")
tarball_name = f"tarball.tar{ext}"
tarball_path = files / tarball_name
# Create tarball
tarball_content = (
Command(
[
"tar",
f"--mtime={files / 'tarball' / 'default.nix'}",
"--owner=0",
"--group=0",
"--numeric-owner",
"--sort=name",
f"--to-command={compressor}",
"-c",
"-f",
"-",
"tarball",
],
env,
exe=tar_exe,
)
.run()
.ok()
.stdout
)
tarball_path.write_bytes(tarball_content)
res = nix.nix_env(["-f", f"file://{tarball_path}", "-qa", "--out-path"], build=True).run().ok()
assert "dependencies" in res.stdout_plain
return tarball_path
@pytest.fixture
def tar_hash(files: Path, nix: Nix) -> str:
return nix.hash_path(files / "tarball")
@with_files(TAR_FILES)
@pytest.mark.parametrize(
"flags",
[
["file://{tarball}"], # noqa: RUF027 # yes, true, but we don't have the variables here
["<foo>", "-I", "foo=file://{tarball}"], # noqa: RUF027
["-E", 'import (fetchTarball "file://{tarball}")'], # noqa: RUF027
# Do not re-fetch paths already present
[
"-E",
'import (fetchTarball {{ url = "file:///does-not-exist/must-remain-unused/{tarball}"; sha256 = "{tar_hash}"; }})', # noqa: RUF027
],
],
)
def test_fetch_tarball(nix: Nix, tarball: Path, tar_hash: str, flags: list[str]):
# HACK(Commentator2.0): if we don't create a copy, we'd try to format it twice, as it is the same list used in other test calls
flags = flags[:]
flags[-1] = flags[-1].format(tarball=tarball, tar_hash=tar_hash)
nix.nix_build(["-o", "result", *flags]).run().ok()
@with_files(TAR_FILES | {"actual-tmp-dir": {}, "tmp-dir": Symlink("./actual-tmp-dir")})
def test_tarball_symlink_extraction(nix: Nix, files: Path, tarball: Path):
nix.env.dirs.tmpdir = files / "tmp-dir"
nix.nix_build(
["-o", "result", "-E", f'import (fetchTarball "file://{files / tarball.name}")']
).run().ok()
real_tmp_dir = nix.env.dirs.test_root / "tmp"
real_tmp_dir.mkdir(exist_ok=True)
nix.env.dirs.tmpdir = real_tmp_dir
nix.nix_build(
[
"-o",
"result",
"--temp-dir",
f"{files}/tmp-dir",
"-E",
f'import (fetchTarball "file://{files / tarball.name}")',
]
).run().ok()
@with_files(TAR_FILES)
@pytest.mark.parametrize(
"expr",
[
'import (fetchTree "file://{tarball}")', # noqa: RUF027 # yes, true, but we don't have the variables here
'import (fetchTree {{ type = "tarball"; url = "file://{tarball}"; }})', # noqa: RUF027
'import (fetchTree {{ type = "tarball"; url = "file://{tarball}"; narHash = "{tar_hash}"; }})', # noqa: RUF027
],
)
def test_fetch_tree(nix: Nix, tarball: Path, tar_hash: str, expr: str):
expr = expr.format(tarball=tarball, tar_hash=tar_hash)
nix.nix_build(["-o", "result", "-E", expr], flake=True).run().ok()
@with_files(TAR_FILES)
def test_fetch_tree_hash_mismatch(nix: Nix, tarball: Path):
res = (
nix.nix_build(
[
"-o",
"result",
"-E",
f'import (fetchTree {{ type = "tarball"; url = "file://{tarball}"; narHash = "sha256-xdKv2pq/IiwLSnBBJXW8hNowI4MrdZfW+SYqDQs7Tzc="; }})',
],
flake=True,
)
.run()
.expect(102)
)
assert "NAR hash mismatch in input" in res.stderr_plain
@with_files(TAR_FILES)
def test_last_modified(nix: Nix, tarball: Path):
res = (
nix.nix(
["eval", "--impure", "--expr", f'(fetchTree "file://{tarball}").lastModified'],
flake=True,
build=True,
)
.run()
.ok()
)
assert res.stdout_plain == "1000000000"
@with_files({"config.nix": get_global_asset("config.nix")})
@pytest.mark.parametrize(
("flags", "exit_code"),
[
(["1 + 2"], 0),
(["with <fnord/xyzzy>; 1 + 2"], 0),
(["<fnord/config.nix>", "-I", "fnord=."], 0),
(["<fnord/xyzzy> 1"], 1),
],
)
def test_no_accessing_tar(nix: Nix, flags: list[str], exit_code: int):
nix.nix_instantiate(
["--eval", "-I", "fnord=file://no-such-tarball.tar.gz", "-E", *flags]
).run().expect(exit_code)
@with_files(TAR_FILES)
def test_no_submodules(nix: Nix, tarball: Path, tar_hash: str):
res = (
nix.nix_instantiate(
[
"--strict",
"--eval",
"-E",
f'!((fetchTree {{ type = "tarball"; url = "file://{tarball}"; narHash = "{tar_hash}"; }})) ? submodules',
],
flake=True,
build=True,
)
.run()
.ok()
)
assert res.stdout_plain == "true"
@with_files(TAR_FILES)
def test_no_accessing_name(nix: Nix, tarball: Path, tar_hash: str):
"""
Ensure that the `name` attribute isn't accepted as that would mess with the content-addressing
"""
res = (
nix.nix_instantiate(
[
"--eval",
"-E",
f'fetchTree {{ type = "tarball"; url = "file://{tarball}"; narHash = "{tar_hash}"; name = "foo"; }}',
],
flake=True,
)
.run()
.expect(1)
)
assert "error: attribute 'name' isnt supported in call" in res.stderr_plain # noqa: RUF001 # for some reason, this error message wants to feel special
@with_files({"bad.tar.xz": CopyFile("assets/test_tarball/bad.tar.xz")})
def test_nix_env_bad_tarball(nix: Nix, files: Path):
res = nix.nix_env(["-f", f"file://{files / 'bad.tar.xz'}", "-qa", "--out-path"]).run().expect(1)
assert "error: failed to extract archive (Path contains '..')" in res.stderr_plain
assert not (nix.env.dirs.tmpdir / "bad").exists()
@@ -139,6 +139,7 @@ class _Dirs:
nix_store_dir: Path | None
cache_dir: Path | None
xdg_cache_home: Path | None
tmpdir: Path | None
"""used for nar caching"""
def get_env_keys(self) -> set[str]:
@@ -176,6 +177,7 @@ class ManagedEnv:
nix_store_dir=self._get_dir("nix/store"),
cache_dir=self._get_dir("binary-cache"),
xdg_cache_home=self._get_dir("test-home/.cache"),
tmpdir=self._get_dir("tmp"),
)
self.path.prepend(self.dirs.nix_bin_dir)
self.init_defaults(global_path)
@@ -128,6 +128,7 @@ def test_env_to_env(tmp_path: Path):
"XDG_CACHE_HOME",
"PATH",
"BUILD_TEST_SHELL",
"TMPDIR",
} | ({"_NIX_TEST_NO_SANDBOX"} if sys.platform == "darwin" else set())