...
▽

Source file src/cmd/go/internal/vcs/vcs_test.go

Documentation: cmd/go/internal/vcs

     1  // Copyright 2014 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 vcs
     6  
     7  import (
     8  	"errors"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  	"testing"
    15  
    16  	"cmd/go/internal/web"
    17  	"cmd/go/internal/web/intercept"
    18  )
    19  
    20  func init() {
    21  	// GOVCS defaults to public:git|hg,private:all,
    22  	// which breaks many tests here - they can't use non-git, non-hg VCS at all!
    23  	// Change to fully permissive.
    24  	// The tests of the GOVCS setting itself are in ../../testdata/script/govcs.txt.
    25  	os.Setenv("GOVCS", "*:all")
    26  }
    27  
    28  // Test that RepoRootForImportPath determines the correct RepoRoot for a given importPath.
    29  // TODO(cmang): Add tests for SVN.
    30  func TestRepoRootForImportPath(t *testing.T) {
    31  	// Intercept requests to external servers to avoid making external network connections from the test.
    32  	ts := httptest.NewServer(http.NotFoundHandler())
    33  	t.Cleanup(ts.Close)
    34  	var interceptors []intercept.Interceptor
    35  	for _, d := range []string{"hub.jazz.net", "hubajazz.net", "hub2.jazz.net", "different.example.com"} {
    36  		interceptors = append(interceptors,
    37  			intercept.Interceptor{Scheme: "https", FromHost: d, ToHost: ts.Listener.Addr().String()},
    38  			intercept.Interceptor{Scheme: "http", FromHost: d, ToHost: ts.Listener.Addr().String()},
    39  		)
    40  	}
    41  	intercept.EnableTestHooks(interceptors)
    42  	t.Cleanup(intercept.DisableTestHooks)
    43  
    44  	tests := []struct {
    45  		path string
    46  		want *RepoRoot
    47  	}{
    48  		{
    49  			"github.com/golang/groupcache",
    50  			&RepoRoot{
    51  				VCS:  vcsGit,
    52  				Repo: "https://github.com/golang/groupcache",
    53  			},
    54  		},
    55  		// Unicode letters in directories are not valid.
    56  		{
    57  			"github.com/user/unicode/испытание",
    58  			nil,
    59  		},
    60  		// IBM DevOps Services tests
    61  		{
    62  			"hub.jazz.net/git/user1/pkgname",
    63  			&RepoRoot{
    64  				VCS:  vcsGit,
    65  				Repo: "https://hub.jazz.net/git/user1/pkgname",
    66  			},
    67  		},
    68  		{
    69  			"hub.jazz.net/git/user1/pkgname/submodule/submodule/submodule",
    70  			&RepoRoot{
    71  				VCS:  vcsGit,
    72  				Repo: "https://hub.jazz.net/git/user1/pkgname",
    73  			},
    74  		},
    75  		{
    76  			"hub.jazz.net",
    77  			nil,
    78  		},
    79  		{
    80  			"hubajazz.net",
    81  			nil,
    82  		},
    83  		{
    84  			"hub2.jazz.net",
    85  			nil,
    86  		},
    87  		{
    88  			"hub.jazz.net/someotherprefix",
    89  			nil,
    90  		},
    91  		{
    92  			"hub.jazz.net/someotherprefix/user1/pkgname",
    93  			nil,
    94  		},
    95  		// Spaces are not valid in user names or package names
    96  		{
    97  			"hub.jazz.net/git/User 1/pkgname",
    98  			nil,
    99  		},
   100  		{
   101  			"hub.jazz.net/git/user1/pkg name",
   102  			nil,
   103  		},
   104  		// Dots are not valid in user names
   105  		{
   106  			"hub.jazz.net/git/user.1/pkgname",
   107  			nil,
   108  		},
   109  		{
   110  			"hub.jazz.net/git/user/pkg.name",
   111  			&RepoRoot{
   112  				VCS:  vcsGit,
   113  				Repo: "https://hub.jazz.net/git/user/pkg.name",
   114  			},
   115  		},
   116  		// User names cannot have uppercase letters
   117  		{
   118  			"hub.jazz.net/git/USER/pkgname",
   119  			nil,
   120  		},
   121  		// OpenStack tests
   122  		{
   123  			"git.openstack.org/openstack/swift",
   124  			&RepoRoot{
   125  				VCS:  vcsGit,
   126  				Repo: "https://git.openstack.org/openstack/swift",
   127  			},
   128  		},
   129  		// Trailing .git is less preferred but included for
   130  		// compatibility purposes while the same source needs to
   131  		// be compilable on both old and new go
   132  		{
   133  			"git.openstack.org/openstack/swift.git",
   134  			&RepoRoot{
   135  				VCS:  vcsGit,
   136  				Repo: "https://git.openstack.org/openstack/swift.git",
   137  			},
   138  		},
   139  		{
   140  			"git.openstack.org/openstack/swift/go/hummingbird",
   141  			&RepoRoot{
   142  				VCS:  vcsGit,
   143  				Repo: "https://git.openstack.org/openstack/swift",
   144  			},
   145  		},
   146  		{
   147  			"git.openstack.org",
   148  			nil,
   149  		},
   150  		{
   151  			"git.openstack.org/openstack",
   152  			nil,
   153  		},
   154  		// Spaces are not valid in package name
   155  		{
   156  			"git.apache.org/package name/path/to/lib",
   157  			nil,
   158  		},
   159  		// Should have ".git" suffix
   160  		{
   161  			"git.apache.org/package-name/path/to/lib",
   162  			nil,
   163  		},
   164  		{
   165  			"gitbapache.org",
   166  			nil,
   167  		},
   168  		{
   169  			"git.apache.org/package-name.git",
   170  			&RepoRoot{
   171  				VCS:  vcsGit,
   172  				Repo: "https://git.apache.org/package-name.git",
   173  			},
   174  		},
   175  		{
   176  			"git.apache.org/package-name_2.x.git/path/to/lib",
   177  			&RepoRoot{
   178  				VCS:  vcsGit,
   179  				Repo: "https://git.apache.org/package-name_2.x.git",
   180  			},
   181  		},
   182  		{
   183  			"chiselapp.com/user/kyle/repository/fossilgg",
   184  			&RepoRoot{
   185  				VCS:  vcsFossil,
   186  				Repo: "https://chiselapp.com/user/kyle/repository/fossilgg",
   187  			},
   188  		},
   189  		{
   190  			// must have a user/$name/repository/$repo path
   191  			"chiselapp.com/kyle/repository/fossilgg",
   192  			nil,
   193  		},
   194  		{
   195  			"chiselapp.com/user/kyle/fossilgg",
   196  			nil,
   197  		},
   198  		{
   199  			"bitbucket.org/workspace/pkgname",
   200  			&RepoRoot{
   201  				VCS:  vcsGit,
   202  				Repo: "https://bitbucket.org/workspace/pkgname",
   203  			},
   204  		},
   205  	}
   206  
   207  	for _, test := range tests {
   208  		got, err := RepoRootForImportPath(test.path, IgnoreMod, web.SecureOnly)
   209  		want := test.want
   210  
   211  		if want == nil {
   212  			if err == nil {
   213  				t.Errorf("RepoRootForImportPath(%q): Error expected but not received", test.path)
   214  			}
   215  			continue
   216  		}
   217  		if err != nil {
   218  			t.Errorf("RepoRootForImportPath(%q): %v", test.path, err)
   219  			continue
   220  		}
   221  		if got.VCS.Name != want.VCS.Name || got.Repo != want.Repo {
   222  			t.Errorf("RepoRootForImportPath(%q) = VCS(%s) Repo(%s), want VCS(%s) Repo(%s)", test.path, got.VCS, got.Repo, want.VCS, want.Repo)
   223  		}
   224  	}
   225  }
   226  
   227  // Test that vcs.FromDir correctly inspects a given directory and returns the
   228  // right VCS and repo directory.
   229  func TestFromDir(t *testing.T) {
   230  	tests := []struct {
   231  		name   string
   232  		vcs    string
   233  		root   string
   234  		create func(path string) error
   235  	}{
   236  		{"hg", "Mercurial", ".hg", mkdir},
   237  		{"git_dir", "Git", ".git", mkdir},
   238  		{"git_worktree", "Git", ".git", createGitWorktreeFile},
   239  		{"svn", "Subversion", ".svn", mkdir},
   240  		{"fossil_fslckout", "Fossil", ".fslckout", touch},
   241  		{"fossil_FOSSIL_", "Fossil", "_FOSSIL_", touch},
   242  	}
   243  
   244  	for _, tt := range tests {
   245  		t.Run(tt.name, func(t *testing.T) {
   246  			tempDir := t.TempDir()
   247  			repoDir := filepath.Join(tempDir, "example.com")
   248  			if err := mkdir(repoDir); err != nil {
   249  				t.Fatal(err)
   250  			}
   251  			rootPath := filepath.Join(repoDir, tt.root)
   252  			if err := tt.create(rootPath); err != nil {
   253  				t.Fatal(err)
   254  			}
   255  			gotRepoDir, gotVCS, err := FromDir(repoDir, tempDir)
   256  			if err != nil {
   257  				t.Fatal(err)
   258  			}
   259  			if gotRepoDir != repoDir {
   260  				t.Errorf("RepoDir = %q, want %q", gotRepoDir, repoDir)
   261  			}
   262  			if gotVCS.Name != tt.vcs {
   263  				t.Errorf("VCS = %q, want %q", gotVCS.Name, tt.vcs)
   264  			}
   265  		})
   266  	}
   267  }
   268  
   269  func mkdir(path string) error {
   270  	return os.Mkdir(path, 0o755)
   271  }
   272  
   273  func touch(path string) error {
   274  	return os.WriteFile(path, nil, 0o644)
   275  }
   276  
   277  func createGitWorktreeFile(path string) error {
   278  	gitdir := path + ".worktree"
   279  	// gitdir must point to a real directory
   280  	if err := mkdir(gitdir); err != nil {
   281  		return err
   282  	}
   283  	return os.WriteFile(path, []byte("gitdir: "+gitdir+"\n"), 0o644)
   284  }
   285  
   286  func TestIsSecure(t *testing.T) {
   287  	tests := []struct {
   288  		vcs    *Cmd
   289  		url    string
   290  		secure bool
   291  	}{
   292  		{vcsGit, "http://example.com/foo.git", false},
   293  		{vcsGit, "https://example.com/foo.git", true},
   294  		{vcsSvn, "http://example.com/svn", false},
   295  		{vcsSvn, "https://example.com/svn", true},
   296  		{vcsHg, "http://example.com/foo.hg", false},
   297  		{vcsHg, "https://example.com/foo.hg", true},
   298  		{vcsGit, "ssh://user@example.com/foo.git", true},
   299  		{vcsGit, "user@server:path/to/repo.git", false},
   300  		{vcsGit, "user@server:", false},
   301  		{vcsGit, "server:repo.git", false},
   302  		{vcsGit, "server:path/to/repo.git", false},
   303  		{vcsGit, "example.com:path/to/repo.git", false},
   304  		{vcsGit, "path/that/contains/a:colon/repo.git", false},
   305  		{vcsHg, "ssh://user@example.com/path/to/repo.hg", true},
   306  		{vcsFossil, "http://example.com/foo", false},
   307  		{vcsFossil, "https://example.com/foo", true},
   308  	}
   309  
   310  	for _, test := range tests {
   311  		secure := test.vcs.IsSecure(test.url)
   312  		if secure != test.secure {
   313  			t.Errorf("%s isSecure(%q) = %t; want %t", test.vcs, test.url, secure, test.secure)
   314  		}
   315  	}
   316  }
   317  
   318  func TestIsSecureGitAllowProtocol(t *testing.T) {
   319  	tests := []struct {
   320  		vcs    *Cmd
   321  		url    string
   322  		secure bool
   323  	}{
   324  		// Same as TestIsSecure to verify same behavior.
   325  		{vcsGit, "http://example.com/foo.git", false},
   326  		{vcsGit, "https://example.com/foo.git", true},
   327  		{vcsSvn, "http://example.com/svn", false},
   328  		{vcsSvn, "https://example.com/svn", true},
   329  		{vcsHg, "http://example.com/foo.hg", false},
   330  		{vcsHg, "https://example.com/foo.hg", true},
   331  		{vcsGit, "user@server:path/to/repo.git", false},
   332  		{vcsGit, "user@server:", false},
   333  		{vcsGit, "server:repo.git", false},
   334  		{vcsGit, "server:path/to/repo.git", false},
   335  		{vcsGit, "example.com:path/to/repo.git", false},
   336  		{vcsGit, "path/that/contains/a:colon/repo.git", false},
   337  		{vcsHg, "ssh://user@example.com/path/to/repo.hg", true},
   338  		// New behavior.
   339  		{vcsGit, "ssh://user@example.com/foo.git", false},
   340  		{vcsGit, "foo://example.com/bar.git", true},
   341  		{vcsHg, "foo://example.com/bar.hg", false},
   342  		{vcsSvn, "foo://example.com/svn", false},
   343  	}
   344  
   345  	defer os.Unsetenv("GIT_ALLOW_PROTOCOL")
   346  	os.Setenv("GIT_ALLOW_PROTOCOL", "https:foo")
   347  	for _, test := range tests {
   348  		secure := test.vcs.IsSecure(test.url)
   349  		if secure != test.secure {
   350  			t.Errorf("%s isSecure(%q) = %t; want %t", test.vcs, test.url, secure, test.secure)
   351  		}
   352  	}
   353  }
   354  
   355  func TestMatchGoImport(t *testing.T) {
   356  	tests := []struct {
   357  		imports []metaImport
   358  		path    string
   359  		mi      metaImport
   360  		err     error
   361  	}{
   362  		{
   363  			imports: []metaImport{
   364  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   365  			},
   366  			path: "example.com/user/foo",
   367  			mi:   metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   368  		},
   369  		{
   370  			imports: []metaImport{
   371  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   372  			},
   373  			path: "example.com/user/foo/",
   374  			mi:   metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   375  		},
   376  		{
   377  			imports: []metaImport{
   378  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   379  				{Prefix: "example.com/user/fooa", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   380  			},
   381  			path: "example.com/user/foo",
   382  			mi:   metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   383  		},
   384  		{
   385  			imports: []metaImport{
   386  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   387  				{Prefix: "example.com/user/fooa", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   388  			},
   389  			path: "example.com/user/fooa",
   390  			mi:   metaImport{Prefix: "example.com/user/fooa", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   391  		},
   392  		{
   393  			imports: []metaImport{
   394  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   395  				{Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   396  			},
   397  			path: "example.com/user/foo/bar",
   398  			err:  errors.New("should not be allowed to create nested repo"),
   399  		},
   400  		{
   401  			imports: []metaImport{
   402  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   403  				{Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   404  			},
   405  			path: "example.com/user/foo/bar/baz",
   406  			err:  errors.New("should not be allowed to create nested repo"),
   407  		},
   408  		{
   409  			imports: []metaImport{
   410  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   411  				{Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   412  			},
   413  			path: "example.com/user/foo/bar/baz/qux",
   414  			err:  errors.New("should not be allowed to create nested repo"),
   415  		},
   416  		{
   417  			imports: []metaImport{
   418  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   419  				{Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   420  			},
   421  			path: "example.com/user/foo/bar/baz/",
   422  			err:  errors.New("should not be allowed to create nested repo"),
   423  		},
   424  		{
   425  			imports: []metaImport{
   426  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   427  				{Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   428  			},
   429  			path: "example.com",
   430  			err:  errors.New("pathologically short path"),
   431  		},
   432  		{
   433  			imports: []metaImport{
   434  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   435  			},
   436  			path: "different.example.com/user/foo",
   437  			err:  errors.New("meta tags do not match import path"),
   438  		},
   439  		{
   440  			imports: []metaImport{
   441  				{Prefix: "myitcv.io/blah2", VCS: "mod", RepoRoot: "https://raw.githubusercontent.com/myitcv/pubx/master"},
   442  				{Prefix: "myitcv.io", VCS: "git", RepoRoot: "https://github.com/myitcv/x"},
   443  			},
   444  			path: "myitcv.io/blah2/foo",
   445  			mi:   metaImport{Prefix: "myitcv.io/blah2", VCS: "mod", RepoRoot: "https://raw.githubusercontent.com/myitcv/pubx/master"},
   446  		},
   447  		{
   448  			imports: []metaImport{
   449  				{Prefix: "myitcv.io/blah2", VCS: "mod", RepoRoot: "https://raw.githubusercontent.com/myitcv/pubx/master"},
   450  				{Prefix: "myitcv.io", VCS: "git", RepoRoot: "https://github.com/myitcv/x"},
   451  			},
   452  			path: "myitcv.io/other",
   453  			mi:   metaImport{Prefix: "myitcv.io", VCS: "git", RepoRoot: "https://github.com/myitcv/x"},
   454  		},
   455  		{
   456  			imports: []metaImport{
   457  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target", SubDir: "subdir"},
   458  			},
   459  			path: "example.com/user/foo",
   460  			mi:   metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target", SubDir: "subdir"},
   461  		},
   462  		{
   463  			imports: []metaImport{
   464  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target", SubDir: "foo/subdir"},
   465  			},
   466  			path: "example.com/user/foo",
   467  			mi:   metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target", SubDir: "foo/subdir"},
   468  		},
   469  		{
   470  			imports: []metaImport{
   471  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target", SubDir: "subdir"},
   472  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target", SubDir: ""},
   473  			},
   474  			path: "example.com/user/foo",
   475  			err:  errors.New("multiple meta tags match import path"),
   476  		},
   477  	}
   478  
   479  	for _, test := range tests {
   480  		mi, err := matchGoImport(test.imports, test.path)
   481  		if mi != test.mi {
   482  			t.Errorf("unexpected metaImport; got %v, want %v", mi, test.mi)
   483  		}
   484  
   485  		got := err
   486  		want := test.err
   487  		if (got == nil) != (want == nil) {
   488  			t.Errorf("unexpected error; got %v, want %v", got, want)
   489  		}
   490  	}
   491  }
   492  
   493  func TestValidateRepoRoot(t *testing.T) {
   494  	tests := []struct {
   495  		root string
   496  		ok   bool
   497  	}{
   498  		{
   499  			root: "",
   500  			ok:   false,
   501  		},
   502  		{
   503  			root: "http://",
   504  			ok:   true,
   505  		},
   506  		{
   507  			root: "git+ssh://",
   508  			ok:   true,
   509  		},
   510  		{
   511  			root: "http#://",
   512  			ok:   false,
   513  		},
   514  		{
   515  			root: "-config",
   516  			ok:   false,
   517  		},
   518  		{
   519  			root: "-config://",
   520  			ok:   false,
   521  		},
   522  	}
   523  
   524  	for _, test := range tests {
   525  		err := validateRepoRoot(test.root)
   526  		ok := err == nil
   527  		if ok != test.ok {
   528  			want := "error"
   529  			if test.ok {
   530  				want = "nil"
   531  			}
   532  			t.Errorf("validateRepoRoot(%q) = %q, want %s", test.root, err, want)
   533  		}
   534  	}
   535  }
   536  
   537  func TestValidateRepoSubDir(t *testing.T) {
   538  	tests := []struct {
   539  		subdir string
   540  		ok     bool
   541  	}{
   542  		{
   543  			subdir: "",
   544  			ok:     true,
   545  		},
   546  		{
   547  			subdir: "sub/dir",
   548  			ok:     true,
   549  		},
   550  		{
   551  			subdir: "/leading/slash",
   552  			ok:     false,
   553  		},
   554  		{
   555  			subdir: "-leading/hyphen",
   556  			ok:     false,
   557  		},
   558  	}
   559  
   560  	for _, test := range tests {
   561  		err := validateRepoSubDir(test.subdir)
   562  		ok := err == nil
   563  		if ok != test.ok {
   564  			want := "error"
   565  			if test.ok {
   566  				want = "nil"
   567  			}
   568  			t.Errorf("validateRepoSubDir(%q) = %q, want %s", test.subdir, err, want)
   569  		}
   570  	}
   571  }
   572  
   573  var govcsTests = []struct {
   574  	govcs string
   575  	path  string
   576  	vcs   string
   577  	ok    bool
   578  }{
   579  	{"private:all", "is-public.com/foo", "zzz", false},
   580  	{"private:all", "is-private.com/foo", "zzz", true},
   581  	{"public:all", "is-public.com/foo", "zzz", true},
   582  	{"public:all", "is-private.com/foo", "zzz", false},
   583  	{"public:all,private:none", "is-public.com/foo", "zzz", true},
   584  	{"public:all,private:none", "is-private.com/foo", "zzz", false},
   585  	{"*:all", "is-public.com/foo", "zzz", true},
   586  	{"golang.org:git", "golang.org/x/text", "zzz", false},
   587  	{"golang.org:git", "golang.org/x/text", "git", true},
   588  	{"golang.org:zzz", "golang.org/x/text", "zzz", true},
   589  	{"golang.org:zzz", "golang.org/x/text", "git", false},
   590  	{"golang.org:zzz", "golang.org/x/text", "zzz", true},
   591  	{"golang.org:zzz", "golang.org/x/text", "git", false},
   592  	{"golang.org:git|hg", "golang.org/x/text", "hg", true},
   593  	{"golang.org:git|hg", "golang.org/x/text", "git", true},
   594  	{"golang.org:git|hg", "golang.org/x/text", "zzz", false},
   595  	{"golang.org:all", "golang.org/x/text", "hg", true},
   596  	{"golang.org:all", "golang.org/x/text", "git", true},
   597  	{"golang.org:all", "golang.org/x/text", "zzz", true},
   598  	{"other.xyz/p:none,golang.org/x:git", "other.xyz/p/x", "git", false},
   599  	{"other.xyz/p:none,golang.org/x:git", "unexpected.com", "git", false},
   600  	{"other.xyz/p:none,golang.org/x:git", "golang.org/x/text", "zzz", false},
   601  	{"other.xyz/p:none,golang.org/x:git", "golang.org/x/text", "git", true},
   602  	{"other.xyz/p:none,golang.org/x:zzz", "golang.org/x/text", "zzz", true},
   603  	{"other.xyz/p:none,golang.org/x:zzz", "golang.org/x/text", "git", false},
   604  	{"other.xyz/p:none,golang.org/x:git|hg", "golang.org/x/text", "hg", true},
   605  	{"other.xyz/p:none,golang.org/x:git|hg", "golang.org/x/text", "git", true},
   606  	{"other.xyz/p:none,golang.org/x:git|hg", "golang.org/x/text", "zzz", false},
   607  	{"other.xyz/p:none,golang.org/x:all", "golang.org/x/text", "hg", true},
   608  	{"other.xyz/p:none,golang.org/x:all", "golang.org/x/text", "git", true},
   609  	{"other.xyz/p:none,golang.org/x:all", "golang.org/x/text", "zzz", true},
   610  	{"other.xyz/p:none,golang.org/x:git", "golang.org/y/text", "zzz", false},
   611  	{"other.xyz/p:none,golang.org/x:git", "golang.org/y/text", "git", false},
   612  	{"other.xyz/p:none,golang.org/x:zzz", "golang.org/y/text", "zzz", false},
   613  	{"other.xyz/p:none,golang.org/x:zzz", "golang.org/y/text", "git", false},
   614  	{"other.xyz/p:none,golang.org/x:git|hg", "golang.org/y/text", "hg", false},
   615  	{"other.xyz/p:none,golang.org/x:git|hg", "golang.org/y/text", "git", false},
   616  	{"other.xyz/p:none,golang.org/x:git|hg", "golang.org/y/text", "zzz", false},
   617  	{"other.xyz/p:none,golang.org/x:all", "golang.org/y/text", "hg", false},
   618  	{"other.xyz/p:none,golang.org/x:all", "golang.org/y/text", "git", false},
   619  	{"other.xyz/p:none,golang.org/x:all", "golang.org/y/text", "zzz", false},
   620  }
   621  
   622  func TestGOVCS(t *testing.T) {
   623  	for _, tt := range govcsTests {
   624  		cfg, err := parseGOVCS(tt.govcs)
   625  		if err != nil {
   626  			t.Errorf("parseGOVCS(%q): %v", tt.govcs, err)
   627  			continue
   628  		}
   629  		private := strings.HasPrefix(tt.path, "is-private")
   630  		ok := cfg.allow(tt.path, private, tt.vcs)
   631  		if ok != tt.ok {
   632  			t.Errorf("parseGOVCS(%q).allow(%q, %v, %q) = %v, want %v",
   633  				tt.govcs, tt.path, private, tt.vcs, ok, tt.ok)
   634  		}
   635  	}
   636  }
   637  
   638  var govcsErrors = []struct {
   639  	s   string
   640  	err string
   641  }{
   642  	{`,`, `empty entry in GOVCS`},
   643  	{`,x`, `empty entry in GOVCS`},
   644  	{`x,`, `malformed entry in GOVCS (missing colon): "x"`},
   645  	{`x:y,`, `empty entry in GOVCS`},
   646  	{`x`, `malformed entry in GOVCS (missing colon): "x"`},
   647  	{`x:`, `empty VCS list in GOVCS: "x:"`},
   648  	{`x:|`, `empty VCS name in GOVCS: "x:|"`},
   649  	{`x:y|`, `empty VCS name in GOVCS: "x:y|"`},
   650  	{`x:|y`, `empty VCS name in GOVCS: "x:|y"`},
   651  	{`x:y,z:`, `empty VCS list in GOVCS: "z:"`},
   652  	{`x:y,z:|`, `empty VCS name in GOVCS: "z:|"`},
   653  	{`x:y,z:|w`, `empty VCS name in GOVCS: "z:|w"`},
   654  	{`x:y,z:w|`, `empty VCS name in GOVCS: "z:w|"`},
   655  	{`x:y,z:w||v`, `empty VCS name in GOVCS: "z:w||v"`},
   656  	{`x:y,x:z`, `unreachable pattern in GOVCS: "x:z" after "x:y"`},
   657  }
   658  
   659  func TestGOVCSErrors(t *testing.T) {
   660  	for _, tt := range govcsErrors {
   661  		_, err := parseGOVCS(tt.s)
   662  		if err == nil || !strings.Contains(err.Error(), tt.err) {
   663  			t.Errorf("parseGOVCS(%s): err=%v, want %v", tt.s, err, tt.err)
   664  		}
   665  	}
   666  }
   667  

View as plain text