Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1faecd3

Browse files
committed
tools/internal/diff: fix off-by-one error in computing diffs
In the removed code if endCol := end - 1 - strings.LastIndex(src[:end], "\n"); endCol > 0 the endCol test is off by one. It should have been endCol >= 0, otherwise there are cases where extending the edit won't be considered. But endCol is always >= 0, as LastIndex(src[:end], "\n") is <= end-1. Another way of saying all this is the extending the edit doesn't need endCol at all. The effect of this was to fail to extend edits if the edit started the line. The tests were revised to check unified diffs by using the patch command. Fixes: golang/go#457256 Change-Id: Idb23f1a28d36f92a7b8712e9459df86a3d420d7e Reviewed-on: https://go-review.googlesource.com/c/tools/+/459236 TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Alan Donovan <[email protected]> gopls-CI: kokoro <[email protected]> Run-TryBot: Peter Weinberger <[email protected]>
1 parent a7f033a commit 1faecd3

File tree

3 files changed

+72
-21
lines changed

3 files changed

+72
-21
lines changed

internal/diff/diff.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ type Edit struct {
1717
New string // the replacement
1818
}
1919

20+
func (e Edit) String() string {
21+
return fmt.Sprintf("{Start:%d,End:%d,New:%s}", e.Start, e.End, e.New)
22+
}
23+
2024
// Apply applies a sequence of edits to the src buffer and returns the
2125
// result. Edits are applied in order of start offset; edits with the
2226
// same start offset are applied in they order they were provided.
@@ -146,16 +150,13 @@ func expandEdit(edit Edit, src string) Edit {
146150
}
147151

148152
// Expand end right to end of line.
149-
// (endCol is the zero-based column number of end.)
150153
end := edit.End
151-
if endCol := end - 1 - strings.LastIndex(src[:end], "\n"); endCol > 0 {
152-
if nl := strings.IndexByte(src[end:], '\n'); nl < 0 {
153-
edit.End = len(src) // extend to EOF
154-
} else {
155-
edit.End = end + nl + 1 // extend beyond \n
156-
}
157-
edit.New += src[end:edit.End]
154+
if nl := strings.IndexByte(src[end:], '\n'); nl < 0 {
155+
edit.End = len(src) // extend to EOF
156+
} else {
157+
edit.End = end + nl + 1 // extend beyond \n
158158
}
159+
edit.New += src[end:edit.End]
159160

160161
return edit
161162
}

internal/diff/diff_test.go

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,19 @@
55
package diff_test
66

77
import (
8+
"bytes"
89
"math/rand"
10+
"os"
11+
"os/exec"
12+
"path/filepath"
913
"reflect"
14+
"strings"
1015
"testing"
1116
"unicode/utf8"
1217

1318
"golang.org/x/tools/internal/diff"
1419
"golang.org/x/tools/internal/diff/difftest"
20+
"golang.org/x/tools/internal/testenv"
1521
)
1622

1723
func TestApply(t *testing.T) {
@@ -99,31 +105,50 @@ func TestLineEdits(t *testing.T) {
99105
t.Fatalf("LineEdits: %v", err)
100106
}
101107
if !reflect.DeepEqual(got, edits) {
102-
t.Errorf("LineEdits got %q, want %q", got, edits)
108+
t.Errorf("LineEdits got\n%q, want\n%q\n%#v", got, edits, tc)
103109
}
104110
})
105111
}
106112
}
107113

108114
func TestToUnified(t *testing.T) {
115+
testenv.NeedsTool(t, "patch")
109116
for _, tc := range difftest.TestCases {
110117
t.Run(tc.Name, func(t *testing.T) {
111118
unified, err := diff.ToUnified(difftest.FileA, difftest.FileB, tc.In, tc.Edits)
112119
if err != nil {
113120
t.Fatal(err)
114121
}
115-
if unified != tc.Unified {
116-
t.Errorf("Unified(Edits): got diff:\n%v\nexpected:\n%v", unified, tc.Unified)
122+
if unified == "" {
123+
return
117124
}
118-
if tc.LineEdits != nil {
119-
unified, err := diff.ToUnified(difftest.FileA, difftest.FileB, tc.In, tc.LineEdits)
120-
if err != nil {
121-
t.Fatal(err)
122-
}
123-
if unified != tc.Unified {
124-
t.Errorf("Unified(LineEdits): got diff:\n%v\nexpected:\n%v", unified, tc.Unified)
125-
}
125+
orig := filepath.Join(t.TempDir(), "original")
126+
err = os.WriteFile(orig, []byte(tc.In), 0644)
127+
if err != nil {
128+
t.Fatal(err)
126129
}
130+
temp := filepath.Join(t.TempDir(), "patched")
131+
err = os.WriteFile(temp, []byte(tc.In), 0644)
132+
if err != nil {
133+
t.Fatal(err)
134+
}
135+
cmd := exec.Command("patch", "-p0", "-u", "-s", "-o", temp, orig)
136+
cmd.Stdin = strings.NewReader(unified)
137+
cmd.Stdout = new(bytes.Buffer)
138+
cmd.Stderr = new(bytes.Buffer)
139+
if err = cmd.Run(); err != nil {
140+
t.Fatalf("%v: %q (%q) (%q)", err, cmd.String(),
141+
cmd.Stderr, cmd.Stdout)
142+
}
143+
got, err := os.ReadFile(temp)
144+
if err != nil {
145+
t.Fatal(err)
146+
}
147+
if string(got) != tc.Out {
148+
t.Errorf("applying unified failed: got\n%q, wanted\n%q unified\n%q",
149+
got, tc.Out, unified)
150+
}
151+
127152
})
128153
}
129154
}

internal/diff/difftest/difftest.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
// "golang.org/x/tools/internal/diff"
88
package difftest
99

10+
// There are two kinds of tests, semantic tests, and 'golden data' tests.
11+
// The semantic tests check that the computed diffs transform the input to
12+
// the output, and that 'patch' accepts the computed unified diffs.
13+
// The other tests just check that Edits and LineEdits haven't changed
14+
// unexpectedly. These fields may need to be changed when the diff algorithm
15+
// changes.
16+
1017
import (
1118
"testing"
1219

@@ -201,6 +208,12 @@ var TestCases = []struct {
201208
{Start: 10, End: 12, New: ""},
202209
{Start: 14, End: 14, New: "C\n"},
203210
},
211+
LineEdits: []diff.Edit{
212+
{Start: 0, End: 6, New: "C\n"},
213+
{Start: 6, End: 8, New: "B\nA\n"},
214+
{Start: 10, End: 14, New: "A\n"},
215+
{Start: 14, End: 14, New: "C\n"},
216+
},
204217
}, {
205218
Name: "replace_last_line",
206219
In: "A\nB\n",
@@ -239,6 +252,16 @@ var TestCases = []struct {
239252
},
240253
NoDiff: true, // diff algorithm produces different delete/insert pattern
241254
},
255+
{
256+
Name: "extra_newline",
257+
In: "\nA\n",
258+
Out: "A\n",
259+
Edits: []diff.Edit{{Start: 0, End: 1, New: ""}},
260+
Unified: UnifiedPrefix + `@@ -1,2 +1 @@
261+
-
262+
A
263+
`,
264+
},
242265
}
243266

244267
func DiffTest(t *testing.T, compute func(before, after string) []diff.Edit) {
@@ -254,10 +277,12 @@ func DiffTest(t *testing.T, compute func(before, after string) []diff.Edit) {
254277
t.Fatalf("ToUnified: %v", err)
255278
}
256279
if got != test.Out {
257-
t.Errorf("Apply: got patched:\n%v\nfrom diff:\n%v\nexpected:\n%v", got, unified, test.Out)
280+
t.Errorf("Apply: got patched:\n%v\nfrom diff:\n%v\nexpected:\n%v",
281+
got, unified, test.Out)
258282
}
259283
if !test.NoDiff && unified != test.Unified {
260-
t.Errorf("Unified: got diff:\n%v\nexpected:\n%v", unified, test.Unified)
284+
t.Errorf("Unified: got diff:\n%q\nexpected:\n%q diffs:%v",
285+
unified, test.Unified, edits)
261286
}
262287
})
263288
}

0 commit comments

Comments
 (0)