Merge pull request #107 from fiddlerwoaroof/wave/i-import

Wave I: five silent import defects (jc95, 5hfh, l1f5, jcfs, 2cw9)
This commit is contained in:
Ed Langley
2026-09-21 16:23:46 -07:00
committed by GitHub
5 changed files with 628 additions and 75 deletions
+49 -44
View File
@@ -1,5 +1,5 @@
//! Report-only bug-hunt reproductions: assert the intended behavior, without fixes.
//! Run with `cargo test --test bug_hunt_reproductions -- --ignored --nocapture`.
//! Bug-hunt reproductions. An `#[ignore]`d one is still open; un-ignoring it is
//! part of the fix. Open ones: `cargo test --test bug_hunt_reproductions -- --ignored`.
use improvise_io::import::csv_parser::parse_csv_bytes_with_warnings;
use improvise_io::{
@@ -46,30 +46,37 @@ fn deleted_default_stays_deleted() {
);
}
#[test]
#[ignore = "improvise-jc95: known bug reproduction"]
fn csv_nonfinite_is_not_zero() {
for token in ["NaN", "inf", "-inf", "1e999"] {
let (r, _) = parse_csv_bytes_with_warnings(format!("Value\n{token}\n").as_bytes()).unwrap();
assert_ne!(r[0]["Value"].as_f64(), Some(0.0), "{token} became zero");
assert_eq!(
r[0]["Value"].as_str(),
Some(token),
"{token} must survive as its own text, not as some other number"
);
}
}
#[test]
#[ignore = "improvise-5hfh: known bug reproduction"]
fn duplicate_csv_headers_not_silent() {
let result = parse_csv_bytes_with_warnings(b"A,A\n1,2\n");
let (records, warnings) = parse_csv_bytes_with_warnings(b"A,A\n1,2\n").unwrap();
let fields = records[0].as_object().unwrap();
assert_eq!(
fields.len(),
2,
"both columns must survive as separate fields: {fields:?}"
);
assert_eq!(fields["A"].as_f64(), Some(1.0), "{fields:?}");
assert_eq!(fields["A_2"].as_f64(), Some(2.0), "{fields:?}");
assert!(
match result {
Err(error) => error.to_string().to_lowercase().contains("duplicate"),
Ok((_, warnings)) => warnings.iter().any(|warning| {
warning.to_lowercase().contains("duplicate") && warning.contains("A")
}),
},
"duplicate headers silently overwrite a field"
warnings
.iter()
.any(|warning| warning.to_lowercase().contains("duplicate") && warning.contains("A")),
"the rename must be reported: {warnings:?}"
);
}
#[test]
#[ignore = "improvise-l1f5: known bug reproduction"]
fn blank_measure_still_infers_numeric() {
use improvise_io::import::{analyzer::FieldKind, wizard::ImportPipeline};
let (records, _) = parse_csv_bytes_with_warnings(b"Region,Revenue\nEast,10\nWest,\n").unwrap();
@@ -78,7 +85,6 @@ fn blank_measure_still_infers_numeric() {
assert_eq!(proposal.kind, FieldKind::Measure);
}
#[test]
#[ignore = "improvise-2cw9: known bug reproduction"]
fn repeated_import_coordinates_not_silently_overwritten() {
use improvise_io::import::wizard::ImportPipeline;
let (records, _) =
@@ -86,24 +92,23 @@ fn repeated_import_coordinates_not_silently_overwritten() {
let (wb, warnings) = ImportPipeline::new(serde_json::Value::Array(records))
.build_model_with_warnings()
.unwrap();
let total: f64 = wb
let values: Vec<f64> = wb
.model
.data
.iter_cells()
.filter_map(|(_, v)| v.as_f64())
.sum();
assert!(
total == 30.0
|| warnings.iter().any(|warning| {
let warning = warning.to_lowercase();
(warning.contains("duplicate")
|| warning.contains("overwrit")
|| warning.contains("overwrot"))
&& warning.contains("east")
&& warning.contains("revenue")
}),
"total {total}, no warning {warnings:?}"
.collect();
assert_eq!(
values,
[30.0],
"the two records are one cell and neither may be dropped"
);
let warning = warnings
.iter()
.find(|w| w.to_lowercase().contains("duplicate"))
.unwrap_or_else(|| panic!("summing is a decision and is reported: {warnings:?}"));
assert!(warning.contains("Region=East"), "{warning}");
assert!(warning.contains("_Measure=Revenue"), "{warning}");
}
#[test]
#[ignore = "improvise-55v2: known bug reproduction"]
@@ -168,25 +173,25 @@ fn label_stays_off_axes_in_new_view() {
assert_eq!(w.create_view("Other").axis_of("Description"), Axis::None);
}
#[test]
#[ignore = "improvise-k8i: known bug reproduction"]
fn long_csv_row_is_not_silently_dropped() {
let r = parse_csv_bytes_with_warnings(b"A,B\n1,2,3\n");
let (records, warnings) = parse_csv_bytes_with_warnings(b"A,B\n1,2,3\n4,5\n1,2,3,4\n").unwrap();
assert_eq!(records.len(), 3, "no row is discarded outright");
let warning = warnings
.iter()
.find(|w| w.contains("more fields"))
.unwrap_or_else(|| panic!("extra values dropped silently: {warnings:?}"));
assert!(
match r {
Err(error) => {
let message = error.to_string().to_lowercase();
message.contains("3")
&& message.contains("2")
&& (message.contains("field") || message.contains("column"))
}
Ok((_, warnings)) => warnings.iter().any(|warning| {
let warning = warning.to_lowercase();
warning.contains("3")
&& warning.contains("2")
&& (warning.contains("field") || warning.contains("column"))
}),
},
"extra value 3 dropped silently"
warning.contains("2 row(s)") && warning.contains("2 header"),
"the warning counts the affected rows and the header width: {warning}"
);
assert!(
warning.contains("widest row: 4"),
"the warning says how much was dropped: {warning}"
);
let (_, clean) = parse_csv_bytes_with_warnings(b"A,B\n1,2\n").unwrap();
assert!(
clean.is_empty(),
"control: a full row is not long: {clean:?}"
);
}
// Passing control: embedded carriage returns already survive persistence.