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