cuelang.org/go@v0.13.0/cue/path_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 cue_test
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"testing"
    21  
    22  	"cuelang.org/go/cue"
    23  	"cuelang.org/go/cue/cuecontext"
    24  	"cuelang.org/go/internal/cuetdtest"
    25  )
    26  
    27  func TestPaths(t *testing.T) {
    28  	type testCase struct {
    29  		path cue.Path
    30  		out  string
    31  		str  string
    32  		err  bool
    33  	}
    34  	testCases := []testCase{{
    35  		path: cue.MakePath(cue.Str("list"), cue.AnyIndex),
    36  		out:  "int",
    37  		str:  "list.[_]",
    38  	}, {
    39  
    40  		path: cue.MakePath(cue.Def("#Foo"), cue.Str("a"), cue.Str("b")),
    41  		out:  "1",
    42  		str:  "#Foo.a.b",
    43  	}, {
    44  		path: cue.ParsePath(`#Foo.a.b`),
    45  		out:  "1",
    46  		str:  "#Foo.a.b",
    47  	}, {
    48  		path: cue.ParsePath(`"#Foo".c.d`),
    49  		out:  "2",
    50  		str:  `"#Foo".c.d`,
    51  	}, {
    52  		// fallback Def(Foo) -> Def(#Foo)
    53  		path: cue.MakePath(cue.Def("Foo"), cue.Str("a"), cue.Str("b")),
    54  		out:  "1",
    55  		str:  "#Foo.a.b",
    56  	}, {
    57  		path: cue.MakePath(cue.Str("b"), cue.Index(2)),
    58  		out:  "6",
    59  		str:  "b[2]", // #Foo.b.2
    60  	}, {
    61  		path: cue.MakePath(cue.Str("c"), cue.Str("#Foo")),
    62  		out:  "7",
    63  		str:  `c."#Foo"`,
    64  	}, {
    65  		path: cue.MakePath(cue.Hid("_foo", "_"), cue.Str("b")),
    66  		out:  "5",
    67  		str:  `_foo.b`,
    68  	}, {
    69  		path: cue.ParsePath("#Foo.a.b"),
    70  		str:  "#Foo.a.b",
    71  		out:  "1",
    72  	}, {
    73  		path: cue.ParsePath("#Foo.a.c"),
    74  		str:  "#Foo.a.c",
    75  		out:  `_|_ // field not found: c`,
    76  	}, {
    77  		path: cue.ParsePath(`b[2]`),
    78  		str:  `b[2]`,
    79  		out:  "6",
    80  	}, {
    81  		path: cue.ParsePath(`c."#Foo"`),
    82  		str:  `c."#Foo"`,
    83  		out:  "7",
    84  	}, {
    85  		path: cue.ParsePath("foo._foo"),
    86  		str:  "_|_",
    87  		err:  true,
    88  		out:  `_|_ // invalid path: hidden label _foo not allowed`,
    89  	}, {
    90  		path: cue.ParsePath(`c."#Foo`),
    91  		str:  "_|_",
    92  		err:  true,
    93  		out:  `_|_ // string literal not terminated`,
    94  	}, {
    95  		path: cue.ParsePath(`b[a]`),
    96  		str:  "_|_",
    97  		err:  true,
    98  		out:  `_|_ // non-constant expression a`,
    99  	}, {
   100  		path: cue.ParsePath(`b['1']`),
   101  		str:  "_|_",
   102  		err:  true,
   103  		out:  `_|_ // invalid string index '1'`,
   104  	}, {
   105  		path: cue.ParsePath(`b[3T]`),
   106  		str:  "_|_",
   107  		err:  true,
   108  		out:  `_|_ // int label out of range (3000000000000 not >=0 and <= 268435454)`,
   109  	}, {
   110  		path: cue.ParsePath(`b[3.3]`),
   111  		str:  "_|_",
   112  		err:  true,
   113  		out:  `_|_ // invalid literal 3.3`,
   114  	}, {
   115  		path: cue.MakePath(cue.Str("map"), cue.AnyString),
   116  		out:  "int",
   117  		str:  "map.[_]",
   118  	}, {
   119  		path: cue.MakePath(cue.Str("list"), cue.AnyIndex),
   120  		out:  "int",
   121  		str:  "list.[_]",
   122  	}, {
   123  		path: cue.ParsePath("x.y"),
   124  		out:  "{\n\tb: 0\n}",
   125  		str:  "x.y",
   126  	}, {
   127  		path: cue.ParsePath("x.y.b"),
   128  		out:  "0",
   129  		str:  "x.y.b",
   130  	}, {
   131  		// Issue 3577
   132  		path: cue.ParsePath("pkg.y"),
   133  		out:  `"hello"`,
   134  		str:  "pkg.y", // show original path, not structure shared path.
   135  	}, {
   136  		// Issue 3922
   137  		path: cue.ParsePath("out.name"),
   138  		out:  `"one"`,
   139  		str:  "out.name",
   140  	}}
   141  
   142  	cuetdtest.Run(t, testCases, func(t *cuetdtest.T, tc *testCase) {
   143  		ctx := t.M.CueContext()
   144  		val := mustCompile(t, ctx, `
   145  			#Foo:   a: b: 1
   146  			"#Foo": c: d: 2
   147  			_foo: b: 5
   148  			a: 3
   149  			b: [4, 5, 6]
   150  			c: "#Foo": 7
   151  			map: [string]: int
   152  			list: [...int]
   153  
   154  			// Issue 2060
   155  			let X = {a: b: 0}
   156  			x: y: X.a
   157  
   158  			// Issue 3577
   159  			pkg: z
   160  			z: y: "hello"
   161  
   162  			// Issue 3922
   163  			out: #Output
   164  			#Output: name: _data.name
   165  			_data: name: "one"
   166  		`)
   167  
   168  		t.Equal(tc.path.Err() != nil, tc.err)
   169  
   170  		w := val.LookupPath(tc.path)
   171  
   172  		t.Equal(fmt.Sprint(w), tc.out)
   173  
   174  		if w.Err() != nil {
   175  			return
   176  		}
   177  
   178  		t.Equal(w.Path().String(), tc.str)
   179  	})
   180  }
   181  
   182  // TestWalkPath is a more comprehensive table-driven test.
   183  func TestWalkPath(t *testing.T) {
   184  	ctx := cuecontext.New()
   185  
   186  	testCases := []struct {
   187  		name      string
   188  		cueInput  string
   189  		wantPaths []string
   190  	}{
   191  		{
   192  			name: "issue 3922",
   193  			cueInput: `
   194  				out: #Output
   195  				#Output: name: _data.name
   196  				_data: name: "one"
   197  			`,
   198  			wantPaths: []string{
   199  				"", // root
   200  				"out",
   201  				"out.name",
   202  			},
   203  		},
   204  		{
   205  			name: "simple struct",
   206  			cueInput: `
   207  				b: {
   208  					d: 3
   209  					c: 2
   210  				}
   211  				a: 1
   212  			`,
   213  			wantPaths: []string{
   214  				"", // root
   215  				"b",
   216  				"b.d",
   217  				"b.c",
   218  				"a",
   219  			},
   220  		},
   221  		{
   222  			name: "struct with list",
   223  			cueInput: `
   224  				l: [10, {y: 30, x: 20}]
   225  			`,
   226  			wantPaths: []string{
   227  				"", // root
   228  				"l",
   229  				"l[0]",
   230  				"l[1]",
   231  				"l[1].y",
   232  				"l[1].x",
   233  			},
   234  		},
   235  		{
   236  			name:     "root list",
   237  			cueInput: `[10, {x: 20}]`,
   238  			wantPaths: []string{
   239  				"", // root
   240  				"[0]",
   241  				"[1]",
   242  				"[1].x",
   243  			},
   244  		},
   245  		{
   246  			name:     "root literal string",
   247  			cueInput: `"hello"`,
   248  			wantPaths: []string{
   249  				"", // root
   250  			},
   251  		},
   252  		{
   253  			name:     "root literal int",
   254  			cueInput: `123`,
   255  			wantPaths: []string{
   256  				"", // root
   257  			},
   258  		},
   259  		{
   260  			name:     "empty struct",
   261  			cueInput: `{}`,
   262  			wantPaths: []string{
   263  				"", // root
   264  			},
   265  		},
   266  		{
   267  			name: "struct with various field types",
   268  			cueInput: `
   269  				c: _h
   270  				_h: #D
   271  				#D: b: c: 3
   272  				a: _g
   273  				_g: #B
   274  				#B: x: "definition B"
   275  			`,
   276  			// Order: regular (a, c), definitions (#B, #D), hidden (_g, _h)
   277  			wantPaths: []string{
   278  				"", // root
   279  				"c",
   280  				"c.b",
   281  				"c.b.c",
   282  				"a",
   283  				"a.x",
   284  			},
   285  		},
   286  	}
   287  
   288  	for _, tc := range testCases {
   289  		t.Run(tc.name, func(t *testing.T) {
   290  			v := ctx.CompileString(tc.cueInput)
   291  			if err := v.Err(); err != nil {
   292  				t.Fatalf("CompileString failed for input\n%s\nError: %v", tc.cueInput, err)
   293  			}
   294  
   295  			var gotPaths []string
   296  			v.Walk(func(val cue.Value) bool {
   297  				gotPaths = append(gotPaths, val.Path().String())
   298  				return true
   299  			}, nil)
   300  
   301  			if !reflect.DeepEqual(gotPaths, tc.wantPaths) {
   302  				t.Errorf("Walk() paths mismatch for input\n%s\ngot:  %#v\nwant: %#v", tc.cueInput, gotPaths, tc.wantPaths)
   303  			}
   304  		})
   305  	}
   306  }
   307  
   308  var selectorTests = []struct {
   309  	sel          cue.Selector
   310  	stype        cue.SelectorType
   311  	string       string
   312  	unquoted     string
   313  	index        int
   314  	isHidden     bool
   315  	isConstraint bool
   316  	isDefinition bool
   317  	isString     bool
   318  	pkgPath      string
   319  }{{
   320  	sel:      cue.Str("foo"),
   321  	stype:    cue.StringLabel,
   322  	string:   "foo",
   323  	unquoted: "foo",
   324  	isString: true,
   325  }, {
   326  	sel:      cue.Str("_foo"),
   327  	stype:    cue.StringLabel,
   328  	string:   `"_foo"`,
   329  	unquoted: "_foo",
   330  	isString: true,
   331  }, {
   332  	sel:      cue.Str(`a "b`),
   333  	stype:    cue.StringLabel,
   334  	string:   `"a \"b"`,
   335  	unquoted: `a "b`,
   336  	isString: true,
   337  }, {
   338  	sel:    cue.Index(5),
   339  	stype:  cue.IndexLabel,
   340  	string: "5",
   341  	index:  5,
   342  }, {
   343  	sel:          cue.Def("foo"),
   344  	stype:        cue.DefinitionLabel,
   345  	string:       "#foo",
   346  	isDefinition: true,
   347  }, {
   348  	sel:          cue.Str("foo").Optional(),
   349  	stype:        cue.StringLabel | cue.OptionalConstraint,
   350  	string:       "foo?",
   351  	unquoted:     "foo",
   352  	isString:     true,
   353  	isConstraint: true,
   354  }, {
   355  	sel:          cue.Str("foo").Required(),
   356  	stype:        cue.StringLabel | cue.RequiredConstraint,
   357  	string:       "foo!",
   358  	unquoted:     "foo",
   359  	isString:     true,
   360  	isConstraint: true,
   361  }, {
   362  	sel:          cue.Def("foo").Required().Optional(),
   363  	stype:        cue.DefinitionLabel | cue.OptionalConstraint,
   364  	string:       "#foo?",
   365  	isDefinition: true,
   366  	isConstraint: true,
   367  }, {
   368  	sel:          cue.Def("foo").Optional().Required(),
   369  	stype:        cue.DefinitionLabel | cue.RequiredConstraint,
   370  	string:       "#foo!",
   371  	isDefinition: true,
   372  	isConstraint: true,
   373  }, {
   374  	sel:          cue.AnyString,
   375  	stype:        cue.StringLabel | cue.PatternConstraint,
   376  	string:       "[_]",
   377  	isConstraint: true,
   378  }, {
   379  	sel:          cue.AnyIndex,
   380  	stype:        cue.IndexLabel | cue.PatternConstraint,
   381  	string:       "[_]",
   382  	isConstraint: true,
   383  }, {
   384  	sel:      cue.Hid("_foo", "example.com"),
   385  	stype:    cue.HiddenLabel,
   386  	string:   "_foo",
   387  	isHidden: true,
   388  	pkgPath:  "example.com",
   389  }, {
   390  	sel:          cue.Hid("_#foo", "example.com"),
   391  	stype:        cue.HiddenDefinitionLabel,
   392  	string:       "_#foo",
   393  	isHidden:     true,
   394  	isDefinition: true,
   395  	pkgPath:      "example.com",
   396  }}
   397  
   398  func TestSelector(t *testing.T) {
   399  	for _, tc := range selectorTests {
   400  		t.Run(tc.sel.String(), func(t *testing.T) {
   401  			sel := tc.sel
   402  			if got, want := sel.Type(), tc.stype; got != want {
   403  				t.Errorf("unexpected type; got %v want %v", got, want)
   404  			}
   405  			if got, want := sel.String(), tc.string; got != want {
   406  				t.Errorf("unexpected sel.String result; got %q want %q", got, want)
   407  			}
   408  			if tc.unquoted == "" {
   409  				checkPanic(t, "Selector.Unquoted invoked on non-string label", func() {
   410  					sel.Unquoted()
   411  				})
   412  			} else {
   413  				if got, want := sel.Unquoted(), tc.unquoted; got != want {
   414  					t.Errorf("unexpected sel.Unquoted result; got %q want %q", got, want)
   415  				}
   416  			}
   417  			if sel.Type() != cue.IndexLabel {
   418  				checkPanic(t, "Index called on non-index selector", func() {
   419  					sel.Index()
   420  				})
   421  			} else {
   422  				if got, want := sel.Index(), tc.index; got != want {
   423  					t.Errorf("unexpected sel.Index result; got %v want %v", got, want)
   424  				}
   425  			}
   426  			if got, want := sel.Type().IsHidden(), tc.isHidden; got != want {
   427  				t.Errorf("unexpected sel.IsHidden result; got %v want %v", got, want)
   428  			}
   429  			if got, want := sel.IsConstraint(), tc.isConstraint; got != want {
   430  				t.Errorf("unexpected sel.IsOptional result; got %v want %v", got, want)
   431  			}
   432  			if got, want := sel.IsString(), tc.isString; got != want {
   433  				t.Errorf("unexpected sel.IsString result; got %v want %v", got, want)
   434  			}
   435  			if got, want := sel.IsDefinition(), tc.isDefinition; got != want {
   436  				t.Errorf("unexpected sel.IsDefinition result; got %v want %v", got, want)
   437  			}
   438  			if got, want := sel.PkgPath(), tc.pkgPath; got != want {
   439  				t.Errorf("unexpected sel.PkgPath result; got %v want %v", got, want)
   440  			}
   441  		})
   442  	}
   443  }
   444  
   445  func TestSelectorTypeString(t *testing.T) {
   446  	if got, want := cue.InvalidSelectorType.String(), "NoLabels"; got != want {
   447  		t.Errorf("unexpected SelectorType.String result; got %q want %q", got, want)
   448  	}
   449  	if got, want := cue.PatternConstraint.String(), "PatternConstraint"; got != want {
   450  		t.Errorf("unexpected SelectorType.String result; got %q want %q", got, want)
   451  	}
   452  	if got, want := (cue.StringLabel | cue.OptionalConstraint).String(), "StringLabel|OptionalConstraint"; got != want {
   453  		t.Errorf("unexpected SelectorType.String result; got %q want %q", got, want)
   454  	}
   455  	if got, want := cue.SelectorType(255).String(), "StringLabel|IndexLabel|DefinitionLabel|HiddenLabel|HiddenDefinitionLabel|OptionalConstraint|RequiredConstraint|PatternConstraint"; got != want {
   456  		t.Errorf("unexpected SelectorType.String result; got %q want %q", got, want)
   457  	}
   458  }
   459  
   460  func checkPanic(t *testing.T, wantPanicStr string, f func()) {
   461  	gotPanicStr := ""
   462  	func() {
   463  		defer func() {
   464  			e := recover()
   465  			if e == nil {
   466  				t.Errorf("function did not panic")
   467  				return
   468  			}
   469  			gotPanicStr = fmt.Sprint(e)
   470  		}()
   471  		f()
   472  	}()
   473  	if got, want := gotPanicStr, wantPanicStr; got != want {
   474  		t.Errorf("unexpected panic message; got %q want %q", got, want)
   475  	}
   476  }