use std::collections::{BTreeMap, BTreeSet};
use proptest::collection::vec;
use proptest::prelude::*;
use proptest::test_runner::TestCaseError;
use super::harness::{
ClipSpec, desired_set, fast_opts, mutating_actions, probe_local, run_sync, sources_for, world,
};
use crate::config::AudioFormat;
use crate::fs::Filesystem;
use crate::manifest::Manifest;
use crate::reconcile::{SourceMode, SourceStatus, reconcile};
use crate::testutil::MemFs;
const IDS: u8 = 6;
fn id_of(n: u8) -> String {
format!("c{n:03}")
}
#[derive(Clone, Debug)]
struct ModelClip {
title_rev: u32,
creator_rev: u32,
tags_rev: u32,
art_rev: u32,
copy: bool,
private: bool,
trashed: bool,
format: AudioFormat,
}
impl ModelClip {
fn fresh() -> Self {
Self {
title_rev: 0,
creator_rev: 0,
tags_rev: 0,
art_rev: 0,
copy: false,
private: false,
trashed: false,
format: AudioFormat::Mp3,
}
}
}
type Model = BTreeMap<String, ModelClip>;
fn spec_of(id: &str, clip: &ModelClip) -> ClipSpec {
let mut modes = vec![SourceMode::Mirror];
if clip.copy {
modes.push(SourceMode::Copy);
}
ClipSpec {
id: id.to_owned(),
title: format!("title-{}", clip.title_rev),
creator: format!("creator-{}", clip.creator_rev),
tags: format!("tags-{}", clip.tags_rev),
art: format!("https://cdn1.suno.ai/{id}-art{}.jpeg", clip.art_rev),
format: clip.format,
modes,
trashed: clip.trashed,
private: clip.private,
}
}
fn specs_of(model: &Model) -> Vec<ClipSpec> {
model.iter().map(|(id, c)| spec_of(id, c)).collect()
}
#[derive(Clone, Debug)]
enum Mutation {
Add(u8),
Remove(u8),
BumpTags(u8),
BumpTitle(u8),
BumpCreator(u8),
BumpArt(u8),
ToggleCopy(u8),
TogglePrivate(u8),
ToggleTrashed(u8),
ToggleFormat(u8),
}
fn apply(model: &mut Model, mutation: &Mutation) {
match mutation {
Mutation::Add(n) => {
model.entry(id_of(*n)).or_insert_with(ModelClip::fresh);
}
Mutation::Remove(n) => {
model.remove(&id_of(*n));
}
Mutation::BumpTags(n) => with_clip(model, *n, |c| c.tags_rev += 1),
Mutation::BumpTitle(n) => with_clip(model, *n, |c| c.title_rev += 1),
Mutation::BumpCreator(n) => with_clip(model, *n, |c| c.creator_rev += 1),
Mutation::BumpArt(n) => with_clip(model, *n, |c| c.art_rev += 1),
Mutation::ToggleCopy(n) => with_clip(model, *n, |c| c.copy = !c.copy),
Mutation::TogglePrivate(n) => with_clip(model, *n, |c| c.private = !c.private),
Mutation::ToggleTrashed(n) => with_clip(model, *n, |c| c.trashed = !c.trashed),
Mutation::ToggleFormat(n) => with_clip(model, *n, |c| {
c.format = match c.format {
AudioFormat::Mp3 => AudioFormat::Flac,
_ => AudioFormat::Mp3,
}
}),
}
}
fn with_clip(model: &mut Model, n: u8, edit: impl FnOnce(&mut ModelClip)) {
if let Some(clip) = model.get_mut(&id_of(n)) {
edit(clip);
}
}
#[derive(Clone, Debug)]
enum RunMode {
Clean,
PartialListing,
FailedEmptyListing,
}
#[derive(Clone, Debug)]
struct Step {
mutations: Vec<Mutation>,
mode: RunMode,
}
fn mutation() -> impl Strategy<Value = Mutation> {
let id = || 0u8..IDS;
prop_oneof![
id().prop_map(Mutation::Add),
id().prop_map(Mutation::Remove),
id().prop_map(Mutation::BumpTags),
id().prop_map(Mutation::BumpTitle),
id().prop_map(Mutation::BumpCreator),
id().prop_map(Mutation::BumpArt),
id().prop_map(Mutation::ToggleCopy),
id().prop_map(Mutation::TogglePrivate),
id().prop_map(Mutation::ToggleTrashed),
id().prop_map(Mutation::ToggleFormat),
]
}
fn run_mode() -> impl Strategy<Value = RunMode> {
prop_oneof![
3 => Just(RunMode::Clean),
1 => Just(RunMode::PartialListing),
1 => Just(RunMode::FailedEmptyListing),
]
}
fn step() -> impl Strategy<Value = Step> {
(vec(mutation(), 0..4), run_mode()).prop_map(|(mutations, mode)| Step { mutations, mode })
}
fn script() -> impl Strategy<Value = Vec<Step>> {
vec(step(), 1..12)
}
fn present(manifest: &Manifest, fs: &MemFs, id: &str) -> bool {
manifest
.get(id)
.is_some_and(|entry| fs.metadata(&entry.path).is_some())
}
fn protected_present(manifest: &Manifest, fs: &MemFs, specs: &[ClipSpec]) -> BTreeSet<String> {
let desired_ids: BTreeSet<&str> = specs.iter().map(|s| s.id.as_str()).collect();
let live_protected: BTreeSet<&str> = specs
.iter()
.filter(|s| s.private || s.modes.contains(&SourceMode::Copy))
.map(|s| s.id.as_str())
.collect();
manifest
.iter()
.filter(|(id, entry)| {
let protected = if desired_ids.contains(id.as_str()) {
live_protected.contains(id.as_str())
} else {
entry.preserve
};
protected && fs.metadata(&entry.path).is_some()
})
.map(|(id, _)| id.clone())
.collect()
}
fn assert_protected_survive(
manifest: &Manifest,
fs: &MemFs,
protected: &BTreeSet<String>,
) -> Result<(), TestCaseError> {
for id in protected {
prop_assert!(
present(manifest, fs, id),
"protected clip {id} lost its file or manifest entry"
);
}
Ok(())
}
fn assert_manifest_disk_consistent(manifest: &Manifest, fs: &MemFs) -> Result<(), TestCaseError> {
for (id, entry) in manifest.iter() {
let stat = fs.metadata(&entry.path);
prop_assert!(
stat.is_some(),
"manifest entry {id} points at a missing file {}",
entry.path
);
prop_assert_eq!(
stat.unwrap().size,
entry.size,
"manifest size disagrees with disk for {}",
id
);
}
Ok(())
}
fn check_script(script: &[Step]) -> Result<(), TestCaseError> {
let fs = MemFs::new();
let mut manifest = Manifest::new();
let mut model: Model = BTreeMap::new();
for step in script {
for mutation in &step.mutations {
apply(&mut model, mutation);
}
let specs = specs_of(&model);
let protected = protected_present(&manifest, &fs, &specs);
let disk_before = fs.paths();
match step.mode {
RunMode::Clean => {
let sources = sources_for(&specs);
let http = world(&specs);
let (_plan, outcome) =
run_sync(&specs, &sources, &fs, &mut manifest, &http, &fast_opts());
prop_assert_eq!(outcome.failed(), 0, "clean run had failures");
let (_p2, o2) = run_sync(&specs, &sources, &fs, &mut manifest, &http, &fast_opts());
prop_assert_eq!(o2.failed(), 0, "second clean run had failures");
let local = probe_local(&manifest, &fs);
let replan = reconcile(&manifest, &desired_set(&specs), &local, &sources);
prop_assert_eq!(
mutating_actions(&replan),
0,
"clean runs did not converge within two passes: {:?}",
replan.actions
);
let mut tracked: Vec<String> =
manifest.iter().map(|(_, e)| e.path.clone()).collect();
tracked.sort();
prop_assert_eq!(
fs.paths(),
tracked,
"converged disk is not exactly the tracked set"
);
}
RunMode::PartialListing => {
let sources = [SourceStatus {
mode: SourceMode::Mirror,
fully_enumerated: false,
}];
let http = world(&specs);
let (plan, outcome) =
run_sync(&specs, &sources, &fs, &mut manifest, &http, &fast_opts());
prop_assert_eq!(plan.deletes(), 0, "partial listing planned a delete");
prop_assert_eq!(outcome.deleted, 0, "partial listing executed a delete");
}
RunMode::FailedEmptyListing => {
let sources = [SourceStatus {
mode: SourceMode::Mirror,
fully_enumerated: false,
}];
let (plan, outcome) =
run_sync(&[], &sources, &fs, &mut manifest, &world(&[]), &fast_opts());
prop_assert_eq!(plan.deletes(), 0, "failed empty listing planned a delete");
prop_assert_eq!(outcome.deleted, 0, "failed empty listing executed a delete");
prop_assert_eq!(
fs.paths(),
disk_before,
"failed empty listing changed the disk"
);
}
}
assert_protected_survive(&manifest, &fs, &protected)?;
assert_manifest_disk_consistent(&manifest, &fs)?;
}
Ok(())
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 128,
failure_persistence: None,
..ProptestConfig::default()
})]
#[test]
fn library_integrity_holds_across_random_runs(script in script()) {
check_script(&script)?;
}
}
#[test]
fn library_integrity_holds_for_a_crafted_high_value_script() {
let clean = |mutations: Vec<Mutation>| Step {
mutations,
mode: RunMode::Clean,
};
let script = vec![
clean(vec![
Mutation::Add(0),
Mutation::Add(1),
Mutation::Add(2),
Mutation::ToggleCopy(1),
Mutation::TogglePrivate(2),
]),
clean(vec![Mutation::ToggleTrashed(0)]),
clean(vec![
Mutation::ToggleCopy(1),
Mutation::TogglePrivate(2),
Mutation::Remove(1),
Mutation::Remove(2),
]),
clean(vec![]),
];
check_script(&script).expect("crafted high-value script holds every invariant");
}