github.com/golang/dep@v0.5.4/gps/source_cache_test.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package gps
     6  
     7  import (
     8  	"io/ioutil"
     9  	"log"
    10  	"path"
    11  	"reflect"
    12  	"sort"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/golang/dep/gps/pkgtree"
    17  	"github.com/golang/dep/internal/test"
    18  	"github.com/pkg/errors"
    19  )
    20  
    21  func Test_singleSourceCache(t *testing.T) {
    22  	newMem := func(*testing.T, string) sourceCache {
    23  		return memoryCache{}
    24  	}
    25  	t.Run("mem", singleSourceCacheTest{newCache: newMem}.run)
    26  
    27  	epoch := time.Now().Unix()
    28  	newBolt := func(t *testing.T, cachedir string) sourceCache {
    29  		bc, err := newBoltCache(cachedir, epoch, log.New(test.Writer{TB: t}, "", 0))
    30  		if err != nil {
    31  			t.Fatal(err)
    32  		}
    33  		return bc
    34  	}
    35  	t.Run("bolt/keepOpen", singleSourceCacheTest{newCache: newBolt}.run)
    36  	t.Run("bolt/reOpen", singleSourceCacheTest{newCache: newBolt, persistent: true}.run)
    37  
    38  	newMulti := func(t *testing.T, cachedir string) sourceCache {
    39  		bc, err := newBoltCache(cachedir, epoch, log.New(test.Writer{TB: t}, "", 0))
    40  		if err != nil {
    41  			t.Fatal(err)
    42  		}
    43  		return newMultiCache(memoryCache{}, bc)
    44  	}
    45  	t.Run("multi/keepOpen", singleSourceCacheTest{newCache: newMulti}.run)
    46  	t.Run("multi/reOpen", singleSourceCacheTest{persistent: true, newCache: newMulti}.run)
    47  
    48  	t.Run("multi/keepOpen/noDisk", singleSourceCacheTest{
    49  		newCache: func(*testing.T, string) sourceCache {
    50  			return newMultiCache(memoryCache{}, discardCache{})
    51  		},
    52  	}.run)
    53  
    54  	t.Run("multi/reOpen/noMem", singleSourceCacheTest{
    55  		persistent: true,
    56  		newCache: func(t *testing.T, cachedir string) sourceCache {
    57  			bc, err := newBoltCache(cachedir, epoch, log.New(test.Writer{TB: t}, "", 0))
    58  			if err != nil {
    59  				t.Fatal(err)
    60  			}
    61  			return newMultiCache(discardCache{}, bc)
    62  		},
    63  	}.run)
    64  }
    65  
    66  var testAnalyzerInfo = ProjectAnalyzerInfo{
    67  	Name:    "test-analyzer",
    68  	Version: 1,
    69  }
    70  
    71  type singleSourceCacheTest struct {
    72  	newCache   func(*testing.T, string) sourceCache
    73  	persistent bool
    74  }
    75  
    76  // run tests singleSourceCache methods of caches returned by test.newCache.
    77  // For test.persistent caches, test.newCache is periodically called mid-test to ensure persistence.
    78  func (test singleSourceCacheTest) run(t *testing.T) {
    79  	const root = "example.com/test"
    80  	pi := mkPI(root).normalize()
    81  	cpath, err := ioutil.TempDir("", "singlesourcecache")
    82  	if err != nil {
    83  		t.Fatalf("Failed to create temp cache dir: %s", err)
    84  	}
    85  
    86  	t.Run("info", func(t *testing.T) {
    87  		const rev Revision = "revision"
    88  
    89  		sc := test.newCache(t, cpath)
    90  		c := sc.newSingleSourceCache(pi)
    91  		defer func() {
    92  			if err := sc.close(); err != nil {
    93  				t.Fatal("failed to close cache:", err)
    94  			}
    95  		}()
    96  
    97  		var m Manifest = &simpleRootManifest{
    98  			c: ProjectConstraints{
    99  				ProjectRoot("foo"): ProjectProperties{
   100  					Constraint: Any(),
   101  				},
   102  				ProjectRoot("bar"): ProjectProperties{
   103  					Source:     "whatever",
   104  					Constraint: testSemverConstraint(t, "> 1.3"),
   105  				},
   106  			},
   107  			ovr: ProjectConstraints{
   108  				ProjectRoot("b"): ProjectProperties{
   109  					Constraint: testSemverConstraint(t, "2.0.0"),
   110  				},
   111  			},
   112  			req: map[string]bool{
   113  				"c": true,
   114  				"d": true,
   115  			},
   116  			ig: pkgtree.NewIgnoredRuleset([]string{"a", "b"}),
   117  		}
   118  		var l Lock = &safeLock{
   119  			p: []LockedProject{
   120  				NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0").Pair("anything"), []string{"gps"}),
   121  				NewLockedProject(mkPI("github.com/sdboyer/gps2"), NewVersion("v0.10.0").Pair("whatever"), nil),
   122  				NewLockedProject(mkPI("github.com/sdboyer/gps3"), NewVersion("v0.10.0").Pair("again"), []string{"gps", "flugle"}),
   123  				NewLockedProject(mkPI("foo"), NewVersion("nada").Pair("itsaliving"), []string{"foo"}),
   124  				NewLockedProject(mkPI("github.com/sdboyer/gps4"), NewVersion("v0.10.0").Pair("meow"), []string{"flugle", "gps"}),
   125  			},
   126  		}
   127  		c.setManifestAndLock(rev, testAnalyzerInfo, m, l)
   128  
   129  		if test.persistent {
   130  			if err := sc.close(); err != nil {
   131  				t.Fatal("failed to close cache:", err)
   132  			}
   133  			sc = test.newCache(t, cpath)
   134  			c = sc.newSingleSourceCache(pi)
   135  		}
   136  
   137  		gotM, gotL, ok := c.getManifestAndLock(rev, testAnalyzerInfo)
   138  		if !ok {
   139  			t.Error("no manifest and lock found for revision")
   140  		}
   141  		compareManifests(t, m, gotM)
   142  		// TODO(sdboyer) use DiffLocks after refactoring to avoid import cycles
   143  		if !locksAreEq(l, gotL) {
   144  			t.Errorf("locks are different:\n\t(GOT): %s\n\t(WNT): %s", l, gotL)
   145  		}
   146  
   147  		m = &simpleRootManifest{
   148  			c: ProjectConstraints{
   149  				ProjectRoot("foo"): ProjectProperties{
   150  					Source:     "whatever",
   151  					Constraint: Any(),
   152  				},
   153  			},
   154  			ovr: ProjectConstraints{
   155  				ProjectRoot("bar"): ProjectProperties{
   156  					Constraint: testSemverConstraint(t, "2.0.0"),
   157  				},
   158  			},
   159  			req: map[string]bool{
   160  				"a": true,
   161  				"b": true,
   162  			},
   163  			ig: pkgtree.NewIgnoredRuleset([]string{"c", "d"}),
   164  		}
   165  		l = &safeLock{
   166  			p: []LockedProject{
   167  				NewLockedProject(mkPI("github.com/sdboyer/gps"), NewVersion("v0.10.0").Pair("278a227dfc3d595a33a77ff3f841fd8ca1bc8cd0"), []string{"gps"}),
   168  				NewLockedProject(mkPI("github.com/sdboyer/gps2"), NewVersion("v0.11.0").Pair("anything"), []string{"gps"}),
   169  				NewLockedProject(mkPI("github.com/sdboyer/gps3"), Revision("278a227dfc3d595a33a77ff3f841fd8ca1bc8cd0"), []string{"gps"}),
   170  			},
   171  		}
   172  		c.setManifestAndLock(rev, testAnalyzerInfo, m, l)
   173  
   174  		if test.persistent {
   175  			if err := sc.close(); err != nil {
   176  				t.Fatal("failed to close cache:", err)
   177  			}
   178  			sc = test.newCache(t, cpath)
   179  			c = sc.newSingleSourceCache(pi)
   180  		}
   181  
   182  		gotM, gotL, ok = c.getManifestAndLock(rev, testAnalyzerInfo)
   183  		if !ok {
   184  			t.Error("no manifest and lock found for revision")
   185  		}
   186  		compareManifests(t, m, gotM)
   187  		// TODO(sdboyer) use DiffLocks after refactoring to avoid import cycles
   188  		if !locksAreEq(l, gotL) {
   189  			t.Errorf("locks are different:\n\t(GOT): %s\n\t(WNT): %s", l, gotL)
   190  		}
   191  	})
   192  
   193  	t.Run("pkgTree", func(t *testing.T) {
   194  		sc := test.newCache(t, cpath)
   195  		c := sc.newSingleSourceCache(pi)
   196  		defer func() {
   197  			if err := sc.close(); err != nil {
   198  				t.Fatal("failed to close cache:", err)
   199  			}
   200  		}()
   201  
   202  		const rev Revision = "rev_adsfjkl"
   203  
   204  		if got, ok := c.getPackageTree(rev, root); ok {
   205  			t.Fatalf("unexpected result before setting package tree: %v", got)
   206  		}
   207  
   208  		if test.persistent {
   209  			if err := sc.close(); err != nil {
   210  				t.Fatal("failed to close cache:", err)
   211  			}
   212  			sc = test.newCache(t, cpath)
   213  			c = sc.newSingleSourceCache(pi)
   214  		}
   215  
   216  		pt := pkgtree.PackageTree{
   217  			ImportRoot: root,
   218  			Packages: map[string]pkgtree.PackageOrErr{
   219  				root: {
   220  					P: pkgtree.Package{
   221  						ImportPath:  root,
   222  						CommentPath: "comment",
   223  						Name:        "test",
   224  						Imports: []string{
   225  							"sort",
   226  						},
   227  					},
   228  				},
   229  				path.Join(root, "simple"): {
   230  					P: pkgtree.Package{
   231  						ImportPath:  path.Join(root, "simple"),
   232  						CommentPath: "comment",
   233  						Name:        "simple",
   234  						Imports: []string{
   235  							"github.com/golang/dep/gps",
   236  							"sort",
   237  						},
   238  					},
   239  				},
   240  				path.Join(root, "m1p"): {
   241  					P: pkgtree.Package{
   242  						ImportPath:  path.Join(root, "m1p"),
   243  						CommentPath: "",
   244  						Name:        "m1p",
   245  						Imports: []string{
   246  							"github.com/golang/dep/gps",
   247  							"os",
   248  							"sort",
   249  						},
   250  					},
   251  				},
   252  			},
   253  		}
   254  		c.setPackageTree(rev, pt)
   255  
   256  		if test.persistent {
   257  			if err := sc.close(); err != nil {
   258  				t.Fatal("failed to close cache:", err)
   259  			}
   260  			sc = test.newCache(t, cpath)
   261  			c = sc.newSingleSourceCache(pi)
   262  		}
   263  
   264  		got, ok := c.getPackageTree(rev, root)
   265  		if !ok {
   266  			t.Errorf("no package tree found:\n\t(WNT): %#v", pt)
   267  		}
   268  		comparePackageTree(t, pt, got)
   269  
   270  		if test.persistent {
   271  			if err := sc.close(); err != nil {
   272  				t.Fatal("failed to close cache:", err)
   273  			}
   274  			sc = test.newCache(t, cpath)
   275  			c = sc.newSingleSourceCache(pi)
   276  		}
   277  
   278  		pt = pkgtree.PackageTree{
   279  			ImportRoot: root,
   280  			Packages: map[string]pkgtree.PackageOrErr{
   281  				path.Join(root, "test"): {
   282  					Err: errors.New("error"),
   283  				},
   284  			},
   285  		}
   286  		c.setPackageTree(rev, pt)
   287  
   288  		if test.persistent {
   289  			if err := sc.close(); err != nil {
   290  				t.Fatal("failed to close cache:", err)
   291  			}
   292  			sc = test.newCache(t, cpath)
   293  			c = sc.newSingleSourceCache(pi)
   294  		}
   295  
   296  		got, ok = c.getPackageTree(rev, root)
   297  		if !ok {
   298  			t.Errorf("no package tree found:\n\t(WNT): %#v", pt)
   299  		}
   300  		comparePackageTree(t, pt, got)
   301  	})
   302  
   303  	t.Run("versions", func(t *testing.T) {
   304  		sc := test.newCache(t, cpath)
   305  		c := sc.newSingleSourceCache(pi)
   306  		defer func() {
   307  			if err := sc.close(); err != nil {
   308  				t.Fatal("failed to close cache:", err)
   309  			}
   310  		}()
   311  
   312  		const rev1, rev2 = "rev1", "rev2"
   313  		const br, ver = "branch_name", "2.10"
   314  		versions := []PairedVersion{
   315  			NewBranch(br).Pair(rev1),
   316  			NewVersion(ver).Pair(rev2),
   317  		}
   318  		SortPairedForDowngrade(versions)
   319  		c.setVersionMap(versions)
   320  
   321  		if test.persistent {
   322  			if err := sc.close(); err != nil {
   323  				t.Fatal("failed to close cache:", err)
   324  			}
   325  			sc = test.newCache(t, cpath)
   326  			c = sc.newSingleSourceCache(pi)
   327  		}
   328  
   329  		t.Run("getAllVersions", func(t *testing.T) {
   330  			got, ok := c.getAllVersions()
   331  			if !ok || len(got) != len(versions) {
   332  				t.Errorf("unexpected versions:\n\t(GOT): %#v\n\t(WNT): %#v", got, versions)
   333  			} else {
   334  				SortPairedForDowngrade(got)
   335  				for i := range versions {
   336  					if !versions[i].identical(got[i]) {
   337  						t.Errorf("unexpected versions:\n\t(GOT): %#v\n\t(WNT): %#v", got, versions)
   338  						break
   339  					}
   340  				}
   341  			}
   342  		})
   343  
   344  		revToUV := map[Revision]UnpairedVersion{
   345  			rev1: NewBranch(br),
   346  			rev2: NewVersion(ver),
   347  		}
   348  
   349  		t.Run("getVersionsFor", func(t *testing.T) {
   350  			for rev, want := range revToUV {
   351  				rev, want := rev, want
   352  				t.Run(string(rev), func(t *testing.T) {
   353  					uvs, ok := c.getVersionsFor(rev)
   354  					if !ok {
   355  						t.Errorf("no version found:\n\t(WNT) %#v", want)
   356  					} else if len(uvs) != 1 {
   357  						t.Errorf("expected one result but got %d", len(uvs))
   358  					} else {
   359  						uv := uvs[0]
   360  						if uv.Type() != want.Type() {
   361  							t.Errorf("expected version type %d but got %d", want.Type(), uv.Type())
   362  						}
   363  						if uv.String() != want.String() {
   364  							t.Errorf("expected version %q but got %q", want.String(), uv.String())
   365  						}
   366  					}
   367  				})
   368  			}
   369  		})
   370  
   371  		t.Run("getRevisionFor", func(t *testing.T) {
   372  			for want, uv := range revToUV {
   373  				want, uv := want, uv
   374  				t.Run(uv.String(), func(t *testing.T) {
   375  					rev, ok := c.getRevisionFor(uv)
   376  					if !ok {
   377  						t.Errorf("expected revision %q but got none", want)
   378  					} else if rev != want {
   379  						t.Errorf("expected revision %q but got %q", want, rev)
   380  					}
   381  				})
   382  			}
   383  		})
   384  
   385  		t.Run("toRevision", func(t *testing.T) {
   386  			for want, uv := range revToUV {
   387  				want, uv := want, uv
   388  				t.Run(uv.String(), func(t *testing.T) {
   389  					rev, ok := c.toRevision(uv)
   390  					if !ok {
   391  						t.Errorf("expected revision %q but got none", want)
   392  					} else if rev != want {
   393  						t.Errorf("expected revision %q but got %q", want, rev)
   394  					}
   395  				})
   396  			}
   397  		})
   398  
   399  		t.Run("toUnpaired", func(t *testing.T) {
   400  			for rev, want := range revToUV {
   401  				rev, want := rev, want
   402  				t.Run(want.String(), func(t *testing.T) {
   403  					uv, ok := c.toUnpaired(rev)
   404  					if !ok {
   405  						t.Errorf("no UnpairedVersion found:\n\t(WNT): %#v", uv)
   406  					} else if !uv.identical(want) {
   407  						t.Errorf("unexpected UnpairedVersion:\n\t(GOT): %#v\n\t(WNT): %#v", uv, want)
   408  					}
   409  				})
   410  			}
   411  		})
   412  	})
   413  }
   414  
   415  // compareManifests compares two manifests and reports differences as test errors.
   416  func compareManifests(t *testing.T, want, got Manifest) {
   417  	if (want == nil || got == nil) && (got != nil || want != nil) {
   418  		t.Errorf("one manifest is nil:\n\t(GOT): %#v\n\t(WNT): %#v", got, want)
   419  		return
   420  	}
   421  	{
   422  		want, got := want.DependencyConstraints(), got.DependencyConstraints()
   423  		if !projectConstraintsEqual(want, got) {
   424  			t.Errorf("unexpected constraints:\n\t(GOT): %#v\n\t(WNT): %#v", got, want)
   425  		}
   426  	}
   427  
   428  	wantRM, wantOK := want.(RootManifest)
   429  	gotRM, gotOK := got.(RootManifest)
   430  	if wantOK && !gotOK {
   431  		t.Errorf("expected RootManifest:\n\t(GOT): %#v", got)
   432  		return
   433  	}
   434  	if gotOK && !wantOK {
   435  		t.Errorf("didn't expected RootManifest:\n\t(GOT): %#v", got)
   436  		return
   437  	}
   438  
   439  	{
   440  		want, got := wantRM.IgnoredPackages(), gotRM.IgnoredPackages()
   441  		if !reflect.DeepEqual(want.ToSlice(), got.ToSlice()) {
   442  			t.Errorf("unexpected ignored packages:\n\t(GOT): %#v\n\t(WNT): %#v", got, want)
   443  		}
   444  	}
   445  
   446  	{
   447  		want, got := wantRM.Overrides(), gotRM.Overrides()
   448  		if !projectConstraintsEqual(want, got) {
   449  			t.Errorf("unexpected overrides:\n\t(GOT): %#v\n\t(WNT): %#v", got, want)
   450  		}
   451  	}
   452  
   453  	{
   454  		want, got := wantRM.RequiredPackages(), gotRM.RequiredPackages()
   455  		if !mapStringBoolEqual(want, got) {
   456  			t.Errorf("unexpected required packages:\n\t(GOT): %#v\n\t(WNT): %#v", got, want)
   457  		}
   458  	}
   459  }
   460  
   461  // comparePackageTree compares two pkgtree.PackageTree and reports differences as test errors.
   462  func comparePackageTree(t *testing.T, want, got pkgtree.PackageTree) {
   463  	if got.ImportRoot != want.ImportRoot {
   464  		t.Errorf("expected package tree root %q but got %q", want.ImportRoot, got.ImportRoot)
   465  	}
   466  	{
   467  		want, got := want.Packages, got.Packages
   468  		if len(want) != len(got) {
   469  			t.Errorf("unexpected packages:\n\t(GOT): %#v\n\t(WNT): %#v", got, want)
   470  		} else {
   471  			for k, v := range want {
   472  				if v2, ok := got[k]; !ok {
   473  					t.Errorf("key %s: expected %v but got none", k, v)
   474  				} else if !packageOrErrEqual(v, v2) {
   475  					t.Errorf("key %s: expected %v but got %v", k, v, v2)
   476  				}
   477  			}
   478  		}
   479  	}
   480  }
   481  
   482  func projectConstraintsEqual(want, got ProjectConstraints) bool {
   483  	loop, check := want, got
   484  	if len(got) > len(want) {
   485  		loop, check = got, want
   486  	}
   487  	for pr, pp := range loop {
   488  		pp2, ok := check[pr]
   489  		if !ok {
   490  			return false
   491  		}
   492  		if pp.Source != pp2.Source {
   493  			return false
   494  		}
   495  		if pp.Constraint == nil || pp2.Constraint == nil {
   496  			if pp.Constraint != nil || pp2.Constraint != nil {
   497  				return false
   498  			}
   499  		} else if !pp.Constraint.identical(pp2.Constraint) {
   500  			return false
   501  		}
   502  	}
   503  	return true
   504  }
   505  
   506  func mapStringBoolEqual(exp, got map[string]bool) bool {
   507  	loop, check := exp, got
   508  	if len(got) > len(exp) {
   509  		loop, check = got, exp
   510  	}
   511  	for k, v := range loop {
   512  		v2, ok := check[k]
   513  		if !ok || v != v2 {
   514  			return false
   515  		}
   516  	}
   517  	return true
   518  }
   519  
   520  func safeError(err error) string {
   521  	if err == nil {
   522  		return ""
   523  	}
   524  	return err.Error()
   525  }
   526  
   527  // packageOrErrEqual return true if the pkgtree.PackageOrErrs are equal. Error equality is
   528  // string based. Imports and TestImports are treated as sets, and will be sorted.
   529  func packageOrErrEqual(a, b pkgtree.PackageOrErr) bool {
   530  	if safeError(a.Err) != safeError(b.Err) {
   531  		return false
   532  	}
   533  	if a.P.Name != b.P.Name {
   534  		return false
   535  	}
   536  	if a.P.ImportPath != b.P.ImportPath {
   537  		return false
   538  	}
   539  	if a.P.CommentPath != b.P.CommentPath {
   540  		return false
   541  	}
   542  
   543  	if len(a.P.Imports) != len(b.P.Imports) {
   544  		return false
   545  	}
   546  	sort.Strings(a.P.Imports)
   547  	sort.Strings(b.P.Imports)
   548  	for i := range a.P.Imports {
   549  		if a.P.Imports[i] != b.P.Imports[i] {
   550  			return false
   551  		}
   552  	}
   553  
   554  	if len(a.P.TestImports) != len(b.P.TestImports) {
   555  		return false
   556  	}
   557  	sort.Strings(a.P.TestImports)
   558  	sort.Strings(b.P.TestImports)
   559  	for i := range a.P.TestImports {
   560  		if a.P.TestImports[i] != b.P.TestImports[i] {
   561  			return false
   562  		}
   563  	}
   564  
   565  	return true
   566  }
   567  
   568  // discardCache produces singleSourceDiscardCaches.
   569  type discardCache struct{}
   570  
   571  func (discardCache) newSingleSourceCache(ProjectIdentifier) singleSourceCache {
   572  	return discard
   573  }
   574  
   575  func (discardCache) close() error { return nil }
   576  
   577  var discard singleSourceCache = singleSourceDiscardCache{}
   578  
   579  // singleSourceDiscardCache discards set values and returns nothing.
   580  type singleSourceDiscardCache struct{}
   581  
   582  func (singleSourceDiscardCache) setManifestAndLock(Revision, ProjectAnalyzerInfo, Manifest, Lock) {}
   583  
   584  func (singleSourceDiscardCache) getManifestAndLock(Revision, ProjectAnalyzerInfo) (Manifest, Lock, bool) {
   585  	return nil, nil, false
   586  }
   587  
   588  func (singleSourceDiscardCache) setPackageTree(Revision, pkgtree.PackageTree) {}
   589  
   590  func (singleSourceDiscardCache) getPackageTree(Revision, ProjectRoot) (pkgtree.PackageTree, bool) {
   591  	return pkgtree.PackageTree{}, false
   592  }
   593  
   594  func (singleSourceDiscardCache) markRevisionExists(r Revision) {}
   595  
   596  func (singleSourceDiscardCache) setVersionMap(versionList []PairedVersion) {}
   597  
   598  func (singleSourceDiscardCache) getVersionsFor(Revision) ([]UnpairedVersion, bool) {
   599  	return nil, false
   600  }
   601  
   602  func (singleSourceDiscardCache) getAllVersions() ([]PairedVersion, bool) {
   603  	return nil, false
   604  }
   605  
   606  func (singleSourceDiscardCache) getRevisionFor(UnpairedVersion) (Revision, bool) {
   607  	return "", false
   608  }
   609  
   610  func (singleSourceDiscardCache) toRevision(v Version) (Revision, bool) {
   611  	return "", false
   612  }
   613  
   614  func (singleSourceDiscardCache) toUnpaired(v Version) (UnpairedVersion, bool) {
   615  	return nil, false
   616  }