Skip to content

Commit 2a5df44

Browse files
committed
test(vm): add e2e integration tests for gateway binary
Two #[ignore] tests that require libkrun + pre-built rootfs: - gateway_boots_and_service_becomes_reachable: starts the full gateway and verifies the gRPC service on port 30051 - gateway_exec_runs_guest_command: runs /bin/true inside the VM via --exec and checks the exit code
1 parent 73aa16c commit 2a5df44

File tree

1 file changed

+132
-0
lines changed

1 file changed

+132
-0
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Integration tests for the standalone `gateway` binary.
5+
//!
6+
//! These tests require:
7+
//! - libkrun installed (e.g. `brew tap slp/krun && brew install libkrun`)
8+
//! - macOS ARM64 with Apple Hypervisor.framework
9+
//! - A pre-built rootfs at `~/.local/share/nemoclaw/gateway/rootfs`
10+
//!
11+
//! All tests are `#[ignore]` — run them explicitly:
12+
//!
13+
//! ```sh
14+
//! cargo test -p navigator-vm --test gateway_integration -- --ignored
15+
//! ```
16+
17+
#![allow(unsafe_code)]
18+
19+
use std::net::{SocketAddr, TcpStream};
20+
use std::process::{Command, Stdio};
21+
use std::time::{Duration, Instant};
22+
23+
/// Path to the built `gateway` binary (resolved by Cargo at compile time).
24+
const GATEWAY: &str = env!("CARGO_BIN_EXE_gateway");
25+
26+
// ── Helpers ────────────────────────────────────────────────────────────
27+
28+
/// Codesign the binary on macOS so it can access Hypervisor.framework.
29+
fn codesign_if_needed() {
30+
if cfg!(target_os = "macos") {
31+
let entitlements = format!("{}/entitlements.plist", env!("CARGO_MANIFEST_DIR"));
32+
let status = Command::new("codesign")
33+
.args([
34+
"--entitlements",
35+
&entitlements,
36+
"--force",
37+
"-s",
38+
"-",
39+
GATEWAY,
40+
])
41+
.status()
42+
.expect("codesign command failed to execute");
43+
assert!(status.success(), "failed to codesign gateway binary");
44+
}
45+
}
46+
47+
/// Build environment variables so libkrun can find libkrunfw at runtime.
48+
fn libkrun_env() -> Vec<(&'static str, String)> {
49+
if cfg!(target_os = "macos") {
50+
let homebrew_lib = Command::new("brew")
51+
.args(["--prefix"])
52+
.output()
53+
.ok()
54+
.and_then(|o| String::from_utf8(o.stdout).ok())
55+
.map(|s| format!("{}/lib", s.trim()))
56+
.unwrap_or_else(|| "/opt/homebrew/lib".to_string());
57+
58+
let existing = std::env::var("DYLD_FALLBACK_LIBRARY_PATH").unwrap_or_default();
59+
let val = if existing.is_empty() {
60+
homebrew_lib
61+
} else {
62+
format!("{homebrew_lib}:{existing}")
63+
};
64+
vec![("DYLD_FALLBACK_LIBRARY_PATH", val)]
65+
} else {
66+
vec![]
67+
}
68+
}
69+
70+
// ── Tests ──────────────────────────────────────────────────────────────
71+
72+
/// Boot the full NemoClaw gateway and verify the gRPC service becomes
73+
/// reachable on port 30051.
74+
#[test]
75+
#[ignore] // requires libkrun + rootfs
76+
fn gateway_boots_and_service_becomes_reachable() {
77+
codesign_if_needed();
78+
79+
let mut cmd = Command::new(GATEWAY);
80+
cmd.stdout(Stdio::null()).stderr(Stdio::piped());
81+
for (k, v) in libkrun_env() {
82+
cmd.env(k, v);
83+
}
84+
85+
let mut child = cmd.spawn().expect("failed to start gateway");
86+
87+
// Poll for the navigator gRPC service.
88+
let addr: SocketAddr = ([127, 0, 0, 1], 30051).into();
89+
let timeout = Duration::from_secs(180);
90+
let start = Instant::now();
91+
let mut reachable = false;
92+
93+
while start.elapsed() < timeout {
94+
if TcpStream::connect_timeout(&addr, Duration::from_secs(1)).is_ok() {
95+
reachable = true;
96+
break;
97+
}
98+
std::thread::sleep(Duration::from_secs(2));
99+
}
100+
101+
// Tear down regardless of result.
102+
let _ = unsafe { libc::kill(child.id() as i32, libc::SIGTERM) };
103+
let _ = child.wait();
104+
105+
assert!(
106+
reachable,
107+
"gateway service on port 30051 not reachable within {timeout:?}"
108+
);
109+
}
110+
111+
/// Run a trivial command inside the VM via `--exec` and verify it exits
112+
/// successfully, proving the VM boots and can execute guest processes.
113+
#[test]
114+
#[ignore] // requires libkrun + rootfs
115+
fn gateway_exec_runs_guest_command() {
116+
codesign_if_needed();
117+
118+
let mut cmd = Command::new(GATEWAY);
119+
cmd.args(["--exec", "/bin/true"]);
120+
for (k, v) in libkrun_env() {
121+
cmd.env(k, v);
122+
}
123+
124+
let output = cmd.output().expect("failed to run gateway --exec");
125+
126+
assert!(
127+
output.status.success(),
128+
"gateway --exec /bin/true failed with status {:?}\nstderr: {}",
129+
output.status,
130+
String::from_utf8_lossy(&output.stderr),
131+
);
132+
}

0 commit comments

Comments
 (0)