Test / test (push) Successful in 7m5s
Release / gates (push) Successful in 7m28s
Release / build (amd64, freebsd) (push) Successful in 2m52s
Release / build (amd64, linux) (push) Successful in 2m46s
Release / build (arm64, freebsd) (push) Successful in 2m22s
Release / build (arm64, linux) (push) Successful in 2m38s
Release / build (loong64, linux) (push) Successful in 2m7s
Release / build (riscv64, linux) (push) Successful in 2m17s
Release / release (push) Successful in 1m0s
Assisted-by: GLM 5.3
130 lines
3.2 KiB
Go
130 lines
3.2 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
|
|
|
|
package diff
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func render(chunks []Chunk) string {
|
|
var b strings.Builder
|
|
for _, c := range chunks {
|
|
switch c.Op {
|
|
case Equal:
|
|
for _, l := range c.Lines {
|
|
b.WriteString(" " + l + "\n")
|
|
}
|
|
case Removed:
|
|
for _, l := range c.Lines {
|
|
b.WriteString("- " + l + "\n")
|
|
}
|
|
case Added:
|
|
for _, l := range c.Lines {
|
|
b.WriteString("+ " + l + "\n")
|
|
}
|
|
case Skipped:
|
|
b.WriteString("… " + strings.Repeat("#", c.Skipped) + "\n")
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func TestIdentical(t *testing.T) {
|
|
got := render(Chunks("alpha\nbeta\n", "alpha\nbeta\n", 3))
|
|
if got != " alpha\n beta\n" {
|
|
t.Errorf("identical diff = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestSingleChange(t *testing.T) {
|
|
got := render(Chunks("alpha\nbeta\ngamma\n", "alpha\nbeta+\ngamma\n", 3))
|
|
want := " alpha\n- beta\n+ beta+\n gamma\n"
|
|
if got != want {
|
|
t.Errorf("change diff = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestInsertAndDelete(t *testing.T) {
|
|
got := render(Chunks("one\ntwo\n", "one\nthree\nfour\ntwo\n", 3))
|
|
want := " one\n+ three\n+ four\n two\n"
|
|
if got != want {
|
|
t.Errorf("insert diff = %q, want %q", got, want)
|
|
}
|
|
|
|
got = render(Chunks("one\nthree\ntwo\n", "one\ntwo\n", 3))
|
|
want = " one\n- three\n two\n"
|
|
if got != want {
|
|
t.Errorf("delete diff = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestEmptySides(t *testing.T) {
|
|
got := render(Chunks("", "alpha\n", 3))
|
|
if got != "+ alpha\n" {
|
|
t.Errorf("empty old = %q", got)
|
|
}
|
|
got = render(Chunks("alpha\n", "", 3))
|
|
if got != "- alpha\n" {
|
|
t.Errorf("empty new = %q", got)
|
|
}
|
|
got = render(Chunks("", "", 3))
|
|
if got != "" {
|
|
t.Errorf("both empty = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestContextCollapse(t *testing.T) {
|
|
// Twelve equal lines between two changes: with context 3 the middle
|
|
// six collapse into one skipped chunk.
|
|
old := "x1\nx2\nx3\nx4\nx5\nx6\nx7\nx8\nx9\nx10\nx11\nx12\nx13\nx14\n"
|
|
new := "A1\nx2\nx3\nx4\nx5\nx6\nx7\nx8\nx9\nx10\nx11\nx12\nx13\nA14\n"
|
|
got := render(Chunks(old, new, 3))
|
|
want := "- x1\n+ A1\n" +
|
|
" x2\n x3\n x4\n" +
|
|
"… ######\n" +
|
|
" x11\n x12\n x13\n" +
|
|
"- x14\n+ A14\n"
|
|
if got != want {
|
|
t.Errorf("collapse diff = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestTrailingNewlineIsNotALine(t *testing.T) {
|
|
with := render(Chunks("alpha\n", "alpha\n", 3))
|
|
without := render(Chunks("alpha", "alpha", 3))
|
|
if with != without {
|
|
t.Errorf("trailing newline changed the diff: %q vs %q", with, without)
|
|
}
|
|
}
|
|
|
|
func TestCellBudgetFallsBackToReplace(t *testing.T) {
|
|
// 2500 equal lines on both sides exceeds the cell budget; the diff
|
|
// must degrade to a full replacement instead of hanging.
|
|
var b strings.Builder
|
|
for range 2500 {
|
|
b.WriteString(strings.Repeat("x", 40) + "\n")
|
|
}
|
|
big := b.String()
|
|
chunks := Chunks(big, big, 3)
|
|
var removed, added bool
|
|
for _, c := range chunks {
|
|
switch c.Op {
|
|
case Removed:
|
|
removed = true
|
|
case Added:
|
|
added = true
|
|
}
|
|
}
|
|
if !removed || !added {
|
|
t.Errorf("oversized input did not degrade to replacement (removed=%v added=%v)", removed, added)
|
|
}
|
|
}
|
|
|
|
func TestOpIs(t *testing.T) {
|
|
if !Removed.Is("removed") || Added.Is("removed") || Skipped.Is("skipped") == false {
|
|
t.Error("Op.Is misclassifies")
|
|
}
|
|
}
|