f7a720204a
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用 - 添加所有子项目的完整源代码 - 保留原始 .git 为 .git.bak 备份
206 lines
7.2 KiB
Rust
206 lines
7.2 KiB
Rust
//! Integration tests for startup materialization of the embedded
|
|
//! builtin-skills corpus. Uses a purpose-built in-test `include_dir`
|
|
//! tree so results are deterministic and independent of the real
|
|
//! embedded corpus's contents.
|
|
|
|
use std::path::Path;
|
|
|
|
use include_dir::{Dir, include_dir};
|
|
use nomifun_extension::startup_materialize::{materialize_embedded_builtin_skills, materialize_if_needed};
|
|
use tempfile::TempDir;
|
|
|
|
static FIXTURE_CORPUS: Dir<'static> = include_dir!("$CARGO_MANIFEST_DIR/tests/fixtures/builtin-skills-fixture");
|
|
|
|
fn read_version(target: &Path) -> Option<String> {
|
|
std::fs::read_to_string(target.join(".version")).ok()
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn materialize_writes_tree_and_version_file() {
|
|
let tmp = TempDir::new().unwrap();
|
|
materialize_embedded_builtin_skills(tmp.path(), &FIXTURE_CORPUS, "1.2.3")
|
|
.await
|
|
.unwrap();
|
|
|
|
let target = tmp.path().join("builtin-skills");
|
|
assert!(target.is_dir(), "target dir should exist");
|
|
assert_eq!(read_version(&target).as_deref(), Some("1.2.3"));
|
|
assert!(
|
|
target.join("example-skill").join("SKILL.md").is_file(),
|
|
"expected fixture skill to be materialized"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn materialize_overwrites_existing_target() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let target = tmp.path().join("builtin-skills");
|
|
std::fs::create_dir_all(target.join("stale-dir")).unwrap();
|
|
std::fs::write(target.join("junk.txt"), b"old").unwrap();
|
|
|
|
materialize_embedded_builtin_skills(tmp.path(), &FIXTURE_CORPUS, "1.2.3")
|
|
.await
|
|
.unwrap();
|
|
|
|
assert!(!target.join("junk.txt").exists(), "stale file should be gone");
|
|
assert!(!target.join("stale-dir").exists(), "stale dir should be gone");
|
|
assert_eq!(read_version(&target).as_deref(), Some("1.2.3"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn materialize_cleans_staging_from_prior_crash() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let staging = tmp.path().join(".builtin-skills.tmp");
|
|
std::fs::create_dir_all(&staging).unwrap();
|
|
std::fs::write(staging.join("leftover.txt"), b"x").unwrap();
|
|
|
|
materialize_embedded_builtin_skills(tmp.path(), &FIXTURE_CORPUS, "1.2.3")
|
|
.await
|
|
.unwrap();
|
|
|
|
assert!(!staging.exists(), "staging should be cleaned after success");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn gate_skips_when_version_matches() {
|
|
let tmp = TempDir::new().unwrap();
|
|
// Pre-populate target with matching version.
|
|
let target = tmp.path().join("builtin-skills");
|
|
std::fs::create_dir_all(&target).unwrap();
|
|
std::fs::write(target.join(".version"), "1.2.3").unwrap();
|
|
std::fs::write(target.join("sentinel"), "do-not-delete").unwrap();
|
|
|
|
let wrote = materialize_if_needed(tmp.path(), &FIXTURE_CORPUS, "1.2.3")
|
|
.await
|
|
.unwrap();
|
|
assert!(!wrote, "version match should skip materialize");
|
|
assert!(
|
|
target.join("sentinel").is_file(),
|
|
"sentinel must be preserved when gate says skip"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn gate_triggers_when_version_mismatches() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let target = tmp.path().join("builtin-skills");
|
|
std::fs::create_dir_all(&target).unwrap();
|
|
std::fs::write(target.join(".version"), "0.0.0").unwrap();
|
|
std::fs::write(target.join("sentinel"), "will-be-wiped").unwrap();
|
|
|
|
let wrote = materialize_if_needed(tmp.path(), &FIXTURE_CORPUS, "1.2.3")
|
|
.await
|
|
.unwrap();
|
|
assert!(wrote, "version mismatch should materialize");
|
|
assert!(
|
|
!target.join("sentinel").exists(),
|
|
"sentinel must be wiped by fresh materialize"
|
|
);
|
|
assert_eq!(read_version(&target).as_deref(), Some("1.2.3"));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn gate_triggers_when_version_file_missing() {
|
|
let tmp = TempDir::new().unwrap();
|
|
|
|
let wrote = materialize_if_needed(tmp.path(), &FIXTURE_CORPUS, "1.2.3")
|
|
.await
|
|
.unwrap();
|
|
assert!(wrote, "no existing version should materialize");
|
|
assert_eq!(
|
|
read_version(&tmp.path().join("builtin-skills")).as_deref(),
|
|
Some("1.2.3")
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn gate_keeps_existing_tree_when_refresh_fails() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let target = tmp.path().join("builtin-skills");
|
|
std::fs::create_dir_all(&target).unwrap();
|
|
std::fs::write(target.join(".version"), "0.0.0").unwrap();
|
|
std::fs::write(target.join("sentinel"), "keep-existing").unwrap();
|
|
std::fs::write(tmp.path().join(".builtin-skills.tmp"), "stale-file").unwrap();
|
|
|
|
let wrote = materialize_if_needed(tmp.path(), &FIXTURE_CORPUS, "1.2.3")
|
|
.await
|
|
.unwrap();
|
|
|
|
assert!(!wrote, "refresh failure should fall back to existing tree");
|
|
assert_eq!(read_version(&target).as_deref(), Some("0.0.0"));
|
|
assert!(
|
|
target.join("sentinel").is_file(),
|
|
"existing tree must be preserved when refresh fails"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn gate_fails_when_initial_materialize_fails_without_existing_tree() {
|
|
let tmp = TempDir::new().unwrap();
|
|
std::fs::write(tmp.path().join(".builtin-skills.tmp"), "stale-file").unwrap();
|
|
|
|
let result = materialize_if_needed(tmp.path(), &FIXTURE_CORPUS, "1.2.3").await;
|
|
|
|
assert!(
|
|
result.is_err(),
|
|
"first startup must not silently continue without a usable builtin-skills tree"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn concurrent_materialize_produces_consistent_tree() {
|
|
// Two concurrent invocations share the same staging/target paths, so
|
|
// at most one reliably wins the atomic rename; the other may legally
|
|
// fail mid-staging. What matters is that after both complete, the
|
|
// on-disk tree is in a consistent, fully-populated state driven by
|
|
// whichever call succeeded.
|
|
let tmp = TempDir::new().unwrap();
|
|
let dir1 = tmp.path().to_path_buf();
|
|
let dir2 = tmp.path().to_path_buf();
|
|
|
|
let (a, b) = tokio::join!(
|
|
materialize_embedded_builtin_skills(&dir1, &FIXTURE_CORPUS, "1.2.3"),
|
|
materialize_embedded_builtin_skills(&dir2, &FIXTURE_CORPUS, "1.2.3"),
|
|
);
|
|
assert!(
|
|
a.is_ok() || b.is_ok(),
|
|
"at least one concurrent materialize must succeed; a={a:?}, b={b:?}"
|
|
);
|
|
|
|
let target = tmp.path().join("builtin-skills");
|
|
assert_eq!(read_version(&target).as_deref(), Some("1.2.3"));
|
|
assert!(target.join("example-skill").join("SKILL.md").is_file());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn concurrent_gate_materialize_all_callers_succeed() {
|
|
let tmp = TempDir::new().unwrap();
|
|
let data_dir = tmp.path().to_path_buf();
|
|
let mut handles = Vec::new();
|
|
for _ in 0..8 {
|
|
let dir = data_dir.clone();
|
|
handles.push(tokio::spawn(async move {
|
|
materialize_if_needed(&dir, &FIXTURE_CORPUS, "1.2.3").await
|
|
}));
|
|
}
|
|
let mut results = Vec::new();
|
|
for handle in handles {
|
|
results.push(handle.await.unwrap());
|
|
}
|
|
|
|
for result in &results {
|
|
assert!(
|
|
result.is_ok(),
|
|
"concurrent startup materialize callers should not fail: {results:?}"
|
|
);
|
|
}
|
|
assert!(
|
|
results.iter().filter(|result| matches!(result, Ok(true))).count() >= 1,
|
|
"at least one caller should perform the materialize write: {results:?}"
|
|
);
|
|
|
|
let target = tmp.path().join("builtin-skills");
|
|
assert_eq!(read_version(&target).as_deref(), Some("1.2.3"));
|
|
assert!(target.join("example-skill").join("SKILL.md").is_file());
|
|
}
|