mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
Add hooks/apis to support writing of coverage counter data and meta-data under user control (from within an executing "-cover" binary), so as to provide a way to obtain coverage data from programs that do not terminate. This patch also adds a hook for clearing the coverage counter data for a running program, something that can be helpful when the intent is to capture coverage info from a specific window of program execution. Updates #51430. Change-Id: I34ee6cee52e5597fa3698b8b04f1b34a2a2a418f Reviewed-on: https://go-review.googlesource.com/c/go/+/401236 Reviewed-by: David Chase <drchase@google.com>
258 lines
7.1 KiB
Go
258 lines
7.1 KiB
Go
// Copyright 2022 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"internal/coverage/slicewriter"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"path/filepath"
|
|
"runtime/coverage"
|
|
"strings"
|
|
)
|
|
|
|
var verbflag = flag.Int("v", 0, "Verbose trace output level")
|
|
var testpointflag = flag.String("tp", "", "Testpoint to run")
|
|
var outdirflag = flag.String("o", "", "Output dir into which to emit")
|
|
|
|
func emitToWriter() {
|
|
log.SetPrefix("emitToWriter: ")
|
|
var slwm slicewriter.WriteSeeker
|
|
if err := coverage.EmitMetaDataToWriter(&slwm); err != nil {
|
|
log.Fatalf("error: EmitMetaDataToWriter returns %v", err)
|
|
}
|
|
mf := filepath.Join(*outdirflag, "covmeta.0abcdef")
|
|
if err := ioutil.WriteFile(mf, slwm.BytesWritten(), 0666); err != nil {
|
|
log.Fatalf("error: writing %s: %v", mf, err)
|
|
}
|
|
var slwc slicewriter.WriteSeeker
|
|
if err := coverage.EmitCounterDataToWriter(&slwc); err != nil {
|
|
log.Fatalf("error: EmitCounterDataToWriter returns %v", err)
|
|
}
|
|
cf := filepath.Join(*outdirflag, "covcounters.0abcdef.99.77")
|
|
if err := ioutil.WriteFile(cf, slwc.BytesWritten(), 0666); err != nil {
|
|
log.Fatalf("error: writing %s: %v", cf, err)
|
|
}
|
|
}
|
|
|
|
func emitToDir() {
|
|
log.SetPrefix("emitToDir: ")
|
|
if err := coverage.EmitMetaDataToDir(*outdirflag); err != nil {
|
|
log.Fatalf("error: EmitMetaDataToDir returns %v", err)
|
|
}
|
|
if err := coverage.EmitCounterDataToDir(*outdirflag); err != nil {
|
|
log.Fatalf("error: EmitCounterDataToDir returns %v", err)
|
|
}
|
|
}
|
|
|
|
func emitToNonexistentDir() {
|
|
log.SetPrefix("emitToNonexistentDir: ")
|
|
|
|
want := []string{
|
|
"no such file or directory", // linux-ish
|
|
"system cannot find the file specified", // windows
|
|
}
|
|
|
|
checkWant := func(which string, got string) {
|
|
found := false
|
|
for _, w := range want {
|
|
if strings.Contains(got, w) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
log.Fatalf("%s emit to bad dir: got error:\n %v\nwanted error with one of:\n %+v", which, got, want)
|
|
}
|
|
}
|
|
|
|
// Mangle the output directory to produce something nonexistent.
|
|
mangled := *outdirflag + "_MANGLED"
|
|
if err := coverage.EmitMetaDataToDir(mangled); err == nil {
|
|
log.Fatal("expected error from EmitMetaDataToDir to nonexistent dir")
|
|
} else {
|
|
got := fmt.Sprintf("%v", err)
|
|
checkWant("meta data", got)
|
|
}
|
|
|
|
// Now try to emit counter data file to a bad dir.
|
|
if err := coverage.EmitCounterDataToDir(mangled); err == nil {
|
|
log.Fatal("expected error emitting counter data to bad dir")
|
|
} else {
|
|
got := fmt.Sprintf("%v", err)
|
|
checkWant("counter data", got)
|
|
}
|
|
}
|
|
|
|
func emitToUnwritableDir() {
|
|
log.SetPrefix("emitToUnwritableDir: ")
|
|
|
|
want := "permission denied"
|
|
|
|
if err := coverage.EmitMetaDataToDir(*outdirflag); err == nil {
|
|
log.Fatal("expected error from EmitMetaDataToDir to unwritable dir")
|
|
} else {
|
|
got := fmt.Sprintf("%v", err)
|
|
if !strings.Contains(got, want) {
|
|
log.Fatalf("meta-data emit to unwritable dir: wanted error containing %q got %q", want, got)
|
|
}
|
|
}
|
|
|
|
// Similarly with writing counter data.
|
|
if err := coverage.EmitCounterDataToDir(*outdirflag); err == nil {
|
|
log.Fatal("expected error emitting counter data to unwritable dir")
|
|
} else {
|
|
got := fmt.Sprintf("%v", err)
|
|
if !strings.Contains(got, want) {
|
|
log.Fatalf("emitting counter data to unwritable dir: wanted error containing %q got %q", want, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func emitToNilWriter() {
|
|
log.SetPrefix("emitToWriter: ")
|
|
want := "nil writer"
|
|
var bad io.WriteSeeker
|
|
if err := coverage.EmitMetaDataToWriter(bad); err == nil {
|
|
log.Fatal("expected error passing nil writer for meta emit")
|
|
} else {
|
|
got := fmt.Sprintf("%v", err)
|
|
if !strings.Contains(got, want) {
|
|
log.Fatalf("emitting meta-data passing nil writer: wanted error containing %q got %q", want, got)
|
|
}
|
|
}
|
|
|
|
if err := coverage.EmitCounterDataToWriter(bad); err == nil {
|
|
log.Fatal("expected error passing nil writer for counter emit")
|
|
} else {
|
|
got := fmt.Sprintf("%v", err)
|
|
if !strings.Contains(got, want) {
|
|
log.Fatalf("emitting counter data passing nil writer: wanted error containing %q got %q", want, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
type failingWriter struct {
|
|
writeCount int
|
|
writeLimit int
|
|
slws slicewriter.WriteSeeker
|
|
}
|
|
|
|
func (f *failingWriter) Write(p []byte) (n int, err error) {
|
|
c := f.writeCount
|
|
f.writeCount++
|
|
if f.writeLimit < 0 || c < f.writeLimit {
|
|
return f.slws.Write(p)
|
|
}
|
|
return 0, fmt.Errorf("manufactured write error")
|
|
}
|
|
|
|
func (f *failingWriter) Seek(offset int64, whence int) (int64, error) {
|
|
return f.slws.Seek(offset, whence)
|
|
}
|
|
|
|
func (f *failingWriter) reset(lim int) {
|
|
f.writeCount = 0
|
|
f.writeLimit = lim
|
|
f.slws = slicewriter.WriteSeeker{}
|
|
}
|
|
|
|
func writeStressTest(tag string, testf func(testf *failingWriter) error) {
|
|
// Invoke the function initially without the write limit
|
|
// set, to capture the number of writes performed.
|
|
fw := &failingWriter{writeLimit: -1}
|
|
testf(fw)
|
|
|
|
// Now that we know how many writes are going to happen, run the
|
|
// function repeatedly, each time with a Write operation set to
|
|
// fail at a new spot. The goal here is to make sure that:
|
|
// A) an error is reported, and B) nothing crashes.
|
|
tot := fw.writeCount
|
|
for i := 0; i < tot; i++ {
|
|
fw.reset(i)
|
|
err := testf(fw)
|
|
if err == nil {
|
|
log.Fatalf("no error from write %d tag %s", i, tag)
|
|
}
|
|
}
|
|
}
|
|
|
|
func postClear() int {
|
|
return 42
|
|
}
|
|
|
|
func preClear() int {
|
|
return 42
|
|
}
|
|
|
|
// This test is designed to ensure that write errors are properly
|
|
// handled by the code that writes out coverage data. It repeatedly
|
|
// invokes the 'emit to writer' apis using a specially crafted writer
|
|
// that captures the total number of expected writes, then replays the
|
|
// execution N times with a manufactured write error at the
|
|
// appropriate spot.
|
|
func emitToFailingWriter() {
|
|
log.SetPrefix("emitToFailingWriter: ")
|
|
|
|
writeStressTest("emit-meta", func(f *failingWriter) error {
|
|
return coverage.EmitMetaDataToWriter(f)
|
|
})
|
|
writeStressTest("emit-counter", func(f *failingWriter) error {
|
|
return coverage.EmitCounterDataToWriter(f)
|
|
})
|
|
}
|
|
|
|
func emitWithCounterClear() {
|
|
log.SetPrefix("emitWitCounterClear: ")
|
|
preClear()
|
|
if err := coverage.ClearCoverageCounters(); err != nil {
|
|
log.Fatalf("clear failed: %v", err)
|
|
}
|
|
postClear()
|
|
if err := coverage.EmitMetaDataToDir(*outdirflag); err != nil {
|
|
log.Fatalf("error: EmitMetaDataToDir returns %v", err)
|
|
}
|
|
if err := coverage.EmitCounterDataToDir(*outdirflag); err != nil {
|
|
log.Fatalf("error: EmitCounterDataToDir returns %v", err)
|
|
}
|
|
}
|
|
|
|
func final() int {
|
|
println("I run last.")
|
|
return 43
|
|
}
|
|
|
|
func main() {
|
|
log.SetFlags(0)
|
|
flag.Parse()
|
|
if *testpointflag == "" {
|
|
log.Fatalf("error: no testpoint (use -tp flag)")
|
|
}
|
|
if *outdirflag == "" {
|
|
log.Fatalf("error: no output dir specified (use -o flag)")
|
|
}
|
|
switch *testpointflag {
|
|
case "emitToDir":
|
|
emitToDir()
|
|
case "emitToWriter":
|
|
emitToWriter()
|
|
case "emitToNonexistentDir":
|
|
emitToNonexistentDir()
|
|
case "emitToUnwritableDir":
|
|
emitToUnwritableDir()
|
|
case "emitToNilWriter":
|
|
emitToNilWriter()
|
|
case "emitToFailingWriter":
|
|
emitToFailingWriter()
|
|
case "emitWithCounterClear":
|
|
emitWithCounterClear()
|
|
default:
|
|
log.Fatalf("error: unknown testpoint %q", *testpointflag)
|
|
}
|
|
final()
|
|
}
|