Source file src/cmd/covdata/tool_test.go

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

View as plain text