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  // testcovdata returns the path to the unit test executable to be used as
    25  // standin for 'go tool covdata'.
    26  func 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.
    36  var testTempDir string
    37  
    38  // If set, this will preserve all the tmpdir files from the test run.
    39  var 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.
    44  func 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  
    68  var tdmu sync.Mutex
    69  var tdcount int
    70  
    71  func 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  
    82  const debugtrace = false
    83  
    84  func 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  
   104  func 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  
   114  const mainPkgPath = "prog"
   115  
   116  func 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  
   148  type 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  
   160  const debugWorkDir = false
   161  
   162  func 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  
   285  const showToolInvocations = true
   286  
   287  func 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  
   308  func 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  
   351  func 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  
   386  func 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  
   409  func 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  
   439  func dumplines(lines []string) {
   440  	for i := range lines {
   441  		fmt.Fprintf(os.Stderr, "%s\n", lines[i])
   442  	}
   443  }
   444  
   445  type 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.
   456  func 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  
   514  func 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  
   581  func 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  
   633  func 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  
   706  func 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  
   763  func 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  
   802  func 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  
   845  func 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  
   911  func 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  

View as plain text