| // 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.WriteMeta(&slwm); err != nil { |
| log.Fatalf("error: WriteMeta 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.WriteCounters(&slwc); err != nil { |
| log.Fatalf("error: WriteCounters 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.WriteMetaDir(*outdirflag); err != nil { |
| log.Fatalf("error: WriteMetaDir returns %v", err) |
| } |
| if err := coverage.WriteCountersDir(*outdirflag); err != nil { |
| log.Fatalf("error: WriteCountersDir returns %v", err) |
| } |
| } |
| |
| func emitToNonexistentDir() { |
| log.SetPrefix("emitToNonexistentDir: ") |
| |
| want := []string{ |
| "no such file or directory", // linux-ish |
| "system cannot find the file specified", // windows |
| "does not exist", // plan9 |
| } |
| |
| 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.WriteMetaDir(mangled); err == nil { |
| log.Fatal("expected error from WriteMetaDir 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.WriteCountersDir(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.WriteMetaDir(*outdirflag); err == nil { |
| log.Fatal("expected error from WriteMetaDir 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.WriteCountersDir(*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.WriteMeta(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.WriteCounters(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.WriteMeta(f) |
| }) |
| writeStressTest("emit-counter", func(f *failingWriter) error { |
| return coverage.WriteCounters(f) |
| }) |
| } |
| |
| func emitWithCounterClear() { |
| log.SetPrefix("emitWitCounterClear: ") |
| preClear() |
| if err := coverage.ClearCounters(); err != nil { |
| log.Fatalf("clear failed: %v", err) |
| } |
| postClear() |
| if err := coverage.WriteMetaDir(*outdirflag); err != nil { |
| log.Fatalf("error: WriteMetaDir returns %v", err) |
| } |
| if err := coverage.WriteCountersDir(*outdirflag); err != nil { |
| log.Fatalf("error: WriteCountersDir 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() |
| } |