From 36d14eb820ee2c1d6207173bb1c0d23fc0cfb3f4 Mon Sep 17 00:00:00 2001 From: Schneems Date: Mon, 30 Sep 2024 13:46:57 -0500 Subject: [PATCH] Add tests to shared layer cache logic To fully exercise all caching logic in the shared folder, I'm introducing a helper to create a temporary `BuildContext` that can be used for exercising caching logic in test. This also introduces tests for both `restored_layer_action` states as called through `cached_layer_write_metadata`. Which should address this comment https://github.com/heroku/buildpacks-ruby/pull/327#discussion_r1775410807. --- buildpacks/ruby/src/layers/shared.rs | 129 +++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/buildpacks/ruby/src/layers/shared.rs b/buildpacks/ruby/src/layers/shared.rs index 1f457889..d4198635 100644 --- a/buildpacks/ruby/src/layers/shared.rs +++ b/buildpacks/ruby/src/layers/shared.rs @@ -93,3 +93,132 @@ where ), } } + +/// Takes in a directory and returns a minimal build context for use in testing shared caching behavior +/// +/// Intented only for use with this buildpack, but meant to be used by multiple layers to assert caching behavior. +#[cfg(test)] +pub(crate) fn temp_build_context( + from_dir: impl AsRef, +) -> BuildContext { + let base_dir = from_dir.as_ref().to_path_buf(); + let layers_dir = base_dir.join("layers"); + let app_dir = base_dir.join("app_dir"); + let platform_dir = base_dir.join("platform_dir"); + let buildpack_dir = base_dir.join("buildpack_dir"); + for dir in [&app_dir, &layers_dir, &buildpack_dir, &platform_dir] { + std::fs::create_dir_all(dir).unwrap(); + } + + let target = libcnb::Target { + os: String::new(), + arch: String::new(), + arch_variant: None, + distro_name: String::new(), + distro_version: String::new(), + }; + let buildpack_toml_string = include_str!("../../buildpack.toml"); + let platform = + <::Platform as libcnb::Platform>::from_path(&platform_dir).unwrap(); + let buildpack_descriptor: libcnb::data::buildpack::ComponentBuildpackDescriptor< + ::Metadata, + > = toml::from_str(buildpack_toml_string).unwrap(); + let buildpack_plan = libcnb::data::buildpack_plan::BuildpackPlan { + entries: Vec::::new(), + }; + let store = None; + + BuildContext { + layers_dir, + app_dir, + buildpack_dir, + target, + platform, + buildpack_plan, + buildpack_descriptor, + store, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::RubyBuildpack; + use libcnb::data::layer_name; + use libcnb::layer::{EmptyLayerCause, LayerState}; + use magic_migrate::{migrate_toml_chain, Migrate}; + use serde::Deserializer; + + /// Struct for asserting the behavior of `cached_layer_write_metadata` + #[derive(Debug, serde::Serialize, serde::Deserialize)] + struct TestMetadata { + value: String, + } + impl MetadataDiff for TestMetadata { + fn diff(&self, old: &Self) -> Vec { + if self.value == old.value { + vec![] + } else { + vec![format!("value ({} to {})", old.value, self.value)] + } + } + } + migrate_toml_chain! {TestMetadata} + + #[test] + fn test_cached_layer_write_metadata() { + let temp = tempfile::tempdir().unwrap(); + let context = temp_build_context::(temp.path()); + + // First write + let result = cached_layer_write_metadata( + layer_name!("testing"), + &context, + &TestMetadata { + value: "hello".to_string(), + }, + ) + .unwrap(); + assert!(matches!( + result.state, + LayerState::Empty { + cause: EmptyLayerCause::NewlyCreated + } + )); + + // Second write, preserve the contents + let result = cached_layer_write_metadata( + layer_name!("testing"), + &context, + &TestMetadata { + value: "hello".to_string(), + }, + ) + .unwrap(); + let LayerState::Restored { cause } = &result.state else { + panic!("Expected restored layer") + }; + assert_eq!(cause, "Using cache"); + + // Third write, change the data + let result = cached_layer_write_metadata( + layer_name!("testing"), + &context, + &TestMetadata { + value: "world".to_string(), + }, + ) + .unwrap(); + + let LayerState::Empty { + cause: EmptyLayerCause::RestoredLayerAction { cause }, + } = &result.state + else { + panic!("Expected empty layer with restored layer action"); + }; + assert_eq!( + cause, + "Clearing cache due to change: value (hello to world)" + ); + } +}