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