-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpatch_test.go
More file actions
218 lines (209 loc) · 8.71 KB
/
Copy pathpatch_test.go
File metadata and controls
218 lines (209 loc) · 8.71 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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package applypatch
import (
"maps"
"reflect"
"strings"
"testing"
)
func TestParse(t *testing.T) {
t.Parallel()
update := func(path, move, diff string) Operation {
return Operation{Kind: UpdateFile, Path: path, MoveTo: move, Diff: diff}
}
for _, tc := range []struct {
name, patch string
want *Patch
}{
{
"all operations",
"*** Begin Patch\n*** Add File: a.txt\n+one\n+\n*** Delete File: b.txt\n" +
"*** Update File: c.txt\n*** Move to: d.txt\n@@ x\n-y\n+z\n*** End Patch",
&Patch{Operations: []Operation{
{Kind: AddFile, Path: "a.txt", Contents: "one\n\n"},
{Kind: DeleteFile, Path: "b.txt"},
update("c.txt", "d.txt", "@@ x\n-y\n+z\n"),
}},
},
{
"environment ID and an empty add",
"*** Begin Patch\n*** Environment ID: env \n*** Add File: a.txt\n*** End Patch",
&Patch{EnvironmentID: "env", Operations: []Operation{{Kind: AddFile, Path: "a.txt"}}},
},
{
"heredoc, CRLF, and padded markers",
"<<'EOF'\r\n *** Begin Patch \r\n*** Update File: a.txt \r\n-x\r\n *** End Patch\r\nEOF\r\n",
&Patch{Operations: []Operation{update("a.txt", "", "-x\n")}},
},
{
"indented markers inside an update are diff lines",
"*** Begin Patch\n*** Update File: a.txt\n-x\n *** Add File: b.txt\n*** End Patch",
&Patch{Operations: []Operation{update("a.txt", "", "-x\n *** Add File: b.txt\n")}},
},
{
"move after a leading end of file",
"*** Begin Patch\n*** Update File: a.txt\n*** End of File\n*** Move to: b.txt\n-x\n*** End Patch",
&Patch{Operations: []Operation{update("a.txt", "b.txt", "*** End of File\n-x\n")}},
},
{
"a second move is a diff line",
"*** Begin Patch\n*** Update File: a.txt\n*** Move to: b.txt\n*** Move to: c.txt\n*** End Patch",
&Patch{Operations: []Operation{update("a.txt", "b.txt", "*** Move to: c.txt\n")}},
},
{
"carriage returns are removed twice at most",
"*** Begin Patch\n*** Update File: a.txt\n-x\r\r\r\n*** End Patch",
&Patch{Operations: []Operation{update("a.txt", "", "-x\r\n")}},
},
{
"blank lines after the end",
"*** Begin Patch\n*** Delete File: a.txt\n*** End Patch\n\n \n*** End Patch",
&Patch{Operations: []Operation{{Kind: DeleteFile, Path: "a.txt"}}},
},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got, err := Parse(tc.patch)
if err != nil || !reflect.DeepEqual(got, tc.want) {
t.Fatalf("got %#v, %v\nwant %#v", got, err, tc.want)
}
})
}
}
// envelope wraps lines in "*** Begin Patch" and "*** End Patch".
func envelope(lines ...string) string {
return "*** Begin Patch\n" + strings.Join(append(lines, "*** End Patch"), "\n")
}
func TestParseErrors(t *testing.T) {
t.Parallel()
for _, tc := range []struct{ name, patch, err string }{
{"empty", " \n ", "first line of the patch must be '*** Begin Patch'"},
{"no begin", "*** Add File: a\n*** End Patch", "first line of the patch must be '*** Begin Patch'"},
{"no end", "*** Begin Patch\n*** Add File: a", "last line of the patch must be '*** End Patch'"},
{"bad heredoc", "<<EOF\n*** Begin Patch\n*** End Patch\nEOX", "first line of the patch must be"},
{"short heredoc", "<<EOF\n*** Begin Patch\nEOF", "first line of the patch must be"},
{"unknown heredoc marker", "<<X\n*** Begin Patch\n*** End Patch\nEOF", "first line of the patch must be"},
{"heredoc without end", "<<EOF\n*** Begin Patch\n+a\nEOF", "last line of the patch must be"},
{"invalid header", envelope("*** Frobnicate: a"), "'*** Frobnicate: a' is not a valid hunk header"},
{"line in an add", "*** Begin Patch\n*** Add File: a\nx\n*** End Patch", "'x' is not a valid hunk header"},
{"line after a delete", "*** Begin Patch\n*** Delete File: a\n+x\n*** End Patch", "not a valid hunk header"},
{"empty update", envelope("*** Update File: a", "*** Delete File: b"), "hunk for path 'a' is empty"},
{"empty update at the end", "*** Begin Patch\n*** Update File: a\n*** End Patch", "hunk for path 'a' is empty"},
{"update with only a move", "*** Begin Patch\n*** Update File: a\n*** Move to: b\n*** End Patch", "is empty"},
{"text after the end", "*** Begin Patch\n*** End Patch\nx\n*** End Patch", "last line of the patch must be"},
{"two environment IDs", envelope("*** Environment ID: a", "*** Environment ID: b"), "more than once"},
{"empty environment ID", "*** Begin Patch\n*** Environment ID: \n*** End Patch", "cannot be empty"},
{"environment ID after a hunk", envelope("*** Delete File: a", "*** Environment ID: b"), "not a valid hunk header"},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got, err := Parse(tc.patch)
if err == nil || !strings.Contains(err.Error(), tc.err) || got != nil {
t.Fatalf("got %#v, %v; want error containing %q", got, err, tc.err)
}
})
}
}
func TestOperationKindString(t *testing.T) {
t.Parallel()
names := []string{AddFile.String(), DeleteFile.String(), UpdateFile.String(), OperationKind(0).String()}
if strings.Join(names, " ") != "add delete update OperationKind(0)" {
t.Fatalf("names: %v", names)
}
}
func TestApplyFiles(t *testing.T) {
t.Parallel()
files := map[string]string{"a.txt": "a\n", "b.txt": "b\n"}
self := []string{"*** Update File: a.txt", "*** Move to: a.txt", "-a", "+z"}
for _, tc := range []struct {
name string
mode Mode
patch string
want map[string]string
err string
}{
{
"add replaces, delete removes, update moves over a file",
Codex, "*** Begin Patch\n*** Add File: a.txt\n+new\n*** Delete File: b.txt\n" +
"*** Update File: a.txt\n*** Move to: b.txt\n@@\n-new\n+moved\n*** End Patch",
map[string]string{"b.txt": "moved\n"},
"",
},
{
"strict update", Strict, envelope("*** Update File: a.txt", "-a", "+z"),
map[string]string{"a.txt": "z\n", "b.txt": "b\n"},
"",
},
{"Codex move to itself deletes", Codex, envelope(self...), map[string]string{"b.txt": "b\n"}, ""},
{"strict move to itself fails", Strict, envelope(self...), nil, "cannot move a.txt to itself"},
{"missing update", Codex, envelope("*** Update File: c.txt", "-a"), nil, "Failed to read file to update c.txt"},
{"missing delete", Strict, envelope("*** Delete File: c.txt"), nil, "Failed to delete file c.txt"},
{"strict match failure", Strict, envelope("*** Update File: a.txt", "-z"), nil, "target must match exactly once"},
{"no operations", Codex, "*** Begin Patch\n*** End Patch", nil, "No files were modified."},
{
"bad diff after a good operation", Codex,
envelope("*** Delete File: b.txt", "*** Update File: a.txt", "bad"), nil, "Unexpected line",
},
{"invalid UTF-8 patch", Codex, "*** Begin Patch\n*** Add File: c.txt\n+\xff\n*** End Patch", nil, "not valid UTF-8"},
{"parse error", Strict, "*** Begin Patch", nil, "last line of the patch must be"},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
before := maps.Clone(files)
got, err := tc.mode.ApplyFiles(files, tc.patch)
if !maps.Equal(files, before) {
t.Fatalf("ApplyFiles changed its input: %q", files)
}
if tc.err != "" {
if err == nil || !strings.Contains(err.Error(), tc.err) || got != nil {
t.Fatalf("got %q, %v; want error containing %q", got, err, tc.err)
}
return
}
if err != nil || !maps.Equal(got, tc.want) {
t.Fatalf("got %q, %v; want %q", got, err, tc.want)
}
got["a.txt"] = "changed"
if !maps.Equal(files, before) {
t.Fatal("the result shares storage with the input")
}
})
}
}
func TestApplyFilesNilMap(t *testing.T) {
t.Parallel()
got, err := ApplyFiles(nil, "*** Begin Patch\n*** Add File: a\n+x\n*** End Patch")
if err != nil || !maps.Equal(got, map[string]string{"a": "x\n"}) {
t.Fatalf("got %q, %v", got, err)
}
}
func TestApplyFilesPartialState(t *testing.T) {
t.Parallel()
// Internally, a failure reports the files as Codex would leave them.
files := map[string]string{"a.txt": "a\n"}
got, err := Codex.applyFiles(files, "*** Begin Patch\n*** Add File: n.txt\n+n\n"+
"*** Update File: m.txt\n-x\n*** End Patch")
if err == nil || !maps.Equal(got, map[string]string{"a.txt": "a\n", "n.txt": "n\n"}) {
t.Fatalf("got %q, %v", got, err)
}
}
// Operation.Diff promises that Mode.Apply applies it as ApplyFiles would.
func TestOperationDiffAppliesAsInApplyFiles(t *testing.T) {
t.Parallel()
generator := newGenerator(3)
for range 500 {
input := generator.text()
patch := "*** Begin Patch\n*** Update File: f\n" + generator.diff(input) + "*** End Patch"
parsed, err := Parse(patch)
if err != nil {
continue
}
for _, mode := range []Mode{Strict, Codex} {
files, filesErr := mode.ApplyFiles(map[string]string{"f": input}, patch)
applied, applyErr := mode.Apply(input, parsed.Operations[0].Diff)
if (filesErr == nil) != (applyErr == nil) || files["f"] != applied {
t.Fatalf("%v: ApplyFiles gave %q, %v; Apply gave %q, %v\ninput %q\npatch %q",
mode, files["f"], filesErr, applied, applyErr, input, patch)
}
}
}
}