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

View as plain text