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
5package vcs
6
7import (
8	"errors"
9	"fmt"
10	"internal/testenv"
11	"os"
12	"path/filepath"
13	"strings"
14	"testing"
15
16	"cmd/go/internal/web"
17)
18
19func 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.
29func 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			"bitbucket.org/workspace/pkgname",
188			&RepoRoot{
189				VCS:  vcsGit,
190				Repo: "https://bitbucket.org/workspace/pkgname",
191			},
192		},
193	}
194
195	for _, test := range tests {
196		got, err := RepoRootForImportPath(test.path, IgnoreMod, web.SecureOnly)
197		want := test.want
198
199		if want == nil {
200			if err == nil {
201				t.Errorf("RepoRootForImportPath(%q): Error expected but not received", test.path)
202			}
203			continue
204		}
205		if err != nil {
206			t.Errorf("RepoRootForImportPath(%q): %v", test.path, err)
207			continue
208		}
209		if got.VCS.Name != want.VCS.Name || got.Repo != want.Repo {
210			t.Errorf("RepoRootForImportPath(%q) = VCS(%s) Repo(%s), want VCS(%s) Repo(%s)", test.path, got.VCS, got.Repo, want.VCS, want.Repo)
211		}
212	}
213}
214
215// Test that vcs.FromDir correctly inspects a given directory and returns the
216// right VCS and repo directory.
217func TestFromDir(t *testing.T) {
218	tempDir := t.TempDir()
219
220	for _, vcs := range vcsList {
221		for r, root := range vcs.RootNames {
222			vcsName := fmt.Sprint(vcs.Name, r)
223			dir := filepath.Join(tempDir, "example.com", vcsName, root.filename)
224			if root.isDir {
225				err := os.MkdirAll(dir, 0755)
226				if err != nil {
227					t.Fatal(err)
228				}
229			} else {
230				err := os.MkdirAll(filepath.Dir(dir), 0755)
231				if err != nil {
232					t.Fatal(err)
233				}
234				f, err := os.Create(dir)
235				if err != nil {
236					t.Fatal(err)
237				}
238				f.Close()
239			}
240
241			wantRepoDir := filepath.Dir(dir)
242			gotRepoDir, gotVCS, err := FromDir(dir, tempDir, false)
243			if err != nil {
244				t.Errorf("FromDir(%q, %q): %v", dir, tempDir, err)
245				continue
246			}
247			if gotRepoDir != wantRepoDir || gotVCS.Name != vcs.Name {
248				t.Errorf("FromDir(%q, %q) = RepoDir(%s), VCS(%s); want RepoDir(%s), VCS(%s)", dir, tempDir, gotRepoDir, gotVCS.Name, wantRepoDir, vcs.Name)
249			}
250		}
251	}
252}
253
254func TestIsSecure(t *testing.T) {
255	tests := []struct {
256		vcs    *Cmd
257		url    string
258		secure bool
259	}{
260		{vcsGit, "http://example.com/foo.git", false},
261		{vcsGit, "https://example.com/foo.git", true},
262		{vcsBzr, "http://example.com/foo.bzr", false},
263		{vcsBzr, "https://example.com/foo.bzr", true},
264		{vcsSvn, "http://example.com/svn", false},
265		{vcsSvn, "https://example.com/svn", true},
266		{vcsHg, "http://example.com/foo.hg", false},
267		{vcsHg, "https://example.com/foo.hg", true},
268		{vcsGit, "ssh://[email protected]/foo.git", true},
269		{vcsGit, "user@server:path/to/repo.git", false},
270		{vcsGit, "user@server:", false},
271		{vcsGit, "server:repo.git", false},
272		{vcsGit, "server:path/to/repo.git", false},
273		{vcsGit, "example.com:path/to/repo.git", false},
274		{vcsGit, "path/that/contains/a:colon/repo.git", false},
275		{vcsHg, "ssh://[email protected]/path/to/repo.hg", true},
276		{vcsFossil, "http://example.com/foo", false},
277		{vcsFossil, "https://example.com/foo", true},
278	}
279
280	for _, test := range tests {
281		secure := test.vcs.IsSecure(test.url)
282		if secure != test.secure {
283			t.Errorf("%s isSecure(%q) = %t; want %t", test.vcs, test.url, secure, test.secure)
284		}
285	}
286}
287
288func TestIsSecureGitAllowProtocol(t *testing.T) {
289	tests := []struct {
290		vcs    *Cmd
291		url    string
292		secure bool
293	}{
294		// Same as TestIsSecure to verify same behavior.
295		{vcsGit, "http://example.com/foo.git", false},
296		{vcsGit, "https://example.com/foo.git", true},
297		{vcsBzr, "http://example.com/foo.bzr", false},
298		{vcsBzr, "https://example.com/foo.bzr", true},
299		{vcsSvn, "http://example.com/svn", false},
300		{vcsSvn, "https://example.com/svn", true},
301		{vcsHg, "http://example.com/foo.hg", false},
302		{vcsHg, "https://example.com/foo.hg", true},
303		{vcsGit, "user@server:path/to/repo.git", false},
304		{vcsGit, "user@server:", false},
305		{vcsGit, "server:repo.git", false},
306		{vcsGit, "server:path/to/repo.git", false},
307		{vcsGit, "example.com:path/to/repo.git", false},
308		{vcsGit, "path/that/contains/a:colon/repo.git", false},
309		{vcsHg, "ssh://[email protected]/path/to/repo.hg", true},
310		// New behavior.
311		{vcsGit, "ssh://[email protected]/foo.git", false},
312		{vcsGit, "foo://example.com/bar.git", true},
313		{vcsHg, "foo://example.com/bar.hg", false},
314		{vcsSvn, "foo://example.com/svn", false},
315		{vcsBzr, "foo://example.com/bar.bzr", false},
316	}
317
318	defer os.Unsetenv("GIT_ALLOW_PROTOCOL")
319	os.Setenv("GIT_ALLOW_PROTOCOL", "https:foo")
320	for _, test := range tests {
321		secure := test.vcs.IsSecure(test.url)
322		if secure != test.secure {
323			t.Errorf("%s isSecure(%q) = %t; want %t", test.vcs, test.url, secure, test.secure)
324		}
325	}
326}
327
328func TestMatchGoImport(t *testing.T) {
329	tests := []struct {
330		imports []metaImport
331		path    string
332		mi      metaImport
333		err     error
334	}{
335		{
336			imports: []metaImport{
337				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
338			},
339			path: "example.com/user/foo",
340			mi:   metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
341		},
342		{
343			imports: []metaImport{
344				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
345			},
346			path: "example.com/user/foo/",
347			mi:   metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
348		},
349		{
350			imports: []metaImport{
351				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
352				{Prefix: "example.com/user/fooa", VCS: "git", RepoRoot: "https://example.com/repo/target"},
353			},
354			path: "example.com/user/foo",
355			mi:   metaImport{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
356		},
357		{
358			imports: []metaImport{
359				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
360				{Prefix: "example.com/user/fooa", VCS: "git", RepoRoot: "https://example.com/repo/target"},
361			},
362			path: "example.com/user/fooa",
363			mi:   metaImport{Prefix: "example.com/user/fooa", VCS: "git", RepoRoot: "https://example.com/repo/target"},
364		},
365		{
366			imports: []metaImport{
367				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
368				{Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
369			},
370			path: "example.com/user/foo/bar",
371			err:  errors.New("should not be allowed to create nested repo"),
372		},
373		{
374			imports: []metaImport{
375				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
376				{Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
377			},
378			path: "example.com/user/foo/bar/baz",
379			err:  errors.New("should not be allowed to create nested repo"),
380		},
381		{
382			imports: []metaImport{
383				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
384				{Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
385			},
386			path: "example.com/user/foo/bar/baz/qux",
387			err:  errors.New("should not be allowed to create nested repo"),
388		},
389		{
390			imports: []metaImport{
391				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
392				{Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
393			},
394			path: "example.com/user/foo/bar/baz/",
395			err:  errors.New("should not be allowed to create nested repo"),
396		},
397		{
398			imports: []metaImport{
399				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
400				{Prefix: "example.com/user/foo/bar", VCS: "git", RepoRoot: "https://example.com/repo/target"},
401			},
402			path: "example.com",
403			err:  errors.New("pathologically short path"),
404		},
405		{
406			imports: []metaImport{
407				{Prefix: "example.com/user/foo", VCS: "git", RepoRoot: "https://example.com/repo/target"},
408			},
409			path: "different.example.com/user/foo",
410			err:  errors.New("meta tags do not match import path"),
411		},
412		{
413			imports: []metaImport{
414				{Prefix: "myitcv.io/blah2", VCS: "mod", RepoRoot: "https://raw.githubusercontent.com/myitcv/pubx/master"},
415				{Prefix: "myitcv.io", VCS: "git", RepoRoot: "https://github.com/myitcv/x"},
416			},
417			path: "myitcv.io/blah2/foo",
418			mi:   metaImport{Prefix: "myitcv.io/blah2", VCS: "mod", RepoRoot: "https://raw.githubusercontent.com/myitcv/pubx/master"},
419		},
420		{
421			imports: []metaImport{
422				{Prefix: "myitcv.io/blah2", VCS: "mod", RepoRoot: "https://raw.githubusercontent.com/myitcv/pubx/master"},
423				{Prefix: "myitcv.io", VCS: "git", RepoRoot: "https://github.com/myitcv/x"},
424			},
425			path: "myitcv.io/other",
426			mi:   metaImport{Prefix: "myitcv.io", VCS: "git", RepoRoot: "https://github.com/myitcv/x"},
427		},
428	}
429
430	for _, test := range tests {
431		mi, err := matchGoImport(test.imports, test.path)
432		if mi != test.mi {
433			t.Errorf("unexpected metaImport; got %v, want %v", mi, test.mi)
434		}
435
436		got := err
437		want := test.err
438		if (got == nil) != (want == nil) {
439			t.Errorf("unexpected error; got %v, want %v", got, want)
440		}
441	}
442}
443
444func TestValidateRepoRoot(t *testing.T) {
445	tests := []struct {
446		root string
447		ok   bool
448	}{
449		{
450			root: "",
451			ok:   false,
452		},
453		{
454			root: "http://",
455			ok:   true,
456		},
457		{
458			root: "git+ssh://",
459			ok:   true,
460		},
461		{
462			root: "http#://",
463			ok:   false,
464		},
465		{
466			root: "-config",
467			ok:   false,
468		},
469		{
470			root: "-config://",
471			ok:   false,
472		},
473	}
474
475	for _, test := range tests {
476		err := validateRepoRoot(test.root)
477		ok := err == nil
478		if ok != test.ok {
479			want := "error"
480			if test.ok {
481				want = "nil"
482			}
483			t.Errorf("validateRepoRoot(%q) = %q, want %s", test.root, err, want)
484		}
485	}
486}
487
488var govcsTests = []struct {
489	govcs string
490	path  string
491	vcs   string
492	ok    bool
493}{
494	{"private:all", "is-public.com/foo", "zzz", false},
495	{"private:all", "is-private.com/foo", "zzz", true},
496	{"public:all", "is-public.com/foo", "zzz", true},
497	{"public:all", "is-private.com/foo", "zzz", false},
498	{"public:all,private:none", "is-public.com/foo", "zzz", true},
499	{"public:all,private:none", "is-private.com/foo", "zzz", false},
500	{"*:all", "is-public.com/foo", "zzz", true},
501	{"golang.org:git", "golang.org/x/text", "zzz", false},
502	{"golang.org:git", "golang.org/x/text", "git", true},
503	{"golang.org:zzz", "golang.org/x/text", "zzz", true},
504	{"golang.org:zzz", "golang.org/x/text", "git", false},
505	{"golang.org:zzz", "golang.org/x/text", "zzz", true},
506	{"golang.org:zzz", "golang.org/x/text", "git", false},
507	{"golang.org:git|hg", "golang.org/x/text", "hg", true},
508	{"golang.org:git|hg", "golang.org/x/text", "git", true},
509	{"golang.org:git|hg", "golang.org/x/text", "zzz", false},
510	{"golang.org:all", "golang.org/x/text", "hg", true},
511	{"golang.org:all", "golang.org/x/text", "git", true},
512	{"golang.org:all", "golang.org/x/text", "zzz", true},
513	{"other.xyz/p:none,golang.org/x:git", "other.xyz/p/x", "git", false},
514	{"other.xyz/p:none,golang.org/x:git", "unexpected.com", "git", false},
515	{"other.xyz/p:none,golang.org/x:git", "golang.org/x/text", "zzz", false},
516	{"other.xyz/p:none,golang.org/x:git", "golang.org/x/text", "git", true},
517	{"other.xyz/p:none,golang.org/x:zzz", "golang.org/x/text", "zzz", true},
518	{"other.xyz/p:none,golang.org/x:zzz", "golang.org/x/text", "git", false},
519	{"other.xyz/p:none,golang.org/x:git|hg", "golang.org/x/text", "hg", true},
520	{"other.xyz/p:none,golang.org/x:git|hg", "golang.org/x/text", "git", true},
521	{"other.xyz/p:none,golang.org/x:git|hg", "golang.org/x/text", "zzz", false},
522	{"other.xyz/p:none,golang.org/x:all", "golang.org/x/text", "hg", true},
523	{"other.xyz/p:none,golang.org/x:all", "golang.org/x/text", "git", true},
524	{"other.xyz/p:none,golang.org/x:all", "golang.org/x/text", "zzz", true},
525	{"other.xyz/p:none,golang.org/x:git", "golang.org/y/text", "zzz", false},
526	{"other.xyz/p:none,golang.org/x:git", "golang.org/y/text", "git", false},
527	{"other.xyz/p:none,golang.org/x:zzz", "golang.org/y/text", "zzz", false},
528	{"other.xyz/p:none,golang.org/x:zzz", "golang.org/y/text", "git", false},
529	{"other.xyz/p:none,golang.org/x:git|hg", "golang.org/y/text", "hg", false},
530	{"other.xyz/p:none,golang.org/x:git|hg", "golang.org/y/text", "git", false},
531	{"other.xyz/p:none,golang.org/x:git|hg", "golang.org/y/text", "zzz", false},
532	{"other.xyz/p:none,golang.org/x:all", "golang.org/y/text", "hg", false},
533	{"other.xyz/p:none,golang.org/x:all", "golang.org/y/text", "git", false},
534	{"other.xyz/p:none,golang.org/x:all", "golang.org/y/text", "zzz", false},
535}
536
537func TestGOVCS(t *testing.T) {
538	for _, tt := range govcsTests {
539		cfg, err := parseGOVCS(tt.govcs)
540		if err != nil {
541			t.Errorf("parseGOVCS(%q): %v", tt.govcs, err)
542			continue
543		}
544		private := strings.HasPrefix(tt.path, "is-private")
545		ok := cfg.allow(tt.path, private, tt.vcs)
546		if ok != tt.ok {
547			t.Errorf("parseGOVCS(%q).allow(%q, %v, %q) = %v, want %v",
548				tt.govcs, tt.path, private, tt.vcs, ok, tt.ok)
549		}
550	}
551}
552
553var govcsErrors = []struct {
554	s   string
555	err string
556}{
557	{`,`, `empty entry in GOVCS`},
558	{`,x`, `empty entry in GOVCS`},
559	{`x,`, `malformed entry in GOVCS (missing colon): "x"`},
560	{`x:y,`, `empty entry in GOVCS`},
561	{`x`, `malformed entry in GOVCS (missing colon): "x"`},
562	{`x:`, `empty VCS list in GOVCS: "x:"`},
563	{`x:|`, `empty VCS name in GOVCS: "x:|"`},
564	{`x:y|`, `empty VCS name in GOVCS: "x:y|"`},
565	{`x:|y`, `empty VCS name in GOVCS: "x:|y"`},
566	{`x:y,z:`, `empty VCS list in GOVCS: "z:"`},
567	{`x:y,z:|`, `empty VCS name in GOVCS: "z:|"`},
568	{`x:y,z:|w`, `empty VCS name in GOVCS: "z:|w"`},
569	{`x:y,z:w|`, `empty VCS name in GOVCS: "z:w|"`},
570	{`x:y,z:w||v`, `empty VCS name in GOVCS: "z:w||v"`},
571	{`x:y,x:z`, `unreachable pattern in GOVCS: "x:z" after "x:y"`},
572}
573
574func TestGOVCSErrors(t *testing.T) {
575	for _, tt := range govcsErrors {
576		_, err := parseGOVCS(tt.s)
577		if err == nil || !strings.Contains(err.Error(), tt.err) {
578			t.Errorf("parseGOVCS(%s): err=%v, want %v", tt.s, err, tt.err)
579		}
580	}
581}
582