use do_memory_core::episodic::{CapacityManager, EvictionPolicy};
use do_memory_core::*;
use proptest::prelude::*;
proptest! {
#[test]
fn new_episode_initial_state(
task_description in "[a-zA-Z0-9 ]{1,100}",
domain in "[a-z]{3,15}",
task_type in prop::sample::select(vec![
TaskType::CodeGeneration,
TaskType::Testing,
TaskType::Debugging,
TaskType::Refactoring,
TaskType::Analysis,
TaskType::Documentation,
]),
) {
let episode = Episode::new(
task_description,
TaskContext {
language: Some("rust".to_string()),
framework: None,
complexity: ComplexityLevel::Moderate,
domain,
tags: vec![],
},
task_type,
);
prop_assert!(!episode.is_complete());
prop_assert!(episode.outcome.is_none());
prop_assert!(episode.end_time.is_none());
prop_assert!(episode.reward.is_none());
prop_assert!(episode.steps.is_empty());
prop_assert!(episode.duration().is_none());
}
#[test]
fn adding_steps_preserves_incomplete_state(
num_steps in 1usize..100usize,
) {
let mut episode = Episode::new(
"Test task".to_string(),
TaskContext::default(),
TaskType::CodeGeneration,
);
for i in 0..num_steps {
let step = ExecutionStep::new(
i + 1,
format!("tool_{i}"),
format!("Action {i}"),
);
episode.add_step(step);
prop_assert!(!episode.is_complete());
prop_assert!(episode.outcome.is_none());
prop_assert!(episode.end_time.is_none());
}
prop_assert_eq!(episode.steps.len(), num_steps);
}
#[test]
fn completion_transitions_to_complete_state(
num_steps in 0usize..50usize,
outcome_kind in 0u8..3u8,
) {
let mut episode = Episode::new(
"Test task".to_string(),
TaskContext::default(),
TaskType::Testing,
);
for i in 0..num_steps {
let step = ExecutionStep::new(i + 1, "tool".to_string(), "action".to_string());
episode.add_step(step);
}
let outcome = match outcome_kind {
0 => TaskOutcome::Success {
verdict: "Done".to_string(),
artifacts: vec![],
},
1 => TaskOutcome::PartialSuccess {
verdict: "Partial".to_string(),
completed: vec![],
failed: vec![],
},
_ => TaskOutcome::Failure {
reason: "Failed".to_string(),
error_details: Some("error".to_string()),
},
};
episode.complete(outcome);
prop_assert!(episode.is_complete());
prop_assert!(episode.outcome.is_some());
prop_assert!(episode.end_time.is_some());
prop_assert!(episode.duration().is_some());
prop_assert_eq!(episode.steps.len(), num_steps);
}
#[test]
fn step_counts_invariant(
success_count in 0usize..30usize,
error_count in 0usize..30usize,
) {
let mut episode = Episode::new(
"Test task".to_string(),
TaskContext::default(),
TaskType::CodeGeneration,
);
for i in 0..success_count {
let mut step = ExecutionStep::new(
i + 1,
"tool".to_string(),
"action".to_string(),
);
step.result = Some(ExecutionResult::Success {
output: "ok".to_string(),
});
episode.add_step(step);
}
for i in 0..error_count {
let mut step = ExecutionStep::new(
success_count + i + 1,
"tool".to_string(),
"action".to_string(),
);
step.result = Some(ExecutionResult::Error {
message: "err".to_string(),
});
episode.add_step(step);
}
prop_assert_eq!(episode.steps.len(), success_count + error_count);
prop_assert_eq!(episode.successful_steps_count(), success_count);
prop_assert_eq!(episode.failed_steps_count(), error_count);
}
}
proptest! {
#[test]
fn can_store_under_capacity_invariant(
max_episodes in 1usize..1000usize,
current_count in 0usize..2000usize,
) {
let manager = CapacityManager::new(max_episodes, EvictionPolicy::LRU);
let can_store = manager.can_store(current_count);
if current_count < max_episodes {
prop_assert!(can_store);
} else {
prop_assert!(!can_store);
}
}
#[test]
fn eviction_count_bounded_by_episode_count(
num_episodes in 1usize..20usize,
max_capacity in 1usize..20usize,
policy in prop::sample::select(vec![
EvictionPolicy::LRU,
EvictionPolicy::RelevanceWeighted,
]),
) {
let manager = CapacityManager::new(max_capacity, policy);
let episodes: Vec<Episode> = (0..num_episodes)
.map(|i| Episode::new(
format!("Task {i}"),
TaskContext::default(),
TaskType::Testing,
))
.collect();
let to_evict = manager.evict_if_needed(&episodes);
prop_assert!(to_evict.len() <= num_episodes);
}
#[test]
fn no_eviction_under_capacity(
num_episodes in 0usize..10usize,
) {
let max_capacity = num_episodes + 5; let manager = CapacityManager::new(max_capacity, EvictionPolicy::LRU);
let episodes: Vec<Episode> = (0..num_episodes)
.map(|i| Episode::new(
format!("Task {i}"),
TaskContext::default(),
TaskType::Testing,
))
.collect();
let to_evict = manager.evict_if_needed(&episodes);
prop_assert!(to_evict.is_empty());
}
#[test]
fn eviction_returns_unique_ids(
num_episodes in 2usize..15usize,
policy in prop::sample::select(vec![
EvictionPolicy::LRU,
EvictionPolicy::RelevanceWeighted,
]),
) {
let max_capacity = 1; let manager = CapacityManager::new(max_capacity, policy);
let episodes: Vec<Episode> = (0..num_episodes)
.map(|i| Episode::new(
format!("Task {i}"),
TaskContext::default(),
TaskType::Testing,
))
.collect();
let to_evict = manager.evict_if_needed(&episodes);
let unique_count = to_evict.iter().collect::<std::collections::HashSet<_>>().len();
prop_assert_eq!(to_evict.len(), unique_count);
}
#[test]
fn relevance_score_bounded(
reward_total in 0.0f32..3.0f32,
has_reward in proptest::bool::ANY,
) {
let manager = CapacityManager::new(100, EvictionPolicy::RelevanceWeighted);
let mut episode = Episode::new(
"Test".to_string(),
TaskContext::default(),
TaskType::Testing,
);
if has_reward {
episode.reward = Some(RewardScore {
total: reward_total,
base: 0.5,
efficiency: 1.0,
complexity_bonus: 1.0,
quality_multiplier: 1.0,
learning_bonus: 0.0,
});
}
episode.complete(TaskOutcome::Success {
verdict: "Done".to_string(),
artifacts: vec![],
});
let score = manager.relevance_score(&episode);
prop_assert!((0.0..=1.0).contains(&score),
"Relevance score {} out of bounds", score);
}
#[test]
fn quality_score_bounded(
reward_total in 0.0f32..5.0f32,
) {
let manager = CapacityManager::new(100, EvictionPolicy::RelevanceWeighted);
let mut episode = Episode::new(
"Test".to_string(),
TaskContext::default(),
TaskType::Testing,
);
episode.reward = Some(RewardScore {
total: reward_total,
base: 0.5,
efficiency: 1.0,
complexity_bonus: 1.0,
quality_multiplier: 1.0,
learning_bonus: 0.0,
});
let score = manager.extract_quality_score(&episode);
prop_assert!((0.0..=1.0).contains(&score),
"Quality score {} out of bounds for reward total {}", score, reward_total);
}
#[test]
fn recency_score_bounded(
has_end_time in proptest::bool::ANY,
) {
let manager = CapacityManager::new(100, EvictionPolicy::RelevanceWeighted);
let mut episode = Episode::new(
"Test".to_string(),
TaskContext::default(),
TaskType::Testing,
);
if has_end_time {
episode.complete(TaskOutcome::Success {
verdict: "Done".to_string(),
artifacts: vec![],
});
}
let score = manager.calculate_recency_score(&episode);
prop_assert!((0.0..=1.0).contains(&score),
"Recency score {} out of bounds", score);
}
}