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 "flag" 11 "fmt" 12 "internal/testenv" 13 "io/ioutil" 14 "log" 15 "os" 16 "os/exec" 17 "path" 18 "path/filepath" 19 "reflect" 20 "strings" 21 "testing" 22 "time" 23) 24 25func TestMain(m *testing.M) { 26 // needed for initializing the test environment variables as testing.Short 27 // and HasExternalNetwork 28 flag.Parse() 29 os.Exit(testMain(m)) 30} 31 32const ( 33 gitrepo1 = "https://vcs-test.golang.org/git/gitrepo1" 34 hgrepo1 = "https://vcs-test.golang.org/hg/hgrepo1" 35) 36 37var altRepos = []string{ 38 "localGitRepo", 39 hgrepo1, 40} 41 42// TODO: Convert gitrepo1 to svn, bzr, fossil and add tests. 43// For now, at least the hgrepo1 tests check the general vcs.go logic. 44 45// localGitRepo is like gitrepo1 but allows archive access. 46var localGitRepo string 47 48func testMain(m *testing.M) int { 49 if _, err := exec.LookPath("git"); err != nil { 50 fmt.Fprintln(os.Stderr, "skipping because git binary not found") 51 fmt.Println("PASS") 52 return 0 53 } 54 55 dir, err := ioutil.TempDir("", "gitrepo-test-") 56 if err != nil { 57 log.Fatal(err) 58 } 59 defer os.RemoveAll(dir) 60 WorkRoot = dir 61 62 if testenv.HasExternalNetwork() && testenv.HasExec() { 63 // Clone gitrepo1 into a local directory. 64 // If we use a file:// URL to access the local directory, 65 // then git starts up all the usual protocol machinery, 66 // which will let us test remote git archive invocations. 67 localGitRepo = filepath.Join(dir, "gitrepo2") 68 if _, err := Run("", "git", "clone", "--mirror", gitrepo1, localGitRepo); err != nil { 69 log.Fatal(err) 70 } 71 if _, err := Run(localGitRepo, "git", "config", "daemon.uploadarch", "true"); err != nil { 72 log.Fatal(err) 73 } 74 } 75 76 return m.Run() 77} 78 79func testRepo(remote string) (Repo, error) { 80 if remote == "localGitRepo" { 81 return LocalGitRepo(filepath.ToSlash(localGitRepo)) 82 } 83 kind := "git" 84 for _, k := range []string{"hg"} { 85 if strings.Contains(remote, "/"+k+"/") { 86 kind = k 87 } 88 } 89 return NewRepo(kind, remote) 90} 91 92var tagsTests = []struct { 93 repo string 94 prefix string 95 tags []string 96}{ 97 {gitrepo1, "xxx", []string{}}, 98 {gitrepo1, "", []string{"v1.2.3", "v1.2.4-annotated", "v2.0.1", "v2.0.2", "v2.3"}}, 99 {gitrepo1, "v", []string{"v1.2.3", "v1.2.4-annotated", "v2.0.1", "v2.0.2", "v2.3"}}, 100 {gitrepo1, "v1", []string{"v1.2.3", "v1.2.4-annotated"}}, 101 {gitrepo1, "2", []string{}}, 102} 103 104func TestTags(t *testing.T) { 105 testenv.MustHaveExternalNetwork(t) 106 testenv.MustHaveExec(t) 107 108 for _, tt := range tagsTests { 109 f := func(t *testing.T) { 110 r, err := testRepo(tt.repo) 111 if err != nil { 112 t.Fatal(err) 113 } 114 tags, err := r.Tags(tt.prefix) 115 if err != nil { 116 t.Fatal(err) 117 } 118 if !reflect.DeepEqual(tags, tt.tags) { 119 t.Errorf("Tags: incorrect tags\nhave %v\nwant %v", tags, tt.tags) 120 } 121 } 122 t.Run(path.Base(tt.repo)+"/"+tt.prefix, f) 123 if tt.repo == gitrepo1 { 124 for _, tt.repo = range altRepos { 125 t.Run(path.Base(tt.repo)+"/"+tt.prefix, f) 126 } 127 } 128 } 129} 130 131var latestTests = []struct { 132 repo string 133 info *RevInfo 134}{ 135 { 136 gitrepo1, 137 &RevInfo{ 138 Name: "ede458df7cd0fdca520df19a33158086a8a68e81", 139 Short: "ede458df7cd0", 140 Version: "ede458df7cd0fdca520df19a33158086a8a68e81", 141 Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC), 142 Tags: []string{"v1.2.3", "v1.2.4-annotated"}, 143 }, 144 }, 145 { 146 hgrepo1, 147 &RevInfo{ 148 Name: "18518c07eb8ed5c80221e997e518cccaa8c0c287", 149 Short: "18518c07eb8e", 150 Version: "18518c07eb8ed5c80221e997e518cccaa8c0c287", 151 Time: time.Date(2018, 6, 27, 16, 16, 30, 0, time.UTC), 152 }, 153 }, 154} 155 156func TestLatest(t *testing.T) { 157 testenv.MustHaveExternalNetwork(t) 158 testenv.MustHaveExec(t) 159 160 for _, tt := range latestTests { 161 f := func(t *testing.T) { 162 r, err := testRepo(tt.repo) 163 if err != nil { 164 t.Fatal(err) 165 } 166 info, err := r.Latest() 167 if err != nil { 168 t.Fatal(err) 169 } 170 if !reflect.DeepEqual(info, tt.info) { 171 t.Errorf("Latest: incorrect info\nhave %+v\nwant %+v", *info, *tt.info) 172 } 173 } 174 t.Run(path.Base(tt.repo), f) 175 if tt.repo == gitrepo1 { 176 tt.repo = "localGitRepo" 177 t.Run(path.Base(tt.repo), f) 178 } 179 } 180} 181 182var readFileTests = []struct { 183 repo string 184 rev string 185 file string 186 err string 187 data string 188}{ 189 { 190 repo: gitrepo1, 191 rev: "latest", 192 file: "README", 193 data: "", 194 }, 195 { 196 repo: gitrepo1, 197 rev: "v2", 198 file: "another.txt", 199 data: "another\n", 200 }, 201 { 202 repo: gitrepo1, 203 rev: "v2.3.4", 204 file: "another.txt", 205 err: os.ErrNotExist.Error(), 206 }, 207} 208 209func TestReadFile(t *testing.T) { 210 testenv.MustHaveExternalNetwork(t) 211 testenv.MustHaveExec(t) 212 213 for _, tt := range readFileTests { 214 f := func(t *testing.T) { 215 r, err := testRepo(tt.repo) 216 if err != nil { 217 t.Fatal(err) 218 } 219 data, err := r.ReadFile(tt.rev, tt.file, 100) 220 if err != nil { 221 if tt.err == "" { 222 t.Fatalf("ReadFile: unexpected error %v", err) 223 } 224 if !strings.Contains(err.Error(), tt.err) { 225 t.Fatalf("ReadFile: wrong error %q, want %q", err, tt.err) 226 } 227 if len(data) != 0 { 228 t.Errorf("ReadFile: non-empty data %q with error %v", data, err) 229 } 230 return 231 } 232 if tt.err != "" { 233 t.Fatalf("ReadFile: no error, wanted %v", tt.err) 234 } 235 if string(data) != tt.data { 236 t.Errorf("ReadFile: incorrect data\nhave %q\nwant %q", data, tt.data) 237 } 238 } 239 t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.file, f) 240 if tt.repo == gitrepo1 { 241 for _, tt.repo = range altRepos { 242 t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.file, f) 243 } 244 } 245 } 246} 247 248var readZipTests = []struct { 249 repo string 250 rev string 251 subdir string 252 actualSubdir string 253 err string 254 files map[string]uint64 255}{ 256 { 257 repo: gitrepo1, 258 rev: "v2.3.4", 259 subdir: "", 260 files: map[string]uint64{ 261 "prefix/": 0, 262 "prefix/README": 0, 263 "prefix/v2": 3, 264 }, 265 }, 266 { 267 repo: hgrepo1, 268 rev: "v2.3.4", 269 subdir: "", 270 files: map[string]uint64{ 271 "prefix/.hg_archival.txt": ^uint64(0), 272 "prefix/README": 0, 273 "prefix/v2": 3, 274 }, 275 }, 276 277 { 278 repo: gitrepo1, 279 rev: "v2", 280 subdir: "", 281 files: map[string]uint64{ 282 "prefix/": 0, 283 "prefix/README": 0, 284 "prefix/v2": 3, 285 "prefix/another.txt": 8, 286 "prefix/foo.txt": 13, 287 }, 288 }, 289 { 290 repo: hgrepo1, 291 rev: "v2", 292 subdir: "", 293 files: map[string]uint64{ 294 "prefix/.hg_archival.txt": ^uint64(0), 295 "prefix/README": 0, 296 "prefix/v2": 3, 297 "prefix/another.txt": 8, 298 "prefix/foo.txt": 13, 299 }, 300 }, 301 302 { 303 repo: gitrepo1, 304 rev: "v3", 305 subdir: "", 306 files: map[string]uint64{ 307 "prefix/": 0, 308 "prefix/v3/": 0, 309 "prefix/v3/sub/": 0, 310 "prefix/v3/sub/dir/": 0, 311 "prefix/v3/sub/dir/file.txt": 16, 312 "prefix/README": 0, 313 }, 314 }, 315 { 316 repo: hgrepo1, 317 rev: "v3", 318 subdir: "", 319 files: map[string]uint64{ 320 "prefix/.hg_archival.txt": ^uint64(0), 321 "prefix/.hgtags": 405, 322 "prefix/v3/sub/dir/file.txt": 16, 323 "prefix/README": 0, 324 }, 325 }, 326 327 { 328 repo: gitrepo1, 329 rev: "v3", 330 subdir: "v3/sub/dir", 331 files: map[string]uint64{ 332 "prefix/": 0, 333 "prefix/v3/": 0, 334 "prefix/v3/sub/": 0, 335 "prefix/v3/sub/dir/": 0, 336 "prefix/v3/sub/dir/file.txt": 16, 337 }, 338 }, 339 { 340 repo: hgrepo1, 341 rev: "v3", 342 subdir: "v3/sub/dir", 343 files: map[string]uint64{ 344 "prefix/v3/sub/dir/file.txt": 16, 345 }, 346 }, 347 348 { 349 repo: gitrepo1, 350 rev: "v3", 351 subdir: "v3/sub", 352 files: map[string]uint64{ 353 "prefix/": 0, 354 "prefix/v3/": 0, 355 "prefix/v3/sub/": 0, 356 "prefix/v3/sub/dir/": 0, 357 "prefix/v3/sub/dir/file.txt": 16, 358 }, 359 }, 360 { 361 repo: hgrepo1, 362 rev: "v3", 363 subdir: "v3/sub", 364 files: map[string]uint64{ 365 "prefix/v3/sub/dir/file.txt": 16, 366 }, 367 }, 368 369 { 370 repo: gitrepo1, 371 rev: "aaaaaaaaab", 372 subdir: "", 373 err: "unknown revision", 374 }, 375 { 376 repo: hgrepo1, 377 rev: "aaaaaaaaab", 378 subdir: "", 379 err: "unknown revision", 380 }, 381 382 { 383 repo: "https://github.com/rsc/vgotest1", 384 rev: "submod/v1.0.4", 385 subdir: "submod", 386 files: map[string]uint64{ 387 "prefix/": 0, 388 "prefix/submod/": 0, 389 "prefix/submod/go.mod": 53, 390 "prefix/submod/pkg/": 0, 391 "prefix/submod/pkg/p.go": 31, 392 }, 393 }, 394} 395 396type zipFile struct { 397 name string 398 size int64 399} 400 401func TestReadZip(t *testing.T) { 402 testenv.MustHaveExternalNetwork(t) 403 testenv.MustHaveExec(t) 404 405 for _, tt := range readZipTests { 406 f := func(t *testing.T) { 407 r, err := testRepo(tt.repo) 408 if err != nil { 409 t.Fatal(err) 410 } 411 rc, actualSubdir, err := r.ReadZip(tt.rev, tt.subdir, 100000) 412 if err != nil { 413 if tt.err == "" { 414 t.Fatalf("ReadZip: unexpected error %v", err) 415 } 416 if !strings.Contains(err.Error(), tt.err) { 417 t.Fatalf("ReadZip: wrong error %q, want %q", err, tt.err) 418 } 419 if rc != nil { 420 t.Errorf("ReadZip: non-nil io.ReadCloser with error %v", err) 421 } 422 return 423 } 424 defer rc.Close() 425 if tt.err != "" { 426 t.Fatalf("ReadZip: no error, wanted %v", tt.err) 427 } 428 if actualSubdir != tt.actualSubdir { 429 t.Fatalf("ReadZip: actualSubdir = %q, want %q", actualSubdir, tt.actualSubdir) 430 } 431 zipdata, err := ioutil.ReadAll(rc) 432 if err != nil { 433 t.Fatal(err) 434 } 435 z, err := zip.NewReader(bytes.NewReader(zipdata), int64(len(zipdata))) 436 if err != nil { 437 t.Fatalf("ReadZip: cannot read zip file: %v", err) 438 } 439 have := make(map[string]bool) 440 for _, f := range z.File { 441 size, ok := tt.files[f.Name] 442 if !ok { 443 t.Errorf("ReadZip: unexpected file %s", f.Name) 444 continue 445 } 446 have[f.Name] = true 447 if size != ^uint64(0) && f.UncompressedSize64 != size { 448 t.Errorf("ReadZip: file %s has unexpected size %d != %d", f.Name, f.UncompressedSize64, size) 449 } 450 } 451 for name := range tt.files { 452 if !have[name] { 453 t.Errorf("ReadZip: missing file %s", name) 454 } 455 } 456 } 457 t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.subdir, f) 458 if tt.repo == gitrepo1 { 459 tt.repo = "localGitRepo" 460 t.Run(path.Base(tt.repo)+"/"+tt.rev+"/"+tt.subdir, f) 461 } 462 } 463} 464 465var hgmap = map[string]string{ 466 "HEAD": "41964ddce1180313bdc01d0a39a2813344d6261d", // not tip due to bad hgrepo1 conversion 467 "9d02800338b8a55be062c838d1f02e0c5780b9eb": "8f49ee7a6ddcdec6f0112d9dca48d4a2e4c3c09e", 468 "76a00fb249b7f93091bc2c89a789dab1fc1bc26f": "88fde824ec8b41a76baa16b7e84212cee9f3edd0", 469 "ede458df7cd0fdca520df19a33158086a8a68e81": "41964ddce1180313bdc01d0a39a2813344d6261d", 470 "97f6aa59c81c623494825b43d39e445566e429a4": "c0cbbfb24c7c3c50c35c7b88e7db777da4ff625d", 471} 472 473var statTests = []struct { 474 repo string 475 rev string 476 err string 477 info *RevInfo 478}{ 479 { 480 repo: gitrepo1, 481 rev: "HEAD", 482 info: &RevInfo{ 483 Name: "ede458df7cd0fdca520df19a33158086a8a68e81", 484 Short: "ede458df7cd0", 485 Version: "ede458df7cd0fdca520df19a33158086a8a68e81", 486 Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC), 487 Tags: []string{"v1.2.3", "v1.2.4-annotated"}, 488 }, 489 }, 490 { 491 repo: gitrepo1, 492 rev: "v2", // branch 493 info: &RevInfo{ 494 Name: "9d02800338b8a55be062c838d1f02e0c5780b9eb", 495 Short: "9d02800338b8", 496 Version: "9d02800338b8a55be062c838d1f02e0c5780b9eb", 497 Time: time.Date(2018, 4, 17, 20, 00, 32, 0, time.UTC), 498 Tags: []string{"v2.0.2"}, 499 }, 500 }, 501 { 502 repo: gitrepo1, 503 rev: "v2.3.4", // badly-named branch (semver should be a tag) 504 info: &RevInfo{ 505 Name: "76a00fb249b7f93091bc2c89a789dab1fc1bc26f", 506 Short: "76a00fb249b7", 507 Version: "76a00fb249b7f93091bc2c89a789dab1fc1bc26f", 508 Time: time.Date(2018, 4, 17, 19, 45, 48, 0, time.UTC), 509 Tags: []string{"v2.0.1", "v2.3"}, 510 }, 511 }, 512 { 513 repo: gitrepo1, 514 rev: "v2.3", // badly-named tag (we only respect full semver v2.3.0) 515 info: &RevInfo{ 516 Name: "76a00fb249b7f93091bc2c89a789dab1fc1bc26f", 517 Short: "76a00fb249b7", 518 Version: "v2.3", 519 Time: time.Date(2018, 4, 17, 19, 45, 48, 0, time.UTC), 520 Tags: []string{"v2.0.1", "v2.3"}, 521 }, 522 }, 523 { 524 repo: gitrepo1, 525 rev: "v1.2.3", // tag 526 info: &RevInfo{ 527 Name: "ede458df7cd0fdca520df19a33158086a8a68e81", 528 Short: "ede458df7cd0", 529 Version: "v1.2.3", 530 Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC), 531 Tags: []string{"v1.2.3", "v1.2.4-annotated"}, 532 }, 533 }, 534 { 535 repo: gitrepo1, 536 rev: "ede458df", // hash prefix in refs 537 info: &RevInfo{ 538 Name: "ede458df7cd0fdca520df19a33158086a8a68e81", 539 Short: "ede458df7cd0", 540 Version: "ede458df7cd0fdca520df19a33158086a8a68e81", 541 Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC), 542 Tags: []string{"v1.2.3", "v1.2.4-annotated"}, 543 }, 544 }, 545 { 546 repo: gitrepo1, 547 rev: "97f6aa59", // hash prefix not in refs 548 info: &RevInfo{ 549 Name: "97f6aa59c81c623494825b43d39e445566e429a4", 550 Short: "97f6aa59c81c", 551 Version: "97f6aa59c81c623494825b43d39e445566e429a4", 552 Time: time.Date(2018, 4, 17, 20, 0, 19, 0, time.UTC), 553 }, 554 }, 555 { 556 repo: gitrepo1, 557 rev: "v1.2.4-annotated", // annotated tag uses unwrapped commit hash 558 info: &RevInfo{ 559 Name: "ede458df7cd0fdca520df19a33158086a8a68e81", 560 Short: "ede458df7cd0", 561 Version: "v1.2.4-annotated", 562 Time: time.Date(2018, 4, 17, 19, 43, 22, 0, time.UTC), 563 Tags: []string{"v1.2.3", "v1.2.4-annotated"}, 564 }, 565 }, 566 { 567 repo: gitrepo1, 568 rev: "aaaaaaaaab", 569 err: "unknown revision", 570 }, 571} 572 573func TestStat(t *testing.T) { 574 testenv.MustHaveExternalNetwork(t) 575 testenv.MustHaveExec(t) 576 577 for _, tt := range statTests { 578 f := func(t *testing.T) { 579 r, err := testRepo(tt.repo) 580 if err != nil { 581 t.Fatal(err) 582 } 583 info, err := r.Stat(tt.rev) 584 if err != nil { 585 if tt.err == "" { 586 t.Fatalf("Stat: unexpected error %v", err) 587 } 588 if !strings.Contains(err.Error(), tt.err) { 589 t.Fatalf("Stat: wrong error %q, want %q", err, tt.err) 590 } 591 if info != nil { 592 t.Errorf("Stat: non-nil info with error %q", err) 593 } 594 return 595 } 596 if !reflect.DeepEqual(info, tt.info) { 597 t.Errorf("Stat: incorrect info\nhave %+v\nwant %+v", *info, *tt.info) 598 } 599 } 600 t.Run(path.Base(tt.repo)+"/"+tt.rev, f) 601 if tt.repo == gitrepo1 { 602 for _, tt.repo = range altRepos { 603 old := tt 604 var m map[string]string 605 if tt.repo == hgrepo1 { 606 m = hgmap 607 } 608 if tt.info != nil { 609 info := *tt.info 610 tt.info = &info 611 tt.info.Name = remap(tt.info.Name, m) 612 tt.info.Version = remap(tt.info.Version, m) 613 tt.info.Short = remap(tt.info.Short, m) 614 } 615 tt.rev = remap(tt.rev, m) 616 t.Run(path.Base(tt.repo)+"/"+tt.rev, f) 617 tt = old 618 } 619 } 620 } 621} 622 623func remap(name string, m map[string]string) string { 624 if m[name] != "" { 625 return m[name] 626 } 627 if AllHex(name) { 628 for k, v := range m { 629 if strings.HasPrefix(k, name) { 630 return v[:len(name)] 631 } 632 } 633 } 634 return name 635} 636