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",