cuelang.org/go@v0.13.0/internal/core/validate/validate_test.go (about)

     1  // Copyright 2020 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  
    15  package validate_test
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  	"testing"
    21  
    22  	"cuelang.org/go/cue/errors"
    23  	"cuelang.org/go/cue/parser"
    24  	"cuelang.org/go/internal/core/adt"
    25  	"cuelang.org/go/internal/core/compile"
    26  	"cuelang.org/go/internal/core/eval"
    27  	"cuelang.org/go/internal/core/validate"
    28  	"cuelang.org/go/internal/cuetdtest"
    29  	"github.com/google/go-cmp/cmp"
    30  )
    31  
    32  func TestValidate(t *testing.T) {
    33  	type testCase struct {
    34  		name   string
    35  		in     string
    36  		out    string
    37  		lookup string
    38  		cfg    *validate.Config
    39  
    40  		todo_v3 bool
    41  	}
    42  	testCases := []testCase{{
    43  		name: "no error, but not concrete, even with definition label",
    44  		cfg:  &validate.Config{Concrete: true},
    45  		in: `
    46  		#foo: { use: string }
    47  		`,
    48  		lookup: "#foo",
    49  		out:    "incomplete\n#foo.use: incomplete value string:\n    test:2:16",
    50  	}, {
    51  		name: "definitions not considered for completeness",
    52  		cfg:  &validate.Config{Concrete: true},
    53  		in: `
    54  		#foo: { use: string }
    55  		`,
    56  	}, {
    57  		name: "hidden fields not considered for completeness",
    58  		cfg:  &validate.Config{Concrete: true},
    59  		in: `
    60  		_foo: { use: string }
    61  		`,
    62  	}, {
    63  		name: "hidden fields not considered for completeness",
    64  		in: `
    65  		_foo: { use: string }
    66  		`,
    67  	}, {
    68  		name: "evaluation error at top",
    69  		in: `
    70  		1 & 2
    71  		`,
    72  		out: "eval\nconflicting values 2 and 1:\n    test:2:3\n    test:2:7",
    73  	}, {
    74  		name: "evaluation error in field",
    75  		in: `
    76  		x: 1 & 2
    77  		`,
    78  		out: "eval\nx: conflicting values 2 and 1:\n    test:2:6\n    test:2:10",
    79  	}, {
    80  		name: "first error",
    81  		in: `
    82  		x: 1 & 2
    83  		y: 2 & 4
    84  		`,
    85  		out: "eval\nx: conflicting values 2 and 1:\n    test:2:6\n    test:2:10",
    86  	}, {
    87  		name: "all errors",
    88  		cfg:  &validate.Config{AllErrors: true},
    89  		in: `
    90  		x: 1 & 2
    91  		y: 2 & 4
    92  		`,
    93  		out: `eval
    94  x: conflicting values 2 and 1:
    95      test:2:6
    96      test:2:10
    97  y: conflicting values 4 and 2:
    98      test:3:6
    99      test:3:10`,
   100  	}, {
   101  		name: "incomplete",
   102  		cfg:  &validate.Config{Concrete: true},
   103  		in: `
   104  		y: 2 + x
   105  		x: string
   106  		`,
   107  		out: "incomplete\ny: non-concrete value string in operand to +:\n    test:2:6\n    test:3:6",
   108  	}, {
   109  		name: "allowed incomplete cycle",
   110  		in: `
   111  		y: x
   112  		x: y
   113  		`,
   114  	}, {
   115  		name: "allowed incomplete when disallowing cycles",
   116  		cfg:  &validate.Config{DisallowCycles: true},
   117  		in: `
   118  		y: string
   119  		x: y
   120  		`,
   121  	}, {
   122  		// TODO: different error position
   123  		todo_v3: true,
   124  
   125  		name: "disallow cycle",
   126  		cfg:  &validate.Config{DisallowCycles: true},
   127  		in: `
   128  		y: x + 1
   129  		x: y - 1
   130  		`,
   131  		out: "cycle\ncycle error:\n    test:2:6",
   132  	}, {
   133  		// TODO: different error position
   134  		todo_v3: true,
   135  
   136  		name: "disallow cycle",
   137  		cfg:  &validate.Config{DisallowCycles: true},
   138  		in: `
   139  		a: b - 100
   140  		b: a + 100
   141  		c: [c[1], c[0]]		`,
   142  		out: "cycle\ncycle error:\n    test:2:6",
   143  	}, {
   144  		name: "treat cycles as incomplete when not disallowing",
   145  		cfg:  &validate.Config{},
   146  		in: `
   147  		y: x + 1
   148  		x: y - 1
   149  		`,
   150  	}, {
   151  		// Note: this is already handled by evaluation, as terminal errors
   152  		// are percolated up.
   153  		name: "catch most serious error",
   154  		cfg:  &validate.Config{Concrete: true},
   155  		in: `
   156  		y: string
   157  		x: 1 & 2
   158  		`,
   159  		out: "eval\nx: conflicting values 2 and 1:\n    test:3:6\n    test:3:10",
   160  	}, {
   161  		name: "consider defaults for concreteness",
   162  		cfg:  &validate.Config{Concrete: true},
   163  		in: `
   164  		x: *1 | 2
   165  		`,
   166  	}, {
   167  		name: "allow non-concrete in definitions in concrete mode",
   168  		cfg:  &validate.Config{Concrete: true},
   169  		in: `
   170  		x: 2
   171  		#d: {
   172  			b: int
   173  			c: b + b
   174  		}
   175  		`,
   176  	}, {
   177  		name: "pick up non-concrete value in default",
   178  		cfg:  &validate.Config{Concrete: true},
   179  		in: `
   180  		x: null | *{
   181  			a: int
   182  		}
   183  		`,
   184  		out: "incomplete\nx.a: incomplete value int:\n    test:3:7",
   185  	}, {
   186  		name: "pick up non-concrete value in default",
   187  		cfg:  &validate.Config{Concrete: true},
   188  		in: `
   189  			x: null | *{
   190  				a: 1 | 2
   191  			}
   192  			`,
   193  		out: "incomplete\nx.a: incomplete value 1 | 2",
   194  	}, {
   195  		name: "required field not present",
   196  		cfg:  &validate.Config{Final: true},
   197  		in: `
   198  			Person: {
   199  				name!:  string
   200  				age?:   int
   201  				height: 1.80
   202  			}
   203  			`,
   204  		out: "incomplete\nPerson.name: field is required but not present:\n    test:3:5",
   205  	}, {
   206  		name: "allow required fields in definitions",
   207  		cfg:  &validate.Config{Concrete: true},
   208  		in: `
   209  		#Person: {
   210  			name!: string
   211  			age?:  int
   212  		}
   213  		`,
   214  		out: "",
   215  	}, {
   216  		name: "allow required fields when not concrete",
   217  		in: `
   218  		Person: {
   219  			name!: string
   220  			age?:  int
   221  		}
   222  		`,
   223  		out: "",
   224  	}, {
   225  		name: "indirect resolved disjunction using matchN",
   226  		cfg:  &validate.Config{Final: true},
   227  		in: `
   228  			x: {}
   229  			x: matchN(0, [bool | {x!: _}])
   230  		`,
   231  		out: "",
   232  	}, {
   233  		name: "indirect resolved disjunction",
   234  		cfg:  &validate.Config{Final: true},
   235  		in: `
   236  				x: {bar: 2}
   237  				x: string | {foo!: string}
   238  			`,
   239  		out: "incomplete\nx.foo: field is required but not present:\n    test:3:18",
   240  	}, {
   241  		name: "disallow incomplete error with final",
   242  		cfg:  &validate.Config{Final: true},
   243  		in: `
   244  			x: y + 1
   245  			y: int
   246  				`,
   247  		out: "incomplete\nx: non-concrete value int in operand to +:\n    test:2:7\n    test:3:7",
   248  	}, {
   249  		name: "allow incomplete error with final while in definition",
   250  		cfg:  &validate.Config{Final: true},
   251  		in: `
   252  			#D: x: #D.y + 1
   253  			#D: y: int
   254  				`,
   255  	}, {
   256  		name: "allow incomplete error with final while in definition",
   257  		cfg:  &validate.Config{Final: true},
   258  		in: `
   259  			#D: x: #D.y + 1
   260  			#D: y: int
   261  				`,
   262  	}, {
   263  		name: "report non-concrete value of structure shared node in correct position",
   264  		cfg: &validate.Config{
   265  			Concrete: true,
   266  			Final:    true,
   267  		},
   268  		in: `
   269  			#Def: a: x!: int
   270  			b: #Def
   271  			`,
   272  		out: "incomplete\nb.a.x: field is required but not present:\n    test:2:13\n    test:3:7",
   273  
   274  		todo_v3: true, // missing position
   275  	}, {
   276  		// Issue #3864: issue resulting from structure sharing.
   277  		name: "attribute incomplete values in definitions to concrete path",
   278  		cfg: &validate.Config{
   279  			Concrete: true,
   280  			Final:    true,
   281  		},
   282  		in: `
   283  			#A: y: string
   284  			#B: x: #A
   285  			#C: {
   286  				x: #A
   287  				v: #B & { x: x } // note: 'x' resolves to self.
   288  			}
   289  			config: #C & {
   290  				x: y: "dev"
   291  			}
   292  		`,
   293  		out: "incomplete\nconfig.v.x.y: incomplete value string:\n    test:2:11",
   294  	}}
   295  
   296  	cuetdtest.Run(t, testCases, func(t *cuetdtest.T, tc *testCase) {
   297  		if tc.todo_v3 {
   298  			t.M.TODO_V3(t) // P1: cycle error missing? Other error.
   299  		}
   300  		r := t.M.Runtime()
   301  		ctx := eval.NewContext(r, nil)
   302  
   303  		f, err := parser.ParseFile("test", tc.in)
   304  		if err != nil {
   305  			t.Fatal(err)
   306  		}
   307  		v, err := compile.Files(nil, r, "", f)
   308  		if err != nil {
   309  			t.Fatal(err)
   310  		}
   311  		v.Finalize(ctx)
   312  		if tc.lookup != "" {
   313  			v = v.Lookup(adt.MakeIdentLabel(r, tc.lookup, "main"))
   314  		}
   315  
   316  		b := validate.Validate(ctx, v, tc.cfg)
   317  
   318  		w := &strings.Builder{}
   319  		if b != nil {
   320  			fmt.Fprintln(w, b.Code)
   321  			errors.Print(w, b.Err, nil)
   322  		}
   323  
   324  		got := strings.TrimSpace(w.String())
   325  		if tc.out != got {
   326  			t.Error(cmp.Diff(tc.out, got))
   327  		}
   328  	})
   329  }