talos/pkg/machinery/textdiff/textdiff_test.go
Andrey Smirnov f98e76f8d8
fix: panics in diff algorithms
The fix PR https://github.com/neticdk/go-stdlib/pull/44

Replace the library for now.

Add fuzzing test, keep panic causing vectors.

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
(cherry picked from commit eff89d1ed46e5f3c709305a8cb134dabae925420)
2026-03-26 15:57:01 +04:00

205 lines
3.6 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package textdiff_test
import (
"fmt"
"math/rand/v2"
"runtime"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/siderolabs/talos/pkg/machinery/textdiff"
)
func TestDiff(t *testing.T) {
t.Parallel()
for _, test := range []struct {
name string
a, b string
expected string
}{
{
name: "empty",
a: "",
b: "",
expected: "",
},
{
name: "identical",
a: "line 1\nline 2\nline 3\n",
b: "line 1\nline 2\nline 3\n",
expected: "",
},
{
name: "completely different",
a: "line 1\nline 2\nline 3\n",
b: "line A\nline B\nline C\n",
expected: `--- a
+++ b
@@ -1,3 +1,3 @@
-line 1
-line 2
-line 3
+line A
+line B
+line C
`,
},
{
name: "inserted line",
a: "line 1\nline 3\n",
b: "line 1\nline 2\nline 3\n",
expected: `--- a
+++ b
@@ -1,2 +1,3 @@
line 1
+line 2
line 3
`,
},
} {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
diff, err := textdiff.Diff(test.a, test.b)
require.NoError(t, err)
assert.Equal(t, test.expected, diff)
})
}
}
func TestDiffWithCustomPaths(t *testing.T) {
t.Parallel()
diff, err := textdiff.DiffWithCustomPaths("line 1\nline 3\n", "line 1\nline 2\nline 3\n", "fileA.txt", "fileB.txt")
require.NoError(t, err)
assert.Equal(t, `--- fileA.txt
+++ fileB.txt
@@ -1,2 +1,3 @@
line 1
+line 2
line 3
`, diff)
}
func genRandomLines(n int) string {
var sb strings.Builder
for range n {
fmt.Fprintf(&sb, "line %d\n", rand.Int())
}
return sb.String()
}
func TestDiffMemoryBudge(t *testing.T) {
// not parallel, we need to measure memory allocations
linesA := genRandomLines(1000)
linesB := genRandomLines(20000)
linesC := genRandomLines(1000)
for _, test := range []struct {
name string
a, b string
memoryBudget uint64
}{
{
name: "empty",
a: "",
b: "",
memoryBudget: 1024, // 1KB
},
{
name: "large",
a: linesA + linesB,
b: linesB + linesC,
memoryBudget: 3 * 1024 * 1024, // 3MB
},
{
name: "large 2",
a: linesA + linesB,
b: linesA + linesB + linesC,
memoryBudget: 2 * 1024 * 1024, // 2MB
},
{
name: "completely different",
a: linesA,
b: linesC,
memoryBudget: 512 * 1024, // 512KB
},
{
name: "large to empty",
a: linesA + linesB + linesC,
b: "",
memoryBudget: 6 * 1024 * 1024, // 6MB
},
{
name: "empty to large",
a: "",
b: linesA + linesB + linesC,
memoryBudget: 6 * 1024 * 1024, // 6MB
},
} {
t.Run(test.name, func(t *testing.T) {
differ := textdiff.Diff
allocs := MemoryAllocated(func() {
_, err := differ(test.a, test.b)
require.NoError(t, err)
})
require.LessOrEqual(t, allocs, test.memoryBudget, "memory allocations exceeded the budget")
})
}
}
func MemoryAllocated(f func()) uint64 {
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
runtime.GC()
// Measure the starting statistics
var memstats runtime.MemStats
runtime.ReadMemStats(&memstats)
allocs := 0 - memstats.TotalAlloc
f()
// Read the final statistics
runtime.ReadMemStats(&memstats)
allocs += memstats.TotalAlloc
return allocs
}
func FuzzDiff(f *testing.F) {
f.Add("", "")
f.Add("line 1\nline 2\n", "line 1\nline 2\n")
f.Add("line 1\nline 2\n", "line 1\nline 2\nline 3\n")
f.Fuzz(func(t *testing.T, a, b string) {
_, err := textdiff.Diff(a, b)
require.NoError(t, err)
})
}