// Copyright 2019 CUE Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package diff import ( "bytes" "testing" "cuelang.org/go/cue" "cuelang.org/go/cue/cuecontext" "cuelang.org/go/internal/cuetdtest" ) func TestDiff(t *testing.T) { testCases := []struct { name string x, y string kind Kind diff string profile *Profile }{{ name: "identity struct", x: `{ a: { b: 1 c: 2 } l: { d: 1 } }`, y: `{ a: { c: 2 b: 1 } l: { d: 1 } }`, }, { name: "identity list", x: `[1, 2, 3]`, y: `[1, 2, 3]`, }, { name: "identity value", x: `"foo"`, y: `"foo"`, }, { name: "modified value", x: `"foo"`, y: `"bar"`, kind: Modified, diff: `- "foo", + "bar", `, }, { name: "basics", x: `{ a: int b: 2 s: 4 d: 1 e: null } `, y: ` { a: string c: 3 s: 4 d: int e: null } `, kind: Modified, diff: ` { - a: int + a: string - b: 2 + c: 3 s: 4 - d: 1 + d: int e: null } `, }, { name: "basics 2", x: `{ ls: [2, 3, 4] "foo-bar": 2 s: 4 lm1: [2, 3, 5] lm2: [6] } `, y: ` { ls: [2, 3, 4] "foo-bar": 3 s: 4 lm1: [2, 3, 4, 6] lm2: [] la: [2, 3, 4] } `, kind: Modified, diff: ` { ls: [2, 3, 4] - "foo-bar": 2 + "foo-bar": 3 s: 4 lm1: [ 2, 3, - 5, + 4, + 6, ] lm2: [ - 6, ] + la: [2, 3, 4] } `, }, { name: "interupted run 1", x: `{ a: 1 b: 2 c: 3 d: 4 e: 10 f: 6 g: 7 h: 8 i: 9 j: 10 } `, y: ` { a: 1 b: 2 c: 3 d: 4 e: 5 f: 6 g: 7 h: 8 i: 9 j: 10 } `, kind: Modified, diff: ` { ... // 2 identical elements c: 3 d: 4 - e: 10 + e: 5 f: 6 g: 7 ... // 3 identical elements } `, }, { name: "interupted run 2", x: `{ a: -1 b: 2 c: 3 d: 4 e: 5 f: 6 g: 7 h: 8 i: 9 j: -10 }`, y: `{ a: 1 b: 2 c: 3 d: 4 e: 5 f: 6 g: 7 h: 8 i: 9 j: 10 } `, kind: Modified, diff: ` { - a: -1 + a: 1 b: 2 c: 3 ... // 4 identical elements h: 8 i: 9 - j: -10 + j: 10 } `, }, { name: "recursion", x: `{ s: { a: 1 b: 3 d: 4 } l: [ [3, 4] ] }`, y: `{ s: { a: 2 b: 3 c: 4 } l: [ [3, 5, 6] ] } `, kind: Modified, diff: ` { s: { - a: 1 + a: 2 b: 3 - d: 4 + c: 4 } l: [ [ 3, - 4, + 5, + 6, ] ] } `, }, { name: "optional and definitions", x: `{ #s: { #a: 1 b: 2 } o?: 3 #od?: 1 oc?: 5 }`, y: `{ #s: { a: 2 #b: 2 } o?: 4 #od: 1 #oc?: 5 } `, kind: Modified, diff: ` { #s: { - #a: 1 - b: 2 + a: 2 + #b: 2 } - o?: 3 + o?: 4 - #od?: 1 - oc?: 5 + #od: 1 + #oc?: 5 } `, }, { name: "bulk optional", x: `{[_]: x: "hello"} a: x: "hello" `, y: `[_]: x: "hello" `, kind: Modified, diff: ` { - a: { - x: "hello" - } } `, }, { x: ` #Directory: { { // Directory from another directory (e.g. subdirectory) from: #Directory } | { // Reference to remote directory ref: string } | { // Use a local directory local: string } path: string | *"/" } `, y: ` #Directory: { { // Directory from another directory (e.g. subdirectory) from: #Directory } | { // Reference to remote directory ref: string } | { // Use a local directory local: string } path: string | *"/" } `, profile: Final, }, { x: ` #Directory: { { // Directory from another directory (e.g. subdirectory) from: #Directory } | { // Reference to remote directory ref: string } | { // Use a local directory local: string } path: string | *"/" } `, y: ` #Directory: { { // Directory from another directory (e.g. subdirectory) from: #Directory } | { // Reference to remote directory ref: string } | { // Use a local directory local: string } path: string | *"/" } `, }, { name: "hidden fields", x: `{a: 1, _hidden1: 1, _hidden: 1}`, y: `{a: 1, _hidden2: 1, _hidden: 2}`, diff: ` { a: 1 - _hidden1: 1 + _hidden2: 1 - _hidden: 1 + _hidden: 2 } `, kind: Modified, }, { name: "ignore hidden fields in schema", x: `{a: 1, _hidden1: 1, _hidden: 1}`, y: `{a: 1, _hidden2: 1, _hidden: 2}`, profile: &Profile{SkipHidden: true}, }, { name: "ignore hidden fields in data", x: `{a: 1, _hidden1: 1, _hidden: 1}`, y: `{a: 1, _hidden2: 1, _hidden: 2}`, profile: &Profile{SkipHidden: true, Concrete: true}, }, { name: "all errors are equal", x: `1 & 3`, y: `1 & 4`, }} for _, tc := range testCases { cuetdtest.FullMatrix.Run(t, tc.name, func(t *testing.T, m *cuetdtest.M) { ctx := m.CueContext() // it is not fatal if x or y contain errors: some test cases // rely on interacting with such errors. x := ctx.CompileString(tc.x, cue.Filename("x")) y := ctx.CompileString(tc.y, cue.Filename("y")) p := tc.profile if p == nil { p = Schema } kind, script := p.Diff(x, y) if kind != tc.kind { t.Fatalf("got %d; want %d", kind, tc.kind) } if script != nil { w := &bytes.Buffer{} err := Print(w, script) if err != nil { t.Fatal(err) } if got := w.String(); got != tc.diff { t.Errorf("\ngot\n%s;\nwant\n%s", got, tc.diff) } } }) } } func TestX(t *testing.T) { t.Skip() tc := struct { x, y string kind Kind diff string }{ x: `{ } `, y: ` { } `, kind: Modified, diff: ``, } ctx := cuecontext.New() // it is not fatal if x or y contain errors: some test cases // rely on interacting with such errors. x := ctx.CompileString(tc.x, cue.Filename("x")) y := ctx.CompileString(tc.y, cue.Filename("y")) kind, script := Diff(x, y) if kind != tc.kind { t.Fatalf("got %d; want %d", kind, tc.kind) } w := &bytes.Buffer{} err := Print(w, script) if err != nil { t.Fatal(err) } if got := w.String(); got != tc.diff { t.Errorf("got\n%s;\nwant\n%s", got, tc.diff) } }