Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .config/nextest.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[profile.ci]
fail-fast = false

[profile.ci.junit]
path = "junit.xml"
155 changes: 155 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,161 @@ jobs:
- name: Build iii-worker for target
run: cargo build -p iii-worker --target ${{ matrix.target }}

# ──────────────────────────────────────────────────────────────
# Worker Test Matrix (cross-platform integration tests)
# ──────────────────────────────────────────────────────────────

worker-test-matrix:
name: Worker Tests - ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
- os: macos-latest
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2
with:
key: worker-test-${{ matrix.os }}

- uses: taiki-e/install-action@cargo-nextest

- name: Install system dependencies (Linux only)
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y libcap-ng-dev

- name: Run iii-worker tests
run: cargo nextest run -p iii-worker --profile ci

- uses: actions/upload-artifact@v4
if: always()
with:
name: junit-worker-test-${{ matrix.os }}
path: target/nextest/ci/junit.xml
retention-days: 7

# ──────────────────────────────────────────────────────────────
# Worker Feature-Gated Tests
# ──────────────────────────────────────────────────────────────

worker-test-vm-linux:
name: Worker Tests (VM) - linux
if: github.event_name == 'workflow_dispatch'
runs-on: [self-hosted, linux, kvm]
continue-on-error: true
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2
with:
key: worker-test-vm-linux

- uses: taiki-e/install-action@cargo-nextest

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libcap-ng-dev

- name: Run VM feature-gated tests
run: cargo nextest run -p iii-worker --features integration-vm --profile ci

- uses: actions/upload-artifact@v4
if: always()
with:
name: junit-worker-test-vm-linux
path: target/nextest/ci/junit.xml
retention-days: 7

worker-test-vm-macos:
name: Worker Tests (VM) - macos
runs-on: macos-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2
with:
key: worker-test-vm-macos

- uses: taiki-e/install-action@cargo-nextest

- name: Run VM feature-gated tests
run: cargo nextest run -p iii-worker --features integration-vm --profile ci

- uses: actions/upload-artifact@v4
if: always()
with:
name: junit-worker-test-vm-macos
path: target/nextest/ci/junit.xml
retention-days: 7

worker-test-oci-linux:
name: Worker Tests (OCI) - linux
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2
with:
key: worker-test-oci-linux

- uses: taiki-e/install-action@cargo-nextest

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libcap-ng-dev

- name: Run OCI feature-gated tests
run: cargo nextest run -p iii-worker --features integration-oci --profile ci

- uses: actions/upload-artifact@v4
if: always()
with:
name: junit-worker-test-oci-linux
path: target/nextest/ci/junit.xml
retention-days: 7

worker-test-oci-macos:
name: Worker Tests (OCI) - macos
runs-on: macos-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2
with:
key: worker-test-oci-macos

- uses: taiki-e/install-action@cargo-nextest

- name: Run OCI feature-gated tests
run: cargo nextest run -p iii-worker --features integration-oci --profile ci

- uses: actions/upload-artifact@v4
if: always()
with:
name: junit-worker-test-oci-macos
path: target/nextest/ci/junit.xml
retention-days: 7

# ──────────────────────────────────────────────────────────────
# SDK Node
# ──────────────────────────────────────────────────────────────
Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/iii-worker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ path = "src/main.rs"
default = []
embed-libkrunfw = []
embed-init = ["iii-filesystem/embed-init"]
# Test-only features — gate heavy integration tests (never in default)
integration-vm = []
integration-oci = []

[dependencies]
iii-filesystem = { path = "../iii-filesystem" }
Expand Down
7 changes: 5 additions & 2 deletions crates/iii-worker/src/cli/binary_download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ pub fn current_target() -> &'static str {
}

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

Expand Down
2 changes: 1 addition & 1 deletion crates/iii-worker/src/cli/local_worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub fn parse_manifest_resources(manifest_path: &Path) -> (u32, u32) {

/// Remove workspace contents except installed dependency directories.
/// This lets us re-copy source files without losing `npm install` artifacts.
fn clean_workspace_preserving_deps(workspace: &Path) {
pub fn clean_workspace_preserving_deps(workspace: &Path) {
let preserve = ["node_modules", "target", ".venv", "__pycache__"];
if let Ok(entries) = std::fs::read_dir(workspace) {
for entry in entries.flatten() {
Expand Down
18 changes: 11 additions & 7 deletions crates/iii-worker/src/cli/vm_boot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub struct VmBootArgs {
}

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

fn shell_quote(s: &str) -> String {
pub fn shell_quote(s: &str) -> String {
if s.chars().all(|c| {
c.is_alphanumeric() || c == '-' || c == '_' || c == '/' || c == '.' || c == ':' || c == '='
}) {
Expand All @@ -118,7 +118,7 @@ fn shell_quote(s: &str) -> String {
}
}

fn build_worker_cmd(exec: &str, args: &[String]) -> String {
pub fn build_worker_cmd(exec: &str, args: &[String]) -> String {
if args.is_empty() {
shell_quote(exec)
} else {
Expand All @@ -130,6 +130,13 @@ fn build_worker_cmd(exec: &str, args: &[String]) -> String {
}
}

/// Rewrite localhost/loopback URLs to use the given gateway IP.
/// Used by the VM boot process to redirect traffic into the guest network.
pub fn rewrite_localhost(s: &str, gateway_ip: &str) -> String {
s.replace("://localhost:", &format!("://{}:", gateway_ip))
.replace("://127.0.0.1:", &format!("://{}:", gateway_ip))
}

/// Boot the VM. Called from `main()` when `__vm-boot` is parsed.
/// This function does NOT return -- `krun_start_enter` replaces the process.
pub fn run(args: &VmBootArgs) -> ! {
Expand Down Expand Up @@ -229,10 +236,7 @@ fn boot_vm(args: &VmBootArgs) -> Result<std::convert::Infallible, String> {
let guest_ip = network.guest_ipv4().to_string();
let gateway_ip = network.gateway_ipv4().to_string();

let rewrite_localhost = |s: &str| -> String {
s.replace("://localhost:", &format!("://{}:", gateway_ip))
.replace("://127.0.0.1:", &format!("://{}:", gateway_ip))
};
let rewrite_localhost = |s: &str| -> String { rewrite_localhost(s, &gateway_ip) };
let worker_cmd = rewrite_localhost(&worker_cmd);

let worker_heap_mib = (args.ram as u64 * 3 / 4).max(128);
Expand Down
10 changes: 5 additions & 5 deletions crates/iii-worker/src/cli/worker_manager/libkrun.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,15 +202,15 @@ impl LibkrunAdapter {
Self
}

fn worker_dir(name: &str) -> PathBuf {
pub fn worker_dir(name: &str) -> PathBuf {
dirs::home_dir()
.unwrap_or_else(|| PathBuf::from("/tmp"))
.join(".iii")
.join("managed")
.join(name)
}

fn image_rootfs(image: &str) -> PathBuf {
pub fn image_rootfs(image: &str) -> PathBuf {
let hash = {
use sha2::Digest;
let mut hasher = sha2::Sha256::new();
Expand All @@ -224,11 +224,11 @@ impl LibkrunAdapter {
.join(hash)
}

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

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

Expand Down Expand Up @@ -524,7 +524,7 @@ This image likely does not publish arm64. Rebuild/push a multi-arch image (linux
}
}

fn k8s_mem_to_mib(value: &str) -> Option<String> {
pub fn k8s_mem_to_mib(value: &str) -> Option<String> {
if let Some(n) = value.strip_suffix("Mi") {
Some(n.to_string())
} else if let Some(n) = value.strip_suffix("Gi") {
Expand Down
Loading
Loading