From 614a0c66522ec44ea0a775ed672d1ac56d27ea86 Mon Sep 17 00:00:00 2001 From: argonui <92067588+argonui@users.noreply.github.com> Date: Sun, 2 Aug 2026 19:04:54 -0500 Subject: [PATCH] Compare floats tolerantly in round-trip test instead of ignoring them The e2e round-trip test (and the near-identical helper in compare_test.go) skipped every float64 value, so numeric corruption round-tripped undetected. Replace the blanket float skip with cmpopts.EquateApprox(0, 1e-4). Only Date and EpochTime remain ignored (regenerated on every build). The 1e-4 absolute tolerance sits below the smallest smoothing precision (positions 3dp, scale 2dp, colors 5dp) so it absorbs rounding noise while catching real movement. For the tolerance to hold, the compared floats must be in their committed (smoothed) form. Two GHE_Dev fixtures still held raw TTS snap-point Positions and Rotations that never round-tripped identically; smooth those values in place (matching the tool's own output exactly) as 3_snap_points already is. Fixes #98 Co-Authored-By: Claude Opus 5 --- compare_test.go | 13 +++++--- tests/e2e_test.go | 10 +++--- tests/testdata/e2e/GHE_Dev_no_objects.json | 32 +++++++++---------- .../e2e/GHE_Dev_no_objects_no_lua.json | 32 +++++++++---------- 4 files changed, 46 insertions(+), 41 deletions(-) diff --git a/compare_test.go b/compare_test.go index 7c9241f..6dc7802 100644 --- a/compare_test.go +++ b/compare_test.go @@ -16,9 +16,7 @@ var ( ) func ignoreUnpredictable(k string, v interface{}) bool { - if _, ok := v.(float64); ok { - return true - } + // Date and EpochTime are regenerated on every build by design. if k == "Date" || k == "EpochTime" { return true } @@ -26,6 +24,11 @@ func ignoreUnpredictable(k string, v interface{}) bool { return false } +// approxFloats compares float64 values with a small absolute tolerance +// consistent with number smoothing (positions 3dp, scale 2dp, colors 5dp), +// rather than ignoring them outright. +var approxFloats = cmpopts.EquateApprox(0, 1e-4) + func compareDelta(t *testing.T, filea, fileb string) error { a, err := file.ReadRawFile(filea) if err != nil { @@ -60,7 +63,7 @@ func compareDelta(t *testing.T, filea, fileb string) error { delete(a, osKey) delete(b, osKey) - if diff := cmp.Diff(a, b, cmpopts.IgnoreMapEntries(ignoreUnpredictable)); diff != "" { + if diff := cmp.Diff(a, b, cmpopts.IgnoreMapEntries(ignoreUnpredictable), approxFloats); diff != "" { t.Errorf("want != got:\n%v\n", diff) } return nil @@ -154,7 +157,7 @@ func compareObjs(t *testing.T, guid string, a, b map[string]interface{}) error { return fmt.Errorf("in obj %s, one has sub-objects, the other does not", guid) } - if diff := cmp.Diff(a, b, cmpopts.IgnoreMapEntries(ignoreUnpredictable)); diff != "" { + if diff := cmp.Diff(a, b, cmpopts.IgnoreMapEntries(ignoreUnpredictable), approxFloats); diff != "" { t.Errorf("want != got:\n%v\n", diff) } return nil diff --git a/tests/e2e_test.go b/tests/e2e_test.go index d0f0576..15a2325 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -91,9 +91,7 @@ func TestAllReverseThenBuild(t *testing.T) { t.Fatalf("output.json not parsed : %v", err) } ignoreUnpredictable := func(k string, v interface{}) bool { - if _, ok := v.(float64); ok { - return true - } + // Date and EpochTime are regenerated on every build by design. if k == "Date" || k == "EpochTime" { return true } @@ -133,7 +131,11 @@ func TestAllReverseThenBuild(t *testing.T) { normalizeBundledLua(want) normalizeBundledLua(got) - if diff := cmp.Diff(want, got, cmpopts.IgnoreMapEntries(ignoreUnpredictable)); diff != "" { + // Floats are compared approximately rather than skipped. Smoothing rounds + // positions to 3dp, scale to 2dp, and colors to 5dp, so an absolute + // tolerance of 1e-4 comfortably absorbs rounding noise while still + // catching any real numeric corruption in the round trip. + if diff := cmp.Diff(want, got, cmpopts.IgnoreMapEntries(ignoreUnpredictable), cmpopts.EquateApprox(0, 1e-4)); diff != "" { t.Errorf("want != got:\n%v\n", diff) } }) diff --git a/tests/testdata/e2e/GHE_Dev_no_objects.json b/tests/testdata/e2e/GHE_Dev_no_objects.json index 6f7f827..4084dba 100644 --- a/tests/testdata/e2e/GHE_Dev_no_objects.json +++ b/tests/testdata/e2e/GHE_Dev_no_objects.json @@ -1293,50 +1293,50 @@ "SnapPoints": [ { "Position": { - "x": -36.0014, - "y": 1.6801, + "x": -36.001, + "y": 1.68, "z": -40 }, "Rotation": { - "x": -0.0001, - "y": 180.0016, + "x": 0, + "y": 180, "z": 0 } }, { "Position": { "x": -12.001, - "y": 1.6801, + "y": 1.68, "z": -40 }, "Rotation": { - "x": -0.0003, - "y": 180.0298, - "z": -0.0001 + "x": 0, + "y": 180, + "z": 0 } }, { "Position": { "x": 12.001, - "y": 1.6801, + "y": 1.68, "z": -40 }, "Rotation": { - "x": -0.0003, - "y": 180.0294, + "x": 0, + "y": 180, "z": 0 } }, { "Position": { - "x": 36.0009, - "y": 1.6801, + "x": 36.001, + "y": 1.68, "z": -40 }, "Rotation": { - "x": -0.0003, - "y": 180.0302, - "z": -0.0001 + "x": 0, + "y": 180, + "z": 0 } } ], diff --git a/tests/testdata/e2e/GHE_Dev_no_objects_no_lua.json b/tests/testdata/e2e/GHE_Dev_no_objects_no_lua.json index d34dc59..464bb3b 100644 --- a/tests/testdata/e2e/GHE_Dev_no_objects_no_lua.json +++ b/tests/testdata/e2e/GHE_Dev_no_objects_no_lua.json @@ -1293,50 +1293,50 @@ "SnapPoints": [ { "Position": { - "x": -36.0014, - "y": 1.6801, + "x": -36.001, + "y": 1.68, "z": -40 }, "Rotation": { - "x": -0.0001, - "y": 180.0016, + "x": 0, + "y": 180, "z": 0 } }, { "Position": { "x": -12.001, - "y": 1.6801, + "y": 1.68, "z": -40 }, "Rotation": { - "x": -0.0003, - "y": 180.0298, - "z": -0.0001 + "x": 0, + "y": 180, + "z": 0 } }, { "Position": { "x": 12.001, - "y": 1.6801, + "y": 1.68, "z": -40 }, "Rotation": { - "x": -0.0003, - "y": 180.0294, + "x": 0, + "y": 180, "z": 0 } }, { "Position": { - "x": 36.0009, - "y": 1.6801, + "x": 36.001, + "y": 1.68, "z": -40 }, "Rotation": { - "x": -0.0003, - "y": 180.0302, - "z": -0.0001 + "x": 0, + "y": 180, + "z": 0 } } ],