fix: gate DTW comparisons on wafer type + recording category; surface load errors
This commit is contained in:
@@ -47,6 +47,36 @@ def test_pause_stop_timer(controller):
|
||||
controller.stop()
|
||||
assert controller.frameIndex == 0
|
||||
|
||||
def test_load_file_emits_error_for_empty_recording(controller, tmp_path):
|
||||
"""A recording with a valid header but zero data rows (e.g. Record was
|
||||
started/stopped without any live frames arriving) must surface a clear
|
||||
loadFileError instead of silently doing nothing."""
|
||||
empty_recording = tmp_path / "live_A00001_20260706_120427.csv"
|
||||
empty_recording.write_text(
|
||||
"Wafer ID=A00001\n"
|
||||
"Acquisition Date=07/06/2026\n"
|
||||
"Label,1,2\n"
|
||||
"X (mm),0.0,10.0\n"
|
||||
"Y (mm),10.0,-10.0\n"
|
||||
"data\n"
|
||||
)
|
||||
|
||||
mock_slot = MagicMock()
|
||||
controller.loadFileError.connect(mock_slot)
|
||||
controller.loadFile(str(empty_recording))
|
||||
|
||||
assert mock_slot.call_count == 1
|
||||
assert "no recorded frames" in mock_slot.call_args[0][0]
|
||||
assert controller.loadedFile == ""
|
||||
|
||||
def test_load_file_switches_to_review_mode(controller):
|
||||
"""Loading a file for playback must always land in review mode, even if
|
||||
the controller was left in live mode from a previous session — otherwise
|
||||
a successful load can be invisible behind a stale Live toggle."""
|
||||
controller._mode = "live"
|
||||
controller.loadFile("tests/fixtures/sample_stream.csv")
|
||||
assert controller.mode == "review"
|
||||
|
||||
def test_compare_files_integration(controller):
|
||||
# Mock the comparisonResult signal
|
||||
mock_slot = MagicMock()
|
||||
@@ -65,6 +95,96 @@ def test_compare_files_integration(controller):
|
||||
assert "series_b" in args
|
||||
assert args["frame_offset"] == 0
|
||||
|
||||
def test_compare_files_uses_full_run_not_first_100_frames(controller, tmp_path):
|
||||
"""compareFiles must score the whole run, not just an initial 100-frame window.
|
||||
|
||||
Regression test: max_frames used to be hard-capped at 100, so a >100-frame
|
||||
run's soak/cool phases were silently excluded from the DTW comparison.
|
||||
"""
|
||||
num_frames = 150
|
||||
csv_path = tmp_path / "long_run.csv"
|
||||
lines = ["Wafer ID=A12", "Acquisition Date=03/26/2025", "Label,1,2,3",
|
||||
"X (mm),0,10,-10", "Y (mm),10,-10,-10", "data"]
|
||||
for i in range(num_frames):
|
||||
t = i * 0.5
|
||||
lines.append(f"{t},{149.0 + i * 0.01},{148.5 + i * 0.01},{150.6 + i * 0.01}")
|
||||
csv_path.write_text("\n".join(lines) + "\n")
|
||||
|
||||
mock_slot = MagicMock()
|
||||
controller.comparisonResult.connect(mock_slot)
|
||||
controller.compareFiles(str(csv_path), str(csv_path))
|
||||
|
||||
assert mock_slot.call_count == 1
|
||||
result = mock_slot.call_args[0][0]
|
||||
assert result["success"] is True
|
||||
assert len(result["series_a"]) == num_frames
|
||||
assert len(result["series_b"]) == num_frames
|
||||
|
||||
def test_compare_files_rejects_mismatched_wafer_types(controller, tmp_path):
|
||||
"""compareFiles must refuse to compare two different wafer families.
|
||||
|
||||
Otherwise the overlap view silently truncates to min(sensor_count) and
|
||||
pairs up unrelated physical sensors between the two wafer types.
|
||||
"""
|
||||
header = ["Wafer ID=A12", "Acquisition Date=03/26/2025", "Label,1,2,3",
|
||||
"X (mm),0,10,-10", "Y (mm),10,-10,-10", "data",
|
||||
"0.0,149.0,148.5,150.6"]
|
||||
file_a = tmp_path / "A00055-20250326.csv"
|
||||
file_b = tmp_path / "X00108-20250326.csv"
|
||||
file_a.write_text("\n".join(header) + "\n")
|
||||
file_b.write_text("\n".join(header) + "\n")
|
||||
|
||||
mock_slot = MagicMock()
|
||||
controller.comparisonResult.connect(mock_slot)
|
||||
controller.compareFiles(str(file_a), str(file_b))
|
||||
|
||||
assert mock_slot.call_count == 1
|
||||
result = mock_slot.call_args[0][0]
|
||||
assert result["success"] is False
|
||||
assert "A" in result["error"] and "X" in result["error"]
|
||||
|
||||
def test_compare_files_rejects_recording_vs_read_mem(controller, tmp_path):
|
||||
"""A live_-prefixed recording must not be compared against a read-memory
|
||||
dump. Regression: stem[0] on "live_P0001_..." is "L", which used to be
|
||||
misreported as a wafer-type mismatch ("L vs P") instead of this clearer
|
||||
recording-vs-read-mem error.
|
||||
"""
|
||||
header = ["Wafer ID=P0001", "Acquisition Date=03/26/2025", "Label,1,2,3",
|
||||
"X (mm),0,10,-10", "Y (mm),10,-10,-10", "data",
|
||||
"0.0,149.0,148.5,150.6"]
|
||||
recording = tmp_path / "live_P0001_20260706_143022.csv"
|
||||
read_mem = tmp_path / "P0001-20260706_143500.csv"
|
||||
recording.write_text("\n".join(header) + "\n")
|
||||
read_mem.write_text("\n".join(header) + "\n")
|
||||
|
||||
mock_slot = MagicMock()
|
||||
controller.comparisonResult.connect(mock_slot)
|
||||
controller.compareFiles(str(recording), str(read_mem))
|
||||
|
||||
assert mock_slot.call_count == 1
|
||||
result = mock_slot.call_args[0][0]
|
||||
assert result["success"] is False
|
||||
assert result["error"] == "Cannot compare a recording file with a read-memory file"
|
||||
|
||||
def test_compare_files_allows_two_recordings_of_same_wafer_type(controller, tmp_path):
|
||||
"""Two live recordings of the same wafer family should compare fine —
|
||||
the "live_" prefix must not be mistaken for the wafer type letter."""
|
||||
header = ["Wafer ID=P0001", "Acquisition Date=03/26/2025", "Label,1,2,3",
|
||||
"X (mm),0,10,-10", "Y (mm),10,-10,-10", "data",
|
||||
"0.0,149.0,148.5,150.6"]
|
||||
file_a = tmp_path / "live_P0001_20260706_143022.csv"
|
||||
file_b = tmp_path / "live_P0002_20260706_150000.csv"
|
||||
file_a.write_text("\n".join(header) + "\n")
|
||||
file_b.write_text("\n".join(header) + "\n")
|
||||
|
||||
mock_slot = MagicMock()
|
||||
controller.comparisonResult.connect(mock_slot)
|
||||
controller.compareFiles(str(file_a), str(file_b))
|
||||
|
||||
assert mock_slot.call_count == 1
|
||||
result = mock_slot.call_args[0][0]
|
||||
assert result["success"] is True
|
||||
|
||||
def test_comparison_result_reaches_qml(qapp, controller):
|
||||
"""The result dict must arrive in QML as a real JS object with fields.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user