Skip to content

meta: initial VM tests setup#551

Open
NotAShelf wants to merge 2 commits intomasterfrom
vm-test-stuff
Open

meta: initial VM tests setup#551
NotAShelf wants to merge 2 commits intomasterfrom
vm-test-stuff

Conversation

@NotAShelf
Copy link
Member

@NotAShelf NotAShelf commented Feb 3, 2026

Supersedes #516

we test those

Summary by CodeRabbit

Release Notes

  • Tests

    • Added comprehensive remote build and deployment testing infrastructure across multiple systems and architectures.
    • Expanded CI/CD workflow with additional NixOS VM testing capabilities.
  • Chores

    • Updated workflow naming to clarify architecture targets for test jobs.

Partially borrowed from Nixpkgs

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Icae2926727255621cc1178488f7b65f06a6a6964
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I4fc35496a845d9a16179a20b831a22526a6a6964
@coderabbitai
Copy link

coderabbitai bot commented Feb 3, 2026

Walkthrough

The changes add remote build and deployment testing infrastructure by introducing a comprehensive NixOS VM test that validates SSH-based remote building across multiple nodes. The CI workflow is updated to include the new test, test SSH keys are defined, and flake.nix integrates the test derivation into the checks attribute.

Changes

Cohort / File(s) Summary
CI Workflow Updates
.github/workflows/test.yaml
Renames Linux and Darwin job names to include architecture suffixes (x86_64-linux, aarch64-darwin) and adds a new step to run NixOS VM tests for nh-remote-test.
Flake Configuration
flake.nix
Expands checks attribute to merge per-system derivations, adding a new nh-remote-test entry constructed via callPackage for system-specific remote test builds.
Test Infrastructure
test/ssh-keys.nix, test/vm/test-remote.nix
Introduces test SSH key pair (private EC key and public key string) and a comprehensive NixOS test file that orchestrates multi-node SSH-based remote building scenarios, including setup of deployer, target, and buildHost environments with extensive testScript logic for validation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: setting up initial NixOS VM tests, which is reflected across all modified files including the new test infrastructure (test/vm/test-remote.nix, test/ssh-keys.nix), flake configuration, and CI workflow updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch vm-test-stuff

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@flake.nix`:
- Around line 29-37: The current checks assignment uses the shallow merge
operator (//) which causes self.packages.${system} to be overwritten by the
genAttrs result and drops Linux package checks; update the merge to perform a
deep/recursive merge so nested keys are preserved — for example use
nixpkgs.lib.recursiveUpdate (or nixpkgs.lib.recursiveUpdateAttrs) to combine
self.packages and the genAttrs result that defines nh-remote-test, or refactor
to forAllSystems with conditional logic that adds nh-remote-test only for
non-Darwin systems; locate the checks assignment and replace the top-level //
merge of self.packages and the genAttrs block (the nh-remote-test definition)
with a recursiveUpdate-based merge or a forAllSystems conditional so nh and
default entries in self.packages.${system} are not lost.

In `@test/vm/test-remote.nix`:
- Around line 209-224: The remote subtests call deployer.succeed("nh os switch
... -f '<nixpkgs/nixos>'") but never create /root/configuration.nix on the
deployer; copy the appropriate prepared file to /root/configuration.nix on the
deployer before each remote deployer.succeed invocation (e.g. copy
configuration-2.nix before the subtest that expects config-2-deployed and
configuration-3.nix before the subtest that expects config-3-deployed). Use
deployer.succeed to perform the copy on the deployer host immediately prior to
the deployer.succeed("nh os switch ...") calls so the switch command finds
/root/configuration.nix.
🧹 Nitpick comments (2)
.github/workflows/test.yaml (1)

6-24: Workflow paths filter may be incomplete.

The paths filter references .github/workflows/build.yaml (line 13, 24) but this file is test.yaml. Additionally, changes to test files (test/**) won't trigger this workflow, which means modifications to test/vm/test-remote.nix or test/ssh-keys.nix won't automatically run the tests.

Consider updating the paths filter:

Suggested paths additions
     paths:
       - "Cargo.toml"
       - "Cargo.lock"
       - "xtask/Cargo.toml"
       - "src/**"
       - "xtask/src/**"
       - ".cargo/**"
-      - ".github/workflows/build.yaml"
+      - ".github/workflows/test.yaml"
+      - "test/**"

Apply to both pull_request and push sections.

test/vm/test-remote.nix (1)

13-16: Consider documenting the security tradeoff in the SSH config.

The SSH config disables host key verification, which is appropriate for ephemeral test VMs. However, the test also populates known_hosts (lines 176-177), making these settings partially redundant. Consider either:

  1. Removing known_hosts population since verification is disabled, or
  2. Removing the StrictHostKeyChecking=no since known_hosts is populated

The current approach works but is slightly inconsistent.

Comment on lines +29 to +37
checks =
self.packages
// self.devShells
// nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" ] (system: {
nh-remote-test = nixpkgs.legacyPackages.${system}.callPackage ./test/vm/test-remote.nix {
inherit (self.packages.${system}) nh;
inherit nixpkgs;
};
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Incorrect attribute set merge overwrites checks for Linux systems.

The // operator performs shallow merging at the top level (system keys), not a deep merge of the inner attributes. This means:

  • self.packages.x86_64-linux (containing nh, default) gets overwritten by the genAttrs result { nh-remote-test = ...; }
  • Linux systems lose their package checks entirely
  • Darwin systems won't have nh-remote-test (correct) but the merge still has issues

To properly merge nested attrsets, use lib.recursiveUpdate or restructure:

Proposed fix using recursiveUpdate
       checks =
-        self.packages
-        // self.devShells
-        // nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" ] (system: {
-          nh-remote-test = nixpkgs.legacyPackages.${system}.callPackage ./test/vm/test-remote.nix {
-            inherit (self.packages.${system}) nh;
-            inherit nixpkgs;
-          };
-        });
+        nixpkgs.lib.recursiveUpdate
+          (nixpkgs.lib.recursiveUpdate self.packages self.devShells)
+          (nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" ] (system: {
+            nh-remote-test = nixpkgs.legacyPackages.${system}.callPackage ./test/vm/test-remote.nix {
+              inherit (self.packages.${system}) nh;
+              inherit nixpkgs;
+            };
+          }));

Alternatively, use forAllSystems with conditional logic for a cleaner approach.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
checks =
self.packages
// self.devShells
// nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" ] (system: {
nh-remote-test = nixpkgs.legacyPackages.${system}.callPackage ./test/vm/test-remote.nix {
inherit (self.packages.${system}) nh;
inherit nixpkgs;
};
});
checks =
nixpkgs.lib.recursiveUpdate
(nixpkgs.lib.recursiveUpdate self.packages self.devShells)
(nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" ] (system: {
nh-remote-test = nixpkgs.legacyPackages.${system}.callPackage ./test/vm/test-remote.nix {
inherit (self.packages.${system}) nh;
inherit nixpkgs;
};
}));
🤖 Prompt for AI Agents
In `@flake.nix` around lines 29 - 37, The current checks assignment uses the
shallow merge operator (//) which causes self.packages.${system} to be
overwritten by the genAttrs result and drops Linux package checks; update the
merge to perform a deep/recursive merge so nested keys are preserved — for
example use nixpkgs.lib.recursiveUpdate (or nixpkgs.lib.recursiveUpdateAttrs) to
combine self.packages and the genAttrs result that defines nh-remote-test, or
refactor to forAllSystems with conditional logic that adds nh-remote-test only
for non-Darwin systems; locate the checks assignment and replace the top-level
// merge of self.packages and the genAttrs block (the nh-remote-test definition)
with a recursiveUpdate-based merge or a forAllSystems conditional so nh and
default entries in self.packages.${system} are not lost.

Comment on lines +209 to +224
with subtest("Remote build on deployer, deploy to target with --target-host"):
deployer.succeed(f"nh os switch --bypass-root-check -f '<nixpkgs/nixos>' --target-host root@{target_ip}")

# Verify hostname changed
target_hostname = target.succeed("cat /etc/hostname").strip()
assert target_hostname == "config-2-deployed", f"Expected 'config-2-deployed', got '{target_hostname}'"

# Build on buildHost, activate on target (both different from deployer)
with subtest("Remote build on buildHost with --build-host, deploy to target with --target-host"):
deployer.succeed(
f"nh os switch --bypass-root-check -f '<nixpkgs/nixos>' --build-host root@{build_host_ip} --target-host root@{target_ip}"
)

# Verify hostname changed
target_hostname = target.succeed("cat /etc/hostname").strip()
assert target_hostname == "config-3-deployed", f"Expected 'config-3-deployed', got '{target_hostname}'"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Missing configuration file setup for remote subtests.

The remote build subtests (lines 209-248) run nh os switch with -f '<nixpkgs/nixos>' on the deployer, which expects /root/configuration.nix to exist. However, only /root/configuration-{1,2,3}.nix files are copied to the deployer (lines 189-191), and /root/configuration.nix is never created on the deployer.

Each remote subtest should copy the appropriate configuration file to /root/configuration.nix on the deployer before running the switch command:

Proposed fix for the second subtest
     # Build on deployer, activate on target
     with subtest("Remote build on deployer, deploy to target with --target-host"):
+        deployer.succeed("cp /root/configuration-2.nix /root/configuration.nix")
         deployer.succeed(f"nh os switch --bypass-root-check -f '<nixpkgs/nixos>' --target-host root@{target_ip}")

Similar changes needed for the other remote subtests with the appropriate configuration file.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
with subtest("Remote build on deployer, deploy to target with --target-host"):
deployer.succeed(f"nh os switch --bypass-root-check -f '<nixpkgs/nixos>' --target-host root@{target_ip}")
# Verify hostname changed
target_hostname = target.succeed("cat /etc/hostname").strip()
assert target_hostname == "config-2-deployed", f"Expected 'config-2-deployed', got '{target_hostname}'"
# Build on buildHost, activate on target (both different from deployer)
with subtest("Remote build on buildHost with --build-host, deploy to target with --target-host"):
deployer.succeed(
f"nh os switch --bypass-root-check -f '<nixpkgs/nixos>' --build-host root@{build_host_ip} --target-host root@{target_ip}"
)
# Verify hostname changed
target_hostname = target.succeed("cat /etc/hostname").strip()
assert target_hostname == "config-3-deployed", f"Expected 'config-3-deployed', got '{target_hostname}'"
with subtest("Remote build on deployer, deploy to target with --target-host"):
deployer.succeed("cp /root/configuration-2.nix /root/configuration.nix")
deployer.succeed(f"nh os switch --bypass-root-check -f '<nixpkgs/nixos>' --target-host root@{target_ip}")
# Verify hostname changed
target_hostname = target.succeed("cat /etc/hostname").strip()
assert target_hostname == "config-2-deployed", f"Expected 'config-2-deployed', got '{target_hostname}'"
# Build on buildHost, activate on target (both different from deployer)
with subtest("Remote build on buildHost with --build-host, deploy to target with --target-host"):
deployer.succeed(
f"nh os switch --bypass-root-check -f '<nixpkgs/nixos>' --build-host root@{build_host_ip} --target-host root@{target_ip}"
)
# Verify hostname changed
target_hostname = target.succeed("cat /etc/hostname").strip()
assert target_hostname == "config-3-deployed", f"Expected 'config-3-deployed', got '{target_hostname}'"
🤖 Prompt for AI Agents
In `@test/vm/test-remote.nix` around lines 209 - 224, The remote subtests call
deployer.succeed("nh os switch ... -f '<nixpkgs/nixos>'") but never create
/root/configuration.nix on the deployer; copy the appropriate prepared file to
/root/configuration.nix on the deployer before each remote deployer.succeed
invocation (e.g. copy configuration-2.nix before the subtest that expects
config-2-deployed and configuration-3.nix before the subtest that expects
config-3-deployed). Use deployer.succeed to perform the copy on the deployer
host immediately prior to the deployer.succeed("nh os switch ...") calls so the
switch command finds /root/configuration.nix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant