Skip to content

Commit bb1126d

Browse files
authored
feat(iii-worker): add cross-platform integration test suite and CI (#1449)
1 parent 5e6ad82 commit bb1126d

31 files changed

Lines changed: 4908 additions & 805 deletions

.config/nextest.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[profile.ci]
2+
fail-fast = false
3+
4+
[profile.ci.junit]
5+
path = "junit.xml"

.github/workflows/ci.yml

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,161 @@ jobs:
200200
- name: Build iii-worker for target
201201
run: cargo build -p iii-worker --target ${{ matrix.target }}
202202

203+
# ──────────────────────────────────────────────────────────────
204+
# Worker Test Matrix (cross-platform integration tests)
205+
# ──────────────────────────────────────────────────────────────
206+
207+
worker-test-matrix:
208+
name: Worker Tests - ${{ matrix.os }}
209+
runs-on: ${{ matrix.os }}
210+
strategy:
211+
fail-fast: false
212+
matrix:
213+
include:
214+
- os: ubuntu-latest
215+
- os: macos-latest
216+
steps:
217+
- uses: actions/checkout@v4
218+
219+
- uses: dtolnay/rust-toolchain@stable
220+
221+
- uses: Swatinem/rust-cache@v2
222+
with:
223+
key: worker-test-${{ matrix.os }}
224+
225+
- uses: taiki-e/install-action@cargo-nextest
226+
227+
- name: Install system dependencies (Linux only)
228+
if: matrix.os == 'ubuntu-latest'
229+
run: |
230+
sudo apt-get update
231+
sudo apt-get install -y libcap-ng-dev
232+
233+
- name: Run iii-worker tests
234+
run: cargo nextest run -p iii-worker --profile ci
235+
236+
- uses: actions/upload-artifact@v4
237+
if: always()
238+
with:
239+
name: junit-worker-test-${{ matrix.os }}
240+
path: target/nextest/ci/junit.xml
241+
retention-days: 7
242+
243+
# ──────────────────────────────────────────────────────────────
244+
# Worker Feature-Gated Tests
245+
# ──────────────────────────────────────────────────────────────
246+
247+
worker-test-vm-linux:
248+
name: Worker Tests (VM) - linux
249+
if: github.event_name == 'workflow_dispatch'
250+
runs-on: [self-hosted, linux, kvm]
251+
continue-on-error: true
252+
steps:
253+
- uses: actions/checkout@v4
254+
255+
- uses: dtolnay/rust-toolchain@stable
256+
257+
- uses: Swatinem/rust-cache@v2
258+
with:
259+
key: worker-test-vm-linux
260+
261+
- uses: taiki-e/install-action@cargo-nextest
262+
263+
- name: Install system dependencies
264+
run: |
265+
sudo apt-get update
266+
sudo apt-get install -y libcap-ng-dev
267+
268+
- name: Run VM feature-gated tests
269+
run: cargo nextest run -p iii-worker --features integration-vm --profile ci
270+
271+
- uses: actions/upload-artifact@v4
272+
if: always()
273+
with:
274+
name: junit-worker-test-vm-linux
275+
path: target/nextest/ci/junit.xml
276+
retention-days: 7
277+
278+
worker-test-vm-macos:
279+
name: Worker Tests (VM) - macos
280+
runs-on: macos-latest
281+
continue-on-error: true
282+
steps:
283+
- uses: actions/checkout@v4
284+
285+
- uses: dtolnay/rust-toolchain@stable
286+
287+
- uses: Swatinem/rust-cache@v2
288+
with:
289+
key: worker-test-vm-macos
290+
291+
- uses: taiki-e/install-action@cargo-nextest
292+
293+
- name: Run VM feature-gated tests
294+
run: cargo nextest run -p iii-worker --features integration-vm --profile ci
295+
296+
- uses: actions/upload-artifact@v4
297+
if: always()
298+
with:
299+
name: junit-worker-test-vm-macos
300+
path: target/nextest/ci/junit.xml
301+
retention-days: 7
302+
303+
worker-test-oci-linux:
304+
name: Worker Tests (OCI) - linux
305+
runs-on: ubuntu-latest
306+
continue-on-error: true
307+
steps:
308+
- uses: actions/checkout@v4
309+
310+
- uses: dtolnay/rust-toolchain@stable
311+
312+
- uses: Swatinem/rust-cache@v2
313+
with:
314+
key: worker-test-oci-linux
315+
316+
- uses: taiki-e/install-action@cargo-nextest
317+
318+
- name: Install system dependencies
319+
run: |
320+
sudo apt-get update
321+
sudo apt-get install -y libcap-ng-dev
322+
323+
- name: Run OCI feature-gated tests
324+
run: cargo nextest run -p iii-worker --features integration-oci --profile ci
325+
326+
- uses: actions/upload-artifact@v4
327+
if: always()
328+
with:
329+
name: junit-worker-test-oci-linux
330+
path: target/nextest/ci/junit.xml
331+
retention-days: 7
332+
333+
worker-test-oci-macos:
334+
name: Worker Tests (OCI) - macos
335+
runs-on: macos-latest
336+
continue-on-error: true
337+
steps:
338+
- uses: actions/checkout@v4
339+
340+
- uses: dtolnay/rust-toolchain@stable
341+
342+
- uses: Swatinem/rust-cache@v2
343+
with:
344+
key: worker-test-oci-macos
345+
346+
- uses: taiki-e/install-action@cargo-nextest
347+
348+
- name: Run OCI feature-gated tests
349+
run: cargo nextest run -p iii-worker --features integration-oci --profile ci
350+
351+
- uses: actions/upload-artifact@v4
352+
if: always()
353+
with:
354+
name: junit-worker-test-oci-macos
355+
path: target/nextest/ci/junit.xml
356+
retention-days: 7
357+
203358
# ──────────────────────────────────────────────────────────────
204359
# SDK Node
205360
# ──────────────────────────────────────────────────────────────

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/iii-worker/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ path = "src/main.rs"
1717
default = []
1818
embed-libkrunfw = []
1919
embed-init = ["iii-filesystem/embed-init"]
20+
# Test-only features — gate heavy integration tests (never in default)
21+
integration-vm = []
22+
integration-oci = []
2023

2124
[dependencies]
2225
iii-filesystem = { path = "../iii-filesystem" }

crates/iii-worker/src/cli/binary_download.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ pub fn current_target() -> &'static str {
5959
}
6060

6161
/// Returns the platform-appropriate archive extension.
62-
fn archive_extension(target: &str) -> &'static str {
62+
pub fn archive_extension(target: &str) -> &'static str {
6363
if target.contains("windows") {
6464
"zip"
6565
} else {
@@ -107,7 +107,10 @@ pub fn checksum_download_url(
107107
/// Extracts a named binary from a tar.gz archive.
108108
///
109109
/// Looks for an entry whose filename matches `binary_name` (ignoring directory prefixes).
110-
fn extract_binary_from_targz(binary_name: &str, archive_bytes: &[u8]) -> Result<Vec<u8>, String> {
110+
pub fn extract_binary_from_targz(
111+
binary_name: &str,
112+
archive_bytes: &[u8],
113+
) -> Result<Vec<u8>, String> {
111114
let decoder = flate2::read::GzDecoder::new(archive_bytes);
112115
let mut archive = tar::Archive::new(decoder);
113116

crates/iii-worker/src/cli/local_worker.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ pub fn parse_manifest_resources(manifest_path: &Path) -> (u32, u32) {
109109

110110
/// Remove workspace contents except installed dependency directories.
111111
/// This lets us re-copy source files without losing `npm install` artifacts.
112-
fn clean_workspace_preserving_deps(workspace: &Path) {
112+
pub fn clean_workspace_preserving_deps(workspace: &Path) {
113113
let preserve = ["node_modules", "target", ".venv", "__pycache__"];
114114
if let Ok(entries) = std::fs::read_dir(workspace) {
115115
for entry in entries.flatten() {

crates/iii-worker/src/cli/vm_boot.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ pub struct VmBootArgs {
6060
}
6161

6262
/// Compose the full libkrunfw file path from the resolved directory and platform filename.
63-
fn resolve_krunfw_file_path() -> Option<std::path::PathBuf> {
63+
pub fn resolve_krunfw_file_path() -> Option<std::path::PathBuf> {
6464
let dir = crate::cli::firmware::resolve::resolve_libkrunfw_dir()?;
6565
let filename = crate::cli::firmware::constants::libkrunfw_filename();
6666
let file_path = dir.join(&filename);
@@ -108,7 +108,7 @@ fn raise_fd_limit() {
108108
}
109109
}
110110

111-
fn shell_quote(s: &str) -> String {
111+
pub fn shell_quote(s: &str) -> String {
112112
if s.chars().all(|c| {
113113
c.is_alphanumeric() || c == '-' || c == '_' || c == '/' || c == '.' || c == ':' || c == '='
114114
}) {
@@ -118,7 +118,7 @@ fn shell_quote(s: &str) -> String {
118118
}
119119
}
120120

121-
fn build_worker_cmd(exec: &str, args: &[String]) -> String {
121+
pub fn build_worker_cmd(exec: &str, args: &[String]) -> String {
122122
if args.is_empty() {
123123
shell_quote(exec)
124124
} else {
@@ -130,6 +130,13 @@ fn build_worker_cmd(exec: &str, args: &[String]) -> String {
130130
}
131131
}
132132

133+
/// Rewrite localhost/loopback URLs to use the given gateway IP.
134+
/// Used by the VM boot process to redirect traffic into the guest network.
135+
pub fn rewrite_localhost(s: &str, gateway_ip: &str) -> String {
136+
s.replace("://localhost:", &format!("://{}:", gateway_ip))
137+
.replace("://127.0.0.1:", &format!("://{}:", gateway_ip))
138+
}
139+
133140
/// Boot the VM. Called from `main()` when `__vm-boot` is parsed.
134141
/// This function does NOT return -- `krun_start_enter` replaces the process.
135142
pub fn run(args: &VmBootArgs) -> ! {
@@ -229,10 +236,7 @@ fn boot_vm(args: &VmBootArgs) -> Result<std::convert::Infallible, String> {
229236
let guest_ip = network.guest_ipv4().to_string();
230237
let gateway_ip = network.gateway_ipv4().to_string();
231238

232-
let rewrite_localhost = |s: &str| -> String {
233-
s.replace("://localhost:", &format!("://{}:", gateway_ip))
234-
.replace("://127.0.0.1:", &format!("://{}:", gateway_ip))
235-
};
239+
let rewrite_localhost = |s: &str| -> String { rewrite_localhost(s, &gateway_ip) };
236240
let worker_cmd = rewrite_localhost(&worker_cmd);
237241

238242
let worker_heap_mib = (args.ram as u64 * 3 / 4).max(128);

crates/iii-worker/src/cli/worker_manager/libkrun.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -202,15 +202,15 @@ impl LibkrunAdapter {
202202
Self
203203
}
204204

205-
fn worker_dir(name: &str) -> PathBuf {
205+
pub fn worker_dir(name: &str) -> PathBuf {
206206
dirs::home_dir()
207207
.unwrap_or_else(|| PathBuf::from("/tmp"))
208208
.join(".iii")
209209
.join("managed")
210210
.join(name)
211211
}
212212

213-
fn image_rootfs(image: &str) -> PathBuf {
213+
pub fn image_rootfs(image: &str) -> PathBuf {
214214
let hash = {
215215
use sha2::Digest;
216216
let mut hasher = sha2::Sha256::new();
@@ -224,11 +224,11 @@ impl LibkrunAdapter {
224224
.join(hash)
225225
}
226226

227-
fn pid_file(name: &str) -> PathBuf {
227+
pub fn pid_file(name: &str) -> PathBuf {
228228
Self::worker_dir(name).join("vm.pid")
229229
}
230230

231-
fn logs_dir(name: &str) -> PathBuf {
231+
pub fn logs_dir(name: &str) -> PathBuf {
232232
Self::worker_dir(name).join("logs")
233233
}
234234

@@ -524,7 +524,7 @@ This image likely does not publish arm64. Rebuild/push a multi-arch image (linux
524524
}
525525
}
526526

527-
fn k8s_mem_to_mib(value: &str) -> Option<String> {
527+
pub fn k8s_mem_to_mib(value: &str) -> Option<String> {
528528
if let Some(n) = value.strip_suffix("Mi") {
529529
Some(n.to_string())
530530
} else if let Some(n) = value.strip_suffix("Gi") {

0 commit comments

Comments
 (0)