use std::io::Write;
use dataprof::Profiler;
fn write_csv(contents: &str) -> tempfile::NamedTempFile {
let mut file = tempfile::Builder::new().suffix(".csv").tempfile().unwrap();
write!(file, "{contents}").unwrap();
file.flush().unwrap();
file
}
#[test]
fn ragged_rows_are_counted_not_swallowed() {
let csv = write_csv("name,age,city\nAlice,25,NYC\nBob,30\nCarol,35,LA,EXTRA\nDave,40,SF\n");
let report = Profiler::new()
.analyze_file(csv.path())
.expect("flexible parsing recovers ragged rows");
assert_eq!(report.execution.rows_processed, 4);
assert_eq!(
report.execution.ragged_row_count, 2,
"one short and one over-long row must both count as ragged"
);
}
#[test]
fn well_formed_csv_reports_zero_ragged_rows() {
let csv = write_csv("name,age,city\nAlice,25,NYC\nBob,30,LA\n");
let report = Profiler::new()
.analyze_file(csv.path())
.expect("a clean file profiles cleanly");
assert_eq!(
report.execution.ragged_row_count, 0,
"a file with no field-count violations is not ragged"
);
}
#[test]
fn truly_broken_example_is_not_reported_clean() {
let report = Profiler::new()
.analyze_file("examples/test_datasets/truly_broken.csv")
.expect("flexible parsing recovers the fixture");
assert!(
report.execution.ragged_row_count > 0,
"truly_broken.csv must surface a structural-violation signal, got {}",
report.execution.ragged_row_count
);
}
#[test]
fn strict_file_parsing_keeps_its_own_diagnostic() {
let csv = write_csv("name,age,city\nAlice,25,NYC\nBob,30\n");
let err = Profiler::new()
.csv_flexible(false)
.analyze_file(csv.path())
.expect_err("strict parsing must reject a ragged record");
let message = err.to_string();
assert!(
!message.contains("All engines failed"),
"a deterministic parse rejection must not be buried: {message}"
);
assert!(message.contains("3 fields"), "unexpected error: {message}");
}
#[cfg(feature = "async-streaming")]
mod async_parity {
use super::write_csv;
use dataprof::{AsyncSourceInfo, BytesSource, FileFormat, Profiler};
const RAGGED: &[u8] = b"name,age,city\nAlice,25,NYC\nBob,30\nCarol,35,LA,EXTRA\nDave,40,SF\n";
const CLEAN: &[u8] = b"name,age,city\nAlice,25,NYC\nBob,30,LA\n";
fn source(data: &'static [u8]) -> BytesSource {
BytesSource::new(
bytes::Bytes::from_static(data),
AsyncSourceInfo::new("ragged-parity", FileFormat::Csv)
.size_hint(Some(data.len() as u64)),
)
}
#[tokio::test]
async fn async_bytes_match_the_file_path_ragged_count() {
let file = write_csv(std::str::from_utf8(RAGGED).unwrap());
let from_file = Profiler::new().analyze_file(file.path()).unwrap();
let from_stream = Profiler::new()
.profile_stream(source(RAGGED))
.await
.unwrap();
assert_eq!(from_file.execution.ragged_row_count, 2);
assert_eq!(
from_stream.execution.ragged_row_count, from_file.execution.ragged_row_count,
"async bytes and the file path must agree on the same input"
);
assert_eq!(
from_stream.execution.rows_processed,
from_file.execution.rows_processed
);
}
#[tokio::test]
async fn async_bytes_report_clean_input_as_clean() {
let report = Profiler::new().profile_stream(source(CLEAN)).await.unwrap();
assert_eq!(report.execution.ragged_row_count, 0);
assert_eq!(report.execution.error_count, 0);
}
#[tokio::test]
async fn async_bytes_honor_csv_flexible_false() {
let err = Profiler::new()
.csv_flexible(false)
.profile_stream(source(RAGGED))
.await
.expect_err("strict parsing must reject a ragged record");
assert!(
err.to_string().contains("csv_flexible"),
"strict failure must name the recovering option: {err}"
);
}
}