| // Copyright 2022 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package main_test |
| |
| import ( |
| cmdcovdata "cmd/covdata" |
| "flag" |
| "fmt" |
| "internal/coverage/pods" |
| "internal/goexperiment" |
| "internal/testenv" |
| "log" |
| "os" |
| "path/filepath" |
| "regexp" |
| "strconv" |
| "strings" |
| "sync" |
| "testing" |
| ) |
| |
| // testcovdata returns the path to the unit test executable to be used as |
| // standin for 'go tool covdata'. |
| func testcovdata(t testing.TB) string { |
| exe, err := os.Executable() |
| if err != nil { |
| t.Helper() |
| t.Fatal(err) |
| } |
| return exe |
| } |
| |
| // Top level tempdir for test. |
| var testTempDir string |
| |
| // If set, this will preserve all the tmpdir files from the test run. |
| var preserveTmp = flag.Bool("preservetmp", false, "keep tmpdir files for debugging") |
| |
| // TestMain used here so that we can leverage the test executable |
| // itself as a cmd/covdata executable; compare to similar usage in |
| // the cmd/go tests. |
| func TestMain(m *testing.M) { |
| // When CMDCOVDATA_TEST_RUN_MAIN is set, we're reusing the test |
| // binary as cmd/cover. In this case we run the main func exported |
| // via export_test.go, and exit; CMDCOVDATA_TEST_RUN_MAIN is set below |
| // for actual test invocations. |
| if os.Getenv("CMDCOVDATA_TEST_RUN_MAIN") != "" { |
| cmdcovdata.Main() |
| os.Exit(0) |
| } |
| flag.Parse() |
| topTmpdir, err := os.MkdirTemp("", "cmd-covdata-test-") |
| if err != nil { |
| log.Fatal(err) |
| } |
| testTempDir = topTmpdir |
| if !*preserveTmp { |
| defer os.RemoveAll(topTmpdir) |
| } else { |
| fmt.Fprintf(os.Stderr, "debug: preserving tmpdir %s\n", topTmpdir) |
| } |
| os.Setenv("CMDCOVDATA_TEST_RUN_MAIN", "true") |
| os.Exit(m.Run()) |
| } |
| |
| var tdmu sync.Mutex |
| var tdcount int |
| |
| func tempDir(t *testing.T) string { |
| tdmu.Lock() |
| dir := filepath.Join(testTempDir, fmt.Sprintf("%03d", tdcount)) |
| tdcount++ |
| if err := os.Mkdir(dir, 0777); err != nil { |
| t.Fatal(err) |
| } |
| defer tdmu.Unlock() |
| return dir |
| } |
| |
| const debugtrace = false |
| |
| func gobuild(t *testing.T, indir string, bargs []string) { |
| t.Helper() |
| |
| if debugtrace { |
| if indir != "" { |
| t.Logf("in dir %s: ", indir) |
| } |
| t.Logf("cmd: %s %+v\n", testenv.GoToolPath(t), bargs) |
| } |
| cmd := testenv.Command(t, testenv.GoToolPath(t), bargs...) |
| cmd.Dir = indir |
| b, err := cmd.CombinedOutput() |
| if len(b) != 0 { |
| t.Logf("## build output:\n%s", b) |
| } |
| if err != nil { |
| t.Fatalf("build error: %v", err) |
| } |
| } |
| |
| func emitFile(t *testing.T, dst, src string) { |
| payload, err := os.ReadFile(src) |
| if err != nil { |
| t.Fatalf("error reading %q: %v", src, err) |
| } |
| if err := os.WriteFile(dst, payload, 0666); err != nil { |
| t.Fatalf("writing %q: %v", dst, err) |
| } |
| } |
| |
| const mainPkgPath = "prog" |
| |
| func buildProg(t *testing.T, prog string, dir string, tag string, flags []string) (string, string) { |
| // Create subdirs. |
| subdir := filepath.Join(dir, prog+"dir"+tag) |
| if err := os.Mkdir(subdir, 0777); err != nil { |
| t.Fatalf("can't create outdir %s: %v", subdir, err) |
| } |
| depdir := filepath.Join(subdir, "dep") |
| if err := os.Mkdir(depdir, 0777); err != nil { |
| t.Fatalf("can't create outdir %s: %v", depdir, err) |
| } |
| |
| // Emit program. |
| insrc := filepath.Join("testdata", prog+".go") |
| src := filepath.Join(subdir, prog+".go") |
| emitFile(t, src, insrc) |
| indep := filepath.Join("testdata", "dep.go") |
| dep := filepath.Join(depdir, "dep.go") |
| emitFile(t, dep, indep) |
| |
| // Emit go.mod. |
| mod := filepath.Join(subdir, "go.mod") |
| modsrc := "\nmodule " + mainPkgPath + "\n\ngo 1.19\n" |
| if err := os.WriteFile(mod, []byte(modsrc), 0666); err != nil { |
| t.Fatal(err) |
| } |
| exepath := filepath.Join(subdir, prog+".exe") |
| bargs := []string{"build", "-cover", "-o", exepath} |
| bargs = append(bargs, flags...) |
| gobuild(t, subdir, bargs) |
| return exepath, subdir |
| } |
| |
| type state struct { |
| dir string |
| exedir1 string |
| exedir2 string |
| exedir3 string |
| exepath1 string |
| exepath2 string |
| exepath3 string |
| tool string |
| outdirs [4]string |
| } |
| |
| const debugWorkDir = false |
| |
| func TestCovTool(t *testing.T) { |
| testenv.MustHaveGoBuild(t) |
| if !goexperiment.CoverageRedesign { |
| t.Skipf("stubbed out due to goexperiment.CoverageRedesign=false") |
| } |
| dir := tempDir(t) |
| if testing.Short() { |
| t.Skip() |
| } |
| if debugWorkDir { |
| // debugging |
| dir = "/tmp/qqq" |
| os.RemoveAll(dir) |
| os.Mkdir(dir, 0777) |
| } |
| |
| s := state{ |
| dir: dir, |
| } |
| s.exepath1, s.exedir1 = buildProg(t, "prog1", dir, "", nil) |
| s.exepath2, s.exedir2 = buildProg(t, "prog2", dir, "", nil) |
| flags := []string{"-covermode=atomic"} |
| s.exepath3, s.exedir3 = buildProg(t, "prog1", dir, "atomic", flags) |
| |
| // Reuse unit test executable as tool to be tested. |
| s.tool = testcovdata(t) |
| |
| // Create a few coverage output dirs. |
| for i := 0; i < 4; i++ { |
| d := filepath.Join(dir, fmt.Sprintf("covdata%d", i)) |
| s.outdirs[i] = d |
| if err := os.Mkdir(d, 0777); err != nil { |
| t.Fatalf("can't create outdir %s: %v", d, err) |
| } |
| } |
| |
| // Run instrumented program to generate some coverage data output files, |
| // as follows: |
| // |
| // <tmp>/covdata0 -- prog1.go compiled -cover |
| // <tmp>/covdata1 -- prog1.go compiled -cover |
| // <tmp>/covdata2 -- prog1.go compiled -covermode=atomic |
| // <tmp>/covdata3 -- prog1.go compiled -covermode=atomic |
| // |
| for m := 0; m < 2; m++ { |
| for k := 0; k < 2; k++ { |
| args := []string{} |
| if k != 0 { |
| args = append(args, "foo", "bar") |
| } |
| for i := 0; i <= k; i++ { |
| exepath := s.exepath1 |
| if m != 0 { |
| exepath = s.exepath3 |
| } |
| cmd := testenv.Command(t, exepath, args...) |
| cmd.Env = append(cmd.Env, "GOCOVERDIR="+s.outdirs[m*2+k]) |
| b, err := cmd.CombinedOutput() |
| if len(b) != 0 { |
| t.Logf("## instrumented run output:\n%s", b) |
| } |
| if err != nil { |
| t.Fatalf("instrumented run error: %v", err) |
| } |
| } |
| } |
| } |
| |
| // At this point we can fork off a bunch of child tests |
| // to check different tool modes. |
| t.Run("MergeSimple", func(t *testing.T) { |
| t.Parallel() |
| testMergeSimple(t, s, s.outdirs[0], s.outdirs[1], "set") |
| testMergeSimple(t, s, s.outdirs[2], s.outdirs[3], "atomic") |
| }) |
| t.Run("MergeSelect", func(t *testing.T) { |
| t.Parallel() |
| testMergeSelect(t, s, s.outdirs[0], s.outdirs[1], "set") |
| testMergeSelect(t, s, s.outdirs[2], s.outdirs[3], "atomic") |
| }) |
| t.Run("MergePcombine", func(t *testing.T) { |
| t.Parallel() |
| testMergeCombinePrograms(t, s) |
| }) |
| t.Run("Dump", func(t *testing.T) { |
| t.Parallel() |
| testDump(t, s) |
| }) |
| t.Run("Percent", func(t *testing.T) { |
| t.Parallel() |
| testPercent(t, s) |
| }) |
| t.Run("PkgList", func(t *testing.T) { |
| t.Parallel() |
| testPkgList(t, s) |
| }) |
| t.Run("Textfmt", func(t *testing.T) { |
| t.Parallel() |
| testTextfmt(t, s) |
| }) |
| t.Run("Subtract", func(t *testing.T) { |
| t.Parallel() |
| testSubtract(t, s) |
| }) |
| t.Run("Intersect", func(t *testing.T) { |
| t.Parallel() |
| testIntersect(t, s, s.outdirs[0], s.outdirs[1], "set") |
| testIntersect(t, s, s.outdirs[2], s.outdirs[3], "atomic") |
| }) |
| t.Run("CounterClash", func(t *testing.T) { |
| t.Parallel() |
| testCounterClash(t, s) |
| }) |
| t.Run("TestEmpty", func(t *testing.T) { |
| t.Parallel() |
| testEmpty(t, s) |
| }) |
| t.Run("TestCommandLineErrors", func(t *testing.T) { |
| t.Parallel() |
| testCommandLineErrors(t, s, s.outdirs[0]) |
| }) |
| } |
| |
| const showToolInvocations = true |
| |
| func runToolOp(t *testing.T, s state, op string, args []string) []string { |
| // Perform tool run. |
| t.Helper() |
| args = append([]string{op}, args...) |
| if showToolInvocations { |
| t.Logf("%s cmd is: %s %+v", op, s.tool, args) |
| } |
| cmd := testenv.Command(t, s.tool, args...) |
| b, err := cmd.CombinedOutput() |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "## %s output: %s\n", op, string(b)) |
| t.Fatalf("%q run error: %v", op, err) |
| } |
| output := strings.TrimSpace(string(b)) |
| lines := strings.Split(output, "\n") |
| if len(lines) == 1 && lines[0] == "" { |
| lines = nil |
| } |
| return lines |
| } |
| |
| func testDump(t *testing.T, s state) { |
| // Run the dumper on the two dirs we generated. |
| dargs := []string{"-pkg=" + mainPkgPath, "-live", "-i=" + s.outdirs[0] + "," + s.outdirs[1]} |
| lines := runToolOp(t, s, "debugdump", dargs) |
| |
| // Sift through the output to make sure it has some key elements. |
| testpoints := []struct { |
| tag string |
| re *regexp.Regexp |
| }{ |
| { |
| "args", |
| regexp.MustCompile(`^data file .+ GOOS=.+ GOARCH=.+ program args: .+$`), |
| }, |
| { |
| "main package", |
| regexp.MustCompile(`^Package path: ` + mainPkgPath + `\s*$`), |
| }, |
| { |
| "main function", |
| regexp.MustCompile(`^Func: main\s*$`), |
| }, |
| } |
| |
| bad := false |
| for _, testpoint := range testpoints { |
| found := false |
| for _, line := range lines { |
| if m := testpoint.re.FindStringSubmatch(line); m != nil { |
| found = true |
| break |
| } |
| } |
| if !found { |
| t.Errorf("dump output regexp match failed for %q", testpoint.tag) |
| bad = true |
| } |
| } |
| if bad { |
| dumplines(lines) |
| } |
| } |
| |
| func testPercent(t *testing.T, s state) { |
| // Run the dumper on the two dirs we generated. |
| dargs := []string{"-pkg=" + mainPkgPath, "-i=" + s.outdirs[0] + "," + s.outdirs[1]} |
| lines := runToolOp(t, s, "percent", dargs) |
| |
| // Sift through the output to make sure it has the needful. |
| testpoints := []struct { |
| tag string |
| re *regexp.Regexp |
| }{ |
| { |
| "statement coverage percent", |
| regexp.MustCompile(`coverage: \d+\.\d% of statements\s*$`), |
| }, |
| } |
| |
| bad := false |
| for _, testpoint := range testpoints { |
| found := false |
| for _, line := range lines { |
| if m := testpoint.re.FindStringSubmatch(line); m != nil { |
| found = true |
| break |
| } |
| } |
| if !found { |
| t.Errorf("percent output regexp match failed for %s", testpoint.tag) |
| bad = true |
| } |
| } |
| if bad { |
| dumplines(lines) |
| } |
| } |
| |
| func testPkgList(t *testing.T, s state) { |
| dargs := []string{"-i=" + s.outdirs[0] + "," + s.outdirs[1]} |
| lines := runToolOp(t, s, "pkglist", dargs) |
| |
| want := []string{mainPkgPath, mainPkgPath + "/dep"} |
| bad := false |
| if len(lines) != 2 { |
| t.Errorf("expect pkglist to return two lines") |
| bad = true |
| } else { |
| for i := 0; i < 2; i++ { |
| lines[i] = strings.TrimSpace(lines[i]) |
| if want[i] != lines[i] { |
| t.Errorf("line %d want %s got %s", i, want[i], lines[i]) |
| bad = true |
| } |
| } |
| } |
| if bad { |
| dumplines(lines) |
| } |
| } |
| |
| func testTextfmt(t *testing.T, s state) { |
| outf := s.dir + "/" + "t.txt" |
| dargs := []string{"-pkg=" + mainPkgPath, "-i=" + s.outdirs[0] + "," + s.outdirs[1], |
| "-o", outf} |
| lines := runToolOp(t, s, "textfmt", dargs) |
| |
| // No output expected. |
| if len(lines) != 0 { |
| dumplines(lines) |
| t.Errorf("unexpected output from go tool covdata textfmt") |
| } |
| |
| // Open and read the first few bits of the file. |
| payload, err := os.ReadFile(outf) |
| if err != nil { |
| t.Errorf("opening %s: %v\n", outf, err) |
| } |
| lines = strings.Split(string(payload), "\n") |
| want0 := "mode: set" |
| if lines[0] != want0 { |
| dumplines(lines[0:10]) |
| t.Errorf("textfmt: want %s got %s", want0, lines[0]) |
| } |
| want1 := mainPkgPath + "/prog1.go:13.14,15.2 1 1" |
| if lines[1] != want1 { |
| dumplines(lines[0:10]) |
| t.Errorf("textfmt: want %s got %s", want1, lines[1]) |
| } |
| } |
| |
| func dumplines(lines []string) { |
| for i := range lines { |
| fmt.Fprintf(os.Stderr, "%s\n", lines[i]) |
| } |
| } |
| |
| type dumpCheck struct { |
| tag string |
| re *regexp.Regexp |
| negate bool |
| nonzero bool |
| zero bool |
| } |
| |
| // runDumpChecks examines the output of "go tool covdata debugdump" |
| // for a given output directory, looking for the presence or absence |
| // of specific markers. |
| func runDumpChecks(t *testing.T, s state, dir string, flags []string, checks []dumpCheck) { |
| dargs := []string{"-i", dir} |
| dargs = append(dargs, flags...) |
| lines := runToolOp(t, s, "debugdump", dargs) |
| if len(lines) == 0 { |
| t.Fatalf("dump run produced no output") |
| } |
| |
| bad := false |
| for _, check := range checks { |
| found := false |
| for _, line := range lines { |
| if m := check.re.FindStringSubmatch(line); m != nil { |
| found = true |
| if check.negate { |
| t.Errorf("tag %q: unexpected match", check.tag) |
| bad = true |
| |
| } |
| if check.nonzero || check.zero { |
| if len(m) < 2 { |
| t.Errorf("tag %s: submatch failed (short m)", check.tag) |
| bad = true |
| continue |
| } |
| if m[1] == "" { |
| t.Errorf("tag %s: submatch failed", check.tag) |
| bad = true |
| continue |
| } |
| i, err := strconv.Atoi(m[1]) |
| if err != nil { |
| t.Errorf("tag %s: match Atoi failed on %s", |
| check.tag, m[1]) |
| continue |
| } |
| if check.zero && i != 0 { |
| t.Errorf("tag %s: match zero failed on %s", |
| check.tag, m[1]) |
| } else if check.nonzero && i == 0 { |
| t.Errorf("tag %s: match nonzero failed on %s", |
| check.tag, m[1]) |
| } |
| } |
| break |
| } |
| } |
| if !found && !check.negate { |
| t.Errorf("dump output regexp match failed for %s", check.tag) |
| bad = true |
| } |
| } |
| if bad { |
| fmt.Printf("output from 'dump' run:\n") |
| dumplines(lines) |
| } |
| } |
| |
| func testMergeSimple(t *testing.T, s state, indir1, indir2, tag string) { |
| outdir := filepath.Join(s.dir, "simpleMergeOut"+tag) |
| if err := os.Mkdir(outdir, 0777); err != nil { |
| t.Fatalf("can't create outdir %s: %v", outdir, err) |
| } |
| |
| // Merge the two dirs into a final result. |
| ins := fmt.Sprintf("-i=%s,%s", indir1, indir2) |
| out := fmt.Sprintf("-o=%s", outdir) |
| margs := []string{ins, out} |
| lines := runToolOp(t, s, "merge", margs) |
| if len(lines) != 0 { |
| t.Errorf("merge run produced %d lines of unexpected output", len(lines)) |
| dumplines(lines) |
| } |
| |
| // We expect the merge tool to produce exactly two files: a meta |
| // data file and a counter file. If we get more than just this one |
| // pair, something went wrong. |
| podlist, err := pods.CollectPods([]string{outdir}, true) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if len(podlist) != 1 { |
| t.Fatalf("expected 1 pod, got %d pods", len(podlist)) |
| } |
| ncdfs := len(podlist[0].CounterDataFiles) |
| if ncdfs != 1 { |
| t.Fatalf("expected 1 counter data file, got %d", ncdfs) |
| } |
| |
| // Sift through the output to make sure it has some key elements. |
| // In particular, we want to see entries for all three functions |
| // ("first", "second", and "third"). |
| testpoints := []dumpCheck{ |
| { |
| tag: "first function", |
| re: regexp.MustCompile(`^Func: first\s*$`), |
| }, |
| { |
| tag: "second function", |
| re: regexp.MustCompile(`^Func: second\s*$`), |
| }, |
| { |
| tag: "third function", |
| re: regexp.MustCompile(`^Func: third\s*$`), |
| }, |
| { |
| tag: "third function unit 0", |
| re: regexp.MustCompile(`^0: L23:C23 -- L24:C12 NS=1 = (\d+)$`), |
| nonzero: true, |
| }, |
| { |
| tag: "third function unit 1", |
| re: regexp.MustCompile(`^1: L27:C2 -- L28:C10 NS=2 = (\d+)$`), |
| nonzero: true, |
| }, |
| { |
| tag: "third function unit 2", |
| re: regexp.MustCompile(`^2: L24:C12 -- L26:C3 NS=1 = (\d+)$`), |
| nonzero: true, |
| }, |
| } |
| flags := []string{"-live", "-pkg=" + mainPkgPath} |
| runDumpChecks(t, s, outdir, flags, testpoints) |
| } |
| |
| func testMergeSelect(t *testing.T, s state, indir1, indir2 string, tag string) { |
| outdir := filepath.Join(s.dir, "selectMergeOut"+tag) |
| if err := os.Mkdir(outdir, 0777); err != nil { |
| t.Fatalf("can't create outdir %s: %v", outdir, err) |
| } |
| |
| // Merge two input dirs into a final result, but filter |
| // based on package. |
| ins := fmt.Sprintf("-i=%s,%s", indir1, indir2) |
| out := fmt.Sprintf("-o=%s", outdir) |
| margs := []string{"-pkg=" + mainPkgPath + "/dep", ins, out} |
| lines := runToolOp(t, s, "merge", margs) |
| if len(lines) != 0 { |
| t.Errorf("merge run produced %d lines of unexpected output", len(lines)) |
| dumplines(lines) |
| } |
| |
| // Dump the files in the merged output dir and examine the result. |
| // We expect to see only the functions in package "dep". |
| dargs := []string{"-i=" + outdir} |
| lines = runToolOp(t, s, "debugdump", dargs) |
| if len(lines) == 0 { |
| t.Fatalf("dump run produced no output") |
| } |
| want := map[string]int{ |
| "Package path: " + mainPkgPath + "/dep": 0, |
| "Func: Dep1": 0, |
| "Func: PDep": 0, |
| } |
| bad := false |
| for _, line := range lines { |
| if v, ok := want[line]; ok { |
| if v != 0 { |
| t.Errorf("duplicate line %s", line) |
| bad = true |
| break |
| } |
| want[line] = 1 |
| continue |
| } |
| // no other functions or packages expected. |
| if strings.HasPrefix(line, "Func:") || strings.HasPrefix(line, "Package path:") { |
| t.Errorf("unexpected line: %s", line) |
| bad = true |
| break |
| } |
| } |
| if bad { |
| dumplines(lines) |
| } |
| } |
| |
| func testMergeCombinePrograms(t *testing.T, s state) { |
| |
| // Run the new program, emitting output into a new set |
| // of outdirs. |
| runout := [2]string{} |
| for k := 0; k < 2; k++ { |
| runout[k] = filepath.Join(s.dir, fmt.Sprintf("newcovdata%d", k)) |
| if err := os.Mkdir(runout[k], 0777); err != nil { |
| t.Fatalf("can't create outdir %s: %v", runout[k], err) |
| } |
| args := []string{} |
| if k != 0 { |
| args = append(args, "foo", "bar") |
| } |
| cmd := testenv.Command(t, s.exepath2, args...) |
| cmd.Env = append(cmd.Env, "GOCOVERDIR="+runout[k]) |
| b, err := cmd.CombinedOutput() |
| if len(b) != 0 { |
| t.Logf("## instrumented run output:\n%s", b) |
| } |
| if err != nil { |
| t.Fatalf("instrumented run error: %v", err) |
| } |
| } |
| |
| // Create out dir for -pcombine merge. |
| moutdir := filepath.Join(s.dir, "mergeCombineOut") |
| if err := os.Mkdir(moutdir, 0777); err != nil { |
| t.Fatalf("can't create outdir %s: %v", moutdir, err) |
| } |
| |
| // Run a merge over both programs, using the -pcombine |
| // flag to do maximal combining. |
| ins := fmt.Sprintf("-i=%s,%s,%s,%s", s.outdirs[0], s.outdirs[1], |
| runout[0], runout[1]) |
| out := fmt.Sprintf("-o=%s", moutdir) |
| margs := []string{"-pcombine", ins, out} |
| lines := runToolOp(t, s, "merge", margs) |
| if len(lines) != 0 { |
| t.Errorf("merge run produced unexpected output: %v", lines) |
| } |
| |
| // We expect the merge tool to produce exactly two files: a meta |
| // data file and a counter file. If we get more than just this one |
| // pair, something went wrong. |
| podlist, err := pods.CollectPods([]string{moutdir}, true) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if len(podlist) != 1 { |
| t.Fatalf("expected 1 pod, got %d pods", len(podlist)) |
| } |
| ncdfs := len(podlist[0].CounterDataFiles) |
| if ncdfs != 1 { |
| t.Fatalf("expected 1 counter data file, got %d", ncdfs) |
| } |
| |
| // Sift through the output to make sure it has some key elements. |
| testpoints := []dumpCheck{ |
| { |
| tag: "first function", |
| re: regexp.MustCompile(`^Func: first\s*$`), |
| }, |
| { |
| tag: "sixth function", |
| re: regexp.MustCompile(`^Func: sixth\s*$`), |
| }, |
| } |
| |
| flags := []string{"-live", "-pkg=" + mainPkgPath} |
| runDumpChecks(t, s, moutdir, flags, testpoints) |
| } |
| |
| func testSubtract(t *testing.T, s state) { |
| // Create out dir for subtract merge. |
| soutdir := filepath.Join(s.dir, "subtractOut") |
| if err := os.Mkdir(soutdir, 0777); err != nil { |
| t.Fatalf("can't create outdir %s: %v", soutdir, err) |
| } |
| |
| // Subtract the two dirs into a final result. |
| ins := fmt.Sprintf("-i=%s,%s", s.outdirs[0], s.outdirs[1]) |
| out := fmt.Sprintf("-o=%s", soutdir) |
| sargs := []string{ins, out} |
| lines := runToolOp(t, s, "subtract", sargs) |
| if len(lines) != 0 { |
| t.Errorf("subtract run produced unexpected output: %+v", lines) |
| } |
| |
| // Dump the files in the subtract output dir and examine the result. |
| dargs := []string{"-pkg=" + mainPkgPath, "-live", "-i=" + soutdir} |
| lines = runToolOp(t, s, "debugdump", dargs) |
| if len(lines) == 0 { |
| t.Errorf("dump run produced no output") |
| } |
| |
| // Vet the output. |
| testpoints := []dumpCheck{ |
| { |
| tag: "first function", |
| re: regexp.MustCompile(`^Func: first\s*$`), |
| }, |
| { |
| tag: "dep function", |
| re: regexp.MustCompile(`^Func: Dep1\s*$`), |
| }, |
| { |
| tag: "third function", |
| re: regexp.MustCompile(`^Func: third\s*$`), |
| }, |
| { |
| tag: "third function unit 0", |
| re: regexp.MustCompile(`^0: L23:C23 -- L24:C12 NS=1 = (\d+)$`), |
| zero: true, |
| }, |
| { |
| tag: "third function unit 1", |
| re: regexp.MustCompile(`^1: L27:C2 -- L28:C10 NS=2 = (\d+)$`), |
| nonzero: true, |
| }, |
| { |
| tag: "third function unit 2", |
| re: regexp.MustCompile(`^2: L24:C12 -- L26:C3 NS=1 = (\d+)$`), |
| zero: true, |
| }, |
| } |
| flags := []string{} |
| runDumpChecks(t, s, soutdir, flags, testpoints) |
| } |
| |
| func testIntersect(t *testing.T, s state, indir1, indir2, tag string) { |
| // Create out dir for intersection. |
| ioutdir := filepath.Join(s.dir, "intersectOut"+tag) |
| if err := os.Mkdir(ioutdir, 0777); err != nil { |
| t.Fatalf("can't create outdir %s: %v", ioutdir, err) |
| } |
| |
| // Intersect the two dirs into a final result. |
| ins := fmt.Sprintf("-i=%s,%s", indir1, indir2) |
| out := fmt.Sprintf("-o=%s", ioutdir) |
| sargs := []string{ins, out} |
| lines := runToolOp(t, s, "intersect", sargs) |
| if len(lines) != 0 { |
| t.Errorf("intersect run produced unexpected output: %+v", lines) |
| } |
| |
| // Dump the files in the subtract output dir and examine the result. |
| dargs := []string{"-pkg=" + mainPkgPath, "-live", "-i=" + ioutdir} |
| lines = runToolOp(t, s, "debugdump", dargs) |
| if len(lines) == 0 { |
| t.Errorf("dump run produced no output") |
| } |
| |
| // Vet the output. |
| testpoints := []dumpCheck{ |
| { |
| tag: "first function", |
| re: regexp.MustCompile(`^Func: first\s*$`), |
| negate: true, |
| }, |
| { |
| tag: "third function", |
| re: regexp.MustCompile(`^Func: third\s*$`), |
| }, |
| } |
| flags := []string{"-live"} |
| runDumpChecks(t, s, ioutdir, flags, testpoints) |
| } |
| |
| func testCounterClash(t *testing.T, s state) { |
| // Create out dir. |
| ccoutdir := filepath.Join(s.dir, "ccOut") |
| if err := os.Mkdir(ccoutdir, 0777); err != nil { |
| t.Fatalf("can't create outdir %s: %v", ccoutdir, err) |
| } |
| |
| // Try to merge covdata0 (from prog1.go -countermode=set) with |
| // covdata1 (from prog1.go -countermode=atomic"). This should |
| // work properly, but result in multiple meta-data files. |
| ins := fmt.Sprintf("-i=%s,%s", s.outdirs[0], s.outdirs[3]) |
| out := fmt.Sprintf("-o=%s", ccoutdir) |
| args := append([]string{}, "merge", ins, out, "-pcombine") |
| if debugtrace { |
| t.Logf("cc merge command is %s %v\n", s.tool, args) |
| } |
| cmd := testenv.Command(t, s.tool, args...) |
| b, err := cmd.CombinedOutput() |
| t.Logf("%% output: %s\n", string(b)) |
| if err != nil { |
| t.Fatalf("clash merge failed: %v", err) |
| } |
| |
| // Ask for a textual report from the two dirs. Here we have |
| // to report the mode clash. |
| out = "-o=" + filepath.Join(ccoutdir, "file.txt") |
| args = append([]string{}, "textfmt", ins, out) |
| if debugtrace { |
| t.Logf("clash textfmt command is %s %v\n", s.tool, args) |
| } |
| cmd = testenv.Command(t, s.tool, args...) |
| b, err = cmd.CombinedOutput() |
| t.Logf("%% output: %s\n", string(b)) |
| if err == nil { |
| t.Fatalf("expected mode clash") |
| } |
| got := string(b) |
| want := "counter mode clash while reading meta-data" |
| if !strings.Contains(got, want) { |
| t.Errorf("counter clash textfmt: wanted %s got %s", want, got) |
| } |
| } |
| |
| func testEmpty(t *testing.T, s state) { |
| |
| // Create a new empty directory. |
| empty := filepath.Join(s.dir, "empty") |
| if err := os.Mkdir(empty, 0777); err != nil { |
| t.Fatalf("can't create dir %s: %v", empty, err) |
| } |
| |
| // Create out dir. |
| eoutdir := filepath.Join(s.dir, "emptyOut") |
| if err := os.Mkdir(eoutdir, 0777); err != nil { |
| t.Fatalf("can't create outdir %s: %v", eoutdir, err) |
| } |
| |
| // Run various operations (merge, dump, textfmt, and so on) |
| // using the empty directory. We're not interested in the output |
| // here, just making sure that you can do these runs without |
| // any error or crash. |
| |
| scenarios := []struct { |
| tag string |
| args []string |
| }{ |
| { |
| tag: "merge", |
| args: []string{"merge", "-o", eoutdir}, |
| }, |
| { |
| tag: "textfmt", |
| args: []string{"textfmt", "-o", filepath.Join(eoutdir, "foo.txt")}, |
| }, |
| { |
| tag: "func", |
| args: []string{"func"}, |
| }, |
| { |
| tag: "pkglist", |
| args: []string{"pkglist"}, |
| }, |
| { |
| tag: "debugdump", |
| args: []string{"debugdump"}, |
| }, |
| { |
| tag: "percent", |
| args: []string{"percent"}, |
| }, |
| } |
| |
| for _, x := range scenarios { |
| ins := fmt.Sprintf("-i=%s", empty) |
| args := append([]string{}, x.args...) |
| args = append(args, ins) |
| if false { |
| t.Logf("cmd is %s %v\n", s.tool, args) |
| } |
| cmd := testenv.Command(t, s.tool, args...) |
| b, err := cmd.CombinedOutput() |
| t.Logf("%% output: %s\n", string(b)) |
| if err != nil { |
| t.Fatalf("command %s %+v failed with %v", |
| s.tool, x.args, err) |
| } |
| } |
| } |
| |
| func testCommandLineErrors(t *testing.T, s state, outdir string) { |
| |
| // Create out dir. |
| eoutdir := filepath.Join(s.dir, "errorsOut") |
| if err := os.Mkdir(eoutdir, 0777); err != nil { |
| t.Fatalf("can't create outdir %s: %v", eoutdir, err) |
| } |
| |
| // Run various operations (merge, dump, textfmt, and so on) |
| // using the empty directory. We're not interested in the output |
| // here, just making sure that you can do these runs without |
| // any error or crash. |
| |
| scenarios := []struct { |
| tag string |
| args []string |
| exp string |
| }{ |
| { |
| tag: "input missing", |
| args: []string{"merge", "-o", eoutdir, "-i", "not there"}, |
| exp: "error: reading inputs: ", |
| }, |
| { |
| tag: "badv", |
| args: []string{"textfmt", "-i", outdir, "-v=abc"}, |
| }, |
| } |
| |
| for _, x := range scenarios { |
| args := append([]string{}, x.args...) |
| if false { |
| t.Logf("cmd is %s %v\n", s.tool, args) |
| } |
| cmd := testenv.Command(t, s.tool, args...) |
| b, err := cmd.CombinedOutput() |
| if err == nil { |
| t.Logf("%% output: %s\n", string(b)) |
| t.Fatalf("command %s %+v unexpectedly succeeded", |
| s.tool, x.args) |
| } else { |
| if !strings.Contains(string(b), x.exp) { |
| t.Fatalf("command %s %+v:\ngot:\n%s\nwanted to see: %v\n", |
| s.tool, x.args, string(b), x.exp) |
| } |
| } |
| } |
| } |