921 lines
38 KiB
Python
921 lines
38 KiB
Python
from fnmatch import fnmatch
|
|
from pathlib import Path
|
|
from textwrap import dedent
|
|
import pytest
|
|
import re
|
|
import tarfile
|
|
import json
|
|
import shutil
|
|
|
|
from testlib.fixtures.nix import Nix
|
|
from testlib.fixtures.env import ManagedEnv
|
|
from testlib.fixtures.git import Git
|
|
from testlib.utils import get_global_asset_pack, get_global_asset
|
|
from testlib.fixtures.file_helper import with_files, File, FileDeclaration, _init_files # noqa: PLC2701
|
|
from testlib.environ import environ
|
|
from .common import simple_flake, dependent_flake
|
|
|
|
system = environ.get("system")
|
|
|
|
|
|
def dotgit_other_branch() -> FileDeclaration:
|
|
result = get_global_asset_pack(".git")
|
|
result[".git"]["HEAD"] = File("ref: refs/heads/other-branch")
|
|
return result
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def setup_env(nix: Nix):
|
|
nix.settings.add_xp_feature("nix-command", "flakes")
|
|
|
|
|
|
flake1_files = {"flake1": get_global_asset_pack(".git") | simple_flake()}
|
|
flake2_files = {
|
|
"flake2": dotgit_other_branch()
|
|
| {
|
|
"flake.nix": File(f"""{{
|
|
description = "Fnord";
|
|
|
|
outputs = {{ self, flake1 }}: rec {{
|
|
packages.{system}.bar = flake1.packages.{system}.foo;
|
|
}};
|
|
}}""")
|
|
}
|
|
}
|
|
flake3_files = {
|
|
"flake3": get_global_asset_pack(".git")
|
|
| {
|
|
"flake.nix": File(f"""{{
|
|
description = "Fnord";
|
|
|
|
outputs = {{ self, flake2 }}: rec {{
|
|
packages.{system}.xyzzy = flake2.packages.{system}.bar;
|
|
|
|
checks.xyzzy = packages.{system}.xyzzy;
|
|
}};
|
|
}}"""),
|
|
"default.nix": File("{ x = 123; }"),
|
|
}
|
|
}
|
|
flake5_files = {"flake5": dependent_flake()}
|
|
flake7_files = {
|
|
"flake7": get_global_asset_pack(".git")
|
|
| {"flake.nix": File("{ outputs = { self }: { expr = 123; }; }")}
|
|
}
|
|
nonflake_files = {"nonFlake": get_global_asset_pack(".git") | {"README.md": File("FNORD")}}
|
|
|
|
|
|
def _make_flake_repo(
|
|
name: str, files: FileDeclaration, git: Git, env: ManagedEnv, request: pytest.FixtureRequest
|
|
) -> Path:
|
|
_init_files(files, env.dirs.test_root, request.path.parent, env)
|
|
repo = env.dirs.test_root / name
|
|
git(repo, "add", ".")
|
|
git(repo, "commit", "-m", "Initial")
|
|
return repo
|
|
|
|
|
|
@pytest.fixture
|
|
def flake1(git: Git, env: ManagedEnv, request: pytest.FixtureRequest) -> Path:
|
|
return _make_flake_repo("flake1", flake1_files, git, env, request)
|
|
|
|
|
|
def _modify_flake1(flake1: Path, git: Git | None):
|
|
(flake1 / "foo").write_text("foo")
|
|
(flake1 / "flake.nix").write_text((flake1 / "flake.nix").read_text() + "# foo")
|
|
if git is not None:
|
|
git(flake1, "add", "foo")
|
|
git(flake1, "commit", "-a", "-m", "Foo")
|
|
|
|
|
|
@pytest.fixture
|
|
def flake2(git: Git, env: ManagedEnv, request: pytest.FixtureRequest) -> Path:
|
|
return _make_flake_repo("flake2", flake2_files, git, env, request)
|
|
|
|
|
|
@pytest.fixture
|
|
def flake3(git: Git, env: ManagedEnv, request: pytest.FixtureRequest) -> Path:
|
|
return _make_flake_repo("flake3", flake3_files, git, env, request)
|
|
|
|
|
|
@pytest.fixture
|
|
def flake3_locked(git: Git, env: ManagedEnv, nix: Nix, flake3: Path, nonflake: Path) -> Path: # noqa: ARG001
|
|
nix.nix(["flake", "lock", flake3, "--commit-lock-file"]).run().ok()
|
|
return flake3
|
|
|
|
|
|
@pytest.fixture
|
|
def flake3_medium(git: Git, env: ManagedEnv, nix: Nix, flake3: Path, nonflake: Path) -> Path: # noqa: ARG001
|
|
(flake3 / "flake.nix").write_text(f"""{{
|
|
inputs = {{
|
|
nonFlake = {{
|
|
url = "{env.dirs.test_root}/nonFlake";
|
|
flake = false;
|
|
}};
|
|
}};
|
|
|
|
description = "Fnord";
|
|
|
|
outputs = {{ self, flake1, flake2, nonFlake }}: rec {{
|
|
packages.{system}.sth = flake1.packages.{system}.foo;
|
|
packages.{system}.fnord =
|
|
with import ./config.nix;
|
|
mkDerivation {{
|
|
inherit system;
|
|
name = "fnord";
|
|
buildCommand = ''
|
|
cat ${{nonFlake}}/README.md > $out
|
|
'';
|
|
}};
|
|
}};
|
|
}}""")
|
|
nix.nix(["flake", "lock", flake3]).run().ok()
|
|
git(flake3, "add", "flake.nix", "flake.lock")
|
|
git(flake3, "commit", "-m", "medium config")
|
|
return flake3
|
|
|
|
|
|
@pytest.fixture
|
|
def flake5(env: ManagedEnv, request: pytest.FixtureRequest) -> Path:
|
|
_init_files(flake5_files, env.dirs.test_root, request.path.parent, env)
|
|
return env.dirs.test_root / "flake5"
|
|
|
|
|
|
@pytest.fixture
|
|
def flake5_locked_tarball(env: ManagedEnv, flake5: Path, nix: Nix) -> Path:
|
|
nix.nix(["flake", "lock", flake5]).run().ok()
|
|
path = env.dirs.test_root / "flake5.tar.gz"
|
|
with tarfile.open(path, "w:gz") as tar:
|
|
tar.add(flake5, "flake5")
|
|
return path
|
|
|
|
|
|
@pytest.fixture
|
|
def flake7(git: Git, env: ManagedEnv, request: pytest.FixtureRequest) -> Path:
|
|
return _make_flake_repo("flake7", flake7_files, git, env, request)
|
|
|
|
|
|
@pytest.fixture
|
|
def nonflake(git: Git, env: ManagedEnv, request: pytest.FixtureRequest) -> Path:
|
|
_init_files(nonflake_files, env.dirs.test_root, request.path.parent, env)
|
|
repo = env.dirs.test_root / "nonFlake"
|
|
git(repo, "add", ".")
|
|
git(repo, "commit", "-m", "Initial")
|
|
return repo
|
|
|
|
|
|
@pytest.fixture
|
|
def registry(nix: Nix, flake1: Path, flake2: Path, flake3: Path) -> Path:
|
|
registry = nix.env.dirs.test_root / "registry.json"
|
|
nix.settings.flake_registry = str(registry)
|
|
|
|
# Construct a custom registry, additionally test the --registry flag
|
|
nix.nix(
|
|
["registry", "add", "--registry", registry, "flake1", f"git+file://{flake1}"]
|
|
).run().ok()
|
|
nix.nix(
|
|
["registry", "add", "--registry", registry, "flake2", f"git+file://{flake2}"]
|
|
).run().ok()
|
|
nix.nix(
|
|
["registry", "add", "--registry", registry, "flake3", f"git+file://{flake3}"]
|
|
).run().ok()
|
|
nix.nix(["registry", "add", "--registry", registry, "flake4", "flake3"]).run().ok()
|
|
nix.nix(["registry", "add", "--registry", registry, "nixpkgs", "flake1"]).run().ok()
|
|
|
|
return registry
|
|
|
|
|
|
@pytest.mark.usefixtures("registry")
|
|
class TestAttrMatch:
|
|
def test_fuzzy_plain(self, nix: Nix):
|
|
logs = nix.nix(["eval", "flake1#ERROR"]).run().expect(1).stderr_s
|
|
assert re.search(r"error:.*does not provide attribute.*or 'ERROR'$", logs)
|
|
|
|
def test_exact_plain(self, nix: Nix):
|
|
logs = nix.nix(["eval", "flake1#.ERROR"]).run().expect(1).stderr_s
|
|
assert re.search(r"error:.*does not provide attribute 'ERROR'$", logs)
|
|
|
|
def test_fuzzy_branch(self, nix: Nix):
|
|
path = nix.nix(["eval", "flake1/main#foo"]).run().ok().stdout_s
|
|
assert "-simple.drv" in path
|
|
|
|
class TestRegistryBranch:
|
|
@pytest.fixture(autouse=True)
|
|
def create_other_branch(self, flake1: Path, git: Git):
|
|
git(flake1, "checkout", "-b", "other")
|
|
(flake1 / "flake.nix").write_text(
|
|
(flake1 / "flake.nix").read_text().replace("foo", "bar")
|
|
)
|
|
git(flake1, "commit", "-a", "-mrename foo -> bar")
|
|
git(flake1, "checkout", "main")
|
|
|
|
def test_fuzzy_main_branch(self, nix: Nix):
|
|
assert (
|
|
"flake 'flake:flake1/main' does not provide attribute"
|
|
in nix.nix(["eval", "flake1/main#bar"]).run().expect(1).stderr_s
|
|
)
|
|
path = nix.nix(["eval", "flake1/main#foo"]).run().ok().stdout_s
|
|
assert "-simple.drv" in path
|
|
|
|
def test_fuzzy_other_branch(self, nix: Nix):
|
|
assert (
|
|
"flake 'flake:flake1/other' does not provide attribute"
|
|
in nix.nix(["eval", "flake1/other#foo"]).run().expect(1).stderr_s
|
|
)
|
|
path = nix.nix(["eval", "flake1/other#bar"]).run().ok().stdout_s
|
|
assert "-simple.drv" in path
|
|
|
|
|
|
def test_eval_system_takes_effect(nix: Nix, flake1: Path):
|
|
(flake1 / "flake.nix").write_text(
|
|
(flake1 / "flake.nix").read_text().replace(system, "kitty-kitty")
|
|
)
|
|
nix.nix(["build", "--eval-system", "kitty-kitty", flake1]).run().ok()
|
|
|
|
|
|
class TestGetFlake:
|
|
def test_unlocked_pure_fails(self, nix: Nix, flake1: Path):
|
|
expr = f'(builtins.getFlake "{flake1}").packages.{system}.default'
|
|
result = nix.nix(["build", "--expr", expr]).run().expect(1)
|
|
assert "error: cannot call 'getFlake' on unlocked flake reference" in result.stderr_s
|
|
|
|
def test_unlocked_impure_works(self, nix: Nix, flake1: Path):
|
|
expr = f'(builtins.getFlake "{flake1}").packages.{system}.default'
|
|
nix.nix(["build", "--impure", "--expr", expr]).run().ok()
|
|
assert (Path(nix.env.dirs.home / "result/hello")).exists()
|
|
|
|
def test_locked_url_pure_works(self, nix: Nix, flake1: Path, git: Git):
|
|
flake1_original_commit = git(flake1, "rev-parse", "HEAD").stdout_s.strip()
|
|
expr = f'(builtins.getFlake "git+file://{flake1}?rev={flake1_original_commit}").packages.{system}.default'
|
|
nix.nix(["build", "--expr", expr]).run().ok()
|
|
assert (Path(nix.env.dirs.home / "result/hello")).exists()
|
|
|
|
def test_unlocked_dep_pure_fails(self, nix: Nix, flake2: Path):
|
|
# NOTE: this is the same test as test_unlocked_dep_pure_fails because flake2 is not locked,
|
|
# and flake2 *cannot* be locked because that would also lock flake1 in the flake2 lock file
|
|
nix.nix(["eval", "--expr", f'(builtins.getFlake "{flake2}")']).run().expect(1)
|
|
|
|
@pytest.mark.usefixtures("registry")
|
|
def test_unlocked_dep_impure_works(self, nix: Nix, flake2: Path):
|
|
# NOTE: see test above, same thing happens here
|
|
nix.nix(["eval", "--impure", "--expr", f'(builtins.getFlake "{flake2}")']).run().ok()
|
|
|
|
|
|
@pytest.mark.usefixtures("registry")
|
|
class TestRegistry:
|
|
def test_registry_list(self, nix: Nix):
|
|
result = nix.nix(["registry", "list"]).run().ok().stdout_s
|
|
assert len(result.splitlines()) == 5
|
|
assert re.search(r"^global", result, re.M)
|
|
assert not re.search(r"^user", result, re.M) # nothing in user registry
|
|
|
|
def test_registry_caching(self, nix: Nix, registry: Path):
|
|
result = (
|
|
nix.nix(["registry", "list", "--flake-registry", f"file://{registry}"])
|
|
.run()
|
|
.ok()
|
|
.stdout_s
|
|
)
|
|
assert "flake3" in result
|
|
registry.unlink()
|
|
nix.nix(["store", "gc"]).run().ok()
|
|
result = (
|
|
nix.nix(["registry", "list", "--flake-registry", f"file://{registry}"])
|
|
.run()
|
|
.ok()
|
|
.stdout_s
|
|
)
|
|
assert "flake3" in result
|
|
|
|
class TestCLI:
|
|
def test_add(self, nix: Nix):
|
|
nix.nix(["registry", "add", "flake1", "flake3"]).run().ok()
|
|
result = nix.nix(["registry", "list"]).run().ok().stdout_s
|
|
assert len(result.splitlines()) == 6
|
|
assert re.search(r"^global", result, re.M)
|
|
assert re.search(r"^user", result, re.M) # something in user registry now
|
|
|
|
def test_pin_rev(self, nix: Nix, flake1: Path, git: Git):
|
|
commit = git(flake1, "rev-parse", "HEAD").stdout_s.strip()
|
|
nix.nix(["registry", "pin", "flake1"]).run().ok()
|
|
result = nix.nix(["registry", "list"]).run().ok().stdout_s
|
|
assert f"flake1?ref=refs/heads/main&rev={commit}" in result
|
|
|
|
def test_pin_to_other(self, nix: Nix, flake3: Path, git: Git):
|
|
commit = git(flake3, "rev-parse", "HEAD").stdout_s.strip()
|
|
nix.nix(["registry", "pin", "flake1", "flake3"]).run().ok()
|
|
result = nix.nix(["registry", "list"]).run().ok().stdout_s
|
|
assert f"flake3?ref=refs/heads/main&rev={commit}" in result
|
|
|
|
def test_add_remove(self, nix: Nix):
|
|
nix.nix(["registry", "add", "flake1", "flake3"]).run().ok()
|
|
nix.nix(["registry", "remove", "flake1"]).run().ok()
|
|
result = nix.nix(["registry", "list"]).run().ok().stdout_s
|
|
assert len(result.splitlines()) == 5
|
|
assert re.search(r"^global", result, re.M)
|
|
assert not re.search(r"^user", result, re.M) # nothing in user registry
|
|
|
|
class TestShorthands:
|
|
"""
|
|
'nix registry add' should accept flake shorthands (with or without branch or rev)
|
|
in the from argument, but reject fully-qualified from-urls (direct or indirect).
|
|
"""
|
|
|
|
def test_plain(self, nix: Nix):
|
|
nix.nix(["registry", "add", "nixpkgz", "github:NixOS/nixpkgz"]).run().ok()
|
|
assert "user flake:nixpkgz" in nix.nix(["registry", "list"]).run().ok().stdout_s
|
|
nix.nix(["registry", "remove", "nixpkgz"]).run().ok()
|
|
assert (
|
|
"user flake:nixpkgz" not in nix.nix(["registry", "list"]).run().ok().stdout_s
|
|
)
|
|
|
|
def test_branch(self, nix: Nix):
|
|
nix.nix(["registry", "add", "nixpkgz/branch", "github:NixOS/nixpkgz"]).run().ok()
|
|
assert (
|
|
"user flake:nixpkgz/branch"
|
|
in nix.nix(["registry", "list"]).run().ok().stdout_s
|
|
)
|
|
nix.nix(["registry", "remove", "nixpkgz/branch"]).run().ok()
|
|
assert (
|
|
"user flake:nixpkgz/branch"
|
|
not in nix.nix(["registry", "list"]).run().ok().stdout_s
|
|
)
|
|
|
|
def test_branch_rev(self, nix: Nix):
|
|
ref = "nixpkgz/branch/1db42b7fe3878f3f5f7a4f2dc210772fd080e205"
|
|
nix.nix(["registry", "add", ref, "github:NixOS/nixpkgz"]).run().ok()
|
|
assert f"user flake:{ref}" in nix.nix(["registry", "list"]).run().ok().stdout_s
|
|
nix.nix(["registry", "remove", ref]).run().ok()
|
|
assert (
|
|
f"user flake:{ref}" not in nix.nix(["registry", "list"]).run().ok().stdout_s
|
|
)
|
|
|
|
def test_rejects_qualified(self, nix: Nix):
|
|
assert (
|
|
"error: 'from-url' argument must be a shorthand"
|
|
in nix.nix(["registry", "add", "flake:nixpkgz", "github:NixOS/nixpkgz"])
|
|
.run()
|
|
.expect(1)
|
|
.stderr_s
|
|
)
|
|
assert (
|
|
"error: 'from-url' argument must be a shorthand"
|
|
in nix.nix(["registry", "add", "github:NixOS/nixpkgz", "github:NixOS/nixpkgz"])
|
|
.run()
|
|
.expect(1)
|
|
.stderr_s
|
|
)
|
|
|
|
def test_disabled_global_registry(self, nix: Nix, flake1: Path, flake2: Path):
|
|
nix.nix(["registry", "add", "user-flake1", f"git+file://{flake1}"]).run().ok()
|
|
nix.nix(["registry", "add", "user-flake2", f"git+file://{flake2}"]).run().ok()
|
|
|
|
result = nix.nix(["--flake-registry", "", "registry", "list"]).run().ok().stdout_s
|
|
assert len(result.splitlines()) == 2
|
|
assert not re.search(r"^global", result, re.M) # nothing in global registry
|
|
assert re.search(r"^user", result, re.M)
|
|
|
|
|
|
@pytest.mark.usefixtures("registry")
|
|
class TestBuild:
|
|
def test_build_attr(self, nix: Nix):
|
|
nix.nix(["build", "flake1#foo"]).run().ok()
|
|
assert (nix.env.dirs.home / "result/hello").exists()
|
|
|
|
def test_build_without_attr_registry(self, nix: Nix):
|
|
nix.nix(["build", "flake1"]).run().ok()
|
|
assert (nix.env.dirs.home / "result/hello").exists()
|
|
|
|
@pytest.mark.parametrize("scheme", ["", "git+file://"])
|
|
def test_build_without_attr_url(self, nix: Nix, flake1: Path, scheme: str):
|
|
nix.nix(["build", f"{scheme}{flake1}"]).run().ok()
|
|
assert (nix.env.dirs.home / "result/hello").exists()
|
|
|
|
@pytest.mark.parametrize("arg", ["--no-registries", "--no-use-registries"])
|
|
def test_pure_build_unlocked_deps_failure(self, nix: Nix, arg: str):
|
|
result = nix.nix(["build", "flake2#bar", arg]).run().expect(1).stderr_s
|
|
assert (
|
|
"error: 'flake:flake2' is an indirect flake reference, but registry lookups are not allowed"
|
|
in result
|
|
)
|
|
|
|
def test_impure_build_unlocked_deps_fails(self, nix: Nix):
|
|
result = nix.nix(["build", "flake2#bar", "--impure"]).run().expect(1).stderr_s
|
|
assert "error: cannot write modified lock file" in result
|
|
|
|
def test_impure_build_unlocked_deps_succeeds_with_no_write(self, nix: Nix):
|
|
logs = (
|
|
nix.nix(["build", "flake2#bar", "--impure", "--no-write-lock-file"]).run().ok().stderr_s
|
|
)
|
|
assert re.search(r"building '.*-simple.drv'", logs)
|
|
|
|
def test_build_unlocked_fails_with_no_update(self, nix: Nix, flake2: Path):
|
|
logs = nix.nix(["build", f"{flake2}#bar", "--no-update-lock-file"]).run().expect(1).stderr_s
|
|
assert "requires lock file changes" in logs
|
|
|
|
def test_build_unlocked_succeeds_with_no_write(self, nix: Nix):
|
|
logs = nix.nix(["build", "flake2#bar", "--no-write-lock-file"]).run().ok().stderr_s
|
|
assert re.search(r"building '.*-simple.drv'", logs)
|
|
|
|
class TestLockedFlake2:
|
|
@pytest.fixture(autouse=True)
|
|
def lock_flake2(self, nix: Nix, flake2: Path, registry: Path): # noqa: ARG002
|
|
nix.nix(["flake", "lock", flake2, "--commit-lock-file"]).run().ok()
|
|
|
|
def test_setup_did_commit(self, flake2: Path, git: Git):
|
|
assert (flake2 / "flake.lock").exists()
|
|
assert not git(flake2, "diff", "HEAD").stdout_s
|
|
|
|
def test_rerunning_builds_does_not_change_lockfile(self, nix: Nix, flake2: Path, git: Git):
|
|
nix.nix(["build", f"{flake2}#bar", "--no-write-lock-file"]).run().ok()
|
|
assert not git(flake2, "diff", "HEAD").stdout_s
|
|
|
|
@pytest.mark.parametrize(
|
|
"args",
|
|
[
|
|
["--flake-registry", "file:///no-registry.json", "--refresh"],
|
|
["--no-registries", "--refresh"],
|
|
["--no-use-registries", "--refresh"],
|
|
[],
|
|
],
|
|
)
|
|
@pytest.mark.parametrize("scheme", ["", "git+file://"])
|
|
def test_locked_build_works(self, nix: Nix, flake2: Path, args: list[str], scheme: str):
|
|
# registry fetches are not logged anywhere!
|
|
nix.nix(["build", *args, f"{scheme}{flake2}#bar"]).run().ok()
|
|
|
|
def test_lock_idempotent(self, nix: Nix, flake2: Path, git: Git):
|
|
nix.nix(["flake", "lock", flake2]).run().ok()
|
|
assert not git(flake2, "diff", "HEAD").stdout_s
|
|
|
|
def test_indirect_dependencies(self, nix: Nix, flake3: Path):
|
|
logs = nix.nix(["build", f"{flake3}#xyzzy"]).run().ok().stderr_s
|
|
assert "Added input 'flake2'" in logs
|
|
assert "Added input 'flake2/flake1'" in logs
|
|
assert re.search(r"building '.*-simple.drv'", logs)
|
|
|
|
def test_bare_repo(self, nix: Nix, flake1: Path, git: Git):
|
|
git(None, "clone", "--bare", flake1, "bare")
|
|
logs = nix.nix(["build", f"git+file://{nix.env.dirs.home}/bare"]).run().ok().stderr_s
|
|
assert re.search(r"building '.*-simple.drv'", logs)
|
|
|
|
def test_tarball(self, nix: Nix, flake5_locked_tarball: Path):
|
|
logs = nix.nix(["build", f"file://{flake5_locked_tarball}"]).run().ok().stderr_s
|
|
assert "fetching tarball input" in logs
|
|
assert re.search(r"building '.*-simple.drv'", logs)
|
|
|
|
def test_tarball_with_sri(self, nix: Nix, flake5_locked_tarball: Path):
|
|
# lockfile contains absolute references to test data, hash can't be deterministic
|
|
url = (
|
|
nix.nix(["flake", "metadata", "--json", f"file://{flake5_locked_tarball}"])
|
|
.run()
|
|
.json()["url"]
|
|
)
|
|
assert "sha256-" in url
|
|
|
|
nix.clear_store()
|
|
|
|
logs = nix.nix(["build", url]).run().ok().stderr_s
|
|
assert "fetching tarball input" in logs
|
|
assert re.search(r"building '.*-simple.drv'", logs)
|
|
|
|
def test_tarball_bad_sri(self, nix: Nix, flake5_locked_tarball: Path):
|
|
url = f"file://{flake5_locked_tarball}?narHash=sha256-qQ2Zz4DNHViCUrp6gTS7EE4+RMqFQtUfWF2UNUtJKS0="
|
|
logs = nix.nix(["build", url]).run().expect(102).stderr_s
|
|
assert "NAR hash mismatch" in logs
|
|
|
|
class TestIncompleteLockfile:
|
|
@pytest.fixture(autouse=True)
|
|
def update_flake(self, nix: Nix, flake3: Path, git: Git, registry: Path): # noqa: ARG002
|
|
nix.nix(["flake", "lock", flake3]).run().ok()
|
|
git(flake3, "add", "flake.lock")
|
|
(flake3 / "flake.nix").write_text(f"""{{
|
|
outputs = {{ self, flake1, flake2 }}: rec {{
|
|
packages.{system}.xyzzy = flake2.packages.{system}.bar;
|
|
packages.{system}."sth sth" = flake1.packages.{system}.foo;
|
|
}};
|
|
}}""")
|
|
git(flake3, "add", "flake.nix")
|
|
git(flake3, "commit", "-m", "Update flake.nix")
|
|
|
|
@pytest.mark.parametrize("attr", ["sth sth", "sth%20sth"])
|
|
def test_build(self, nix: Nix, flake3: Path, git: Git, attr: str):
|
|
nix.nix(["build", f"{flake3}#{attr}"]).run().ok()
|
|
|
|
# check that the lockfile was changed
|
|
assert git(flake3, "diff", "HEAD").stdout_s
|
|
|
|
def test_deep_build(self, nix: Nix, flake3: Path):
|
|
"""test builds that need recursive flakeref resolution"""
|
|
# flake4 points to flake3 which thus needs locked
|
|
nix.nix(["flake", "lock", flake3]).run().ok()
|
|
nix.nix(["build", "flake4#xyzzy"]).run().ok()
|
|
|
|
|
|
@with_files({"config.nix": get_global_asset("config.nix")})
|
|
@pytest.mark.usefixtures("registry")
|
|
class TestWithNonFlake:
|
|
@pytest.fixture(autouse=True)
|
|
def add_nonflake(self, flake3: Path, nonflake: Path, git: Git, files: Path):
|
|
(flake3 / "flake.nix").write_text(f"""{{
|
|
inputs = {{
|
|
flake1 = {{}};
|
|
flake2 = {{}};
|
|
nonFlake = {{
|
|
url = "git+file://{nonflake}";
|
|
flake = false;
|
|
}};
|
|
nonFlakeFile = {{
|
|
url = "path://{nonflake}/README.md";
|
|
flake = false;
|
|
}};
|
|
nonFlakeFile2 = {{
|
|
url = "{nonflake}/README.md";
|
|
flake = false;
|
|
}};
|
|
}};
|
|
outputs = inputs: rec {{
|
|
packages.{system}.xyzzy = inputs.flake2.packages.{system}.bar;
|
|
packages.{system}.sth = inputs.flake1.packages.{system}.foo;
|
|
packages.{system}.fnord =
|
|
with import ./config.nix;
|
|
mkDerivation {{
|
|
inherit system;
|
|
name = "fnord";
|
|
dummy = builtins.readFile (builtins.path {{ name = "source"; path = ./.; filter = path: type: baseNameOf path == "config.nix"; }} + "/config.nix");
|
|
dummy2 = builtins.readFile (builtins.path {{ name = "source"; path = inputs.flake1; filter = path: type: baseNameOf path == "simple.nix"; }} + "/simple.nix");
|
|
buildCommand = ''
|
|
cat ${{inputs.nonFlake}}/README.md > $out
|
|
[[ $(cat ${{inputs.nonFlake}}/README.md) = $(cat ${{inputs.nonFlakeFile}}) ]]
|
|
[[ ${{inputs.nonFlakeFile}} = ${{inputs.nonFlakeFile2}} ]]
|
|
'';
|
|
}};
|
|
}};
|
|
}}""")
|
|
(flake3 / "config.nix").write_text((files / "config.nix").read_text())
|
|
git(flake3, "add", "flake.nix", "config.nix")
|
|
git(flake3, "commit", "-m", "Add nonFlakeInputs")
|
|
|
|
def test_flake_lock_updates(self, nix: Nix, flake3: Path, git: Git):
|
|
nix.nix(["build", f"{flake3}#sth", "--commit-lock-file"]).run().ok()
|
|
msg = git(flake3, "show", "-s", "--format=format:%B").stdout_s
|
|
assert fnmatch(
|
|
msg,
|
|
dedent("""\
|
|
flake.lock: Add
|
|
|
|
Flake lock file updates:
|
|
|
|
• Added input 'flake1':
|
|
'git+file://*/flake1[?]ref=refs/heads/main&rev=*' *
|
|
• Added input 'nonFlake':
|
|
'git+file://*/nonFlake[?]ref=refs/heads/main&rev=*' *
|
|
• Added input 'nonFlakeFile':
|
|
'path:*/nonFlake/README.md[?]lastModified=*&narHash=sha256-W9JwWKcSn1bi98dD6XPmDM3cDkXXPK%2B9tLkPhjxAHyg%3D' *
|
|
• Added input 'nonFlakeFile2':
|
|
'path:*/nonFlake/README.md[?]lastModified=*&narHash=sha256-W9JwWKcSn1bi98dD6XPmDM3cDkXXPK%2B9tLkPhjxAHyg%3D' *"""),
|
|
)
|
|
|
|
def test_build_nonflake(self, nix: Nix, flake3: Path):
|
|
nix.nix(["build", f"{flake3}#fnord"]).run().ok()
|
|
assert (nix.env.dirs.home / "result").read_text() == "FNORD"
|
|
|
|
class TestFetchIsLazy:
|
|
"""
|
|
Check whether flake input fetching is lazy: flake3#sth does not
|
|
depend on flake2, so this shouldn't fail.
|
|
"""
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def clear_state(
|
|
self,
|
|
nix: Nix,
|
|
registry: Path, # noqa: ARG002
|
|
flake2: Path, # noqa: ARG002
|
|
flake3_locked: Path, # noqa: ARG002
|
|
nonflake: Path, # noqa: ARG002
|
|
):
|
|
shutil.rmtree(nix.env.dirs.home / ".cache")
|
|
nix.clear_store()
|
|
|
|
def test_deps_not_fetched_unless_needed(self, nix: Nix, flake2: Path, nonflake: Path):
|
|
shutil.rmtree(flake2)
|
|
shutil.rmtree(nonflake)
|
|
|
|
nix.nix(["build", "flake3#sth"]).run().ok()
|
|
|
|
def test_dep_fetches_fail_builds(self, nix: Nix, flake2: Path, nonflake: Path):
|
|
shutil.rmtree(flake2)
|
|
shutil.rmtree(nonflake)
|
|
result = nix.nix(["build", "flake3#xyzzy"]).run().expect(1).stderr_s
|
|
assert fnmatch(result, "*while fetching the input '*/flake2*'*")
|
|
result = nix.nix(["build", "flake3#fnord"]).run().expect(1).stderr_s
|
|
assert fnmatch(result, "*while fetching the input '*/nonFlake*'*")
|
|
|
|
def test_deps_fetched_when_present(self, nix: Nix):
|
|
nix.nix(["build", "flake3#sth"]).run().ok()
|
|
nix.nix(["build", "flake3#xyzzy", "flake3#fnord"]).run().ok()
|
|
|
|
|
|
@pytest.mark.usefixtures("registry")
|
|
def test_update_with_override_flake(nix: Nix, flake3: Path, git: Git):
|
|
nix.nix(
|
|
["flake", "update", "--flake", flake3, "--override-input", "flake2", "nixpkgs"]
|
|
).run().ok()
|
|
assert git(flake3, "diff", "HEAD").stdout_s
|
|
|
|
|
|
def test_symlinks_in_flakes_are_not_flakes(nix: Nix, flake1: Path):
|
|
nix.nix(["build", "-o", flake1 / "result", f"git+file://{flake1}"]).run().ok()
|
|
nix.nix(["path-info", flake1 / "result"]).run().ok()
|
|
|
|
|
|
@pytest.mark.usefixtures("registry")
|
|
def test_flakes_gcroots(nix: Nix, flake1: Path, flake2: Path):
|
|
"""
|
|
Test whether flakes are registered as GC roots for offline use.
|
|
FIXME(ancient): use tarballs rather than git.
|
|
"""
|
|
|
|
nix.env["_NIX_FORCE_HTTP"] = "1"
|
|
shutil.rmtree(nix.env.dirs.home / ".cache")
|
|
nix.clear_store() # absolutely make sure the store is empty first
|
|
|
|
nix.nix(["flake", "lock", flake2, "--commit-lock-file"]).run().ok()
|
|
nix.nix(["build", f"git+file://{flake2}#bar"]).run().ok()
|
|
|
|
shutil.rmtree(flake1)
|
|
shutil.rmtree(flake2)
|
|
|
|
nix.nix(["store", "gc"]).run().ok()
|
|
|
|
nix.nix(["build", f"git+file://{flake2}#bar"]).run().ok()
|
|
nix.nix(["build", f"git+file://{flake2}#bar", "--refresh"]).run().ok()
|
|
|
|
|
|
@pytest.mark.usefixtures("registry")
|
|
def test_flake_clone(nix: Nix):
|
|
dest = nix.env.dirs.home / "flake1-v2"
|
|
nix.nix(["flake", "clone", "flake1", "--dest", dest]).run().ok()
|
|
assert (dest / "flake.nix").exists()
|
|
|
|
|
|
@pytest.mark.usefixtures("registry")
|
|
class TestLock:
|
|
def test_path_url(self, nix: Nix, flake5: Path):
|
|
logs = nix.nix(["flake", "lock", f"path://{flake5}"]).run().ok().stderr_s
|
|
assert "Added input 'flake1'" in logs
|
|
assert f"fetching path input 'path:{flake5}" in logs
|
|
|
|
def test_override_inputs(
|
|
self, nix: Nix, flake1: Path, flake3: Path, flake5_locked_tarball: Path, git: Git
|
|
):
|
|
# this is one big test due to necessary state changes, 🐻 with me
|
|
flake1_original_commit = git(flake1, "rev-parse", "HEAD").stdout_s.strip()
|
|
|
|
url = f"file://{flake5_locked_tarball}"
|
|
nix.nix(["flake", "lock", flake3, "--override-input", "flake2/flake1", url]).run().ok()
|
|
lock = json.loads((flake3 / "flake.lock").read_text())
|
|
assert lock["nodes"]["flake1"]["locked"]["url"] == url
|
|
|
|
nix.nix(["flake", "lock", flake3, "--override-input", "flake2/flake1", "flake1"]).run().ok()
|
|
lock = json.loads((flake3 / "flake.lock").read_text())
|
|
assert lock["nodes"]["flake1"]["locked"]["rev"] == flake1_original_commit
|
|
|
|
git(flake1, "checkout", "-b", "other")
|
|
_modify_flake1(flake1, git)
|
|
other_hash = git(flake1, "rev-parse", "HEAD").stdout_s.strip()
|
|
git(flake1, "checkout", "main")
|
|
|
|
ref = f"flake1/other/{other_hash}"
|
|
nix.nix(["flake", "lock", flake3, "--override-input", "flake2/flake1", ref]).run().ok()
|
|
lock = json.loads((flake3 / "flake.lock").read_text())
|
|
assert lock["nodes"]["flake1"]["locked"]["rev"] == other_hash
|
|
|
|
nix.nix(["flake", "lock", flake3]).run().ok()
|
|
lock = json.loads((flake3 / "flake.lock").read_text())
|
|
assert lock["nodes"]["flake1"]["locked"]["rev"] == other_hash
|
|
|
|
def test_update_individual_input(self, nix: Nix, flake1: Path, flake3_medium: Path, git: Git):
|
|
flake1_original_commit = git(flake1, "rev-parse", "HEAD").stdout_s.strip()
|
|
_modify_flake1(flake1, git)
|
|
flake1_modified_commit = git(flake1, "rev-parse", "HEAD").stdout_s.strip()
|
|
|
|
nix.nix(["flake", "update", "flake2/flake1", "--flake", flake3_medium]).run().ok()
|
|
lock = json.loads((flake3_medium / "flake.lock").read_text())
|
|
assert lock["nodes"]["flake1"]["locked"]["rev"] == flake1_original_commit
|
|
assert lock["nodes"]["flake1_2"]["locked"]["rev"] == flake1_modified_commit
|
|
|
|
def test_update_multiple_inputs(self, nix: Nix, flake1: Path, flake3_medium: Path, git: Git):
|
|
_modify_flake1(flake1, git)
|
|
flake1_modified_commit = git(flake1, "rev-parse", "HEAD").stdout_s.strip()
|
|
|
|
nix.nix(["flake", "update", "flake1", "flake2/flake1", "--flake", flake3_medium]).run().ok()
|
|
lock = json.loads((flake3_medium / "flake.lock").read_text())
|
|
assert lock["nodes"]["flake1"]["locked"]["rev"] == flake1_modified_commit
|
|
assert lock["nodes"]["flake1_2"]["locked"]["rev"] == flake1_modified_commit
|
|
|
|
class TestFollows:
|
|
def test_simple_explicit(self, nix: Nix, flake3: Path):
|
|
(flake3 / "flake.nix").write_text("""{
|
|
inputs.foo = {
|
|
type = "indirect";
|
|
id = "flake1";
|
|
};
|
|
inputs.bar.follows = "foo";
|
|
|
|
outputs = { self, foo, bar }: {};
|
|
}""")
|
|
nix.nix(["flake", "lock", flake3]).run().ok()
|
|
lock = json.loads((flake3 / "flake.lock").read_text())
|
|
assert lock["nodes"]["root"]["inputs"]["bar"] == ["foo"]
|
|
|
|
def test_nested_implicit(self, nix: Nix, flake3: Path):
|
|
(flake3 / "flake.nix").write_text("""{
|
|
inputs.bar.follows = "flake2/flake1";
|
|
|
|
outputs = { self, flake2, bar }: {};
|
|
}""")
|
|
nix.nix(["flake", "lock", flake3]).run().ok()
|
|
lock = json.loads((flake3 / "flake.lock").read_text())
|
|
assert lock["nodes"]["root"]["inputs"]["bar"] == ["flake2", "flake1"]
|
|
|
|
def test_simple_implicit(self, nix: Nix, flake3: Path):
|
|
(flake3 / "flake.nix").write_text("""{
|
|
inputs.bar.follows = "flake2";
|
|
|
|
outputs = { self, flake2, bar }: {};
|
|
}""")
|
|
nix.nix(["flake", "lock", flake3]).run().ok()
|
|
lock = json.loads((flake3 / "flake.lock").read_text())
|
|
assert lock["nodes"]["root"]["inputs"]["bar"] == ["flake2"]
|
|
|
|
class TestInputOverrides:
|
|
def test_simple_explicit(self, nix: Nix, flake3: Path, flake7: Path):
|
|
(flake3 / "flake.nix").write_text(f"""{{
|
|
inputs.flake2.inputs.flake1 = {{
|
|
type = "git";
|
|
url = "file://{flake7}";
|
|
}};
|
|
|
|
outputs = {{ self, flake2 }}: {{}};
|
|
}}""")
|
|
nix.nix(["flake", "lock", flake3]).run().ok()
|
|
lock = json.loads((flake3 / "flake.lock").read_text())
|
|
assert lock["nodes"]["flake1"]["locked"]["url"] == f"file://{flake7}"
|
|
|
|
def test_simple_follows(self, nix: Nix, flake3: Path, flake7: Path):
|
|
(flake3 / "flake.nix").write_text(f"""{{
|
|
inputs.flake2.inputs.flake1.follows = "foo";
|
|
inputs.foo.url = "git+file://{flake7}";
|
|
|
|
outputs = {{ self, flake2 }}: {{}};
|
|
}}""")
|
|
nix.nix(["flake", "lock", flake3]).run().ok()
|
|
lock = json.loads((flake3 / "flake.lock").read_text())
|
|
assert lock["nodes"]["flake2"]["inputs"]["flake1"] == ["foo"]
|
|
assert lock["nodes"]["foo"]["locked"]["url"] == f"file://{flake7}"
|
|
|
|
|
|
@pytest.mark.usefixtures("registry")
|
|
class TestMetadata:
|
|
def test_registry(self, nix: Nix):
|
|
metadata = nix.nix(["flake", "metadata", "flake1"]).run().ok().stdout_s
|
|
assert re.search(r"Locked URL:.*flake1.*", metadata)
|
|
|
|
@pytest.mark.parametrize("source", [[], ["."]])
|
|
def test_cwd(self, nix: Nix, flake1: Path, source: list[str]):
|
|
metadata = nix.nix(["flake", "metadata", *source], cwd=flake1).run().ok().stdout_s
|
|
assert re.search(r"Locked URL:.*flake1.*", metadata)
|
|
|
|
def test_absolute(self, nix: Nix, flake1: Path):
|
|
metadata = nix.nix(["flake", "metadata", flake1]).run().ok().stdout_s
|
|
assert re.search(r"Locked URL:.*flake1.*", metadata)
|
|
|
|
@pytest.mark.parametrize("source", ["flake1", None])
|
|
def test_json(self, nix: Nix, flake1: Path, source: str | None, git: Git):
|
|
flake1_original_commit = git(flake1, "rev-parse", "HEAD").stdout_s.strip()
|
|
metadata = nix.nix(["flake", "metadata", source or flake1, "--json"]).run().json()
|
|
assert metadata["description"] == "Bla bla"
|
|
assert Path(metadata["path"]).is_dir()
|
|
assert metadata["lastModified"] == 23
|
|
assert metadata["revision"] == flake1_original_commit
|
|
|
|
def test_json_registry_dirty(self, nix: Nix, flake1: Path, git: Git):
|
|
flake1_original_commit = git(flake1, "rev-parse", "HEAD").stdout_s.strip()
|
|
(flake1 / "foo").write_text("foo")
|
|
git(flake1, "add", "foo")
|
|
metadata = nix.nix(["flake", "metadata", "flake1", "--json"]).run().json()
|
|
assert metadata["dirtyRevision"] == f"{flake1_original_commit}-dirty"
|
|
|
|
_modify_flake1(flake1, git)
|
|
flake1_modified_commit = git(flake1, "rev-parse", "HEAD").stdout_s.strip()
|
|
metadata = nix.nix(["flake", "metadata", "flake1", "--json", "--refresh"]).run().json()
|
|
assert metadata["revision"] == flake1_modified_commit
|
|
assert "dirtyRevision" not in metadata
|
|
|
|
|
|
class TestAddPath:
|
|
@with_files({"badFlake": {"flake.nix": File("INVALID")}})
|
|
def test_does_not_eval(self, nix: Nix, files: Path):
|
|
nix.nix(["store", "add-path", str(files / "badFlake")]).run().ok()
|
|
|
|
def test_add_flake(self, nix: Nix, flake1: Path):
|
|
path = nix.nix(["store", "add-path", flake1]).run().ok().stdout_s.strip()
|
|
|
|
info = nix.nix(["path-info", path]).run().ok().stdout_s
|
|
assert "flake1" in info
|
|
|
|
# required to the path: test, otherwise outputs are not valid
|
|
nix.nix(["build", flake1]).run().ok()
|
|
info = nix.nix(["path-info", f"path:{path}"]).run().ok().stdout_s
|
|
assert "simple" in info
|
|
|
|
|
|
@pytest.mark.usefixtures("registry")
|
|
class TestLegacyCLI:
|
|
def test_instantiate_indirect(self, nix: Nix):
|
|
assert (
|
|
nix.nix_instantiate(["--eval", "flake:flake3", "-A", "x"]).run().ok().stdout_s
|
|
== "123\n"
|
|
)
|
|
|
|
def test_instantiate_url(self, nix: Nix, flake3: Path):
|
|
assert (
|
|
nix.nix_instantiate(["--eval", f"flake:git+file://{flake3}", "-A", "x"])
|
|
.run()
|
|
.ok()
|
|
.stdout_s
|
|
== "123\n"
|
|
)
|
|
|
|
def test_instantiate_nix_path_option(self, nix: Nix):
|
|
assert (
|
|
nix.nix_instantiate(["-I", "flake3=flake:flake3", "--eval", "<flake3>", "-A", "x"])
|
|
.run()
|
|
.ok()
|
|
.stdout_s
|
|
== "123\n"
|
|
)
|
|
|
|
def test_instantiate_nix_path_env(self, nix: Nix):
|
|
nix.env["NIX_PATH"] = "flake3=flake:flake3"
|
|
assert nix.nix_instantiate(["--eval", "<flake3>", "-A", "x"]).run().ok().stdout_s == "123\n"
|
|
|
|
|
|
class TestAlternateLockFiles:
|
|
@pytest.fixture(autouse=True)
|
|
def init(self, nix: Nix, flake1: Path, flake2: Path, registry: Path, git: Git): # noqa: ARG002
|
|
git(flake1, "commit", "--allow-empty", "-m", "to change branch head")
|
|
nix.nix(["flake", "lock", flake2]).run().ok()
|
|
|
|
def test_other_lockfile_is_identical(self, nix: Nix, flake2: Path):
|
|
nix.nix(["flake", "lock", flake2, "--output-lock-file", "flake2.lock"]).run().ok()
|
|
assert (nix.env.dirs.home / "flake2.lock").read_text() == (
|
|
flake2 / "flake.lock"
|
|
).read_text()
|
|
|
|
def test_input_overrides_change_lockfiles(self, nix: Nix, flake1: Path, flake2: Path, git: Git):
|
|
flake1_original_commit = git(flake1, "rev-parse", "HEAD").stdout_s.strip()
|
|
nix.nix(
|
|
[
|
|
"flake",
|
|
"lock",
|
|
flake2,
|
|
"--output-lock-file",
|
|
"flake2.lock",
|
|
"--override-input",
|
|
"flake1",
|
|
f"git+file://{flake1}?rev={flake1_original_commit}",
|
|
]
|
|
).run().ok()
|
|
assert (nix.env.dirs.home / "flake2.lock").read_text() != (
|
|
flake2 / "flake.lock"
|
|
).read_text()
|
|
assert (
|
|
flake1_original_commit
|
|
in nix.nix(["flake", "metadata", flake2, "--reference-lock-file", "flake2.lock"])
|
|
.run()
|
|
.ok()
|
|
.stdout_s
|
|
)
|
|
|
|
|
|
def test_reference_lock_file_requires_allow_dirty(nix: Nix, flake2: Path):
|
|
assert (
|
|
"error: reference lock file was provided, but the `allow-dirty` setting is set to false"
|
|
in nix.nix(
|
|
[
|
|
"flake",
|
|
"metadata",
|
|
flake2,
|
|
"--no-allow-dirty",
|
|
"--reference-lock-file",
|
|
f"{nix.env.dirs.home}/flake2-overridden.lock",
|
|
]
|
|
)
|
|
.run()
|
|
.expect(1)
|
|
.stderr_s
|
|
)
|