1
2
3
4
5 package cfile
6
7 import (
8 "fmt"
9 "internal/coverage"
10 "internal/platform"
11 "internal/testenv"
12 "os"
13 "os/exec"
14 "path/filepath"
15 "runtime"
16 "strings"
17 "testing"
18 )
19
20
21 const fixedTestDir = false
22
23 func TestCoverageApis(t *testing.T) {
24 if testing.Short() {
25 t.Skipf("skipping test: too long for short mode")
26 }
27 testenv.MustHaveGoBuild(t)
28 dir := t.TempDir()
29 if fixedTestDir {
30 dir = "/tmp/qqqzzz"
31 os.RemoveAll(dir)
32 mkdir(t, dir)
33 }
34
35
36
37 bdir1 := mkdir(t, filepath.Join(dir, "build1"))
38 hargs1 := []string{"-covermode=atomic", "-coverpkg=all"}
39 atomicHarnessPath := buildHarness(t, bdir1, hargs1)
40 nonAtomicMode := testing.CoverMode()
41 if testing.CoverMode() == "atomic" {
42 nonAtomicMode = "set"
43 }
44 bdir2 := mkdir(t, filepath.Join(dir, "build2"))
45 hargs2 := []string{"-coverpkg=all", "-covermode=" + nonAtomicMode}
46 nonAtomicHarnessPath := buildHarness(t, bdir2, hargs2)
47
48 t.Logf("atomic harness path is %s", atomicHarnessPath)
49 t.Logf("non-atomic harness path is %s", nonAtomicHarnessPath)
50
51
52
53 t.Run("emitToDir", func(t *testing.T) {
54 t.Parallel()
55 testEmitToDir(t, atomicHarnessPath, dir)
56 })
57 t.Run("emitToWriter", func(t *testing.T) {
58 t.Parallel()
59 testEmitToWriter(t, atomicHarnessPath, dir)
60 })
61 t.Run("emitToNonexistentDir", func(t *testing.T) {
62 t.Parallel()
63 testEmitToNonexistentDir(t, atomicHarnessPath, dir)
64 })
65 t.Run("emitToNilWriter", func(t *testing.T) {
66 t.Parallel()
67 testEmitToNilWriter(t, atomicHarnessPath, dir)
68 })
69 t.Run("emitToFailingWriter", func(t *testing.T) {
70 t.Parallel()
71 testEmitToFailingWriter(t, atomicHarnessPath, dir)
72 })
73 t.Run("emitWithCounterClear", func(t *testing.T) {
74 t.Parallel()
75 testEmitWithCounterClear(t, atomicHarnessPath, dir)
76 })
77 t.Run("emitToDirNonAtomic", func(t *testing.T) {
78 t.Parallel()
79 testEmitToDirNonAtomic(t, nonAtomicHarnessPath, nonAtomicMode, dir)
80 })
81 t.Run("emitToWriterNonAtomic", func(t *testing.T) {
82 t.Parallel()
83 testEmitToWriterNonAtomic(t, nonAtomicHarnessPath, nonAtomicMode, dir)
84 })
85 t.Run("emitWithCounterClearNonAtomic", func(t *testing.T) {
86 t.Parallel()
87 testEmitWithCounterClearNonAtomic(t, nonAtomicHarnessPath, nonAtomicMode, dir)
88 })
89 }
90
91
92
93
94
95
96
97
98 func upmergeCoverData(t *testing.T, gocoverdir string, mode string) {
99 if testing.CoverMode() != mode {
100 return
101 }
102 testGoCoverDir := os.Getenv("GOCOVERDIR")
103 if testGoCoverDir == "" {
104 return
105 }
106 args := []string{"tool", "covdata", "merge", "-pkg=runtime/coverage",
107 "-o", testGoCoverDir, "-i", gocoverdir}
108 t.Logf("up-merge of covdata from %s to %s", gocoverdir, testGoCoverDir)
109 t.Logf("executing: go %+v", args)
110 cmd := exec.Command(testenv.GoToolPath(t), args...)
111 if b, err := cmd.CombinedOutput(); err != nil {
112 t.Fatalf("covdata merge failed (%v): %s", err, b)
113 }
114 }
115
116
117 func buildHarness(t *testing.T, dir string, opts []string) string {
118 harnessPath := filepath.Join(dir, "harness.exe")
119 harnessSrc := filepath.Join("testdata", "harness.go")
120 args := []string{"build", "-o", harnessPath}
121 args = append(args, opts...)
122 args = append(args, harnessSrc)
123
124 cmd := exec.Command(testenv.GoToolPath(t), args...)
125 if b, err := cmd.CombinedOutput(); err != nil {
126 t.Fatalf("build failed (%v): %s", err, b)
127 }
128 return harnessPath
129 }
130
131 func mkdir(t *testing.T, d string) string {
132 t.Helper()
133 if err := os.Mkdir(d, 0777); err != nil {
134 t.Fatalf("mkdir failed: %v", err)
135 }
136 return d
137 }
138
139
140
141
142 func updateGoCoverDir(env []string, gcd string, setGoCoverDir bool) []string {
143 rv := []string{}
144 found := false
145 for _, v := range env {
146 if strings.HasPrefix(v, "GOCOVERDIR=") {
147 if !setGoCoverDir {
148 continue
149 }
150 v = "GOCOVERDIR=" + gcd
151 found = true
152 }
153 rv = append(rv, v)
154 }
155 if !found && setGoCoverDir {
156 rv = append(rv, "GOCOVERDIR="+gcd)
157 }
158 return rv
159 }
160
161 func runHarness(t *testing.T, harnessPath string, tp string, setGoCoverDir bool, rdir, edir string) (string, error) {
162 t.Logf("running: %s -tp %s -o %s with rdir=%s and GOCOVERDIR=%v", harnessPath, tp, edir, rdir, setGoCoverDir)
163 cmd := exec.Command(harnessPath, "-tp", tp, "-o", edir)
164 cmd.Dir = rdir
165 cmd.Env = updateGoCoverDir(os.Environ(), rdir, setGoCoverDir)
166 b, err := cmd.CombinedOutput()
167
168 return string(b), err
169 }
170
171 func testForSpecificFunctions(t *testing.T, dir string, want []string, avoid []string) string {
172 args := []string{"tool", "covdata", "debugdump",
173 "-live", "-pkg=command-line-arguments", "-i=" + dir}
174 t.Logf("running: go %v\n", args)
175 cmd := exec.Command(testenv.GoToolPath(t), args...)
176 b, err := cmd.CombinedOutput()
177 if err != nil {
178 t.Fatalf("'go tool covdata failed (%v): %s", err, b)
179 }
180 output := string(b)
181 rval := ""
182 for _, f := range want {
183 wf := "Func: " + f + "\n"
184 if strings.Contains(output, wf) {
185 continue
186 }
187 rval += fmt.Sprintf("error: output should contain %q but does not\n", wf)
188 }
189 for _, f := range avoid {
190 wf := "Func: " + f + "\n"
191 if strings.Contains(output, wf) {
192 rval += fmt.Sprintf("error: output should not contain %q but does\n", wf)
193 }
194 }
195 if rval != "" {
196 t.Logf("=-= begin output:\n%s\n=-= end output\n", output)
197 }
198 return rval
199 }
200
201 func withAndWithoutRunner(f func(setit bool, tag string)) {
202
203 for i := 0; i < 2; i++ {
204 tag := "x"
205 setGoCoverDir := true
206 if i == 0 {
207 setGoCoverDir = false
208 tag = "y"
209 }
210 f(setGoCoverDir, tag)
211 }
212 }
213
214 func mktestdirs(t *testing.T, tag, tp, dir string) (string, string) {
215 t.Helper()
216 rdir := mkdir(t, filepath.Join(dir, tp+"-rdir-"+tag))
217 edir := mkdir(t, filepath.Join(dir, tp+"-edir-"+tag))
218 return rdir, edir
219 }
220
221 func testEmitToDir(t *testing.T, harnessPath string, dir string) {
222 withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
223 tp := "emitToDir"
224 rdir, edir := mktestdirs(t, tag, tp, dir)
225 output, err := runHarness(t, harnessPath, tp,
226 setGoCoverDir, rdir, edir)
227 if err != nil {
228 t.Logf("%s", output)
229 t.Fatalf("running 'harness -tp emitDir': %v", err)
230 }
231
232
233
234
235 dents, err := os.ReadDir(edir)
236 if err != nil {
237 t.Fatalf("os.ReadDir(%s) failed: %v", edir, err)
238 }
239 mfc := 0
240 cdc := 0
241 for _, e := range dents {
242 if e.IsDir() {
243 continue
244 }
245 if strings.HasPrefix(e.Name(), coverage.MetaFilePref) {
246 mfc++
247 } else if strings.HasPrefix(e.Name(), coverage.CounterFilePref) {
248 cdc++
249 }
250 }
251 wantmf := 1
252 wantcf := 1
253 if mfc != wantmf {
254 t.Errorf("EmitToDir: want %d meta-data files, got %d\n", wantmf, mfc)
255 }
256 if cdc != wantcf {
257 t.Errorf("EmitToDir: want %d counter-data files, got %d\n", wantcf, cdc)
258 }
259 upmergeCoverData(t, edir, "atomic")
260 upmergeCoverData(t, rdir, "atomic")
261 })
262 }
263
264 func testEmitToWriter(t *testing.T, harnessPath string, dir string) {
265 withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
266 tp := "emitToWriter"
267 rdir, edir := mktestdirs(t, tag, tp, dir)
268 output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
269 if err != nil {
270 t.Logf("%s", output)
271 t.Fatalf("running 'harness -tp %s': %v", tp, err)
272 }
273 want := []string{"main", tp}
274 avoid := []string{"final"}
275 if msg := testForSpecificFunctions(t, edir, want, avoid); msg != "" {
276 t.Errorf("coverage data from %q output match failed: %s", tp, msg)
277 }
278 upmergeCoverData(t, edir, "atomic")
279 upmergeCoverData(t, rdir, "atomic")
280 })
281 }
282
283 func testEmitToNonexistentDir(t *testing.T, harnessPath string, dir string) {
284 withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
285 tp := "emitToNonexistentDir"
286 rdir, edir := mktestdirs(t, tag, tp, dir)
287 output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
288 if err != nil {
289 t.Logf("%s", output)
290 t.Fatalf("running 'harness -tp %s': %v", tp, err)
291 }
292 upmergeCoverData(t, edir, "atomic")
293 upmergeCoverData(t, rdir, "atomic")
294 })
295 }
296
297 func testEmitToUnwritableDir(t *testing.T, harnessPath string, dir string) {
298 withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
299
300 tp := "emitToUnwritableDir"
301 rdir, edir := mktestdirs(t, tag, tp, dir)
302
303
304 if err := os.Chmod(edir, 0555); err != nil {
305 t.Fatalf("chmod failed: %v", err)
306 }
307 defer os.Chmod(edir, 0777)
308
309 output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
310 if err != nil {
311 t.Logf("%s", output)
312 t.Fatalf("running 'harness -tp %s': %v", tp, err)
313 }
314 upmergeCoverData(t, edir, "atomic")
315 upmergeCoverData(t, rdir, "atomic")
316 })
317 }
318
319 func testEmitToNilWriter(t *testing.T, harnessPath string, dir string) {
320 withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
321 tp := "emitToNilWriter"
322 rdir, edir := mktestdirs(t, tag, tp, dir)
323 output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
324 if err != nil {
325 t.Logf("%s", output)
326 t.Fatalf("running 'harness -tp %s': %v", tp, err)
327 }
328 upmergeCoverData(t, edir, "atomic")
329 upmergeCoverData(t, rdir, "atomic")
330 })
331 }
332
333 func testEmitToFailingWriter(t *testing.T, harnessPath string, dir string) {
334 withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
335 tp := "emitToFailingWriter"
336 rdir, edir := mktestdirs(t, tag, tp, dir)
337 output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
338 if err != nil {
339 t.Logf("%s", output)
340 t.Fatalf("running 'harness -tp %s': %v", tp, err)
341 }
342 upmergeCoverData(t, edir, "atomic")
343 upmergeCoverData(t, rdir, "atomic")
344 })
345 }
346
347 func testEmitWithCounterClear(t *testing.T, harnessPath string, dir string) {
348 withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
349 tp := "emitWithCounterClear"
350 rdir, edir := mktestdirs(t, tag, tp, dir)
351 output, err := runHarness(t, harnessPath, tp,
352 setGoCoverDir, rdir, edir)
353 if err != nil {
354 t.Logf("%s", output)
355 t.Fatalf("running 'harness -tp %s': %v", tp, err)
356 }
357 want := []string{tp, "postClear"}
358 avoid := []string{"preClear", "main", "final"}
359 if msg := testForSpecificFunctions(t, edir, want, avoid); msg != "" {
360 t.Logf("%s", output)
361 t.Errorf("coverage data from %q output match failed: %s", tp, msg)
362 }
363 upmergeCoverData(t, edir, "atomic")
364 upmergeCoverData(t, rdir, "atomic")
365 })
366 }
367
368 func testEmitToDirNonAtomic(t *testing.T, harnessPath string, naMode string, dir string) {
369 tp := "emitToDir"
370 tag := "nonatomdir"
371 rdir, edir := mktestdirs(t, tag, tp, dir)
372 output, err := runHarness(t, harnessPath, tp,
373 true, rdir, edir)
374
375
376 if err == nil {
377 t.Logf("%s", output)
378 t.Fatalf("running 'harness -tp %s': did not get expected error", tp)
379 }
380
381 got := strings.TrimSpace(string(output))
382 want := "WriteCountersDir invoked for program built"
383 if !strings.Contains(got, want) {
384 t.Errorf("running 'harness -tp %s': got:\n%s\nwant: %s",
385 tp, got, want)
386 }
387 upmergeCoverData(t, edir, naMode)
388 upmergeCoverData(t, rdir, naMode)
389 }
390
391 func testEmitToWriterNonAtomic(t *testing.T, harnessPath string, naMode string, dir string) {
392 tp := "emitToWriter"
393 tag := "nonatomw"
394 rdir, edir := mktestdirs(t, tag, tp, dir)
395 output, err := runHarness(t, harnessPath, tp,
396 true, rdir, edir)
397
398
399 if err == nil {
400 t.Logf("%s", output)
401 t.Fatalf("running 'harness -tp %s': did not get expected error", tp)
402 }
403
404 got := strings.TrimSpace(string(output))
405 want := "WriteCounters invoked for program built"
406 if !strings.Contains(got, want) {
407 t.Errorf("running 'harness -tp %s': got:\n%s\nwant: %s",
408 tp, got, want)
409 }
410
411 upmergeCoverData(t, edir, naMode)
412 upmergeCoverData(t, rdir, naMode)
413 }
414
415 func testEmitWithCounterClearNonAtomic(t *testing.T, harnessPath string, naMode string, dir string) {
416 tp := "emitWithCounterClear"
417 tag := "cclear"
418 rdir, edir := mktestdirs(t, tag, tp, dir)
419 output, err := runHarness(t, harnessPath, tp,
420 true, rdir, edir)
421
422
423 if err == nil {
424 t.Logf("%s", output)
425 t.Fatalf("running 'harness -tp %s' nonatomic: did not get expected error", tp)
426 }
427
428 got := strings.TrimSpace(string(output))
429 want := "ClearCounters invoked for program built"
430 if !strings.Contains(got, want) {
431 t.Errorf("running 'harness -tp %s': got:\n%s\nwant: %s",
432 tp, got, want)
433 }
434
435 upmergeCoverData(t, edir, naMode)
436 upmergeCoverData(t, rdir, naMode)
437 }
438
439 func TestApisOnNocoverBinary(t *testing.T) {
440 if testing.Short() {
441 t.Skipf("skipping test: too long for short mode")
442 }
443 testenv.MustHaveGoBuild(t)
444 dir := t.TempDir()
445
446
447 bdir := mkdir(t, filepath.Join(dir, "nocover"))
448 edir := mkdir(t, filepath.Join(dir, "emitDirNo"))
449 harnessPath := buildHarness(t, bdir, nil)
450 output, err := runHarness(t, harnessPath, "emitToDir", false, edir, edir)
451 if err == nil {
452 t.Fatalf("expected error on TestApisOnNocoverBinary harness run")
453 }
454 const want = "not built with -cover"
455 if !strings.Contains(output, want) {
456 t.Errorf("error output does not contain %q: %s", want, output)
457 }
458 }
459
460 func TestIssue56006EmitDataRaceCoverRunningGoroutine(t *testing.T) {
461 if testing.Short() {
462 t.Skipf("skipping test: too long for short mode")
463 }
464
465
466
467 testenv.MustHaveGoRun(t)
468 if !platform.RaceDetectorSupported(runtime.GOOS, runtime.GOARCH) ||
469 !testenv.HasCGO() {
470 t.Skip("skipped due to lack of race detector support / CGO")
471 }
472
473
474
475
476 cmd := exec.Command(testenv.GoToolPath(t), "test", "-cover", "-race")
477 cmd.Dir = filepath.Join("testdata", "issue56006")
478 b, err := cmd.CombinedOutput()
479 if err != nil {
480 t.Fatalf("go test -cover -race failed: %v\n%s", err, b)
481 }
482
483
484 avoid := []string{"DATA RACE"}
485 for _, no := range avoid {
486 if strings.Contains(string(b), no) {
487 t.Logf("%s\n", string(b))
488 t.Fatalf("found %s in test output, not permitted", no)
489 }
490 }
491 }
492
493 func TestIssue59563TruncatedCoverPkgAll(t *testing.T) {
494 if testing.Short() {
495 t.Skipf("skipping test: too long for short mode")
496 }
497 testenv.MustHaveGoRun(t)
498
499 tmpdir := t.TempDir()
500 ppath := filepath.Join(tmpdir, "foo.cov")
501
502 cmd := exec.Command(testenv.GoToolPath(t), "test", "-coverpkg=all", "-coverprofile="+ppath)
503 cmd.Dir = filepath.Join("testdata", "issue59563")
504 b, err := cmd.CombinedOutput()
505 if err != nil {
506 t.Fatalf("go test -cover failed: %v\n%s", err, b)
507 }
508
509 cmd = exec.Command(testenv.GoToolPath(t), "tool", "cover", "-func="+ppath)
510 b, err = cmd.CombinedOutput()
511 if err != nil {
512 t.Fatalf("go tool cover -func failed: %v", err)
513 }
514
515 lines := strings.Split(string(b), "\n")
516 nfound := 0
517 bad := false
518 for _, line := range lines {
519 f := strings.Fields(line)
520 if len(f) == 0 {
521 continue
522 }
523
524
525
526 if !(strings.HasPrefix(f[0], "internal/coverage/cfile/testdata/issue59563/repro.go") && strings.Contains(line, "large")) {
527 continue
528 }
529 nfound++
530 want := "100.0%"
531 if f[len(f)-1] != want {
532 t.Errorf("wanted %s got: %q\n", want, line)
533 bad = true
534 }
535 }
536 if nfound != 1 {
537 t.Errorf("wanted 1 found, got %d\n", nfound)
538 bad = true
539 }
540 if bad {
541 t.Logf("func output:\n%s\n", string(b))
542 }
543 }
544
View as plain text