From e6f85be66d85a2dab45f548c7a679f3fd3e51f5e Mon Sep 17 00:00:00 2001 From: Jason Hall Date: Wed, 15 Oct 2025 15:24:48 -0400 Subject: [PATCH 1/3] simplify ci Signed-off-by: Jason Hall --- .github/workflows/ci.yml | 64 ++++++++-------------------------------- 1 file changed, 12 insertions(+), 52 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f24c19..b03d76c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,7 @@ jobs: with: toolchain: ${{ matrix.rust }} targets: x86_64-unknown-linux-musl,aarch64-unknown-linux-musl + components: rustfmt,clippy - name: Install cross-compilation tools for both architectures run: | sudo apt-get update @@ -44,6 +45,9 @@ jobs: path: target key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} + - run: make check-fmt + - run: make lint + - name: Setup cargo config for CI cross-compilation run: | mkdir -p .cargo @@ -58,59 +62,15 @@ jobs: - run: make verify-cross-compile - run: make build - run: make test + - run: make test-e2e - - run: make run-built-image + # Only run cross-compilation integration test on x86_64 runners + - name: Run integration test (cross-compilation) if: matrix.os == 'ubuntu-latest' - - fmt: - name: Rustfmt - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Install Rust - uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 # stable - with: - toolchain: stable - components: rustfmt - - name: Check formatting - run: make check-fmt + run: make run-built-image - clippy: - name: Clippy - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Install Rust - uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 # stable - with: - toolchain: stable - components: clippy - - name: Run clippy - run: make lint + - run: cargo install cargo-audit + - run: cargo audit - security-audit: - name: Security Audit - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Install Rust - uses: dtolnay/rust-toolchain@6d653acede28d24f02e3cd41383119e8b1b35921 # stable - with: - toolchain: stable - - name: Install cargo-audit - run: cargo install cargo-audit - - name: Run security audit - run: cargo audit - - # 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 + - run: cargo install cargo-tarpaulin + - run: cargo tarpaulin --verbose --workspace From f324904eaa30efe1a074a106d35f5313c6a4715d Mon Sep 17 00:00:00 2001 From: Jason Hall Date: Wed, 15 Oct 2025 15:35:28 -0400 Subject: [PATCH 2/3] improve test coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ⏺ Test coverage improved from 47.65% to 50.14% (+2.49%). Coverage improvements by module: - resolve: 54.7% → 89.8% (+35.08%) - image: 46.6% → 57.7% (+11.14%) - config: 69.2% → 80.0% (+10.77%) - builder: 51.1% → 52.8% (+1.70%) Signed-off-by: Jason Hall --- src/builder/tests.rs | 70 ++++++++++++++++++++++++++++++++++ src/config/tests.rs | 58 ++++++++++++++++++++++++++++ src/image/mod.rs | 91 ++++++++++++++++++++++++++++++++++++++++++++ src/resolve/mod.rs | 67 ++++++++++++++++++++++++++++++++ 4 files changed, 286 insertions(+) diff --git a/src/builder/tests.rs b/src/builder/tests.rs index 8aa0c08..cd6c3a4 100644 --- a/src/builder/tests.rs +++ b/src/builder/tests.rs @@ -1,6 +1,8 @@ #[cfg(test)] mod tests { use super::super::*; + use std::fs; + use tempfile::tempdir; #[test] fn test_get_rust_target_triple() { @@ -38,4 +40,72 @@ mod tests { ); assert!(get_rust_target_triple("windows/amd64").is_err()); } + + #[test] + fn test_get_binary_name_valid() { + let dir = tempdir().unwrap(); + let cargo_toml = dir.path().join("Cargo.toml"); + fs::write( + &cargo_toml, + r#" +[package] +name = "test-binary" +version = "0.1.0" +"#, + ) + .unwrap(); + + let builder = RustBuilder::new(dir.path(), "x86_64-unknown-linux-musl"); + let name = builder.get_binary_name().unwrap(); + assert_eq!(name, "test-binary"); + } + + #[test] + fn test_get_binary_name_missing_cargo_toml() { + let dir = tempdir().unwrap(); + let builder = RustBuilder::new(dir.path(), "x86_64-unknown-linux-musl"); + let result = builder.get_binary_name(); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Cargo.toml")); + } + + #[test] + fn test_get_binary_name_invalid_toml() { + let dir = tempdir().unwrap(); + let cargo_toml = dir.path().join("Cargo.toml"); + fs::write(&cargo_toml, "invalid toml [[[").unwrap(); + + let builder = RustBuilder::new(dir.path(), "x86_64-unknown-linux-musl"); + let result = builder.get_binary_name(); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("parse")); + } + + #[test] + fn test_get_binary_name_missing_package_name() { + let dir = tempdir().unwrap(); + let cargo_toml = dir.path().join("Cargo.toml"); + fs::write( + &cargo_toml, + r#" +[package] +version = "0.1.0" +"#, + ) + .unwrap(); + + let builder = RustBuilder::new(dir.path(), "x86_64-unknown-linux-musl"); + let result = builder.get_binary_name(); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("package name")); + } + + #[test] + fn test_rust_builder_with_cargo_args() { + let dir = tempdir().unwrap(); + let builder = RustBuilder::new(dir.path(), "x86_64-unknown-linux-musl") + .with_cargo_args(vec!["--features".to_string(), "foo".to_string()]); + + assert_eq!(builder.cargo_args, vec!["--features", "foo"]); + } } diff --git a/src/config/tests.rs b/src/config/tests.rs index 95ef128..29621bb 100644 --- a/src/config/tests.rs +++ b/src/config/tests.rs @@ -1,6 +1,8 @@ #[cfg(test)] mod tests { use super::super::*; + use std::fs; + use tempfile::tempdir; #[test] fn test_default_config() { @@ -17,4 +19,60 @@ mod tests { assert!(build_config.cargo_args.is_empty()); assert!(build_config.target_dir.is_none()); } + + #[test] + fn test_load_project_config_no_cargo_toml() { + let dir = tempdir().unwrap(); + let config = Config::load_project_config(dir.path()).unwrap(); + assert!(config.base_image.is_none()); + } + + #[test] + fn test_load_project_config_with_metadata() { + let dir = tempdir().unwrap(); + let cargo_toml = dir.path().join("Cargo.toml"); + fs::write( + &cargo_toml, + r#" +[package] +name = "test" +version = "0.1.0" + +[package.metadata.krust] +base-image = "custom:latest" +"#, + ) + .unwrap(); + + let config = Config::load_project_config(dir.path()).unwrap(); + assert_eq!(config.base_image, Some("custom:latest".to_string())); + } + + #[test] + fn test_load_project_config_without_metadata() { + let dir = tempdir().unwrap(); + let cargo_toml = dir.path().join("Cargo.toml"); + fs::write( + &cargo_toml, + r#" +[package] +name = "test" +version = "0.1.0" +"#, + ) + .unwrap(); + + let config = Config::load_project_config(dir.path()).unwrap(); + assert!(config.base_image.is_none()); + } + + #[test] + fn test_load_project_config_invalid_toml() { + let dir = tempdir().unwrap(); + let cargo_toml = dir.path().join("Cargo.toml"); + fs::write(&cargo_toml, "invalid toml [[[").unwrap(); + + let result = Config::load_project_config(dir.path()); + assert!(result.is_err()); + } } diff --git a/src/image/mod.rs b/src/image/mod.rs index d6b0577..a23d125 100644 --- a/src/image/mod.rs +++ b/src/image/mod.rs @@ -424,4 +424,95 @@ mod tests { assert_eq!(parsed.rootfs.diff_ids.len(), 1); assert_eq!(parsed.history.len(), 0); // Default empty for missing field } + + #[test] + fn test_parse_platform_invalid_format() { + let builder = ImageBuilder::new( + PathBuf::from("/tmp/test"), + "test-base".to_string(), + "linux-amd64".to_string(), // Wrong format + ); + + let result = builder.parse_platform(); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Invalid platform")); + } + + #[test] + fn test_create_layer_with_valid_binary() { + let mut temp_file = NamedTempFile::new().unwrap(); + temp_file.write_all(b"test binary content").unwrap(); + temp_file.flush().unwrap(); + + let builder = ImageBuilder::new( + temp_file.path().to_path_buf(), + "test-base".to_string(), + "linux/amd64".to_string(), + ); + + let result = builder.create_layer(); + assert!(result.is_ok()); + + let (compressed_data, diff_id) = result.unwrap(); + assert!(!compressed_data.is_empty()); + assert!(diff_id.starts_with("sha256:")); + } + + #[test] + fn test_create_layer_with_nonexistent_binary() { + let builder = ImageBuilder::new( + PathBuf::from("/nonexistent/binary"), + "test-base".to_string(), + "linux/amd64".to_string(), + ); + + let result = builder.create_layer(); + assert!(result.is_err()); + } + + #[test] + fn test_create_layered_config_adds_path_if_missing() { + let binary_path = create_test_binary(); + let builder = ImageBuilder::new( + binary_path, + "test-base".to_string(), + "linux/amd64".to_string(), + ); + + let mut base_config = create_base_image_config(); + base_config.config.env = vec![]; // No PATH + + let app_diff_id = "sha256:app_layer_diff_id"; + + let result = builder + .create_layered_config(&base_config, app_diff_id) + .unwrap(); + + // Should add PATH since it was missing + assert!(result.config.env.iter().any(|env| env.starts_with("PATH="))); + } + + #[test] + fn test_create_layered_config_sets_cmd() { + let binary_path = create_test_binary(); + let builder = ImageBuilder::new( + binary_path.clone(), + "test-base".to_string(), + "linux/amd64".to_string(), + ); + + let base_config = create_base_image_config(); + let app_diff_id = "sha256:app_layer_diff_id"; + + let result = builder + .create_layered_config(&base_config, app_diff_id) + .unwrap(); + + // Should set CMD to the binary + let binary_name = binary_path.file_name().unwrap().to_str().unwrap(); + assert_eq!( + result.config.cmd, + Some(vec![format!("/app/{}", binary_name)]) + ); + } } diff --git a/src/resolve/mod.rs b/src/resolve/mod.rs index 9a8a3d5..b97162d 100644 --- a/src/resolve/mod.rs +++ b/src/resolve/mod.rs @@ -196,4 +196,71 @@ image: krust://./app2 assert!(refs.contains("./app1")); assert!(refs.contains("./app2")); } + + #[test] + fn test_read_yaml_files_single_file() { + use std::fs; + use tempfile::tempdir; + + let dir = tempdir().unwrap(); + let file_path = dir.path().join("test.yaml"); + fs::write(&file_path, "image: krust://./app").unwrap(); + + let files = read_yaml_files(&file_path).unwrap(); + assert_eq!(files.len(), 1); + assert!(files[0].0.contains("test.yaml")); + assert!(files[0].1.contains("krust://./app")); + } + + #[test] + fn test_read_yaml_files_directory() { + use std::fs; + use tempfile::tempdir; + + let dir = tempdir().unwrap(); + fs::write(dir.path().join("test1.yaml"), "image: krust://./app1").unwrap(); + fs::write(dir.path().join("test2.yml"), "image: krust://./app2").unwrap(); + fs::write(dir.path().join("test.txt"), "not yaml").unwrap(); + + let files = read_yaml_files(dir.path()).unwrap(); + assert_eq!(files.len(), 2); + assert!(files.iter().any(|(name, _)| name.contains("test1.yaml"))); + assert!(files.iter().any(|(name, _)| name.contains("test2.yml"))); + } + + #[test] + fn test_read_yaml_files_empty_directory() { + use tempfile::tempdir; + + let dir = tempdir().unwrap(); + let result = read_yaml_files(dir.path()); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("No YAML files found")); + } + + #[test] + fn test_read_yaml_files_nonexistent_path() { + use std::path::PathBuf; + + let path = PathBuf::from("/nonexistent/path/that/does/not/exist"); + let result = read_yaml_files(&path); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("Path does not exist")); + } + + #[test] + fn test_replace_references_empty_replacements() { + let yaml = r#"image: krust://./app"#; + let replacements = HashMap::new(); + + let result = replace_krust_references(yaml, &replacements).unwrap(); + // Should keep original reference if no replacement found + assert!(result.contains("krust://./app")); + } } From c0d62918c21fe68c12b4175ede37eb7a5cd429b8 Mon Sep 17 00:00:00 2001 From: Jason Hall Date: Wed, 15 Oct 2025 15:52:26 -0400 Subject: [PATCH 3/3] Disable cargo-tarpaulin in CI workflow Comment out cargo-tarpaulin installation and execution due to CI failure. --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b03d76c..f08f3fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,5 +72,6 @@ jobs: - run: cargo install cargo-audit - run: cargo audit - - run: cargo install cargo-tarpaulin - - run: cargo tarpaulin --verbose --workspace + # TODO: fails in CI for now. + #- run: cargo install cargo-tarpaulin + #- run: cargo tarpaulin --verbose --workspace