diff --git a/AGENTS.md b/AGENTS.md index 59ddd6382f71..a8a71cad047f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,7 +18,7 @@ Building and testing Nix: * Build Nix using `nix build ./packaging/nix-make#release.nix`. The result is in `./result`. -* Run the functional tests in `tests/functional` using `nix build -L ./packaging/nix-make#release.functional-tests`. +* Run all tests using `nix build -L ./packaging/nix-make#release.nix-all-tests`. This covers the unit tests (e.g. `release.nix-util-tests-run`, which runs `src/libutil-tests`) and the functional tests in `tests/functional` (`release.functional-tests`); each can also be built on its own. * Individual tests can be executed by doing `nix build ./packaging/nix-make#release.nix && nix run ./packaging/nix-make#test-runner -- --nix-bin-dir ./result/bin $TEST_NAMES`, where `TEST_NAMES` are the file names inside `tests/functional` (without the `.sh` suffix), e.g. `simple` or `flakes/relative-paths`. diff --git a/packaging/nix-make/deps.nix b/packaging/nix-make/deps.nix index a66604cc2999..50a2454bb568 100644 --- a/packaging/nix-make/deps.nix +++ b/packaging/nix-make/deps.nix @@ -212,7 +212,8 @@ in pkgconfig = [ "libeditline" ]; } { - prefix = "git2/"; + # Matches both and . + prefix = "git2"; pkg = deps.libgit2; pkgconfig = [ "libgit2" ]; } @@ -224,4 +225,23 @@ in ]; libs = [ "-lwasmtime" ]; } + # Unit test frameworks. + { + prefix = "gtest/"; + pkg = deps.gtest; + pkgconfig = [ "gtest" ]; + } + { + prefix = "gmock/"; + pkg = deps.gtest; + pkgconfig = [ "gmock" ]; + } + { + # Matches both and . rapidcheck.pc has + # an empty `Libs:`, so the library is named explicitly. + prefix = "rapidcheck"; + pkg = deps.rapidcheck; + pkgconfig = [ "rapidcheck" ]; + libs = [ "-lrapidcheck" ]; + } ] diff --git a/packaging/nix-make/flake.nix b/packaging/nix-make/flake.nix index aceb4cae11b5..290b83c48cd6 100644 --- a/packaging/nix-make/flake.nix +++ b/packaging/nix-make/flake.nix @@ -52,8 +52,40 @@ nix-cmd = self.callPackage ../../src/libcmd/make.nix { }; nix = self.callPackage ../../src/nix/make.nix { }; + # The unit tests: the test executables and derivations running them. + nix-util-c = self.callPackage ../../src/libutil-c/make.nix { }; + nix-util-test-support = self.callPackage ../../src/libutil-test-support/make.nix { }; + nix-util-tests = self.callPackage ../../src/libutil-tests/make.nix { }; + nix-util-tests-run = self.nix-util-tests.tests.run; + nix-util-tests-run-without-new-syscalls = self.nix-util-tests.tests.run-without-new-syscalls; + nix-store-c = self.callPackage ../../src/libstore-c/make.nix { }; + nix-store-test-support = self.callPackage ../../src/libstore-test-support/make.nix { }; + nix-store-tests = self.callPackage ../../src/libstore-tests/make.nix { }; + nix-store-tests-run = self.nix-store-tests.tests.run; + nix-fetchers-c = self.callPackage ../../src/libfetchers-c/make.nix { }; + nix-fetchers-tests = self.callPackage ../../src/libfetchers-tests/make.nix { }; + nix-fetchers-tests-run = self.nix-fetchers-tests.tests.run; + nix-expr-c = self.callPackage ../../src/libexpr-c/make.nix { }; + nix-expr-test-support = self.callPackage ../../src/libexpr-test-support/make.nix { }; + nix-expr-tests = self.callPackage ../../src/libexpr-tests/make.nix { }; + nix-expr-tests-run = self.nix-expr-tests.tests.run; + nix-flake-c = self.callPackage ../../src/libflake-c/make.nix { }; + nix-flake-tests = self.callPackage ../../src/libflake-tests/make.nix { }; + nix-flake-tests-run = self.nix-flake-tests.tests.run; + test-runner = self.callPackage ../../tests/functional/test-runner.nix { }; functional-tests = self.callPackage ../../tests/functional/make.nix { }; + + # Everything that runs tests; building it runs them all. + nix-all-tests = pkgs.linkFarmFromDrvs "nix-all-tests" [ + self.nix-util-tests-run + self.nix-util-tests-run-without-new-syscalls + self.nix-store-tests-run + self.nix-fetchers-tests-run + self.nix-expr-tests-run + self.nix-flake-tests-run + self.functional-tests + ]; }); in rec { @@ -61,6 +93,11 @@ inherit (make.${system}.release) test-runner; }; + # `nix flake check` runs all tests against the release variant. + checks.${system} = { + inherit (make.${system}.release) nix-all-tests; + }; + devShells.${system}.default = pkgs.mkShell { packages = [ make.${system}.release.test-runner ]; }; @@ -100,6 +137,7 @@ inherit (flake-schemas.schemas) schemas packages + checks devShells ; diff --git a/packaging/nix-make/lib.nix b/packaging/nix-make/lib.nix index d53546255098..36928eba2377 100644 --- a/packaging/nix-make/lib.nix +++ b/packaging/nix-make/lib.nix @@ -134,6 +134,26 @@ let } ); + # A derivation that runs a test program (e.g. a gtest executable) with + # `env` added to the environment, and succeeds if it does. Its output is + # empty; the program's output is in the build log. `HOME` is a writable + # directory, which the values in `env` may refer to as `$HOME`. + runTest = + { + name, + command, + env ? { }, + nativeBuildInputs ? [ ], + }: + mkPythonDerivation { + inherit + name + command + env + nativeBuildInputs + ; + } ./run-test.py; + # The stdenv's default hardening flags, except that `_FORTIFY_SOURCE` # warns on every unit when not optimizing, so it is disabled then (like # `hardeningDisable = [ "fortify" ]` in `stdenv.mkDerivation`). @@ -493,6 +513,7 @@ in weakVtablesFlags mkStringHeader mkComponent + runTest commonSupportFiles ; } diff --git a/packaging/nix-make/run-test.py b/packaging/nix-make/run-test.py new file mode 100644 index 000000000000..0f65f5aa919f --- /dev/null +++ b/packaging/nix-make/run-test.py @@ -0,0 +1,26 @@ +# The builder for running a test program (see `runTest` in lib.nix). It +# runs `command` with `env` added to the environment; the output of the +# program goes to the build log. + +import json +import os +import subprocess + + +def main(): + with open(os.environ["NIX_ATTRS_JSON_FILE"]) as f: + attrs = json.load(f) + + # A writable home directory, for tests that keep state (e.g. a store) + # there. The values in `env` may refer to it as `$HOME`. + os.environ["HOME"] = os.path.join(os.environ["TMPDIR"], "home") + os.mkdir(os.environ["HOME"]) + env = dict(os.environ) + for name, value in attrs["env"].items(): + env[name] = os.path.expandvars(value) + + subprocess.run(attrs["command"], env=env, check=True) + os.mkdir(os.environ["out"]) + + +main() diff --git a/src/libexpr-c/make.nix b/src/libexpr-c/make.nix new file mode 100644 index 000000000000..29758f322ad4 --- /dev/null +++ b/src/libexpr-c/make.nix @@ -0,0 +1,34 @@ +# libnixexprc, the C API of libnixexpr, transcribed from meson.build. +{ + nixMake, + nix-util, + nix-util-c, + nix-store, + nix-store-c, + nix-expr, +}: + +nixMake.mkComponent { + name = "determinate-nix-expr-c"; + libName = "nixexprc"; + + deps = [ + nix-util + nix-util-c + nix-store + nix-store-c + nix-expr + ]; + + root = ./.; + + includeDirs = [ "" ]; + # The headers live in the root, which the default would not export. + publicIncludeDirs = [ "" ]; + + files = nixMake.commonSupportFiles; + + extraCxxFlags = nixMake.weakVtablesFlags; + + linkFlags = [ "-Wl,--wrap=__assert_fail" ]; +} diff --git a/src/libexpr-test-support/make.nix b/src/libexpr-test-support/make.nix new file mode 100644 index 000000000000..1ecbb726f00c --- /dev/null +++ b/src/libexpr-test-support/make.nix @@ -0,0 +1,33 @@ +# libnix-expr-test-support, helpers for the unit tests of libnixexpr and +# the components built on it, transcribed from meson.build. +{ + nixMake, + nix-util, + nix-util-test-support, + nix-store, + nix-store-test-support, + nix-expr, + nix-expr-c, +}: + +nixMake.mkComponent { + name = "determinate-nix-expr-test-support"; + libName = "nix-expr-test-support"; + + deps = [ + nix-util + nix-util-test-support + nix-store + nix-store-test-support + nix-expr + nix-expr-c + ]; + + root = ./.; + + includeDirs = [ "include" ]; + + files = nixMake.commonSupportFiles; + + linkFlags = [ "-Wl,--wrap=__assert_fail" ]; +} diff --git a/src/libexpr-tests/make.nix b/src/libexpr-tests/make.nix new file mode 100644 index 000000000000..9f3637748120 --- /dev/null +++ b/src/libexpr-tests/make.nix @@ -0,0 +1,60 @@ +# The unit tests of libnixexpr, transcribed from meson.build and +# package.nix: the `nix-expr-tests` executable, with the derivation that +# runs it as `tests.run`. +{ + pkgs, + nixMake, + nix-expr, + nix-expr-c, + nix-expr-test-support, +}: + +let + inherit (pkgs) lib; + + tests = nixMake.mkComponent { + name = "determinate-nix-expr-tests"; + type = "executable"; + exeName = "nix-expr-tests"; + + deps = [ + nix-expr + nix-expr-c + nix-expr-test-support + ]; + + root = ./.; + + # The benchmarks (Meson option `benchmarks`, off by default). + excludeSources = [ + "bench-main.cc" + "dynamic-attrs-bench.cc" + "get-drvs-bench.cc" + "regex-cache-bench.cc" + ]; + + includeDirs = [ "" ]; + + files = nixMake.commonSupportFiles; + + configHeaders = { + "expr-tests-config.hh" = { + PACKAGE_VERSION = lib.fileContents ../../.version; + }; + }; + + linkFlags = [ "-Wl,--wrap=__assert_fail" ]; + }; +in + +tests +// { + tests.run = nixMake.runTest { + name = "${tests.name}-run"; + command = [ "${tests}/bin/${tests.exeName}" ]; + env = { + _NIX_TEST_UNIT_DATA = ./data; + NIX_STORE = ""; + }; + }; +} diff --git a/src/libfetchers-c/make.nix b/src/libfetchers-c/make.nix new file mode 100644 index 000000000000..a3b84b324070 --- /dev/null +++ b/src/libfetchers-c/make.nix @@ -0,0 +1,32 @@ +# libnixfetchersc, the C API of libnixfetchers, transcribed from meson.build. +{ + nixMake, + nix-util, + nix-util-c, + nix-store, + nix-store-c, + nix-fetchers, +}: + +nixMake.mkComponent { + name = "determinate-nix-fetchers-c"; + libName = "nixfetchersc"; + + deps = [ + nix-util + nix-util-c + nix-store + nix-store-c + nix-fetchers + ]; + + root = ./.; + + includeDirs = [ "" ]; + # The headers live in the root, which the default would not export. + publicIncludeDirs = [ "" ]; + + files = nixMake.commonSupportFiles; + + linkFlags = [ "-Wl,--wrap=__assert_fail" ]; +} diff --git a/src/libfetchers-tests/make.nix b/src/libfetchers-tests/make.nix new file mode 100644 index 000000000000..6ae60646e15c --- /dev/null +++ b/src/libfetchers-tests/make.nix @@ -0,0 +1,47 @@ +# The unit tests of libnixfetchers, transcribed from meson.build and +# package.nix: the `nix-fetchers-tests` executable, with the derivation +# that runs it as `tests.run`. +{ + nixMake, + nix-store-test-support, + nix-fetchers, + nix-fetchers-c, +}: + +let + tests = nixMake.mkComponent { + name = "determinate-nix-fetchers-tests"; + type = "executable"; + exeName = "nix-fetchers-tests"; + + deps = [ + nix-store-test-support + nix-fetchers + nix-fetchers-c + ]; + + root = ./.; + + includeDirs = [ "" ]; + + files = nixMake.commonSupportFiles; + + # The tests have no main() of their own; it comes from gtest_main + # (see src/libutil-tests/make.nix). + extraLinkLibs = [ "-lgtest_main" ]; + + linkFlags = [ "-Wl,--wrap=__assert_fail" ]; + }; +in + +tests +// { + tests.run = nixMake.runTest { + name = "${tests.name}-run"; + command = [ "${tests}/bin/${tests.exeName}" ]; + env = { + _NIX_TEST_UNIT_DATA = ./data; + NIX_STORE = ""; + }; + }; +} diff --git a/src/libflake-c/make.nix b/src/libflake-c/make.nix new file mode 100644 index 000000000000..fcc65d40e745 --- /dev/null +++ b/src/libflake-c/make.nix @@ -0,0 +1,40 @@ +# libnixflakec, the C API of libnixflake, transcribed from meson.build. +{ + nixMake, + nix-util, + nix-util-c, + nix-store, + nix-store-c, + nix-fetchers, + nix-fetchers-c, + nix-expr, + nix-expr-c, + nix-flake, +}: + +nixMake.mkComponent { + name = "determinate-nix-flake-c"; + libName = "nixflakec"; + + deps = [ + nix-util + nix-util-c + nix-store + nix-store-c + nix-fetchers + nix-fetchers-c + nix-expr + nix-expr-c + nix-flake + ]; + + root = ./.; + + includeDirs = [ "" ]; + # The headers live in the root, which the default would not export. + publicIncludeDirs = [ "" ]; + + files = nixMake.commonSupportFiles; + + linkFlags = [ "-Wl,--wrap=__assert_fail" ]; +} diff --git a/src/libflake-tests/make.nix b/src/libflake-tests/make.nix new file mode 100644 index 000000000000..c075190595c9 --- /dev/null +++ b/src/libflake-tests/make.nix @@ -0,0 +1,47 @@ +# The unit tests of libnixflake, transcribed from meson.build and +# package.nix: the `nix-flake-tests` executable, with the derivation that +# runs it as `tests.run`. +{ + nixMake, + nix-expr-test-support, + nix-flake, + nix-flake-c, +}: + +let + tests = nixMake.mkComponent { + name = "determinate-nix-flake-tests"; + type = "executable"; + exeName = "nix-flake-tests"; + + deps = [ + nix-expr-test-support + nix-flake + nix-flake-c + ]; + + root = ./.; + + includeDirs = [ "" ]; + + files = nixMake.commonSupportFiles; + + # The tests have no main() of their own; it comes from gtest_main + # (see src/libutil-tests/make.nix). + extraLinkLibs = [ "-lgtest_main" ]; + + linkFlags = [ "-Wl,--wrap=__assert_fail" ]; + }; +in + +tests +// { + tests.run = nixMake.runTest { + name = "${tests.name}-run"; + command = [ "${tests}/bin/${tests.exeName}" ]; + env = { + _NIX_TEST_UNIT_DATA = ./data; + NIX_STORE = ""; + }; + }; +} diff --git a/src/libstore-c/make.nix b/src/libstore-c/make.nix new file mode 100644 index 000000000000..1a6baf4bc8cc --- /dev/null +++ b/src/libstore-c/make.nix @@ -0,0 +1,30 @@ +# libnixstorec, the C API of libnixstore, transcribed from meson.build. +{ + nixMake, + nix-util, + nix-util-c, + nix-store, +}: + +nixMake.mkComponent { + name = "determinate-nix-store-c"; + libName = "nixstorec"; + + deps = [ + nix-util + nix-util-c + nix-store + ]; + + root = ./.; + + includeDirs = [ "" ]; + # The headers live in the root, which the default would not export. + publicIncludeDirs = [ "" ]; + + files = nixMake.commonSupportFiles; + + extraCxxFlags = nixMake.weakVtablesFlags; + + linkFlags = [ "-Wl,--wrap=__assert_fail" ]; +} diff --git a/src/libstore-test-support/make.nix b/src/libstore-test-support/make.nix new file mode 100644 index 000000000000..9c14f7917e68 --- /dev/null +++ b/src/libstore-test-support/make.nix @@ -0,0 +1,29 @@ +# libnix-store-test-support, helpers for the unit tests of libnixstore and +# the components built on it, transcribed from meson.build. +{ + nixMake, + nix-util, + nix-util-test-support, + nix-store, + nix-store-c, +}: + +nixMake.mkComponent { + name = "determinate-nix-store-test-support"; + libName = "nix-store-test-support"; + + deps = [ + nix-util + nix-util-test-support + nix-store + nix-store-c + ]; + + root = ./.; + + includeDirs = [ "include" ]; + + files = nixMake.commonSupportFiles; + + linkFlags = [ "-Wl,--wrap=__assert_fail" ]; +} diff --git a/src/libstore-tests/make.nix b/src/libstore-tests/make.nix new file mode 100644 index 000000000000..c1ab2f7f31b3 --- /dev/null +++ b/src/libstore-tests/make.nix @@ -0,0 +1,73 @@ +# The unit tests of libnixstore, transcribed from meson.build and +# package.nix: the `nix-store-tests` executable, with the derivation that +# runs it as `tests.run`. +{ + pkgs, + nixMake, + nix-store, + nix-store-c, + nix-store-test-support, +}: + +let + inherit (pkgs) lib; + + tests = nixMake.mkComponent { + name = "determinate-nix-store-tests"; + type = "executable"; + exeName = "nix-store-tests"; + + deps = [ + nix-store + nix-store-c + nix-store-test-support + ]; + + root = ./.; + + # The benchmarks (Meson option `benchmarks`, off by default). + excludeSources = [ + "bench-main.cc" + "derivation-parser-bench.cc" + "ref-scan-bench.cc" + "register-valid-paths-bench.cc" + ]; + + includeDirs = [ "" ]; + + files = nixMake.commonSupportFiles; + + configHeaders = { + "store-tests-config.hh" = { + PACKAGE_VERSION = lib.fileContents ../../.version; + NIX_STORE_DIR = "/nix/store"; + }; + }; + + linkFlags = [ "-Wl,--wrap=__assert_fail" ]; + }; + + # Some data is shared with the functional tests: they create it, we + # consume it. Rooted at the repository so that both are present. + data = lib.fileset.toSource { + root = ../..; + fileset = lib.fileset.unions [ + ./data + ../../tests/functional/derivation + ]; + }; +in + +tests +// { + tests.run = nixMake.runTest { + name = "${tests.name}-run"; + command = [ "${tests}/bin/${tests.exeName}" ]; + env = { + _NIX_TEST_UNIT_DATA = "${data}/src/libstore-tests/data"; + NIX_REMOTE = "$HOME/store"; + }; + # The HTTPS binary cache tests generate certificates with `openssl`. + nativeBuildInputs = [ pkgs.openssl ]; + }; +} diff --git a/src/libutil-c/make.nix b/src/libutil-c/make.nix new file mode 100644 index 000000000000..b52daa84c5f0 --- /dev/null +++ b/src/libutil-c/make.nix @@ -0,0 +1,35 @@ +# libnixutilc, the C API of libnixutil, transcribed from meson.build. +{ + pkgs, + nixMake, + nix-util, +}: + +let + inherit (pkgs) lib; +in + +nixMake.mkComponent { + name = "determinate-nix-util-c"; + libName = "nixutilc"; + + deps = [ nix-util ]; + + root = ./.; + + includeDirs = [ "" ]; + # The headers live in the root, which the default would not export. + publicIncludeDirs = [ "" ]; + + files = nixMake.commonSupportFiles; + + configHeaders = { + "nix_api_util_config.h" = { + PACKAGE_VERSION = lib.fileContents ../../.version; + }; + }; + + extraCxxFlags = nixMake.weakVtablesFlags; + + linkFlags = [ "-Wl,--wrap=__assert_fail" ]; +} diff --git a/src/libutil-test-support/make.nix b/src/libutil-test-support/make.nix new file mode 100644 index 000000000000..1fb6d3ed8b0d --- /dev/null +++ b/src/libutil-test-support/make.nix @@ -0,0 +1,25 @@ +# libnix-util-test-support, helpers for the unit tests of libnixutil and +# the components built on it, transcribed from meson.build. +{ + nixMake, + nix-util, + nix-util-c, +}: + +nixMake.mkComponent { + name = "determinate-nix-util-test-support"; + libName = "nix-util-test-support"; + + deps = [ + nix-util + nix-util-c + ]; + + root = ./.; + + includeDirs = [ "include" ]; + + files = nixMake.commonSupportFiles; + + linkFlags = [ "-Wl,--wrap=__assert_fail" ]; +} diff --git a/src/libutil-tests/make.nix b/src/libutil-tests/make.nix new file mode 100644 index 000000000000..3a41a1715ab8 --- /dev/null +++ b/src/libutil-tests/make.nix @@ -0,0 +1,82 @@ +# The unit tests of libnixutil, transcribed from meson.build and +# package.nix: the `nix-util-tests` executable, with the derivations that +# run it as `tests.run` and `tests.run-without-new-syscalls`. +{ + pkgs, + nixMake, + nix-util, + nix-util-c, + nix-util-test-support, +}: + +let + inherit (pkgs) lib; + + tests = nixMake.mkComponent { + name = "determinate-nix-util-tests"; + type = "executable"; + exeName = "nix-util-tests"; + + deps = [ + nix-util + nix-util-c + nix-util-test-support + ]; + + root = ./.; + + includeDirs = [ "" ]; + + files = nixMake.commonSupportFiles; + + configHeaders = { + "util-tests-config.hh" = { + PACKAGE_VERSION = lib.fileContents ../../.version; + # libnixutil interposes __cxa_throw (see src/libutil/make.nix). + HAVE_CXA_THROW = 1; + }; + }; + + # The tests have no main() of their own; it comes from gtest_main, + # which no #include refers to, so it is linked explicitly (after the + # -lgtest from pkg-config, which also provides its -L). + extraLinkLibs = [ "-lgtest_main" ]; + + linkFlags = [ "-Wl,--wrap=__assert_fail" ]; + }; + + exe = "${tests}/bin/${tests.exeName}"; + + env = { + _NIX_TEST_UNIT_DATA = ./data; + }; +in + +tests +// { + tests = { + run = nixMake.runTest { + name = "${tests.name}-run"; + command = [ exe ]; + inherit env; + }; + + # Run the same tests with newer syscalls disabled via seccomp, to + # exercise fallback paths (iterative openat for openat2, /proc/self/fd + # for fchmodat2). + run-without-new-syscalls = nixMake.runTest { + name = "${tests.name}-run-without-new-syscalls"; + command = [ + "enosys" + "--syscall" + "openat2" + "--syscall" + "fchmodat2" + "--" + exe + ]; + inherit env; + nativeBuildInputs = [ pkgs.util-linux ]; + }; + }; +}