github.com/sdboyer/gps@v0.16.3/deduce_test.go (about)

     1  package gps
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"net/url"
     9  	"reflect"
    10  	"testing"
    11  )
    12  
    13  type pathDeductionFixture struct {
    14  	in     string
    15  	root   string
    16  	rerr   error
    17  	mb     maybeSource
    18  	srcerr error
    19  }
    20  
    21  // helper func to generate testing *url.URLs, panicking on err
    22  func mkurl(s string) (u *url.URL) {
    23  	var err error
    24  	u, err = url.Parse(s)
    25  	if err != nil {
    26  		panic(fmt.Sprint("string is not a valid URL:", s))
    27  	}
    28  	return
    29  }
    30  
    31  var pathDeductionFixtures = map[string][]pathDeductionFixture{
    32  	"github": []pathDeductionFixture{
    33  		{
    34  			in:   "github.com/sdboyer/gps",
    35  			root: "github.com/sdboyer/gps",
    36  			mb: maybeSources{
    37  				maybeGitSource{url: mkurl("https://github.com/sdboyer/gps")},
    38  				maybeGitSource{url: mkurl("ssh://git@github.com/sdboyer/gps")},
    39  				maybeGitSource{url: mkurl("git://github.com/sdboyer/gps")},
    40  				maybeGitSource{url: mkurl("http://github.com/sdboyer/gps")},
    41  			},
    42  		},
    43  		{
    44  			in:   "github.com/sdboyer/gps/foo",
    45  			root: "github.com/sdboyer/gps",
    46  			mb: maybeSources{
    47  				maybeGitSource{url: mkurl("https://github.com/sdboyer/gps")},
    48  				maybeGitSource{url: mkurl("ssh://git@github.com/sdboyer/gps")},
    49  				maybeGitSource{url: mkurl("git://github.com/sdboyer/gps")},
    50  				maybeGitSource{url: mkurl("http://github.com/sdboyer/gps")},
    51  			},
    52  		},
    53  		{
    54  			// TODO(sdboyer) is this a problem for enforcing uniqueness? do we
    55  			// need to collapse these extensions?
    56  			in:   "github.com/sdboyer/gps.git/foo",
    57  			root: "github.com/sdboyer/gps.git",
    58  			mb: maybeSources{
    59  				maybeGitSource{url: mkurl("https://github.com/sdboyer/gps.git")},
    60  				maybeGitSource{url: mkurl("ssh://git@github.com/sdboyer/gps.git")},
    61  				maybeGitSource{url: mkurl("git://github.com/sdboyer/gps.git")},
    62  				maybeGitSource{url: mkurl("http://github.com/sdboyer/gps.git")},
    63  			},
    64  		},
    65  		{
    66  			in:   "git@github.com:sdboyer/gps",
    67  			root: "github.com/sdboyer/gps",
    68  			mb:   maybeGitSource{url: mkurl("ssh://git@github.com/sdboyer/gps")},
    69  		},
    70  		{
    71  			in:   "https://github.com/sdboyer/gps",
    72  			root: "github.com/sdboyer/gps",
    73  			mb:   maybeGitSource{url: mkurl("https://github.com/sdboyer/gps")},
    74  		},
    75  		{
    76  			in:   "https://github.com/sdboyer/gps/foo/bar",
    77  			root: "github.com/sdboyer/gps",
    78  			mb:   maybeGitSource{url: mkurl("https://github.com/sdboyer/gps")},
    79  		},
    80  		{
    81  			in:   "github.com/sdboyer-/gps/foo",
    82  			root: "github.com/sdboyer-/gps",
    83  			mb: maybeSources{
    84  				maybeGitSource{url: mkurl("https://github.com/sdboyer-/gps")},
    85  				maybeGitSource{url: mkurl("ssh://git@github.com/sdboyer-/gps")},
    86  				maybeGitSource{url: mkurl("git://github.com/sdboyer-/gps")},
    87  				maybeGitSource{url: mkurl("http://github.com/sdboyer-/gps")},
    88  			},
    89  		},
    90  		{
    91  			in:   "github.com/a/gps/foo",
    92  			root: "github.com/a/gps",
    93  			mb: maybeSources{
    94  				maybeGitSource{url: mkurl("https://github.com/a/gps")},
    95  				maybeGitSource{url: mkurl("ssh://git@github.com/a/gps")},
    96  				maybeGitSource{url: mkurl("git://github.com/a/gps")},
    97  				maybeGitSource{url: mkurl("http://github.com/a/gps")},
    98  			},
    99  		},
   100  		// some invalid github username patterns
   101  		{
   102  			in:   "github.com/-sdboyer/gps/foo",
   103  			rerr: errors.New("github.com/-sdboyer/gps/foo is not a valid path for a source on github.com"),
   104  		},
   105  		{
   106  			in:   "github.com/sdbo.yer/gps/foo",
   107  			rerr: errors.New("github.com/sdbo.yer/gps/foo is not a valid path for a source on github.com"),
   108  		},
   109  		{
   110  			in:   "github.com/sdbo_yer/gps/foo",
   111  			rerr: errors.New("github.com/sdbo_yer/gps/foo is not a valid path for a source on github.com"),
   112  		},
   113  		// Regression - gh does allow two-letter usernames
   114  		{
   115  			in:   "github.com/kr/pretty",
   116  			root: "github.com/kr/pretty",
   117  			mb: maybeSources{
   118  				maybeGitSource{url: mkurl("https://github.com/kr/pretty")},
   119  				maybeGitSource{url: mkurl("ssh://git@github.com/kr/pretty")},
   120  				maybeGitSource{url: mkurl("git://github.com/kr/pretty")},
   121  				maybeGitSource{url: mkurl("http://github.com/kr/pretty")},
   122  			},
   123  		},
   124  	},
   125  	"gopkg.in": []pathDeductionFixture{
   126  		{
   127  			in:   "gopkg.in/sdboyer/gps.v0",
   128  			root: "gopkg.in/sdboyer/gps.v0",
   129  			mb: maybeSources{
   130  				maybeGopkginSource{opath: "gopkg.in/sdboyer/gps.v0", url: mkurl("https://github.com/sdboyer/gps"), major: 0},
   131  				maybeGopkginSource{opath: "gopkg.in/sdboyer/gps.v0", url: mkurl("ssh://git@github.com/sdboyer/gps"), major: 0},
   132  				maybeGopkginSource{opath: "gopkg.in/sdboyer/gps.v0", url: mkurl("git://github.com/sdboyer/gps"), major: 0},
   133  				maybeGopkginSource{opath: "gopkg.in/sdboyer/gps.v0", url: mkurl("http://github.com/sdboyer/gps"), major: 0},
   134  			},
   135  		},
   136  		{
   137  			in:   "gopkg.in/sdboyer/gps.v0/foo",
   138  			root: "gopkg.in/sdboyer/gps.v0",
   139  			mb: maybeSources{
   140  				maybeGopkginSource{opath: "gopkg.in/sdboyer/gps.v0", url: mkurl("https://github.com/sdboyer/gps"), major: 0},
   141  				maybeGopkginSource{opath: "gopkg.in/sdboyer/gps.v0", url: mkurl("ssh://git@github.com/sdboyer/gps"), major: 0},
   142  				maybeGopkginSource{opath: "gopkg.in/sdboyer/gps.v0", url: mkurl("git://github.com/sdboyer/gps"), major: 0},
   143  				maybeGopkginSource{opath: "gopkg.in/sdboyer/gps.v0", url: mkurl("http://github.com/sdboyer/gps"), major: 0},
   144  			},
   145  		},
   146  		{
   147  			in:   "gopkg.in/sdboyer/gps.v1/foo/bar",
   148  			root: "gopkg.in/sdboyer/gps.v1",
   149  			mb: maybeSources{
   150  				maybeGopkginSource{opath: "gopkg.in/sdboyer/gps.v1", url: mkurl("https://github.com/sdboyer/gps"), major: 1},
   151  				maybeGopkginSource{opath: "gopkg.in/sdboyer/gps.v1", url: mkurl("ssh://git@github.com/sdboyer/gps"), major: 1},
   152  				maybeGopkginSource{opath: "gopkg.in/sdboyer/gps.v1", url: mkurl("git://github.com/sdboyer/gps"), major: 1},
   153  				maybeGopkginSource{opath: "gopkg.in/sdboyer/gps.v1", url: mkurl("http://github.com/sdboyer/gps"), major: 1},
   154  			},
   155  		},
   156  		{
   157  			in:   "gopkg.in/yaml.v1",
   158  			root: "gopkg.in/yaml.v1",
   159  			mb: maybeSources{
   160  				maybeGopkginSource{opath: "gopkg.in/yaml.v1", url: mkurl("https://github.com/go-yaml/yaml"), major: 1},
   161  				maybeGopkginSource{opath: "gopkg.in/yaml.v1", url: mkurl("ssh://git@github.com/go-yaml/yaml"), major: 1},
   162  				maybeGopkginSource{opath: "gopkg.in/yaml.v1", url: mkurl("git://github.com/go-yaml/yaml"), major: 1},
   163  				maybeGopkginSource{opath: "gopkg.in/yaml.v1", url: mkurl("http://github.com/go-yaml/yaml"), major: 1},
   164  			},
   165  		},
   166  		{
   167  			in:   "gopkg.in/yaml.v1/foo/bar",
   168  			root: "gopkg.in/yaml.v1",
   169  			mb: maybeSources{
   170  				maybeGopkginSource{opath: "gopkg.in/yaml.v1", url: mkurl("https://github.com/go-yaml/yaml"), major: 1},
   171  				maybeGopkginSource{opath: "gopkg.in/yaml.v1", url: mkurl("ssh://git@github.com/go-yaml/yaml"), major: 1},
   172  				maybeGopkginSource{opath: "gopkg.in/yaml.v1", url: mkurl("git://github.com/go-yaml/yaml"), major: 1},
   173  				maybeGopkginSource{opath: "gopkg.in/yaml.v1", url: mkurl("http://github.com/go-yaml/yaml"), major: 1},
   174  			},
   175  		},
   176  		{
   177  			in:   "gopkg.in/inf.v0",
   178  			root: "gopkg.in/inf.v0",
   179  			mb: maybeSources{
   180  				maybeGopkginSource{opath: "gopkg.in/inf.v0", url: mkurl("https://github.com/go-inf/inf"), major: 0},
   181  				maybeGopkginSource{opath: "gopkg.in/inf.v0", url: mkurl("ssh://git@github.com/go-inf/inf"), major: 0},
   182  				maybeGopkginSource{opath: "gopkg.in/inf.v0", url: mkurl("git://github.com/go-inf/inf"), major: 0},
   183  				maybeGopkginSource{opath: "gopkg.in/inf.v0", url: mkurl("http://github.com/go-inf/inf"), major: 0},
   184  			},
   185  		},
   186  		{
   187  			// gopkg.in only allows specifying major version in import path
   188  			in:   "gopkg.in/yaml.v1.2",
   189  			rerr: errors.New("gopkg.in/yaml.v1.2 is not a valid import path; gopkg.in only allows major versions (\"v1\" instead of \"v1.2\")"),
   190  		},
   191  	},
   192  	"jazz": []pathDeductionFixture{
   193  		// IBM hub devops services - fixtures borrowed from go get
   194  		{
   195  			in:   "hub.jazz.net/git/user1/pkgname",
   196  			root: "hub.jazz.net/git/user1/pkgname",
   197  			mb:   maybeGitSource{url: mkurl("https://hub.jazz.net/git/user1/pkgname")},
   198  		},
   199  		{
   200  			in:   "hub.jazz.net/git/user1/pkgname/submodule/submodule/submodule",
   201  			root: "hub.jazz.net/git/user1/pkgname",
   202  			mb:   maybeGitSource{url: mkurl("https://hub.jazz.net/git/user1/pkgname")},
   203  		},
   204  		{
   205  			in:   "hub.jazz.net/someotherprefix",
   206  			rerr: errors.New("hub.jazz.net/someotherprefix is not a valid path for a source on hub.jazz.net"),
   207  		},
   208  		{
   209  			in:   "hub.jazz.net/someotherprefix/user1/packagename",
   210  			rerr: errors.New("hub.jazz.net/someotherprefix/user1/packagename is not a valid path for a source on hub.jazz.net"),
   211  		},
   212  		// Spaces are not valid in user names or package names
   213  		{
   214  			in:   "hub.jazz.net/git/User 1/pkgname",
   215  			rerr: errors.New("hub.jazz.net/git/User 1/pkgname is not a valid path for a source on hub.jazz.net"),
   216  		},
   217  		{
   218  			in:   "hub.jazz.net/git/user1/pkg name",
   219  			rerr: errors.New("hub.jazz.net/git/user1/pkg name is not a valid path for a source on hub.jazz.net"),
   220  		},
   221  		// Dots are not valid in user names
   222  		{
   223  			in:   "hub.jazz.net/git/user.1/pkgname",
   224  			rerr: errors.New("hub.jazz.net/git/user.1/pkgname is not a valid path for a source on hub.jazz.net"),
   225  		},
   226  		{
   227  			in:   "hub.jazz.net/git/user1/pkg.name",
   228  			root: "hub.jazz.net/git/user1/pkg.name",
   229  			mb:   maybeGitSource{url: mkurl("https://hub.jazz.net/git/user1/pkg.name")},
   230  		},
   231  		// User names cannot have uppercase letters
   232  		{
   233  			in:   "hub.jazz.net/git/USER/pkgname",
   234  			rerr: errors.New("hub.jazz.net/git/USER/pkgname is not a valid path for a source on hub.jazz.net"),
   235  		},
   236  	},
   237  	"bitbucket": []pathDeductionFixture{
   238  		{
   239  			in:   "bitbucket.org/sdboyer/reporoot",
   240  			root: "bitbucket.org/sdboyer/reporoot",
   241  			mb: maybeSources{
   242  				maybeHgSource{url: mkurl("https://bitbucket.org/sdboyer/reporoot")},
   243  				maybeHgSource{url: mkurl("ssh://hg@bitbucket.org/sdboyer/reporoot")},
   244  				maybeHgSource{url: mkurl("http://bitbucket.org/sdboyer/reporoot")},
   245  				maybeGitSource{url: mkurl("https://bitbucket.org/sdboyer/reporoot")},
   246  				maybeGitSource{url: mkurl("ssh://git@bitbucket.org/sdboyer/reporoot")},
   247  				maybeGitSource{url: mkurl("git://bitbucket.org/sdboyer/reporoot")},
   248  				maybeGitSource{url: mkurl("http://bitbucket.org/sdboyer/reporoot")},
   249  			},
   250  		},
   251  		{
   252  			in:   "bitbucket.org/sdboyer/reporoot/foo/bar",
   253  			root: "bitbucket.org/sdboyer/reporoot",
   254  			mb: maybeSources{
   255  				maybeHgSource{url: mkurl("https://bitbucket.org/sdboyer/reporoot")},
   256  				maybeHgSource{url: mkurl("ssh://hg@bitbucket.org/sdboyer/reporoot")},
   257  				maybeHgSource{url: mkurl("http://bitbucket.org/sdboyer/reporoot")},
   258  				maybeGitSource{url: mkurl("https://bitbucket.org/sdboyer/reporoot")},
   259  				maybeGitSource{url: mkurl("ssh://git@bitbucket.org/sdboyer/reporoot")},
   260  				maybeGitSource{url: mkurl("git://bitbucket.org/sdboyer/reporoot")},
   261  				maybeGitSource{url: mkurl("http://bitbucket.org/sdboyer/reporoot")},
   262  			},
   263  		},
   264  		{
   265  			in:   "https://bitbucket.org/sdboyer/reporoot/foo/bar",
   266  			root: "bitbucket.org/sdboyer/reporoot",
   267  			mb: maybeSources{
   268  				maybeHgSource{url: mkurl("https://bitbucket.org/sdboyer/reporoot")},
   269  				maybeGitSource{url: mkurl("https://bitbucket.org/sdboyer/reporoot")},
   270  			},
   271  		},
   272  		// Less standard behaviors possible due to the hg/git ambiguity
   273  		{
   274  			in:   "bitbucket.org/sdboyer/reporoot.git",
   275  			root: "bitbucket.org/sdboyer/reporoot.git",
   276  			mb: maybeSources{
   277  				maybeGitSource{url: mkurl("https://bitbucket.org/sdboyer/reporoot.git")},
   278  				maybeGitSource{url: mkurl("ssh://git@bitbucket.org/sdboyer/reporoot.git")},
   279  				maybeGitSource{url: mkurl("git://bitbucket.org/sdboyer/reporoot.git")},
   280  				maybeGitSource{url: mkurl("http://bitbucket.org/sdboyer/reporoot.git")},
   281  			},
   282  		},
   283  		{
   284  			in:   "git@bitbucket.org:sdboyer/reporoot.git",
   285  			root: "bitbucket.org/sdboyer/reporoot.git",
   286  			mb:   maybeGitSource{url: mkurl("ssh://git@bitbucket.org/sdboyer/reporoot.git")},
   287  		},
   288  		{
   289  			in:   "bitbucket.org/sdboyer/reporoot.hg",
   290  			root: "bitbucket.org/sdboyer/reporoot.hg",
   291  			mb: maybeSources{
   292  				maybeHgSource{url: mkurl("https://bitbucket.org/sdboyer/reporoot.hg")},
   293  				maybeHgSource{url: mkurl("ssh://hg@bitbucket.org/sdboyer/reporoot.hg")},
   294  				maybeHgSource{url: mkurl("http://bitbucket.org/sdboyer/reporoot.hg")},
   295  			},
   296  		},
   297  		{
   298  			in:   "hg@bitbucket.org:sdboyer/reporoot",
   299  			root: "bitbucket.org/sdboyer/reporoot",
   300  			mb:   maybeHgSource{url: mkurl("ssh://hg@bitbucket.org/sdboyer/reporoot")},
   301  		},
   302  		{
   303  			in:     "git://bitbucket.org/sdboyer/reporoot.hg",
   304  			root:   "bitbucket.org/sdboyer/reporoot.hg",
   305  			srcerr: errors.New("git is not a valid scheme for accessing an hg repository"),
   306  		},
   307  	},
   308  	"launchpad": []pathDeductionFixture{
   309  		// tests for launchpad, mostly bazaar
   310  		// TODO(sdboyer) need more tests to deal w/launchpad's oddities
   311  		{
   312  			in:   "launchpad.net/govcstestbzrrepo",
   313  			root: "launchpad.net/govcstestbzrrepo",
   314  			mb: maybeSources{
   315  				maybeBzrSource{url: mkurl("https://launchpad.net/govcstestbzrrepo")},
   316  				maybeBzrSource{url: mkurl("bzr+ssh://launchpad.net/govcstestbzrrepo")},
   317  				maybeBzrSource{url: mkurl("bzr://launchpad.net/govcstestbzrrepo")},
   318  				maybeBzrSource{url: mkurl("http://launchpad.net/govcstestbzrrepo")},
   319  			},
   320  		},
   321  		{
   322  			in:   "launchpad.net/govcstestbzrrepo/foo/bar",
   323  			root: "launchpad.net/govcstestbzrrepo",
   324  			mb: maybeSources{
   325  				maybeBzrSource{url: mkurl("https://launchpad.net/govcstestbzrrepo")},
   326  				maybeBzrSource{url: mkurl("bzr+ssh://launchpad.net/govcstestbzrrepo")},
   327  				maybeBzrSource{url: mkurl("bzr://launchpad.net/govcstestbzrrepo")},
   328  				maybeBzrSource{url: mkurl("http://launchpad.net/govcstestbzrrepo")},
   329  			},
   330  		},
   331  		{
   332  			in:   "launchpad.net/repo root",
   333  			rerr: errors.New("launchpad.net/repo root is not a valid path for a source on launchpad.net"),
   334  		},
   335  	},
   336  	"git.launchpad": []pathDeductionFixture{
   337  		{
   338  			in:   "git.launchpad.net/reporoot",
   339  			root: "git.launchpad.net/reporoot",
   340  			mb: maybeSources{
   341  				maybeGitSource{url: mkurl("https://git.launchpad.net/reporoot")},
   342  				maybeGitSource{url: mkurl("ssh://git.launchpad.net/reporoot")},
   343  				maybeGitSource{url: mkurl("git://git.launchpad.net/reporoot")},
   344  				maybeGitSource{url: mkurl("http://git.launchpad.net/reporoot")},
   345  			},
   346  		},
   347  		{
   348  			in:   "git.launchpad.net/reporoot/foo/bar",
   349  			root: "git.launchpad.net/reporoot",
   350  			mb: maybeSources{
   351  				maybeGitSource{url: mkurl("https://git.launchpad.net/reporoot")},
   352  				maybeGitSource{url: mkurl("ssh://git.launchpad.net/reporoot")},
   353  				maybeGitSource{url: mkurl("git://git.launchpad.net/reporoot")},
   354  				maybeGitSource{url: mkurl("http://git.launchpad.net/reporoot")},
   355  			},
   356  		},
   357  		{
   358  			in:   "git.launchpad.net/repo root",
   359  			rerr: errors.New("git.launchpad.net/repo root is not a valid path for a source on launchpad.net"),
   360  		},
   361  	},
   362  	"apache": []pathDeductionFixture{
   363  		{
   364  			in:   "git.apache.org/package-name.git",
   365  			root: "git.apache.org/package-name.git",
   366  			mb: maybeSources{
   367  				maybeGitSource{url: mkurl("https://git.apache.org/package-name.git")},
   368  				maybeGitSource{url: mkurl("ssh://git.apache.org/package-name.git")},
   369  				maybeGitSource{url: mkurl("git://git.apache.org/package-name.git")},
   370  				maybeGitSource{url: mkurl("http://git.apache.org/package-name.git")},
   371  			},
   372  		},
   373  		{
   374  			in:   "git.apache.org/package-name.git/foo/bar",
   375  			root: "git.apache.org/package-name.git",
   376  			mb: maybeSources{
   377  				maybeGitSource{url: mkurl("https://git.apache.org/package-name.git")},
   378  				maybeGitSource{url: mkurl("ssh://git.apache.org/package-name.git")},
   379  				maybeGitSource{url: mkurl("git://git.apache.org/package-name.git")},
   380  				maybeGitSource{url: mkurl("http://git.apache.org/package-name.git")},
   381  			},
   382  		},
   383  	},
   384  	"vcsext": []pathDeductionFixture{
   385  		// VCS extension-based syntax
   386  		{
   387  			in:   "foobar.com/baz.git",
   388  			root: "foobar.com/baz.git",
   389  			mb: maybeSources{
   390  				maybeGitSource{url: mkurl("https://foobar.com/baz.git")},
   391  				maybeGitSource{url: mkurl("ssh://foobar.com/baz.git")},
   392  				maybeGitSource{url: mkurl("git://foobar.com/baz.git")},
   393  				maybeGitSource{url: mkurl("http://foobar.com/baz.git")},
   394  			},
   395  		},
   396  		{
   397  			in:   "foobar.com/baz.git/extra/path",
   398  			root: "foobar.com/baz.git",
   399  			mb: maybeSources{
   400  				maybeGitSource{url: mkurl("https://foobar.com/baz.git")},
   401  				maybeGitSource{url: mkurl("ssh://foobar.com/baz.git")},
   402  				maybeGitSource{url: mkurl("git://foobar.com/baz.git")},
   403  				maybeGitSource{url: mkurl("http://foobar.com/baz.git")},
   404  			},
   405  		},
   406  		{
   407  			in:   "foobar.com/baz.bzr",
   408  			root: "foobar.com/baz.bzr",
   409  			mb: maybeSources{
   410  				maybeBzrSource{url: mkurl("https://foobar.com/baz.bzr")},
   411  				maybeBzrSource{url: mkurl("bzr+ssh://foobar.com/baz.bzr")},
   412  				maybeBzrSource{url: mkurl("bzr://foobar.com/baz.bzr")},
   413  				maybeBzrSource{url: mkurl("http://foobar.com/baz.bzr")},
   414  			},
   415  		},
   416  		{
   417  			in:   "foo-bar.com/baz.hg",
   418  			root: "foo-bar.com/baz.hg",
   419  			mb: maybeSources{
   420  				maybeHgSource{url: mkurl("https://foo-bar.com/baz.hg")},
   421  				maybeHgSource{url: mkurl("ssh://foo-bar.com/baz.hg")},
   422  				maybeHgSource{url: mkurl("http://foo-bar.com/baz.hg")},
   423  			},
   424  		},
   425  		{
   426  			in:   "git@foobar.com:baz.git",
   427  			root: "foobar.com/baz.git",
   428  			mb:   maybeGitSource{url: mkurl("ssh://git@foobar.com/baz.git")},
   429  		},
   430  		{
   431  			in:   "bzr+ssh://foobar.com/baz.bzr",
   432  			root: "foobar.com/baz.bzr",
   433  			mb:   maybeBzrSource{url: mkurl("bzr+ssh://foobar.com/baz.bzr")},
   434  		},
   435  		{
   436  			in:   "ssh://foobar.com/baz.bzr",
   437  			root: "foobar.com/baz.bzr",
   438  			mb:   maybeBzrSource{url: mkurl("ssh://foobar.com/baz.bzr")},
   439  		},
   440  		{
   441  			in:   "https://foobar.com/baz.hg",
   442  			root: "foobar.com/baz.hg",
   443  			mb:   maybeHgSource{url: mkurl("https://foobar.com/baz.hg")},
   444  		},
   445  		{
   446  			in:     "git://foobar.com/baz.hg",
   447  			root:   "foobar.com/baz.hg",
   448  			srcerr: errors.New("git is not a valid scheme for accessing hg repositories (path foobar.com/baz.hg)"),
   449  		},
   450  		// who knows why anyone would do this, but having a second vcs ext
   451  		// shouldn't throw us off - only the first one counts
   452  		{
   453  			in:   "foobar.com/baz.git/quark/quizzle.bzr/quorum",
   454  			root: "foobar.com/baz.git",
   455  			mb: maybeSources{
   456  				maybeGitSource{url: mkurl("https://foobar.com/baz.git")},
   457  				maybeGitSource{url: mkurl("ssh://foobar.com/baz.git")},
   458  				maybeGitSource{url: mkurl("git://foobar.com/baz.git")},
   459  				maybeGitSource{url: mkurl("http://foobar.com/baz.git")},
   460  			},
   461  		},
   462  	},
   463  	"vanity": []pathDeductionFixture{
   464  		// Vanity imports
   465  		{
   466  			in:   "golang.org/x/exp",
   467  			root: "golang.org/x/exp",
   468  			mb:   maybeGitSource{url: mkurl("https://go.googlesource.com/exp")},
   469  		},
   470  		{
   471  			in:   "golang.org/x/exp/inotify",
   472  			root: "golang.org/x/exp",
   473  			mb:   maybeGitSource{url: mkurl("https://go.googlesource.com/exp")},
   474  		},
   475  		{
   476  			in:   "golang.org/x/net/html",
   477  			root: "golang.org/x/net",
   478  			mb:   maybeGitSource{url: mkurl("https://go.googlesource.com/net")},
   479  		},
   480  	},
   481  }
   482  
   483  func TestDeduceFromPath(t *testing.T) {
   484  	do := func(typ string, fixtures []pathDeductionFixture, t *testing.T) {
   485  		t.Run(typ, func(t *testing.T) {
   486  			t.Parallel()
   487  
   488  			var deducer pathDeducer
   489  			switch typ {
   490  			case "github":
   491  				deducer = githubDeducer{regexp: ghRegex}
   492  			case "gopkg.in":
   493  				deducer = gopkginDeducer{regexp: gpinNewRegex}
   494  			case "jazz":
   495  				deducer = jazzDeducer{regexp: jazzRegex}
   496  			case "bitbucket":
   497  				deducer = bitbucketDeducer{regexp: bbRegex}
   498  			case "launchpad":
   499  				deducer = launchpadDeducer{regexp: lpRegex}
   500  			case "git.launchpad":
   501  				deducer = launchpadGitDeducer{regexp: glpRegex}
   502  			case "apache":
   503  				deducer = apacheDeducer{regexp: apacheRegex}
   504  			case "vcsext":
   505  				deducer = vcsExtensionDeducer{regexp: vcsExtensionRegex}
   506  			default:
   507  				// Should just be the vanity imports, which we do elsewhere
   508  				t.Log("skipping")
   509  				t.SkipNow()
   510  			}
   511  
   512  			var printmb func(mb maybeSource, t *testing.T) string
   513  			printmb = func(mb maybeSource, t *testing.T) string {
   514  				switch tmb := mb.(type) {
   515  				case maybeSources:
   516  					var buf bytes.Buffer
   517  					fmt.Fprintf(&buf, "%v maybeSources:", len(tmb))
   518  					for _, elem := range tmb {
   519  						fmt.Fprintf(&buf, "\n\t\t%s", printmb(elem, t))
   520  					}
   521  					return buf.String()
   522  				case maybeGitSource:
   523  					return fmt.Sprintf("%T: %s", tmb, ufmt(tmb.url))
   524  				case maybeBzrSource:
   525  					return fmt.Sprintf("%T: %s", tmb, ufmt(tmb.url))
   526  				case maybeHgSource:
   527  					return fmt.Sprintf("%T: %s", tmb, ufmt(tmb.url))
   528  				case maybeGopkginSource:
   529  					return fmt.Sprintf("%T: %s (v%v) %s ", tmb, tmb.opath, tmb.major, ufmt(tmb.url))
   530  				default:
   531  					t.Errorf("Unknown maybeSource type: %T", mb)
   532  				}
   533  				return ""
   534  			}
   535  
   536  			for _, fix := range fixtures {
   537  				fix := fix
   538  				t.Run(fix.in, func(t *testing.T) {
   539  					t.Parallel()
   540  					u, in, uerr := normalizeURI(fix.in)
   541  					if uerr != nil {
   542  						if fix.rerr == nil {
   543  							t.Errorf("bad input URI %s", uerr)
   544  						}
   545  						t.SkipNow()
   546  					}
   547  
   548  					root, rerr := deducer.deduceRoot(in)
   549  					if fix.rerr != nil {
   550  						if rerr == nil {
   551  							t.Errorf("Expected error on deducing root, got none:\n\t(WNT) %s", fix.rerr)
   552  						} else if fix.rerr.Error() != rerr.Error() {
   553  							t.Errorf("Got unexpected error on deducing root:\n\t(GOT) %s\n\t(WNT) %s", rerr, fix.rerr)
   554  						}
   555  					} else if rerr != nil {
   556  						t.Errorf("Got unexpected error on deducing root:\n\t(GOT) %s", rerr)
   557  					} else if root != fix.root {
   558  						t.Errorf("Deducer did not return expected root:\n\t(GOT) %s\n\t(WNT) %s", root, fix.root)
   559  					}
   560  
   561  					mb, mberr := deducer.deduceSource(in, u)
   562  					if fix.srcerr != nil {
   563  						if mberr == nil {
   564  							t.Errorf("Expected error on deducing source, got none:\n\t(WNT) %s", fix.srcerr)
   565  						} else if fix.srcerr.Error() != mberr.Error() {
   566  							t.Errorf("Got unexpected error on deducing source:\n\t(GOT) %s\n\t(WNT) %s", mberr, fix.srcerr)
   567  						}
   568  					} else if mberr != nil {
   569  						// don't complain the fix already expected an rerr
   570  						if fix.rerr == nil {
   571  							t.Errorf("Got unexpected error on deducing source:\n\t(GOT) %s", mberr)
   572  						}
   573  					} else if !reflect.DeepEqual(mb, fix.mb) {
   574  						if mb == nil {
   575  							t.Errorf("Deducer returned source maybes, but none expected:\n\t(GOT) (none)\n\t(WNT) %s", printmb(fix.mb, t))
   576  						} else if fix.mb == nil {
   577  							t.Errorf("Deducer returned source maybes, but none expected:\n\t(GOT) %s\n\t(WNT) (none)", printmb(mb, t))
   578  						} else {
   579  							t.Errorf("Deducer did not return expected source:\n\t(GOT) %s\n\t(WNT) %s", printmb(mb, t), printmb(fix.mb, t))
   580  						}
   581  					}
   582  				})
   583  			}
   584  		})
   585  	}
   586  	for typ, fixtures := range pathDeductionFixtures {
   587  		typ, fixtures := typ, fixtures
   588  		t.Run("first", func(t *testing.T) {
   589  			do(typ, fixtures, t)
   590  		})
   591  	}
   592  
   593  	// Run the test set twice to ensure results are correct for both cached
   594  	// and uncached deductions.
   595  	for typ, fixtures := range pathDeductionFixtures {
   596  		typ, fixtures := typ, fixtures
   597  		t.Run("second", func(t *testing.T) {
   598  			do(typ, fixtures, t)
   599  		})
   600  	}
   601  }
   602  
   603  func TestVanityDeduction(t *testing.T) {
   604  	if testing.Short() {
   605  		t.Skip("Skipping slow test in short mode")
   606  	}
   607  
   608  	sm, clean := mkNaiveSM(t)
   609  	defer clean()
   610  
   611  	vanities := pathDeductionFixtures["vanity"]
   612  	// group to avoid sourcemanager cleanup
   613  	ctx := context.Background()
   614  	do := func(t *testing.T) {
   615  		for _, fix := range vanities {
   616  			fix := fix
   617  			t.Run(fmt.Sprintf("%s", fix.in), func(t *testing.T) {
   618  				t.Parallel()
   619  
   620  				pr, err := sm.DeduceProjectRoot(fix.in)
   621  				if err != nil {
   622  					t.Errorf("Unexpected err on deducing project root: %s", err)
   623  					return
   624  				} else if string(pr) != fix.root {
   625  					t.Errorf("Deducer did not return expected root:\n\t(GOT) %s\n\t(WNT) %s", pr, fix.root)
   626  				}
   627  
   628  				pd, err := sm.deduceCoord.deduceRootPath(ctx, fix.in)
   629  				if err != nil {
   630  					t.Errorf("Unexpected err on deducing source: %s", err)
   631  					return
   632  				}
   633  
   634  				goturl, wanturl := pd.mb.(maybeGitSource).url.String(), fix.mb.(maybeGitSource).url.String()
   635  				if goturl != wanturl {
   636  					t.Errorf("Deduced repo ident does not match fixture:\n\t(GOT) %s\n\t(WNT) %s", goturl, wanturl)
   637  				}
   638  			})
   639  		}
   640  	}
   641  
   642  	// Run twice, to ensure correctness of cache
   643  	t.Run("first", do)
   644  	t.Run("second", do)
   645  }
   646  
   647  func TestVanityDeductionSchemeMismatch(t *testing.T) {
   648  	if testing.Short() {
   649  		t.Skip("Skipping slow test in short mode")
   650  	}
   651  
   652  	ctx := context.Background()
   653  	cm := newSupervisor(ctx)
   654  	dc := newDeductionCoordinator(cm)
   655  	_, err := dc.deduceRootPath(ctx, "ssh://golang.org/exp")
   656  	if err == nil {
   657  		t.Error("should have errored on scheme mismatch between input and go-get metadata")
   658  	}
   659  }
   660  
   661  // borrow from stdlib
   662  // more useful string for debugging than fmt's struct printer
   663  func ufmt(u *url.URL) string {
   664  	var user, pass interface{}
   665  	if u.User != nil {
   666  		user = u.User.Username()
   667  		if p, ok := u.User.Password(); ok {
   668  			pass = p
   669  		}
   670  	}
   671  	return fmt.Sprintf("host=%q, path=%q, opaque=%q, scheme=%q, user=%#v, pass=%#v, rawpath=%q, rawq=%q, frag=%q",
   672  		u.Host, u.Path, u.Opaque, u.Scheme, user, pass, u.RawPath, u.RawQuery, u.Fragment)
   673  }