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

     1  // Copyright 2016 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  	"bytes"
     9  	"context"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"log"
    13  	"os"
    14  	"path"
    15  	"path/filepath"
    16  	"runtime"
    17  	"sort"
    18  	"sync"
    19  	"sync/atomic"
    20  	"testing"
    21  	"text/tabwriter"
    22  	"time"
    23  
    24  	"github.com/golang/dep/internal/test"
    25  )
    26  
    27  // An analyzer that passes nothing back, but doesn't error. This is the naive
    28  // case - no constraints, no lock, and no errors. The SourceManager will
    29  // interpret this as open/Any constraints on everything in the import graph.
    30  type naiveAnalyzer struct{}
    31  
    32  func (naiveAnalyzer) DeriveManifestAndLock(string, ProjectRoot) (Manifest, Lock, error) {
    33  	return nil, nil, nil
    34  }
    35  
    36  func (a naiveAnalyzer) Info() ProjectAnalyzerInfo {
    37  	return ProjectAnalyzerInfo{
    38  		Name:    "naive-analyzer",
    39  		Version: 1,
    40  	}
    41  }
    42  
    43  func mkNaiveSM(t *testing.T) (*SourceMgr, func()) {
    44  	cpath, err := ioutil.TempDir("", "smcache")
    45  	if err != nil {
    46  		t.Fatalf("Failed to create temp dir: %s", err)
    47  	}
    48  
    49  	sm, err := NewSourceManager(SourceManagerConfig{
    50  		Cachedir: cpath,
    51  		Logger:   log.New(test.Writer{TB: t}, "", 0),
    52  	})
    53  	if err != nil {
    54  		t.Fatalf("Unexpected error on SourceManager creation: %s", err)
    55  	}
    56  
    57  	return sm, func() {
    58  		sm.Release()
    59  		err := os.RemoveAll(cpath)
    60  		if err != nil {
    61  			t.Errorf("removeAll failed: %s", err)
    62  		}
    63  	}
    64  }
    65  
    66  func remakeNaiveSM(osm *SourceMgr, t *testing.T) (*SourceMgr, func()) {
    67  	cpath := osm.cachedir
    68  	osm.Release()
    69  
    70  	sm, err := NewSourceManager(SourceManagerConfig{
    71  		Cachedir: cpath,
    72  		Logger:   log.New(test.Writer{TB: t}, "", 0),
    73  	})
    74  	if err != nil {
    75  		t.Fatalf("unexpected error on SourceManager recreation: %s", err)
    76  	}
    77  
    78  	return sm, func() {
    79  		sm.Release()
    80  		err := os.RemoveAll(cpath)
    81  		if err != nil {
    82  			t.Errorf("removeAll failed: %s", err)
    83  		}
    84  	}
    85  }
    86  
    87  func TestSourceManagerInit(t *testing.T) {
    88  	cpath, err := ioutil.TempDir("", "smcache")
    89  	if err != nil {
    90  		t.Errorf("Failed to create temp dir: %s", err)
    91  	}
    92  	cfg := SourceManagerConfig{
    93  		Cachedir: cpath,
    94  		Logger:   log.New(test.Writer{TB: t}, "", 0),
    95  	}
    96  
    97  	sm, err := NewSourceManager(cfg)
    98  
    99  	if err != nil {
   100  		t.Errorf("Unexpected error on SourceManager creation: %s", err)
   101  	}
   102  
   103  	_, err = NewSourceManager(cfg)
   104  	if err == nil {
   105  		t.Errorf("Creating second SourceManager should have failed due to file lock contention")
   106  	} else if te, ok := err.(CouldNotCreateLockError); !ok {
   107  		t.Errorf("Should have gotten CouldNotCreateLockError error type, but got %T", te)
   108  	}
   109  
   110  	if _, err = os.Stat(path.Join(cpath, "sm.lock")); err != nil {
   111  		t.Errorf("Global cache lock file not created correctly")
   112  	}
   113  
   114  	sm.Release()
   115  	err = os.RemoveAll(cpath)
   116  	if err != nil {
   117  		t.Errorf("removeAll failed: %s", err)
   118  	}
   119  
   120  	if _, err = os.Stat(path.Join(cpath, "sm.lock")); !os.IsNotExist(err) {
   121  		t.Fatalf("Global cache lock file not cleared correctly on Release()")
   122  	}
   123  
   124  	err = os.MkdirAll(cpath, 0777)
   125  	if err != nil {
   126  		t.Errorf("Failed to re-create temp dir: %s", err)
   127  	}
   128  	defer func() {
   129  		err = os.RemoveAll(cpath)
   130  		if err != nil {
   131  			t.Errorf("removeAll failed: %s", err)
   132  		}
   133  	}()
   134  	// Set another one up at the same spot now, just to be sure
   135  	sm, err = NewSourceManager(cfg)
   136  	if err != nil {
   137  		t.Fatalf("Creating a second SourceManager should have succeeded when the first was released, but failed with err %s", err)
   138  	}
   139  
   140  	sm.Release()
   141  }
   142  
   143  func TestSourceInit(t *testing.T) {
   144  	// This test is a bit slow, skip it on -short
   145  	if testing.Short() {
   146  		t.Skip("Skipping project manager init test in short mode")
   147  	}
   148  
   149  	cpath, err := ioutil.TempDir("", "smcache")
   150  	if err != nil {
   151  		t.Fatalf("Failed to create temp dir: %s", err)
   152  	}
   153  
   154  	sm, err := NewSourceManager(SourceManagerConfig{
   155  		Cachedir: cpath,
   156  		Logger:   log.New(test.Writer{TB: t}, "", 0),
   157  	})
   158  	if err != nil {
   159  		t.Fatalf("Unexpected error on SourceManager creation: %s", err)
   160  	}
   161  
   162  	defer func() {
   163  		sm.Release()
   164  		err := os.RemoveAll(cpath)
   165  		if err != nil {
   166  			t.Errorf("removeAll failed: %s", err)
   167  		}
   168  	}()
   169  
   170  	id := mkPI("github.com/sdboyer/gpkt").normalize()
   171  	pvl, err := sm.ListVersions(id)
   172  	if err != nil {
   173  		t.Errorf("Unexpected error during initial project setup/fetching %s", err)
   174  	}
   175  
   176  	if len(pvl) != 7 {
   177  		t.Errorf("Expected seven version results from the test repo, got %v", len(pvl))
   178  	} else {
   179  		expected := []PairedVersion{
   180  			NewVersion("v2.0.0").Pair(Revision("4a54adf81c75375d26d376459c00d5ff9b703e5e")),
   181  			NewVersion("v1.1.0").Pair(Revision("b2cb48dda625f6640b34d9ffb664533359ac8b91")),
   182  			NewVersion("v1.0.0").Pair(Revision("bf85021c0405edbc4f3648b0603818d641674f72")),
   183  			newDefaultBranch("master").Pair(Revision("bf85021c0405edbc4f3648b0603818d641674f72")),
   184  			NewBranch("v1").Pair(Revision("e3777f683305eafca223aefe56b4e8ecf103f467")),
   185  			NewBranch("v1.1").Pair(Revision("f1fbc520489a98306eb28c235204e39fa8a89c84")),
   186  			NewBranch("v3").Pair(Revision("4a54adf81c75375d26d376459c00d5ff9b703e5e")),
   187  		}
   188  
   189  		// SourceManager itself doesn't guarantee ordering; sort them here so we
   190  		// can dependably check output
   191  		SortPairedForUpgrade(pvl)
   192  
   193  		for k, e := range expected {
   194  			if !pvl[k].Matches(e) {
   195  				t.Errorf("Expected version %s in position %v but got %s", e, k, pvl[k])
   196  			}
   197  		}
   198  	}
   199  
   200  	// Two birds, one stone - make sure the internal ProjectManager vlist cache
   201  	// works (or at least doesn't not work) by asking for the versions again,
   202  	// and do it through smcache to ensure its sorting works, as well.
   203  	smc := &bridge{
   204  		sm:     sm,
   205  		vlists: make(map[ProjectIdentifier][]Version),
   206  		s:      &solver{mtr: newMetrics()},
   207  	}
   208  
   209  	vl, err := smc.listVersions(id)
   210  	if err != nil {
   211  		t.Errorf("Unexpected error during initial project setup/fetching %s", err)
   212  	}
   213  
   214  	if len(vl) != 7 {
   215  		t.Errorf("Expected seven version results from the test repo, got %v", len(vl))
   216  	} else {
   217  		expected := []Version{
   218  			NewVersion("v2.0.0").Pair(Revision("4a54adf81c75375d26d376459c00d5ff9b703e5e")),
   219  			NewVersion("v1.1.0").Pair(Revision("b2cb48dda625f6640b34d9ffb664533359ac8b91")),
   220  			NewVersion("v1.0.0").Pair(Revision("bf85021c0405edbc4f3648b0603818d641674f72")),
   221  			newDefaultBranch("master").Pair(Revision("bf85021c0405edbc4f3648b0603818d641674f72")),
   222  			NewBranch("v1").Pair(Revision("e3777f683305eafca223aefe56b4e8ecf103f467")),
   223  			NewBranch("v1.1").Pair(Revision("f1fbc520489a98306eb28c235204e39fa8a89c84")),
   224  			NewBranch("v3").Pair(Revision("4a54adf81c75375d26d376459c00d5ff9b703e5e")),
   225  		}
   226  
   227  		for k, e := range expected {
   228  			if !vl[k].Matches(e) {
   229  				t.Errorf("Expected version %s in position %v but got %s", e, k, vl[k])
   230  			}
   231  		}
   232  
   233  		if !vl[3].(versionPair).v.(branchVersion).isDefault {
   234  			t.Error("Expected master branch version to have isDefault flag, but it did not")
   235  		}
   236  		if vl[4].(versionPair).v.(branchVersion).isDefault {
   237  			t.Error("Expected v1 branch version not to have isDefault flag, but it did")
   238  		}
   239  		if vl[5].(versionPair).v.(branchVersion).isDefault {
   240  			t.Error("Expected v1.1 branch version not to have isDefault flag, but it did")
   241  		}
   242  		if vl[6].(versionPair).v.(branchVersion).isDefault {
   243  			t.Error("Expected v3 branch version not to have isDefault flag, but it did")
   244  		}
   245  	}
   246  
   247  	present, err := smc.RevisionPresentIn(id, Revision("4a54adf81c75375d26d376459c00d5ff9b703e5e"))
   248  	if err != nil {
   249  		t.Errorf("Should have found revision in source, but got err: %s", err)
   250  	} else if !present {
   251  		t.Errorf("Should have found revision in source, but did not")
   252  	}
   253  
   254  	// SyncSourceFor will ensure we have everything
   255  	err = smc.SyncSourceFor(id)
   256  	if err != nil {
   257  		t.Errorf("SyncSourceFor failed with unexpected error: %s", err)
   258  	}
   259  
   260  	// Ensure that the appropriate cache dirs and files exist
   261  	_, err = os.Stat(filepath.Join(cpath, "sources", "https---github.com-sdboyer-gpkt", ".git"))
   262  	if err != nil {
   263  		t.Error("Cache repo does not exist in expected location")
   264  	}
   265  
   266  	os.Stat(filepath.Join(cpath, "metadata", "github.com", "sdboyer", "gpkt", "cache.json"))
   267  
   268  	// Ensure source existence values are what we expect
   269  	var exists bool
   270  	exists, err = sm.SourceExists(id)
   271  	if err != nil {
   272  		t.Errorf("Error on checking SourceExists: %s", err)
   273  	}
   274  	if !exists {
   275  		t.Error("Source should exist after non-erroring call to ListVersions")
   276  	}
   277  }
   278  
   279  func TestDefaultBranchAssignment(t *testing.T) {
   280  	if testing.Short() {
   281  		t.Skip("Skipping default branch assignment test in short mode")
   282  	}
   283  
   284  	sm, clean := mkNaiveSM(t)
   285  	defer clean()
   286  
   287  	id := mkPI("github.com/sdboyer/test-multibranch")
   288  	v, err := sm.ListVersions(id)
   289  	if err != nil {
   290  		t.Errorf("Unexpected error during initial project setup/fetching %s", err)
   291  	}
   292  
   293  	if len(v) != 3 {
   294  		t.Errorf("Expected three version results from the test repo, got %v", len(v))
   295  	} else {
   296  		brev := Revision("fda020843ac81352004b9dca3fcccdd517600149")
   297  		mrev := Revision("9f9c3a591773d9b28128309ac7a9a72abcab267d")
   298  		expected := []PairedVersion{
   299  			NewBranch("branchone").Pair(brev),
   300  			NewBranch("otherbranch").Pair(brev),
   301  			NewBranch("master").Pair(mrev),
   302  		}
   303  
   304  		SortPairedForUpgrade(v)
   305  
   306  		for k, e := range expected {
   307  			if !v[k].Matches(e) {
   308  				t.Errorf("Expected version %s in position %v but got %s", e, k, v[k])
   309  			}
   310  		}
   311  
   312  		if !v[0].(versionPair).v.(branchVersion).isDefault {
   313  			t.Error("Expected branchone branch version to have isDefault flag, but it did not")
   314  		}
   315  		if !v[0].(versionPair).v.(branchVersion).isDefault {
   316  			t.Error("Expected otherbranch branch version to have isDefault flag, but it did not")
   317  		}
   318  		if v[2].(versionPair).v.(branchVersion).isDefault {
   319  			t.Error("Expected master branch version not to have isDefault flag, but it did")
   320  		}
   321  	}
   322  }
   323  
   324  func TestMgrMethodsFailWithBadPath(t *testing.T) {
   325  	// a symbol will always bork it up
   326  	bad := mkPI("foo/##&^").normalize()
   327  	sm, clean := mkNaiveSM(t)
   328  	defer clean()
   329  
   330  	var err error
   331  	if _, err = sm.SourceExists(bad); err == nil {
   332  		t.Error("SourceExists() did not error on bad input")
   333  	}
   334  	if err = sm.SyncSourceFor(bad); err == nil {
   335  		t.Error("SyncSourceFor() did not error on bad input")
   336  	}
   337  	if _, err = sm.ListVersions(bad); err == nil {
   338  		t.Error("ListVersions() did not error on bad input")
   339  	}
   340  	if _, err = sm.RevisionPresentIn(bad, Revision("")); err == nil {
   341  		t.Error("RevisionPresentIn() did not error on bad input")
   342  	}
   343  	if _, err = sm.ListPackages(bad, nil); err == nil {
   344  		t.Error("ListPackages() did not error on bad input")
   345  	}
   346  	if _, _, err = sm.GetManifestAndLock(bad, nil, naiveAnalyzer{}); err == nil {
   347  		t.Error("GetManifestAndLock() did not error on bad input")
   348  	}
   349  	if err = sm.ExportProject(context.Background(), bad, nil, ""); err == nil {
   350  		t.Error("ExportProject() did not error on bad input")
   351  	}
   352  }
   353  
   354  type sourceCreationTestFixture struct {
   355  	roots               []ProjectIdentifier
   356  	namecount, srccount int
   357  }
   358  
   359  func (f sourceCreationTestFixture) run(t *testing.T) {
   360  	t.Parallel()
   361  	sm, clean := mkNaiveSM(t)
   362  	defer clean()
   363  
   364  	for _, pi := range f.roots {
   365  		_, err := sm.SourceExists(pi)
   366  		if err != nil {
   367  			t.Fatal(err)
   368  		}
   369  	}
   370  
   371  	if len(sm.srcCoord.nameToURL) != f.namecount {
   372  		t.Errorf("want %v names in the name->url map, but got %v. contents: \n%v", f.namecount, len(sm.srcCoord.nameToURL), sm.srcCoord.nameToURL)
   373  	}
   374  
   375  	if len(sm.srcCoord.srcs) != f.srccount {
   376  		t.Errorf("want %v gateways in the sources map, but got %v", f.srccount, len(sm.srcCoord.srcs))
   377  	}
   378  
   379  	if t.Failed() {
   380  		var keys []string
   381  		for k := range sm.srcCoord.nameToURL {
   382  			keys = append(keys, k)
   383  		}
   384  		sort.Strings(keys)
   385  
   386  		var buf bytes.Buffer
   387  		w := tabwriter.NewWriter(&buf, 0, 4, 2, ' ', 0)
   388  		fmt.Fprint(w, "NAME\tMAPPED URL\n")
   389  		for _, r := range keys {
   390  			fmt.Fprintf(w, "%s\t%s\n", r, sm.srcCoord.nameToURL[r])
   391  		}
   392  		w.Flush()
   393  		t.Log("\n", buf.String())
   394  
   395  		t.Log("SRC KEYS")
   396  		for k := range sm.srcCoord.srcs {
   397  			t.Log(k)
   398  		}
   399  	}
   400  }
   401  
   402  // This test is primarily about making sure that the logic around folding
   403  // together different ways of referencing the same underlying resource - whether
   404  // that be intentionally folding them, or intentionally keeping them separate -
   405  // work as intended.
   406  func TestSourceCreationCounts(t *testing.T) {
   407  	if testing.Short() {
   408  		t.Skip("Skipping slow test in short mode")
   409  	}
   410  
   411  	fixtures := map[string]sourceCreationTestFixture{
   412  		"gopkgin uniqueness": {
   413  			roots: []ProjectIdentifier{
   414  				mkPI("gopkg.in/sdboyer/gpkt.v1"),
   415  				mkPI("gopkg.in/sdboyer/gpkt.v2"),
   416  				mkPI("gopkg.in/sdboyer/gpkt.v3"),
   417  			},
   418  			namecount: 6,
   419  			srccount:  3,
   420  		},
   421  		"gopkgin separation from github": {
   422  			roots: []ProjectIdentifier{
   423  				mkPI("gopkg.in/sdboyer/gpkt.v1"),
   424  				mkPI("github.com/sdboyer/gpkt"),
   425  				mkPI("http://github.com/sdboyer/gpkt"),
   426  				mkPI("https://github.com/sdboyer/gpkt"),
   427  			},
   428  			namecount: 5,
   429  			srccount:  3,
   430  		},
   431  		"case variance across path and URL-based access": {
   432  			roots: []ProjectIdentifier{
   433  				{ProjectRoot: ProjectRoot("github.com/sdboyer/gpkt"), Source: "https://github.com/Sdboyer/gpkt"},
   434  				{ProjectRoot: ProjectRoot("github.com/sdboyer/gpkt"), Source: "https://github.com/SdbOyer/gpkt"},
   435  				mkPI("github.com/sdboyer/gpkt"),
   436  				{ProjectRoot: ProjectRoot("github.com/sdboyer/gpkt"), Source: "https://github.com/sdboyeR/gpkt"},
   437  				mkPI("github.com/sdboyeR/gpkt"),
   438  			},
   439  			namecount: 6,
   440  			srccount:  1,
   441  		},
   442  	}
   443  
   444  	for name, fix := range fixtures {
   445  		t.Run(name, fix.run)
   446  	}
   447  }
   448  
   449  func TestGetSources(t *testing.T) {
   450  	// This test is a tad slow, skip it on -short
   451  	if testing.Short() {
   452  		t.Skip("Skipping source setup test in short mode")
   453  	}
   454  	requiresBins(t, "git", "hg", "bzr")
   455  
   456  	sm, clean := mkNaiveSM(t)
   457  
   458  	pil := []ProjectIdentifier{
   459  		mkPI("github.com/Masterminds/VCSTestRepo").normalize(),
   460  		mkPI("bitbucket.org/mattfarina/testhgrepo").normalize(),
   461  		mkPI("launchpad.net/govcstestbzrrepo").normalize(),
   462  	}
   463  
   464  	ctx := context.Background()
   465  	// protects against premature release of sm
   466  	t.Run("inner", func(t *testing.T) {
   467  		for _, pi := range pil {
   468  			lpi := pi
   469  			t.Run(lpi.normalizedSource(), func(t *testing.T) {
   470  				t.Parallel()
   471  
   472  				srcg, err := sm.srcCoord.getSourceGatewayFor(ctx, lpi)
   473  				if err != nil {
   474  					t.Errorf("unexpected error setting up source: %s", err)
   475  					return
   476  				}
   477  
   478  				// Re-get the same, make sure they are the same
   479  				srcg2, err := sm.srcCoord.getSourceGatewayFor(ctx, lpi)
   480  				if err != nil {
   481  					t.Errorf("unexpected error re-getting source: %s", err)
   482  				} else if srcg != srcg2 {
   483  					t.Error("first and second sources are not eq")
   484  				}
   485  
   486  				// All of them _should_ select https, so this should work
   487  				lpi.Source = "https://" + lpi.Source
   488  				srcg3, err := sm.srcCoord.getSourceGatewayFor(ctx, lpi)
   489  				if err != nil {
   490  					t.Errorf("unexpected error getting explicit https source: %s", err)
   491  				} else if srcg != srcg3 {
   492  					t.Error("explicit https source should reuse autodetected https source")
   493  				}
   494  
   495  				// Now put in http, and they should differ
   496  				lpi.Source = "http://" + string(lpi.ProjectRoot)
   497  				srcg4, err := sm.srcCoord.getSourceGatewayFor(ctx, lpi)
   498  				if err != nil {
   499  					t.Errorf("unexpected error getting explicit http source: %s", err)
   500  				} else if srcg == srcg4 {
   501  					t.Error("explicit http source should create a new src")
   502  				}
   503  			})
   504  		}
   505  	})
   506  
   507  	// nine entries (of which three are dupes): for each vcs, raw import path,
   508  	// the https url, and the http url. also three more from case folding of
   509  	// github.com/Masterminds/VCSTestRepo -> github.com/masterminds/vcstestrepo
   510  	if len(sm.srcCoord.nameToURL) != 12 {
   511  		t.Errorf("Should have twelve discrete entries in the nameToURL map, got %v", len(sm.srcCoord.nameToURL))
   512  	}
   513  	clean()
   514  }
   515  
   516  func TestFSCaseSensitivityConvergesSources(t *testing.T) {
   517  	if testing.Short() {
   518  		t.Skip("Skipping slow test in short mode")
   519  	}
   520  
   521  	f := func(name string, pi1, pi2 ProjectIdentifier) {
   522  		t.Run(name, func(t *testing.T) {
   523  			t.Parallel()
   524  			sm, clean := mkNaiveSM(t)
   525  			defer clean()
   526  
   527  			sm.SyncSourceFor(pi1)
   528  			sg1, err := sm.srcCoord.getSourceGatewayFor(context.Background(), pi1)
   529  			if err != nil {
   530  				t.Fatal(err)
   531  			}
   532  
   533  			sm.SyncSourceFor(pi2)
   534  			sg2, err := sm.srcCoord.getSourceGatewayFor(context.Background(), pi2)
   535  			if err != nil {
   536  				t.Fatal(err)
   537  			}
   538  
   539  			path1 := sg1.src.(*gitSource).repo.LocalPath()
   540  			stat1, err := os.Stat(path1)
   541  			if err != nil {
   542  				t.Fatal("path1:", path1, err)
   543  			}
   544  			path2 := sg2.src.(*gitSource).repo.LocalPath()
   545  			stat2, err := os.Stat(path2)
   546  			if err != nil {
   547  				t.Fatal("path2:", path2, err)
   548  			}
   549  
   550  			same, count := os.SameFile(stat1, stat2), len(sm.srcCoord.srcs)
   551  			if same && count != 1 {
   552  				t.Log("are same, count", count)
   553  				t.Fatal("on case-insensitive filesystem, case-varying sources should have been folded together but were not")
   554  			}
   555  			if !same && count != 2 {
   556  				t.Log("not same, count", count)
   557  				t.Fatal("on case-sensitive filesystem, case-varying sources should not have been folded together, but were")
   558  			}
   559  		})
   560  	}
   561  
   562  	folded := mkPI("github.com/sdboyer/deptest").normalize()
   563  	casevar1 := mkPI("github.com/Sdboyer/deptest").normalize()
   564  	casevar2 := mkPI("github.com/SdboyeR/deptest").normalize()
   565  	f("folded first", folded, casevar1)
   566  	f("folded second", casevar1, folded)
   567  	f("both unfolded", casevar1, casevar2)
   568  }
   569  
   570  // Regression test for #32
   571  func TestGetInfoListVersionsOrdering(t *testing.T) {
   572  	// This test is quite slow, skip it on -short
   573  	if testing.Short() {
   574  		t.Skip("Skipping slow test in short mode")
   575  	}
   576  
   577  	sm, clean := mkNaiveSM(t)
   578  	defer clean()
   579  
   580  	// setup done, now do the test
   581  
   582  	id := mkPI("github.com/sdboyer/gpkt").normalize()
   583  
   584  	_, _, err := sm.GetManifestAndLock(id, NewVersion("v1.0.0"), naiveAnalyzer{})
   585  	if err != nil {
   586  		t.Errorf("Unexpected error from GetInfoAt %s", err)
   587  	}
   588  
   589  	v, err := sm.ListVersions(id)
   590  	if err != nil {
   591  		t.Errorf("Unexpected error from ListVersions %s", err)
   592  	}
   593  
   594  	if len(v) != 7 {
   595  		t.Errorf("Expected seven results from ListVersions, got %v", len(v))
   596  	}
   597  }
   598  
   599  func TestDeduceProjectRoot(t *testing.T) {
   600  	sm, clean := mkNaiveSM(t)
   601  	defer clean()
   602  
   603  	in := "github.com/sdboyer/gps"
   604  	pr, err := sm.DeduceProjectRoot(in)
   605  	if err != nil {
   606  		t.Errorf("Problem while detecting root of %q %s", in, err)
   607  	}
   608  	if string(pr) != in {
   609  		t.Errorf("Wrong project root was deduced;\n\t(GOT) %s\n\t(WNT) %s", pr, in)
   610  	}
   611  	if sm.deduceCoord.rootxt.Len() != 1 {
   612  		t.Errorf("Root path trie should have one element after one deduction, has %v", sm.deduceCoord.rootxt.Len())
   613  	}
   614  
   615  	pr, err = sm.DeduceProjectRoot(in)
   616  	if err != nil {
   617  		t.Errorf("Problem while detecting root of %q %s", in, err)
   618  	} else if string(pr) != in {
   619  		t.Errorf("Wrong project root was deduced;\n\t(GOT) %s\n\t(WNT) %s", pr, in)
   620  	}
   621  	if sm.deduceCoord.rootxt.Len() != 1 {
   622  		t.Errorf("Root path trie should still have one element after performing the same deduction twice; has %v", sm.deduceCoord.rootxt.Len())
   623  	}
   624  
   625  	// Now do a subpath
   626  	sub := path.Join(in, "foo")
   627  	pr, err = sm.DeduceProjectRoot(sub)
   628  	if err != nil {
   629  		t.Errorf("Problem while detecting root of %q %s", sub, err)
   630  	} else if string(pr) != in {
   631  		t.Errorf("Wrong project root was deduced;\n\t(GOT) %s\n\t(WNT) %s", pr, in)
   632  	}
   633  	if sm.deduceCoord.rootxt.Len() != 1 {
   634  		t.Errorf("Root path trie should still have one element, as still only one unique root has gone in; has %v", sm.deduceCoord.rootxt.Len())
   635  	}
   636  
   637  	// Now do a fully different root, but still on github
   638  	in2 := "github.com/bagel/lox"
   639  	sub2 := path.Join(in2, "cheese")
   640  	pr, err = sm.DeduceProjectRoot(sub2)
   641  	if err != nil {
   642  		t.Errorf("Problem while detecting root of %q %s", sub2, err)
   643  	} else if string(pr) != in2 {
   644  		t.Errorf("Wrong project root was deduced;\n\t(GOT) %s\n\t(WNT) %s", pr, in)
   645  	}
   646  	if sm.deduceCoord.rootxt.Len() != 2 {
   647  		t.Errorf("Root path trie should have two elements, one for each unique root; has %v", sm.deduceCoord.rootxt.Len())
   648  	}
   649  
   650  	// Ensure that our prefixes are bounded by path separators
   651  	in4 := "github.com/bagel/loxx"
   652  	pr, err = sm.DeduceProjectRoot(in4)
   653  	if err != nil {
   654  		t.Errorf("Problem while detecting root of %q %s", in4, err)
   655  	} else if string(pr) != in4 {
   656  		t.Errorf("Wrong project root was deduced;\n\t(GOT) %s\n\t(WNT) %s", pr, in)
   657  	}
   658  	if sm.deduceCoord.rootxt.Len() != 3 {
   659  		t.Errorf("Root path trie should have three elements, one for each unique root; has %v", sm.deduceCoord.rootxt.Len())
   660  	}
   661  
   662  	// Ensure that vcs extension-based matching comes through
   663  	in5 := "ffffrrrraaaaaapppppdoesnotresolve.com/baz.git"
   664  	pr, err = sm.DeduceProjectRoot(in5)
   665  	if err != nil {
   666  		t.Errorf("Problem while detecting root of %q %s", in5, err)
   667  	} else if string(pr) != in5 {
   668  		t.Errorf("Wrong project root was deduced;\n\t(GOT) %s\n\t(WNT) %s", pr, in)
   669  	}
   670  	if sm.deduceCoord.rootxt.Len() != 4 {
   671  		t.Errorf("Root path trie should have four elements, one for each unique root; has %v", sm.deduceCoord.rootxt.Len())
   672  	}
   673  }
   674  
   675  func TestMultiFetchThreadsafe(t *testing.T) {
   676  	// This test is quite slow, skip it on -short
   677  	if testing.Short() {
   678  		t.Skip("Skipping slow test in short mode")
   679  	}
   680  
   681  	projects := []ProjectIdentifier{
   682  		mkPI("github.com/sdboyer/gps"),
   683  		mkPI("github.com/sdboyer/gpkt"),
   684  		{
   685  			ProjectRoot: ProjectRoot("github.com/sdboyer/gpkt"),
   686  			Source:      "https://github.com/sdboyer/gpkt",
   687  		},
   688  		mkPI("github.com/sdboyer/gogl"),
   689  		mkPI("github.com/sdboyer/gliph"),
   690  		mkPI("github.com/sdboyer/frozone"),
   691  		mkPI("gopkg.in/sdboyer/gpkt.v1"),
   692  		mkPI("gopkg.in/sdboyer/gpkt.v2"),
   693  		mkPI("github.com/Masterminds/VCSTestRepo"),
   694  		mkPI("github.com/go-yaml/yaml"),
   695  		mkPI("github.com/sirupsen/logrus"),
   696  		mkPI("github.com/Masterminds/semver"),
   697  		mkPI("github.com/Masterminds/vcs"),
   698  		//mkPI("bitbucket.org/sdboyer/withbm"),
   699  		//mkPI("bitbucket.org/sdboyer/nobm"),
   700  	}
   701  
   702  	do := func(name string, sm SourceManager) {
   703  		t.Run(name, func(t *testing.T) {
   704  			// This gives us ten calls per op, per project, which should be(?)
   705  			// decently likely to reveal underlying concurrency problems
   706  			ops := 4
   707  			cnum := len(projects) * ops * 10
   708  
   709  			for i := 0; i < cnum; i++ {
   710  				// Trigger all four ops on each project, then move on to the next
   711  				// project.
   712  				id, op := projects[(i/ops)%len(projects)], i%ops
   713  				// The count of times this op has been been invoked on this project
   714  				// (after the upcoming invocation)
   715  				opcount := i/(ops*len(projects)) + 1
   716  
   717  				switch op {
   718  				case 0:
   719  					t.Run(fmt.Sprintf("deduce:%v:%s", opcount, id), func(t *testing.T) {
   720  						t.Parallel()
   721  						if _, err := sm.DeduceProjectRoot(string(id.ProjectRoot)); err != nil {
   722  							t.Error(err)
   723  						}
   724  					})
   725  				case 1:
   726  					t.Run(fmt.Sprintf("sync:%v:%s", opcount, id), func(t *testing.T) {
   727  						t.Parallel()
   728  						err := sm.SyncSourceFor(id)
   729  						if err != nil {
   730  							t.Error(err)
   731  						}
   732  					})
   733  				case 2:
   734  					t.Run(fmt.Sprintf("listVersions:%v:%s", opcount, id), func(t *testing.T) {
   735  						t.Parallel()
   736  						vl, err := sm.ListVersions(id)
   737  						if err != nil {
   738  							t.Fatal(err)
   739  						}
   740  						if len(vl) == 0 {
   741  							t.Error("no versions returned")
   742  						}
   743  					})
   744  				case 3:
   745  					t.Run(fmt.Sprintf("exists:%v:%s", opcount, id), func(t *testing.T) {
   746  						t.Parallel()
   747  						y, err := sm.SourceExists(id)
   748  						if err != nil {
   749  							t.Fatal(err)
   750  						}
   751  						if !y {
   752  							t.Error("said source does not exist")
   753  						}
   754  					})
   755  				default:
   756  					panic(fmt.Sprintf("wtf, %s %v", id, op))
   757  				}
   758  			}
   759  		})
   760  	}
   761  
   762  	sm, _ := mkNaiveSM(t)
   763  	do("first", sm)
   764  
   765  	// Run the thing twice with a remade sm so that we cover both the cases of
   766  	// pre-existing and new clones.
   767  	//
   768  	// This triggers a release of the first sm, which is much of what we're
   769  	// testing here - that the release is complete and clean, and can be
   770  	// immediately followed by a new sm coming in.
   771  	sm2, clean := remakeNaiveSM(sm, t)
   772  	do("second", sm2)
   773  	clean()
   774  }
   775  
   776  // Ensure that we don't see concurrent map writes when calling ListVersions.
   777  // Regression test for https://github.com/sdboyer/gps/issues/156.
   778  //
   779  // Ideally this would be caught by TestMultiFetchThreadsafe, but perhaps the
   780  // high degree of parallelism pretty much eliminates that as a realistic
   781  // possibility?
   782  func TestListVersionsRacey(t *testing.T) {
   783  	// This test is quite slow, skip it on -short
   784  	if testing.Short() {
   785  		t.Skip("Skipping slow test in short mode")
   786  	}
   787  
   788  	sm, clean := mkNaiveSM(t)
   789  	defer clean()
   790  
   791  	wg := &sync.WaitGroup{}
   792  	id := mkPI("github.com/sdboyer/gps")
   793  	for i := 0; i < 20; i++ {
   794  		wg.Add(1)
   795  		go func() {
   796  			_, err := sm.ListVersions(id)
   797  			if err != nil {
   798  				t.Errorf("listing versions failed with err %s", err.Error())
   799  			}
   800  			wg.Done()
   801  		}()
   802  	}
   803  
   804  	wg.Wait()
   805  }
   806  
   807  func TestErrAfterRelease(t *testing.T) {
   808  	sm, clean := mkNaiveSM(t)
   809  	clean()
   810  	id := ProjectIdentifier{}
   811  
   812  	_, err := sm.SourceExists(id)
   813  	if err == nil {
   814  		t.Errorf("SourceExists did not error after calling Release()")
   815  	} else if err != ErrSourceManagerIsReleased {
   816  		t.Errorf("SourceExists errored after Release(), but with unexpected error: %T %s", err, err.Error())
   817  	}
   818  
   819  	err = sm.SyncSourceFor(id)
   820  	if err == nil {
   821  		t.Errorf("SyncSourceFor did not error after calling Release()")
   822  	} else if err != ErrSourceManagerIsReleased {
   823  		t.Errorf("SyncSourceFor errored after Release(), but with unexpected error: %T %s", err, err.Error())
   824  	}
   825  
   826  	_, err = sm.ListVersions(id)
   827  	if err == nil {
   828  		t.Errorf("ListVersions did not error after calling Release()")
   829  	} else if err != ErrSourceManagerIsReleased {
   830  		t.Errorf("ListVersions errored after Release(), but with unexpected error: %T %s", err, err.Error())
   831  	}
   832  
   833  	_, err = sm.RevisionPresentIn(id, "")
   834  	if err == nil {
   835  		t.Errorf("RevisionPresentIn did not error after calling Release()")
   836  	} else if err != ErrSourceManagerIsReleased {
   837  		t.Errorf("RevisionPresentIn errored after Release(), but with unexpected error: %T %s", err, err.Error())
   838  	}
   839  
   840  	_, err = sm.ListPackages(id, nil)
   841  	if err == nil {
   842  		t.Errorf("ListPackages did not error after calling Release()")
   843  	} else if err != ErrSourceManagerIsReleased {
   844  		t.Errorf("ListPackages errored after Release(), but with unexpected error: %T %s", err, err.Error())
   845  	}
   846  
   847  	_, _, err = sm.GetManifestAndLock(id, nil, naiveAnalyzer{})
   848  	if err == nil {
   849  		t.Errorf("GetManifestAndLock did not error after calling Release()")
   850  	} else if err != ErrSourceManagerIsReleased {
   851  		t.Errorf("GetManifestAndLock errored after Release(), but with unexpected error: %T %s", err, err.Error())
   852  	}
   853  
   854  	err = sm.ExportProject(context.Background(), id, nil, "")
   855  	if err == nil {
   856  		t.Errorf("ExportProject did not error after calling Release()")
   857  	} else if err != ErrSourceManagerIsReleased {
   858  		t.Errorf("ExportProject errored after Release(), but with unexpected error: %T %s", err, err.Error())
   859  	}
   860  
   861  	_, err = sm.DeduceProjectRoot("")
   862  	if err == nil {
   863  		t.Errorf("DeduceProjectRoot did not error after calling Release()")
   864  	} else if err != ErrSourceManagerIsReleased {
   865  		t.Errorf("DeduceProjectRoot errored after Release(), but with unexpected error: %T %s", err, err.Error())
   866  	}
   867  }
   868  
   869  func TestSignalHandling(t *testing.T) {
   870  	if testing.Short() {
   871  		t.Skip("Skipping slow test in short mode")
   872  	}
   873  
   874  	sm, clean := mkNaiveSM(t)
   875  
   876  	sigch := make(chan os.Signal)
   877  	sm.HandleSignals(sigch)
   878  
   879  	sigch <- os.Interrupt
   880  	<-time.After(10 * time.Millisecond)
   881  
   882  	if atomic.LoadInt32(&sm.releasing) != 1 {
   883  		t.Error("Releasing flag did not get set")
   884  	}
   885  
   886  	clean()
   887  
   888  	// Test again, this time with a running call
   889  	sm, clean = mkNaiveSM(t)
   890  	sm.HandleSignals(sigch)
   891  
   892  	errchan := make(chan error)
   893  	go func() {
   894  		_, callerr := sm.DeduceProjectRoot("k8s.io/kubernetes")
   895  		errchan <- callerr
   896  	}()
   897  	go func() { sigch <- os.Interrupt }()
   898  	runtime.Gosched()
   899  
   900  	callerr := <-errchan
   901  	if callerr == nil {
   902  		t.Error("network call could not have completed before cancellation, should have gotten an error")
   903  	}
   904  	if atomic.LoadInt32(&sm.releasing) != 1 {
   905  		t.Error("Releasing flag did not get set")
   906  	}
   907  	clean()
   908  
   909  	sm, clean = mkNaiveSM(t)
   910  	// Ensure that handling also works after stopping and restarting itself,
   911  	// and that Release happens only once.
   912  	sm.UseDefaultSignalHandling()
   913  	sm.StopSignalHandling()
   914  	sm.HandleSignals(sigch)
   915  
   916  	go func() {
   917  		_, callerr := sm.DeduceProjectRoot("k8s.io/kubernetes")
   918  		errchan <- callerr
   919  	}()
   920  	go func() {
   921  		sigch <- os.Interrupt
   922  		sm.Release()
   923  	}()
   924  	runtime.Gosched()
   925  
   926  	after := time.After(2 * time.Second)
   927  	select {
   928  	case <-sm.qch:
   929  	case <-after:
   930  		t.Error("did not shut down in reasonable time")
   931  	}
   932  
   933  	clean()
   934  }
   935  
   936  func TestUnreachableSource(t *testing.T) {
   937  	// If a git remote is unreachable (maybe the server is only accessible behind a VPN, or
   938  	// something), we should return a clear error, not a panic.
   939  	if testing.Short() {
   940  		t.Skip("Skipping slow test in short mode")
   941  	}
   942  
   943  	sm, clean := mkNaiveSM(t)
   944  	defer clean()
   945  
   946  	id := mkPI("github.com/golang/notexist").normalize()
   947  	err := sm.SyncSourceFor(id)
   948  	if err == nil {
   949  		t.Error("expected err when listing versions of a bogus source, but got nil")
   950  	}
   951  }
   952  
   953  func TestSupervisor(t *testing.T) {
   954  	bgc := context.Background()
   955  	ctx, cancelFunc := context.WithCancel(bgc)
   956  	superv := newSupervisor(ctx)
   957  
   958  	ci := callInfo{
   959  		name: "foo",
   960  		typ:  0,
   961  	}
   962  
   963  	_, err := superv.start(ci)
   964  	if err != nil {
   965  		t.Fatal("unexpected err on setUpCall:", err)
   966  	}
   967  
   968  	tc, exists := superv.running[ci]
   969  	if !exists {
   970  		t.Fatal("running call not recorded in map")
   971  	}
   972  
   973  	if tc.count != 1 {
   974  		t.Fatalf("wrong count of running ci: wanted 1 got %v", tc.count)
   975  	}
   976  
   977  	// run another, but via do
   978  	block, wait := make(chan struct{}), make(chan struct{})
   979  	errchan := make(chan error)
   980  	go func() {
   981  		wait <- struct{}{}
   982  		err := superv.do(bgc, "foo", 0, func(ctx context.Context) error {
   983  			<-block
   984  			return nil
   985  		})
   986  		errchan <- err
   987  		//if err != nil {
   988  		//	t.Fatal("unexpected err on do() completion:", err)
   989  		//}
   990  		close(wait)
   991  	}()
   992  	<-wait
   993  
   994  	superv.mu.Lock()
   995  	tc, exists = superv.running[ci]
   996  	if !exists {
   997  		t.Fatal("running call not recorded in map")
   998  	}
   999  
  1000  	// TODO (kris-nova) We need to disable this bypass here, and in the .travis.yml
  1001  	// as soon as dep#501 is fixed
  1002  	bypass := os.Getenv("DEPTESTBYPASS501")
  1003  	if bypass != "" {
  1004  		t.Log("bypassing tc.count check for running ci")
  1005  	} else if tc.count != 2 {
  1006  		t.Fatalf("wrong count of running ci: wanted 2 got %v", tc.count)
  1007  	}
  1008  	superv.mu.Unlock()
  1009  
  1010  	close(block)
  1011  
  1012  	possibleConcurrentError := <-errchan
  1013  	if possibleConcurrentError != nil {
  1014  		t.Fatal("unexpected err on do() completion:", err)
  1015  	}
  1016  
  1017  	<-wait
  1018  	superv.mu.Lock()
  1019  	if len(superv.ran) != 0 {
  1020  		t.Fatal("should not record metrics until last one drops")
  1021  	}
  1022  
  1023  	tc, exists = superv.running[ci]
  1024  	if !exists {
  1025  		t.Fatal("running call not recorded in map")
  1026  	}
  1027  
  1028  	if tc.count != 1 {
  1029  		t.Fatalf("wrong count of running ci: wanted 1 got %v", tc.count)
  1030  	}
  1031  	superv.mu.Unlock()
  1032  
  1033  	superv.done(ci)
  1034  	superv.mu.Lock()
  1035  	ran, exists := superv.ran[0]
  1036  	if !exists {
  1037  		t.Fatal("should have metrics after closing last of a ci, but did not")
  1038  	}
  1039  
  1040  	if ran.count != 1 {
  1041  		t.Fatalf("wrong count of serial runs of a call: wanted 1 got %v", ran.count)
  1042  	}
  1043  	superv.mu.Unlock()
  1044  
  1045  	cancelFunc()
  1046  	_, err = superv.start(ci)
  1047  	if err == nil {
  1048  		t.Fatal("should have errored on cm.run() after canceling cm's input context")
  1049  	}
  1050  
  1051  	superv.do(bgc, "foo", 0, func(ctx context.Context) error {
  1052  		t.Fatal("calls should not be initiated by do() after main context is cancelled")
  1053  		return nil
  1054  	})
  1055  }