cuelang.org/go@v0.10.1/cue/load/loader_test.go (about)

     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  	"fmt"
    20  	"os"
    21  	"path/filepath"
    22  	"strings"
    23  	"sync"
    24  	"testing"
    25  	"text/template"
    26  	"unicode"
    27  
    28  	"github.com/go-quicktest/qt"
    29  
    30  	"cuelang.org/go/cue"
    31  	"cuelang.org/go/cue/cuecontext"
    32  	"cuelang.org/go/cue/errors"
    33  	"cuelang.org/go/cue/format"
    34  	"cuelang.org/go/internal/cueexperiment"
    35  	"cuelang.org/go/internal/tdtest"
    36  )
    37  
    38  func init() {
    39  	// Ignore the value of CUE_EXPERIMENT for the purposes
    40  	// of these tests, which we want to test both with the experiment
    41  	// enabled and disabled.
    42  	os.Setenv("CUE_EXPERIMENT", "")
    43  
    44  	// Once we've called cueexperiment.Init, cueexperiment.Vars
    45  	// will not be touched again, so we can set fields in it for the tests.
    46  	cueexperiment.Init()
    47  
    48  	// The user running `go test` might have a broken environment,
    49  	// such as an invalid $CUE_REGISTRY like the one below,
    50  	// or a broken $DOCKER_CONFIG/config.json due to syntax errors.
    51  	// Go tests should be hermetic by explicitly setting load.Config.Env;
    52  	// catch any that do not by leaving a broken $CUE_REGISTRY in os.Environ.
    53  	os.Setenv("CUE_REGISTRY", "inline:{")
    54  }
    55  
    56  // TestLoad is an end-to-end test.
    57  func TestLoad(t *testing.T) {
    58  	cwd, err := os.Getwd()
    59  	if err != nil {
    60  		t.Fatal(err)
    61  	}
    62  	testdataDir := testdata("testmod")
    63  	dirCfg := &Config{
    64  		Dir:   testdataDir,
    65  		Tools: true,
    66  	}
    67  	badModCfg := &Config{
    68  		Dir: testdata("badmod"),
    69  	}
    70  	type loadTest struct {
    71  		name string
    72  		cfg  *Config
    73  		args []string
    74  		want string
    75  	}
    76  
    77  	testCases := []loadTest{{
    78  		name: "BadModuleFile",
    79  		cfg:  badModCfg,
    80  		args: []string{"."},
    81  		want: `err:    module: 2 errors in empty disjunction:
    82  module: conflicting values 123 and "" (mismatched types int and string):
    83      $CWD/testdata/badmod/cue.mod/module.cue:2:9
    84      cuelang.org/go/mod/modfile/schema.cue:56:22
    85  module: conflicting values 123 and string (mismatched types int and string):
    86      $CWD/testdata/badmod/cue.mod/module.cue:2:9
    87      cuelang.org/go/mod/modfile/schema.cue:56:12
    88      cuelang.org/go/mod/modfile/schema.cue:98:12
    89  path:   ""
    90  module: ""
    91  root:   ""
    92  dir:    ""
    93  display:""`,
    94  	}, {
    95  		name: "DefaultPackage",
    96  		// Even though the directory is called testdata, the last path in
    97  		// the module is test. So "package test" is correctly the default
    98  		// package of this directory.
    99  		cfg:  dirCfg,
   100  		args: nil,
   101  		want: `path:   mod.test/test@v0
   102  module: mod.test/test@v0
   103  root:   $CWD/testdata/testmod
   104  dir:    $CWD/testdata/testmod
   105  display:.
   106  files:
   107      $CWD/testdata/testmod/test.cue
   108  imports:
   109      mod.test/test/sub: $CWD/testdata/testmod/sub/sub.cue`}, {
   110  		name: "DefaultPackageWithExplicitDotArgument",
   111  		// Even though the directory is called testdata, the last path in
   112  		// the module is test. So "package test" is correctly the default
   113  		// package of this directory.
   114  		cfg:  dirCfg,
   115  		args: []string{"."},
   116  		want: `path:   mod.test/test@v0
   117  module: mod.test/test@v0
   118  root:   $CWD/testdata/testmod
   119  dir:    $CWD/testdata/testmod
   120  display:.
   121  files:
   122      $CWD/testdata/testmod/test.cue
   123  imports:
   124      mod.test/test/sub: $CWD/testdata/testmod/sub/sub.cue`}, {
   125  		name: "RelativeImportPathWildcard",
   126  		cfg:  dirCfg,
   127  		args: []string{"./other/..."},
   128  		want: `err:    import failed: relative import paths not allowed ("./file"):
   129      $CWD/testdata/testmod/other/main.cue:6:2
   130  path:   ""
   131  module: mod.test/test@v0
   132  root:   $CWD/testdata/testmod
   133  dir:    ""
   134  display:""`}, {
   135  		name: "NoMatchingPackageName",
   136  		cfg:  dirCfg,
   137  		args: []string{"./anon"},
   138  		want: `err:    build constraints exclude all CUE files in ./anon:
   139      anon/anon.cue: no package name
   140  path:   mod.test/test/anon@v0
   141  module: mod.test/test@v0
   142  root:   $CWD/testdata/testmod
   143  dir:    $CWD/testdata/testmod/anon
   144  display:./anon`}, {
   145  		name: "RelativeImportPathSingle",
   146  		cfg:  dirCfg,
   147  		args: []string{"./other"},
   148  		want: `err:    import failed: relative import paths not allowed ("./file"):
   149      $CWD/testdata/testmod/other/main.cue:6:2
   150  path:   mod.test/test/other@v0:main
   151  module: mod.test/test@v0
   152  root:   $CWD/testdata/testmod
   153  dir:    $CWD/testdata/testmod/other
   154  display:./other
   155  files:
   156      $CWD/testdata/testmod/other/main.cue`}, {
   157  		name: "RelativePathSuccess",
   158  		cfg:  dirCfg,
   159  		args: []string{"./hello"},
   160  		want: `path:   mod.test/test/hello@v0:test
   161  module: mod.test/test@v0
   162  root:   $CWD/testdata/testmod
   163  dir:    $CWD/testdata/testmod/hello
   164  display:./hello
   165  files:
   166      $CWD/testdata/testmod/test.cue
   167      $CWD/testdata/testmod/hello/test.cue
   168  imports:
   169      mod.test/test/sub: $CWD/testdata/testmod/sub/sub.cue`}, {
   170  		name: "ExplicitPackageIdentifier",
   171  		cfg:  dirCfg,
   172  		args: []string{"mod.test/test/hello:test"},
   173  		want: `path:   mod.test/test/hello:test
   174  module: mod.test/test@v0
   175  root:   $CWD/testdata/testmod
   176  dir:    $CWD/testdata/testmod/hello
   177  display:mod.test/test/hello:test
   178  files:
   179      $CWD/testdata/testmod/test.cue
   180      $CWD/testdata/testmod/hello/test.cue
   181  imports:
   182      mod.test/test/sub: $CWD/testdata/testmod/sub/sub.cue`}, {
   183  		name: "NoPackageName",
   184  		cfg:  dirCfg,
   185  		args: []string{"mod.test/test/hello:nonexist"},
   186  		want: `err:    cannot find package "mod.test/test/hello": no files in package directory with package name "nonexist"
   187  path:   mod.test/test/hello:nonexist
   188  module: ""
   189  root:   $CWD/testdata/testmod
   190  dir:    ""
   191  display:mod.test/test/hello:nonexist`,
   192  	}, {
   193  		name: "ExplicitNonPackageFiles",
   194  		cfg:  dirCfg,
   195  		args: []string{"./anon.cue", "./other/anon.cue"},
   196  		want: `path:   ""
   197  module: ""
   198  root:   $CWD/testdata/testmod
   199  dir:    $CWD/testdata/testmod
   200  display:command-line-arguments
   201  files:
   202      $CWD/testdata/testmod/anon.cue
   203      $CWD/testdata/testmod/other/anon.cue`,
   204  	}, {
   205  		name: "AbsoluteFileIsNormalized", // TODO(rogpeppe) what is this actually testing?
   206  		cfg:  dirCfg,
   207  		// Absolute file is normalized.
   208  		args: []string{filepath.Join(cwd, testdata("testmod", "anon.cue"))},
   209  		want: `path:   ""
   210  module: ""
   211  root:   $CWD/testdata/testmod
   212  dir:    $CWD/testdata/testmod
   213  display:command-line-arguments
   214  files:
   215      $CWD/testdata/testmod/anon.cue`}, {
   216  		name: "StandardInput",
   217  		cfg:  dirCfg,
   218  		args: []string{"-"},
   219  		want: `path:   ""
   220  module: ""
   221  root:   $CWD/testdata/testmod
   222  dir:    $CWD/testdata/testmod
   223  display:command-line-arguments
   224  files:
   225      -`}, {
   226  		name: "BadIdentifier",
   227  		cfg:  dirCfg,
   228  		args: []string{"foo.com/bad-identifier"},
   229  		want: `err:    cannot determine package name for "foo.com/bad-identifier"; set it explicitly with ':'
   230  cannot find package "foo.com/bad-identifier": cannot find module providing package foo.com/bad-identifier
   231  path:   foo.com/bad-identifier
   232  module: ""
   233  root:   $CWD/testdata/testmod
   234  dir:    ""
   235  display:foo.com/bad-identifier`,
   236  	}, {
   237  		name: "NonexistentStdlibImport",
   238  		cfg:  dirCfg,
   239  		args: []string{"nonexisting"},
   240  		want: `err:    standard library import path "nonexisting" cannot be imported as a CUE package
   241  path:   nonexisting
   242  module: ""
   243  root:   $CWD/testdata/testmod
   244  dir:    ""
   245  display:nonexisting`,
   246  	}, {
   247  		name: "ExistingStdlibImport",
   248  		cfg:  dirCfg,
   249  		args: []string{"strconv"},
   250  		want: `err:    standard library import path "strconv" cannot be imported as a CUE package
   251  path:   strconv
   252  module: ""
   253  root:   $CWD/testdata/testmod
   254  dir:    ""
   255  display:strconv`,
   256  	}, {
   257  		name: "EmptyPackageDirectory",
   258  		cfg:  dirCfg,
   259  		args: []string{"./empty"},
   260  		want: `err:    no CUE files in ./empty
   261  path:   mod.test/test/empty@v0
   262  module: mod.test/test@v0
   263  root:   $CWD/testdata/testmod
   264  dir:    $CWD/testdata/testmod/empty
   265  display:./empty`,
   266  	}, {
   267  		name: "PackageWithImports",
   268  		cfg:  dirCfg,
   269  		args: []string{"./imports"},
   270  		want: `path:   mod.test/test/imports@v0
   271  module: mod.test/test@v0
   272  root:   $CWD/testdata/testmod
   273  dir:    $CWD/testdata/testmod/imports
   274  display:./imports
   275  files:
   276      $CWD/testdata/testmod/imports/imports.cue
   277  imports:
   278      mod.test/catch: $CWD/testdata/testmod/cue.mod/pkg/mod.test/catch/catch.cue
   279      mod.test/helper:helper1: $CWD/testdata/testmod/cue.mod/pkg/mod.test/helper/helper1.cue`}, {
   280  		name: "PackageWithImportsWithSkipImportsConfig",
   281  		cfg: &Config{
   282  			Dir:         testdataDir,
   283  			Tools:       true,
   284  			SkipImports: true,
   285  		},
   286  		args: []string{"./imports"},
   287  		want: `path:   mod.test/test/imports@v0
   288  module: mod.test/test@v0
   289  root:   $CWD/testdata/testmod
   290  dir:    $CWD/testdata/testmod/imports
   291  display:./imports
   292  files:
   293      $CWD/testdata/testmod/imports/imports.cue`}, {
   294  		name: "OnlyToolFiles",
   295  		cfg:  dirCfg,
   296  		args: []string{"./toolonly"},
   297  		want: `path:   mod.test/test/toolonly@v0:foo
   298  module: mod.test/test@v0
   299  root:   $CWD/testdata/testmod
   300  dir:    $CWD/testdata/testmod/toolonly
   301  display:./toolonly
   302  files:
   303      $CWD/testdata/testmod/toolonly/foo_tool.cue`}, {
   304  		name: "OnlyToolFilesWithToolsDisabledInConfig",
   305  		cfg: &Config{
   306  			Dir: testdataDir,
   307  		},
   308  		args: []string{"./toolonly"},
   309  		want: `err:    build constraints exclude all CUE files in ./toolonly:
   310      test.cue: package is test, want foo
   311      toolonly/foo_tool.cue: _tool.cue files excluded in non-cmd mode
   312  path:   mod.test/test/toolonly@v0:foo
   313  module: mod.test/test@v0
   314  root:   $CWD/testdata/testmod
   315  dir:    $CWD/testdata/testmod/toolonly
   316  display:./toolonly`}, {
   317  		name: "WithBoolTag",
   318  		cfg: &Config{
   319  			Dir:  testdataDir,
   320  			Tags: []string{"prod"},
   321  		},
   322  		args: []string{"./tags"},
   323  		want: `path:   mod.test/test/tags@v0
   324  module: mod.test/test@v0
   325  root:   $CWD/testdata/testmod
   326  dir:    $CWD/testdata/testmod/tags
   327  display:./tags
   328  files:
   329      $CWD/testdata/testmod/tags/prod.cue`}, {
   330  		name: "WithAttrValTag",
   331  		cfg: &Config{
   332  			Dir:  testdataDir,
   333  			Tags: []string{"prod", "foo=bar"},
   334  		},
   335  		args: []string{"./tags"},
   336  		want: `path:   mod.test/test/tags@v0
   337  module: mod.test/test@v0
   338  root:   $CWD/testdata/testmod
   339  dir:    $CWD/testdata/testmod/tags
   340  display:./tags
   341  files:
   342      $CWD/testdata/testmod/tags/prod.cue`}, {
   343  		name: "UnusedTag",
   344  		cfg: &Config{
   345  			Dir:  testdataDir,
   346  			Tags: []string{"prod"},
   347  		},
   348  		args: []string{"./tagsbad"},
   349  		want: `err:    tag "prod" not used in any file
   350  previous declaration here:
   351      $CWD/testdata/testmod/tagsbad/prod.cue:1:1
   352  multiple @if attributes:
   353      $CWD/testdata/testmod/tagsbad/prod.cue:2:1
   354  path:   mod.test/test/tagsbad@v0
   355  module: mod.test/test@v0
   356  root:   $CWD/testdata/testmod
   357  dir:    $CWD/testdata/testmod/tagsbad
   358  display:./tagsbad`}, {
   359  		name: "ImportCycle",
   360  		cfg: &Config{
   361  			Dir: testdataDir,
   362  		},
   363  		args: []string{"./cycle"},
   364  		want: `err:    import failed: import failed: import failed: package import cycle not allowed:
   365      $CWD/testdata/testmod/cycle/cycle.cue:3:8
   366      $CWD/testdata/testmod/cue.mod/pkg/mod.test/cycle/bar/bar.cue:3:8
   367      $CWD/testdata/testmod/cue.mod/pkg/mod.test/cycle/foo/foo.cue:3:8
   368  path:   mod.test/test/cycle@v0
   369  module: mod.test/test@v0
   370  root:   $CWD/testdata/testmod
   371  dir:    $CWD/testdata/testmod/cycle
   372  display:./cycle
   373  files:
   374      $CWD/testdata/testmod/cycle/cycle.cue`}, {
   375  		name: "AcceptLegacyModuleWithLegacyModule",
   376  		cfg: &Config{
   377  			Dir:                 testdata("testmod_legacy"),
   378  			AcceptLegacyModules: true,
   379  		},
   380  		want: `path:   test.example/foo@v0
   381  module: test.example/foo@v0
   382  root:   $CWD/testdata/testmod_legacy
   383  dir:    $CWD/testdata/testmod_legacy
   384  display:.
   385  files:
   386      $CWD/testdata/testmod_legacy/foo.cue`}, {
   387  		name: "AcceptLegacyModuleWithNonLegacyModule",
   388  		cfg: &Config{
   389  			Dir:                 testdataDir,
   390  			Tools:               true,
   391  			AcceptLegacyModules: true,
   392  		},
   393  		args: []string{"./imports"},
   394  		want: `path:   mod.test/test/imports@v0
   395  module: mod.test/test@v0
   396  root:   $CWD/testdata/testmod
   397  dir:    $CWD/testdata/testmod/imports
   398  display:./imports
   399  files:
   400      $CWD/testdata/testmod/imports/imports.cue
   401  imports:
   402      mod.test/catch: $CWD/testdata/testmod/cue.mod/pkg/mod.test/catch/catch.cue
   403      mod.test/helper:helper1: $CWD/testdata/testmod/cue.mod/pkg/mod.test/helper/helper1.cue`}, {
   404  		name: "MismatchedModulePathInConfig",
   405  		cfg: &Config{
   406  			Dir:    testdataDir,
   407  			Tools:  true,
   408  			Module: "wrong.test@v0",
   409  		},
   410  		args: []string{"./imports"},
   411  		want: `err:    inconsistent modules: got "mod.test/test@v0", want "wrong.test@v0"
   412  path:   ""
   413  module: wrong.test@v0
   414  root:   ""
   415  dir:    ""
   416  display:""`}, {
   417  		name: "ModulePathInConfigWithoutMajorVersion",
   418  		cfg: &Config{
   419  			Dir:    testdataDir,
   420  			Tools:  true,
   421  			Module: "mod.test/test",
   422  		},
   423  		args: []string{"./imports"},
   424  		want: `err:    inconsistent modules: got "mod.test/test@v0", want "mod.test/test"
   425  path:   ""
   426  module: mod.test/test
   427  root:   ""
   428  dir:    ""
   429  display:""`}, {
   430  		name: "ModulePathInConfigWithoutMajorVersionAndMismatchedPath",
   431  		cfg: &Config{
   432  			Dir:    testdataDir,
   433  			Tools:  true,
   434  			Module: "mod.test/wrong",
   435  		},
   436  		args: []string{"./imports"},
   437  		want: `err:    inconsistent modules: got "mod.test/test@v0", want "mod.test/wrong"
   438  path:   ""
   439  module: mod.test/wrong
   440  root:   ""
   441  dir:    ""
   442  display:""`}, {
   443  		name: "ExplicitPackageWithUnqualifiedImportPath#1",
   444  		cfg: &Config{
   445  			Dir:     filepath.Join(testdataDir, "multi"),
   446  			Package: "main",
   447  		},
   448  		args: []string{"."},
   449  		want: `path:   mod.test/test/multi@v0:main
   450  module: mod.test/test@v0
   451  root:   $CWD/testdata/testmod
   452  dir:    $CWD/testdata/testmod/multi
   453  display:.
   454  files:
   455      $CWD/testdata/testmod/multi/file.cue`}, {
   456  		name: "ExplicitPackageWithUnqualifiedImportPath#2",
   457  		// This test replicates the failure reported in https://cuelang.org/issue/3213
   458  		cfg: &Config{
   459  			Dir:     filepath.Join(testdataDir, "multi2"),
   460  			Package: "other",
   461  		},
   462  		args: []string{"."},
   463  		want: `path:   mod.test/test/multi2@v0:other
   464  module: mod.test/test@v0
   465  root:   $CWD/testdata/testmod
   466  dir:    $CWD/testdata/testmod/multi2
   467  display:.
   468  files:
   469      $CWD/testdata/testmod/multi2/other.cue
   470  imports:
   471      mod.test/test/sub: $CWD/testdata/testmod/sub/sub.cue`}, {
   472  		name: "ExplicitPackageWithUnqualifiedImportPath#3",
   473  		cfg: &Config{
   474  			Dir:     filepath.Join(testdataDir, "multi3"),
   475  			Package: "other",
   476  		},
   477  		args: []string{"."},
   478  		want: `path:   mod.test/test/multi3@v0:other
   479  module: mod.test/test@v0
   480  root:   $CWD/testdata/testmod
   481  dir:    $CWD/testdata/testmod/multi3
   482  display:.
   483  files:
   484      $CWD/testdata/testmod/multi3/other.cue
   485  imports:
   486      mod.test/test/sub: $CWD/testdata/testmod/sub/sub.cue`}, {
   487  		// Test that we can explicitly ask for non-package
   488  		// CUE files by setting Config.Package to "_".
   489  		name: "ExplicitPackageWithUnqualifiedImportPath#4",
   490  		cfg: &Config{
   491  			Dir:     filepath.Join(testdataDir, "multi4"),
   492  			Package: "_",
   493  		},
   494  		args: []string{"."},
   495  		want: `path:   mod.test/test/multi4@v0:_
   496  module: mod.test/test@v0
   497  root:   $CWD/testdata/testmod
   498  dir:    $CWD/testdata/testmod/multi4
   499  display:.
   500  files:
   501      $CWD/testdata/testmod/multi4/nopackage1.cue
   502      $CWD/testdata/testmod/multi4/nopackage2.cue`}, {
   503  		// Test what happens when there's a single CUE file
   504  		// with an explicit `package _` directive.
   505  		name: "ExplicitPackageWithUnqualifiedImportPath#5",
   506  		cfg: &Config{
   507  			Dir:     filepath.Join(testdataDir, "multi5"),
   508  			Package: "_",
   509  		},
   510  		args: []string{"."},
   511  		want: `path:   mod.test/test/multi5@v0:_
   512  module: mod.test/test@v0
   513  root:   $CWD/testdata/testmod
   514  dir:    $CWD/testdata/testmod/multi5
   515  display:.
   516  files:
   517      $CWD/testdata/testmod/multi5/nopackage.cue`}, {
   518  		// Check that imports are only considered from files
   519  		// that match the build paths.
   520  		name: "BuildTagsWithImports#1",
   521  		cfg: &Config{
   522  			Dir:  filepath.Join(testdataDir, "tagswithimports"),
   523  			Tags: []string{"prod"},
   524  		},
   525  		args: []string{"."},
   526  		want: `path:   mod.test/test/tagswithimports@v0
   527  module: mod.test/test@v0
   528  root:   $CWD/testdata/testmod
   529  dir:    $CWD/testdata/testmod/tagswithimports
   530  display:.
   531  files:
   532      $CWD/testdata/testmod/tagswithimports/prod.cue
   533  imports:
   534      mod.test/test/hello:test: $CWD/testdata/testmod/test.cue $CWD/testdata/testmod/hello/test.cue
   535      mod.test/test/sub: $CWD/testdata/testmod/sub/sub.cue`}, {
   536  		// Check that imports are only considered from files
   537  		// that match the build paths. When we don't have the prod
   538  		// tag, the bad import path mentioned in testdata/testmod/tagswithimports/nonprod.cue
   539  		// surfaces in the errors.
   540  		name: "BuildTagsWithImports#2",
   541  		cfg: &Config{
   542  			Dir: filepath.Join(testdataDir, "tagswithimports"),
   543  		},
   544  		args: []string{"."},
   545  		want: `err:    mod.test/test/tagswithimports@v0: import failed: cannot find package "bad-import.example/foo": cannot find module providing package bad-import.example/foo:
   546      $CWD/testdata/testmod/tagswithimports/nonprod.cue:5:8
   547  path:   mod.test/test/tagswithimports@v0
   548  module: mod.test/test@v0
   549  root:   $CWD/testdata/testmod
   550  dir:    $CWD/testdata/testmod/tagswithimports
   551  display:.
   552  files:
   553      $CWD/testdata/testmod/tagswithimports/nonprod.cue`}, {
   554  		name: "ModuleFileNonDirectory",
   555  		cfg: &Config{
   556  			Dir: testdata("testmod_legacymodfile"),
   557  		},
   558  		args: []string{"."},
   559  		want: `err:    cue.mod files are no longer supported; use cue.mod/module.cue
   560  path:   ""
   561  module: ""
   562  root:   ""
   563  dir:    ""
   564  display:""`}, {
   565  		// This test checks that files in parent directories
   566  		// do not result in irrelevant instances appearing
   567  		// in the result of Instances.
   568  		name: "Issue3306",
   569  		cfg: &Config{
   570  			Dir:         testdataDir,
   571  			Package:     "*",
   572  			SkipImports: true,
   573  		},
   574  		args: []string{"./issue3306/..."},
   575  		want: `path:   mod.test/test/issue3306@v0:x
   576  module: mod.test/test@v0
   577  root:   $CWD/testdata/testmod
   578  dir:    $CWD/testdata/testmod/issue3306
   579  display:./issue3306
   580  files:
   581      $CWD/testdata/testmod/issue3306/x.cue
   582  
   583  path:   mod.test/test/issue3306/a@v0:a
   584  module: mod.test/test@v0
   585  root:   $CWD/testdata/testmod
   586  dir:    $CWD/testdata/testmod/issue3306/a
   587  display:./issue3306/a
   588  files:
   589      $CWD/testdata/testmod/issue3306/a/a.cue
   590  
   591  path:   mod.test/test/issue3306/a@v0:b
   592  module: mod.test/test@v0
   593  root:   $CWD/testdata/testmod
   594  dir:    $CWD/testdata/testmod/issue3306/a
   595  display:./issue3306/a
   596  files:
   597      $CWD/testdata/testmod/issue3306/a/b.cue
   598  
   599  path:   mod.test/test/issue3306/a@v0:x
   600  module: mod.test/test@v0
   601  root:   $CWD/testdata/testmod
   602  dir:    $CWD/testdata/testmod/issue3306/a
   603  display:./issue3306/a
   604  files:
   605      $CWD/testdata/testmod/issue3306/x.cue
   606      $CWD/testdata/testmod/issue3306/a/x.cue
   607  
   608  path:   mod.test/test/issue3306/x@v0:x
   609  module: mod.test/test@v0
   610  root:   $CWD/testdata/testmod
   611  dir:    $CWD/testdata/testmod/issue3306/x
   612  display:./issue3306/x
   613  files:
   614      $CWD/testdata/testmod/issue3306/x.cue
   615      $CWD/testdata/testmod/issue3306/x/x.cue`}}
   616  	tdtest.Run(t, testCases, func(t *tdtest.T, tc *loadTest) {
   617  		pkgs := Instances(tc.args, tc.cfg)
   618  
   619  		buf := &bytes.Buffer{}
   620  		err := pkgInfo.Execute(buf, pkgs)
   621  		if err != nil {
   622  			t.Fatal(err)
   623  		}
   624  
   625  		got := strings.TrimSpace(buf.String())
   626  		got = strings.Replace(got, cwd, "$CWD", -1)
   627  		// Errors are printed with slashes, so replace
   628  		// the slash-separated form of CWD too.
   629  		got = strings.Replace(got, filepath.ToSlash(cwd), "$CWD", -1)
   630  		// Make test work with Windows.
   631  		got = strings.Replace(got, string(filepath.Separator), "/", -1)
   632  
   633  		t.Equal(got, tc.want)
   634  	})
   635  }
   636  
   637  var pkgInfo = template.Must(template.New("pkg").Funcs(template.FuncMap{
   638  	"errordetails": func(err error) string {
   639  		s := errors.Details(err, &errors.Config{
   640  			ToSlash: true,
   641  		})
   642  		s = strings.TrimSuffix(s, "\n")
   643  		return s
   644  	}}).Parse(`
   645  {{- range . -}}
   646  {{- if .Err}}err:    {{errordetails .Err}}{{end}}
   647  path:   {{if .ImportPath}}{{.ImportPath}}{{else}}""{{end}}
   648  module: {{with .Module}}{{.}}{{else}}""{{end}}
   649  root:   {{with .Root}}{{.}}{{else}}""{{end}}
   650  dir:    {{with .Dir}}{{.}}{{else}}""{{end}}
   651  display:{{with .DisplayPath}}{{.}}{{else}}""{{end}}
   652  {{if .Files -}}
   653  files:
   654  {{- range .Files}}
   655      {{.Filename}}
   656  {{- end -}}
   657  {{- end}}
   658  {{if .Imports -}}
   659  imports:
   660  {{- range .Dependencies}}
   661      {{.ImportPath}}:{{range .Files}} {{.Filename}}{{end}}
   662  {{- end}}
   663  {{end -}}
   664  {{- end -}}
   665  `))
   666  
   667  func TestOverlays(t *testing.T) {
   668  	cwd, _ := os.Getwd()
   669  	abs := func(path string) string {
   670  		return filepath.Join(cwd, path)
   671  	}
   672  	c := &Config{
   673  		Overlay: map[string]Source{
   674  			// Not necessary, but nice to add.
   675  			abs("cue.mod/module.cue"): FromString(`module: "mod.test", language: version: "v0.9.0"`),
   676  
   677  			abs("dir/top.cue"): FromBytes([]byte(`
   678  			   package top
   679  			   msg: "Hello"
   680  			`)),
   681  			abs("dir/b/foo.cue"): FromString(`
   682  			   package foo
   683  
   684  			   a: <= 5
   685  			`),
   686  			abs("dir/b/bar.cue"): FromString(`
   687  			   package foo
   688  
   689  			   a: >= 5
   690  			`),
   691  		},
   692  	}
   693  	want := []string{
   694  		`{msg:"Hello"}`,
   695  		`{a:5}`,
   696  	}
   697  	rmSpace := func(r rune) rune {
   698  		if unicode.IsSpace(r) {
   699  			return -1
   700  		}
   701  		return r
   702  	}
   703  	ctx := cuecontext.New()
   704  	insts, err := ctx.BuildInstances(Instances([]string{"./dir/..."}, c))
   705  	if err != nil {
   706  		t.Fatal(err)
   707  	}
   708  	for i, inst := range insts {
   709  		if err := inst.Err(); err != nil {
   710  			t.Error(err)
   711  			continue
   712  		}
   713  		b, err := format.Node(inst.Value().Syntax(cue.Final()))
   714  		if err != nil {
   715  			t.Error(err)
   716  			continue
   717  		}
   718  		if got := string(bytes.Map(rmSpace, b)); got != want[i] {
   719  			t.Errorf("%s: got %s; want %s", inst.BuildInstance().Dir, got, want[i])
   720  		}
   721  	}
   722  }
   723  
   724  func TestLoadOrder(t *testing.T) {
   725  	testDir := t.TempDir()
   726  	letters := "abcdefghij"
   727  
   728  	for _, c := range letters {
   729  		contents := fmt.Sprintf(`
   730  package %s
   731  
   732  x: 1
   733  `, string(c))
   734  		err := os.WriteFile(filepath.Join(testDir, string(c)+".cue"), []byte(contents), 0o644)
   735  		qt.Assert(t, qt.IsNil(err))
   736  	}
   737  
   738  	insts := Instances([]string{"."}, &Config{
   739  		Package: "*",
   740  		Dir:     testDir,
   741  	})
   742  
   743  	var actualFiles = []string{}
   744  	for _, inst := range insts {
   745  		for _, f := range inst.BuildFiles {
   746  			if strings.Contains(f.Filename, testDir) {
   747  				actualFiles = append(actualFiles, filepath.Base(f.Filename))
   748  			}
   749  		}
   750  	}
   751  	var expectedFiles []string
   752  	for _, c := range letters {
   753  		expectedFiles = append(expectedFiles, string(c)+".cue")
   754  	}
   755  	qt.Assert(t, qt.DeepEquals(actualFiles, expectedFiles))
   756  }
   757  
   758  func TestLoadInstancesConcurrent(t *testing.T) {
   759  	// This test is designed to fail when run with the race detector
   760  	// if there's an underlying race condition.
   761  	// See https://cuelang.org/issue/1746
   762  	race(t, func() error {
   763  		_, err := getInst(".", testdata("testmod", "hello"))
   764  		return err
   765  	})
   766  }
   767  
   768  func race(t *testing.T, f func() error) {
   769  	var wg sync.WaitGroup
   770  	for i := 0; i < 2; i++ {
   771  		wg.Add(1)
   772  		go func() {
   773  			if err := f(); err != nil {
   774  				t.Error(err)
   775  			}
   776  			wg.Done()
   777  		}()
   778  	}
   779  	wg.Wait()
   780  }