this repo has no description
at master 213 lines 5.8 kB view raw
1package modcache 2 3import ( 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/mod/modregistry" 19 "cuelang.org/go/mod/modregistrytest" 20 "cuelang.org/go/mod/module" 21) 22 23func 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 -- 28module: "example.com/foo@v0" 29language: version: "v0.8.0" 30deps: { 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 80func TestFetchFromCacheNotFound(t *testing.T) { 81 dir := t.TempDir() 82 t.Cleanup(func() { 83 RemoveAll(dir) 84 }) 85 // The cache should never be hit, so just use a nil registry value. 86 // We'll get a panic if it gets used. 87 cr, err := New(nil, dir) 88 qt.Assert(t, qt.IsNil(err)) 89 _, err = cr.FetchFromCache(module.MustNewVersion("example.com/foo", "v0.0.1")) 90 qt.Assert(t, qt.Not(qt.IsNil(err))) 91 qt.Assert(t, qt.ErrorIs(err, modregistry.ErrNotFound)) 92} 93 94func TestFetch(t *testing.T) { 95 dir := t.TempDir() 96 t.Cleanup(func() { 97 RemoveAll(dir) 98 }) 99 ctx := context.Background() 100 registryFS, err := txtar.FS(txtar.Parse([]byte(` 101-- example.com_foo_v0.0.1/cue.mod/module.cue -- 102module: "example.com/foo@v0" 103language: version: "v0.8.0" 104deps: { 105 "foo.com/bar/hello@v0": v: "v0.2.3" 106 "bar.com@v0": v: "v0.5.0" 107} 108-- example.com_foo_v0.0.1/example.cue -- 109package example 110-- example.com_foo_v0.0.1/x/x.cue -- 111package x 112`))) 113 qt.Assert(t, qt.IsNil(err)) 114 r := newRegistry(t, registryFS) 115 wantContents, err := txtarContents(fsSub(registryFS, "example.com_foo_v0.0.1")) 116 qt.Assert(t, qt.IsNil(err)) 117 checkContents := func(t *testing.T, loc module.SourceLoc) bool { 118 gotContents, err := txtarContents(fsSub(loc.FS, loc.Dir)) 119 if !qt.Check(t, qt.IsNil(err)) { 120 return false 121 } 122 if !qt.Check(t, qt.Equals(string(gotContents), string(wantContents))) { 123 return false 124 } 125 // Check that the location can be used to retrieve the OS file path. 126 osrFS, ok := loc.FS.(module.OSRootFS) 127 if !qt.Check(t, qt.IsTrue(ok)) { 128 return false 129 } 130 root := osrFS.OSRoot() 131 if !qt.Check(t, qt.Not(qt.Equals(root, ""))) { 132 return false 133 } 134 // Check that we can access a module file directly. 135 srcPath := filepath.Join(root, loc.Dir, "example.cue") 136 data, err := os.ReadFile(srcPath) 137 qt.Assert(t, qt.IsNil(err)) 138 qt.Assert(t, qt.Equals(string(data), "package example\n")) 139 // Check that the actual paths are as expected. 140 qt.Check(t, qt.Equals(srcPath, filepath.Join(dir, "mod", "extract", "example.com", "foo@v0.0.1", "example.cue"))) 141 return true 142 } 143 var wg sync.WaitGroup 144 fetch := func(r ociregistry.Interface) { 145 defer wg.Done() 146 cr, err := New(modregistry.NewClient(r), dir) 147 if !qt.Check(t, qt.IsNil(err)) { 148 return 149 } 150 loc, err := cr.Fetch(ctx, module.MustNewVersion("example.com/foo", "v0.0.1")) 151 if !qt.Check(t, qt.IsNil(err)) { 152 return 153 } 154 checkContents(t, loc) 155 156 // After Fetch has succeeded, FetchFromCache should also succeed 157 // and return the same thing. 158 loc, err = cr.FetchFromCache(module.MustNewVersion("example.com/foo", "v0.0.1")) 159 if !qt.Check(t, qt.IsNil(err)) { 160 return 161 } 162 checkContents(t, loc) 163 } 164 wg.Add(2) 165 go fetch(r) 166 go fetch(r) 167 wg.Wait() 168 // Check that it still functions without a functional registry. 169 wg.Add(1) 170 fetch(nil) 171} 172 173func fsSub(fsys fs.FS, sub string) fs.FS { 174 fsys, err := fs.Sub(fsys, sub) 175 if err != nil { 176 panic(err) 177 } 178 return fsys 179} 180 181// txtarContents returns the contents of fsys in txtar format. 182// It assumes that all files end in a newline and do not contain 183// a txtar separator. 184func txtarContents(fsys fs.FS) ([]byte, error) { 185 var buf bytes.Buffer 186 err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { 187 if err != nil { 188 return err 189 } 190 if d.IsDir() { 191 return nil 192 } 193 data, err := fs.ReadFile(fsys, path) 194 if err != nil { 195 return err 196 } 197 fmt.Fprintf(&buf, "-- %s --\n", path) 198 buf.Write(data) 199 return nil 200 }) 201 return buf.Bytes(), err 202} 203 204func newRegistry(t *testing.T, fsys fs.FS) ociregistry.Interface { 205 regSrv, err := modregistrytest.New(fsys, "") 206 qt.Assert(t, qt.IsNil(err)) 207 t.Cleanup(regSrv.Close) 208 regOCI, err := ociclient.New(regSrv.Host(), &ociclient.Options{ 209 Insecure: true, 210 }) 211 qt.Assert(t, qt.IsNil(err)) 212 return regOCI 213}