1
0
Fork 0
mirror of https://github.com/imjasonh/krust synced 2026-07-08 14:55:35 +00:00
krust/tests/integration_test.rs

465 lines
15 KiB
Rust
Raw Normal View History

use anyhow::Result;
use assert_cmd::Command;
use predicates::prelude::*;
use std::env;
use std::process::Command as StdCommand;
// Helper to get the appropriate test platform based on runtime architecture
fn get_test_platform() -> &'static str {
// In CI, test the native platform
if env::var("CI").is_ok() {
if cfg!(target_arch = "x86_64") {
"linux/amd64"
} else if cfg!(target_arch = "aarch64") {
"linux/arm64"
} else {
"linux/amd64"
}
} else {
// For local development, always use amd64 as it's most commonly available
"linux/amd64"
}
}
#[test]
fn test_version_command() -> Result<()> {
let mut cmd = Command::cargo_bin("krust")?;
cmd.arg("--version");
cmd.assert()
.success()
.stdout(predicate::str::contains("krust 0.1.0"));
Ok(())
}
#[test]
fn test_version_subcommand() -> Result<()> {
let mut cmd = Command::cargo_bin("krust")?;
cmd.arg("version");
cmd.assert()
.success()
.stdout(predicate::str::contains("krust 0.1.0"));
Ok(())
}
#[test]
fn test_help_command() -> Result<()> {
let mut cmd = Command::cargo_bin("krust")?;
cmd.arg("--help");
cmd.assert().success().stdout(predicate::str::contains(
"A container image build tool for Rust applications",
));
Ok(())
}
#[test]
fn test_build_help() -> Result<()> {
let mut cmd = Command::cargo_bin("krust")?;
cmd.arg("build").arg("--help");
cmd.assert().success().stdout(predicate::str::contains(
"Build a container image from a Rust application",
));
Ok(())
}
#[test]
fn test_build_requires_repo_or_image() -> Result<()> {
// Use the example directory
let example_dir = env::current_dir()?.join("example").join("hello-krust");
// Run krust build without KRUST_REPO or --image
let mut cmd = Command::cargo_bin("krust")?;
cmd.arg("build")
.arg("--no-push")
.arg(".") // Explicitly pass current directory
.current_dir(&example_dir)
.env_remove("KRUST_REPO");
cmd.assert().failure().stderr(predicate::str::contains(
"Either --image or KRUST_REPO must be set",
));
Ok(())
}
#[test]
fn test_build_with_krust_repo_env() -> Result<()> {
// Use the example directory
let example_dir = env::current_dir()?.join("example").join("hello-krust");
// Run krust build with KRUST_REPO env var
let mut cmd = Command::cargo_bin("krust")?;
cmd.arg("build")
.arg("--no-push")
.arg("--platform")
.arg(get_test_platform())
.arg(".") // Explicitly pass current directory
.env("KRUST_REPO", "test.local")
.current_dir(&example_dir);
cmd.assert()
.success()
.stderr(predicate::str::contains("Building Rust project"))
.stderr(predicate::str::contains(
"Successfully built image for 1 platform(s)",
));
Ok(())
}
#[test]
fn test_command_substitution_syntax() -> Result<()> {
// Get the example project directory
let example_dir = env::current_dir()?.join("example").join("hello-krust");
// Test that output is clean for command substitution
let mut cmd = Command::cargo_bin("krust")?;
let output = cmd
.arg("build")
.arg("--no-push")
.arg("--platform")
.arg(get_test_platform())
.arg("--image")
.arg("test.local/hello:latest")
.arg(".") // Explicitly pass current directory
.current_dir(&example_dir)
.output()?;
// Stdout should be empty when --no-push is used
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.trim().is_empty(),
"Stdout should be empty with --no-push"
);
// Stderr should contain log messages
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("Building Rust project"));
assert!(stderr.contains("Successfully built image"));
Ok(())
}
#[test]
fn test_verbose_logging() -> Result<()> {
let example_dir = env::current_dir()?.join("example").join("hello-krust");
let mut cmd = Command::cargo_bin("krust")?;
cmd.arg("--verbose")
.arg("build")
.arg("--no-push")
.arg("--platform")
.arg(get_test_platform())
.arg("--image")
.arg("test.local/hello:latest")
.arg(".") // Explicitly pass current directory
.current_dir(&example_dir);
cmd.assert()
.success()
.stderr(predicate::str::contains("DEBUG"));
Ok(())
}
#[test]
fn test_full_build_and_run_workflow() -> Result<()> {
// This test requires Docker
let docker_check = StdCommand::new("docker").arg("version").output();
match docker_check {
Ok(output) if output.status.success() => {
// Docker is available, proceed with test
}
_ => {
// Docker not available or not working
panic!("Docker is required for this test but is not available");
}
}
// Get the example project directory
let example_dir = env::current_dir()?.join("example").join("hello-krust");
// Build and push to ttl.sh
// For the full workflow test, build for the actual native platform so Docker can run it
let native_platform = if cfg!(target_arch = "aarch64") {
"linux/arm64"
} else {
"linux/amd64"
};
let mut cmd = Command::cargo_bin("krust")?;
let output = cmd
.arg("build")
.arg("--platform")
.arg(native_platform)
.arg(".") // Explicitly pass current directory
.env("KRUST_REPO", "ttl.sh/krust-test")
.current_dir(&example_dir)
.output()?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
panic!("Build failed: {}", stderr);
}
// Get the image reference from stdout
let image_ref = String::from_utf8_lossy(&output.stdout).trim().to_string();
assert!(image_ref.starts_with("ttl.sh/krust-test/hello-krust@sha256:"));
// Try to run the image
let docker_output = StdCommand::new("docker")
.args(&["run", "--rm", &image_ref])
.output()?;
if !docker_output.status.success() {
let stderr = String::from_utf8_lossy(&docker_output.stderr);
panic!("Docker run failed with image {}: {}", image_ref, stderr);
}
let docker_stdout = String::from_utf8_lossy(&docker_output.stdout);
assert!(docker_stdout.contains("Hello from krust example!"));
Ok(())
}
#[test]
fn test_single_platform_build() -> Result<()> {
let example_dir = env::current_dir()?.join("example").join("hello-krust");
let platform = get_test_platform();
let mut cmd = Command::cargo_bin("krust")?;
cmd.arg("build")
.arg("--no-push")
.arg("--platform")
.arg(platform)
.arg("--image")
.arg("test.local/single-platform:latest")
.arg(".")
.current_dir(&example_dir);
cmd.assert()
.success()
.stderr(predicate::str::contains(format!(
"Building for platform: {}",
platform
)))
.stderr(predicate::str::contains(
"Successfully built image for 1 platform(s)",
));
Ok(())
}
#[test]
fn test_multi_platform_build() -> Result<()> {
let example_dir = env::current_dir()?.join("example").join("hello-krust");
let mut cmd = Command::cargo_bin("krust")?;
// Test building for multiple platforms
// This will use whatever targets are available
cmd.arg("build")
.arg("--no-push")
.arg("--platform")
.arg("linux/amd64,linux/arm64")
.arg("--image")
.arg("test.local/multi-platform:latest")
.arg(".")
.current_dir(&example_dir);
// The command might fail if targets aren't installed, but we should test that it tries
let output = cmd.output()?;
if output.status.success() {
// If it succeeds, verify we built for multiple platforms
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(stderr.contains("Building for platform: linux/amd64"));
assert!(stderr.contains("Building for platform: linux/arm64"));
assert!(stderr.contains("Successfully built image for 2 platform(s)"));
} else {
// If it fails, it should be because of missing targets
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("target may not be installed")
|| stderr.contains("linker")
|| stderr.contains("cross-compilation")
|| stderr.contains("not found"),
"Build failed for unexpected reason: {}",
stderr
);
}
Ok(())
}
#[test]
fn test_multi_arch_build_and_run() -> Result<()> {
// This test requires Docker
let docker_check = StdCommand::new("docker").arg("version").output();
match docker_check {
Ok(output) if output.status.success() => {
// Docker is available, proceed with test
}
_ => {
eprintln!("Docker is required for this test but is not available");
return Ok(());
}
}
let example_dir = env::current_dir()?.join("example").join("hello-krust");
// Build multi-arch image and push to ttl.sh
let mut cmd = Command::cargo_bin("krust")?;
let output = cmd
.arg("build")
.arg("--platform")
.arg("linux/amd64,linux/arm64")
.arg(".")
.env("KRUST_REPO", "ttl.sh/krust-multiarch-test")
.current_dir(&example_dir)
.output()?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
// If it fails due to missing targets, that's expected in some environments
if stderr.contains("target may not be installed")
|| stderr.contains("linker")
|| stderr.contains("cross-compilation")
|| stderr.contains("not found")
{
eprintln!("Skipping multi-arch run test - build failed due to missing toolchain");
return Ok(());
}
panic!("Build failed unexpectedly: {}", stderr);
}
// Get the image reference from stdout
let image_ref = String::from_utf8_lossy(&output.stdout).trim().to_string();
assert!(image_ref.starts_with("ttl.sh/krust-multiarch-test/hello-krust"));
// Try to run the image - it should work on the current architecture
let docker_output = StdCommand::new("docker")
.args(&["run", "--rm", &image_ref])
.output()?;
assert!(docker_output.status.success(), "Docker run failed");
let docker_stdout = String::from_utf8_lossy(&docker_output.stdout);
assert!(docker_stdout.contains("Hello from krust example!"));
Ok(())
}
#[test]
fn test_platform_detection_from_base_image() -> Result<()> {
let example_dir = env::current_dir()?.join("example").join("hello-krust");
let mut cmd = Command::cargo_bin("krust")?;
cmd.arg("build")
.arg("--no-push")
.arg("--image")
.arg("test.local/platform-detection:latest")
.arg(".")
.current_dir(&example_dir)
.env("RUST_LOG", "info");
// The default base image (cgr.dev/chainguard/static:latest) supports multiple platforms
// so we should see platform detection happening
let output = cmd.output()?;
let stderr = String::from_utf8_lossy(&output.stderr);
// We should always see platform detection
assert!(stderr.contains("Detecting available platforms from base image"));
assert!(stderr.contains("Detected platforms"));
// The build might fail if we don't have all cross-compilation toolchains
// (e.g., on ARM runners trying to build for x86_64)
if !output.status.success() {
assert!(
stderr.contains("linker")
|| stderr.contains("target may not be installed")
|| stderr.contains("cross-compilation"),
"Build failed for unexpected reason: {}",
stderr
);
}
Ok(())
}
#[test]
fn test_explicit_platform_overrides_detection() -> Result<()> {
let example_dir = env::current_dir()?.join("example").join("hello-krust");
let platform = get_test_platform();
let mut cmd = Command::cargo_bin("krust")?;
cmd.arg("build")
.arg("--no-push")
.arg("--platform")
.arg(platform)
.arg("--image")
.arg("test.local/explicit-platform:latest")
.arg(".")
.current_dir(&example_dir)
.env("RUST_LOG", "info");
// When platform is explicitly specified, we should NOT see platform detection
let output = cmd.output()?;
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(output.status.success());
assert!(!stderr.contains("Detecting available platforms from base image"));
assert!(stderr.contains(&format!("Building for platform: {}", platform)));
Ok(())
}
#[test]
fn test_alpine_base_image_many_platforms() -> Result<()> {
// Create a temporary directory for this test
let temp_dir = tempfile::tempdir()?;
let test_project_dir = temp_dir.path().join("test-alpine");
std::fs::create_dir_all(&test_project_dir)?;
// Create a simple Cargo.toml with Alpine as base image
let cargo_toml = r#"[package]
name = "test-alpine"
version = "0.1.0"
edition = "2021"
[dependencies]
[package.metadata.krust]
base-image = "alpine:latest"
"#;
std::fs::write(test_project_dir.join("Cargo.toml"), cargo_toml)?;
// Create src directory and main.rs
std::fs::create_dir_all(test_project_dir.join("src"))?;
std::fs::write(
test_project_dir.join("src/main.rs"),
r#"fn main() { println!("Hello from Alpine test!"); }"#,
)?;
let mut cmd = Command::cargo_bin("krust")?;
let output = cmd
.arg("build")
.arg("--no-push")
.arg("--image")
.arg("test.local/alpine-platforms:latest")
.arg(".")
.current_dir(&test_project_dir)
.env("RUST_LOG", "info")
.output()?;
let stderr = String::from_utf8_lossy(&output.stderr);
// Alpine typically supports many platforms, but we might not have all the toolchains
// So we check that platform detection happened
assert!(stderr.contains("Detecting available platforms from base image: alpine:latest"));
assert!(stderr.contains("Found platforms:") || stderr.contains("Detected platforms:"));
// The build might fail due to missing toolchains for some platforms, which is OK
// We're mainly testing that platform detection works
if !output.status.success() {
// Check if it failed due to missing toolchains (expected)
assert!(
stderr.contains("target may not be installed")
|| stderr.contains("linker")
|| stderr.contains("cross-compilation"),
"Build failed for unexpected reason: {}",
stderr
);
}
Ok(())
}