Black Lives Matter. Support the Equal Justice Initiative.

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

View as plain text