From 1d38ffa9c2cfe8d4c9baf59cc518e1faf6dd2038 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Sun, 12 Oct 2025 16:07:08 +0200 Subject: [PATCH] inventory: unit test autoloading with a virtual fs --- checks/flake-module.nix | 27 +++++---- lib/clanTest/virtual-fs.nix | 51 +++++++++++++++++ lib/default.nix | 4 ++ lib/modules/clan/module.nix | 6 +- lib/modules/dir_test.nix | 108 ++++++++++++++++++++++++++++++++++++ lib/modules/tests.nix | 1 + 6 files changed, 180 insertions(+), 17 deletions(-) create mode 100644 lib/clanTest/virtual-fs.nix create mode 100644 lib/modules/dir_test.nix diff --git a/checks/flake-module.nix b/checks/flake-module.nix index 9edb8dc72..5c895c856 100644 --- a/checks/flake-module.nix +++ b/checks/flake-module.nix @@ -19,20 +19,19 @@ let nixosLib = import (self.inputs.nixpkgs + "/nixos/lib") { }; in { - imports = - filter pathExists [ - ./devshell/flake-module.nix - ./flash/flake-module.nix - ./installation/flake-module.nix - ./update/flake-module.nix - ./morph/flake-module.nix - ./nixos-documentation/flake-module.nix - ./dont-depend-on-repo-root.nix - # clan core submodule tests - ../nixosModules/clanCore/machine-id/tests/flake-module.nix - ../nixosModules/clanCore/postgresql/tests/flake-module.nix - ../nixosModules/clanCore/state-version/tests/flake-module.nix - ]; + imports = filter pathExists [ + ./devshell/flake-module.nix + ./flash/flake-module.nix + ./installation/flake-module.nix + ./update/flake-module.nix + ./morph/flake-module.nix + ./nixos-documentation/flake-module.nix + ./dont-depend-on-repo-root.nix + # clan core submodule tests + ../nixosModules/clanCore/machine-id/tests/flake-module.nix + ../nixosModules/clanCore/postgresql/tests/flake-module.nix + ../nixosModules/clanCore/state-version/tests/flake-module.nix + ]; flake.check = genAttrs [ "x86_64-linux" "aarch64-darwin" ] ( system: let diff --git a/lib/clanTest/virtual-fs.nix b/lib/clanTest/virtual-fs.nix new file mode 100644 index 000000000..385463002 --- /dev/null +++ b/lib/clanTest/virtual-fs.nix @@ -0,0 +1,51 @@ +{ lib }: +let + sanitizePath = + rootPath: path: + let + storePrefix = builtins.unsafeDiscardStringContext ("${rootPath}"); + pathStr = lib.removePrefix "/" ( + lib.removePrefix storePrefix (builtins.unsafeDiscardStringContext (toString path)) + ); + in + pathStr; + + mkFunctions = rootPath: passthru: virtual_fs: { + # Some functions to override lib functions + pathExists = + path: + let + pathStr = sanitizePath rootPath path; + isPassthru = builtins.any (exclude: (builtins.match exclude pathStr) != null) passthru; + in + if isPassthru then + builtins.pathExists path + else + let + res = virtual_fs ? ${pathStr}; + in + lib.trace "pathExists: '${pathStr}' -> '${lib.generators.toPretty { } res}'" res; + readDir = + path: + let + pathStr = sanitizePath rootPath path; + base = (pathStr + "/"); + res = lib.mapAttrs' (name: fileInfo: { + name = lib.removePrefix base name; + value = fileInfo.type; + }) (lib.filterAttrs (n: _: lib.hasPrefix base n) virtual_fs); + isPassthru = builtins.any (exclude: (builtins.match exclude pathStr) != null) passthru; + in + if isPassthru then + builtins.readDir path + else + lib.trace "readDir: '${pathStr}' -> '${lib.generators.toPretty { } res}'" res; + }; +in +{ + virtual_fs, + rootPath, + # Patterns + passthru ? [ ], +}: +mkFunctions rootPath passthru virtual_fs diff --git a/lib/default.nix b/lib/default.nix index e77897acb..f9734ed0f 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -36,6 +36,10 @@ lib.fix ( # TODO: Flatten our lib functions like this: resolveModule = clanLib.callLib ./resolve-module { }; + + fs = { + inherit (builtins) pathExists readDir; + }; }; in f diff --git a/lib/modules/clan/module.nix b/lib/modules/clan/module.nix index ed738b116..66d762e87 100644 --- a/lib/modules/clan/module.nix +++ b/lib/modules/clan/module.nix @@ -133,12 +133,12 @@ in } ) { - # TODO: Figure out why this causes infinite recursion - inventory = lib.optionalAttrs (builtins.pathExists "${directory}/machines") ({ + # Note: we use clanLib.fs here, so that we can override it in tests + inventory = lib.optionalAttrs (clanLib.fs.pathExists "${directory}/machines") ({ imports = lib.mapAttrsToList (name: _t: { _file = "${directory}/machines/${name}"; machines.${name} = { }; - }) ((lib.filterAttrs (_: t: t == "directory") (builtins.readDir "${directory}/machines"))); + }) ((lib.filterAttrs (_: t: t == "directory") (clanLib.fs.readDir "${directory}/machines"))); }); } { diff --git a/lib/modules/dir_test.nix b/lib/modules/dir_test.nix new file mode 100644 index 000000000..26ac302f1 --- /dev/null +++ b/lib/modules/dir_test.nix @@ -0,0 +1,108 @@ +{ + lib ? import , +}: +let + clanLibOrig = (import ./.. { inherit lib; }).__unfix__; + clanLibWithFs = + { virtual_fs }: + lib.fix ( + lib.extends ( + final: _: + let + clan-core = { + clanLib = final; + modules.clan.default = lib.modules.importApply ./clan { inherit clan-core; }; + + # Note: Can add other things to "clan-core" + # ... Not needed for this test + }; + in + { + clan = import ../clan { + inherit lib clan-core; + }; + + # Override clanLib.fs for unit-testing against a virtual filesystem + fs = import ../clanTest/virtual-fs.nix { inherit lib; } { + inherit rootPath virtual_fs; + # Example of a passthru + # passthru = [ + # ".*inventory\.json$" + # ]; + }; + } + ) clanLibOrig + ); + + rootPath = ./.; +in +{ + test_autoload_directories = + let + vclan = + (clanLibWithFs { + virtual_fs = { + "machines" = { + type = "directory"; + }; + "machines/foo-machine" = { + type = "directory"; + }; + "machines/bar-machine" = { + type = "directory"; + }; + }; + }).clan + { config.directory = rootPath; }; + in + { + inherit vclan; + expr = { + machines = lib.attrNames vclan.config.inventory.machines; + definedInMachinesDir = map ( + p: lib.hasInfix "/machines/" p + ) vclan.options.inventory.valueMeta.configuration.options.machines.files; + }; + expected = { + machines = [ + "bar-machine" + "foo-machine" + ]; + definedInMachinesDir = [ + true # /machines/foo-machine + true # /machines/bar-machine + false # /module.nix defines "machines" without members + ]; + }; + }; + + # Could probably be unified with the previous test + # This is here for the sake to show that 'virtual_fs' is a test parameter + test_files_are_not_machines = + let + vclan = + (clanLibWithFs { + virtual_fs = { + "machines" = { + type = "directory"; + }; + "machines/foo.nix" = { + type = "file"; + }; + "machines/bar.nix" = { + type = "file"; + }; + }; + }).clan + { config.directory = rootPath; }; + in + { + inherit vclan; + expr = { + machines = lib.attrNames vclan.config.inventory.machines; + }; + expected = { + machines = [ ]; + }; + }; +} diff --git a/lib/modules/tests.nix b/lib/modules/tests.nix index 5ec8707d8..56ed2edbf 100644 --- a/lib/modules/tests.nix +++ b/lib/modules/tests.nix @@ -12,6 +12,7 @@ let in ####### { + autoloading = import ./dir_test.nix { inherit lib; }; test_missing_self = let eval = clan {