1// Copyright 2018 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 codehost 6 7import ( 8 "archive/zip" 9 "bytes" 10 "cmd/go/internal/cfg" 11 "cmd/go/internal/vcweb/vcstest" 12 "context" 13 "flag" 14 "internal/testenv" 15 "io" 16 "io/fs" 17 "log" 18 "os" 19 "path" 20 "path/filepath" 21 "reflect" 22 "runtime" 23 "strings" 24 "sync" 25 "testing" 26 "time" 27) 28 29func TestMain(m *testing.M) { 30 flag.Parse() 31 if err := testMain(m); err != nil { 32 log.Fatal(err) 33 } 34} 35 36var gitrepo1, hgrepo1, vgotest1 string 37 38var altRepos = func() []string { 39 return []string{ 40 "localGitRepo", 41 hgrepo1, 42 } 43} 44 45// TODO: Convert gitrepo1 to svn, bzr, fossil and add tests. 46// For now, at least the hgrepo1 tests check the general vcs.go logic. 47 48// localGitRepo is like gitrepo1 but allows archive access 49// (although that doesn't really matter after CL 120041), 50// and has a file:// URL instead of http:// or https:// 51// (which might still matter). 52var localGitRepo string 53 54// localGitURL initializes the repo in localGitRepo and returns its URL. 55func localGitURL(t testing.TB) string { 56 testenv.MustHaveExecPath(t, "git") 57 if runtime.GOOS == "android" && strings.HasSuffix(testenv.Builder(), "-corellium") { 58 testenv.SkipFlaky(t, 59940) 59 } 60 61 localGitURLOnce.Do(func() { 62 // Clone gitrepo1 into a local directory. 63 // If we use a file:// URL to access the local directory, 64 // then git starts up all the usual protocol machinery, 65 // which will let us test remote git archive invocations. 66 _, localGitURLErr = Run(context.Background(), "", "git", "clone", "--mirror", gitrepo1, localGitRepo) 67 if localGitURLErr != nil { 68 return 69 } 70 _, localGitURLErr = Run(context.Background(), localGitRepo, "git", "config", "daemon.uploadarch", "true") 71 }) 72 73 if localGitURLErr != nil { 74 t.Fatal(localGitURLErr) 75 } 76 // Convert absolute path to file URL. LocalGitRepo will not accept 77 // Windows absolute paths because they look like a host:path remote. 78 // TODO(golang.org/issue/32456): use url.FromFilePath when implemented. 79 if strings.HasPrefix(localGitRepo, "/") { 80 return "file://" + localGitRepo 81 } else { 82 return "file:///" + filepath.ToSlash(localGitRepo) 83 } 84} 85 86var ( 87 localGitURLOnce sync.Once 88 localGitURLErr error 89) 90 91func testMain(m *testing.M) (err error) { 92 cfg.BuildX = testing.Verbose() 93 94 srv, err := vcstest.NewServer() 95 if err != nil { 96 return err 97 } 98 defer func() { 99 if closeErr := srv.Close(); err == nil { 100 err = closeErr 101 } 102 }() 103 104 gitrepo1 = srv.HTTP.URL + "/git/gitrepo1" 105 hgrepo1 = srv.HTTP.URL + "/hg/hgrepo1" 106 vgotest1 = srv.HTTP.URL + "/git/vgotest1" 107 108 dir, err := os.MkdirTemp("", "gitrepo-test-") 109 if err != nil { 110 return err 111 } 112 defer func() { 113 if rmErr := os.RemoveAll(dir); err == nil { 114 err = rmErr 115 } 116 }() 117 118 localGitRepo = filepath.Join(dir, "gitrepo2") 119 120 // Redirect the module cache to a fresh directory to avoid crosstalk, and make 121 // it read/write so that the test can still clean it up easily when done. 122 cfg.GOMODCACHE = filepath.Join(dir, "modcache") 123 cfg.ModCacheRW = true 124 125 m.Run() 126 return nil 127} 128 129func testContext(t testing.TB) context.Context { 130 w := newTestWriter(t) 131 return cfg.WithBuildXWriter(context.Background(), w) 132} 133 134// A testWriter is an io.Writer that writes to a test's log. 135// 136// The writer batches written data until the last byte of a write is a newline 137// character, then flushes the batched data as a single call to Logf. 138// Any remaining unflushed data is logged during Cleanup. 139type testWriter struct { 140 t testing.TB 141 142 mu sync.Mutex 143 buf bytes.Buffer 144} 145 146func newTestWriter(t testing.TB) *testWriter { 147 w := &testWriter{t: t} 148 149 t.Cleanup(func() { 150 w.mu.Lock() 151 defer w.mu.Unlock() 152 if b := w.buf.Bytes(); len(b) > 0 { 153 w.t.Logf("%s", b) 154 w.buf.Reset() 155 } 156 }) 157 158 return w 159} 160 161func (w *testWriter) Write(p []byte) (int, error) { 162 w.mu.Lock() 163 defer w.mu.Unlock() 164 n, err := w.buf.Write(p) 165 if b := w.buf.Bytes(); len(b) > 0 && b[len(b)-1] == '\n' { 166 w.t.Logf("%s", b) 167 w.buf.Reset() 168 } 169 return n, err 170} 171 172func testRepo(ctx context.Context, t *testing.T, remote string) (Repo, error) { 173 if remote == "localGitRepo" { 174 return LocalGitRepo(ctx, localGitURL(t)) 175 } 176 vcsName := "git" 177 for _, k := range []string{"hg"} { 178 if strings.Contains(remote, "/"+k+"/") { 179 vcsName = k 180 } 181 } 182 if testing.Short() && vcsName == "hg" { 183 t.Skipf("skipping hg test in short mode: hg is slow") 184 } 185 testenv.MustHaveExecPath(t, vcsName) 186 if runtime.GOOS == "android" && strings.HasSuffix(testenv.Builder(), "-corellium") { 187 testenv.SkipFlaky(t, 59940) 188 } 189 return NewRepo(ctx, vcsName, remote) 190} 191 192func TestTags(t *testing.T) { 193 t.Parallel() 194 195 type tagsTest struct { 196 repo string 197 prefix string 198 tags []Tag 199 } 200 201 runTest := func(tt tagsTest) func(*testing.T) { 202 return func(t *testing.T) { 203 t.Parallel() 204 ctx := testContext(t) 205 206 r, err := testRepo(ctx, t, tt.repo) 207 if err != nil { 208 t.Fatal(err) 209 } 210 tags, err := r.Tags(ctx, tt.prefix) 211 if err != nil { 212 t.Fatal(err) 213 } 214 if tags == nil || !reflect.DeepEqual(tags.List, tt.tags) { 215 t.Errorf("Tags(%q): incorrect tags\nhave %v\nwant %v", tt.prefix, tags, tt.tags) 216 } 217 } 218 } 219 220 for _, tt := range []tagsTest{ 221 {gitrepo1, "xxx", []Tag{}}, 222 {gitrepo1, "", []Tag{ 223 {"v1.2.3", "ede458df7cd0fdca520df19a33158086a8a68e81"}, 224 {"v1.2.4-annotated", "ede458df7cd0fdca520df19a33158086a8a68e81"}, 225 {"v2.0.1", "76a00fb249b7f93091bc2c89a789dab1fc1bc26f"}, 226 {"v2.0.2", "9d02800338b8a55be062c838d1f02e0c5780b9eb"}, 227 {"v2.3", "76a00fb249b7f93091bc2c89a789dab1fc1bc26f"}, 228 }}, 229 {gitrepo1, "v", []Tag{ 230 {"v1.2.3", "ede458df7cd0fdca520df19a33158086a8a68e81"}, 231 {"v1.2.4-annotated", "ede458df7cd0fdca520df19a33158086a8a68e81"}, 232 {"v2.0.1", "76a00fb249b7f93091bc2c89a789dab1fc1bc26f"}, 233 {"v2.0.2", "9d02800338b8a55be062c838d1f02e0c5780b9eb"}, 234 {"v2.3", "76a00fb249b7f93091bc2c89a789dab1fc1bc26f"}, 235 }}, 236 {gitrepo1, "v1", []Tag{ 237 {"v1.2.3", "ede458df7cd0fdca520df19a33158086a8a68e81"}, 238 {"v1.2.4-annotated", "ede458df7cd0fdca520df19a33158086a8a68e81"}, 239 }}, 240 {gitrepo1, "2", []Tag{}}, 241 } { 242 t.Run(path.Base(tt.repo)+"/"+tt.prefix, runTest(tt)) 243 if tt.repo == gitrepo1 { 244 // Clear hashes. 245 clearTags := []Tag{} 246 for _, tag := range tt.tags { 247 clearTags = append(clearTags, Tag{tag.Name, ""}) 248 } 249 tags := tt.tags 250 for _, tt.repo = range altRepos() { 251 if strings.Contains(tt.repo, "Git") { 252 tt.tags = tags 253 } else { 254 tt.tags = clearTags 255 } 256 t.Run(path.Base(tt.repo)+"/"+tt.prefix, runTest(tt)) 257 } 258 } 259 } 260} 261 262func TestLatest(t *testing.T) { 263 t.Parallel() 264 265 type latestTest struct { 266 repo string 267 info *RevInfo 268 } 269 runTest := func(tt latestTest) func(*testing.T) { 270 return func(t *testing.T) { 271 t.Parallel() 272 ctx := testContext(t) 273 274 r, err := testRepo(ctx, t, tt.repo) 275 if err != nil { 276 t.Fatal(err) 277 } 278 info, err := r.Latest(ctx) 279 if err != nil { 280 t.Fatal(err) 281 } 282 if !reflect.DeepEqual(info, tt.info) { 283 t.Errorf("Latest: incorrect info\nhave %+v (origin %+v)\nwant %+v (origin %+v)", info, info.Origin, tt.info, tt.info.Origin) 284 } 285 } 286 } 287 288 for _, tt := range []latestTest{ 289 { 290 gitrepo1, 291 &RevInfo{ 292 Origin: &Origin{ 293 VCS: "git", 294 URL: gitrepo1, 295 Ref: "HEAD", 296 Hash: "ede458df7cd0fdca520df19a33158086a8a68e81", 297 }, 298 Name: "ede458df7cd0fdca520df19a33158086a8a68e81", 299 Short: "ede458df7cd0", 300 Version: "ede458df7cd0fdca520df19a33158086a8a68e81", 301 Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC), 302 Tags: []string{"v1.2.3", "v1.2.4-annotated"}, 303 }, 304 }, 305 { 306 hgrepo1, 307 &RevInfo{ 308 Origin: &Origin{ 309 VCS: "hg", 310 URL: hgrepo1, 311 Hash: "18518c07eb8ed5c80221e997e518cccaa8c0c287", 312 }, 313 Name: "18518c07eb8ed5c80221e997e518cccaa8c0c287", 314 Short: "18518c07eb8e", 315 Version: "18518c07eb8ed5c80221e997e518cccaa8c0c287", 316 Time: time.Date(2018, 6, 27, 16, 16, 30, 0, time.UTC), 317 }, 318 }, 319 } { 320 t.Run(path.Base(tt.repo), runTest(tt)) 321 if tt.repo == gitrepo1 { 322 tt.repo = "localGitRepo" 323 info := *tt.info 324 tt.info = &info 325 o := *info.Origin 326 info.Origin = &o 327 o.URL = localGitURL(t) 328 t.Run(path.Base(tt.repo), runTest(tt)) 329 } 330 } 331} 332 333func TestReadFile(t *testing.T) { 334 t.Parallel() 335 336 type readFileTest struct { 337 repo string 338 rev string 339 file string 340 err string 341 data string 342 } 343 runTest := func(tt readFileTest) func(*testing.T) { 344 return func(t *testing.T) { 345 t.Parallel() 346 ctx := testContext(t) 347 348 r, err := testRepo(ctx, t, tt.repo) 349 if err != nil { 350 t.Fatal(err) 351 } 352 data, err := r.ReadFile(ctx, tt.rev, tt.file, 100) 353 if err != nil { 354 if tt.err == "" { 355 t.Fatalf("ReadFile: unexpected error %v", err) 356 } 357 if !strings.Contains(err.Error(), tt.err) { 358 t.Fatalf("ReadFile: wrong error %q, want %q", err, tt.err) 359 } 360 if len(data) != 0 { 361 t.Errorf("ReadFile: non-empty data %q with error %v", data, err) 362 } 363 return 364 } 365 if tt.err != "" { 366 t.Fatalf("ReadFile: no error, wanted %v", tt.err) 367 } 368 if string(data) != tt.data { 369 t.Errorf("ReadFile: incorrect data\nhave %q\nwant %q", data, tt.data) 370 } 371 } 372 } 373 374 for _, tt := range []readFileTest{ 375 { 376 repo: gitrepo1, 377 rev: "latest", 378 file: "README", 379 data: "", 380 }, 381 { 382 repo: gitrepo1, 383 rev: "v2", 384 file: "another.txt", 385 data: "another\n", 386 }, 387 { 388 repo: gitrepo1, 389 rev: "v2.3.4", 390 file: "another.txt", 391 err: fs.ErrNotExist.Error(), 392 }, 393 } { 394 t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.file, runTest(tt)) 395 if tt.repo == gitrepo1 { 396 for _, tt.repo = range altRepos() { 397 t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.file, runTest(tt)) 398 } 399 } 400 } 401} 402 403type zipFile struct { 404 name string 405 size int64 406} 407 408func TestReadZip(t *testing.T) { 409 t.Parallel() 410 411 type readZipTest struct { 412 repo string 413 rev string 414 subdir string 415 err string 416 files map[string]uint64 417 } 418 runTest := func(tt readZipTest) func(*testing.T) { 419 return func(t *testing.T) { 420 t.Parallel() 421 ctx := testContext(t) 422 423 r, err := testRepo(ctx, t, tt.repo) 424 if err != nil { 425 t.Fatal(err) 426 } 427 rc, err := r.ReadZip(ctx, tt.rev, tt.subdir, 100000) 428 if err != nil { 429 if tt.err == "" { 430 t.Fatalf("ReadZip: unexpected error %v", err) 431 } 432 if !strings.Contains(err.Error(), tt.err) { 433 t.Fatalf("ReadZip: wrong error %q, want %q", err, tt.err) 434 } 435 if rc != nil { 436 t.Errorf("ReadZip: non-nil io.ReadCloser with error %v", err) 437 } 438 return 439 } 440 defer rc.Close() 441 if tt.err != "" { 442 t.Fatalf("ReadZip: no error, wanted %v", tt.err) 443 } 444 zipdata, err := io.ReadAll(rc) 445 if err != nil { 446 t.Fatal(err) 447 } 448 z, err := zip.NewReader(bytes.NewReader(zipdata), int64(len(zipdata))) 449 if err != nil { 450 t.Fatalf("ReadZip: cannot read zip file: %v", err) 451 } 452 have := make(map[string]bool) 453 for _, f := range z.File { 454 size, ok := tt.files[f.Name] 455 if !ok { 456 t.Errorf("ReadZip: unexpected file %s", f.Name) 457 continue 458 } 459 have[f.Name] = true 460 if size != ^uint64(0) && f.UncompressedSize64 != size { 461 t.Errorf("ReadZip: file %s has unexpected size %d != %d", f.Name, f.UncompressedSize64, size) 462 } 463 } 464 for name := range tt.files { 465 if !have[name] { 466 t.Errorf("ReadZip: missing file %s", name) 467 } 468 } 469 } 470 } 471 472 for _, tt := range []readZipTest{ 473 { 474 repo: gitrepo1, 475 rev: "v2.3.4", 476 subdir: "", 477 files: map[string]uint64{ 478 "prefix/": 0, 479 "prefix/README": 0, 480 "prefix/v2": 3, 481 }, 482 }, 483 { 484 repo: hgrepo1, 485 rev: "v2.3.4", 486 subdir: "", 487 files: map[string]uint64{ 488 "prefix/.hg_archival.txt": ^uint64(0), 489 "prefix/README": 0, 490 "prefix/v2": 3, 491 }, 492 }, 493 494 { 495 repo: gitrepo1, 496 rev: "v2", 497 subdir: "", 498 files: map[string]uint64{ 499 "prefix/": 0, 500 "prefix/README": 0, 501 "prefix/v2": 3, 502 "prefix/another.txt": 8, 503 "prefix/foo.txt": 13, 504 }, 505 }, 506 { 507 repo: hgrepo1, 508 rev: "v2", 509 subdir: "", 510 files: map[string]uint64{ 511 "prefix/.hg_archival.txt": ^uint64(0), 512 "prefix/README": 0, 513 "prefix/v2": 3, 514 "prefix/another.txt": 8, 515 "prefix/foo.txt": 13, 516 }, 517 }, 518 519 { 520 repo: gitrepo1, 521 rev: "v3", 522 subdir: "", 523 files: map[string]uint64{ 524 "prefix/": 0, 525 "prefix/v3/": 0, 526 "prefix/v3/sub/": 0, 527 "prefix/v3/sub/dir/": 0, 528 "prefix/v3/sub/dir/file.txt": 16, 529 "prefix/README": 0, 530 }, 531 }, 532 { 533 repo: hgrepo1, 534 rev: "v3", 535 subdir: "", 536 files: map[string]uint64{ 537 "prefix/.hg_archival.txt": ^uint64(0), 538 "prefix/.hgtags": 405, 539 "prefix/v3/sub/dir/file.txt": 16, 540 "prefix/README": 0, 541 }, 542 }, 543 544 { 545 repo: gitrepo1, 546 rev: "v3", 547 subdir: "v3/sub/dir", 548 files: map[string]uint64{ 549 "prefix/": 0, 550 "prefix/v3/": 0, 551 "prefix/v3/sub/": 0, 552 "prefix/v3/sub/dir/": 0, 553 "prefix/v3/sub/dir/file.txt": 16, 554 }, 555 }, 556 { 557 repo: hgrepo1, 558 rev: "v3", 559 subdir: "v3/sub/dir", 560 files: map[string]uint64{ 561 "prefix/v3/sub/dir/file.txt": 16, 562 }, 563 }, 564 565 { 566 repo: gitrepo1, 567 rev: "v3", 568 subdir: "v3/sub", 569 files: map[string]uint64{ 570 "prefix/": 0, 571 "prefix/v3/": 0, 572 "prefix/v3/sub/": 0, 573 "prefix/v3/sub/dir/": 0, 574 "prefix/v3/sub/dir/file.txt": 16, 575 }, 576 }, 577 { 578 repo: hgrepo1, 579 rev: "v3", 580 subdir: "v3/sub", 581 files: map[string]uint64{ 582 "prefix/v3/sub/dir/file.txt": 16, 583 }, 584 }, 585 586 { 587 repo: gitrepo1, 588 rev: "aaaaaaaaab", 589 subdir: "", 590 err: "unknown revision", 591 }, 592 { 593 repo: hgrepo1, 594 rev: "aaaaaaaaab", 595 subdir: "", 596 err: "unknown revision", 597 }, 598 599 { 600 repo: vgotest1, 601 rev: "submod/v1.0.4", 602 subdir: "submod", 603 files: map[string]uint64{ 604 "prefix/": 0, 605 "prefix/submod/": 0, 606 "prefix/submod/go.mod": 53, 607 "prefix/submod/pkg/": 0, 608 "prefix/submod/pkg/p.go": 31, 609 }, 610 }, 611 } { 612 t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.subdir, runTest(tt)) 613 if tt.repo == gitrepo1 { 614 tt.repo = "localGitRepo" 615 t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.subdir, runTest(tt)) 616 } 617 } 618} 619 620var hgmap = map[string]string{ 621 "HEAD": "41964ddce1180313bdc01d0a39a2813344d6261d", // not tip due to bad hgrepo1 conversion 622 "9d02800338b8a55be062c838d1f02e0c5780b9eb": "8f49ee7a6ddcdec6f0112d9dca48d4a2e4c3c09e", 623 "76a00fb249b7f93091bc2c89a789dab1fc1bc26f": "88fde824ec8b41a76baa16b7e84212cee9f3edd0", 624 "ede458df7cd0fdca520df19a33158086a8a68e81": "41964ddce1180313bdc01d0a39a2813344d6261d", 625 "97f6aa59c81c623494825b43d39e445566e429a4": "c0cbbfb24c7c3c50c35c7b88e7db777da4ff625d", 626} 627 628func TestStat(t *testing.T) { 629 t.Parallel() 630 631 type statTest struct { 632 repo string 633 rev string 634 err string 635 info *RevInfo 636 } 637 runTest := func(tt statTest) func(*testing.T) { 638 return func(t *testing.T) { 639 t.Parallel() 640 ctx := testContext(t) 641 642 r, err := testRepo(ctx, t, tt.repo) 643 if err != nil { 644 t.Fatal(err) 645 } 646 info, err := r.Stat(ctx, tt.rev) 647 if err != nil { 648 if tt.err == "" { 649 t.Fatalf("Stat: unexpected error %v", err) 650 } 651 if !strings.Contains(err.Error(), tt.err) { 652 t.Fatalf("Stat: wrong error %q, want %q", err, tt.err) 653 } 654 if info != nil && info.Origin == nil { 655 t.Errorf("Stat: non-nil info with nil Origin with error %q", err) 656 } 657 return 658 } 659 info.Origin = nil // TestLatest and ../../../testdata/script/reuse_git.txt test Origin well enough 660 if !reflect.DeepEqual(info, tt.info) { 661 t.Errorf("Stat: incorrect info\nhave %+v\nwant %+v", *info, *tt.info) 662 } 663 } 664 } 665 666 for _, tt := range []statTest{ 667 { 668 repo: gitrepo1, 669 rev: "HEAD", 670 info: &RevInfo{ 671 Name: "ede458df7cd0fdca520df19a33158086a8a68e81", 672 Short: "ede458df7cd0", 673 Version: "ede458df7cd0fdca520df19a33158086a8a68e81", 674 Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC), 675 Tags: []string{"v1.2.3", "v1.2.4-annotated"}, 676 }, 677 }, 678 { 679 repo: gitrepo1, 680 rev: "v2", // branch 681 info: &RevInfo{ 682 Name: "9d02800338b8a55be062c838d1f02e0c5780b9eb", 683 Short: "9d02800338b8", 684 Version: "9d02800338b8a55be062c838d1f02e0c5780b9eb", 685 Time: time.Date(2018, 4, 17, 20, 00, 32, 0, time.UTC), 686 Tags: []string{"v2.0.2"}, 687 }, 688 }, 689 { 690 repo: gitrepo1, 691 rev: "v2.3.4", // badly-named branch (semver should be a tag) 692 info: &RevInfo{ 693 Name: "76a00fb249b7f93091bc2c89a789dab1fc1bc26f", 694 Short: "76a00fb249b7", 695 Version: "76a00fb249b7f93091bc2c89a789dab1fc1bc26f", 696 Time: time.Date(2018, 4, 17, 19, 45, 48, 0, time.UTC), 697 Tags: []string{"v2.0.1", "v2.3"}, 698 }, 699 }, 700 { 701 repo: gitrepo1, 702 rev: "v2.3", // badly-named tag (we only respect full semver v2.3.0) 703 info: &RevInfo{ 704 Name: "76a00fb249b7f93091bc2c89a789dab1fc1bc26f", 705 Short: "76a00fb249b7", 706 Version: "v2.3", 707 Time: time.Date(2018, 4, 17, 19, 45, 48, 0, time.UTC), 708 Tags: []string{"v2.0.1", "v2.3"}, 709 }, 710 }, 711 { 712 repo: gitrepo1, 713 rev: "v1.2.3", // tag 714 info: &RevInfo{ 715 Name: "ede458df7cd0fdca520df19a33158086a8a68e81", 716 Short: "ede458df7cd0", 717 Version: "v1.2.3", 718 Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC), 719 Tags: []string{"v1.2.3", "v1.2.4-annotated"}, 720 }, 721 }, 722 { 723 repo: gitrepo1, 724 rev: "ede458df", // hash prefix in refs 725 info: &RevInfo{ 726 Name: "ede458df7cd0fdca520df19a33158086a8a68e81", 727 Short: "ede458df7cd0", 728 Version: "ede458df7cd0fdca520df19a33158086a8a68e81", 729 Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC), 730 Tags: []string{"v1.2.3", "v1.2.4-annotated"}, 731 }, 732 }, 733 { 734 repo: gitrepo1, 735 rev: "97f6aa59", // hash prefix not in refs 736 info: &RevInfo{ 737 Name: "97f6aa59c81c623494825b43d39e445566e429a4", 738 Short: "97f6aa59c81c", 739 Version: "97f6aa59c81c623494825b43d39e445566e429a4", 740 Time: time.Date(2018, 4, 17, 20, 0, 19, 0, time.UTC), 741 }, 742 }, 743 { 744 repo: gitrepo1, 745 rev: "v1.2.4-annotated", // annotated tag uses unwrapped commit hash 746 info: &RevInfo{ 747 Name: "ede458df7cd0fdca520df19a33158086a8a68e81", 748 Short: "ede458df7cd0", 749 Version: "v1.2.4-annotated", 750 Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC), 751 Tags: []string{"v1.2.3", "v1.2.4-annotated"}, 752 }, 753 }, 754 { 755 repo: gitrepo1, 756 rev: "aaaaaaaaab", 757 err: "unknown revision", 758 }, 759 } { 760 t.Run(path.Base(tt.repo)+"/"+tt.rev, runTest(tt)) 761 if tt.repo == gitrepo1 { 762 for _, tt.repo = range altRepos() { 763 old := tt 764 var m map[string]string 765 if tt.repo == hgrepo1 { 766 m = hgmap 767 } 768 if tt.info != nil { 769 info := *tt.info 770 tt.info = &info 771 tt.info.Name = remap(tt.info.Name, m) 772 tt.info.Version = remap(tt.info.Version, m) 773 tt.info.Short = remap(tt.info.Short, m) 774 } 775 tt.rev = remap(tt.rev, m) 776 t.Run(path.Base(tt.repo)+"/"+tt.rev, runTest(tt)) 777 tt = old 778 } 779 } 780 } 781} 782 783func remap(name string, m map[string]string) string { 784 if m[name] != "" { 785 return m[name] 786 } 787 if AllHex(name) { 788 for k, v := range m { 789 if strings.HasPrefix(k, name) { 790 return v[:len(name)] 791 } 792 } 793 } 794 return name 795} 796