diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 96fa2db..9ad34ce 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,7 +42,7 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Build - run: just build + run: just check && just build - name: Run unit tests run: just unit diff --git a/Justfile b/Justfile index c2ca1b9..fe3deb3 100644 --- a/Justfile +++ b/Justfile @@ -2,8 +2,10 @@ build: make +# Quick checks check: cargo t --workspace --no-run + cargo fmt --check # Run unit tests (excludes integration tests) unit *ARGS: diff --git a/crates/integration-tests/src/bin/cleanup.rs b/crates/integration-tests/src/bin/cleanup.rs index 98ecbf1..bea3c9c 100644 --- a/crates/integration-tests/src/bin/cleanup.rs +++ b/crates/integration-tests/src/bin/cleanup.rs @@ -1,7 +1,7 @@ use std::process::Command; -/// Label used to identify containers created by integration tests -const INTEGRATION_TEST_LABEL: &str = "bcvk.integration-test=1"; +// Import shared constants from the library +use integration_tests::{INTEGRATION_TEST_LABEL, LIBVIRT_INTEGRATION_TEST_LABEL}; fn cleanup_integration_test_containers() -> Result<(), Box> { println!("Cleaning up integration test containers..."); @@ -59,9 +59,63 @@ fn cleanup_integration_test_containers() -> Result<(), Box Result<(), Box> { + println!("Cleaning up integration test libvirt VMs..."); + + // Get path to bcvk binary (should be in the same directory as this cleanup binary) + let current_exe = std::env::current_exe()?; + let bcvk_path = current_exe + .parent() + .ok_or("Failed to get parent directory")? + .join("bcvk"); + + if !bcvk_path.exists() { + println!( + "bcvk binary not found at {:?}, skipping libvirt cleanup", + bcvk_path + ); + return Ok(()); + } + + // Use bcvk libvirt rm-all with label filter + let rm_output = Command::new(&bcvk_path) + .args([ + "libvirt", + "rm-all", + "--label", + LIBVIRT_INTEGRATION_TEST_LABEL, + "--force", + "--stop", + ]) + .output()?; + + if !rm_output.status.success() { + let stderr = String::from_utf8_lossy(&rm_output.stderr); + eprintln!("Warning: Failed to clean up libvirt VMs: {}", stderr); + return Ok(()); + } + + let stdout = String::from_utf8_lossy(&rm_output.stdout); + println!("{}", stdout); + + Ok(()) +} + fn main() { + let mut errors = Vec::new(); + if let Err(e) = cleanup_integration_test_containers() { - eprintln!("Error during cleanup: {}", e); + eprintln!("Error during container cleanup: {}", e); + errors.push(format!("containers: {}", e)); + } + + if let Err(e) = cleanup_libvirt_integration_test_vms() { + eprintln!("Error during libvirt VM cleanup: {}", e); + errors.push(format!("libvirt: {}", e)); + } + + if !errors.is_empty() { + eprintln!("Cleanup completed with errors: {}", errors.join(", ")); std::process::exit(1); } } diff --git a/crates/integration-tests/src/lib.rs b/crates/integration-tests/src/lib.rs new file mode 100644 index 0000000..e3bbe63 --- /dev/null +++ b/crates/integration-tests/src/lib.rs @@ -0,0 +1,10 @@ +//! Shared library code for integration tests +//! +//! This module contains constants and utilities that are shared between +//! the main test binary and helper binaries like cleanup. + +/// Label used to identify containers created by integration tests +pub const INTEGRATION_TEST_LABEL: &str = "bcvk.integration-test=1"; + +/// Label used to identify libvirt VMs created by integration tests +pub const LIBVIRT_INTEGRATION_TEST_LABEL: &str = "bcvk-integration"; diff --git a/crates/integration-tests/src/main.rs b/crates/integration-tests/src/main.rs index 0955b32..5668546 100644 --- a/crates/integration-tests/src/main.rs +++ b/crates/integration-tests/src/main.rs @@ -6,8 +6,8 @@ use libtest_mimic::{Arguments, Trial}; use serde_json::Value; use xshell::{cmd, Shell}; -/// Label used to identify containers created by integration tests -pub(crate) const INTEGRATION_TEST_LABEL: &str = "bcvk.integration-test=1"; +// Re-export constants from lib for internal use +pub(crate) use integration_tests::{INTEGRATION_TEST_LABEL, LIBVIRT_INTEGRATION_TEST_LABEL}; mod tests { pub mod libvirt_base_disks; @@ -205,6 +205,10 @@ fn main() { tests::libvirt_verb::test_libvirt_vm_lifecycle(); Ok(()) }), + Trial::test("libvirt_label_functionality", || { + tests::libvirt_verb::test_libvirt_label_functionality(); + Ok(()) + }), Trial::test("libvirt_error_handling", || { tests::libvirt_verb::test_libvirt_error_handling(); Ok(()) diff --git a/crates/integration-tests/src/tests/libvirt_verb.rs b/crates/integration-tests/src/tests/libvirt_verb.rs index 433b5e8..96b56df 100644 --- a/crates/integration-tests/src/tests/libvirt_verb.rs +++ b/crates/integration-tests/src/tests/libvirt_verb.rs @@ -9,7 +9,7 @@ use std::process::Command; -use crate::{get_bck_command, get_test_image}; +use crate::{get_bck_command, get_test_image, LIBVIRT_INTEGRATION_TEST_LABEL}; /// Test libvirt list functionality (lists domains) pub fn test_libvirt_list_functionality() { @@ -224,6 +224,8 @@ pub fn test_libvirt_run_ssh_full_workflow() { "run", "--name", &domain_name, + "--label", + LIBVIRT_INTEGRATION_TEST_LABEL, "--filesystem", "ext4", &test_image, @@ -416,6 +418,8 @@ pub fn test_libvirt_vm_lifecycle() { "ext4", "--name", &domain_name, + "--label", + LIBVIRT_INTEGRATION_TEST_LABEL, test_image, ]) .output() @@ -526,6 +530,8 @@ pub fn test_libvirt_bind_storage_ro() { "run", "--name", &domain_name, + "--label", + LIBVIRT_INTEGRATION_TEST_LABEL, "--bind-storage-ro", "--filesystem", "ext4", @@ -699,6 +705,144 @@ pub fn test_libvirt_bind_storage_ro() { println!("✓ --bind-storage-ro end-to-end test passed"); } +/// Test libvirt label functionality +pub fn test_libvirt_label_functionality() { + let bck = get_bck_command().unwrap(); + let test_image = get_test_image(); + + // Generate unique domain name for this test + let domain_name = format!( + "test-label-{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs() + ); + + println!( + "Testing libvirt label functionality with domain: {}", + domain_name + ); + + // Cleanup any existing domain with this name + let _ = Command::new("virsh") + .args(&["destroy", &domain_name]) + .output(); + let _ = Command::new(&bck) + .args(&["libvirt", "rm", &domain_name, "--force", "--stop"]) + .output(); + + // Create domain with multiple labels + println!("Creating libvirt domain with multiple labels..."); + let create_output = Command::new("timeout") + .args([ + "300s", + &bck, + "libvirt", + "run", + "--name", + &domain_name, + "--label", + LIBVIRT_INTEGRATION_TEST_LABEL, + "--label", + "test-env", + "--label", + "temporary", + "--filesystem", + "ext4", + &test_image, + ]) + .output() + .expect("Failed to run libvirt run with labels"); + + let create_stdout = String::from_utf8_lossy(&create_output.stdout); + let create_stderr = String::from_utf8_lossy(&create_output.stderr); + + println!("Create stdout: {}", create_stdout); + println!("Create stderr: {}", create_stderr); + + if !create_output.status.success() { + cleanup_domain(&domain_name); + panic!("Failed to create domain with labels: {}", create_stderr); + } + + println!("Successfully created domain with labels: {}", domain_name); + + // Verify labels are stored in domain XML + println!("Checking domain XML for labels..."); + let dumpxml_output = Command::new("virsh") + .args(&["dumpxml", &domain_name]) + .output() + .expect("Failed to dump domain XML"); + + let domain_xml = String::from_utf8_lossy(&dumpxml_output.stdout); + + // Check that labels are in the XML + assert!( + domain_xml.contains("bootc:label") || domain_xml.contains("