use std::fs::File;
use std::io::Write;
use std::process::Command;
use tempfile::NamedTempFile;
fn parsm_command() -> Command {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_parsm"));
cmd.env("RUST_LOG", "parsm=error");
cmd
}
#[test]
fn test_csv_field_selection_by_header() {
let input = "name,age,occupation\nTom,45,engineer\nAlice,30,doctor";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("name")
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Tom"));
assert!(stdout.contains("Alice"));
assert!(!stdout.contains("45")); }
#[test]
fn test_csv_field_selection_by_index() {
let input = "Tom,45,engineer\nAlice,30,doctor";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("field_0")
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Tom"));
assert!(stdout.contains("Alice"));
assert!(!stdout.contains("45")); }
#[test]
fn test_csv_header_detection() {
let input = "name,age,occupation\nTom,45,engineer\nAlice,30,doctor";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("name")
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(!stdout.contains("name"));
assert!(stdout.contains("Tom"));
assert!(stdout.contains("Alice"));
}
#[test]
fn test_csv_no_header_detection() {
let input = "Tom,45,engineer\nAlice,30,doctor";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("field_0")
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Tom"));
assert!(stdout.contains("Alice"));
let line_count = stdout
.lines()
.filter(|line| !line.trim().is_empty())
.count();
assert_eq!(line_count, 2);
}
#[test]
fn test_csv_template_with_headers() {
let input = "name,age,occupation\nTom,45,engineer\nAlice,30,doctor";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("[$name is $age years old]")
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Tom is 45 years old"));
assert!(stdout.contains("Alice is 30 years old"));
}
#[test]
fn test_csv_filter_with_headers() {
let input = "name,age,occupation\nTom,45,engineer\nAlice,30,doctor\nBob,35,engineer";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("occupation == \"engineer\" {$name}")
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Tom"));
assert!(stdout.contains("Bob"));
assert!(!stdout.contains("Alice")); }
#[test]
fn test_csv_multiple_field_selection() {
let input = "Alice,30,Engineer\nBob,25,Designer";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("field_2") .stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Engineer"));
assert!(stdout.contains("Designer"));
}
#[test]
fn test_csv_numeric_filtering() {
let input = "Alice,30,Engineer\nBob,25,Designer\nCharlie,35,Manager\nDana,22,Intern";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("field_1 > 27") .stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Alice"));
assert!(stdout.contains("Charlie"));
assert!(!stdout.contains("Bob"));
assert!(!stdout.contains("Dana"));
}
#[test]
fn test_csv_string_filtering() {
let input = "Alice,30,Engineer\nBob,25,Designer\nCharlie,35,Manager\nEva,28,Engineer";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("field_2 == \"Engineer\"") .stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Alice"));
assert!(stdout.contains("Eva"));
assert!(!stdout.contains("Bob"));
assert!(!stdout.contains("Charlie"));
}
#[test]
fn test_csv_template_replacement_indexed() {
let input = "Alice,30,Engineer\nBob,25,Designer";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("field_1 > 20 {Name: $field_0, Age: $field_1, Job: $field_2}")
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Name: Alice, Age: 30, Job: Engineer"));
assert!(stdout.contains("Name: Bob, Age: 25, Job: Designer"));
}
#[test]
fn test_csv_original_input_template() {
let input = "Alice,30,Engineer";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("field_1 > 25 {Person: ${field_0} | Original: ${0}}")
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert_eq!(stdout.trim(), "Person: Alice | Original: Alice,30,Engineer");
}
#[test]
fn test_csv_complex_boolean_logic() {
let input = "Alice,30,Engineer,true\nBob,22,Designer,false\nCharlie,35,Manager,false\nDana,24,Admin,true";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("field_1 > 25 && field_3? {Found: $field_0 ($field_2)}")
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("Found: Alice"));
assert!(!stdout.contains("Bob"));
assert!(!stdout.contains("Charlie"));
assert!(!stdout.contains("Dana"));
}
#[test]
fn test_csv_nonexistent_field() {
let input = "Alice,30,Engineer";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("field_5") .stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert_eq!(stdout.trim(), "");
}
#[test]
fn test_csv_quoted_fields() {
let input = r#""Smith, John",35,"Senior Engineer, Tech Lead""#;
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("field_1") .stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert_eq!(stdout.trim(), "35");
}
#[test]
fn test_csv_empty_fields() {
let input = "Alice,,Engineer\n,25,Designer\nCharlie,35,\n";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("field_0") .stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
let lines: Vec<&str> = stdout.trim().split('\n').collect();
assert_eq!(lines.len(), 3);
assert_eq!(lines[0], "Alice");
assert_eq!(lines[1], ""); assert_eq!(lines[2], "Charlie");
}
#[test]
fn test_csv_numeric_comparisons() {
let test_cases = vec![
("Alice,30,5000", "field_1 > 25", true),
("Bob,22,3000", "field_1 < 25", true),
("Charlie,30,5000", "field_2 >= 5000", true),
("Dana,28,4500", "field_2 <= 4000", false),
("Eve,35,6000", "field_1 == 35", true),
("Frank,25,3500", "field_1 != 30", true),
];
for (input, filter, should_match) in test_cases {
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg(format!("{filter} {{Match: $field_0}}"))
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed for input: {input}");
let stdout = String::from_utf8_lossy(&output.stdout);
if should_match {
assert!(
stdout.contains("Match:"),
"Expected match for: {input} with filter: {filter}"
);
} else {
assert_eq!(
stdout.trim(),
"",
"Expected no match for: {input} with filter: {filter}"
);
}
}
}
#[test]
fn test_csv_different_row_lengths() {
let input = "Alice,30\nBob,25,Designer,Manager\nCharlie,35,Engineer";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("field_1") .stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
let lines: Vec<&str> = stdout.trim().split('\n').collect();
assert_eq!(lines.len(), 3);
assert_eq!(lines[0], "30");
assert_eq!(lines[1], "25");
assert_eq!(lines[2], "35");
}
#[test]
fn test_csv_single_column() {
let input = "Alice\nBob\nCharlie";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("field_0") .stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
if !stdout.trim().is_empty() {
let lines: Vec<&str> = stdout.trim().split('\n').collect();
assert!(lines.len() <= 3); }
}
}
#[test]
fn test_csv_malformed_input() {
let input = r#"Alice,30,"Engineer
Bob,25,Designer"#;
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("field_0 == \"Bob\"")
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(
output.status.success(),
"parsm should handle malformed CSV gracefully"
);
}
#[test]
fn test_csv_braced_field_syntax() {
let input = "Alice,30,Engineer";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("field_1 > 25 {Name: ${field_0}, Age: ${field_1}, Job: ${field_2}}")
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert_eq!(stdout.trim(), "Name: Alice, Age: 30, Job: Engineer");
}
#[test]
fn test_csv_headers_all_data_rows() {
let input = "Name,Age,Job\nAlice,30,Engineer\nBob,25,Designer\nCharlie,35,Manager";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("name") .stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(!stdout.contains("Name"));
assert!(stdout.contains("Alice"));
assert!(stdout.contains("Bob"));
assert!(stdout.contains("Charlie"));
let line_count = stdout
.lines()
.filter(|line| !line.trim().is_empty())
.count();
assert_eq!(line_count, 3); }
#[test]
fn test_csv_forced_format() {
let test_cases = vec![
(
"name,age,occupation\nAlice,30,Engineer",
r#""name""#,
"Alice",
),
("name,age,occupation\nAlice,30,Engineer", r#""age""#, "30"),
("Alice,30,Engineer", r#""field_0""#, "Alice"),
("Alice,30,Engineer", r#""field_1""#, "30"),
("Alice,30,Engineer", r#""field_2""#, "Engineer"),
("\"Smith, John\",30,Engineer", r#""field_0""#, "Smith, John"),
("\"Smith, John\",30,Engineer", r#""field_1""#, "30"),
(
"Alice,30,Engineer",
r#"{Name: ${1}, Age: ${2}}"#,
"Name: Alice, Age: 30",
),
(
"Alice,30,Engineer",
r#"{${field_0} is ${field_1} years old}"#,
"Alice is 30 years old",
),
];
for (input, expression, expected) in test_cases {
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("--csv")
.arg(expression)
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(
output.status.success(),
"CSV forced format failed for input '{}' with expression '{}': {:?}",
input,
expression,
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let result = stdout.trim();
assert_eq!(
result, expected,
"Failed for CSV forced input '{input}' with expression '{expression}'",
);
}
}
#[test]
fn test_csv_forced_format_filtering() {
let test_cases = vec![
("Alice,30,Engineer", "field_1 > \"25\"", true),
("Bob,20,Student", "field_1 > \"25\"", false),
("Alice,30,Engineer", r#"field_0 == "Alice""#, true),
("Bob,20,Student", r#"field_0 == "Alice""#, false),
("Alice,30,Engineer", r#"field_2 *= "Eng""#, true),
("Bob,20,Student", r#"field_2 *= "Eng""#, false),
];
for (input, filter, should_match) in test_cases {
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("--csv")
.arg(filter)
.arg(r#"{match}"#)
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(
output.status.success(),
"CSV forced format filtering failed for input '{}' with filter '{}': {:?}",
input,
filter,
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
let result = stdout.trim();
if should_match {
assert_eq!(
result, "match",
"Expected match for CSV forced filter '{filter}' with input '{input}'",
);
} else {
assert_eq!(
result, "",
"Expected empty output for CSV forced filter '{filter}' with input '{input}'",
);
}
}
}
#[test]
fn test_csv_multiline_output() {
let input = "name,age,occupation\nAlice,30,Engineer\nBob,25,Designer\nCharlie,35,Manager";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
let lines: Vec<&str> = stdout.trim().split('\n').collect();
assert_eq!(lines.len(), 4); assert_eq!(lines[0], "name,age,occupation");
assert_eq!(lines[1], "Alice,30,Engineer");
assert_eq!(lines[2], "Bob,25,Designer");
assert_eq!(lines[3], "Charlie,35,Manager");
let filter_output = parsm_command()
.arg("age > 27") .stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(
filter_output.status.success(),
"parsm filter failed: {filter_output:?}"
);
let filter_stdout = String::from_utf8_lossy(&filter_output.stdout);
assert!(filter_stdout.contains("Alice,30,Engineer"));
assert!(filter_stdout.contains("Charlie,35,Manager"));
assert!(!filter_stdout.contains("Bob,25,Designer"));
let header_filter_output = parsm_command()
.arg("--csv")
.arg("field_1 > 27") .stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(
header_filter_output.status.success(),
"parsm header filter failed: {header_filter_output:?}"
);
let header_filter_stdout = String::from_utf8_lossy(&header_filter_output.stdout);
assert!(header_filter_stdout.contains("Alice,30,Engineer"));
assert!(header_filter_stdout.contains("Charlie,35,Manager"));
assert!(!header_filter_stdout.contains("Bob,25,Designer"));
}
#[test]
fn test_csv_multiline_output_field() {
let input = "name,age,occupation\nAlice,30,Engineer\nBob,25,Designer\nCharlie,35,Manager";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(output.status.success(), "parsm failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
let lines: Vec<&str> = stdout.trim().split('\n').collect();
assert_eq!(lines.len(), 4); assert_eq!(lines[0], "name,age,occupation");
assert_eq!(lines[1], "Alice,30,Engineer");
assert_eq!(lines[2], "Bob,25,Designer");
assert_eq!(lines[3], "Charlie,35,Manager");
let filter_output = parsm_command()
.arg("field_1 > 27") .stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(
filter_output.status.success(),
"parsm filter failed: {filter_output:?}"
);
let filter_stdout = String::from_utf8_lossy(&filter_output.stdout);
assert!(filter_stdout.contains("Alice,30,Engineer"));
assert!(filter_stdout.contains("Charlie,35,Manager"));
assert!(!filter_stdout.contains("Bob,25,Designer"));
let header_filter_output = parsm_command()
.arg("--csv")
.arg("field_1 > 27") .stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(
header_filter_output.status.success(),
"parsm header filter failed: {header_filter_output:?}"
);
let header_filter_stdout = String::from_utf8_lossy(&header_filter_output.stdout);
assert!(header_filter_stdout.contains("Alice,30,Engineer"));
assert!(header_filter_stdout.contains("Charlie,35,Manager"));
assert!(!header_filter_stdout.contains("Bob,25,Designer"));
}
#[test]
fn test_csv_output_regression() {
let input = "name,age,active\nAlice,30,true\nBob,25,false\nCharlie,35,true";
let mut file = NamedTempFile::new().expect("create temp file");
write!(file, "{input}").expect("write temp file");
let output = parsm_command()
.arg("--csv")
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(
output.status.success(),
"parsm default output failed: {output:?}"
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert_eq!(stdout.trim(), input);
let filter_output = parsm_command()
.arg("--csv")
.arg("field_2 == \"true\"")
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(
filter_output.status.success(),
"parsm filter failed: {filter_output:?}"
);
let filter_stdout = String::from_utf8_lossy(&filter_output.stdout);
assert!(filter_stdout.contains("Alice,30,true"));
assert!(filter_stdout.contains("Charlie,35,true"));
assert!(!filter_stdout.contains("Bob,25,false"));
let field_output = parsm_command()
.arg("--csv")
.arg("field_0")
.stdin(File::open(file.path()).unwrap())
.output()
.expect("run parsm");
assert!(
field_output.status.success(),
"parsm field selection failed: {field_output:?}"
);
let field_stdout = String::from_utf8_lossy(&field_output.stdout);
assert_eq!(
field_stdout.trim().lines().collect::<Vec<&str>>().join(","),
"Alice,Bob,Charlie"
);
}