diff --git a/flake.nix b/flake.nix index d047c7bf..02c7afac 100644 --- a/flake.nix +++ b/flake.nix @@ -118,6 +118,11 @@ tests = import ./tests { inherit pkgs; }; renameTestPkg = n: lib.nameValuePair "test-${n}"; in lib.mapAttrs' renameTestPkg tests.build; + + integrationTestPackages = let + tests = import ./tests/integration { inherit pkgs; }; + renameTestPkg = n: lib.nameValuePair "integration-test-${n}"; + in lib.mapAttrs' renameTestPkg tests; in { default = hmPkg; home-manager = hmPkg; @@ -125,7 +130,7 @@ docs-html = docs.manual.html; docs-json = docs.options.json; docs-manpages = docs.manPages; - } // testPackages); + } // testPackages // integrationTestPackages); defaultPackage = forAllSystems (system: self.packages.${system}.default); }); diff --git a/tests/integration/default.nix b/tests/integration/default.nix new file mode 100644 index 00000000..c3103017 --- /dev/null +++ b/tests/integration/default.nix @@ -0,0 +1,20 @@ +{ pkgs }: + +let + nixosLib = import "${pkgs.path}/nixos/lib" { }; + + runTest = test: + nixosLib.runTest { + imports = [ test { node.pkgs = pkgs; } ]; + hostPkgs = pkgs; # the Nixpkgs package set used outside the VMs + }; + + tests = { + nixos-basics = runTest ./nixos/basics.nix; + standalone-flake-basics = runTest ./standalone/flake-basics.nix; + standalone-standard-basics = runTest ./standalone/standard-basics.nix; + }; +in tests // { + all = pkgs.linkFarm "all" + (pkgs.lib.mapAttrsToList (name: path: { inherit name path; }) tests); +} diff --git a/tests/integration/nixos/basics.nix b/tests/integration/nixos/basics.nix new file mode 100644 index 00000000..29c2756f --- /dev/null +++ b/tests/integration/nixos/basics.nix @@ -0,0 +1,44 @@ +{ pkgs, ... }: + +{ + name = "nixos-basics"; + meta.maintainers = [ pkgs.lib.maintainers.rycee ]; + + nodes.machine = { ... }: { + imports = [ ../../../nixos ]; # Import the HM NixOS module. + + users.users.alice = { isNormalUser = true; }; + + home-manager.users.alice = { ... }: { + home.stateVersion = "23.11"; + home.file.test.text = "testfile"; + }; + }; + + testScript = '' + start_all() + + machine.wait_for_unit("home-manager-alice.service") + + with subtest("Home Manager file"): + # The file should be linked with the expected content. + path = "/home/alice/test" + machine.succeed(f"test -L {path}") + actual = machine.succeed(f"cat {path}") + expected = "testfile" + assert actual == expected, f"expected {path} to contain {expected}, but got {actual}" + + with subtest("GC root and profile"): + # There should be a GC root and Home Manager profile and they should point + # to the same path in the Nix store. + gcroot = "/home/alice/.local/state/home-manager/gcroots/current-home" + gcrootTarget = machine.succeed(f"readlink {gcroot}") + + profile = "/home/alice/.local/state/nix/profiles" + profileTarget = machine.succeed(f"readlink {profile}/home-manager") + profile1Target = machine.succeed(f"readlink {profile}/{profileTarget}") + + assert gcrootTarget == profile1Target, \ + f"expected GC root and profile to point to same, but pointed to {gcrootTarget} and {profile1Target}" + ''; +} diff --git a/tests/integration/standalone/alice-flake-init.nix b/tests/integration/standalone/alice-flake-init.nix new file mode 100644 index 00000000..323b4b52 --- /dev/null +++ b/tests/integration/standalone/alice-flake-init.nix @@ -0,0 +1,29 @@ +{ + description = "Home Manager configuration of alice"; + + inputs = { + # Specify the source of Home Manager and Nixpkgs. + nixpkgs.url = "nixpkgs"; + home-manager = { + url = "home-manager"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { nixpkgs, home-manager, ... }: + let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + in { + homeConfigurations."alice" = home-manager.lib.homeManagerConfiguration { + inherit pkgs; + + # Specify your home configuration modules here, for example, + # the path to your home.nix. + modules = [ ./home.nix ]; + + # Optionally use extraSpecialArgs + # to pass through arguments to home.nix + }; + }; +} diff --git a/tests/integration/standalone/alice-home-init.nix b/tests/integration/standalone/alice-home-init.nix new file mode 100644 index 00000000..c5abf850 --- /dev/null +++ b/tests/integration/standalone/alice-home-init.nix @@ -0,0 +1,75 @@ +{ config, pkgs, ... }: + +{ + # Home Manager needs a bit of information about you and the paths it should + # manage. + home.username = "alice"; + home.homeDirectory = "/home/alice"; + + # This value determines the Home Manager release that your configuration is + # compatible with. This helps avoid breakage when a new Home Manager release + # introduces backwards incompatible changes. + # + # You should not change this value, even if you update Home Manager. If you do + # want to update the value, then make sure to first check the Home Manager + # release notes. + home.stateVersion = "23.11"; # Please read the comment before changing. + + # The home.packages option allows you to install Nix packages into your + # environment. + home.packages = [ + # # Adds the 'hello' command to your environment. It prints a friendly + # # "Hello, world!" when run. + # pkgs.hello + + # # It is sometimes useful to fine-tune packages, for example, by applying + # # overrides. You can do that directly here, just don't forget the + # # parentheses. Maybe you want to install Nerd Fonts with a limited number of + # # fonts? + # (pkgs.nerdfonts.override { fonts = [ "FantasqueSansMono" ]; }) + + # # You can also create simple shell scripts directly inside your + # # configuration. For example, this adds a command 'my-hello' to your + # # environment: + # (pkgs.writeShellScriptBin "my-hello" '' + # echo "Hello, ${config.home.username}!" + # '') + ]; + + # Home Manager is pretty good at managing dotfiles. The primary way to manage + # plain files is through 'home.file'. + home.file = { + # # Building this configuration will create a copy of 'dotfiles/screenrc' in + # # the Nix store. Activating the configuration will then make '~/.screenrc' a + # # symlink to the Nix store copy. + # ".screenrc".source = dotfiles/screenrc; + + # # You can also set the file content immediately. + # ".gradle/gradle.properties".text = '' + # org.gradle.console=verbose + # org.gradle.daemon.idletimeout=3600000 + # ''; + }; + + # Home Manager can also manage your environment variables through + # 'home.sessionVariables'. If you don't want to manage your shell through Home + # Manager then you have to manually source 'hm-session-vars.sh' located at + # either + # + # ~/.nix-profile/etc/profile.d/hm-session-vars.sh + # + # or + # + # ~/.local/state/nix/profiles/profile/etc/profile.d/hm-session-vars.sh + # + # or + # + # /etc/profiles/per-user/alice/etc/profile.d/hm-session-vars.sh + # + home.sessionVariables = { + # EDITOR = "emacs"; + }; + + # Let Home Manager install and manage itself. + programs.home-manager.enable = true; +} diff --git a/tests/integration/standalone/alice-home-next.nix b/tests/integration/standalone/alice-home-next.nix new file mode 100644 index 00000000..f4ee8e8f --- /dev/null +++ b/tests/integration/standalone/alice-home-next.nix @@ -0,0 +1,12 @@ +{ config, pkgs, ... }: + +{ + home.username = "alice"; + home.homeDirectory = "/home/alice"; + home.stateVersion = "23.11"; + home.packages = [ pkgs.hello ]; + home.file.test.text = "test"; + home.sessionVariables.EDITOR = "emacs"; + programs.bash.enable = true; + programs.home-manager.enable = true; +} diff --git a/tests/integration/standalone/flake-basics.nix b/tests/integration/standalone/flake-basics.nix new file mode 100644 index 00000000..6c9c1777 --- /dev/null +++ b/tests/integration/standalone/flake-basics.nix @@ -0,0 +1,87 @@ +{ pkgs, ... }: + +{ + name = "standalone-flake-basics"; + meta.maintainers = [ pkgs.lib.maintainers.rycee ]; + + nodes.machine = { ... }: { + imports = [ "${pkgs.path}/nixos/modules/installer/cd-dvd/channel.nix" ]; + virtualisation.memorySize = 2048; + nix.settings.extra-experimental-features = [ "nix-command" "flakes" ]; + users.users.alice = { isNormalUser = true; }; + }; + + testScript = '' + start_all() + machine.wait_for_unit("network-online.target") + machine.wait_for_unit("multi-user.target") + + home_manager = "${../../..}" + nixpkgs = "${pkgs.path}" + + machine.succeed(f"nix registry add home-manager path:{home_manager}") + machine.succeed(f"nix registry add nixpkgs path:{nixpkgs}") + + def as_alice(cmd): + return machine.succeed(f"su - alice -c '{cmd}'") + + with subtest("Home Manager init"): + as_alice(f"nix run path:{home_manager} -- init --home-manager-url home-manager --nixpkgs-url nixpkgs --switch") + + actual = machine.succeed("ls /home/alice/.config/home-manager") + expected = "flake.lock\nflake.nix\nhome.nix\n" + assert actual == expected, \ + f"unexpected content of /home/alice/.config/home-manager: {actual}" + + machine.succeed("diff -u ${ + ./alice-home-init.nix + } /home/alice/.config/home-manager/home.nix") + machine.succeed("diff -u ${ + ./alice-flake-init.nix + } /home/alice/.config/home-manager/flake.nix") + + # The default configuration creates this link on activation. + machine.succeed("test -L /home/alice/.cache/.keep") + + with subtest("GC root and profile"): + # There should be a GC root and Home Manager profile and they should point + # to the same path in the Nix store. + gcroot = "/home/alice/.local/state/home-manager/gcroots/current-home" + gcrootTarget = machine.succeed(f"readlink {gcroot}") + + profile = "/home/alice/.local/state/nix/profiles" + profileTarget = machine.succeed(f"readlink {profile}/home-manager") + profile1Target = machine.succeed(f"readlink {profile}/{profileTarget}") + + assert gcrootTarget == profile1Target, \ + f"expected GC root and profile to point to same, but pointed to {gcrootTarget} and {profile1Target}" + + with subtest("Home Manager switch"): + as_alice("cp ${ + ./alice-home-next.nix + } /home/alice/.config/home-manager/home.nix") + + as_alice("home-manager switch") + as_alice("hello") + + actual = as_alice("echo -n $EDITOR") + assert "emacs" == actual, \ + f"expected $EDITOR to contain emacs, but found {actual}" + + with subtest("Home Manager generations"): + actual = as_alice("home-manager generations") + expected = ": id 1 ->" + assert expected in actual, \ + f"expected generations to contain {expected}, but found {actual}" + + with subtest("Home Manager uninstallation"): + as_alice("yes | home-manager uninstall -L") + + as_alice("! hello") + machine.succeed("test ! -e /home/alice/.cache/.keep") + + machine.succeed("test ! -e /home/alice/.local/share/home-manager/gcroots") + machine.succeed("test ! -e /home/alice/.local/state/home-manager") + machine.succeed("test ! -e /home/alice/.local/state/nix/profiles/home-manager") + ''; +} diff --git a/tests/integration/standalone/standard-basics.nix b/tests/integration/standalone/standard-basics.nix new file mode 100644 index 00000000..6c514bfb --- /dev/null +++ b/tests/integration/standalone/standard-basics.nix @@ -0,0 +1,84 @@ +{ pkgs, ... }: + +{ + name = "standalone-standard-basics"; + meta.maintainers = [ pkgs.lib.maintainers.rycee ]; + + nodes.machine = { ... }: { + imports = [ "${pkgs.path}/nixos/modules/installer/cd-dvd/channel.nix" ]; + virtualisation.memorySize = 2048; + users.users.alice = { isNormalUser = true; }; + }; + + testScript = '' + start_all() + machine.wait_for_unit("network.target") + machine.wait_for_unit("multi-user.target") + + home_manager = "${../../..}" + + def as_alice(cmd): + return machine.succeed(f"su - alice -c '{cmd}'") + + # Set up a home-manager channel. + as_alice("mkdir -p /home/alice/.nix-defexpr/channels") + as_alice(f"ln -s {home_manager} /home/alice/.nix-defexpr/channels/home-manager") + + with subtest("Home Manager installation"): + as_alice("nix-shell \"\" -A install") + + actual = machine.succeed("ls /home/alice/.config/home-manager") + expected = "home.nix\n" + assert actual == expected, \ + f"unexpected content of /home/alice/.config/home-manager: {actual}" + + machine.succeed("diff -u ${ + ./alice-home-init.nix + } /home/alice/.config/home-manager/home.nix") + + # The default configuration creates this link on activation. + machine.succeed("test -L /home/alice/.cache/.keep") + + with subtest("GC root and profile"): + # There should be a GC root and Home Manager profile and they should point + # to the same path in the Nix store. + gcroot = "/home/alice/.local/state/home-manager/gcroots/current-home" + gcrootTarget = machine.succeed(f"readlink {gcroot}") + + profile = "/home/alice/.local/state/nix/profiles" + profileTarget = machine.succeed(f"readlink {profile}/home-manager") + profile1Target = machine.succeed(f"readlink {profile}/{profileTarget}") + + assert gcrootTarget == profile1Target, \ + f"expected GC root and profile to point to same, but pointed to {gcrootTarget} and {profile1Target}" + + with subtest("Home Manager switch"): + as_alice("cp ${ + ./alice-home-next.nix + } /home/alice/.config/home-manager/home.nix") + + as_alice("home-manager switch") + as_alice("hello") + + actual = as_alice("echo -n $EDITOR") + assert "emacs" == actual, \ + f"expected $EDITOR to contain emacs, but found {actual}" + + with subtest("Home Manager generations"): + actual = as_alice("home-manager generations") + expected = ": id 1 ->" + assert expected in actual, \ + f"expected generations to contain {expected}, but found {actual}" + + with subtest("Home Manager uninstallation"): + as_alice("yes | home-manager uninstall -L") + + as_alice("! hello") + machine.succeed("test ! -e /home/alice/.cache/.keep") + + # TODO: Fix uninstall to fully remove the directory. + machine.succeed("test ! -e /home/alice/.local/share/home-manager/gcroots") + machine.succeed("test ! -e /home/alice/.local/state/home-manager") + machine.succeed("test ! -e /home/alice/.local/state/nix/profiles/home-manager") + ''; +}