From 24685b433159fa9194ce1dd7a5bfdc811f9dbe42 Mon Sep 17 00:00:00 2001 From: Jason Hall Date: Tue, 14 Oct 2025 22:18:53 -0400 Subject: [PATCH] migrate integration tests to testscript Signed-off-by: Jason Hall --- Cargo.toml | 1 + tests/integration_test.rs | 351 +---------------------- tests/testdata/alpine_platforms.txt | 22 ++ tests/testdata/build_requires_repo.txt | 17 ++ tests/testdata/build_with_env.txt | 20 ++ tests/testdata/build_with_image_flag.txt | 19 ++ tests/testdata/clean_output.txt | 20 ++ tests/testdata/custom_base_image.txt | 23 ++ tests/testdata/multi_platform.txt | 23 ++ tests/testdata/platform_detection.txt | 19 ++ tests/testdata/platform_override.txt | 20 ++ tests/testdata/single_platform.txt | 22 ++ tests/testdata/verbose_logging.txt | 18 ++ tests/testdata/version.txt | 9 + tests/testdata/workspace_support.txt | 23 ++ tests/testscript_test.rs | 37 +++ 16 files changed, 299 insertions(+), 345 deletions(-) create mode 100644 tests/testdata/alpine_platforms.txt create mode 100644 tests/testdata/build_requires_repo.txt create mode 100644 tests/testdata/build_with_env.txt create mode 100644 tests/testdata/build_with_image_flag.txt create mode 100644 tests/testdata/clean_output.txt create mode 100644 tests/testdata/custom_base_image.txt create mode 100644 tests/testdata/multi_platform.txt create mode 100644 tests/testdata/platform_detection.txt create mode 100644 tests/testdata/platform_override.txt create mode 100644 tests/testdata/single_platform.txt create mode 100644 tests/testdata/verbose_logging.txt create mode 100644 tests/testdata/version.txt create mode 100644 tests/testdata/workspace_support.txt create mode 100644 tests/testscript_test.rs diff --git a/Cargo.toml b/Cargo.toml index bb569d3..171fa25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ base64 = "0.22" tempfile = "3.9" assert_cmd = "2.0" predicates = "3.0" +testscript-rs = "0.2" [[example]] name = "auto_auth_demo" diff --git a/tests/integration_test.rs b/tests/integration_test.rs index 1798d6d..3665f35 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1,162 +1,13 @@ +//! Integration tests for krust +//! +//! Most integration tests have been converted to testscripts in tests/testdata/*.txt +//! These tests remain here because they require Docker to run and verify the built images. + 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 @@ -187,7 +38,7 @@ fn test_full_build_and_run_workflow() -> Result<()> { .arg("build") .arg("--platform") .arg(native_platform) - .arg(".") // Explicitly pass current directory + .arg(".") .env("KRUST_REPO", "ttl.sh/krust-test") .current_dir(&example_dir) .output()?; @@ -216,73 +67,6 @@ fn test_full_build_and_run_workflow() -> Result<()> { 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 @@ -339,126 +123,3 @@ fn test_multi_arch_build_and_run() -> Result<()> { 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(()) -} diff --git a/tests/testdata/alpine_platforms.txt b/tests/testdata/alpine_platforms.txt new file mode 100644 index 0000000..4933136 --- /dev/null +++ b/tests/testdata/alpine_platforms.txt @@ -0,0 +1,22 @@ +# Test Alpine base image platform detection + +-- Cargo.toml -- +[package] +name = "test-alpine" +version = "0.1.0" +edition = "2021" + +[dependencies] + +[package.metadata.krust] +base-image = "alpine:latest" +-- src/main.rs -- +fn main() { + println!("Hello from Alpine!"); +} + +# Build with Alpine base - should detect many platforms +env RUST_LOG=info +exec ./krust build --no-push --image test.local/alpine:latest . +stderr 'Detecting available platforms from base image: alpine:latest' +stderr 'platforms' diff --git a/tests/testdata/build_requires_repo.txt b/tests/testdata/build_requires_repo.txt new file mode 100644 index 0000000..9f81b64 --- /dev/null +++ b/tests/testdata/build_requires_repo.txt @@ -0,0 +1,17 @@ +# Test that build fails without KRUST_REPO or --image + +-- Cargo.toml -- +[package] +name = "test-app" +version = "0.1.0" +edition = "2021" + +[dependencies] +-- src/main.rs -- +fn main() { + println!("Hello, world!"); +} + +# Build without KRUST_REPO or --image should fail +! exec ./krust build --no-push . +stderr 'Either --image or KRUST_REPO must be set' diff --git a/tests/testdata/build_with_env.txt b/tests/testdata/build_with_env.txt new file mode 100644 index 0000000..42a1289 --- /dev/null +++ b/tests/testdata/build_with_env.txt @@ -0,0 +1,20 @@ +# Test build with KRUST_REPO environment variable + +-- Cargo.toml -- +[package] +name = "test-app" +version = "0.1.0" +edition = "2021" + +[dependencies] +-- src/main.rs -- +fn main() { + println!("Hello, world!"); +} + +# Build with KRUST_REPO should succeed +env KRUST_REPO=test.local +[linux] exec ./krust build --no-push --platform linux/amd64 . +[darwin] exec ./krust build --no-push --platform linux/amd64 . +stderr 'Building Rust project' +stderr 'Successfully built image' diff --git a/tests/testdata/build_with_image_flag.txt b/tests/testdata/build_with_image_flag.txt new file mode 100644 index 0000000..13e37c3 --- /dev/null +++ b/tests/testdata/build_with_image_flag.txt @@ -0,0 +1,19 @@ +# Test build with --image flag + +-- Cargo.toml -- +[package] +name = "test-app" +version = "0.1.0" +edition = "2021" + +[dependencies] +-- src/main.rs -- +fn main() { + println!("Hello, world!"); +} + +# Build with --image flag should succeed +[linux] exec ./krust build --no-push --platform linux/amd64 --image test.local/myapp:v1 . +[darwin] exec ./krust build --no-push --platform linux/amd64 --image test.local/myapp:v1 . +stderr 'Building Rust project' +stderr 'Successfully built image' diff --git a/tests/testdata/clean_output.txt b/tests/testdata/clean_output.txt new file mode 100644 index 0000000..c695642 --- /dev/null +++ b/tests/testdata/clean_output.txt @@ -0,0 +1,20 @@ +# Test that stdout is clean for command substitution when using --no-push + +-- Cargo.toml -- +[package] +name = "test-app" +version = "0.1.0" +edition = "2021" + +[dependencies] +-- src/main.rs -- +fn main() { + println!("Hello, world!"); +} + +# With --no-push, stdout should be empty +[linux] exec ./krust build --no-push --platform linux/amd64 --image test.local/clean:latest . +[darwin] exec ./krust build --no-push --platform linux/amd64 --image test.local/clean:latest . +! stdout . +stderr 'Building Rust project' +stderr 'Successfully built image' diff --git a/tests/testdata/custom_base_image.txt b/tests/testdata/custom_base_image.txt new file mode 100644 index 0000000..5308c9c --- /dev/null +++ b/tests/testdata/custom_base_image.txt @@ -0,0 +1,23 @@ +# Test build with custom base image + +-- Cargo.toml -- +[package] +name = "test-app" +version = "0.1.0" +edition = "2021" + +[dependencies] + +[package.metadata.krust] +base-image = "alpine:latest" +-- src/main.rs -- +fn main() { + println!("Hello from Alpine!"); +} + +# Build with custom base image +[linux] exec ./krust build --no-push --platform linux/amd64 --image test.local/alpine-app:latest . +[darwin] exec ./krust build --no-push --platform linux/amd64 --image test.local/alpine-app:latest . +stderr 'Building Rust project' +stderr 'base-image.*alpine' +stderr 'Successfully built image' diff --git a/tests/testdata/multi_platform.txt b/tests/testdata/multi_platform.txt new file mode 100644 index 0000000..3ac6713 --- /dev/null +++ b/tests/testdata/multi_platform.txt @@ -0,0 +1,23 @@ +# Test multi-platform build + +-- Cargo.toml -- +[package] +name = "test-app" +version = "0.1.0" +edition = "2021" + +[dependencies] +-- src/main.rs -- +fn main() { + println!("Hello, world!"); +} + +# Build for multiple platforms +# Note: This may fail if toolchains aren't installed, which is expected +[linux] exec ./krust build --no-push --platform linux/amd64,linux/arm64 --image test.local/multi:latest . +[linux] stderr 'Building for platform: linux/amd64' +[linux] stderr 'Building for platform: linux/arm64' + +[darwin] exec ./krust build --no-push --platform linux/amd64,linux/arm64 --image test.local/multi:latest . +[darwin] stderr 'Building for platform: linux/amd64' +[darwin] stderr 'Building for platform: linux/arm64' diff --git a/tests/testdata/platform_detection.txt b/tests/testdata/platform_detection.txt new file mode 100644 index 0000000..d6fc399 --- /dev/null +++ b/tests/testdata/platform_detection.txt @@ -0,0 +1,19 @@ +# Test platform detection from base image + +-- Cargo.toml -- +[package] +name = "test-app" +version = "0.1.0" +edition = "2021" + +[dependencies] +-- src/main.rs -- +fn main() { + println!("Hello, world!"); +} + +# Build without explicit platform - should detect from base image +env RUST_LOG=info +exec ./krust build --no-push --image test.local/detection:latest . +stderr 'Detecting available platforms' +stderr 'Detected platforms' diff --git a/tests/testdata/platform_override.txt b/tests/testdata/platform_override.txt new file mode 100644 index 0000000..7792161 --- /dev/null +++ b/tests/testdata/platform_override.txt @@ -0,0 +1,20 @@ +# Test that explicit platform overrides base image detection + +-- Cargo.toml -- +[package] +name = "test-app" +version = "0.1.0" +edition = "2021" + +[dependencies] +-- src/main.rs -- +fn main() { + println!("Hello, world!"); +} + +# When platform is explicit, no detection should happen +env RUST_LOG=info +[linux] exec ./krust build --no-push --platform linux/amd64 --image test.local/explicit:latest . +[darwin] exec ./krust build --no-push --platform linux/amd64 --image test.local/explicit:latest . +! stderr 'Detecting available platforms' +stderr 'Building for platform: linux/amd64' diff --git a/tests/testdata/single_platform.txt b/tests/testdata/single_platform.txt new file mode 100644 index 0000000..1ddce5c --- /dev/null +++ b/tests/testdata/single_platform.txt @@ -0,0 +1,22 @@ +# Test single platform build + +-- Cargo.toml -- +[package] +name = "test-app" +version = "0.1.0" +edition = "2021" + +[dependencies] +-- src/main.rs -- +fn main() { + println!("Hello, world!"); +} + +# Build for single platform +[linux] exec ./krust build --no-push --platform linux/amd64 --image test.local/single:latest . +[linux] stderr 'Building for platform: linux/amd64' +[linux] stderr 'Successfully built image for 1 platform' + +[darwin] exec ./krust build --no-push --platform linux/amd64 --image test.local/single:latest . +[darwin] stderr 'Building for platform: linux/amd64' +[darwin] stderr 'Successfully built image for 1 platform' diff --git a/tests/testdata/verbose_logging.txt b/tests/testdata/verbose_logging.txt new file mode 100644 index 0000000..23827a7 --- /dev/null +++ b/tests/testdata/verbose_logging.txt @@ -0,0 +1,18 @@ +# Test verbose logging with --verbose flag + +-- Cargo.toml -- +[package] +name = "test-app" +version = "0.1.0" +edition = "2021" + +[dependencies] +-- src/main.rs -- +fn main() { + println!("Hello, world!"); +} + +# Build with --verbose should show DEBUG logs +[linux] exec ./krust --verbose build --no-push --platform linux/amd64 --image test.local/myapp:latest . +[darwin] exec ./krust --verbose build --no-push --platform linux/amd64 --image test.local/myapp:latest . +stderr 'DEBUG' diff --git a/tests/testdata/version.txt b/tests/testdata/version.txt new file mode 100644 index 0000000..7b12e51 --- /dev/null +++ b/tests/testdata/version.txt @@ -0,0 +1,9 @@ +# Test version command variants + +# Test --version flag +exec ./krust --version +stdout 'krust 0.1.0' + +# Test version subcommand +exec ./krust version +stdout 'krust 0.1.0' diff --git a/tests/testdata/workspace_support.txt b/tests/testdata/workspace_support.txt new file mode 100644 index 0000000..2927879 --- /dev/null +++ b/tests/testdata/workspace_support.txt @@ -0,0 +1,23 @@ +# Test that workspace member can be built + +-- Cargo.toml -- +[workspace] +members = ["app"] + +-- app/Cargo.toml -- +[package] +name = "myapp" +version = "0.1.0" +edition = "2021" + +[dependencies] +-- app/src/main.rs -- +fn main() { + println!("Hello from workspace!"); +} + +# Build workspace member by specifying its directory +[linux] exec ./krust build --no-push --platform linux/amd64 --image test.local/workspace:latest app +[darwin] exec ./krust build --no-push --platform linux/amd64 --image test.local/workspace:latest app +stderr 'Building Rust project' +stderr 'Successfully built image' diff --git a/tests/testscript_test.rs b/tests/testscript_test.rs new file mode 100644 index 0000000..5fa68ff --- /dev/null +++ b/tests/testscript_test.rs @@ -0,0 +1,37 @@ +use std::path::PathBuf; +use testscript_rs::testscript; + +#[test] +fn testscripts() { + // Get the path to the target/debug directory + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let target_dir = manifest_dir.join("target").join("debug"); + + testscript::run("tests/testdata") + .setup(move |env| { + // Build krust binary + std::process::Command::new("cargo") + .args(["build", "--bin", "krust"]) + .status() + .expect("Failed to build krust"); + + // Copy binary to test work directory so it's available in PATH + let krust_src = target_dir.join("krust"); + let krust_dst = env.work_dir.join("krust"); + + std::fs::copy(&krust_src, &krust_dst)?; + + // Make it executable + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mut perms = std::fs::metadata(&krust_dst)?.permissions(); + perms.set_mode(0o755); + std::fs::set_permissions(&krust_dst, perms)?; + } + + Ok(()) + }) + .execute() + .unwrap(); +}