1// Copyright 2019 CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package diff
16
17import (
18 "bytes"
19 "testing"
20
21 "cuelang.org/go/cue"
22 "cuelang.org/go/cue/cuecontext"
23 "cuelang.org/go/internal/cuetdtest"
24)
25
26func TestDiff(t *testing.T) {
27 testCases := []struct {
28 name string
29 x, y string
30 kind Kind
31 diff string
32 profile *Profile
33 }{{
34 name: "identity struct",
35 x: `{
36 a: {
37 b: 1
38 c: 2
39 }
40 l: {
41 d: 1
42 }
43 }`,
44 y: `{
45 a: {
46 c: 2
47 b: 1
48 }
49 l: {
50 d: 1
51 }
52 }`,
53 }, {
54 name: "identity list",
55 x: `[1, 2, 3]`,
56 y: `[1, 2, 3]`,
57 }, {
58 name: "identity value",
59 x: `"foo"`,
60 y: `"foo"`,
61 }, {
62 name: "modified value",
63 x: `"foo"`,
64 y: `"bar"`,
65 kind: Modified,
66 diff: `- "foo",
67+ "bar",
68`,
69 }, {
70 name: "basics",
71 x: `{
72 a: int
73 b: 2
74 s: 4
75 d: 1
76 e: null
77 }
78 `,
79 y: `
80 {
81 a: string
82 c: 3
83 s: 4
84 d: int
85 e: null
86 }
87 `,
88 kind: Modified,
89 diff: ` {
90- a: int
91+ a: string
92- b: 2
93+ c: 3
94 s: 4
95- d: 1
96+ d: int
97 e: null
98 }
99`,
100 }, {
101 name: "basics 2",
102 x: `{
103 ls: [2, 3, 4]
104 "foo-bar": 2
105 s: 4
106 lm1: [2, 3, 5]
107 lm2: [6]
108 }
109 `,
110 y: `
111 {
112 ls: [2, 3, 4]
113 "foo-bar": 3
114 s: 4
115 lm1: [2, 3, 4, 6]
116 lm2: []
117 la: [2, 3, 4]
118 }
119 `,
120 kind: Modified,
121 diff: ` {
122 ls: [2, 3, 4]
123- "foo-bar": 2
124+ "foo-bar": 3
125 s: 4
126 lm1: [
127 2,
128 3,
129- 5,
130+ 4,
131+ 6,
132 ]
133 lm2: [
134- 6,
135 ]
136+ la: [2, 3, 4]
137 }
138`,
139 }, {
140 name: "interupted run 1",
141 x: `{
142 a: 1
143 b: 2
144 c: 3
145 d: 4
146 e: 10
147 f: 6
148 g: 7
149 h: 8
150 i: 9
151 j: 10
152}
153`,
154 y: `
155{
156 a: 1
157 b: 2
158 c: 3
159 d: 4
160 e: 5
161 f: 6
162 g: 7
163 h: 8
164 i: 9
165 j: 10
166}
167`,
168 kind: Modified,
169 diff: ` {
170 ... // 2 identical elements
171 c: 3
172 d: 4
173- e: 10
174+ e: 5
175 f: 6
176 g: 7
177 ... // 3 identical elements
178 }
179`,
180 }, {
181 name: "interupted run 2",
182 x: `{
183 a: -1
184 b: 2
185 c: 3
186 d: 4
187 e: 5
188 f: 6
189 g: 7
190 h: 8
191 i: 9
192 j: -10
193 }`,
194 y: `{
195 a: 1
196 b: 2
197 c: 3
198 d: 4
199 e: 5
200 f: 6
201 g: 7
202 h: 8
203 i: 9
204 j: 10
205 }
206 `,
207 kind: Modified,
208 diff: ` {
209- a: -1
210+ a: 1
211 b: 2
212 c: 3
213 ... // 4 identical elements
214 h: 8
215 i: 9
216- j: -10
217+ j: 10
218 }
219`,
220 }, {
221 name: "recursion",
222 x: `{
223 s: {
224 a: 1
225 b: 3
226 d: 4
227 }
228 l: [
229 [3, 4]
230 ]
231 }`,
232 y: `{
233 s: {
234 a: 2
235 b: 3
236 c: 4
237 }
238 l: [
239 [3, 5, 6]
240 ]
241 }
242 `,
243 kind: Modified,
244 diff: ` {
245 s: {
246- a: 1
247+ a: 2
248 b: 3
249- d: 4
250+ c: 4
251 }
252 l: [
253 [
254 3,
255- 4,
256+ 5,
257+ 6,
258 ]
259 ]
260 }
261`,
262 }, {
263 name: "optional and definitions",
264 x: `{
265 #s: {
266 #a: 1
267 b: 2
268 }
269 o?: 3
270 #od?: 1
271 oc?: 5
272}`,
273 y: `{
274 #s: {
275 a: 2
276 #b: 2
277 }
278 o?: 4
279 #od: 1
280 #oc?: 5
281}
282`,
283 kind: Modified,
284 diff: ` {
285 #s: {
286- #a: 1
287- b: 2
288+ a: 2
289+ #b: 2
290 }
291- o?: 3
292+ o?: 4
293- #od?: 1
294- oc?: 5
295+ #od: 1
296+ #oc?: 5
297 }
298`,
299 }, {
300 name: "bulk optional",
301 x: `{[_]: x: "hello"}
302
303a: x: "hello"
304 `,
305 y: `[_]: x: "hello"
306
307 `,
308 kind: Modified,
309 diff: ` {
310- a: {
311- x: "hello"
312- }
313 }
314`,
315 }, {
316 x: `
317 #Directory: {
318 {
319 // Directory from another directory (e.g. subdirectory)
320 from: #Directory
321 } | {
322 // Reference to remote directory
323 ref: string
324 } | {
325 // Use a local directory
326 local: string
327 }
328 path: string | *"/"
329 }
330 `,
331 y: `
332 #Directory: {
333 {
334 // Directory from another directory (e.g. subdirectory)
335 from: #Directory
336 } | {
337 // Reference to remote directory
338 ref: string
339 } | {
340 // Use a local directory
341 local: string
342 }
343 path: string | *"/"
344 }
345 `,
346 profile: Final,
347 }, {
348 x: `
349 #Directory: {
350 {
351 // Directory from another directory (e.g. subdirectory)
352 from: #Directory
353 } | {
354 // Reference to remote directory
355 ref: string
356 } | {
357 // Use a local directory
358 local: string
359 }
360 path: string | *"/"
361 }
362 `,
363 y: `
364 #Directory: {
365 {
366 // Directory from another directory (e.g. subdirectory)
367 from: #Directory
368 } | {
369 // Reference to remote directory
370 ref: string
371 } | {
372 // Use a local directory
373 local: string
374 }
375 path: string | *"/"
376 }
377`,
378 }, {
379 name: "hidden fields",
380 x: `{a: 1, _hidden1: 1, _hidden: 1}`,
381 y: `{a: 1, _hidden2: 1, _hidden: 2}`,
382 diff: ` {
383 a: 1
384- _hidden1: 1
385+ _hidden2: 1
386- _hidden: 1
387+ _hidden: 2
388 }
389`,
390 kind: Modified,
391 }, {
392 name: "ignore hidden fields in schema",
393 x: `{a: 1, _hidden1: 1, _hidden: 1}`,
394 y: `{a: 1, _hidden2: 1, _hidden: 2}`,
395 profile: &Profile{SkipHidden: true},
396 }, {
397 name: "ignore hidden fields in data",
398 x: `{a: 1, _hidden1: 1, _hidden: 1}`,
399 y: `{a: 1, _hidden2: 1, _hidden: 2}`,
400 profile: &Profile{SkipHidden: true, Concrete: true},
401 }, {
402 name: "all errors are equal",
403 x: `1 & 3`,
404 y: `1 & 4`,
405 }}
406 for _, tc := range testCases {
407 cuetdtest.FullMatrix.Run(t, tc.name, func(t *testing.T, m *cuetdtest.M) {
408 ctx := m.CueContext()
409 // it is not fatal if x or y contain errors: some test cases
410 // rely on interacting with such errors.
411 x := ctx.CompileString(tc.x, cue.Filename("x"))
412 y := ctx.CompileString(tc.y, cue.Filename("y"))
413 p := tc.profile
414 if p == nil {
415 p = Schema
416 }
417 kind, script := p.Diff(x, y)
418 if kind != tc.kind {
419 t.Fatalf("got %d; want %d", kind, tc.kind)
420 }
421 if script != nil {
422 w := &bytes.Buffer{}
423 err := Print(w, script)
424 if err != nil {
425 t.Fatal(err)
426 }
427 if got := w.String(); got != tc.diff {
428 t.Errorf("\ngot\n%s;\nwant\n%s", got, tc.diff)
429 }
430 }
431 })
432 }
433}
434
435func TestX(t *testing.T) {
436 t.Skip()
437
438 tc := struct {
439 x, y string
440 kind Kind
441 diff string
442 }{
443 x: `{
444 }
445 `,
446 y: `
447 {
448 }
449 `,
450 kind: Modified,
451 diff: ``,
452 }
453 ctx := cuecontext.New()
454 // it is not fatal if x or y contain errors: some test cases
455 // rely on interacting with such errors.
456 x := ctx.CompileString(tc.x, cue.Filename("x"))
457 y := ctx.CompileString(tc.y, cue.Filename("y"))
458
459 kind, script := Diff(x, y)
460 if kind != tc.kind {
461 t.Fatalf("got %d; want %d", kind, tc.kind)
462 }
463 w := &bytes.Buffer{}
464 err := Print(w, script)
465 if err != nil {
466 t.Fatal(err)
467 }
468 if got := w.String(); got != tc.diff {
469 t.Errorf("got\n%s;\nwant\n%s", got, tc.diff)
470 }
471}