-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdiff.go
More file actions
169 lines (140 loc) · 3.98 KB
/
Copy pathdiff.go
File metadata and controls
169 lines (140 loc) · 3.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package testastic
import (
"slices"
"strings"
)
type diffType string
const (
// diffChanged indicates a value was modified.
diffChanged diffType = "changed"
// diffAdded indicates a value was added (exists in actual but not expected).
diffAdded diffType = "added"
// diffRemoved indicates a value was removed (exists in expected but not actual).
diffRemoved diffType = "removed"
// diffTypeMismatch indicates the types don't match.
diffTypeMismatch diffType = "type mismatch"
// diffMatcherFailed indicates a matcher didn't match the actual value.
diffMatcherFailed diffType = "matcher failed"
)
func (d diffType) String() string {
return string(d)
}
// difference represents a single difference between expected and actual JSON.
type difference struct {
Path string // JSON path, e.g., "$.users[0].name"
Expected any // Expected value (or matcher description)
Actual any
Type diffType
}
func formatFileDiffInline(expected, actual []string) string {
diff := computeDiff(expected, actual)
var sb strings.Builder
for _, line := range diff {
sb.WriteString(line)
sb.WriteString("\n")
}
return sb.String()
}
type diffOp string
const (
maxDiffMatrixCells = 1 << 20
maxDiffMatrixLines = 1 << 12
diffEqual diffOp = "equal"
diffDelete diffOp = "delete"
diffInsert diffOp = "insert"
)
// computeDiff generates a unified diff between two sets of lines.
// Uses a bounded LCS-based algorithm with a linear fallback.
//
//nolint:funlen // LCS algorithm requires sequential steps.
func computeDiff(expected, actual []string) []string {
if len(expected) > maxDiffMatrixLines || len(actual) > maxDiffMatrixLines {
return computeFallbackDiff(expected, actual)
}
// Build the longest common subsequence (LCS) matrix.
m, n := len(expected), len(actual)
rows, columns := m+1, n+1
if rows > maxDiffMatrixCells/columns {
return computeFallbackDiff(expected, actual)
}
dp := make([][]int, rows)
for i := range dp {
dp[i] = make([]int, columns)
}
for i := 1; i <= m; i++ {
for j := 1; j <= n; j++ {
if expected[i-1] == actual[j-1] {
dp[i][j] = dp[i-1][j-1] + 1
} else {
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
}
}
}
// Backtrack through LCS matrix to build diff operations.
var result []string
i, j := m, n
var ops []struct {
op diffOp
line string
}
for i > 0 || j > 0 {
switch {
case i > 0 && j > 0 && expected[i-1] == actual[j-1]:
ops = append(ops, struct {
op diffOp
line string
}{diffEqual, expected[i-1]})
i--
j--
case j > 0 && (i == 0 || dp[i][j-1] >= dp[i-1][j]):
ops = append(ops, struct {
op diffOp
line string
}{diffInsert, actual[j-1]})
j--
case i > 0:
ops = append(ops, struct {
op diffOp
line string
}{diffDelete, expected[i-1]})
i--
}
}
for _, op := range slices.Backward(ops) {
switch op.op {
case diffEqual:
result = append(result, " "+op.line)
case diffDelete:
result = append(result, red("- "+op.line))
case diffInsert:
result = append(result, green("+ "+op.line))
}
}
return result
}
func computeFallbackDiff(expected, actual []string) []string {
commonPrefix := 0
for commonPrefix < min(len(expected), len(actual)) && expected[commonPrefix] == actual[commonPrefix] {
commonPrefix++
}
commonSuffix := 0
maxCommonSuffix := min(len(expected), len(actual)) - commonPrefix
for commonSuffix < maxCommonSuffix &&
expected[len(expected)-1-commonSuffix] == actual[len(actual)-1-commonSuffix] {
commonSuffix++
}
result := make([]string, 0, max(len(expected), len(actual)))
for _, line := range expected[:commonPrefix] {
result = append(result, " "+line)
}
for _, line := range expected[commonPrefix : len(expected)-commonSuffix] {
result = append(result, red("- "+line))
}
for _, line := range actual[commonPrefix : len(actual)-commonSuffix] {
result = append(result, green("+ "+line))
}
for _, line := range expected[len(expected)-commonSuffix:] {
result = append(result, " "+line)
}
return result
}