github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/go/vcs/vcs_test.go (about)

     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  	"fmt"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/go-asm/go/testenv"
    16  
    17  	"github.com/go-asm/go/cmd/go/web"
    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 and BZR.
    30  func TestRepoRootForImportPath(t *testing.T) {
    31  	testenv.MustHaveExternalNetwork(t)
    32  
    33  	tests := []struct {
    34  		path string
    35  		want *RepoRoot
    36  	}{
    37  		{
    38  			"github.com/golang/groupcache",
    39  			&RepoRoot{
    40  				VCS:  vcsGit,
    41  				Repo: "https://github.com/golang/groupcache",
    42  			},
    43  		},
    44  		// Unicode letters in directories are not valid.
    45  		{
    46  			"github.com/user/unicode/испытание",
    47  			nil,
    48  		},
    49  		// IBM DevOps Services tests
    50  		{
    51  			"hub.jazz.net/git/user1/pkgname",
    52  			&RepoRoot{
    53  				VCS:  vcsGit,
    54  				Repo: "https://hub.jazz.net/git/user1/pkgname",
    55  			},
    56  		},
    57  		{
    58  			"hub.jazz.net/git/user1/pkgname/submodule/submodule/submodule",
    59  			&RepoRoot{
    60  				VCS:  vcsGit,
    61  				Repo: "https://hub.jazz.net/git/user1/pkgname",
    62  			},
    63  		},
    64  		{
    65  			"hub.jazz.net",
    66  			nil,
    67  		},
    68  		{
    69  			"hubajazz.net",
    70  			nil,
    71  		},
    72  		{
    73  			"hub2.jazz.net",
    74  			nil,
    75  		},
    76  		{
    77  			"hub.jazz.net/someotherprefix",
    78  			nil,
    79  		},
    80  		{
    81  			"hub.jazz.net/someotherprefix/user1/pkgname",
    82  			nil,
    83  		},
    84  		// Spaces are not valid in user names or package names
    85  		{
    86  			"hub.jazz.net/git/User 1/pkgname",
    87  			nil,
    88  		},
    89  		{
    90  			"hub.jazz.net/git/user1/pkg name",
    91  			nil,
    92  		},
    93  		// Dots are not valid in user names
    94  		{
    95  			"hub.jazz.net/git/user.1/pkgname",
    96  			nil,
    97  		},
    98  		{
    99  			"hub.jazz.net/git/user/pkg.name",
   100  			&RepoRoot{
   101  				VCS:  vcsGit,
   102  				Repo: "https://hub.jazz.net/git/user/pkg.name",
   103  			},
   104  		},
   105  		// User names cannot have uppercase letters
   106  		{
   107  			"hub.jazz.net/git/USER/pkgname",
   108  			nil,
   109  		},
   110  		// OpenStack tests
   111  		{
   112  			"git.openstack.org/openstack/swift",
   113  			&RepoRoot{
   114  				VCS:  vcsGit,
   115  				Repo: "https://git.openstack.org/openstack/swift",
   116  			},
   117  		},
   118  		// Trailing .git is less preferred but included for
   119  		// compatibility purposes while the same source needs to
   120  		// be compilable on both old and new go
   121  		{
   122  			"git.openstack.org/openstack/swift.git",
   123  			&RepoRoot{
   124  				VCS:  vcsGit,
   125  				Repo: "https://git.openstack.org/openstack/swift.git",
   126  			},
   127  		},
   128  		{
   129  			"git.openstack.org/openstack/swift/go/hummingbird",
   130  			&RepoRoot{
   131  				VCS:  vcsGit,
   132  				Repo: "https://git.openstack.org/openstack/swift",
   133  			},
   134  		},
   135  		{
   136  			"git.openstack.org",
   137  			nil,
   138  		},
   139  		{
   140  			"git.openstack.org/openstack",
   141  			nil,
   142  		},
   143  		// Spaces are not valid in package name
   144  		{
   145  			"git.apache.org/package name/path/to/lib",
   146  			nil,
   147  		},
   148  		// Should have ".git" suffix
   149  		{
   150  			"git.apache.org/package-name/path/to/lib",
   151  			nil,
   152  		},
   153  		{
   154  			"gitbapache.org",
   155  			nil,
   156  		},
   157  		{
   158  			"git.apache.org/package-name.git",
   159  			&RepoRoot{
   160  				VCS:  vcsGit,
   161  				Repo: "https://git.apache.org/package-name.git",
   162  			},
   163  		},
   164  		{
   165  			"git.apache.org/package-name_2.x.git/path/to/lib",
   166  			&RepoRoot{
   167  				VCS:  vcsGit,
   168  				Repo: "https://git.apache.org/package-name_2.x.git",
   169  			},
   170  		},
   171  		{
   172  			"chiselapp.com/user/kyle/repository/fossilgg",
   173  			&RepoRoot{
   174  				VCS:  vcsFossil,
   175  				Repo: "https://chiselapp.com/user/kyle/repository/fossilgg",
   176  			},
   177  		},
   178  		{
   179  			// must have a user/$name/repository/$repo path
   180  			"chiselapp.com/kyle/repository/fossilgg",
   181  			nil,
   182  		},
   183  		{
   184  			"chiselapp.com/user/kyle/fossilgg",
   185  			nil,
   186  		},
   187  		{
   188  			"bitbucket.org/workspace/pkgname",
   189  			&RepoRoot{
   190  				VCS:  vcsGit,
   191  				Repo: "https://bitbucket.org/workspace/pkgname",
   192  			},
   193  		},
   194  	}
   195  
   196  	for _, test := range tests {
   197  		got, err := RepoRootForImportPath(test.path, IgnoreMod, web.SecureOnly)
   198  		want := test.want
   199  
   200  		if want == nil {
   201  			if err == nil {
   202  				t.Errorf("RepoRootForImportPath(%q): Error expected but not received", test.path)
   203  			}
   204  			continue
   205  		}
   206  		if err != nil {
   207  			t.Errorf("RepoRootForImportPath(%q): %v", test.path, err)
   208  			continue
   209  		}
   210  		if got.VCS.Name != want.VCS.Name || got.Repo != want.Repo {
   211  			t.Errorf("RepoRootForImportPath(%q) = VCS(%s) Repo(%s), want VCS(%s) Repo(%s)", test.path, got.VCS, got.Repo, want.VCS, want.Repo)
   212  		}
   213  	}
   214  }
   215  
   216  // Test that vcs.FromDir correctly inspects a given directory and returns the
   217  // right VCS and repo directory.
   218  func TestFromDir(t *testing.T) {
   219  	tempDir := t.TempDir()
   220  
   221  	for _, vcs := range vcsList {
   222  		for r, root := range vcs.RootNames {
   223  			vcsName := fmt.Sprint(vcs.Name, r)
   224  			dir := filepath.Join(tempDir, "example.com", vcsName, root.filename)
   225  			if root.isDir {
   226  				err := os.MkdirAll(dir, 0755)
   227  				if err != nil {
   228  					t.Fatal(err)
   229  				}
   230  			} else {
   231  				err := os.MkdirAll(filepath.Dir(dir), 0755)
   232  				if err != nil {
   233  					t.Fatal(err)
   234  				}
   235  				f, err := os.Create(dir)
   236  				if err != nil {
   237  					t.Fatal(err)
   238  				}
   239  				f.Close()
   240  			}
   241  
   242  			wantRepoDir := filepath.Dir(dir)
   243  			gotRepoDir, gotVCS, err := FromDir(dir, tempDir, false)
   244  			if err != nil {
   245  				t.Errorf("FromDir(%q, %q): %v", dir, tempDir, err)
   246  				continue
   247  			}
   248  			if gotRepoDir != wantRepoDir || gotVCS.Name != vcs.Name {
   249  				t.Errorf("FromDir(%q, %q) = RepoDir(%s), VCS(%s); want RepoDir(%s), VCS(%s)", dir, tempDir, gotRepoDir, gotVCS.Name, wantRepoDir, vcs.Name)
   250  			}
   251  		}
   252  	}
   253  }
   254  
   255  func TestIsSecure(t *testing.T) {
   256  	tests := []struct {
   257  		vcs    *Cmd
   258  		url    string
   259  		secure bool
   260  	}{
   261  		{vcsGit, "http://example.com/foo.git", false},
   262  		{vcsGit, "https://example.com/foo.git", true},
   263  		{vcsBzr, "http://example.com/foo.bzr", false},
   264  		{vcsBzr, "https://example.com/foo.bzr", true},
   265  		{vcsSvn, "http://example.com/svn", false},
   266  		{vcsSvn, "https://example.com/svn", true},
   267  		{vcsHg, "http://example.com/foo.hg", false},
   268  		{vcsHg, "https://example.com/foo.hg", true},
   269  		{vcsGit, "ssh://user@example.com/foo.git", true},
   270  		{vcsGit, "user@server:path/to/repo.git", false},
   271  		{vcsGit, "user@server:", false},
   272  		{vcsGit, "server:repo.git", false},
   273  		{vcsGit, "server:path/to/repo.git", false},
   274  		{vcsGit, "example.com:path/to/repo.git", false},
   275  		{vcsGit, "path/that/contains/a:colon/repo.git", false},
   276  		{vcsHg, "ssh://user@example.com/path/to/repo.hg", true},
   277  		{vcsFossil, "http://example.com/foo", false},
   278  		{vcsFossil, "https://example.com/foo", true},
   279  	}
   280  
   281  	for _, test := range tests {
   282  		secure := test.vcs.IsSecure(test.url)
   283  		if secure != test.secure {
   284  			t.Errorf("%s isSecure(%q) = %t; want %t", test.vcs, test.url, secure, test.secure)
   285  		}
   286  	}
   287  }
   288  
   289  func TestIsSecureGitAllowProtocol(t *testing.T) {
   290  	tests := []struct {
   291  		vcs    *Cmd
   292  		url    string
   293  		secure bool
   294  	}{
   295  		// Same as TestIsSecure to verify same behavior.
   296  		{vcsGit, "http://example.com/foo.git", false},
   297  		{vcsGit, "https://example.com/foo.git", true},
   298  		{vcsBzr, "http://example.com/foo.bzr", false},
   299  		{vcsBzr, "https://example.com/foo.bzr", true},
   300  		{vcsSvn, "http://example.com/svn", false},
   301  		{vcsSvn, "https://example.com/svn", true},
   302  		{vcsHg, "http://example.com/foo.hg", false},
   303  		{vcsHg, "https://example.com/foo.hg", true},
   304  		{vcsGit, "user@server:path/to/repo.git", false},
   305  		{vcsGit, "user@server:", false},
   306  		{vcsGit, "server:repo.git", false},
   307  		{vcsGit, "server:path/to/repo.git", false},
   308  		{vcsGit, "example.com:path/to/repo.git", false},
   309  		{vcsGit, "path/that/contains/a:colon/repo.git", false},
   310  		{vcsHg, "ssh://user@example.com/path/to/repo.hg", true},
   311  		// New behavior.
   312  		{vcsGit, "ssh://user@example.com/foo.git", false},
   313  		{vcsGit, "foo://example.com/bar.git", true},
   314  		{vcsHg, "foo://example.com/bar.hg", false},
   315  		{vcsSvn, "foo://example.com/svn", false},
   316  		{vcsBzr, "foo://example.com/bar.bzr", false},
   317  	}
   318  
   319  	defer os.Unsetenv("GIT_ALLOW_PROTOCOL")
   320  	os.Setenv("GIT_ALLOW_PROTOCOL", "https:foo")
   321  	for _, test := range tests {
   322  		secure := test.vcs.IsSecure(test.url)
   323  		if secure != test.secure {
   324  			t.Errorf("%s isSecure(%q) = %t; want %t", test.vcs, test.url, secure, test.secure)
   325  		}
   326  	}
   327  }
   328  
   329  func TestMatchGoImport(t *testing.T) {
   330  	tests := []struct {
   331  		imports []metaImport
   332  		path    string
   333  		mi      metaImport
   334  		err     error
   335  	}{
   336  		{
   337  			imports: []metaImport{
   338  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   339  			},
   340  			path: "example.com/user/foo",
   341  			mi:   metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   342  		},
   343  		{
   344  			imports: []metaImport{
   345  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   346  			},
   347  			path: "example.com/user/foo/",
   348  			mi:   metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   349  		},
   350  		{
   351  			imports: []metaImport{
   352  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   353  				{Prefix: "example.com/user/fooa", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   354  			},
   355  			path: "example.com/user/foo",
   356  			mi:   metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   357  		},
   358  		{
   359  			imports: []metaImport{
   360  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   361  				{Prefix: "example.com/user/fooa", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   362  			},
   363  			path: "example.com/user/fooa",
   364  			mi:   metaImport{Prefix: "example.com/user/fooa", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   365  		},
   366  		{
   367  			imports: []metaImport{
   368  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   369  				{Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   370  			},
   371  			path: "example.com/user/foo/bar",
   372  			err:  errors.New("should not be allowed to create nested repo"),
   373  		},
   374  		{
   375  			imports: []metaImport{
   376  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   377  				{Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   378  			},
   379  			path: "example.com/user/foo/bar/baz",
   380  			err:  errors.New("should not be allowed to create nested repo"),
   381  		},
   382  		{
   383  			imports: []metaImport{
   384  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   385  				{Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   386  			},
   387  			path: "example.com/user/foo/bar/baz/qux",
   388  			err:  errors.New("should not be allowed to create nested repo"),
   389  		},
   390  		{
   391  			imports: []metaImport{
   392  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   393  				{Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   394  			},
   395  			path: "example.com/user/foo/bar/baz/",
   396  			err:  errors.New("should not be allowed to create nested repo"),
   397  		},
   398  		{
   399  			imports: []metaImport{
   400  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   401  				{Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   402  			},
   403  			path: "example.com",
   404  			err:  errors.New("pathologically short path"),
   405  		},
   406  		{
   407  			imports: []metaImport{
   408  				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
   409  			},
   410  			path: "different.example.com/user/foo",
   411  			err:  errors.New("meta tags do not match import path"),
   412  		},
   413  		{
   414  			imports: []metaImport{
   415  				{Prefix: "myitcv.io/blah2", VCS: "mod", RepoRoot: "https://raw.githubusercontent.com/myitcv/pubx/master"},
   416  				{Prefix: "myitcv.io", VCS: "git", RepoRoot: "https://github.com/myitcv/x"},
   417  			},
   418  			path: "myitcv.io/blah2/foo",
   419  			mi:   metaImport{Prefix: "myitcv.io/blah2", VCS: "mod", RepoRoot: "https://raw.githubusercontent.com/myitcv/pubx/master"},
   420  		},
   421  		{
   422  			imports: []metaImport{
   423  				{Prefix: "myitcv.io/blah2", VCS: "mod", RepoRoot: "https://raw.githubusercontent.com/myitcv/pubx/master"},
   424  				{Prefix: "myitcv.io", VCS: "git", RepoRoot: "https://github.com/myitcv/x"},
   425  			},
   426  			path: "myitcv.io/other",
   427  			mi:   metaImport{Prefix: "myitcv.io", VCS: "git", RepoRoot: "https://github.com/myitcv/x"},
   428  		},
   429  	}
   430  
   431  	for _, test := range tests {
   432  		mi, err := matchGoImport(test.imports, test.path)
   433  		if mi != test.mi {
   434  			t.Errorf("unexpected metaImport; got %v, want %v", mi, test.mi)
   435  		}
   436  
   437  		got := err
   438  		want := test.err
   439  		if (got == nil) != (want == nil) {
   440  			t.Errorf("unexpected error; got %v, want %v", got, want)
   441  		}
   442  	}
   443  }
   444  
   445  func TestValidateRepoRoot(t *testing.T) {
   446  	tests := []struct {
   447  		root string
   448  		ok   bool
   449  	}{
   450  		{
   451  			root: "",
   452  			ok:   false,
   453  		},
   454  		{
   455  			root: "http://",
   456  			ok:   true,
   457  		},
   458  		{
   459  			root: "git+ssh://",
   460  			ok:   true,
   461  		},
   462  		{
   463  			root: "http#://",
   464  			ok:   false,
   465  		},
   466  		{
   467  			root: "-config",
   468  			ok:   false,
   469  		},
   470  		{
   471  			root: "-config://",
   472  			ok:   false,
   473  		},
   474  	}
   475  
   476  	for _, test := range tests {
   477  		err := validateRepoRoot(test.root)
   478  		ok := err == nil
   479  		if ok != test.ok {
   480  			want := "error"
   481  			if test.ok {
   482  				want = "nil"
   483  			}
   484  			t.Errorf("validateRepoRoot(%q) = %q, want %s", test.root, err, want)
   485  		}
   486  	}
   487  }
   488  
   489  var govcsTests = []struct {
   490  	govcs string
   491  	path  string
   492  	vcs   string
   493  	ok    bool
   494  }{
   495  	{"private:all", "is-public.com/foo", "zzz", false},
   496  	{"private:all", "is-private.com/foo", "zzz", true},
   497  	{"public:all", "is-public.com/foo", "zzz", true},
   498  	{"public:all", "is-private.com/foo", "zzz", false},
   499  	{"public:all,private:none", "is-public.com/foo", "zzz", true},
   500  	{"public:all,private:none", "is-private.com/foo", "zzz", false},
   501  	{"*:all", "is-public.com/foo", "zzz", true},
   502  	{"golang.org:git", "golang.org/x/text", "zzz", false},
   503  	{"golang.org:git", "golang.org/x/text", "git", true},
   504  	{"golang.org:zzz", "golang.org/x/text", "zzz", true},
   505  	{"golang.org:zzz", "golang.org/x/text", "git", false},
   506  	{"golang.org:zzz", "golang.org/x/text", "zzz", true},
   507  	{"golang.org:zzz", "golang.org/x/text", "git", false},
   508  	{"golang.org:git|hg", "golang.org/x/text", "hg", true},
   509  	{"golang.org:git|hg", "golang.org/x/text", "git", true},
   510  	{"golang.org:git|hg", "golang.org/x/text", "zzz", false},
   511  	{"golang.org:all", "golang.org/x/text", "hg", true},
   512  	{"golang.org:all", "golang.org/x/text", "git", true},
   513  	{"golang.org:all", "golang.org/x/text", "zzz", true},
   514  	{"other.xyz/p:none,golang.org/x:git", "other.xyz/p/x", "git", false},
   515  	{"other.xyz/p:none,golang.org/x:git", "unexpected.com", "git", false},
   516  	{"other.xyz/p:none,golang.org/x:git", "golang.org/x/text", "zzz", false},
   517  	{"other.xyz/p:none,golang.org/x:git", "golang.org/x/text", "git", true},
   518  	{"other.xyz/p:none,golang.org/x:zzz", "golang.org/x/text", "zzz", true},
   519  	{"other.xyz/p:none,golang.org/x:zzz", "golang.org/x/text", "git", false},
   520  	{"other.xyz/p:none,golang.org/x:git|hg", "golang.org/x/text", "hg", true},
   521  	{"other.xyz/p:none,golang.org/x:git|hg", "golang.org/x/text", "git", true},
   522  	{"other.xyz/p:none,golang.org/x:git|hg", "golang.org/x/text", "zzz", false},
   523  	{"other.xyz/p:none,golang.org/x:all", "golang.org/x/text", "hg", true},
   524  	{"other.xyz/p:none,golang.org/x:all", "golang.org/x/text", "git", true},
   525  	{"other.xyz/p:none,golang.org/x:all", "golang.org/x/text", "zzz", true},
   526  	{"other.xyz/p:none,golang.org/x:git", "golang.org/y/text", "zzz", false},
   527  	{"other.xyz/p:none,golang.org/x:git", "golang.org/y/text", "git", false},
   528  	{"other.xyz/p:none,golang.org/x:zzz", "golang.org/y/text", "zzz", false},
   529  	{"other.xyz/p:none,golang.org/x:zzz", "golang.org/y/text", "git", false},
   530  	{"other.xyz/p:none,golang.org/x:git|hg", "golang.org/y/text", "hg", false},
   531  	{"other.xyz/p:none,golang.org/x:git|hg", "golang.org/y/text", "git", false},
   532  	{"other.xyz/p:none,golang.org/x:git|hg", "golang.org/y/text", "zzz", false},
   533  	{"other.xyz/p:none,golang.org/x:all", "golang.org/y/text", "hg", false},
   534  	{"other.xyz/p:none,golang.org/x:all", "golang.org/y/text", "git", false},
   535  	{"other.xyz/p:none,golang.org/x:all", "golang.org/y/text", "zzz", false},
   536  }
   537  
   538  func TestGOVCS(t *testing.T) {
   539  	for _, tt := range govcsTests {
   540  		cfg, err := parseGOVCS(tt.govcs)
   541  		if err != nil {
   542  			t.Errorf("parseGOVCS(%q): %v", tt.govcs, err)
   543  			continue
   544  		}
   545  		private := strings.HasPrefix(tt.path, "is-private")
   546  		ok := cfg.allow(tt.path, private, tt.vcs)
   547  		if ok != tt.ok {
   548  			t.Errorf("parseGOVCS(%q).allow(%q, %v, %q) = %v, want %v",
   549  				tt.govcs, tt.path, private, tt.vcs, ok, tt.ok)
   550  		}
   551  	}
   552  }
   553  
   554  var govcsErrors = []struct {
   555  	s   string
   556  	err string
   557  }{
   558  	{`,`, `empty entry in GOVCS`},
   559  	{`,x`, `empty entry in GOVCS`},
   560  	{`x,`, `malformed entry in GOVCS (missing colon): "x"`},
   561  	{`x:y,`, `empty entry in GOVCS`},
   562  	{`x`, `malformed entry in GOVCS (missing colon): "x"`},
   563  	{`x:`, `empty VCS list in GOVCS: "x:"`},
   564  	{`x:|`, `empty VCS name in GOVCS: "x:|"`},
   565  	{`x:y|`, `empty VCS name in GOVCS: "x:y|"`},
   566  	{`x:|y`, `empty VCS name in GOVCS: "x:|y"`},
   567  	{`x:y,z:`, `empty VCS list in GOVCS: "z:"`},
   568  	{`x:y,z:|`, `empty VCS name in GOVCS: "z:|"`},
   569  	{`x:y,z:|w`, `empty VCS name in GOVCS: "z:|w"`},
   570  	{`x:y,z:w|`, `empty VCS name in GOVCS: "z:w|"`},
   571  	{`x:y,z:w||v`, `empty VCS name in GOVCS: "z:w||v"`},
   572  	{`x:y,x:z`, `unreachable pattern in GOVCS: "x:z" after "x:y"`},
   573  }
   574  
   575  func TestGOVCSErrors(t *testing.T) {
   576  	for _, tt := range govcsErrors {
   577  		_, err := parseGOVCS(tt.s)
   578  		if err == nil || !strings.Contains(err.Error(), tt.err) {
   579  			t.Errorf("parseGOVCS(%s): err=%v, want %v", tt.s, err, tt.err)
   580  		}
   581  	}
   582  }