cuelang.org/go@v0.13.0/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/mod/modregistry"
    19  	"cuelang.org/go/mod/modregistrytest"
    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 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  
    94  func 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 --
   102  module: "example.com/foo@v0"
   103  language: version: "v0.8.0"
   104  deps: {
   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 --
   109  package example
   110  -- example.com_foo_v0.0.1/x/x.cue --
   111  package 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  
   173  func 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.
   184  func 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  
   204  func 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  }