use crate::edit::series::{self, SeriesFile};
use rowan::ast::AstNode;
use std::sync::Arc;
use std::thread;
#[test]
fn test_empty_series_edge_cases() {
let parsed = series::parse("");
assert!(parsed.errors().is_empty());
let series = parsed.quilt_tree();
assert_eq!(series.entries().count(), 0);
assert_eq!(series.patch_entries().count(), 0);
assert_eq!(series.comment_lines().count(), 0);
let parsed = series::parse(" \n\t \n ");
if !parsed.errors().is_empty() {
eprintln!("Errors for whitespace-only input: {:?}", parsed.errors());
}
let series = parsed.quilt_tree();
assert_eq!(series.patch_entries().count(), 0);
let parsed = series::parse("\n\n\n");
if !parsed.errors().is_empty() {
eprintln!("Errors for newlines-only input: {:?}", parsed.errors());
}
let series = parsed.quilt_tree();
assert_eq!(series.patch_entries().count(), 0);
}
#[test]
fn test_malformed_input_error_recovery() {
let parsed = series::parse(" -p1\n");
let series = parsed.quilt_tree();
let patches: Vec<_> = series.patch_entries().collect();
assert!(parsed.errors().len() > 0 || patches.is_empty());
let parsed = series::parse("#\n");
assert!(parsed.errors().is_empty());
let series = parsed.quilt_tree();
let comments: Vec<_> = series.comment_lines().collect();
assert_eq!(comments.len(), 1);
assert_eq!(comments[0].text(), "");
let parsed = series::parse("patch1.patch\n \npatch2.patch\n");
if !parsed.errors().is_empty() {
eprintln!("Errors for mixed valid/invalid: {:?}", parsed.errors());
}
assert!(parsed.errors().is_empty());
let series = parsed.quilt_tree();
let patches: Vec<_> = series.patch_entries().collect();
assert_eq!(patches.len(), 2);
}
#[test]
fn test_complex_formatting_preservation() {
let text = r#"# Header comment with multiple spaces
patch1.patch -p1 --reverse
# Mid comment
patch2.patch
patch3.patch -p2 --fuzz=3 --ignore-whitespace
# Footer with tabs and spaces
"#;
let parsed = series::parse(text);
let series = parsed.quilt_tree();
assert_eq!(series.syntax().to_string(), text);
let patches: Vec<_> = series.patch_entries().collect();
assert_eq!(patches.len(), 3);
assert_eq!(patches[0].name(), Some("patch1.patch".to_string()));
assert_eq!(patches[0].option_strings(), vec!["-p1", "--reverse"]);
assert_eq!(patches[1].name(), Some("patch2.patch".to_string()));
assert_eq!(patches[1].option_strings(), Vec::<String>::new());
assert_eq!(patches[2].name(), Some("patch3.patch".to_string()));
assert_eq!(
patches[2].option_strings(),
vec!["-p2", "--fuzz=3", "--ignore-whitespace"]
);
}
#[test]
fn test_unicode_and_special_characters() {
let text = "# Pätch sériès with ünïcødé\npatch-ñame.patch\n# Comment with émojis 🚀\nspëcial-patch.patch -p1\n";
let parsed = series::parse(text);
assert!(parsed.errors().is_empty());
let series = parsed.quilt_tree();
assert_eq!(series.syntax().to_string(), text);
let patches: Vec<_> = series.patch_entries().collect();
assert_eq!(patches.len(), 2);
assert_eq!(patches[0].name(), Some("patch-ñame.patch".to_string()));
assert_eq!(patches[1].name(), Some("spëcial-patch.patch".to_string()));
let comments: Vec<_> = series.comment_lines().collect();
assert_eq!(comments.len(), 2);
assert_eq!(comments[0].text(), "Pätch sériès with ünïcødé");
assert_eq!(comments[1].text(), "Comment with émojis 🚀");
}
#[test]
fn test_large_series_performance() {
let mut text = String::new();
for i in 0..1000 {
text.push_str(&format!("patch-{:04}.patch -p1 --reverse\n", i));
if i % 100 == 0 {
text.push_str(&format!("# Batch {}\n", i / 100));
}
}
let start = std::time::Instant::now();
let parsed = series::parse(&text);
let parse_time = start.elapsed();
println!("Parse time for 1000 patches: {:?}", parse_time);
let mut series = parsed.quilt_tree_mut();
assert_eq!(series.patch_entries().count(), 1000);
assert_eq!(series.comment_lines().count(), 10);
let start = std::time::Instant::now();
series.insert(500, "new-patch.patch", vec!["-p2".to_string()]);
let modify_time = start.elapsed();
println!("Modify time for insert at position 500: {:?}", modify_time);
assert_eq!(series.patch_entries().count(), 1001);
}
#[test]
fn test_thread_safety_and_concurrent_access() {
let text = "patch1.patch\npatch2.patch -p1\n# Comment\npatch3.patch\n";
let parsed = series::parse(text);
let green_node = parsed.green().clone();
let green_arc = Arc::new(green_node);
let mut handles = vec![];
for i in 0..5 {
let green_clone = Arc::clone(&green_arc);
let handle = thread::spawn(move || {
let mut series = SeriesFile::new_root_mut((*green_clone).clone());
let patches: Vec<_> = series.patch_entries().collect();
assert_eq!(patches.len(), 3);
assert_eq!(patches[0].name(), Some("patch1.patch".to_string()));
series.insert(i % 3, &format!("thread-{}.patch", i), Vec::<&str>::new());
assert_eq!(series.patch_entries().count(), 4);
let green_node: rowan::GreenNode = series.syntax().green().clone().into();
green_node
});
handles.push(handle);
}
let mut results = vec![];
for handle in handles {
results.push(handle.join().unwrap());
}
for (i, result_green) in results.iter().enumerate() {
let result = SeriesFile::new_root(result_green.clone());
let patches: Vec<_> = result.patch_entries().collect();
assert_eq!(patches.len(), 4);
let thread_patch = patches
.iter()
.find(|p| p.name().as_deref() == Some(&format!("thread-{}.patch", i)));
assert!(thread_patch.is_some());
}
let original = SeriesFile::new_root((*green_arc).clone());
assert_eq!(original.patch_entries().count(), 3);
}
#[test]
fn test_error_conditions_and_edge_cases() {
let parsed = series::parse("patch1.patch\npatch2.patch\n");
let mut series = parsed.quilt_tree_mut();
let result = series.remove("nonexistent.patch");
assert!(!result);
let result = series.set_options("nonexistent.patch", vec!["-p1".to_string()]);
assert!(!result);
series.insert(1000, "new.patch", Vec::<&str>::new()); let patches: Vec<_> = series.patch_entries().collect();
assert_eq!(patches.len(), 3); assert_eq!(patches[2].name(), Some("new.patch".to_string()));
}
#[test]
fn test_complex_option_parsing() {
let text = r#"patch1.patch -p1 --reverse --fuzz=3 --ignore-whitespace
patch2.patch --binary --unified=5
patch3.patch -p0 -R -F3 --posix
"#;
let parsed = series::parse(text);
let series = parsed.quilt_tree();
let patches: Vec<_> = series.patch_entries().collect();
assert_eq!(
patches[0].option_strings(),
vec!["-p1", "--reverse", "--fuzz=3", "--ignore-whitespace"]
);
assert_eq!(patches[1].option_strings(), vec!["--binary", "--unified=5"]);
assert_eq!(
patches[2].option_strings(),
vec!["-p0", "-R", "-F3", "--posix"]
);
}
#[test]
fn test_patch_name_modification() {
let parsed = series::parse("old-name.patch -p1\n");
let series = parsed.quilt_tree_mut();
let patches: Vec<_> = series.patch_entries().collect();
let patch = &patches[0];
patch.set_name("new-name.patch");
println!("Patch name after set_name: {:?}", patch.name());
println!("Options after set_name: {:?}", patch.option_strings());
assert_eq!(patch.name(), Some("new-name.patch".to_string()));
}
#[test]
fn test_builder_comprehensive() {
let series = series::SeriesBuilder::new()
.add_comment("Generated series file")
.add_comment("") .add_patch("001-fix.patch", vec![])
.add_patch("002-feature.patch", vec!["-p1".to_string()])
.add_comment("Security patches")
.add_patch(
"CVE-2023-1234.patch",
vec!["-p2".to_string(), "--reverse".to_string()],
)
.add_patch("003-cleanup.patch", vec!["--fuzz=3".to_string()])
.build();
let patches: Vec<_> = series.patch_entries().collect();
let comments: Vec<_> = series.comment_lines().collect();
assert_eq!(patches.len(), 4);
assert_eq!(comments.len(), 3);
assert_eq!(patches[0].name(), Some("001-fix.patch".to_string()));
assert_eq!(patches[1].option_strings(), vec!["-p1"]);
assert_eq!(patches[2].name(), Some("CVE-2023-1234.patch".to_string()));
assert_eq!(patches[2].option_strings(), vec!["-p2", "--reverse"]);
assert_eq!(comments[0].text(), "Generated series file");
assert_eq!(comments[1].text(), ""); assert_eq!(comments[2].text(), "Security patches");
}
#[test]
fn test_roundtrip_stability() {
let original_text = r#"# Complex series file
patch1.patch -p1 --reverse
# Comment with weird spacing
patch2.patch -p2 --fuzz=3
patch3.patch
# Final comment
"#;
let parsed1 = series::parse(original_text);
let mut series1 = parsed1.quilt_tree_mut();
series1.insert(1, "inserted.patch", vec!["-p0".to_string()]);
let serialized = series1.syntax().to_string();
let parsed2 = series::parse(&serialized);
let series2 = parsed2.quilt_tree();
let patches1: Vec<_> = series1.patch_entries().collect();
let patches2: Vec<_> = series2.patch_entries().collect();
assert_eq!(patches1.len(), patches2.len());
for (p1, p2) in patches1.iter().zip(patches2.iter()) {
assert_eq!(p1.name(), p2.name());
assert_eq!(p1.option_strings(), p2.option_strings());
}
}
#[test]
fn test_memory_efficiency() {
let text = "patch1.patch\npatch2.patch\npatch3.patch\n";
let parsed = series::parse(text);
let mut series = parsed.quilt_tree_mut();
let green1 = parsed.green().clone();
assert_eq!(series.patch_entries().count(), 3);
series.insert(1, "new.patch", Vec::<&str>::new());
assert_eq!(series.patch_entries().count(), 4);
let parsed_modified = series::parse(&series.syntax().to_string());
let green2 = parsed_modified.green();
assert_ne!(&green1, green2);
let patches: Vec<_> = series.patch_entries().collect();
assert_eq!(patches[0].name(), Some("patch1.patch".to_string()));
assert_eq!(patches[1].name(), Some("new.patch".to_string()));
assert_eq!(patches[2].name(), Some("patch2.patch".to_string()));
assert_eq!(patches[3].name(), Some("patch3.patch".to_string()));
}