#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_categorize_failure_timeout() {
let category = categorize_failure("test timed out after 60 seconds");
assert_eq!(category, FailureCategory::Timeout);
let category = categorize_failure("Timeout waiting for response");
assert_eq!(category, FailureCategory::Timeout);
}
#[test]
fn test_categorize_failure_compile_error() {
let category = categorize_failure("failed to compile: unresolved import `foo`");
assert_eq!(category, FailureCategory::CompileError);
let category = categorize_failure("error: unresolved import `bar::baz`");
assert_eq!(category, FailureCategory::CompileError);
}
#[test]
fn test_categorize_failure_runtime_error() {
let category = categorize_failure("thread 'main' panicked at 'oops'");
assert_eq!(category, FailureCategory::RuntimeError);
let category = categorize_failure("thread panicked while executing test");
assert_eq!(category, FailureCategory::RuntimeError);
}
#[test]
fn test_categorize_failure_assertion() {
let category = categorize_failure("assertion failed: expected 1, got 2");
assert_eq!(category, FailureCategory::AssertionFailure);
let category = categorize_failure("expected value to be true");
assert_eq!(category, FailureCategory::AssertionFailure);
}
#[test]
fn test_categorize_failure_unknown() {
let category = categorize_failure("something weird happened");
assert_eq!(category, FailureCategory::Unknown);
}
#[test]
fn test_extract_pattern_panic() {
let pattern = extract_pattern("thread 'test' panicked at 'message here'\nmore stuff");
assert_eq!(pattern, "panicked at 'message here'");
}
#[test]
fn test_extract_pattern_assertion() {
let pattern = extract_pattern("assertion failed: x != y");
assert_eq!(pattern, "assertion failed");
}
#[test]
fn test_extract_pattern_timeout() {
let pattern = extract_pattern("test timed out after 60s");
assert_eq!(pattern, "test timeout");
}
#[test]
fn test_extract_pattern_default() {
let pattern = extract_pattern("some random error message that is quite long");
assert_eq!(pattern, "some random error message that is quite long");
}
#[test]
fn test_extract_number_passed() {
let line = "test result: ok. 42 passed; 3 failed; 10 ignored; 5 filtered out";
assert_eq!(extract_number(line, "passed"), Some(42));
}
#[test]
fn test_extract_number_failed() {
let line = "test result: ok. 42 passed; 3 failed; 10 ignored; 5 filtered out";
assert_eq!(extract_number(line, "failed"), Some(3));
}
#[test]
fn test_extract_number_ignored() {
let line = "test result: ok. 42 passed; 3 failed; 10 ignored; 5 filtered out";
assert_eq!(extract_number(line, "ignored"), Some(10));
}
#[test]
fn test_extract_number_not_found() {
let line = "no numbers here";
assert_eq!(extract_number(line, "passed"), None);
}
#[test]
fn test_parse_test_summary() {
let stdout = "test result: ok. 100 passed; 5 failed; 20 ignored; 10 filtered out";
let stderr = "";
let (passed, failed, ignored) = parse_test_summary(stdout, stderr);
assert_eq!(passed, 100);
assert_eq!(failed, 5);
assert_eq!(ignored, 20);
}
#[test]
fn test_categorize_failures_groups() {
let failures = vec![
TestFailure {
name: "test1".to_string(),
file: PathBuf::from("test.rs"),
line: Some(10),
reason: "test timed out".to_string(),
category: FailureCategory::Timeout,
duration_ms: Some(60000),
},
TestFailure {
name: "test2".to_string(),
file: PathBuf::from("test.rs"),
line: Some(20),
reason: "test timed out".to_string(),
category: FailureCategory::Timeout,
duration_ms: Some(60000),
},
TestFailure {
name: "test3".to_string(),
file: PathBuf::from("test.rs"),
line: Some(30),
reason: "assertion failed".to_string(),
category: FailureCategory::AssertionFailure,
duration_ms: Some(100),
},
];
let groups = categorize_failures(&failures);
assert_eq!(groups.len(), 2);
let timeout_group = groups.iter().find(|g| g.root_cause.contains("Timeout"));
assert!(timeout_group.is_some());
assert_eq!(timeout_group.unwrap().tests.len(), 2);
let assertion_group = groups.iter().find(|g| g.root_cause.contains("Assertion"));
assert!(assertion_group.is_some());
assert_eq!(assertion_group.unwrap().tests.len(), 1);
}
#[test]
fn test_failure_group_priority() {
let failures = vec![TestFailure {
name: "test1".to_string(),
file: PathBuf::from("test.rs"),
line: Some(10),
reason: "assertion failed".to_string(),
category: FailureCategory::AssertionFailure,
duration_ms: None,
}];
let groups = categorize_failures(&failures);
assert_eq!(groups.len(), 1);
assert_eq!(groups[0].priority, 1);
}
#[test]
fn test_discovery_report_serialization() {
let report = DiscoveryReport {
total_tests: 100,
failures: 5,
test_failures: vec![],
timestamp: "2025-01-01T00:00:00Z".to_string(),
command: "cargo test".to_string(),
};
let json = serde_json::to_string(&report).unwrap();
let parsed: DiscoveryReport = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.total_tests, 100);
assert_eq!(parsed.failures, 5);
}
#[test]
fn test_categorization_report_serialization() {
let report = CategorizationReport {
total_failures: 10,
groups: vec![],
timestamp: "2025-01-01T00:00:00Z".to_string(),
};
let json = serde_json::to_string(&report).unwrap();
let parsed: CategorizationReport = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.total_failures, 10);
}
#[test]
fn test_generate_issue_templates_creates_tickets() {
let report = CategorizationReport {
total_failures: 5,
groups: vec![FailureGroup {
root_cause: "Timeout: slow tests".to_string(),
ignore_reason: "GH-98: Slow test".to_string(),
priority: 3,
tests: vec![
TestFailure {
name: "test_slow_1".to_string(),
file: PathBuf::from("test.rs"),
line: Some(10),
reason: "timed out".to_string(),
category: FailureCategory::Timeout,
duration_ms: Some(60000),
},
TestFailure {
name: "test_slow_2".to_string(),
file: PathBuf::from("test.rs"),
line: Some(20),
reason: "timed out".to_string(),
category: FailureCategory::Timeout,
duration_ms: Some(60000),
},
],
}],
timestamp: "2025-01-01T00:00:00Z".to_string(),
};
let tickets = generate_issue_templates(&report, None);
assert_eq!(tickets.len(), 1);
assert!(tickets[0].title.contains("Timeout"));
assert_eq!(tickets[0].test_count, 2);
assert!(tickets[0].body.contains("test_slow_1"));
assert!(tickets[0].body.contains("test_slow_2"));
}
#[test]
fn test_generate_issue_templates_applies_labels() {
let report = CategorizationReport {
total_failures: 1,
groups: vec![FailureGroup {
root_cause: "Assertion failure".to_string(),
ignore_reason: "Fix needed".to_string(),
priority: 1,
tests: vec![TestFailure {
name: "test_assert".to_string(),
file: PathBuf::from("test.rs"),
line: None,
reason: "assertion failed".to_string(),
category: FailureCategory::AssertionFailure,
duration_ms: None,
}],
}],
timestamp: "2025-01-01T00:00:00Z".to_string(),
};
let custom_labels = vec!["bug".to_string(), "urgent".to_string()];
let tickets = generate_issue_templates(&report, Some(custom_labels));
assert!(tickets[0].labels.contains(&"test-quality".to_string()));
assert!(tickets[0].labels.contains(&"bug".to_string()));
assert!(tickets[0].labels.contains(&"urgent".to_string()));
assert!(tickets[0].labels.contains(&"priority-1".to_string()));
}
#[test]
fn test_generate_issue_templates_limits_test_list() {
let tests: Vec<TestFailure> = (0..30)
.map(|i| TestFailure {
name: format!("test_{}", i),
file: PathBuf::from("test.rs"),
line: Some(i as u32),
reason: "failed".to_string(),
category: FailureCategory::Unknown,
duration_ms: None,
})
.collect();
let report = CategorizationReport {
total_failures: 30,
groups: vec![FailureGroup {
root_cause: "Many failures".to_string(),
ignore_reason: "Investigate".to_string(),
priority: 4,
tests,
}],
timestamp: "2025-01-01T00:00:00Z".to_string(),
};
let tickets = generate_issue_templates(&report, None);
assert!(tickets[0].body.contains("test_0"));
assert!(tickets[0].body.contains("test_19"));
assert!(!tickets[0].body.contains("test_20")); assert!(tickets[0].body.contains("... and 10 more tests"));
}
#[test]
fn test_tickets_report_serialization() {
let report = TicketsReport {
tickets: vec![TestIssueTemplate {
title: "Test Issue".to_string(),
body: "Body".to_string(),
labels: vec!["bug".to_string()],
category: "Category".to_string(),
test_count: 5,
}],
total_tests: 5,
timestamp: "2025-01-01T00:00:00Z".to_string(),
};
let json = serde_json::to_string(&report).unwrap();
let parsed: TicketsReport = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.tickets.len(), 1);
assert_eq!(parsed.tickets[0].title, "Test Issue");
assert_eq!(parsed.total_tests, 5);
}
#[test]
fn test_resolve_test_path_exact_match() {
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert("test_foo".to_string(), PathBuf::from("src/tests.rs"));
let result = resolve_test_path("test_foo", &map);
assert_eq!(result, Some(PathBuf::from("src/tests.rs")));
}
#[test]
fn test_resolve_test_path_module_prefix() {
use std::collections::HashMap;
let mut map = HashMap::new();
map.insert("test_bar".to_string(), PathBuf::from("src/module/tests.rs"));
let result = resolve_test_path("module::submodule::test_bar", &map);
assert_eq!(result, Some(PathBuf::from("src/module/tests.rs")));
}
#[test]
fn test_resolve_test_path_not_found() {
use std::collections::HashMap;
let map: HashMap<String, PathBuf> = HashMap::new();
let result = resolve_test_path("nonexistent_test", &map);
assert!(result.is_none());
}
}