cuelang.org/go@v0.10.1/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: discarded cycle error
   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: discarded cycle error
   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  		// TODO: missing error position
   196  		todo_v3: true,
   197  
   198  		name: "required field not present",
   199  		cfg:  &validate.Config{Final: true},
   200  		in: `
   201  			Person: {
   202  				name!:  string
   203  				age?:   int
   204  				height: 1.80
   205  			}
   206  			`,
   207  		out: "incomplete\nPerson.name: field is required but not present:\n    test:3:5",
   208  	}, {
   209  		name: "allow required fields in definitions",
   210  		cfg:  &validate.Config{Concrete: true},
   211  		in: `
   212  		#Person: {
   213  			name!: string
   214  			age?:  int
   215  		}
   216  		`,
   217  		out: "",
   218  	}, {
   219  		name: "allow required fields when not concrete",
   220  		in: `
   221  		Person: {
   222  			name!: string
   223  			age?:  int
   224  		}
   225  		`,
   226  		out: "",
   227  	}}
   228  
   229  	cuetdtest.Run(t, testCases, func(t *cuetdtest.T, tc *testCase) {
   230  		if tc.todo_v3 {
   231  			t.M.TODO_V3()
   232  		}
   233  		r := t.M.Runtime()
   234  		ctx := eval.NewContext(r, nil)
   235  
   236  		f, err := parser.ParseFile("test", tc.in)
   237  		if err != nil {
   238  			t.Fatal(err)
   239  		}
   240  		v, err := compile.Files(nil, r, "", f)
   241  		if err != nil {
   242  			t.Fatal(err)
   243  		}
   244  		v.Finalize(ctx)
   245  		if tc.lookup != "" {
   246  			v = v.Lookup(adt.MakeIdentLabel(r, tc.lookup, "main"))
   247  		}
   248  
   249  		b := validate.Validate(ctx, v, tc.cfg)
   250  
   251  		w := &strings.Builder{}
   252  		if b != nil {
   253  			fmt.Fprintln(w, b.Code)
   254  			errors.Print(w, b.Err, nil)
   255  		}
   256  
   257  		got := strings.TrimSpace(w.String())
   258  		if tc.out != got {
   259  			t.Error(cmp.Diff(tc.out, got))
   260  		}
   261  	})
   262  }