this repo has no description
at master 224 lines 6.8 kB view raw
1package modpkgload 2 3import ( 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 24func 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.FieldsSeq(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 printf("\texternal: %v\n", pkg.FromExternalModule()) 81 // Sanity check that the module file is available at pkg.ModRoot. 82 _, err := fs.Stat(pkg.ModRoot().FS, path.Join(pkg.ModRoot().Dir, "cue.mod/module.cue")) 83 qt.Assert(t, qt.IsNil(err), qt.Commentf("pkg %q; mod root: %#v", pkg.ImportPath(), pkg.ModRoot())) 84 for _, loc := range pkg.Locations() { 85 printf("\tlocation: %v\n", loc.Dir) 86 } 87 for _, file := range pkg.Files() { 88 printf("\tfile: %v: %v\n", file.FilePath, file.Syntax.PackageName()) 89 } 90 if imps := pkg.Imports(); len(imps) > 0 { 91 printf("\timports:\n") 92 for _, imp := range imps { 93 printf("\t\t%v\n", imp.ImportPath()) 94 } 95 } 96 } 97 } 98 if diff := cmp.Diff(want, out.String()); diff != "" { 99 t.Logf("actual result:\n%s", out.String()) 100 t.Fatalf("unexpected results (-want +got):\n%s", diff) 101 } 102 }) 103 } 104 } 105} 106 107func TestFindPackageLocations(t *testing.T) { 108 versionForModule := func(ctx context.Context, prefixPath string) (module.Version, error) { 109 t.Logf("versionForModule %q", prefixPath) 110 switch prefixPath { 111 case "foo.bar": 112 return module.Version{}, nil 113 case "foo.bar/a": 114 return module.MustNewVersion("foo.bar/a@v1", "v1.2.3"), nil 115 case "foo.bar/a/b": 116 return module.MustNewVersion("foo.bar/a/b@v0", "v0.2.4"), nil 117 case "foo.bar/a/b/c": 118 return module.MustNewVersion("foo.bar/a/b/c@v0", "v0.3.6"), nil 119 case "foo.bar/a/b/c/d": 120 return module.MustNewVersion("foo.bar/a/b/c/d@v5", "v5.10.20"), nil 121 default: 122 t.Errorf("unexpected call to versionForModule with prefix %q", prefixPath) 123 return module.Version{}, fmt.Errorf("no version") 124 } 125 } 126 tfs, err := txtar.FS(txtar.Parse([]byte(` 127-- foo.bar_a/b/c/cue.mod/module.cue -- 128// This should cause foo.bar/a to be excluded from the list 129// of possible candidates because c is a nested module. 130module: "something" 131-- foo.bar_a/b/c/d/x.cue -- 132package d 133-- foo.bar_a_b/c/d/x.cue -- 134package C 135-- foo.bar_a_b_c/d/x.cue -- 136package C 137-- foo.bar_a_b_c_d/x.cue -- 138package C 139`))) 140 qt.Assert(t, qt.IsNil(err)) 141 fetch := func(ctx context.Context, m module.Version) (loc module.SourceLoc, isLocal bool, err error) { 142 t.Logf("fetch %v", m) 143 switch m.String() { 144 case "foo.bar/a@v1.2.3": 145 // Note: return true for isLocal to trigger the nested module 146 // checking logic. 147 return module.SourceLoc{ 148 FS: tfs, 149 Dir: "foo.bar_a", 150 }, true, nil 151 case "foo.bar/a/b@v0.2.4": 152 return module.SourceLoc{ 153 FS: tfs, 154 Dir: "foo.bar_a_b", 155 }, false, nil 156 case "foo.bar/a/b/c@v0.3.6": 157 return module.SourceLoc{ 158 FS: tfs, 159 Dir: "foo.bar_a_b_c", 160 }, false, nil 161 case "foo.bar/a/b/c/d@v5.10.20": 162 return module.SourceLoc{ 163 FS: tfs, 164 Dir: "foo.bar_a_b_c_d", 165 }, false, nil 166 default: 167 t.Errorf("unexpected call to versionForModule with module %q", m) 168 return module.SourceLoc{}, false, fmt.Errorf("no module") 169 } 170 } 171 locs, err := FindPackageLocations(context.Background(), "foo.bar/a/b/c/d", versionForModule, fetch) 172 qt.Assert(t, qt.IsNil(err)) 173 var dirs []string 174 for _, loc := range locs { 175 dirs = append(dirs, loc.Locs[0].Dir) 176 } 177 qt.Assert(t, qt.DeepEquals(dirs, []string{ 178 "foo.bar_a_b_c_d", 179 "foo.bar_a_b_c/d", 180 "foo.bar_a_b/c/d", 181 })) 182} 183 184type testRegistry struct { 185 fs fs.FS 186} 187 188func (r testRegistry) Fetch(ctx context.Context, m module.Version) (module.SourceLoc, error) { 189 mpath := r.modpath(m) 190 info, err := fs.Stat(r.fs, mpath) 191 if err != nil || !info.IsDir() { 192 return module.SourceLoc{}, fmt.Errorf("module %v not found at %v", m, mpath) 193 } 194 return module.SourceLoc{ 195 FS: r.fs, 196 Dir: mpath, 197 }, nil 198} 199 200func (r testRegistry) Requirements(ctx context.Context, m module.Version) ([]module.Version, error) { 201 mpath := path.Join(r.modpath(m), "cue.mod/module.cue") 202 data, err := fs.ReadFile(r.fs, mpath) 203 if err != nil { 204 return nil, err 205 } 206 mf, err := modfile.Parse(data, mpath) 207 if err != nil { 208 return nil, fmt.Errorf("cannot parse module file from %v: %v", m, err) 209 } 210 return mf.DepVersions(), nil 211} 212 213func (r testRegistry) modpath(m module.Version) string { 214 mpath, _, _ := ast.SplitPackageVersion(m.Path()) 215 return path.Join("_registry", strings.ReplaceAll(mpath, "/", "_")+"_"+m.Version()) 216} 217 218func mapSlice[From, To any](ss []From, f func(From) To) []To { 219 ts := make([]To, len(ss)) 220 for i := range ss { 221 ts[i] = f(ss[i]) 222 } 223 return ts 224}