NixOS flake checks

I am working on my first NixOS configuration and want to TDD each service as I bring it up.

I’ve put this together as a sample configuration (using this post for clues):

{
  inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
  outputs = { self, nixpkgs, ... }:
    let
      system = "x86_64-linux";
      hello-world-module = { pkgs, lib, ... }:
        let
          hello-world-server = pkgs.runCommand "hello-world-server" { } ''
            mkdir -p $out/{bin,/share/webroot}
            cat > $out/share/webroot/index.html <<EOF
            <html><head><title>Hello world</title></head><body><h1>Hello World!</h1></body></html>
            EOF
            cat > $out/bin/hello-world-server <<EOF
            #!${pkgs.runtimeShell}
            exec ${lib.getExe pkgs.python3} -m http.server 8000 --dir "$out/share/webroot"
            EOF
            chmod +x $out/bin/hello-world-server
          '';
        in
        {
          systemd.services.hello-world-server = {
            after = [ "network.target" ];
            wantedBy = [ "multi-user.target" ];
            serviceConfig = {
              DynamicUser = true;
              ExecStart = lib.getExe hello-world-server;
            };
          };
          services.getty.autologinUser = "root";
          virtualisation.vmVariant.virtualisation.graphics = false;
          boot.loader = {
            systemd-boot.enable = true;
            efi.canTouchEfiVariables = true;
          };
          nixpkgs.hostPlatform = system;
          system.stateVersion = "23.11";
        };
    in
    {
      checks.${system} = {
        check-hello-world =
          let
            pkgs = nixpkgs.legacyPackages.${system};
            inherit (pkgs) lib;
            nixos-lib = import (pkgs.path + "/nixos/lib") { };
          in
          # run test with `nix flake check`
          (nixos-lib.runTest {
            hostPkgs = pkgs;
            defaults.documentation.enable = lib.mkDefault false;
            node.specialArgs = { inherit self; };
            name = "check-hello-world";
            nodes.machine = { self, pkgs, ... }: {
              imports = [ hello-world-module ];
              environment.systemPackages = [ pkgs.curl ];
            };
            testScript = ''
              start_all()
              machine.wait_for_unit("hello-world-server")
              machine.wait_for_open_port(8000)
              output = machine.succeed("curl localhost:8000/index.html")
              assert "Hello world" in output, f"'{output}' does not contain 'Hello world'"
            '';
          }).config.result;
      };

      # XXX: how can i uncomment this so build-vm works without breaking `nix flake check`???
      # if I uncomment this, i can build/run the vm
      #
      ## run vm with `nix build '.#nixosConfigurations.demo.config.system.build.vm' && sudo ./result/bin/run-nixos-vm`
      #nixosConfigurations.demo = nixpkgs.lib.nixosSystem {
      #  inherit system;
      #  specialArgs = { inherit self nixpkgs; };
      #  modules = [ hello-world-module ];
      #};
    };
}

The problem is that nix flake check will only pass when nixosConfigurations is not defined. When it is defined, it fails with this error:

       … while calling 'fold''

         at /nix/store/a27vy9akqhg36njid77z4bckjdbm3iyw-source/lib/lists.nix:56:15:

           55|       len = length list;
           56|       fold' = n:
             |               ^
           57|         if n == len

       error:
       Failed assertions:
       - The ‘fileSystems’ option does not specify your root file system.

I suppose I could break this into two flakes, one for running the NixOS configuration and one for testing it, but that seems like I’ve missed something.

Any suggestions would be appreciated :pray:

You can successfully run the VM (and the checks too, since they also run on a VM) because VMs override the default filesystem with the VM’s filesystem. But on the raw configuration if you don’t have a filesystem defined NixOS won’t know how to mount things on boot.

Adding fileSystems."/" = { device = "/dev/sda1"; fsType = "ext4"; }; should do the trick for getting rid of the error, but make sure that this reflects your disk’s partitioning if you’re going to use this on real hardware. Check the docs if you need more info on this topic.

That worked! Thanks!

VMs override the default filesystem with the VM’s filesystem

I think it’s this causing the behavior.

    # By default, use mkVMOverride to enable building test VMs (e.g. via
    # `nixos-rebuild build-vm`) of a system configuration, where the regular
    # value for the `fileSystems' attribute should be disregarded (since those
    # filesystems don't necessarily exist in the VM). You can disable this
    # override by setting `virtualisation.fileSystems = lib.mkForce { };`.
    fileSystems = lib.mkIf (cfg.fileSystems != { }) (mkVMOverride cfg.fileSystems);

Thanks again!