1// Copyright 2022 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 main_test 6 7import ( 8 cmdcovdata "cmd/covdata" 9 "flag" 10 "fmt" 11 "internal/coverage/pods" 12 "internal/goexperiment" 13 "internal/testenv" 14 "log" 15 "os" 16 "path/filepath" 17 "regexp" 18 "strconv" 19 "strings" 20 "sync" 21 "testing" 22) 23 24// testcovdata returns the path to the unit test executable to be used as 25// standin for 'go tool covdata'. 26func testcovdata(t testing.TB) string { 27 exe, err := os.Executable() 28 if err != nil { 29 t.Helper() 30 t.Fatal(err) 31 } 32 return exe 33} 34 35// Top level tempdir for test. 36var testTempDir string 37 38// If set, this will preserve all the tmpdir files from the test run. 39var preserveTmp = flag.Bool("preservetmp", false, "keep tmpdir files for debugging") 40 41// TestMain used here so that we can leverage the test executable 42// itself as a cmd/covdata executable; compare to similar usage in 43// the cmd/go tests. 44func TestMain(m *testing.M) { 45 // When CMDCOVDATA_TEST_RUN_MAIN is set, we're reusing the test 46 // binary as cmd/cover. In this case we run the main func exported 47 // via export_test.go, and exit; CMDCOVDATA_TEST_RUN_MAIN is set below 48 // for actual test invocations. 49 if os.Getenv("CMDCOVDATA_TEST_RUN_MAIN") != "" { 50 cmdcovdata.Main() 51 os.Exit(0) 52 } 53 flag.Parse() 54 topTmpdir, err := os.MkdirTemp("", "cmd-covdata-test-") 55 if err != nil { 56 log.Fatal(err) 57 } 58 testTempDir = topTmpdir 59 if !*preserveTmp { 60 defer os.RemoveAll(topTmpdir) 61 } else { 62 fmt.Fprintf(os.Stderr, "debug: preserving tmpdir %s\n", topTmpdir) 63 } 64 os.Setenv("CMDCOVDATA_TEST_RUN_MAIN", "true") 65 os.Exit(m.Run()) 66} 67 68var tdmu sync.Mutex 69var tdcount int 70 71func tempDir(t *testing.T) string { 72 tdmu.Lock() 73 dir := filepath.Join(testTempDir, fmt.Sprintf("%03d", tdcount)) 74 tdcount++ 75 if err := os.Mkdir(dir, 0777); err != nil { 76 t.Fatal(err) 77 } 78 defer tdmu.Unlock() 79 return dir 80} 81 82const debugtrace = false 83 84func gobuild(t *testing.T, indir string, bargs []string) { 85 t.Helper() 86 87 if debugtrace { 88 if indir != "" { 89 t.Logf("in dir %s: ", indir) 90 } 91 t.Logf("cmd: %s %+v\n", testenv.GoToolPath(t), bargs) 92 } 93 cmd := testenv.Command(t, testenv.GoToolPath(t), bargs...) 94 cmd.Dir = indir 95 b, err := cmd.CombinedOutput() 96 if len(b) != 0 { 97 t.Logf("## build output:\n%s", b) 98 } 99 if err != nil { 100 t.Fatalf("build error: %v", err) 101 } 102} 103 104func emitFile(t *testing.T, dst, src string) { 105 payload, err := os.ReadFile(src) 106 if err != nil { 107 t.Fatalf("error reading %q: %v", src, err) 108 } 109 if err := os.WriteFile(dst, payload, 0666); err != nil { 110 t.Fatalf("writing %q: %v", dst, err) 111 } 112} 113 114const mainPkgPath = "prog" 115 116func buildProg(t *testing.T, prog string, dir string, tag string, flags []string) (string, string) { 117 // Create subdirs. 118 subdir := filepath.Join(dir, prog+"dir"+tag) 119 if err := os.Mkdir(subdir, 0777); err != nil { 120 t.Fatalf("can't create outdir %s: %v", subdir, err) 121 } 122 depdir := filepath.Join(subdir, "dep") 123 if err := os.Mkdir(depdir, 0777); err != nil { 124 t.Fatalf("can't create outdir %s: %v", depdir, err) 125 } 126 127 // Emit program. 128 insrc := filepath.Join("testdata", prog+".go") 129 src := filepath.Join(subdir, prog+".go") 130 emitFile(t, src, insrc) 131 indep := filepath.Join("testdata", "dep.go") 132 dep := filepath.Join(depdir, "dep.go") 133 emitFile(t, dep, indep) 134 135 // Emit go.mod. 136 mod := filepath.Join(subdir, "go.mod") 137 modsrc := "\nmodule " + mainPkgPath + "\n\ngo 1.19\n" 138 if err := os.WriteFile(mod, []byte(modsrc), 0666); err != nil { 139 t.Fatal(err) 140 } 141 exepath := filepath.Join(subdir, prog+".exe") 142 bargs := []string{"build", "-cover", "-o", exepath} 143 bargs = append(bargs, flags...) 144 gobuild(t, subdir, bargs) 145 return exepath, subdir 146} 147 148type state struct { 149 dir string 150 exedir1 string 151 exedir2 string 152 exedir3 string 153 exepath1 string 154 exepath2 string 155 exepath3 string 156 tool string 157 outdirs [4]string 158} 159 160const debugWorkDir = false 161 162func TestCovTool(t *testing.T) { 163 testenv.MustHaveGoBuild(t) 164 if !goexperiment.CoverageRedesign { 165 t.Skipf("stubbed out due to goexperiment.CoverageRedesign=false") 166 } 167 dir := tempDir(t) 168 if testing.Short() { 169 t.Skip() 170 } 171 if debugWorkDir { 172 // debugging 173 dir = "/tmp/qqq" 174 os.RemoveAll(dir) 175 os.Mkdir(dir, 0777) 176 } 177 178 s := state{ 179 dir: dir, 180 } 181 s.exepath1, s.exedir1 = buildProg(t, "prog1", dir, "", nil) 182 s.exepath2, s.exedir2 = buildProg(t, "prog2", dir, "", nil) 183 flags := []string{"-covermode=atomic"} 184 s.exepath3, s.exedir3 = buildProg(t, "prog1", dir, "atomic", flags) 185 186 // Reuse unit test executable as tool to be tested. 187 s.tool = testcovdata(t) 188 189 // Create a few coverage output dirs. 190 for i := 0; i < 4; i++ { 191 d := filepath.Join(dir, fmt.Sprintf("covdata%d", i)) 192 s.outdirs[i] = d 193 if err := os.Mkdir(d, 0777); err != nil { 194 t.Fatalf("can't create outdir %s: %v", d, err) 195 } 196 } 197 198 // Run instrumented program to generate some coverage data output files, 199 // as follows: 200 // 201 // <tmp>/covdata0 -- prog1.go compiled -cover 202 // <tmp>/covdata1 -- prog1.go compiled -cover 203 // <tmp>/covdata2 -- prog1.go compiled -covermode=atomic 204 // <tmp>/covdata3 -- prog1.go compiled -covermode=atomic 205 // 206 for m := 0; m < 2; m++ { 207 for k := 0; k < 2; k++ { 208 args := []string{} 209 if k != 0 { 210 args = append(args, "foo", "bar") 211 } 212 for i := 0; i <= k; i++ { 213 exepath := s.exepath1 214 if m != 0 { 215 exepath = s.exepath3 216 } 217 cmd := testenv.Command(t, exepath, args...) 218 cmd.Env = append(cmd.Env, "GOCOVERDIR="+s.outdirs[m*2+k]) 219 b, err := cmd.CombinedOutput() 220 if len(b) != 0 { 221 t.Logf("## instrumented run output:\n%s", b) 222 } 223 if err != nil { 224 t.Fatalf("instrumented run error: %v", err) 225 } 226 } 227 } 228 } 229 230 // At this point we can fork off a bunch of child tests 231 // to check different tool modes. 232 t.Run("MergeSimple", func(t *testing.T) { 233 t.Parallel() 234 testMergeSimple(t, s, s.outdirs[0], s.outdirs[1], "set") 235 testMergeSimple(t, s, s.outdirs[2], s.outdirs[3], "atomic") 236 }) 237 t.Run("MergeSelect", func(t *testing.T) { 238 t.Parallel() 239 testMergeSelect(t, s, s.outdirs[0], s.outdirs[1], "set") 240 testMergeSelect(t, s, s.outdirs[2], s.outdirs[3], "atomic") 241 }) 242 t.Run("MergePcombine", func(t *testing.T) { 243 t.Parallel() 244 testMergeCombinePrograms(t, s) 245 }) 246 t.Run("Dump", func(t *testing.T) { 247 t.Parallel() 248 testDump(t, s) 249 }) 250 t.Run("Percent", func(t *testing.T) { 251 t.Parallel() 252 testPercent(t, s) 253 }) 254 t.Run("PkgList", func(t *testing.T) { 255 t.Parallel() 256 testPkgList(t, s) 257 }) 258 t.Run("Textfmt", func(t *testing.T) { 259 t.Parallel() 260 testTextfmt(t, s) 261 }) 262 t.Run("Subtract", func(t *testing.T) { 263 t.Parallel() 264 testSubtract(t, s) 265 }) 266 t.Run("Intersect", func(t *testing.T) { 267 t.Parallel() 268 testIntersect(t, s, s.outdirs[0], s.outdirs[1], "set") 269 testIntersect(t, s, s.outdirs[2], s.outdirs[3], "atomic") 270 }) 271 t.Run("CounterClash", func(t *testing.T) { 272 t.Parallel() 273 testCounterClash(t, s) 274 }) 275 t.Run("TestEmpty", func(t *testing.T) { 276 t.Parallel() 277 testEmpty(t, s) 278 }) 279 t.Run("TestCommandLineErrors", func(t *testing.T) { 280 t.Parallel() 281 testCommandLineErrors(t, s, s.outdirs[0]) 282 }) 283} 284 285const showToolInvocations = true 286 287func runToolOp(t *testing.T, s state, op string, args []string) []string { 288 // Perform tool run. 289 t.Helper() 290 args = append([]string{op}, args...) 291 if showToolInvocations { 292 t.Logf("%s cmd is: %s %+v", op, s.tool, args) 293 } 294 cmd := testenv.Command(t, s.tool, args...) 295 b, err := cmd.CombinedOutput() 296 if err != nil { 297 fmt.Fprintf(os.Stderr, "## %s output: %s\n", op, b) 298 t.Fatalf("%q run error: %v", op, err) 299 } 300 output := strings.TrimSpace(string(b)) 301 lines := strings.Split(output, "\n") 302 if len(lines) == 1 && lines[0] == "" { 303 lines = nil 304 } 305 return lines 306} 307 308func testDump(t *testing.T, s state) { 309 // Run the dumper on the two dirs we generated. 310 dargs := []string{"-pkg=" + mainPkgPath, "-live", "-i=" + s.outdirs[0] + "," + s.outdirs[1]} 311 lines := runToolOp(t, s, "debugdump", dargs) 312 313 // Sift through the output to make sure it has some key elements. 314 testpoints := []struct { 315 tag string 316 re *regexp.Regexp 317 }{ 318 { 319 "args", 320 regexp.MustCompile(`^data file .+ GOOS=.+ GOARCH=.+ program args: .+$`), 321 }, 322 { 323 "main package", 324 regexp.MustCompile(`^Package path: ` + mainPkgPath + `\s*$`), 325 }, 326 { 327 "main function", 328 regexp.MustCompile(`^Func: main\s*$`), 329 }, 330 } 331 332 bad := false 333 for _, testpoint := range testpoints { 334 found := false 335 for _, line := range lines { 336 if m := testpoint.re.FindStringSubmatch(line); m != nil { 337 found = true 338 break 339 } 340 } 341 if !found { 342 t.Errorf("dump output regexp match failed for %q", testpoint.tag) 343 bad = true 344 } 345 } 346 if bad { 347 dumplines(lines) 348 } 349} 350 351func testPercent(t *testing.T, s state) { 352 // Run the dumper on the two dirs we generated. 353 dargs := []string{"-pkg=" + mainPkgPath, "-i=" + s.outdirs[0] + "," + s.outdirs[1]} 354 lines := runToolOp(t, s, "percent", dargs) 355 356 // Sift through the output to make sure it has the needful. 357 testpoints := []struct { 358 tag string 359 re *regexp.Regexp 360 }{ 361 { 362 "statement coverage percent", 363 regexp.MustCompile(`coverage: \d+\.\d% of statements\s*$`), 364 }, 365 } 366 367 bad := false 368 for _, testpoint := range testpoints { 369 found := false 370 for _, line := range lines { 371 if m := testpoint.re.FindStringSubmatch(line); m != nil { 372 found = true 373 break 374 } 375 } 376 if !found { 377 t.Errorf("percent output regexp match failed for %s", testpoint.tag) 378 bad = true 379 } 380 } 381 if bad { 382 dumplines(lines) 383 } 384} 385 386func testPkgList(t *testing.T, s state) { 387 dargs := []string{"-i=" + s.outdirs[0] + "," + s.outdirs[1]} 388 lines := runToolOp(t, s, "pkglist", dargs) 389 390 want := []string{mainPkgPath, mainPkgPath + "/dep"} 391 bad := false 392 if len(lines) != 2 { 393 t.Errorf("expect pkglist to return two lines") 394 bad = true 395 } else { 396 for i := 0; i < 2; i++ { 397 lines[i] = strings.TrimSpace(lines[i]) 398 if want[i] != lines[i] { 399 t.Errorf("line %d want %s got %s", i, want[i], lines[i]) 400 bad = true 401 } 402 } 403 } 404 if bad { 405 dumplines(lines) 406 } 407} 408 409func testTextfmt(t *testing.T, s state) { 410 outf := s.dir + "/" + "t.txt" 411 dargs := []string{"-pkg=" + mainPkgPath, "-i=" + s.outdirs[0] + "," + s.outdirs[1], 412 "-o", outf} 413 lines := runToolOp(t, s, "textfmt", dargs) 414 415 // No output expected. 416 if len(lines) != 0 { 417 dumplines(lines) 418 t.Errorf("unexpected output from go tool covdata textfmt") 419 } 420 421 // Open and read the first few bits of the file. 422 payload, err := os.ReadFile(outf) 423 if err != nil { 424 t.Errorf("opening %s: %v\n", outf, err) 425 } 426 lines = strings.Split(string(payload), "\n") 427 want0 := "mode: set" 428 if lines[0] != want0 { 429 dumplines(lines[0:10]) 430 t.Errorf("textfmt: want %s got %s", want0, lines[0]) 431 } 432 want1 := mainPkgPath + "/prog1.go:13.14,15.2 1 1" 433 if lines[1] != want1 { 434 dumplines(lines[0:10]) 435 t.Errorf("textfmt: want %s got %s", want1, lines[1]) 436 } 437} 438 439func dumplines(lines []string) { 440 for i := range lines { 441 fmt.Fprintf(os.Stderr, "%s\n", lines[i]) 442 } 443} 444 445type dumpCheck struct { 446 tag string 447 re *regexp.Regexp 448 negate bool 449 nonzero bool 450 zero bool 451} 452 453// runDumpChecks examines the output of "go tool covdata debugdump" 454// for a given output directory, looking for the presence or absence 455// of specific markers. 456func runDumpChecks(t *testing.T, s state, dir string, flags []string, checks []dumpCheck) { 457 dargs := []string{"-i", dir} 458 dargs = append(dargs, flags...) 459 lines := runToolOp(t, s, "debugdump", dargs) 460 if len(lines) == 0 { 461 t.Fatalf("dump run produced no output") 462 } 463 464 bad := false 465 for _, check := range checks { 466 found := false 467 for _, line := range lines { 468 if m := check.re.FindStringSubmatch(line); m != nil { 469 found = true 470 if check.negate { 471 t.Errorf("tag %q: unexpected match", check.tag) 472 bad = true 473 474 } 475 if check.nonzero || check.zero { 476 if len(m) < 2 { 477 t.Errorf("tag %s: submatch failed (short m)", check.tag) 478 bad = true 479 continue 480 } 481 if m[1] == "" { 482 t.Errorf("tag %s: submatch failed", check.tag) 483 bad = true 484 continue 485 } 486 i, err := strconv.Atoi(m[1]) 487 if err != nil { 488 t.Errorf("tag %s: match Atoi failed on %s", 489 check.tag, m[1]) 490 continue 491 } 492 if check.zero && i != 0 { 493 t.Errorf("tag %s: match zero failed on %s", 494 check.tag, m[1]) 495 } else if check.nonzero && i == 0 { 496 t.Errorf("tag %s: match nonzero failed on %s", 497 check.tag, m[1]) 498 } 499 } 500 break 501 } 502 } 503 if !found && !check.negate { 504 t.Errorf("dump output regexp match failed for %s", check.tag) 505 bad = true 506 } 507 } 508 if bad { 509 fmt.Printf("output from 'dump' run:\n") 510 dumplines(lines) 511 } 512} 513 514func testMergeSimple(t *testing.T, s state, indir1, indir2, tag string) { 515 outdir := filepath.Join(s.dir, "simpleMergeOut"+tag) 516 if err := os.Mkdir(outdir, 0777); err != nil { 517 t.Fatalf("can't create outdir %s: %v", outdir, err) 518 } 519 520 // Merge the two dirs into a final result. 521 ins := fmt.Sprintf("-i=%s,%s", indir1, indir2) 522 out := fmt.Sprintf("-o=%s", outdir) 523 margs := []string{ins, out} 524 lines := runToolOp(t, s, "merge", margs) 525 if len(lines) != 0 { 526 t.Errorf("merge run produced %d lines of unexpected output", len(lines)) 527 dumplines(lines) 528 } 529 530 // We expect the merge tool to produce exactly two files: a meta 531 // data file and a counter file. If we get more than just this one 532 // pair, something went wrong. 533 podlist, err := pods.CollectPods([]string{outdir}, true) 534 if err != nil { 535 t.Fatal(err) 536 } 537 if len(podlist) != 1 { 538 t.Fatalf("expected 1 pod, got %d pods", len(podlist)) 539 } 540 ncdfs := len(podlist[0].CounterDataFiles) 541 if ncdfs != 1 { 542 t.Fatalf("expected 1 counter data file, got %d", ncdfs) 543 } 544 545 // Sift through the output to make sure it has some key elements. 546 // In particular, we want to see entries for all three functions 547 // ("first", "second", and "third"). 548 testpoints := []dumpCheck{ 549 { 550 tag: "first function", 551 re: regexp.MustCompile(`^Func: first\s*$`), 552 }, 553 { 554 tag: "second function", 555 re: regexp.MustCompile(`^Func: second\s*$`), 556 }, 557 { 558 tag: "third function", 559 re: regexp.MustCompile(`^Func: third\s*$`), 560 }, 561 { 562 tag: "third function unit 0", 563 re: regexp.MustCompile(`^0: L23:C23 -- L24:C12 NS=1 = (\d+)$`), 564 nonzero: true, 565 }, 566 { 567 tag: "third function unit 1", 568 re: regexp.MustCompile(`^1: L27:C2 -- L28:C10 NS=2 = (\d+)$`), 569 nonzero: true, 570 }, 571 { 572 tag: "third function unit 2", 573 re: regexp.MustCompile(`^2: L24:C12 -- L26:C3 NS=1 = (\d+)$`), 574 nonzero: true, 575 }, 576 } 577 flags := []string{"-live", "-pkg=" + mainPkgPath} 578 runDumpChecks(t, s, outdir, flags, testpoints) 579} 580 581func testMergeSelect(t *testing.T, s state, indir1, indir2 string, tag string) { 582 outdir := filepath.Join(s.dir, "selectMergeOut"+tag) 583 if err := os.Mkdir(outdir, 0777); err != nil { 584 t.Fatalf("can't create outdir %s: %v", outdir, err) 585 } 586 587 // Merge two input dirs into a final result, but filter 588 // based on package. 589 ins := fmt.Sprintf("-i=%s,%s", indir1, indir2) 590 out := fmt.Sprintf("-o=%s", outdir) 591 margs := []string{"-pkg=" + mainPkgPath + "/dep", ins, out} 592 lines := runToolOp(t, s, "merge", margs) 593 if len(lines) != 0 { 594 t.Errorf("merge run produced %d lines of unexpected output", len(lines)) 595 dumplines(lines) 596 } 597 598 // Dump the files in the merged output dir and examine the result. 599 // We expect to see only the functions in package "dep". 600 dargs := []string{"-i=" + outdir} 601 lines = runToolOp(t, s, "debugdump", dargs) 602 if len(lines) == 0 { 603 t.Fatalf("dump run produced no output") 604 } 605 want := map[string]int{ 606 "Package path: " + mainPkgPath + "/dep": 0, 607 "Func: Dep1": 0, 608 "Func: PDep": 0, 609 } 610 bad := false 611 for _, line := range lines { 612 if v, ok := want[line]; ok { 613 if v != 0 { 614 t.Errorf("duplicate line %s", line) 615 bad = true 616 break 617 } 618 want[line] = 1 619 continue 620 } 621 // no other functions or packages expected. 622 if strings.HasPrefix(line, "Func:") || strings.HasPrefix(line, "Package path:") { 623 t.Errorf("unexpected line: %s", line) 624 bad = true 625 break 626 } 627 } 628 if bad { 629 dumplines(lines) 630 } 631} 632 633func testMergeCombinePrograms(t *testing.T, s state) { 634 635 // Run the new program, emitting output into a new set 636 // of outdirs. 637 runout := [2]string{} 638 for k := 0; k < 2; k++ { 639 runout[k] = filepath.Join(s.dir, fmt.Sprintf("newcovdata%d", k)) 640 if err := os.Mkdir(runout[k], 0777); err != nil { 641 t.Fatalf("can't create outdir %s: %v", runout[k], err) 642 } 643 args := []string{} 644 if k != 0 { 645 args = append(args, "foo", "bar") 646 } 647 cmd := testenv.Command(t, s.exepath2, args...) 648 cmd.Env = append(cmd.Env, "GOCOVERDIR="+runout[k]) 649 b, err := cmd.CombinedOutput() 650 if len(b) != 0 { 651 t.Logf("## instrumented run output:\n%s", b) 652 } 653 if err != nil { 654 t.Fatalf("instrumented run error: %v", err) 655 } 656 } 657 658 // Create out dir for -pcombine merge. 659 moutdir := filepath.Join(s.dir, "mergeCombineOut") 660 if err := os.Mkdir(moutdir, 0777); err != nil { 661 t.Fatalf("can't create outdir %s: %v", moutdir, err) 662 } 663 664 // Run a merge over both programs, using the -pcombine 665 // flag to do maximal combining. 666 ins := fmt.Sprintf("-i=%s,%s,%s,%s", s.outdirs[0], s.outdirs[1], 667 runout[0], runout[1]) 668 out := fmt.Sprintf("-o=%s", moutdir) 669 margs := []string{"-pcombine", ins, out} 670 lines := runToolOp(t, s, "merge", margs) 671 if len(lines) != 0 { 672 t.Errorf("merge run produced unexpected output: %v", lines) 673 } 674 675 // We expect the merge tool to produce exactly two files: a meta 676 // data file and a counter file. If we get more than just this one 677 // pair, something went wrong. 678 podlist, err := pods.CollectPods([]string{moutdir}, true) 679 if err != nil { 680 t.Fatal(err) 681 } 682 if len(podlist) != 1 { 683 t.Fatalf("expected 1 pod, got %d pods", len(podlist)) 684 } 685 ncdfs := len(podlist[0].CounterDataFiles) 686 if ncdfs != 1 { 687 t.Fatalf("expected 1 counter data file, got %d", ncdfs) 688 } 689 690 // Sift through the output to make sure it has some key elements. 691 testpoints := []dumpCheck{ 692 { 693 tag: "first function", 694 re: regexp.MustCompile(`^Func: first\s*$`), 695 }, 696 { 697 tag: "sixth function", 698 re: regexp.MustCompile(`^Func: sixth\s*$`), 699 }, 700 } 701 702 flags := []string{"-live", "-pkg=" + mainPkgPath} 703 runDumpChecks(t, s, moutdir, flags, testpoints) 704} 705 706func testSubtract(t *testing.T, s state) { 707 // Create out dir for subtract merge. 708 soutdir := filepath.Join(s.dir, "subtractOut") 709 if err := os.Mkdir(soutdir, 0777); err != nil { 710 t.Fatalf("can't create outdir %s: %v", soutdir, err) 711 } 712 713 // Subtract the two dirs into a final result. 714 ins := fmt.Sprintf("-i=%s,%s", s.outdirs[0], s.outdirs[1]) 715 out := fmt.Sprintf("-o=%s", soutdir) 716 sargs := []string{ins, out} 717 lines := runToolOp(t, s, "subtract", sargs) 718 if len(lines) != 0 { 719 t.Errorf("subtract run produced unexpected output: %+v", lines) 720 } 721 722 // Dump the files in the subtract output dir and examine the result. 723 dargs := []string{"-pkg=" + mainPkgPath, "-live", "-i=" + soutdir} 724 lines = runToolOp(t, s, "debugdump", dargs) 725 if len(lines) == 0 { 726 t.Errorf("dump run produced no output") 727 } 728 729 // Vet the output. 730 testpoints := []dumpCheck{ 731 { 732 tag: "first function", 733 re: regexp.MustCompile(`^Func: first\s*$`), 734 }, 735 { 736 tag: "dep function", 737 re: regexp.MustCompile(`^Func: Dep1\s*$`), 738 }, 739 { 740 tag: "third function", 741 re: regexp.MustCompile(`^Func: third\s*$`), 742 }, 743 { 744 tag: "third function unit 0", 745 re: regexp.MustCompile(`^0: L23:C23 -- L24:C12 NS=1 = (\d+)$`), 746 zero: true, 747 }, 748 { 749 tag: "third function unit 1", 750 re: regexp.MustCompile(`^1: L27:C2 -- L28:C10 NS=2 = (\d+)$`), 751 nonzero: true, 752 }, 753 { 754 tag: "third function unit 2", 755 re: regexp.MustCompile(`^2: L24:C12 -- L26:C3 NS=1 = (\d+)$`), 756 zero: true, 757 }, 758 } 759 flags := []string{} 760 runDumpChecks(t, s, soutdir, flags, testpoints) 761} 762 763func testIntersect(t *testing.T, s state, indir1, indir2, tag string) { 764 // Create out dir for intersection. 765 ioutdir := filepath.Join(s.dir, "intersectOut"+tag) 766 if err := os.Mkdir(ioutdir, 0777); err != nil { 767 t.Fatalf("can't create outdir %s: %v", ioutdir, err) 768 } 769 770 // Intersect the two dirs into a final result. 771 ins := fmt.Sprintf("-i=%s,%s", indir1, indir2) 772 out := fmt.Sprintf("-o=%s", ioutdir) 773 sargs := []string{ins, out} 774 lines := runToolOp(t, s, "intersect", sargs) 775 if len(lines) != 0 { 776 t.Errorf("intersect run produced unexpected output: %+v", lines) 777 } 778 779 // Dump the files in the subtract output dir and examine the result. 780 dargs := []string{"-pkg=" + mainPkgPath, "-live", "-i=" + ioutdir} 781 lines = runToolOp(t, s, "debugdump", dargs) 782 if len(lines) == 0 { 783 t.Errorf("dump run produced no output") 784 } 785 786 // Vet the output. 787 testpoints := []dumpCheck{ 788 { 789 tag: "first function", 790 re: regexp.MustCompile(`^Func: first\s*$`), 791 negate: true, 792 }, 793 { 794 tag: "third function", 795 re: regexp.MustCompile(`^Func: third\s*$`), 796 }, 797 } 798 flags := []string{"-live"} 799 runDumpChecks(t, s, ioutdir, flags, testpoints) 800} 801 802func testCounterClash(t *testing.T, s state) { 803 // Create out dir. 804 ccoutdir := filepath.Join(s.dir, "ccOut") 805 if err := os.Mkdir(ccoutdir, 0777); err != nil { 806 t.Fatalf("can't create outdir %s: %v", ccoutdir, err) 807 } 808 809 // Try to merge covdata0 (from prog1.go -countermode=set) with 810 // covdata1 (from prog1.go -countermode=atomic"). This should 811 // work properly, but result in multiple meta-data files. 812 ins := fmt.Sprintf("-i=%s,%s", s.outdirs[0], s.outdirs[3]) 813 out := fmt.Sprintf("-o=%s", ccoutdir) 814 args := append([]string{}, "merge", ins, out, "-pcombine") 815 if debugtrace { 816 t.Logf("cc merge command is %s %v\n", s.tool, args) 817 } 818 cmd := testenv.Command(t, s.tool, args...) 819 b, err := cmd.CombinedOutput() 820 t.Logf("%% output: %s\n", string(b)) 821 if err != nil { 822 t.Fatalf("clash merge failed: %v", err) 823 } 824 825 // Ask for a textual report from the two dirs. Here we have 826 // to report the mode clash. 827 out = "-o=" + filepath.Join(ccoutdir, "file.txt") 828 args = append([]string{}, "textfmt", ins, out) 829 if debugtrace { 830 t.Logf("clash textfmt command is %s %v\n", s.tool, args) 831 } 832 cmd = testenv.Command(t, s.tool, args...) 833 b, err = cmd.CombinedOutput() 834 t.Logf("%% output: %s\n", string(b)) 835 if err == nil { 836 t.Fatalf("expected mode clash") 837 } 838 got := string(b) 839 want := "counter mode clash while reading meta-data" 840 if !strings.Contains(got, want) { 841 t.Errorf("counter clash textfmt: wanted %s got %s", want, got) 842 } 843} 844 845func testEmpty(t *testing.T, s state) { 846 847 // Create a new empty directory. 848 empty := filepath.Join(s.dir, "empty") 849 if err := os.Mkdir(empty, 0777); err != nil { 850 t.Fatalf("can't create dir %s: %v", empty, err) 851 } 852 853 // Create out dir. 854 eoutdir := filepath.Join(s.dir, "emptyOut") 855 if err := os.Mkdir(eoutdir, 0777); err != nil { 856 t.Fatalf("can't create outdir %s: %v", eoutdir, err) 857 } 858 859 // Run various operations (merge, dump, textfmt, and so on) 860 // using the empty directory. We're not interested in the output 861 // here, just making sure that you can do these runs without 862 // any error or crash. 863 864 scenarios := []struct { 865 tag string 866 args []string 867 }{ 868 { 869 tag: "merge", 870 args: []string{"merge", "-o", eoutdir}, 871 }, 872 { 873 tag: "textfmt", 874 args: []string{"textfmt", "-o", filepath.Join(eoutdir, "foo.txt")}, 875 }, 876 { 877 tag: "func", 878 args: []string{"func"}, 879 }, 880 { 881 tag: "pkglist", 882 args: []string{"pkglist"}, 883 }, 884 { 885 tag: "debugdump", 886 args: []string{"debugdump"}, 887 }, 888 { 889 tag: "percent", 890 args: []string{"percent"}, 891 }, 892 } 893 894 for _, x := range scenarios { 895 ins := fmt.Sprintf("-i=%s", empty) 896 args := append([]string{}, x.args...) 897 args = append(args, ins) 898 if false { 899 t.Logf("cmd is %s %v\n", s.tool, args) 900 } 901 cmd := testenv.Command(t, s.tool, args...) 902 b, err := cmd.CombinedOutput() 903 t.Logf("%% output: %s\n", string(b)) 904 if err != nil { 905 t.Fatalf("command %s %+v failed with %v", 906 s.tool, x.args, err) 907 } 908 } 909} 910 911func testCommandLineErrors(t *testing.T, s state, outdir string) { 912 913 // Create out dir. 914 eoutdir := filepath.Join(s.dir, "errorsOut") 915 if err := os.Mkdir(eoutdir, 0777); err != nil { 916 t.Fatalf("can't create outdir %s: %v", eoutdir, err) 917 } 918 919 // Run various operations (merge, dump, textfmt, and so on) 920 // using the empty directory. We're not interested in the output 921 // here, just making sure that you can do these runs without 922 // any error or crash. 923 924 scenarios := []struct { 925 tag string 926 args []string 927 exp string 928 }{ 929 { 930 tag: "input missing", 931 args: []string{"merge", "-o", eoutdir, "-i", "not there"}, 932 exp: "error: reading inputs: ", 933 }, 934 { 935 tag: "badv", 936 args: []string{"textfmt", "-i", outdir, "-v=abc"}, 937 }, 938 } 939 940 for _, x := range scenarios { 941 args := append([]string{}, x.args...) 942 if false { 943 t.Logf("cmd is %s %v\n", s.tool, args) 944 } 945 cmd := testenv.Command(t, s.tool, args...) 946 b, err := cmd.CombinedOutput() 947 if err == nil { 948 t.Logf("%% output: %s\n", string(b)) 949 t.Fatalf("command %s %+v unexpectedly succeeded", 950 s.tool, x.args) 951 } else { 952 if !strings.Contains(string(b), x.exp) { 953 t.Fatalf("command %s %+v:\ngot:\n%s\nwanted to see: %v\n", 954 s.tool, x.args, string(b), x.exp) 955 } 956 } 957 } 958} 959