Skip to content

Commit

Permalink
Add tests to shared layer cache logic
Browse files Browse the repository at this point in the history
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 #327 (comment).
  • Loading branch information
schneems committed Sep 30, 2024
1 parent b4f8850 commit 36d14eb
Showing 1 changed file with 129 additions and 0 deletions.
129 changes: 129 additions & 0 deletions buildpacks/ruby/src/layers/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<B: libcnb::Buildpack>(
from_dir: impl AsRef<std::path::Path>,
) -> BuildContext<B> {
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 =
<<B as libcnb::Buildpack>::Platform as libcnb::Platform>::from_path(&platform_dir).unwrap();
let buildpack_descriptor: libcnb::data::buildpack::ComponentBuildpackDescriptor<
<B as libcnb::Buildpack>::Metadata,
> = toml::from_str(buildpack_toml_string).unwrap();
let buildpack_plan = libcnb::data::buildpack_plan::BuildpackPlan {
entries: Vec::<libcnb::data::buildpack_plan::Entry>::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<String> {
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::<RubyBuildpack>(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)"
);
}
}

0 comments on commit 36d14eb

Please sign in to comment.