Skip to content

nvx-rs: share the sandbox tests' policy and native helpers - #755

Merged
Pedro Henrique Penna (ppenna) merged 1 commit into
devfrom
nvx-rs-quality/sandbox-test-helpers
Oct 22, 2026
Merged

Pedro Henrique Penna (ppenna) merged 1 commit into
devfrom
nvx-rs-quality/sandbox-test-helpers

Conversation

@ppenna

Copy link
Copy Markdown
Contributor

Summary

The launch-profile tests in nvx-rs/src/host/sandbox/tests.rs and the share tests in nvx-rs/src/host/sandbox/mount/tests.rs each defined the same two helpers, word for word:

fn policy(denied: &[&str], allowed: &[&str], writable: &[&str]) -> MountPolicy {
    let owned = |paths: &[&str]| paths.iter().map(|&path| path.to_owned()).collect();
    MountPolicy {
        denied: owned(denied),
        allowed: owned(allowed),
        writable: owned(writable),
    }
}

fn native(path: &str) -> String {
    PurePath::native(path).as_str().to_owned()
}

So a change to MountPolicy, or to how the tests render host paths, had two copies to keep alike. This pull request keeps the launch tests' copies, and the share tests import them, so each helper has one definition. #683, #684, #689, and #705 listed this duplication among their follow-ups.

Changes

  • nvx-rs/src/host/sandbox/tests.rs: policy and native become pub(super), which makes them visible in host::sandbox and the modules below it, with doc comments that say what they return and that the share tests use them.
  • nvx-rs/src/host/sandbox/mount/tests.rs: its copies are gone, and use crate::host::sandbox::tests::{native, policy}; imports the shared ones. The share tests reach the launch tests' module from below host::sandbox, so neither host/sandbox/mod.rs nor host/sandbox/mount.rs, which open pull requests change, changes.
  • The sites that now share the helpers:
    • policy: in the launch tests, their mount helper, launch_contract_attaches_a_file_as_an_aggregate_child, and launch_validation_rejects_overlapping_or_foreign_host_paths; in the share tests, mount_parser_defaults_to_read_only_and_accepts_rw, mount_parser_accepts_host_paths_with_commas, mount_owner_defaults_to_vmm_and_forwards_caller, mount_access_policy_is_validated_and_forwarded, and mount_absolute_path_keeps_symbolic_link_components;
    • native: launch_validation_requires_the_layers_and_the_scratch_image in the launch tests, and mount_parser_rejects_malformed_specifications and mount_parser_accepts_host_paths_with_commas in the share tests.

6 insertions and 15 deletions.

Behavior

Unchanged:

  • Only test code changes. No library item, public or private, changes, and neither does any protocol byte, golden fixture, or guest program's command line, exit status, or output; the library and the executables build from the same code as before.
  • Every caller runs the same function body with the same arguments, so it builds the same MountPolicy values and paths and makes the same assertions. No test is added, removed, skipped, or weakened: the launch tests and the share tests still run 15 and 10 tests in each feature set.
  • pub(super) widens the helpers' visibility only inside the crate's test build, to host::sandbox and the modules below it.

Follow-ups

Not in this pull request, to keep it to one change. I found no suspected bug.

  • net::is_valid_guest_network and guest_subnet in host/network/mod.rs each parse an IPv4 ADDRESS/PREFIX and derive its network, with prefix limits of /1 to /30 and /1 to /32. This run first prepared a change that shared that parsing, and dropped it when nvx-rs: lower sandbox networks through one typed policy with a proxy-only mode #753, opened during the run, began to change both files. nvx-rs: lower sandbox networks through one typed policy with a proxy-only mode #753 moves the first parse into a guest_address helper that also checks the host part; a parser that both could share once nvx-rs: lower sandbox networks through one typed policy with a proxy-only mode #753 lands would take the prefix limit and leave that check to is_valid_guest_network.
  • host/python/dumps.rs: write_value's list and dict arms repeat the bracket, comma, and indentation loop, and their empty forms. This run also drafted one container writer for both, which passed the crate's tests but added 16 lines net, so it chose this smaller change.
  • guest/time_probe/clock.rs: run prints its two kernel-log sections around print_kmsg with the same ten-line match; print_kmsg could print its whole section and return its counters. No test checks those sections' text.
  • The message test helpers of host/sandbox/mount/tests.rs and host/sandbox/options/tests.rs differ only in the message of their error-kind assertion, but the launch tests' message, which asserts no error kind, has the same name, so sharing them needs a name of its own.
  • The other follow-ups of earlier runs remain, among them the kernel_address test helper of guest/ingress/address.rs and collect.rs, the bounded merges of ListenerTable::add and note_wildcard_tcp, the time probe's TSC-rate checks, and the hex test helpers of protocol/tests.rs and protocol/nvxs/tests.rs.

Validation

  • BASE: 2817f4eb79911ef8380dc2b580b9d5c77e6b5b21, origin/dev when the run started and when it committed. No commit since then and no open pull request to dev changes either file.

  • TREE: e0435a8ed2b1ee79d0fe15fe919c312a57c0ca77, the tree of this commit. Every host applied the patch to BASE with git apply --index and checked that git write-tree equals TREE.

  • Release on every host: v0.1.0-dev.e714acbd47bf, which download chose 2 first-parent commits before BASE, since BASE, a [skip ci] baseline update, has no release. Its OpenVMM, 9f04d05c5441a9b31f7f81effa0ce7c4fc66a0d3, is the pinned revision, as each host checked in build/openvmm.provenance.json.

  • nvx-rs: each host deleted the nvx-rs executable that nvx.py runs, which download may install from the release, rebuilt it from TREE with the Cargo command with which nvx.py builds it, and recorded its provenance with record-nvx-rs-provenance. Each host checked that the record names TREE's sources (source_sha256 6f7b3859b4d07785b4164287aba8c6c57bf16a246442c65bb6a97c2c88b59d1c, which this machine also computes for TREE) and the new executable, and then built the openvmm_e2e and managed_lifecycle test binaries, after its other Cargo work and before its hypervisor tests.

  • Guest artifacts were rebuilt from TREE on prometheus30, whose user namespaces let Alpine's apk run its scripts (prometheus32 denies them):

    • build-guest-binaries --output, nine static executables;
    • test-guest-binaries --binaries, where 34 tests passed;
    • the Alpine build-initramfs with NVX_GUEST_BINARIES.

    initramfs.cpio.gz (SHA-256 51fd870d670a1f58b54c76d7efb95f9b5756b499b0323682c8469f566fa37ae1), its packages manifest (1ab07b6c71dccafa627732ed95d6c58717e9dd161eb583469862183ffa0ba572), and its provenance (15d5df20422a7a86c3cb6c3b2252b92bf582432c18633eddf3d4bc9ff8147444) went through this machine to the other two hosts, where the SHA-256 digests matched. The 126 source files that the provenance records match TREE's; the two changed test files are not among the guest's sources.

  • Local checks with Rust 1.95.0 on Windows: rustfmt; Clippy with -D warnings on all targets with all features and on the library without default features; and cargo test with all features (507 tests in 12 test targets) and with the default ones (430 in 10).

  • This machine also ran the steps for which the hosts lack the toolchain or target: on Windows, the MSRV (1.89) checks of nvx, aci_edge_sandboxes, and the runtime adapter's protocol-only and host-only configurations; and, on WSL Ubuntu, the macOS (aarch64-apple-darwin) checks of nvx and aci_edge_sandboxes and the MSRV check of the runtime adapter's guest-only configuration, which ran as the action defines it, starting with its rustup target add, which gave the local 1.89 the musl target. I removed that target again afterwards.

  • The change adds, moves, and deletes no file and changes no Markdown, so the CLI tests of validate-nvx and the link check had nothing to cover.

  • The hosts ran each action's steps with the toolchains and targets that they have, with RUSTUP_AUTO_INSTALL=0 and without the actions' installation commands. On prometheus28, the artifact-bundling step staged its synthetic release with PowerShell instead of the step's bash script, with the same files and the same digests and paths in SHA256SUMS.

  • Earlier in the run, the hosts went through these steps for the change that this run dropped (see Follow-ups). Every host was then prepared again at BASE, and every result below is TREE's.

Host profile Backend Host type check-nvx-rs and check-aci-edge-sandboxes without a hypervisor Guest artifacts test-aci-edge-sandboxes test-microvm, default suite
prometheus32 kvm baremetal Pass: every Linux step, 29 commands, including the MSRV (1.89) checks of nvx, aci_edge_sandboxes, and the runtime adapter's protocol-only and host-only configurations; nvx tests: 946 in 39 test targets with all features and 431 in 13 with the default ones; artifact bundling (15); check-guest-binaries (530 in 32 test targets); the runtime adapter's protocol-only, host-only, and musl guest-only configurations (13, 14, and 21 tests); 158 and 85 aci_edge_sandboxes tests over the two feature sets; agent bundling (4). The macOS checks and the guest-only MSRV check ran locally Copied in, digests match Pass (18/18) Pass (42 scenario runs)
prometheus30 mshv baremetal None; prometheus32 and prometheus28 ran the gates Built and tested here (34 tests) Pass (18/18) Pass (42 scenario runs)
prometheus28 whp baremetal Pass: every Windows step, 21 commands; nvx tests: 507 in 12 test targets with all features and 430 in 10 with the default ones; artifact bundling (15); the runtime adapter's protocol-only and host-only configurations (13 and 14 tests); 158 and 85 aci_edge_sandboxes tests over the two feature sets; agent bundling (4). The MSRV checks ran locally Copied in, digests match Pass (18/18) Pass (43 scenario runs)

Every gate and hypervisor test passed at its first run on TREE. Each suite's managed-lifecycle scenario ran the managed_lifecycle test with Cargo, which passed. The time-ABI evidence lines counted no boot, capture, or restore over its CPU-time budget on any host, and the exhaustive check reported ok/8/0 on each. On every host, one openvmm_e2e test printed skipped: the host has no second volume for a check that needs one, and on the Linux hosts the filesystem caller-ownership scenario reported that OpenVMM cannot assume other host identities, so it checked that guest root and foreign guest callers fail with EPERM; both are properties of the hosts, not of this change.

Left to CI:

  • the OpenVMM unit and VMM suites, test-openvmm-unit and test-openvmm, since OpenVMM and the guest's sources are unchanged;
  • the test-microvm scenarios that run only when named: smp-lapic, and managed-exec-config and sandbox-lifecycle, which need artifacts that packages omit;
  • the debug-kernel scenarios, the Ubuntu and Azure Linux guests, the Docker guest build, and verify-guest-determinism;
  • the benchmarks and performance gates;
  • the virtual-machine runners;
  • check-quality and validate-nvx, which no Python, shell, or Markdown change touches.

The launch tests in host/sandbox/tests.rs and the share tests in
host/sandbox/mount/tests.rs each defined the same two helpers, word for
word: policy, which builds the MountPolicy of a share from the paths
that it hides, exposes again, and lets the workload modify, and native,
which renders a path as str(pathlib.Path(path)) does on the host. So a
change to MountPolicy, or to how the tests render host paths, had two
copies to keep alike.

The share tests now import both helpers from the launch tests, whose
module they can reach from below host::sandbox, so each helper has one
definition. The launch tests' copies become pub(super), with doc
comments that say what they return and that the share tests use them.
Neither host/sandbox/mod.rs nor host/sandbox/mount.rs changes.

Behavior is unchanged: only test code changes, and every test calls
the same function bodies with the same arguments, so it builds the
same policies and paths and makes the same assertions. No library item
changes, and no test is added, removed, or weakened.

The helpers' callers, the launch tests' mount helper, three launch
tests, and six share tests, run in the crate's tests with the default
features and with all of them, on Linux and on Windows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 09:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The narrow test-only refactor preserves helper behavior and callers, with compatible visibility and test configuration.

No open issues 🎉

What changed in this PR

This PR removes duplicated sandbox test helpers in nvx-rs, giving the launch-profile and mount tests one shared implementation without changing production code or test behavior.

Changes:

  • Makes the existing policy and native helpers visible within the sandbox module and documents their shared use.
  • Replaces the mount tests’ identical helper definitions with imports from the launch-profile test module.
File Description
nvx-rs/​src/​host/​sandbox/​tests.rs Exposes and documents the shared test helpers, preserving their bodies.
nvx-rs/​src/​host/​sandbox/​mount/​tests.rs Imports the shared helpers and removes duplicate definitions.

Review effort: Balanced

@ppenna
Pedro Henrique Penna (ppenna) merged commit bcf7d19 into dev Oct 22, 2026
31 checks passed
@ppenna
Pedro Henrique Penna (ppenna) deleted the nvx-rs-quality/sandbox-test-helpers branch October 22, 2026 19:59
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.

2 participants