返回 DeepSeek-Reasonix
diff_test.go
根目录 / internal / diff / diff_test.go
1 package diff
2
3 import (
4 "strings"
5 "testing"
6 )
7
8 func TestBuild_NoChange(t *testing.T) {
9 c := Build("f.txt", "a\nb\n", "a\nb\n", Modify)
10 if c.Diff != "" || c.Added != 0 || c.Removed != 0 {
11 t.Fatalf("identical content should yield empty diff, got %+v", c)
12 }
13 }
14
15 func TestBuild_Create(t *testing.T) {
16 c := Build("new.txt", "", "hello\nworld\n", Create)
17 if c.Added != 2 || c.Removed != 0 {
18 t.Fatalf("added/removed = %d/%d, want 2/0", c.Added, c.Removed)
19 }
20 if !strings.Contains(c.Diff, "@@ -0,0 +1,2 @@") {
21 t.Fatalf("create hunk header missing:\n%s", c.Diff)
22 }
23 if !strings.Contains(c.Diff, "+hello") || !strings.Contains(c.Diff, "+world") {
24 t.Fatalf("added lines missing:\n%s", c.Diff)
25 }
26 }
27
28 func TestBuild_DeleteAll(t *testing.T) {
29 c := Build("gone.txt", "x\ny\n", "", Delete)
30 if c.Added != 0 || c.Removed != 2 {
31 t.Fatalf("added/removed = %d/%d, want 0/2", c.Added, c.Removed)
32 }
33 if !strings.Contains(c.Diff, "@@ -1,2 +0,0 @@") {
34 t.Fatalf("delete hunk header missing:\n%s", c.Diff)
35 }
36 }
37
38 func TestBuild_ModifyMiddle(t *testing.T) {
39 old := "1\n2\n3\n4\n5\n"
40 neu := "1\n2\nThree\n4\n5\n"
41 c := Build("m.txt", old, neu, Modify)
42 if c.Added != 1 || c.Removed != 1 {
43 t.Fatalf("added/removed = %d/%d, want 1/1", c.Added, c.Removed)
44 }
45 if !strings.Contains(c.Diff, "-3") || !strings.Contains(c.Diff, "+Three") {
46 t.Fatalf("expected -3/+Three:\n%s", c.Diff)
47 }
48 // 3 lines of context each side, but the file only has 2 before and 2 after.
49 if !strings.Contains(c.Diff, " 1") || !strings.Contains(c.Diff, " 5") {
50 t.Fatalf("context lines missing:\n%s", c.Diff)
51 }
52 }
53
54 func TestBuildWithOptionsPreviewLabelsAndContext(t *testing.T) {
55 old := "1\n2\n3\n4\n5\n"
56 neu := "1\n2\nThree\n4\n5\n"
57 c := BuildWithOptions("m.txt", old, neu, Modify, BuildOptions{ContextLines: 0, Mode: OutputModePreview})
58 if c.Mode != string(OutputModePreview) {
59 t.Fatalf("Mode = %q", c.Mode)
60 }
61 if !strings.Contains(c.Diff, "--- before/m.txt") || !strings.Contains(c.Diff, "+++ after/m.txt") {
62 t.Fatalf("preview labels missing:\n%s", c.Diff)
63 }
64 if strings.Contains(c.Diff, " 1") || strings.Contains(c.Diff, " 5") {
65 t.Fatalf("zero-context diff leaked unchanged context:\n%s", c.Diff)
66 }
67 if c.Hunks != 1 {
68 t.Fatalf("Hunks = %d, want 1:\n%s", c.Hunks, c.Diff)
69 }
70 }
71
72 func TestBuildWithOptionsCustomLabels(t *testing.T) {
73 c := BuildWithOptions("x.txt", "old\n", "new\n", Modify, BuildOptions{
74 ContextLines: 1,
75 OldLabel: "left",
76 NewLabel: "right",
77 })
78 if !strings.Contains(c.Diff, "--- left") || !strings.Contains(c.Diff, "+++ right") {
79 t.Fatalf("custom labels missing:\n%s", c.Diff)
80 }
81 }
82
83 func TestBuild_Prepend(t *testing.T) {
84 c := Build("p.txt", "b\nc\n", "a\nb\nc\n", Modify)
85 if c.Added != 1 || c.Removed != 0 {
86 t.Fatalf("added/removed = %d/%d, want 1/0", c.Added, c.Removed)
87 }
88 if !strings.Contains(c.Diff, "@@ -1,2 +1,3 @@") {
89 t.Fatalf("prepend header wrong:\n%s", c.Diff)
90 }
91 }
92
93 func TestBuild_TwoSeparateHunks(t *testing.T) {
94 // Two changes far enough apart (>2*context) to form distinct hunks.
95 old := "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\n"
96 neu := "a\nB\nc\nd\ne\nf\ng\nh\ni\nj\nK\nl\n"
97 c := Build("two.txt", old, neu, Modify)
98 if got := strings.Count(c.Diff, "@@ "); got != 2 {
99 t.Fatalf("expected 2 hunks, got %d:\n%s", got, c.Diff)
100 }
101 if c.Added != 2 || c.Removed != 2 {
102 t.Fatalf("added/removed = %d/%d, want 2/2", c.Added, c.Removed)
103 }
104 }
105
106 func TestBuild_AdjacentChangesMerge(t *testing.T) {
107 // Changes within 2*context collapse into one hunk.
108 old := "a\nb\nc\nd\ne\n"
109 neu := "a\nB\nc\nD\ne\n"
110 c := Build("adj.txt", old, neu, Modify)
111 if got := strings.Count(c.Diff, "@@ "); got != 1 {
112 t.Fatalf("expected 1 merged hunk, got %d:\n%s", got, c.Diff)
113 }
114 }
115
116 func TestBuild_NoNewlineAtEOF(t *testing.T) {
117 c := Build("nonl.txt", "a\nb", "a\nc", Modify)
118 if !strings.Contains(c.Diff, "\\ No newline at end of file") {
119 t.Fatalf("expected no-newline marker:\n%s", c.Diff)
120 }
121 }
122
123 func TestBuild_Binary(t *testing.T) {
124 c := Build("bin", "ok\n", "bad\x00data", Modify)
125 if !c.Binary {
126 t.Fatal("expected Binary=true")
127 }
128 if c.Diff != "" || c.Added != 0 || c.Removed != 0 {
129 t.Fatalf("binary change should carry no textual diff, got %+v", c)
130 }
131 }
132
133 // TestBuild_MinimalEditScript checks the third-party line diff keeps the same
134 // user-visible contract: a single line inserted into a run of identical lines
135 // must not be rendered as a block delete+insert.
136 func TestBuild_MinimalEditScript(t *testing.T) {
137 old := "x\nx\nx\n"
138 neu := "x\nx\ny\nx\n"
139 c := Build("min.txt", old, neu, Modify)
140 if c.Added != 1 || c.Removed != 0 {
141 t.Fatalf("added/removed = %d/%d, want 1/0 (minimal):\n%s", c.Added, c.Removed, c.Diff)
142 }
143 }
144
145 func TestChangedWindowSize(t *testing.T) {
146 oldLines, _ := splitLines("a\nb\nc\nd\ne\n")
147 newLines, _ := splitLines("a\nb\nC\nd\ne\n")
148 if got := changedWindowSize(oldLines, newLines); got != 2 {
149 t.Fatalf("single changed line window = %d, want 2", got)
150 }
151 oldLines, _ = splitLines("a\nb\nc\nd\ne\n")
152 newLines, _ = splitLines("a\nB\nc\nD\ne\n")
153 if got := changedWindowSize(oldLines, newLines); got != 6 {
154 t.Fatalf("separate changed lines window = %d, want 6", got)
155 }
156 }
157
158 func TestExactDiffTooLarge(t *testing.T) {
159 oldLines := make([]string, maxDiffEdits+1)
160 newLines := make([]string, maxDiffEdits+1)
161 for i := range oldLines {
162 oldLines[i] = "old"
163 newLines[i] = "new"
164 }
165 if !exactDiffTooLarge(oldLines, newLines) {
166 t.Fatal("large changed window should skip exact diff")
167 }
168 }
169
169 lines GO