Artifact 271795bd7550314039112370f43ebdb3df13510628b1b4e7937a281d46b65b6e:
- File
internal/semdiff/semdiff_test.go
— part of check-in
[cb17edbefa]
at
2026-08-17 20:32:05
on branch trunk
— web: report render time in the footer
Fossil publishes its own ('This page was generated in about 0.003s') and that is what made our timeline regression measurable against it. Same number, same place, every page.
semdiff gains data-volatile so the disposability harness compares state rather than the stopwatch — without it a wipe-and-reingest fails on '<1ms' vs '4ms', which is true about the clock and false about the index. (user: dmestas, size: 4343) [annotate] [blame] [check-ins using]
package semdiff import ( "strings" "testing" ) // --- JSON canonicalization --- func TestCanonJSONSortsKeys(t *testing.T) { a, err := CanonJSON([]byte(`{"b":1,"a":2}`)) if err != nil { t.Fatal(err) } b, err := CanonJSON([]byte(`{"a":2,"b":1}`)) if err != nil { t.Fatal(err) } if string(a) != string(b) { t.Fatalf("key order changed canon form:\n%s\n%s", a, b) } } func TestCanonJSONNormalizesTimestamps(t *testing.T) { // Same instant, different representations. a, err := CanonJSON([]byte(`{"t":"2026-08-14T10:00:00+02:00"}`)) if err != nil { t.Fatal(err) } b, err := CanonJSON([]byte(`{"t":"2026-08-14 08:00:00Z"}`)) if err != nil { t.Fatal(err) } if string(a) != string(b) { t.Fatalf("timestamps not unified:\n%s\n%s", a, b) } if !strings.Contains(string(a), "2026-08-14T08:00:00Z") { t.Fatalf("want UTC RFC3339, got %s", a) } } func TestCanonJSONLeavesNonTimestampsAlone(t *testing.T) { out, err := CanonJSON([]byte(`{"v":"1.2.3","n":42,"s":"2026 was a year"}`)) if err != nil { t.Fatal(err) } for _, want := range []string{`"1.2.3"`, `42`, `"2026 was a year"`} { if !strings.Contains(string(out), want) { t.Fatalf("mangled %s in %s", want, out) } } } func TestCanonJSONRejectsInvalid(t *testing.T) { if _, err := CanonJSON([]byte(`{nope`)); err == nil { t.Fatal("want parse error, got nil") } } // --- HTML canonicalization --- func TestCanonHTMLAttributeOrder(t *testing.T) { a, err := CanonHTML([]byte(`<div id="x" class="y">hi</div>`)) if err != nil { t.Fatal(err) } b, err := CanonHTML([]byte(`<div class="y" id="x">hi</div>`)) if err != nil { t.Fatal(err) } if string(a) != string(b) { t.Fatalf("attr order changed canon form:\n%s\n%s", a, b) } } func TestCanonHTMLWhitespace(t *testing.T) { a, err := CanonHTML([]byte("<p>hello world</p>")) if err != nil { t.Fatal(err) } b, err := CanonHTML([]byte("<p>\n hello world\n</p>")) if err != nil { t.Fatal(err) } if string(a) != string(b) { t.Fatalf("whitespace changed canon form:\n%q\n%q", a, b) } } func TestCanonHTMLPreservesPre(t *testing.T) { a, err := CanonHTML([]byte("<pre>a b\nc</pre>")) if err != nil { t.Fatal(err) } b, err := CanonHTML([]byte("<pre>a b c</pre>")) if err != nil { t.Fatal(err) } if string(a) == string(b) { t.Fatal("pre content collapsed — code blocks must stay byte-exact") } } func TestCanonHTMLContentDifferenceSurvives(t *testing.T) { a, _ := CanonHTML([]byte(`<p>alpha</p>`)) b, _ := CanonHTML([]byte(`<p>beta</p>`)) if string(a) == string(b) { t.Fatal("distinct content canonicalized to same form") } } // --- Diff --- func TestDiffEqualAfterCanon(t *testing.T) { d, err := Diff(JSON, []byte(`{"b":1,"a":2}`), []byte(`{"a":2,"b":1}`)) if err != nil { t.Fatal(err) } if d != "" { t.Fatalf("want no diff, got:\n%s", d) } } func TestDiffReportsFirstDivergence(t *testing.T) { d, err := Diff(JSON, []byte(`{"a":1}`), []byte(`{"a":2}`)) if err != nil { t.Fatal(err) } if d == "" || !strings.Contains(d, "1") || !strings.Contains(d, "2") { t.Fatalf("diff should show both sides, got:\n%s", d) } } func TestDiffHTMLKind(t *testing.T) { d, err := Diff(HTML, []byte(`<div class="y" id="x">a</div>`), []byte(`<div id="x" class="y">a</div>`)) if err != nil { t.Fatal(err) } if d != "" { t.Fatalf("want no diff, got:\n%s", d) } } // Render timings differ between two runs of the same request by // definition. Without an escape hatch the disposability harness reads // "<1ms" vs "4ms" as derived state that failed to converge — a true // statement about the clock and a false one about the index. func TestVolatileContentIsIgnored(t *testing.T) { a := []byte(`<footer>quarry 1.0 · <span data-volatile>rendered in <1ms</span></footer>`) b := []byte(`<footer>quarry 1.0 · <span data-volatile>rendered in 412ms</span></footer>`) d, err := Diff(HTML, a, b) if err != nil { t.Fatal(err) } if d != "" { t.Errorf("volatile content compared: %s", d) } // It must not become a blanket mute: a real difference OUTSIDE the // marked element still has to fail. c := []byte(`<footer>quarry 1.1 · <span data-volatile>rendered in 9ms</span></footer>`) if d, err := Diff(HTML, a, c); err != nil { t.Fatal(err) } else if d == "" { t.Error("a real difference next to volatile content was ignored") } }