herolib-virt 0.3.13

Virtualization and container management for herolib (buildah, nerdctl, kubernetes)
Documentation
// File: /root/code/git.threefold.info/herocode/sal/src/virt/nerdctl/container_test.rs

#[cfg(test)]
mod tests {
    use super::super::container_types::Container;
    use std::process::Command;
    use std::thread;
    use std::time::Duration;

    // Helper function to check if nerdctl is available
    fn is_nerdctl_available() -> bool {
        match Command::new("which").arg("nerdctl").output() {
            Ok(output) => output.status.success(),
            Err(_) => false,
        }
    }

    #[test]
    fn test_container_builder_pattern() {
        // Skip test if nerdctl is not available
        if !is_nerdctl_available() {
            println!("Skipping test: nerdctl is not available");
            return;
        }

        // Create a container with builder pattern
        let container = Container::new("test-container")
            .unwrap()
            .with_port("8080:80")
            .with_volume("/tmp:/data")
            .with_env("TEST_ENV", "test_value")
            .with_detach(true);

        // Verify container properties
        assert_eq!(container.name, "test-container");
        assert_eq!(container.ports.len(), 1);
        assert_eq!(container.ports[0], "8080:80");
        assert_eq!(container.volumes.len(), 1);
        assert_eq!(container.volumes[0], "/tmp:/data");
        assert_eq!(container.env_vars.len(), 1);
        assert_eq!(container.env_vars.get("TEST_ENV").unwrap(), "test_value");
        assert_eq!(container.detach, true);
    }

    #[test]
    fn test_container_from_image() {
        // Skip test if nerdctl is not available
        if !is_nerdctl_available() {
            println!("Skipping test: nerdctl is not available");
            return;
        }

        // Create a container from image
        let container = Container::from_image("test-container", "alpine:latest").unwrap();

        // Verify container properties
        assert_eq!(container.name, "test-container");
        assert_eq!(container.image.as_ref().unwrap(), "alpine:latest");
    }

    #[test]
    fn test_container_health_check() {
        // Skip test if nerdctl is not available
        if !is_nerdctl_available() {
            println!("Skipping test: nerdctl is not available");
            return;
        }

        // Create a container with health check
        let container = Container::new("test-container")
            .unwrap()
            .with_health_check("curl -f http://localhost/ || exit 1");

        // Verify health check
        assert!(container.health_check.is_some());
        let health_check = container.health_check.unwrap();
        assert_eq!(health_check.cmd, "curl -f http://localhost/ || exit 1");
        assert!(health_check.interval.is_none());
        assert!(health_check.timeout.is_none());
        assert!(health_check.retries.is_none());
        assert!(health_check.start_period.is_none());
    }

    #[test]
    fn test_container_health_check_options() {
        // Skip test if nerdctl is not available
        if !is_nerdctl_available() {
            println!("Skipping test: nerdctl is not available");
            return;
        }

        // Create a container with health check options
        let container = Container::new("test-container")
            .unwrap()
            .with_health_check_options(
                "curl -f http://localhost/ || exit 1",
                Some("30s"),
                Some("10s"),
                Some(3),
                Some("5s"),
            );

        // Verify health check options
        assert!(container.health_check.is_some());
        let health_check = container.health_check.unwrap();
        assert_eq!(health_check.cmd, "curl -f http://localhost/ || exit 1");
        assert_eq!(health_check.interval.as_ref().unwrap(), "30s");
        assert_eq!(health_check.timeout.as_ref().unwrap(), "10s");
        assert_eq!(health_check.retries.unwrap(), 3);
        assert_eq!(health_check.start_period.as_ref().unwrap(), "5s");
    }

    #[test]
    #[ignore] // Ignore by default as it requires nerdctl to be installed and running
    fn test_container_runtime_and_resources() {
        // Check if nerdctl is available and properly configured
        let nerdctl_check = super::super::execute_nerdctl_command(&["info"]);
        if nerdctl_check.is_err() {
            println!("Skipping test: nerdctl is not available or properly configured");
            println!("Error: {:?}", nerdctl_check.err());
            return;
        }

        // Create a unique container name for this test
        let container_name = format!(
            "test-runtime-{}",
            std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap()
                .as_secs()
        );

        // Create and build a container that will use resources
        // Use a simple container with a basic command to avoid dependency on external images
        let container_result = Container::from_image(&container_name, "busybox:latest")
            .unwrap()
            .with_detach(true)
            .build();

        // Check if the build was successful
        if container_result.is_err() {
            println!("Failed to build container: {:?}", container_result.err());
            return;
        }

        let container = container_result.unwrap();
        println!("Container created successfully: {}", container_name);

        // Start the container with a simple command
        let start_result =
            container.exec("sh -c 'for i in $(seq 1 10); do echo $i; sleep 1; done'");
        if start_result.is_err() {
            println!("Failed to start container: {:?}", start_result.err());
            // Try to clean up
            let _ = container.remove();
            return;
        }

        println!("Container started successfully");

        // Wait for the container to start and consume resources
        thread::sleep(Duration::from_secs(3));

        // Check container status
        let status_result = container.status();
        if status_result.is_err() {
            println!("Failed to get container status: {:?}", status_result.err());
            // Try to clean up
            let _ = container.stop();
            let _ = container.remove();
            return;
        }

        let status = status_result.unwrap();
        println!("Container status: {:?}", status);

        // Verify the container is running
        if status.status != "running" {
            println!("Container is not running, status: {}", status.status);
            // Try to clean up
            let _ = container.remove();
            return;
        }

        // Check resource usage
        let resources_result = container.resources();
        if resources_result.is_err() {
            println!("Failed to get resource usage: {:?}", resources_result.err());
            // Try to clean up
            let _ = container.stop();
            let _ = container.remove();
            return;
        }

        let resources = resources_result.unwrap();
        println!("Container resources: {:?}", resources);

        // Verify the container is using memory (if we can get the information)
        if resources.memory_usage == "0B" || resources.memory_usage == "unknown" {
            println!(
                "Warning: Container memory usage is {}",
                resources.memory_usage
            );
        } else {
            println!("Container is using memory: {}", resources.memory_usage);
        }

        // Clean up - stop and remove the container
        println!("Stopping container...");
        let stop_result = container.stop();
        if stop_result.is_err() {
            println!("Warning: Failed to stop container: {:?}", stop_result.err());
        }

        println!("Removing container...");
        let remove_result = container.remove();
        if remove_result.is_err() {
            println!(
                "Warning: Failed to remove container: {:?}",
                remove_result.err()
            );
        }

        println!("Test completed successfully");
    }

    #[test]
    fn test_container_with_custom_command() {
        // Skip test if nerdctl is not available
        if !is_nerdctl_available() {
            println!("Skipping test: nerdctl is not available");
            return;
        }

        // Create a container with a custom command
        let container = Container::new("test-command-container")
            .unwrap()
            .with_port("8080:80")
            .with_volume("/tmp:/data")
            .with_env("TEST_ENV", "test_value")
            .with_detach(true);

        // Verify container properties
        assert_eq!(container.name, "test-command-container");
        assert_eq!(container.ports.len(), 1);
        assert_eq!(container.ports[0], "8080:80");
        assert_eq!(container.volumes.len(), 1);
        assert_eq!(container.volumes[0], "/tmp:/data");
        assert_eq!(container.env_vars.len(), 1);
        assert_eq!(container.env_vars.get("TEST_ENV").unwrap(), "test_value");
        assert_eq!(container.detach, true);

        // Convert the container to a command string that would be used to run it
        let command_args = container_to_command_args(&container);

        // Verify the command arguments contain all the expected options
        assert!(command_args.contains(&"--name".to_string()));
        assert!(command_args.contains(&"test-command-container".to_string()));
        assert!(command_args.contains(&"-p".to_string()));
        assert!(command_args.contains(&"8080:80".to_string()));
        assert!(command_args.contains(&"-v".to_string()));
        assert!(command_args.contains(&"/tmp:/data".to_string()));
        assert!(command_args.contains(&"-e".to_string()));
        assert!(command_args.contains(&"TEST_ENV=test_value".to_string()));
        assert!(command_args.contains(&"-d".to_string()));

        println!("Command args: {:?}", command_args);
    }

    // Helper function to convert a container to command arguments
    fn container_to_command_args(container: &Container) -> Vec<String> {
        let mut args = Vec::new();
        args.push("run".to_string());

        if container.detach {
            args.push("-d".to_string());
        }

        args.push("--name".to_string());
        args.push(container.name.clone());

        // Add port mappings
        for port in &container.ports {
            args.push("-p".to_string());
            args.push(port.clone());
        }

        // Add volume mounts
        for volume in &container.volumes {
            args.push("-v".to_string());
            args.push(volume.clone());
        }

        // Add environment variables
        for (key, value) in &container.env_vars {
            args.push("-e".to_string());
            args.push(format!("{}={}", key, value));
        }

        // Add image if available
        if let Some(image) = &container.image {
            args.push(image.clone());
        }

        args
    }
}