cuelang.org/go@v0.10.1/mod/modcache/modcache_test.go (about) 1 package modcache 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io/fs" 8 "os" 9 "path/filepath" 10 "sync" 11 "testing" 12 13 "cuelabs.dev/go/oci/ociregistry" 14 "cuelabs.dev/go/oci/ociregistry/ociclient" 15 "github.com/go-quicktest/qt" 16 "golang.org/x/tools/txtar" 17 18 "cuelang.org/go/internal/registrytest" 19 "cuelang.org/go/mod/modregistry" 20 "cuelang.org/go/mod/module" 21 ) 22 23 func TestRequirements(t *testing.T) { 24 dir := t.TempDir() 25 ctx := context.Background() 26 registryFS, err := txtar.FS(txtar.Parse([]byte(` 27 -- example.com_foo_v0.0.1/cue.mod/module.cue -- 28 module: "example.com/foo@v0" 29 language: version: "v0.8.0" 30 deps: { 31 "foo.com/bar/hello@v0": v: "v0.2.3" 32 "bar.com@v0": v: "v0.5.0" 33 } 34 `))) 35 qt.Assert(t, qt.IsNil(err)) 36 r := newRegistry(t, registryFS) 37 wantRequirements := []module.Version{ 38 module.MustNewVersion("bar.com", "v0.5.0"), 39 module.MustNewVersion("foo.com/bar/hello", "v0.2.3"), 40 } 41 // Test two concurrent fetches both using the same directory. 42 var wg sync.WaitGroup 43 fetch := func(r ociregistry.Interface) { 44 defer wg.Done() 45 cr, err := New(modregistry.NewClient(r), dir) 46 if !qt.Check(t, qt.IsNil(err)) { 47 return 48 } 49 summary, err := cr.Requirements(ctx, module.MustNewVersion("example.com/foo", "v0.0.1")) 50 if !qt.Check(t, qt.IsNil(err)) { 51 return 52 } 53 if !qt.Check(t, qt.DeepEquals(summary, wantRequirements)) { 54 return 55 } 56 // Fetch again so that we test the in-memory cache-hit path. 57 summary, err = cr.Requirements(ctx, module.MustNewVersion("example.com/foo", "v0.0.1")) 58 if !qt.Check(t, qt.IsNil(err)) { 59 return 60 } 61 if !qt.Check(t, qt.DeepEquals(summary, wantRequirements)) { 62 return 63 } 64 } 65 wg.Add(2) 66 go fetch(r) 67 go fetch(r) 68 wg.Wait() 69 70 // Check that it still functions without a functional registry. 71 wg.Add(1) 72 fetch(nil) 73 74 // Check that the file is stored in the expected place. 75 data, err := os.ReadFile(filepath.Join(dir, "mod/download/example.com/foo/@v/v0.0.1.mod")) 76 qt.Assert(t, qt.IsNil(err)) 77 qt.Assert(t, qt.Matches(string(data), `(?s).*module: "example.com/foo@v0".*`)) 78 } 79 80 func TestFetch(t *testing.T) { 81 dir := t.TempDir() 82 t.Cleanup(func() { 83 RemoveAll(dir) 84 }) 85 ctx := context.Background() 86 registryFS, err := txtar.FS(txtar.Parse([]byte(` 87 -- example.com_foo_v0.0.1/cue.mod/module.cue -- 88 module: "example.com/foo@v0" 89 language: version: "v0.8.0" 90 deps: { 91 "foo.com/bar/hello@v0": v: "v0.2.3" 92 "bar.com@v0": v: "v0.5.0" 93 } 94 -- example.com_foo_v0.0.1/example.cue -- 95 package example 96 -- example.com_foo_v0.0.1/x/x.cue -- 97 package x 98 `))) 99 qt.Assert(t, qt.IsNil(err)) 100 r := newRegistry(t, registryFS) 101 wantContents, err := txtarContents(fsSub(registryFS, "example.com_foo_v0.0.1")) 102 qt.Assert(t, qt.IsNil(err)) 103 checkContents := func(t *testing.T, loc module.SourceLoc) bool { 104 gotContents, err := txtarContents(fsSub(loc.FS, loc.Dir)) 105 if !qt.Check(t, qt.IsNil(err)) { 106 return false 107 } 108 if !qt.Check(t, qt.Equals(string(gotContents), string(wantContents))) { 109 return false 110 } 111 // Check that the location can be used to retrieve the OS file path. 112 osrFS, ok := loc.FS.(module.OSRootFS) 113 if !qt.Check(t, qt.IsTrue(ok)) { 114 return false 115 } 116 root := osrFS.OSRoot() 117 if !qt.Check(t, qt.Not(qt.Equals(root, ""))) { 118 return false 119 } 120 // Check that we can access a module file directly. 121 srcPath := filepath.Join(root, loc.Dir, "example.cue") 122 data, err := os.ReadFile(srcPath) 123 qt.Assert(t, qt.IsNil(err)) 124 qt.Assert(t, qt.Equals(string(data), "package example\n")) 125 // Check that the actual paths are as expected. 126 qt.Check(t, qt.Equals(srcPath, filepath.Join(dir, "mod", "extract", "example.com", "foo@v0.0.1", "example.cue"))) 127 return true 128 } 129 var wg sync.WaitGroup 130 fetch := func(r ociregistry.Interface) { 131 defer wg.Done() 132 cr, err := New(modregistry.NewClient(r), dir) 133 if !qt.Check(t, qt.IsNil(err)) { 134 return 135 } 136 loc, err := cr.Fetch(ctx, module.MustNewVersion("example.com/foo", "v0.0.1")) 137 if !qt.Check(t, qt.IsNil(err)) { 138 return 139 } 140 checkContents(t, loc) 141 } 142 wg.Add(2) 143 go fetch(r) 144 go fetch(r) 145 wg.Wait() 146 // Check that it still functions without a functional registry. 147 wg.Add(1) 148 fetch(nil) 149 } 150 151 func fsSub(fsys fs.FS, sub string) fs.FS { 152 fsys, err := fs.Sub(fsys, sub) 153 if err != nil { 154 panic(err) 155 } 156 return fsys 157 } 158 159 // txtarContents returns the contents of fsys in txtar format. 160 // It assumes that all files end in a newline and do not contain 161 // a txtar separator. 162 func txtarContents(fsys fs.FS) ([]byte, error) { 163 var buf bytes.Buffer 164 err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { 165 if err != nil { 166 return err 167 } 168 if d.IsDir() { 169 return nil 170 } 171 data, err := fs.ReadFile(fsys, path) 172 if err != nil { 173 return err 174 } 175 fmt.Fprintf(&buf, "-- %s --\n", path) 176 buf.Write(data) 177 return nil 178 }) 179 return buf.Bytes(), err 180 } 181 182 func newRegistry(t *testing.T, fsys fs.FS) ociregistry.Interface { 183 regSrv, err := registrytest.New(fsys, "") 184 qt.Assert(t, qt.IsNil(err)) 185 t.Cleanup(regSrv.Close) 186 regOCI, err := ociclient.New(regSrv.Host(), &ociclient.Options{ 187 Insecure: true, 188 }) 189 qt.Assert(t, qt.IsNil(err)) 190 return regOCI 191 }