github.com/joomcode/cue@v0.4.4-0.20221111115225-539fe3512047/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  	"os"
    20  	"path/filepath"
    21  	"strconv"
    22  	"strings"
    23  	"testing"
    24  	"text/template"
    25  	"unicode"
    26  
    27  	"github.com/kylelemons/godebug/diff"
    28  
    29  	"github.com/joomcode/cue/cue"
    30  	"github.com/joomcode/cue/cue/format"
    31  	"github.com/joomcode/cue/internal/str"
    32  )
    33  
    34  // TestLoad is an end-to-end test.
    35  func TestLoad(t *testing.T) {
    36  	cwd, err := os.Getwd()
    37  	if err != nil {
    38  		t.Fatal(err)
    39  	}
    40  	testdataDir := filepath.Join(cwd, testdata)
    41  	dirCfg := &Config{
    42  		Dir:   testdataDir,
    43  		Tools: true,
    44  	}
    45  
    46  	args := str.StringList
    47  	testCases := []struct {
    48  		cfg  *Config
    49  		args []string
    50  		want string
    51  	}{{
    52  		// Even though the directory is called testdata, the last path in
    53  		// the module is test. So "package test" is correctly the default
    54  		// package of this directory.
    55  		cfg:  dirCfg,
    56  		args: nil,
    57  		want: `
    58  path:   example.org/test
    59  module: example.org/test
    60  root:   $CWD/testdata
    61  dir:    $CWD/testdata
    62  display:.
    63  files:
    64      $CWD/testdata/test.cue
    65  imports:
    66      example.org/test/sub: $CWD/testdata/sub/sub.cue`,
    67  	}, {
    68  		// Even though the directory is called testdata, the last path in
    69  		// the module is test. So "package test" is correctly the default
    70  		// package of this directory.
    71  		cfg:  dirCfg,
    72  		args: args("."),
    73  		want: `
    74  path:   example.org/test
    75  module: example.org/test
    76  root:   $CWD/testdata
    77  dir:    $CWD/testdata
    78  display:.
    79  files:
    80      $CWD/testdata/test.cue
    81  imports:
    82      example.org/test/sub: $CWD/testdata/sub/sub.cue`,
    83  	}, {
    84  		// TODO:
    85  		// - path incorrect, should be example.org/test/other:main.
    86  		cfg:  dirCfg,
    87  		args: args("./other/..."),
    88  		want: `
    89  err:    import failed: relative import paths not allowed ("./file")
    90  path:   ""
    91  module: example.org/test
    92  root:   $CWD/testdata
    93  dir:    $CWD/testdata/cue.mod/gen
    94  display:`,
    95  	}, {
    96  		cfg:  dirCfg,
    97  		args: args("./anon"),
    98  		want: `
    99  err:    build constraints exclude all CUE files in ./anon:
   100  	anon/anon.cue: no package name
   101  path:   example.org/test/anon
   102  module: example.org/test
   103  root:   $CWD/testdata
   104  dir:    $CWD/testdata/anon
   105  display:./anon`,
   106  	}, {
   107  		// TODO:
   108  		// - paths are incorrect, should be example.org/test/other:main.
   109  		cfg:  dirCfg,
   110  		args: args("./other"),
   111  		want: `
   112  err:    import failed: relative import paths not allowed ("./file")
   113  path:   example.org/test/other:main
   114  module: example.org/test
   115  root:   $CWD/testdata
   116  dir:    $CWD/testdata/other
   117  display:./other
   118  files:
   119  	$CWD/testdata/other/main.cue`,
   120  	}, {
   121  		// TODO:
   122  		// - incorrect path, should be example.org/test/hello:test
   123  		cfg:  dirCfg,
   124  		args: args("./hello"),
   125  		want: `
   126  path:   example.org/test/hello:test
   127  module: example.org/test
   128  root:   $CWD/testdata
   129  dir:    $CWD/testdata/hello
   130  display:./hello
   131  files:
   132  	$CWD/testdata/test.cue
   133  	$CWD/testdata/hello/test.cue
   134  imports:
   135  	example.org/test/sub: $CWD/testdata/sub/sub.cue`,
   136  	}, {
   137  		// TODO:
   138  		// - incorrect path, should be example.org/test/hello:test
   139  		cfg:  dirCfg,
   140  		args: args("example.org/test/hello:test"),
   141  		want: `
   142  path:   example.org/test/hello:test
   143  module: example.org/test
   144  root:   $CWD/testdata
   145  dir:    $CWD/testdata/hello
   146  display:example.org/test/hello:test
   147  files:
   148  	$CWD/testdata/test.cue
   149  	$CWD/testdata/hello/test.cue
   150  imports:
   151  	example.org/test/sub: $CWD/testdata/sub/sub.cue`,
   152  	}, {
   153  		// TODO:
   154  		// - incorrect path, should be example.org/test/hello:test
   155  		cfg:  dirCfg,
   156  		args: args("example.org/test/hello:nonexist"),
   157  		want: `
   158  err:    build constraints exclude all CUE files in example.org/test/hello:nonexist:
   159      anon.cue: no package name
   160      test.cue: package is test, want nonexist
   161      hello/test.cue: package is test, want nonexist
   162  path:   example.org/test/hello:nonexist
   163  module: example.org/test
   164  root:   $CWD/testdata
   165  dir:    $CWD/testdata/hello
   166  display:example.org/test/hello:nonexist`,
   167  	}, {
   168  		cfg:  dirCfg,
   169  		args: args("./anon.cue", "./other/anon.cue"),
   170  		want: `
   171  path:   ""
   172  module: ""
   173  root:   $CWD/testdata
   174  dir:    $CWD/testdata
   175  display:command-line-arguments
   176  files:
   177  	$CWD/testdata/anon.cue
   178  	$CWD/testdata/other/anon.cue`,
   179  	}, {
   180  		cfg: dirCfg,
   181  		// Absolute file is normalized.
   182  		args: args(filepath.Join(cwd, "testdata", "anon.cue")),
   183  		want: `
   184  path:   ""
   185  module: ""
   186  root:   $CWD/testdata
   187  dir:    $CWD/testdata
   188  display:command-line-arguments
   189  files:
   190      $CWD/testdata/anon.cue`,
   191  	}, {
   192  		cfg:  dirCfg,
   193  		args: args("-"),
   194  		want: `
   195  path:   ""
   196  module: ""
   197  root:   $CWD/testdata
   198  dir:    $CWD/testdata
   199  display:command-line-arguments
   200  files:
   201      -`,
   202  	}, {
   203  		// NOTE: dir should probably be set to $CWD/testdata, but either way.
   204  		cfg:  dirCfg,
   205  		args: args("non-existing"),
   206  		want: `
   207  err:    cannot find package "non-existing"
   208  path:   non-existing
   209  module: example.org/test
   210  root:   $CWD/testdata
   211  dir:    $CWD/testdata/cue.mod/gen/non-existing
   212  display:non-existing`,
   213  	}, {
   214  		cfg:  dirCfg,
   215  		args: args("./empty"),
   216  		want: `
   217  err:    no CUE files in ./empty
   218  path:   example.org/test/empty
   219  module: example.org/test
   220  root:   $CWD/testdata
   221  dir:    $CWD/testdata/empty
   222  display:./empty`,
   223  	}, {
   224  		cfg:  dirCfg,
   225  		args: args("./imports"),
   226  		want: `
   227  path:   example.org/test/imports
   228  module: example.org/test
   229  root:   $CWD/testdata
   230  dir:    $CWD/testdata/imports
   231  display:./imports
   232  files:
   233  	$CWD/testdata/imports/imports.cue
   234  imports:
   235  	acme.com/catch: $CWD/testdata/cue.mod/pkg/acme.com/catch/catch.cue
   236  	acme.com/helper:helper1: $CWD/testdata/cue.mod/pkg/acme.com/helper/helper1.cue`,
   237  	}, {
   238  		cfg:  dirCfg,
   239  		args: args("./toolonly"),
   240  		want: `
   241  path:   example.org/test/toolonly:foo
   242  module: example.org/test
   243  root:   $CWD/testdata
   244  dir:    $CWD/testdata/toolonly
   245  display:./toolonly
   246  files:
   247  	$CWD/testdata/toolonly/foo_tool.cue`,
   248  	}, {
   249  		cfg: &Config{
   250  			Dir: testdataDir,
   251  		},
   252  		args: args("./toolonly"),
   253  		want: `
   254  err:    build constraints exclude all CUE files in ./toolonly:
   255      anon.cue: no package name
   256      test.cue: package is test, want foo
   257      toolonly/foo_tool.cue: _tool.cue files excluded in non-cmd mode
   258  path:   example.org/test/toolonly:foo
   259  module: example.org/test
   260  root:   $CWD/testdata
   261  dir:    $CWD/testdata/toolonly
   262  display:./toolonly`,
   263  	}, {
   264  		cfg: &Config{
   265  			Dir:  testdataDir,
   266  			Tags: []string{"prod"},
   267  		},
   268  		args: args("./tags"),
   269  		want: `
   270  path:   example.org/test/tags
   271  module: example.org/test
   272  root:   $CWD/testdata
   273  dir:    $CWD/testdata/tags
   274  display:./tags
   275  files:
   276  	$CWD/testdata/tags/prod.cue`,
   277  	}, {
   278  		cfg: &Config{
   279  			Dir:  testdataDir,
   280  			Tags: []string{"prod", "foo=bar"},
   281  		},
   282  		args: args("./tags"),
   283  		want: `
   284  path:   example.org/test/tags
   285  module: example.org/test
   286  root:   $CWD/testdata
   287  dir:    $CWD/testdata/tags
   288  display:./tags
   289  files:
   290  	$CWD/testdata/tags/prod.cue`,
   291  	}, {
   292  		cfg: &Config{
   293  			Dir:  testdataDir,
   294  			Tags: []string{"prod"},
   295  		},
   296  		args: args("./tagsbad"),
   297  		want: `
   298  err:    multiple @if attributes (and 2 more errors)
   299  path:   example.org/test/tagsbad
   300  module: example.org/test
   301  root:   $CWD/testdata
   302  dir:    $CWD/testdata/tagsbad
   303  display:./tagsbad`,
   304  	}}
   305  	for i, tc := range testCases {
   306  		t.Run(strconv.Itoa(i)+"/"+strings.Join(tc.args, ":"), func(t *testing.T) {
   307  			pkgs := Instances(tc.args, tc.cfg)
   308  
   309  			buf := &bytes.Buffer{}
   310  			err := pkgInfo.Execute(buf, pkgs)
   311  			if err != nil {
   312  				t.Fatal(err)
   313  			}
   314  
   315  			got := strings.TrimSpace(buf.String())
   316  			got = strings.Replace(got, cwd, "$CWD", -1)
   317  			// Make test work with Windows.
   318  			got = strings.Replace(got, string(filepath.Separator), "/", -1)
   319  
   320  			want := strings.TrimSpace(tc.want)
   321  			want = strings.Replace(want, "\t", "    ", -1)
   322  			if got != want {
   323  				t.Errorf("\n%s", diff.Diff(want, got))
   324  				t.Logf("\n%s", got)
   325  			}
   326  		})
   327  	}
   328  }
   329  
   330  var pkgInfo = template.Must(template.New("pkg").Parse(`
   331  {{- range . -}}
   332  {{- if .Err}}err:    {{.Err}}{{end}}
   333  path:   {{if .ImportPath}}{{.ImportPath}}{{else}}""{{end}}
   334  module: {{if .Module}}{{.Module}}{{else}}""{{end}}
   335  root:   {{.Root}}
   336  dir:    {{.Dir}}
   337  display:{{.DisplayPath}}
   338  {{if .Files -}}
   339  files:
   340  {{- range .Files}}
   341      {{.Filename}}
   342  {{- end -}}
   343  {{- end}}
   344  {{if .Imports -}}
   345  imports:
   346  {{- range .Dependencies}}
   347      {{.ImportPath}}:{{range .Files}} {{.Filename}}{{end}}
   348  {{- end}}
   349  {{end -}}
   350  {{- end -}}
   351  `))
   352  
   353  func TestOverlays(t *testing.T) {
   354  	cwd, _ := os.Getwd()
   355  	abs := func(path string) string {
   356  		return filepath.Join(cwd, path)
   357  	}
   358  	c := &Config{
   359  		Overlay: map[string]Source{
   360  			// Not necessary, but nice to add.
   361  			abs("cue.mod"): FromString(`module: "acme.com"`),
   362  
   363  			abs("dir/top.cue"): FromBytes([]byte(`
   364  			   package top
   365  			   msg: "Hello"
   366  			`)),
   367  			abs("dir/b/foo.cue"): FromString(`
   368  			   package foo
   369  
   370  			   a: <= 5
   371  			`),
   372  			abs("dir/b/bar.cue"): FromString(`
   373  			   package foo
   374  
   375  			   a: >= 5
   376  			`),
   377  		},
   378  	}
   379  	want := []string{
   380  		`{msg:"Hello"}`,
   381  		`{a:5}`,
   382  	}
   383  	rmSpace := func(r rune) rune {
   384  		if unicode.IsSpace(r) {
   385  			return -1
   386  		}
   387  		return r
   388  	}
   389  	for i, inst := range cue.Build(Instances([]string{"./dir/..."}, c)) {
   390  		if inst.Err != nil {
   391  			t.Error(inst.Err)
   392  			continue
   393  		}
   394  		b, err := format.Node(inst.Value().Syntax(cue.Final()))
   395  		if err != nil {
   396  			t.Error(err)
   397  			continue
   398  		}
   399  		if got := string(bytes.Map(rmSpace, b)); got != want[i] {
   400  			t.Errorf("%s: got %s; want %s", inst.Dir, got, want[i])
   401  		}
   402  	}
   403  }