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