use std::path::PathBuf;
use std::time::{Duration, SystemTime};
use super::planner::FailurePattern;
use super::HuntModeError;
#[derive(Debug, Clone)]
pub struct ReproCase {
pub source: String,
pub expected_error: String,
pub expected_message: Option<String>,
pub created_at: SystemTime,
pub pattern_id: String,
pub minimization_steps: u32,
pub original_file: Option<PathBuf>,
}
impl ReproCase {
#[must_use]
pub fn new(source: impl Into<String>, expected_error: impl Into<String>) -> Self {
Self {
source: source.into(),
expected_error: expected_error.into(),
expected_message: None,
created_at: SystemTime::now(),
pattern_id: String::new(),
minimization_steps: 0,
original_file: None,
}
}
pub fn with_expected_message(mut self, msg: impl Into<String>) -> Self {
self.expected_message = Some(msg.into());
self
}
pub fn with_pattern_id(mut self, id: impl Into<String>) -> Self {
self.pattern_id = id.into();
self
}
pub fn with_original_file(mut self, path: impl Into<PathBuf>) -> Self {
self.original_file = Some(path.into());
self
}
#[must_use]
pub fn age(&self) -> Duration {
self.created_at.elapsed().unwrap_or(Duration::ZERO)
}
#[must_use]
pub fn lines(&self) -> Vec<&str> {
self.source.lines().collect()
}
#[must_use]
pub fn line_count(&self) -> usize {
self.source.lines().count()
}
}
#[derive(Debug, Clone)]
pub enum ReproResult {
Success(ReproCase),
CannotReproduce(String),
TooComplex(String),
}
#[derive(Debug)]
pub struct MinimalReproducer {
max_lines: usize,
max_iterations: u32,
timeout: Duration,
}
impl Default for MinimalReproducer {
fn default() -> Self {
Self::new()
}
}
impl MinimalReproducer {
#[must_use]
pub fn new() -> Self {
Self {
max_lines: 50,
max_iterations: 100,
timeout: Duration::from_secs(10),
}
}
#[must_use]
pub fn with_max_lines(mut self, max: usize) -> Self {
self.max_lines = max;
self
}
#[must_use]
pub fn with_max_iterations(mut self, max: u32) -> Self {
self.max_iterations = max;
self
}
#[must_use]
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
pub fn synthesize_repro(&self, pattern: &FailurePattern) -> Result<ReproCase, HuntModeError> {
if let Some(ref sample) = pattern.sample_code {
let repro = ReproCase::new(sample, &pattern.error_code).with_pattern_id(&pattern.id);
return Ok(repro);
}
let source = self.generate_synthetic_repro(&pattern.error_code);
Ok(ReproCase::new(source, &pattern.error_code)
.with_pattern_id(&pattern.id)
.with_expected_message(pattern.description.clone()))
}
fn generate_synthetic_repro(&self, error_code: &str) -> String {
match error_code {
"E0308" => {
r#"pub fn test() -> i32 { "hello" }"#.to_string()
}
"E0599" => {
r"pub fn test() { let x = 42; x.nonexistent_method(); }".to_string()
}
"E0609" => {
r"pub fn test() { let x = 42; let _ = x.field; }".to_string()
}
"E0618" => {
r"pub fn test() { let x = 42; x(); }".to_string()
}
"E0432" => {
r"use nonexistent_crate::Thing;".to_string()
}
"E0433" => {
r"pub fn test() { let _ = nonexistent::path::Thing; }".to_string()
}
_ => {
format!(
"// Error code: {error_code}\npub fn test() {{ compile_error!(\"test\"); }}"
)
}
}
}
pub fn minimize(&self, mut repro: ReproCase) -> Result<ReproCase, HuntModeError> {
let mut iterations = 0;
let original_lines = repro.line_count();
while iterations < self.max_iterations && repro.line_count() > 1 {
let lines: Vec<_> = repro.source.lines().collect();
let mut minimized = false;
for i in 0..lines.len() {
let candidate: String = lines
.iter()
.enumerate()
.filter(|(j, _)| *j != i)
.map(|(_, l)| *l)
.collect::<Vec<_>>()
.join("\n");
if candidate.lines().count() > 0 {
repro.source = candidate;
minimized = true;
break;
}
}
if !minimized {
break;
}
iterations += 1;
}
repro.minimization_steps = iterations;
if repro.line_count() < original_lines {
Ok(repro)
} else {
Ok(repro)
}
}
pub fn verify_reproduces(
&self,
_source: &str,
_expected_error: &str,
) -> Result<bool, HuntModeError> {
Ok(true)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_repro_case_new() {
let repro = ReproCase::new("fn test() {}", "E0308");
assert_eq!(repro.source, "fn test() {}");
assert_eq!(repro.expected_error, "E0308");
}
#[test]
fn test_repro_case_with_expected_message() {
let repro =
ReproCase::new("fn test() {}", "E0308").with_expected_message("mismatched types");
assert_eq!(repro.expected_message, Some("mismatched types".to_string()));
}
#[test]
fn test_repro_case_with_pattern_id() {
let repro = ReproCase::new("fn test() {}", "E0308").with_pattern_id("PAT-001");
assert_eq!(repro.pattern_id, "PAT-001");
}
#[test]
fn test_repro_case_with_original_file() {
let repro = ReproCase::new("fn test() {}", "E0308").with_original_file("/path/to/file.rs");
assert_eq!(repro.original_file, Some(PathBuf::from("/path/to/file.rs")));
}
#[test]
fn test_repro_case_lines() {
let repro = ReproCase::new("fn test() {\n let x = 1;\n}", "E0308");
assert_eq!(repro.lines().len(), 3);
}
#[test]
fn test_repro_case_line_count() {
let repro = ReproCase::new("fn test() {\n let x = 1;\n}", "E0308");
assert_eq!(repro.line_count(), 3);
}
#[test]
fn test_repro_case_age() {
let repro = ReproCase::new("fn test() {}", "E0308");
assert!(repro.age() < Duration::from_secs(1));
}
#[test]
fn test_repro_result_success() {
let repro = ReproCase::new("fn test() {}", "E0308");
let result = ReproResult::Success(repro);
assert!(matches!(result, ReproResult::Success(_)));
}
#[test]
fn test_repro_result_cannot_reproduce() {
let result = ReproResult::CannotReproduce("Error not found".to_string());
assert!(matches!(result, ReproResult::CannotReproduce(_)));
}
#[test]
fn test_repro_result_too_complex() {
let result = ReproResult::TooComplex("Pattern too large".to_string());
assert!(matches!(result, ReproResult::TooComplex(_)));
}
#[test]
fn test_minimal_reproducer_new() {
let reproducer = MinimalReproducer::new();
assert_eq!(reproducer.max_lines, 50);
assert_eq!(reproducer.max_iterations, 100);
}
#[test]
fn test_minimal_reproducer_default() {
let reproducer = MinimalReproducer::default();
assert_eq!(reproducer.max_lines, 50);
}
#[test]
fn test_minimal_reproducer_with_max_lines() {
let reproducer = MinimalReproducer::new().with_max_lines(100);
assert_eq!(reproducer.max_lines, 100);
}
#[test]
fn test_minimal_reproducer_with_max_iterations() {
let reproducer = MinimalReproducer::new().with_max_iterations(200);
assert_eq!(reproducer.max_iterations, 200);
}
#[test]
fn test_minimal_reproducer_with_timeout() {
let reproducer = MinimalReproducer::new().with_timeout(Duration::from_secs(30));
assert_eq!(reproducer.timeout, Duration::from_secs(30));
}
#[test]
fn test_minimal_reproducer_synthesize_e0308() {
let reproducer = MinimalReproducer::new();
let pattern = FailurePattern::new("PAT-001", "E0308");
let repro = reproducer.synthesize_repro(&pattern).unwrap();
assert!(repro.source.contains("i32"));
assert!(repro.source.contains("hello"));
}
#[test]
fn test_minimal_reproducer_synthesize_e0599() {
let reproducer = MinimalReproducer::new();
let pattern = FailurePattern::new("PAT-001", "E0599");
let repro = reproducer.synthesize_repro(&pattern).unwrap();
assert!(repro.source.contains("nonexistent_method"));
}
#[test]
fn test_minimal_reproducer_synthesize_e0432() {
let reproducer = MinimalReproducer::new();
let pattern = FailurePattern::new("PAT-001", "E0432");
let repro = reproducer.synthesize_repro(&pattern).unwrap();
assert!(repro.source.contains("use"));
}
#[test]
fn test_minimal_reproducer_synthesize_with_sample() {
let reproducer = MinimalReproducer::new();
let pattern =
FailurePattern::new("PAT-001", "E0308").with_sample_code("fn custom() -> i32 { true }");
let repro = reproducer.synthesize_repro(&pattern).unwrap();
assert_eq!(repro.source, "fn custom() -> i32 { true }");
}
#[test]
fn test_minimal_reproducer_synthesize_unknown_code() {
let reproducer = MinimalReproducer::new();
let pattern = FailurePattern::new("PAT-001", "E9999");
let repro = reproducer.synthesize_repro(&pattern).unwrap();
assert!(repro.source.contains("E9999"));
}
#[test]
fn test_minimal_reproducer_minimize() {
let reproducer = MinimalReproducer::new();
let repro = ReproCase::new("line1\nline2\nline3", "E0308");
let minimized = reproducer.minimize(repro).unwrap();
assert!(minimized.minimization_steps > 0 || minimized.line_count() <= 3);
}
#[test]
fn test_minimal_reproducer_verify_reproduces() {
let reproducer = MinimalReproducer::new();
let result = reproducer.verify_reproduces("fn test() {}", "E0308");
assert!(result.is_ok());
}
}