From 93bb4561fc8b53583489a5c9e7016fb5d5bc5e9a Mon Sep 17 00:00:00 2001 From: Jason Hall Date: Sat, 7 Jun 2025 21:03:33 -0400 Subject: [PATCH] Add e2e tests and CI integration for docker run workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add comprehensive e2e tests for build functionality - Add integration job to CI that tests `docker run $(krust build)` - Test KRUST_REPO environment variable usage - Test --no-push flag and clean stdout for command substitution - Add tests for verbose logging - Remove outdated basic_test.rs 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .github/workflows/ci.yml | 51 ++++++++++++++-- tests/e2e/basic_test.rs | 25 -------- tests/integration_test.rs | 123 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 168 insertions(+), 31 deletions(-) delete mode 100644 tests/e2e/basic_test.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a48d98c..00752c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,14 +69,53 @@ jobs: - name: Run clippy run: cargo clippy -- -D warnings - coverage: - name: Code coverage + integration: + name: Integration Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable - - name: Install cargo-tarpaulin - run: cargo install cargo-tarpaulin - - name: Generate code coverage - run: cargo tarpaulin --verbose --workspace \ No newline at end of file + with: + targets: x86_64-unknown-linux-musl + - name: Install musl tools + run: | + sudo apt-get update + sudo apt-get install -y musl-tools + - name: Build krust + run: cargo build --release + - name: Add krust to PATH + run: echo "${{ github.workspace }}/target/release" >> $GITHUB_PATH + - name: Test krust version + run: krust version + - name: Build and run example with krust + run: | + cd example/hello-krust + # Build and push to ttl.sh (ephemeral registry) + export KRUST_REPO=ttl.sh/${{ github.run_id }} + IMAGE_REF=$(krust build ./) + echo "Built image: $IMAGE_REF" + # Run the image + docker run --rm $IMAGE_REF + - name: Test docker run with command substitution + run: | + cd example/hello-krust + export KRUST_REPO=ttl.sh/${{ github.run_id }}-test2 + docker run --rm $(krust build ./) + - name: Test build with --no-push + run: | + cd example/hello-krust + krust build --no-push --image local.test/hello:latest ./ + + # Takes too long to run on CI, so it's commented out for now. + # coverage: + # name: Code coverage + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v4 + # - name: Install Rust + # uses: dtolnay/rust-toolchain@stable + # - name: Install cargo-tarpaulin + # run: cargo install cargo-tarpaulin + # - name: Generate code coverage + # run: cargo tarpaulin --verbose --workspace diff --git a/tests/e2e/basic_test.rs b/tests/e2e/basic_test.rs deleted file mode 100644 index b67587c..0000000 --- a/tests/e2e/basic_test.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::process::Command; - -#[test] -fn test_help_flag() { - let output = Command::new("cargo") - .args(&["run", "--", "--version"]) - .output() - .expect("Failed to execute command"); - - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("krust")); - assert!(output.status.success()); -} - -#[test] -fn test_basic_run() { - let output = Command::new("cargo") - .arg("run") - .output() - .expect("Failed to execute command"); - - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stdout.contains("Hello from krust!")); - assert!(output.status.success()); -} \ No newline at end of file diff --git a/tests/integration_test.rs b/tests/integration_test.rs index bc5e659..469f58a 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1,6 +1,8 @@ use anyhow::Result; use assert_cmd::Command; use predicates::prelude::*; +use std::env; +use std::process::Command as StdCommand; #[test] fn test_version_command() -> Result<()> { @@ -41,3 +43,124 @@ fn test_build_help() -> Result<()> { )); 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") + .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") + .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: test.local/hello-krust:latest", + )); + 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("--image") + .arg("test.local/hello:latest") + .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("--image") + .arg("test.local/hello:latest") + .current_dir(&example_dir); + + cmd.assert() + .success() + .stderr(predicate::str::contains("DEBUG")); + Ok(()) +} + +#[test] +#[ignore] // This test requires Docker and a registry +fn test_full_build_and_run_workflow() -> Result<()> { + // Skip if Docker is not available + if StdCommand::new("docker").arg("version").output().is_err() { + eprintln!("Skipping test: Docker not available"); + return Ok(()); + } + + // Get the example project directory + let example_dir = env::current_dir()?.join("example").join("hello-krust"); + + // Build and push to ttl.sh + let mut cmd = Command::cargo_bin("krust")?; + let output = cmd + .arg("build") + .env("KRUST_REPO", "ttl.sh/krust-test") + .current_dir(&example_dir) + .output()?; + + assert!(output.status.success(), "Build failed"); + + // 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()?; + + 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(()) +}