use std::{collections::BTreeMap, path::Path};
use ci_engine::{
CheckResult, ExecutionContext, FsResultCache, HermeticEnv, MemoryResultCache, NoopProvider,
ResultCache, ResultCacheEntry, ResultCacheError, RunControls, RunOptions, SpotCheck,
SpotCheckDivergence, run_checks_with,
};
use crypto::{Basis, BasisKind, Conclusion, StateRef};
const MARKER_CHECK: &str = r#"
[meta]
schema = 1
[[check]]
name = "marker"
command = ["/bin/sh", "-c", "echo ran >> marker; echo ok"]
"#;
fn context() -> ExecutionContext {
ExecutionContext {
repo: "test/repo".to_string(),
state: StateRef {
content_hash: "state-content".to_string(),
change_id: "change".to_string(),
logical_change_id: None,
},
basis: Basis {
kind: BasisKind::Branch,
evaluated_tree_digest: "tree".to_string(),
},
definition_digest: "definition".to_string(),
toolchain: None,
pick_id: None,
attempt: 1,
runner: None,
image_digest: None,
}
}
fn run_cached(
raw: &str,
workdir: &Path,
context: &ExecutionContext,
cache: &dyn ResultCache,
spot_check: SpotCheck,
) -> Result<Vec<CheckResult>, ResultCacheError> {
let config = ci_config::parse(raw).expect("config");
let provider = NoopProvider;
let environment = HermeticEnv::with_host(BTreeMap::new());
run_checks_with(
&config,
context,
&RunOptions {
workdir,
services: &provider,
now_rfc3339: &|| "2026-08-14T12:00:00Z".to_string(),
},
&RunControls {
hermetic_env: Some(&environment),
result_cache: Some(cache),
spot_check,
..RunControls::default()
},
)
}
fn marker_runs(workdir: &Path) -> usize {
std::fs::read_to_string(workdir.join("marker"))
.unwrap_or_default()
.lines()
.filter(|line| !line.is_empty())
.count()
}
fn seed(raw: &str, workdir: &Path, context: &ExecutionContext, cache: &dyn ResultCache) {
run_cached(raw, workdir, context, cache, SpotCheck::Never).expect("seed");
}
fn env_check(value: &str) -> String {
format!(
r#"
[meta]
schema = 1
[[check]]
name = "marker"
command = ["/bin/sh", "-c", "echo ran >> marker; echo ok"]
[check.env]
FOO = "{value}"
"#
)
}
#[test]
fn cache_hit_reuses_result_and_does_not_rerun() {
let workdir = tempfile::tempdir().expect("workdir");
let cache = MemoryResultCache::new();
let first = run_cached(
MARKER_CHECK,
workdir.path(),
&context(),
&cache,
SpotCheck::Never,
)
.expect("seed");
assert_eq!(first[0].conclusion(), Conclusion::Success);
assert_eq!(first[0].combined_output, "ok\n");
assert_eq!(marker_runs(workdir.path()), 1);
let second = run_cached(
MARKER_CHECK,
workdir.path(),
&context(),
&cache,
SpotCheck::Never,
)
.expect("hit");
assert_eq!(second[0].combined_output, first[0].combined_output);
assert_eq!(second[0].body.outcome, first[0].body.outcome);
assert_eq!(marker_runs(workdir.path()), 1, "cache hit must not re-run");
}
#[test]
fn changed_env_is_a_cache_miss() {
let workdir = tempfile::tempdir().expect("workdir");
let cache = MemoryResultCache::new();
seed(&env_check("one"), workdir.path(), &context(), &cache);
seed(&env_check("two"), workdir.path(), &context(), &cache);
assert_eq!(
marker_runs(workdir.path()),
2,
"a different declared env must not reuse the cached result"
);
}
#[test]
fn changed_input_is_a_cache_miss() {
let workdir = tempfile::tempdir().expect("workdir");
let cache = MemoryResultCache::new();
seed(MARKER_CHECK, workdir.path(), &context(), &cache);
let mut changed = context();
changed.basis.evaluated_tree_digest = "tree-changed".to_string();
seed(MARKER_CHECK, workdir.path(), &changed, &cache);
assert_eq!(
marker_runs(workdir.path()),
2,
"tree digest change is a miss"
);
changed = context();
changed.state.content_hash = "state-changed".to_string();
seed(MARKER_CHECK, workdir.path(), &changed, &cache);
assert_eq!(
marker_runs(workdir.path()),
3,
"state content hash change is a miss"
);
}
#[test]
fn changed_definition_is_a_cache_miss() {
let workdir = tempfile::tempdir().expect("workdir");
let cache = MemoryResultCache::new();
seed(MARKER_CHECK, workdir.path(), &context(), &cache);
let mut changed = context();
changed.definition_digest = "definition-changed".to_string();
seed(MARKER_CHECK, workdir.path(), &changed, &cache);
assert_eq!(
marker_runs(workdir.path()),
2,
"a different definition digest must not reuse the cached result"
);
}
#[test]
fn serialized_entry_seeds_a_separate_cache_instance() {
let workdir = tempfile::tempdir().expect("workdir");
let seed_cache = MemoryResultCache::new();
seed(MARKER_CHECK, workdir.path(), &context(), &seed_cache);
assert_eq!(marker_runs(workdir.path()), 1);
let stored = seed_cache.entries();
assert_eq!(stored.len(), 1);
let bytes = serde_json::to_vec(&stored[0]).expect("serialize portable entry");
let restored: ResultCacheEntry = serde_json::from_slice(&bytes).expect("deserialize");
let peer_dir = tempfile::tempdir().expect("peer cache");
let peer = FsResultCache::new(peer_dir.path());
peer.put(&restored).expect("seed peer");
let hit = run_cached(
MARKER_CHECK,
workdir.path(),
&context(),
&peer,
SpotCheck::Never,
)
.expect("cross-instance hit");
assert_eq!(hit[0].conclusion(), Conclusion::Success);
assert_eq!(hit[0].combined_output, "ok\n");
assert_eq!(marker_runs(workdir.path()), 1, "portable entry must hit");
}
#[test]
fn spot_check_fails_closed_on_a_divergent_entry() {
let workdir = tempfile::tempdir().expect("workdir");
let cache = MemoryResultCache::new();
seed(MARKER_CHECK, workdir.path(), &context(), &cache);
let mut tampered = cache.entries().pop().expect("seeded entry");
tampered.body.outcome.conclusion = Conclusion::Failure;
cache.put(&tampered).expect("store divergent entry");
let error = run_cached(
MARKER_CHECK,
workdir.path(),
&context(),
&cache,
SpotCheck::Always,
)
.expect_err("divergent spot-check must be a hard error");
match &error {
ResultCacheError::SpotCheckDivergence(divergence) => {
let SpotCheckDivergence {
check_name,
cached_conclusion,
fresh_conclusion,
..
} = &**divergence;
assert_eq!(check_name, "marker");
assert_eq!(cached_conclusion, "failure");
assert_eq!(fresh_conclusion, "success");
}
other => panic!("expected spot-check divergence, got {other}"),
}
assert_eq!(marker_runs(workdir.path()), 2, "spot-check re-runs fresh");
assert!(
error
.to_string()
.contains("refusing to trust the cache entry"),
"{error}"
);
}
#[test]
fn honest_spot_check_accepts_a_matching_entry() {
let workdir = tempfile::tempdir().expect("workdir");
let cache = MemoryResultCache::new();
seed(MARKER_CHECK, workdir.path(), &context(), &cache);
let verified = run_cached(
MARKER_CHECK,
workdir.path(),
&context(),
&cache,
SpotCheck::Always,
)
.expect("matching spot-check must not fail");
assert_eq!(verified[0].conclusion(), Conclusion::Success);
assert_eq!(marker_runs(workdir.path()), 2);
}