From b34f136f102974ca3fa42594edfd99e0341c596f Mon Sep 17 00:00:00 2001 From: eldritch horrors Date: Wed, 11 Feb 2026 01:06:11 +0100 Subject: [PATCH] f2: don't use diverted stores unless specifically requested diverted stores are only necessary when the logical store paths of objects matter for the test itself, such as for derivation hashes, substitution from golden sample nars, or actual tests of the store diversion functionality. all other tests can use undiverted stores to run, especially since only linux can build in diverted a store. Change-Id: I62f0907bdef9961609af22b610195fcec54c1e57 --- tests/functional2/build/test_fixed.py | 3 +- tests/functional2/lang/test_lang.py | 4 +- tests/functional2/store/test_evil_nars.py | 3 +- tests/functional2/store/test_http.py | 3 +- tests/functional2/testlib/fixtures/env.py | 5 +-- tests/functional2/testlib/fixtures/nix.py | 37 +++++++++++++------ .../functional2/testlib/fixtures/test_env.py | 9 +---- 7 files changed, 36 insertions(+), 28 deletions(-) diff --git a/tests/functional2/build/test_fixed.py b/tests/functional2/build/test_fixed.py index 734e2c1b2..4cedd7c1c 100644 --- a/tests/functional2/build/test_fixed.py +++ b/tests/functional2/build/test_fixed.py @@ -1,4 +1,3 @@ -import sys import re from pathlib import Path @@ -24,7 +23,7 @@ def impure_vars(env: ManagedEnv): def test_bad(nix: Nix): res = nix.nix_instantiate(["fixed.nix", "-A", "good.0"], build=True).run().ok() store_path = nix.nix_store(["-q", res.stdout_plain], build=True).run().ok().stdout_plain - path = Path(f"{nix.env.dirs.test_root}{store_path}" if sys.platform != "darwin" else store_path) + path = Path(store_path) assert not path.exists() # Building with the bad hash should produce the "good" output path as diff --git a/tests/functional2/lang/test_lang.py b/tests/functional2/lang/test_lang.py index 4b0ee1a05..9939548a2 100644 --- a/tests/functional2/lang/test_lang.py +++ b/tests/functional2/lang/test_lang.py @@ -8,7 +8,7 @@ import yaml from _pytest.python import Metafunc from lang.lang_util import LangTest, fetch_all_lang_tests, LangTestRunner -from testlib.fixtures.nix import Nix +from testlib.fixtures.nix import Nix, with_diverted_store from testlib.fixtures.snapshot import Snapshot @@ -92,6 +92,7 @@ def test_parse_fail(files: Path, nix: Nix, flags: list[str], snapshot: Callable[ assert snapshot("err.exp") == stderr +@with_diverted_store def test_eval_okay(files: Path, nix: Nix, flags: list[str], snapshot: Callable[[str], Snapshot]): nix_command = nix.nix_instantiate(["--eval", "--strict", *flags, files / "in.nix"], flake=True) result = nix_command.run().ok() @@ -101,6 +102,7 @@ def test_eval_okay(files: Path, nix: Nix, flags: list[str], snapshot: Callable[[ assert snapshot("err.exp") == stderr +@with_diverted_store def test_eval_fail(files: Path, nix: Nix, flags: list[str], snapshot: Callable[[str], Snapshot]): nix_command = nix.nix_instantiate( ["--eval", "--strict", "--show-trace", *flags, files / "in.nix"], flake=True diff --git a/tests/functional2/store/test_evil_nars.py b/tests/functional2/store/test_evil_nars.py index aeaf68994..0a4984522 100644 --- a/tests/functional2/store/test_evil_nars.py +++ b/tests/functional2/store/test_evil_nars.py @@ -6,7 +6,7 @@ from pathlib import Path import pytest -from testlib.fixtures.nix import Nix +from testlib.fixtures.nix import Nix, with_diverted_store from testlib.nar import ( DirectoryUnordered, NarItem, @@ -102,6 +102,7 @@ EVIL_NARS: list[tuple[str, NarItem]] = [ @pytest.mark.parametrize(("name", "nar"), EVIL_NARS, ids=next(zip(*EVIL_NARS))) +@with_diverted_store def test_evil_nar(nix: Nix, name: str, nar: NarItem, logger: Logger): bio = BytesIO() diff --git a/tests/functional2/store/test_http.py b/tests/functional2/store/test_http.py index d27c68200..d88d50478 100644 --- a/tests/functional2/store/test_http.py +++ b/tests/functional2/store/test_http.py @@ -6,7 +6,7 @@ import pytest from testlib.fixtures.file_helper import File, with_files from testlib.fixtures.http_server import http_server -from testlib.fixtures.nix import Nix +from testlib.fixtures.nix import Nix, with_diverted_store class HTTPStore: @@ -101,6 +101,7 @@ def nars_from_narinfo_cache(db_path: Path) -> list[dict[str, str | bool]]: @with_files({"test-file": File("hello world")}) +@with_diverted_store def test_http_simple(nix: Nix, store: HTTPStore, files: Path): test_file = files / "test-file" result = nix.nix(cmd=["store", "add-file", test_file], flake=True).run() diff --git a/tests/functional2/testlib/fixtures/env.py b/tests/functional2/testlib/fixtures/env.py index 1b016f7f5..b3e64d495 100644 --- a/tests/functional2/testlib/fixtures/env.py +++ b/tests/functional2/testlib/fixtures/env.py @@ -154,10 +154,6 @@ class _Dirs: tmpdir: Path | None """used for nar caching""" nix_store_dir: Path | None = None - """ - store dir override for building on macos *specifically*. do not - use or set this for any other purpose or many tests will break. - """ def get_env_keys(self) -> set[str]: return {f.name.upper() for f in dataclasses.fields(self)} @@ -194,6 +190,7 @@ class ManagedEnv: nix_conf_dir=self._get_dir("etc/nix"), nix_bin_dir=lix_bin, real_store_dir=self._get_dir("nix/store"), + nix_store_dir=self._get_dir("nix/store"), cache_dir=self._get_dir("test-binary-cache"), xdg_cache_home=self._get_dir("test-home/.cache"), tmpdir=self._get_dir("tmp"), diff --git a/tests/functional2/testlib/fixtures/nix.py b/tests/functional2/testlib/fixtures/nix.py index 0afa7d8e0..ab210db3b 100644 --- a/tests/functional2/testlib/fixtures/nix.py +++ b/tests/functional2/testlib/fixtures/nix.py @@ -147,11 +147,19 @@ class Nix: if self._settings is None: self._settings = NixSettings() self._settings.store = f"local?root={self.env.dirs.test_root}" + if sys.platform == "linux": + # sandbox build dir cannot be withing store dir. choose a short non-overlapping path. + self._settings.sandbox_build_dir = ( + "/build-f2" if self.env.dirs.test_root.parts[1] != "build-f2" else "/build.f2" + ) return self._settings def nix_cmd( - self, argv: list[str], flake: bool = False, build: bool | Literal["auto"] = "auto" + self, + argv: list[str], + flake: bool = False, + build: bool | Literal["auto"] = "auto", # noqa: ARG002 ) -> Command: """ Constructs a NixCommand with the appropriate settings. @@ -161,17 +169,6 @@ class Nix: settings = self.settings.clone() if flake: settings.add_xp_feature("nix-command", "flakes") - # FIXME(rootile): Darwin needs special handling here, as it does not support (non-root) chroots... - # Hence, it cannot build using a relocated store so we just use the local (aka global) store instead - # This is kinda ugly but what else can one do - if sys.platform == "darwin": - if build is True or ( - # argv[1:2] does not throw a key error on empty lists - # hence not crashing this check on an empty `nix.nix([])` call - build == "auto" and (argv[0] == "nix-build" or argv[1:2] == ["build"]) - ): - settings.store = None - self.env.dirs.nix_store_dir = self.env.dirs.real_store_dir settings.to_env_overlay(self.env) return Command(argv=argv, exe=self._nix_executable, _env=self.env) @@ -352,3 +349,19 @@ def nix(tmp_path: Path, env: ManagedEnv, logger: logging.Logger) -> Generator[Ni # for pytest to be able to delete the files during cleanup cmd = Command(argv=["chmod", "-R", "+w", str(tmp_path.absolute())], _env=env) cmd.run().ok() + + +@pytest.fixture +def enable_diverted_store(nix: Nix): + """ + clear NIX_STORE_DIR, resetting it to the default (ie /nix/store). + this makes builds impossible on platforms that cannot bind-mount, + (e.g. macos) but it is important for eval result reproducibility. + while builds may not work, substitution should still be possible. + """ + nix.env.dirs.nix_store_dir = None + nix.settings.sandbox_build_dir = None + + +def with_diverted_store(func: Callable[[Any], None]) -> Callable[[Any], None]: + return pytest.mark.usefixtures("enable_diverted_store")(func) diff --git a/tests/functional2/testlib/fixtures/test_env.py b/tests/functional2/testlib/fixtures/test_env.py index 9ede7c9c4..53e998d61 100644 --- a/tests/functional2/testlib/fixtures/test_env.py +++ b/tests/functional2/testlib/fixtures/test_env.py @@ -98,13 +98,7 @@ def test_env_dirs_created(env: ManagedEnv): fields = dataclasses.asdict(env.dirs).items() assert len(fields) > 1 for name, field in fields: - field: Path | None - # we never want to set NIX_STORE_DIR on linux due to unfortunate config ordering. - # creating a fresh env should always have it unset, it'll only be set when needed - if name == "nix_store_dir": - assert field is None - else: - field.exists() + field.exists() def test_env_get_path_fails(env: ManagedEnv): @@ -129,6 +123,7 @@ def test_env_to_env(tmp_path: Path): "NIX_STATE_DIR", "NIX_CONF_DIR", "NIX_BIN_DIR", + "NIX_STORE_DIR", "REAL_STORE_DIR", "CACHE_DIR", "XDG_CACHE_HOME",