cuelang.org/go@v0.13.0/internal/mod/modpkgload/pkgload_test.go (about)

     1  package modpkgload
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io/fs"
     8  	"path"
     9  	"path/filepath"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/go-quicktest/qt"
    14  	"github.com/google/go-cmp/cmp"
    15  	"golang.org/x/tools/txtar"
    16  
    17  	"cuelang.org/go/cue/ast"
    18  	"cuelang.org/go/internal/mod/modimports"
    19  	"cuelang.org/go/internal/mod/modrequirements"
    20  	"cuelang.org/go/mod/modfile"
    21  	"cuelang.org/go/mod/module"
    22  )
    23  
    24  func TestLoadPackages(t *testing.T) {
    25  	files, err := filepath.Glob("testdata/*.txtar")
    26  	qt.Assert(t, qt.IsNil(err))
    27  	for _, f := range files {
    28  		ar, err := txtar.ParseFile(f)
    29  		qt.Assert(t, qt.IsNil(err))
    30  		tfs, err := txtar.FS(ar)
    31  		qt.Assert(t, qt.IsNil(err))
    32  		reg := testRegistry{tfs}
    33  		testDirs, _ := fs.Glob(tfs, "test[0-9]*")
    34  		for _, testDir := range testDirs {
    35  			testName := strings.TrimSuffix(filepath.Base(f), ".txtar") + "/" + testDir
    36  			t.Run(testName, func(t *testing.T) {
    37  				t.Logf("test file: %v", f)
    38  				readTestFile := func(name string) string {
    39  					data, err := fs.ReadFile(tfs, path.Join(testDir, name))
    40  					qt.Assert(t, qt.IsNil(err))
    41  					return string(data)
    42  				}
    43  
    44  				initialRequirementsStr := strings.Fields(readTestFile("initial-requirements"))
    45  				mainModulePath, moduleVersions := initialRequirementsStr[0], mapSlice(initialRequirementsStr[1:], module.MustParseVersion)
    46  				defaultMajorVersions := make(map[string]string)
    47  				for _, f := range strings.Fields(readTestFile("default-major-versions")) {
    48  					p, v, ok := strings.Cut(f, "@")
    49  					qt.Assert(t, qt.IsTrue(ok))
    50  					defaultMajorVersions[p] = v
    51  				}
    52  				initialRequirements := modrequirements.NewRequirements(mainModulePath, reg, moduleVersions, defaultMajorVersions)
    53  
    54  				rootPackages := strings.Fields(readTestFile("root-packages"))
    55  				want := readTestFile("want")
    56  
    57  				var out strings.Builder
    58  				printf := func(f string, a ...any) {
    59  					fmt.Fprintf(&out, f, a...)
    60  				}
    61  				pkgs := LoadPackages(
    62  					context.Background(),
    63  					mainModulePath,
    64  					module.SourceLoc{FS: tfs, Dir: "."},
    65  					initialRequirements,
    66  					reg,
    67  					rootPackages,
    68  					func(pkgPath string, mod module.Version, fsys fs.FS, mf modimports.ModuleFile) bool {
    69  						return true
    70  					},
    71  				)
    72  				for _, pkg := range pkgs.All() {
    73  					printf("%s\n", pkg.ImportPath())
    74  					printf("\tflags: %v\n", pkg.Flags())
    75  					if pkg.Error() != nil {
    76  						printf("\terror: %v\n", pkg.Error())
    77  						printf("\tmissing: %v\n", errors.As(pkg.Error(), new(*ImportMissingError)))
    78  					} else {
    79  						printf("\tmod: %v\n", pkg.Mod())
    80  						for _, loc := range pkg.Locations() {
    81  							printf("\tlocation: %v\n", loc.Dir)
    82  						}
    83  						if imps := pkg.Imports(); len(imps) > 0 {
    84  							printf("\timports:\n")
    85  							for _, imp := range imps {
    86  								printf("\t\t%v\n", imp.ImportPath())
    87  							}
    88  						}
    89  					}
    90  				}
    91  				if diff := cmp.Diff(want, out.String()); diff != "" {
    92  					t.Logf("actual result:\n%s", out.String())
    93  					t.Fatalf("unexpected results (-want +got):\n%s", diff)
    94  				}
    95  			})
    96  		}
    97  	}
    98  }
    99  
   100  func TestFindPackageLocations(t *testing.T) {
   101  	versionForModule := func(ctx context.Context, prefixPath string) (module.Version, error) {
   102  		t.Logf("versionForModule %q", prefixPath)
   103  		switch prefixPath {
   104  		case "foo.bar":
   105  			return module.Version{}, nil
   106  		case "foo.bar/a":
   107  			return module.MustNewVersion("foo.bar/a@v1", "v1.2.3"), nil
   108  		case "foo.bar/a/b":
   109  			return module.MustNewVersion("foo.bar/a/b@v0", "v0.2.4"), nil
   110  		case "foo.bar/a/b/c":
   111  			return module.MustNewVersion("foo.bar/a/b/c@v0", "v0.3.6"), nil
   112  		case "foo.bar/a/b/c/d":
   113  			return module.MustNewVersion("foo.bar/a/b/c/d@v5", "v5.10.20"), nil
   114  		default:
   115  			t.Errorf("unexpected call to versionForModule with prefix %q", prefixPath)
   116  			return module.Version{}, fmt.Errorf("no version")
   117  		}
   118  	}
   119  	tfs, err := txtar.FS(txtar.Parse([]byte(`
   120  -- foo.bar_a/b/c/cue.mod/module.cue --
   121  // This should cause foo.bar/a to be excluded from the list
   122  // of possible candidates because c is a nested module.
   123  module: "something"
   124  -- foo.bar_a/b/c/d/x.cue --
   125  package d
   126  -- foo.bar_a_b/c/d/x.cue --
   127  package C
   128  -- foo.bar_a_b_c/d/x.cue --
   129  package C
   130  -- foo.bar_a_b_c_d/x.cue --
   131  package C
   132  `)))
   133  	qt.Assert(t, qt.IsNil(err))
   134  	fetch := func(ctx context.Context, m module.Version) (loc module.SourceLoc, isLocal bool, err error) {
   135  		t.Logf("fetch %v", m)
   136  		switch m.String() {
   137  		case "foo.bar/a@v1.2.3":
   138  			// Note: return true for isLocal to trigger the nested module
   139  			// checking logic.
   140  			return module.SourceLoc{
   141  				FS:  tfs,
   142  				Dir: "foo.bar_a",
   143  			}, true, nil
   144  		case "foo.bar/a/b@v0.2.4":
   145  			return module.SourceLoc{
   146  				FS:  tfs,
   147  				Dir: "foo.bar_a_b",
   148  			}, false, nil
   149  		case "foo.bar/a/b/c@v0.3.6":
   150  			return module.SourceLoc{
   151  				FS:  tfs,
   152  				Dir: "foo.bar_a_b_c",
   153  			}, false, nil
   154  		case "foo.bar/a/b/c/d@v5.10.20":
   155  			return module.SourceLoc{
   156  				FS:  tfs,
   157  				Dir: "foo.bar_a_b_c_d",
   158  			}, false, nil
   159  		default:
   160  			t.Errorf("unexpected call to versionForModule with module %q", m)
   161  			return module.SourceLoc{}, false, fmt.Errorf("no module")
   162  		}
   163  	}
   164  	locs, err := FindPackageLocations(context.Background(), "foo.bar/a/b/c/d", versionForModule, fetch)
   165  	qt.Assert(t, qt.IsNil(err))
   166  	var dirs []string
   167  	for _, loc := range locs {
   168  		dirs = append(dirs, loc.Locs[0].Dir)
   169  	}
   170  	qt.Assert(t, qt.DeepEquals(dirs, []string{
   171  		"foo.bar_a_b_c_d",
   172  		"foo.bar_a_b_c/d",
   173  		"foo.bar_a_b/c/d",
   174  	}))
   175  }
   176  
   177  type testRegistry struct {
   178  	fs fs.FS
   179  }
   180  
   181  func (r testRegistry) Fetch(ctx context.Context, m module.Version) (module.SourceLoc, error) {
   182  	mpath := r.modpath(m)
   183  	info, err := fs.Stat(r.fs, mpath)
   184  	if err != nil || !info.IsDir() {
   185  		return module.SourceLoc{}, fmt.Errorf("module %v not found at %v", m, mpath)
   186  	}
   187  	return module.SourceLoc{
   188  		FS:  r.fs,
   189  		Dir: mpath,
   190  	}, nil
   191  }
   192  
   193  func (r testRegistry) Requirements(ctx context.Context, m module.Version) ([]module.Version, error) {
   194  	mpath := path.Join(r.modpath(m), "cue.mod/module.cue")
   195  	data, err := fs.ReadFile(r.fs, mpath)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	mf, err := modfile.Parse(data, mpath)
   200  	if err != nil {
   201  		return nil, fmt.Errorf("cannot parse module file from %v: %v", m, err)
   202  	}
   203  	return mf.DepVersions(), nil
   204  }
   205  
   206  func (r testRegistry) modpath(m module.Version) string {
   207  	mpath, _, _ := ast.SplitPackageVersion(m.Path())
   208  	return path.Join("_registry", strings.ReplaceAll(mpath, "/", "_")+"_"+m.Version())
   209  }
   210  
   211  func mapSlice[From, To any](ss []From, f func(From) To) []To {
   212  	ts := make([]To, len(ss))
   213  	for i := range ss {
   214  		ts[i] = f(ss[i])
   215  	}
   216  	return ts
   217  }