...

Source file src/cuelang.org/go/cue/load/loader_test.go

Documentation: cuelang.org/go/cue/load

     1  // Copyright 2018 The 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 load
    16  
    17  import (
    18  	"bytes"
    19  	"os"
    20  	"path/filepath"
    21  	"strings"
    22  	"sync"
    23  	"testing"
    24  	"text/template"
    25  	"unicode"
    26  
    27  	"cuelang.org/go/cue"
    28  	"cuelang.org/go/cue/errors"
    29  	"cuelang.org/go/cue/format"
    30  	"cuelang.org/go/internal/cueexperiment"
    31  	"cuelang.org/go/internal/tdtest"
    32  )
    33  
    34  func init() {
    35  	// Ignore the value of CUE_EXPERIMENT for the purposes
    36  	// of these tests, which we want to test both with the experiment
    37  	// enabled and disabled.
    38  	os.Setenv("CUE_EXPERIMENT", "")
    39  
    40  	// Once we've called cueexperiment.Init, cueexperiment.Vars
    41  	// will not be touched again, so we can set fields in it for the tests.
    42  	cueexperiment.Init()
    43  
    44  	// The user running `go test` might have a broken environment,
    45  	// such as an invalid $CUE_REGISTRY like the one below,
    46  	// or a broken $DOCKER_CONFIG/config.json due to syntax errors.
    47  	// Go tests should be hermetic by explicitly setting load.Config.Env;
    48  	// catch any that do not by leaving a broken $CUE_REGISTRY in os.Environ.
    49  	os.Setenv("CUE_REGISTRY", "inline:{")
    50  }
    51  
    52  // TestLoad is an end-to-end test.
    53  func TestLoad(t *testing.T) {
    54  	cwd, err := os.Getwd()
    55  	if err != nil {
    56  		t.Fatal(err)
    57  	}
    58  	testdataDir := testdata("testmod")
    59  	dirCfg := &Config{
    60  		Dir:   testdataDir,
    61  		Tools: true,
    62  	}
    63  	badModCfg := &Config{
    64  		Dir: testdata("badmod"),
    65  	}
    66  	type loadTest struct {
    67  		cfg  *Config
    68  		args []string
    69  		want string
    70  	}
    71  
    72  	testCases := []loadTest{{
    73  		cfg:  badModCfg,
    74  		args: []string{"."},
    75  		want: `err:    module: cannot use value 123 (type int) as string:
    76      $CWD/testdata/badmod/cue.mod/module.cue:2:9
    77  path:   ""
    78  module: ""
    79  root:   ""
    80  dir:    ""
    81  display:""`,
    82  	}, {
    83  		// Even though the directory is called testdata, the last path in
    84  		// the module is test. So "package test" is correctly the default
    85  		// package of this directory.
    86  		cfg:  dirCfg,
    87  		args: nil,
    88  		want: `path:   mod.test/test
    89  module: mod.test/test
    90  root:   $CWD/testdata/testmod
    91  dir:    $CWD/testdata/testmod
    92  display:.
    93  files:
    94      $CWD/testdata/testmod/test.cue
    95  imports:
    96      mod.test/test/sub: $CWD/testdata/testmod/sub/sub.cue`}, {
    97  		// Even though the directory is called testdata, the last path in
    98  		// the module is test. So "package test" is correctly the default
    99  		// package of this directory.
   100  		cfg:  dirCfg,
   101  		args: []string{"."},
   102  		want: `path:   mod.test/test
   103  module: mod.test/test
   104  root:   $CWD/testdata/testmod
   105  dir:    $CWD/testdata/testmod
   106  display:.
   107  files:
   108      $CWD/testdata/testmod/test.cue
   109  imports:
   110      mod.test/test/sub: $CWD/testdata/testmod/sub/sub.cue`}, {
   111  		// TODO:
   112  		// - path incorrect, should be mod.test/test/other:main.
   113  		cfg:  dirCfg,
   114  		args: []string{"./other/..."},
   115  		want: `err:    import failed: relative import paths not allowed ("./file"):
   116      $CWD/testdata/testmod/other/main.cue:6:2
   117  path:   ""
   118  module: mod.test/test
   119  root:   $CWD/testdata/testmod
   120  dir:    ""
   121  display:""`}, {
   122  		cfg:  dirCfg,
   123  		args: []string{"./anon"},
   124  		want: `err:    build constraints exclude all CUE files in ./anon:
   125      anon/anon.cue: no package name
   126  path:   mod.test/test/anon
   127  module: mod.test/test
   128  root:   $CWD/testdata/testmod
   129  dir:    $CWD/testdata/testmod/anon
   130  display:./anon`}, {
   131  		// TODO:
   132  		// - paths are incorrect, should be mod.test/test/other:main.
   133  		cfg:  dirCfg,
   134  		args: []string{"./other"},
   135  		want: `err:    import failed: relative import paths not allowed ("./file"):
   136      $CWD/testdata/testmod/other/main.cue:6:2
   137  path:   mod.test/test/other:main
   138  module: mod.test/test
   139  root:   $CWD/testdata/testmod
   140  dir:    $CWD/testdata/testmod/other
   141  display:./other
   142  files:
   143      $CWD/testdata/testmod/other/main.cue`}, {
   144  		// TODO:
   145  		// - incorrect path, should be mod.test/test/hello:test
   146  		cfg:  dirCfg,
   147  		args: []string{"./hello"},
   148  		want: `path:   mod.test/test/hello:test
   149  module: mod.test/test
   150  root:   $CWD/testdata/testmod
   151  dir:    $CWD/testdata/testmod/hello
   152  display:./hello
   153  files:
   154      $CWD/testdata/testmod/test.cue
   155      $CWD/testdata/testmod/hello/test.cue
   156  imports:
   157      mod.test/test/sub: $CWD/testdata/testmod/sub/sub.cue`}, {
   158  		// TODO:
   159  		// - incorrect path, should be mod.test/test/hello:test
   160  		cfg:  dirCfg,
   161  		args: []string{"mod.test/test/hello:test"},
   162  		want: `path:   mod.test/test/hello:test
   163  module: mod.test/test
   164  root:   $CWD/testdata/testmod
   165  dir:    $CWD/testdata/testmod/hello
   166  display:mod.test/test/hello:test
   167  files:
   168      $CWD/testdata/testmod/test.cue
   169      $CWD/testdata/testmod/hello/test.cue
   170  imports:
   171      mod.test/test/sub: $CWD/testdata/testmod/sub/sub.cue`}, {
   172  		// TODO:
   173  		// - incorrect path, should be mod.test/test/hello:test
   174  		cfg:  dirCfg,
   175  		args: []string{"mod.test/test/hello:nonexist"},
   176  		want: `err:    build constraints exclude all CUE files in mod.test/test/hello:nonexist:
   177      anon.cue: no package name
   178      test.cue: package is test, want nonexist
   179      hello/test.cue: package is test, want nonexist
   180  path:   mod.test/test/hello:nonexist
   181  module: mod.test/test
   182  root:   $CWD/testdata/testmod
   183  dir:    $CWD/testdata/testmod/hello
   184  display:mod.test/test/hello:nonexist`}, {
   185  		cfg:  dirCfg,
   186  		args: []string{"./anon.cue", "./other/anon.cue"},
   187  		want: `path:   ""
   188  module: ""
   189  root:   $CWD/testdata/testmod
   190  dir:    $CWD/testdata/testmod
   191  display:command-line-arguments
   192  files:
   193      $CWD/testdata/testmod/anon.cue
   194      $CWD/testdata/testmod/other/anon.cue`}, {
   195  		cfg: dirCfg,
   196  		// Absolute file is normalized.
   197  		args: []string{filepath.Join(cwd, testdata("testmod", "anon.cue"))},
   198  		want: `path:   ""
   199  module: ""
   200  root:   $CWD/testdata/testmod
   201  dir:    $CWD/testdata/testmod
   202  display:command-line-arguments
   203  files:
   204      $CWD/testdata/testmod/anon.cue`}, {
   205  		cfg:  dirCfg,
   206  		args: []string{"-"},
   207  		want: `path:   ""
   208  module: ""
   209  root:   $CWD/testdata/testmod
   210  dir:    $CWD/testdata/testmod
   211  display:command-line-arguments
   212  files:
   213      -`}, {
   214  		// NOTE: dir should probably be set to $CWD/testdata, but either way.
   215  		cfg:  dirCfg,
   216  		args: []string{"non-existing"},
   217  		want: `err:    implied package identifier "non-existing" from import path "non-existing" is not valid
   218  path:   non-existing
   219  module: mod.test/test
   220  root:   $CWD/testdata/testmod
   221  dir:    $CWD/testdata/testmod/cue.mod/gen/non-existing
   222  display:non-existing`,
   223  	}, {
   224  		cfg:  dirCfg,
   225  		args: []string{"./empty"},
   226  		want: `err:    no CUE files in ./empty
   227  path:   mod.test/test/empty
   228  module: mod.test/test
   229  root:   $CWD/testdata/testmod
   230  dir:    $CWD/testdata/testmod/empty
   231  display:./empty`,
   232  	}, {
   233  		cfg:  dirCfg,
   234  		args: []string{"./imports"},
   235  		want: `path:   mod.test/test/imports
   236  module: mod.test/test
   237  root:   $CWD/testdata/testmod
   238  dir:    $CWD/testdata/testmod/imports
   239  display:./imports
   240  files:
   241      $CWD/testdata/testmod/imports/imports.cue
   242  imports:
   243      mod.test/catch: $CWD/testdata/testmod/cue.mod/pkg/mod.test/catch/catch.cue
   244      mod.test/helper:helper1: $CWD/testdata/testmod/cue.mod/pkg/mod.test/helper/helper1.cue`}, {
   245  		cfg:  dirCfg,
   246  		args: []string{"./toolonly"},
   247  		want: `path:   mod.test/test/toolonly:foo
   248  module: mod.test/test
   249  root:   $CWD/testdata/testmod
   250  dir:    $CWD/testdata/testmod/toolonly
   251  display:./toolonly
   252  files:
   253      $CWD/testdata/testmod/toolonly/foo_tool.cue`}, {
   254  		cfg: &Config{
   255  			Dir: testdataDir,
   256  		},
   257  		args: []string{"./toolonly"},
   258  		want: `err:    build constraints exclude all CUE files in ./toolonly:
   259      anon.cue: no package name
   260      test.cue: package is test, want foo
   261      toolonly/foo_tool.cue: _tool.cue files excluded in non-cmd mode
   262  path:   mod.test/test/toolonly:foo
   263  module: mod.test/test
   264  root:   $CWD/testdata/testmod
   265  dir:    $CWD/testdata/testmod/toolonly
   266  display:./toolonly`}, {
   267  		cfg: &Config{
   268  			Dir:  testdataDir,
   269  			Tags: []string{"prod"},
   270  		},
   271  		args: []string{"./tags"},
   272  		want: `path:   mod.test/test/tags
   273  module: mod.test/test
   274  root:   $CWD/testdata/testmod
   275  dir:    $CWD/testdata/testmod/tags
   276  display:./tags
   277  files:
   278      $CWD/testdata/testmod/tags/prod.cue`}, {
   279  		cfg: &Config{
   280  			Dir:  testdataDir,
   281  			Tags: []string{"prod", "foo=bar"},
   282  		},
   283  		args: []string{"./tags"},
   284  		want: `path:   mod.test/test/tags
   285  module: mod.test/test
   286  root:   $CWD/testdata/testmod
   287  dir:    $CWD/testdata/testmod/tags
   288  display:./tags
   289  files:
   290      $CWD/testdata/testmod/tags/prod.cue`}, {
   291  		cfg: &Config{
   292  			Dir:  testdataDir,
   293  			Tags: []string{"prod"},
   294  		},
   295  		args: []string{"./tagsbad"},
   296  		want: `err:    tag "prod" not used in any file
   297  previous declaration here:
   298      $CWD/testdata/testmod/tagsbad/prod.cue:1:1
   299  multiple @if attributes:
   300      $CWD/testdata/testmod/tagsbad/prod.cue:2:1
   301  path:   mod.test/test/tagsbad
   302  module: mod.test/test
   303  root:   $CWD/testdata/testmod
   304  dir:    $CWD/testdata/testmod/tagsbad
   305  display:./tagsbad`}, {
   306  		cfg: &Config{
   307  			Dir: testdataDir,
   308  		},
   309  		args: []string{"./cycle"},
   310  		want: `err:    import failed: import failed: import failed: package import cycle not allowed:
   311      $CWD/testdata/testmod/cycle/cycle.cue:3:8
   312      $CWD/testdata/testmod/cue.mod/pkg/mod.test/cycle/bar/bar.cue:3:8
   313      $CWD/testdata/testmod/cue.mod/pkg/mod.test/cycle/foo/foo.cue:3:8
   314  path:   mod.test/test/cycle
   315  module: mod.test/test
   316  root:   $CWD/testdata/testmod
   317  dir:    $CWD/testdata/testmod/cycle
   318  display:./cycle
   319  files:
   320      $CWD/testdata/testmod/cycle/cycle.cue`}}
   321  	tdtest.Run(t, testCases, func(t *tdtest.T, tc *loadTest) {
   322  		pkgs := Instances(tc.args, tc.cfg)
   323  
   324  		buf := &bytes.Buffer{}
   325  		err := pkgInfo.Execute(buf, pkgs)
   326  		if err != nil {
   327  			t.Fatal(err)
   328  		}
   329  
   330  		got := strings.TrimSpace(buf.String())
   331  		got = strings.Replace(got, cwd, "$CWD", -1)
   332  		// Errors are printed with slashes, so replace
   333  		// the slash-separated form of CWD too.
   334  		got = strings.Replace(got, filepath.ToSlash(cwd), "$CWD", -1)
   335  		// Make test work with Windows.
   336  		got = strings.Replace(got, string(filepath.Separator), "/", -1)
   337  
   338  		t.Equal(got, tc.want)
   339  	})
   340  }
   341  
   342  var pkgInfo = template.Must(template.New("pkg").Funcs(template.FuncMap{
   343  	"errordetails": func(err error) string {
   344  		s := errors.Details(err, &errors.Config{
   345  			ToSlash: true,
   346  		})
   347  		s = strings.TrimSuffix(s, "\n")
   348  		return s
   349  	}}).Parse(`
   350  {{- range . -}}
   351  {{- if .Err}}err:    {{errordetails .Err}}{{end}}
   352  path:   {{if .ImportPath}}{{.ImportPath}}{{else}}""{{end}}
   353  module: {{with .Module}}{{.}}{{else}}""{{end}}
   354  root:   {{with .Root}}{{.}}{{else}}""{{end}}
   355  dir:    {{with .Dir}}{{.}}{{else}}""{{end}}
   356  display:{{with .DisplayPath}}{{.}}{{else}}""{{end}}
   357  {{if .Files -}}
   358  files:
   359  {{- range .Files}}
   360      {{.Filename}}
   361  {{- end -}}
   362  {{- end}}
   363  {{if .Imports -}}
   364  imports:
   365  {{- range .Dependencies}}
   366      {{.ImportPath}}:{{range .Files}} {{.Filename}}{{end}}
   367  {{- end}}
   368  {{end -}}
   369  {{- end -}}
   370  `))
   371  
   372  func TestOverlays(t *testing.T) {
   373  	cwd, _ := os.Getwd()
   374  	abs := func(path string) string {
   375  		return filepath.Join(cwd, path)
   376  	}
   377  	c := &Config{
   378  		Overlay: map[string]Source{
   379  			// Not necessary, but nice to add.
   380  			abs("cue.mod"): FromString(`module: "mod.test"`),
   381  
   382  			abs("dir/top.cue"): FromBytes([]byte(`
   383  			   package top
   384  			   msg: "Hello"
   385  			`)),
   386  			abs("dir/b/foo.cue"): FromString(`
   387  			   package foo
   388  
   389  			   a: <= 5
   390  			`),
   391  			abs("dir/b/bar.cue"): FromString(`
   392  			   package foo
   393  
   394  			   a: >= 5
   395  			`),
   396  		},
   397  	}
   398  	want := []string{
   399  		`{msg:"Hello"}`,
   400  		`{a:5}`,
   401  	}
   402  	rmSpace := func(r rune) rune {
   403  		if unicode.IsSpace(r) {
   404  			return -1
   405  		}
   406  		return r
   407  	}
   408  	for i, inst := range cue.Build(Instances([]string{"./dir/..."}, c)) {
   409  		if inst.Err != nil {
   410  			t.Error(inst.Err)
   411  			continue
   412  		}
   413  		b, err := format.Node(inst.Value().Syntax(cue.Final()))
   414  		if err != nil {
   415  			t.Error(err)
   416  			continue
   417  		}
   418  		if got := string(bytes.Map(rmSpace, b)); got != want[i] {
   419  			t.Errorf("%s: got %s; want %s", inst.Dir, got, want[i])
   420  		}
   421  	}
   422  }
   423  
   424  func TestLoadInstancesConcurrent(t *testing.T) {
   425  	// This test is designed to fail when run with the race detector
   426  	// if there's an underlying race condition.
   427  	// See https:/cuelang.org/issue/1746
   428  	race(func() {
   429  		Instances([]string{"."}, nil)
   430  	})
   431  }
   432  
   433  func race(f func()) {
   434  	var wg sync.WaitGroup
   435  	for i := 0; i < 2; i++ {
   436  		wg.Add(1)
   437  		go func() {
   438  			f()
   439  			wg.Done()
   440  		}()
   441  	}
   442  	wg.Wait()
   443  }
   444  

View as plain text