go/src/runtime/coverage/testdata/harness.go
Than McIntosh ecd112c0d1 runtime/coverage: apis to emit counter data under user control
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>
2022-09-29 14:14:24 +00:00

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()
}