summaryrefslogtreecommitdiffstats
path: root/src/runtime/coverage
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
commitccd992355df7192993c666236047820244914598 (patch)
treef00fea65147227b7743083c6148396f74cd66935 /src/runtime/coverage
parentInitial commit. (diff)
downloadgolang-1.21-ccd992355df7192993c666236047820244914598.tar.xz
golang-1.21-ccd992355df7192993c666236047820244914598.zip
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/runtime/coverage')
-rw-r--r--src/runtime/coverage/apis.go184
-rw-r--r--src/runtime/coverage/dummy.s8
-rw-r--r--src/runtime/coverage/emit.go622
-rw-r--r--src/runtime/coverage/emitdata_test.go550
-rw-r--r--src/runtime/coverage/hooks.go42
-rw-r--r--src/runtime/coverage/testdata/harness.go259
-rw-r--r--src/runtime/coverage/testdata/issue56006/repro.go26
-rw-r--r--src/runtime/coverage/testdata/issue56006/repro_test.go8
-rw-r--r--src/runtime/coverage/testdata/issue59563/repro.go823
-rw-r--r--src/runtime/coverage/testdata/issue59563/repro_test.go14
-rw-r--r--src/runtime/coverage/testsupport.go323
-rw-r--r--src/runtime/coverage/ts_test.go207
12 files changed, 3066 insertions, 0 deletions
diff --git a/src/runtime/coverage/apis.go b/src/runtime/coverage/apis.go
new file mode 100644
index 0000000..05da345
--- /dev/null
+++ b/src/runtime/coverage/apis.go
@@ -0,0 +1,184 @@
+// 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 coverage
+
+import (
+ "fmt"
+ "internal/coverage"
+ "io"
+ "reflect"
+ "sync/atomic"
+ "unsafe"
+)
+
+// WriteMetaDir writes a coverage meta-data file for the currently
+// running program to the directory specified in 'dir'. An error will
+// be returned if the operation can't be completed successfully (for
+// example, if the currently running program was not built with
+// "-cover", or if the directory does not exist).
+func WriteMetaDir(dir string) error {
+ if !finalHashComputed {
+ return fmt.Errorf("error: no meta-data available (binary not built with -cover?)")
+ }
+ return emitMetaDataToDirectory(dir, getCovMetaList())
+}
+
+// WriteMeta writes the meta-data content (the payload that would
+// normally be emitted to a meta-data file) for the currently running
+// program to the writer 'w'. An error will be returned if the
+// operation can't be completed successfully (for example, if the
+// currently running program was not built with "-cover", or if a
+// write fails).
+func WriteMeta(w io.Writer) error {
+ if w == nil {
+ return fmt.Errorf("error: nil writer in WriteMeta")
+ }
+ if !finalHashComputed {
+ return fmt.Errorf("error: no meta-data available (binary not built with -cover?)")
+ }
+ ml := getCovMetaList()
+ return writeMetaData(w, ml, cmode, cgran, finalHash)
+}
+
+// WriteCountersDir writes a coverage counter-data file for the
+// currently running program to the directory specified in 'dir'. An
+// error will be returned if the operation can't be completed
+// successfully (for example, if the currently running program was not
+// built with "-cover", or if the directory does not exist). The
+// counter data written will be a snapshot taken at the point of the
+// call.
+func WriteCountersDir(dir string) error {
+ if cmode != coverage.CtrModeAtomic {
+ return fmt.Errorf("WriteCountersDir invoked for program built with -covermode=%s (please use -covermode=atomic)", cmode.String())
+ }
+ return emitCounterDataToDirectory(dir)
+}
+
+// WriteCounters writes coverage counter-data content for the
+// currently running program to the writer 'w'. An error will be
+// returned if the operation can't be completed successfully (for
+// example, if the currently running program was not built with
+// "-cover", or if a write fails). The counter data written will be a
+// snapshot taken at the point of the invocation.
+func WriteCounters(w io.Writer) error {
+ if w == nil {
+ return fmt.Errorf("error: nil writer in WriteCounters")
+ }
+ if cmode != coverage.CtrModeAtomic {
+ return fmt.Errorf("WriteCounters invoked for program built with -covermode=%s (please use -covermode=atomic)", cmode.String())
+ }
+ // Ask the runtime for the list of coverage counter symbols.
+ cl := getCovCounterList()
+ if len(cl) == 0 {
+ return fmt.Errorf("program not built with -cover")
+ }
+ if !finalHashComputed {
+ return fmt.Errorf("meta-data not written yet, unable to write counter data")
+ }
+
+ pm := getCovPkgMap()
+ s := &emitState{
+ counterlist: cl,
+ pkgmap: pm,
+ }
+ return s.emitCounterDataToWriter(w)
+}
+
+// ClearCounters clears/resets all coverage counter variables in the
+// currently running program. It returns an error if the program in
+// question was not built with the "-cover" flag. Clearing of coverage
+// counters is also not supported for programs not using atomic
+// counter mode (see more detailed comments below for the rationale
+// here).
+func ClearCounters() error {
+ cl := getCovCounterList()
+ if len(cl) == 0 {
+ return fmt.Errorf("program not built with -cover")
+ }
+ if cmode != coverage.CtrModeAtomic {
+ return fmt.Errorf("ClearCounters invoked for program built with -covermode=%s (please use -covermode=atomic)", cmode.String())
+ }
+
+ // Implementation note: this function would be faster and simpler
+ // if we could just zero out the entire counter array, but for the
+ // moment we go through and zero out just the slots in the array
+ // corresponding to the counter values. We do this to avoid the
+ // following bad scenario: suppose that a user builds their Go
+ // program with "-cover", and that program has a function (call it
+ // main.XYZ) that invokes ClearCounters:
+ //
+ // func XYZ() {
+ // ... do some stuff ...
+ // coverage.ClearCounters()
+ // if someCondition { <<--- HERE
+ // ...
+ // }
+ // }
+ //
+ // At the point where ClearCounters executes, main.XYZ has not yet
+ // finished running, thus as soon as the call returns the line
+ // marked "HERE" above will trigger the writing of a non-zero
+ // value into main.XYZ's counter slab. However since we've just
+ // finished clearing the entire counter segment, we will have lost
+ // the values in the prolog portion of main.XYZ's counter slab
+ // (nctrs, pkgid, funcid). This means that later on at the end of
+ // program execution as we walk through the entire counter array
+ // for the program looking for executed functions, we'll zoom past
+ // main.XYZ's prolog (which was zero'd) and hit the non-zero
+ // counter value corresponding to the "HERE" block, which will
+ // then be interpreted as the start of another live function.
+ // Things will go downhill from there.
+ //
+ // This same scenario is also a potential risk if the program is
+ // running on an architecture that permits reordering of
+ // writes/stores, since the inconsistency described above could
+ // arise here. Example scenario:
+ //
+ // func ABC() {
+ // ... // prolog
+ // if alwaysTrue() {
+ // XYZ() // counter update here
+ // }
+ // }
+ //
+ // In the instrumented version of ABC, the prolog of the function
+ // will contain a series of stores to the initial portion of the
+ // counter array to write number-of-counters, pkgid, funcid. Later
+ // in the function there is also a store to increment a counter
+ // for the block containing the call to XYZ(). If the CPU is
+ // allowed to reorder stores and decides to issue the XYZ store
+ // before the prolog stores, this could be observable as an
+ // inconsistency similar to the one above. Hence the requirement
+ // for atomic counter mode: according to package atomic docs,
+ // "...operations that happen in a specific order on one thread,
+ // will always be observed to happen in exactly that order by
+ // another thread". Thus we can be sure that there will be no
+ // inconsistency when reading the counter array from the thread
+ // running ClearCounters.
+
+ var sd []atomic.Uint32
+
+ bufHdr := (*reflect.SliceHeader)(unsafe.Pointer(&sd))
+ for _, c := range cl {
+ bufHdr.Data = uintptr(unsafe.Pointer(c.Counters))
+ bufHdr.Len = int(c.Len)
+ bufHdr.Cap = int(c.Len)
+ for i := 0; i < len(sd); i++ {
+ // Skip ahead until the next non-zero value.
+ sdi := sd[i].Load()
+ if sdi == 0 {
+ continue
+ }
+ // We found a function that was executed; clear its counters.
+ nCtrs := sdi
+ for j := 0; j < int(nCtrs); j++ {
+ sd[i+coverage.FirstCtrOffset+j].Store(0)
+ }
+ // Move to next function.
+ i += coverage.FirstCtrOffset + int(nCtrs) - 1
+ }
+ }
+ return nil
+}
diff --git a/src/runtime/coverage/dummy.s b/src/runtime/coverage/dummy.s
new file mode 100644
index 0000000..7592859
--- /dev/null
+++ b/src/runtime/coverage/dummy.s
@@ -0,0 +1,8 @@
+// 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.
+
+// The runtime package uses //go:linkname to push a few functions into this
+// package but we still need a .s file so the Go tool does not pass -complete
+// to 'go tool compile' so the latter does not complain about Go functions
+// with no bodies.
diff --git a/src/runtime/coverage/emit.go b/src/runtime/coverage/emit.go
new file mode 100644
index 0000000..bb0c6fb
--- /dev/null
+++ b/src/runtime/coverage/emit.go
@@ -0,0 +1,622 @@
+// 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 coverage
+
+import (
+ "crypto/md5"
+ "fmt"
+ "internal/coverage"
+ "internal/coverage/encodecounter"
+ "internal/coverage/encodemeta"
+ "internal/coverage/rtcov"
+ "io"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "strconv"
+ "sync/atomic"
+ "time"
+ "unsafe"
+)
+
+// This file contains functions that support the writing of data files
+// emitted at the end of code coverage testing runs, from instrumented
+// executables.
+
+// getCovMetaList returns a list of meta-data blobs registered
+// for the currently executing instrumented program. It is defined in the
+// runtime.
+func getCovMetaList() []rtcov.CovMetaBlob
+
+// getCovCounterList returns a list of counter-data blobs registered
+// for the currently executing instrumented program. It is defined in the
+// runtime.
+func getCovCounterList() []rtcov.CovCounterBlob
+
+// getCovPkgMap returns a map storing the remapped package IDs for
+// hard-coded runtime packages (see internal/coverage/pkgid.go for
+// more on why hard-coded package IDs are needed). This function
+// is defined in the runtime.
+func getCovPkgMap() map[int]int
+
+// emitState holds useful state information during the emit process.
+//
+// When an instrumented program finishes execution and starts the
+// process of writing out coverage data, it's possible that an
+// existing meta-data file already exists in the output directory. In
+// this case openOutputFiles() below will leave the 'mf' field below
+// as nil. If a new meta-data file is needed, field 'mfname' will be
+// the final desired path of the meta file, 'mftmp' will be a
+// temporary file, and 'mf' will be an open os.File pointer for
+// 'mftmp'. The meta-data file payload will be written to 'mf', the
+// temp file will be then closed and renamed (from 'mftmp' to
+// 'mfname'), so as to insure that the meta-data file is created
+// atomically; we want this so that things work smoothly in cases
+// where there are several instances of a given instrumented program
+// all terminating at the same time and trying to create meta-data
+// files simultaneously.
+//
+// For counter data files there is less chance of a collision, hence
+// the openOutputFiles() stores the counter data file in 'cfname' and
+// then places the *io.File into 'cf'.
+type emitState struct {
+ mfname string // path of final meta-data output file
+ mftmp string // path to meta-data temp file (if needed)
+ mf *os.File // open os.File for meta-data temp file
+ cfname string // path of final counter data file
+ cftmp string // path to counter data temp file
+ cf *os.File // open os.File for counter data file
+ outdir string // output directory
+
+ // List of meta-data symbols obtained from the runtime
+ metalist []rtcov.CovMetaBlob
+
+ // List of counter-data symbols obtained from the runtime
+ counterlist []rtcov.CovCounterBlob
+
+ // Table to use for remapping hard-coded pkg ids.
+ pkgmap map[int]int
+
+ // emit debug trace output
+ debug bool
+}
+
+var (
+ // finalHash is computed at init time from the list of meta-data
+ // symbols registered during init. It is used both for writing the
+ // meta-data file and counter-data files.
+ finalHash [16]byte
+ // Set to true when we've computed finalHash + finalMetaLen.
+ finalHashComputed bool
+ // Total meta-data length.
+ finalMetaLen uint64
+ // Records whether we've already attempted to write meta-data.
+ metaDataEmitAttempted bool
+ // Counter mode for this instrumented program run.
+ cmode coverage.CounterMode
+ // Counter granularity for this instrumented program run.
+ cgran coverage.CounterGranularity
+ // Cached value of GOCOVERDIR environment variable.
+ goCoverDir string
+ // Copy of os.Args made at init time, converted into map format.
+ capturedOsArgs map[string]string
+ // Flag used in tests to signal that coverage data already written.
+ covProfileAlreadyEmitted bool
+)
+
+// fileType is used to select between counter-data files and
+// meta-data files.
+type fileType int
+
+const (
+ noFile = 1 << iota
+ metaDataFile
+ counterDataFile
+)
+
+// emitMetaData emits the meta-data output file for this coverage run.
+// This entry point is intended to be invoked by the compiler from
+// an instrumented program's main package init func.
+func emitMetaData() {
+ if covProfileAlreadyEmitted {
+ return
+ }
+ ml, err := prepareForMetaEmit()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "error: coverage meta-data prep failed: %v\n", err)
+ if os.Getenv("GOCOVERDEBUG") != "" {
+ panic("meta-data write failure")
+ }
+ }
+ if len(ml) == 0 {
+ fmt.Fprintf(os.Stderr, "program not built with -cover\n")
+ return
+ }
+
+ goCoverDir = os.Getenv("GOCOVERDIR")
+ if goCoverDir == "" {
+ fmt.Fprintf(os.Stderr, "warning: GOCOVERDIR not set, no coverage data emitted\n")
+ return
+ }
+
+ if err := emitMetaDataToDirectory(goCoverDir, ml); err != nil {
+ fmt.Fprintf(os.Stderr, "error: coverage meta-data emit failed: %v\n", err)
+ if os.Getenv("GOCOVERDEBUG") != "" {
+ panic("meta-data write failure")
+ }
+ }
+}
+
+func modeClash(m coverage.CounterMode) bool {
+ if m == coverage.CtrModeRegOnly || m == coverage.CtrModeTestMain {
+ return false
+ }
+ if cmode == coverage.CtrModeInvalid {
+ cmode = m
+ return false
+ }
+ return cmode != m
+}
+
+func granClash(g coverage.CounterGranularity) bool {
+ if cgran == coverage.CtrGranularityInvalid {
+ cgran = g
+ return false
+ }
+ return cgran != g
+}
+
+// prepareForMetaEmit performs preparatory steps needed prior to
+// emitting a meta-data file, notably computing a final hash of
+// all meta-data blobs and capturing os args.
+func prepareForMetaEmit() ([]rtcov.CovMetaBlob, error) {
+ // Ask the runtime for the list of coverage meta-data symbols.
+ ml := getCovMetaList()
+
+ // In the normal case (go build -o prog.exe ... ; ./prog.exe)
+ // len(ml) will always be non-zero, but we check here since at
+ // some point this function will be reachable via user-callable
+ // APIs (for example, to write out coverage data from a server
+ // program that doesn't ever call os.Exit).
+ if len(ml) == 0 {
+ return nil, nil
+ }
+
+ s := &emitState{
+ metalist: ml,
+ debug: os.Getenv("GOCOVERDEBUG") != "",
+ }
+
+ // Capture os.Args() now so as to avoid issues if args
+ // are rewritten during program execution.
+ capturedOsArgs = captureOsArgs()
+
+ if s.debug {
+ fmt.Fprintf(os.Stderr, "=+= GOCOVERDIR is %s\n", os.Getenv("GOCOVERDIR"))
+ fmt.Fprintf(os.Stderr, "=+= contents of covmetalist:\n")
+ for k, b := range ml {
+ fmt.Fprintf(os.Stderr, "=+= slot: %d path: %s ", k, b.PkgPath)
+ if b.PkgID != -1 {
+ fmt.Fprintf(os.Stderr, " hcid: %d", b.PkgID)
+ }
+ fmt.Fprintf(os.Stderr, "\n")
+ }
+ pm := getCovPkgMap()
+ fmt.Fprintf(os.Stderr, "=+= remap table:\n")
+ for from, to := range pm {
+ fmt.Fprintf(os.Stderr, "=+= from %d to %d\n",
+ uint32(from), uint32(to))
+ }
+ }
+
+ h := md5.New()
+ tlen := uint64(unsafe.Sizeof(coverage.MetaFileHeader{}))
+ for _, entry := range ml {
+ if _, err := h.Write(entry.Hash[:]); err != nil {
+ return nil, err
+ }
+ tlen += uint64(entry.Len)
+ ecm := coverage.CounterMode(entry.CounterMode)
+ if modeClash(ecm) {
+ return nil, fmt.Errorf("coverage counter mode clash: package %s uses mode=%d, but package %s uses mode=%s\n", ml[0].PkgPath, cmode, entry.PkgPath, ecm)
+ }
+ ecg := coverage.CounterGranularity(entry.CounterGranularity)
+ if granClash(ecg) {
+ return nil, fmt.Errorf("coverage counter granularity clash: package %s uses gran=%d, but package %s uses gran=%s\n", ml[0].PkgPath, cgran, entry.PkgPath, ecg)
+ }
+ }
+
+ // Hash mode and granularity as well.
+ h.Write([]byte(cmode.String()))
+ h.Write([]byte(cgran.String()))
+
+ // Compute final digest.
+ fh := h.Sum(nil)
+ copy(finalHash[:], fh)
+ finalHashComputed = true
+ finalMetaLen = tlen
+
+ return ml, nil
+}
+
+// emitMetaDataToDirectory emits the meta-data output file to the specified
+// directory, returning an error if something went wrong.
+func emitMetaDataToDirectory(outdir string, ml []rtcov.CovMetaBlob) error {
+ ml, err := prepareForMetaEmit()
+ if err != nil {
+ return err
+ }
+ if len(ml) == 0 {
+ return nil
+ }
+
+ metaDataEmitAttempted = true
+
+ s := &emitState{
+ metalist: ml,
+ debug: os.Getenv("GOCOVERDEBUG") != "",
+ outdir: outdir,
+ }
+
+ // Open output files.
+ if err := s.openOutputFiles(finalHash, finalMetaLen, metaDataFile); err != nil {
+ return err
+ }
+
+ // Emit meta-data file only if needed (may already be present).
+ if s.needMetaDataFile() {
+ if err := s.emitMetaDataFile(finalHash, finalMetaLen); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// emitCounterData emits the counter data output file for this coverage run.
+// This entry point is intended to be invoked by the runtime when an
+// instrumented program is terminating or calling os.Exit().
+func emitCounterData() {
+ if goCoverDir == "" || !finalHashComputed || covProfileAlreadyEmitted {
+ return
+ }
+ if err := emitCounterDataToDirectory(goCoverDir); err != nil {
+ fmt.Fprintf(os.Stderr, "error: coverage counter data emit failed: %v\n", err)
+ if os.Getenv("GOCOVERDEBUG") != "" {
+ panic("counter-data write failure")
+ }
+ }
+}
+
+// emitCounterDataToDirectory emits the counter-data output file for this coverage run.
+func emitCounterDataToDirectory(outdir string) error {
+ // Ask the runtime for the list of coverage counter symbols.
+ cl := getCovCounterList()
+ if len(cl) == 0 {
+ // no work to do here.
+ return nil
+ }
+
+ if !finalHashComputed {
+ return fmt.Errorf("error: meta-data not available (binary not built with -cover?)")
+ }
+
+ // Ask the runtime for the list of coverage counter symbols.
+ pm := getCovPkgMap()
+ s := &emitState{
+ counterlist: cl,
+ pkgmap: pm,
+ outdir: outdir,
+ debug: os.Getenv("GOCOVERDEBUG") != "",
+ }
+
+ // Open output file.
+ if err := s.openOutputFiles(finalHash, finalMetaLen, counterDataFile); err != nil {
+ return err
+ }
+ if s.cf == nil {
+ return fmt.Errorf("counter data output file open failed (no additional info")
+ }
+
+ // Emit counter data file.
+ if err := s.emitCounterDataFile(finalHash, s.cf); err != nil {
+ return err
+ }
+ if err := s.cf.Close(); err != nil {
+ return fmt.Errorf("closing counter data file: %v", err)
+ }
+
+ // Counter file has now been closed. Rename the temp to the
+ // final desired path.
+ if err := os.Rename(s.cftmp, s.cfname); err != nil {
+ return fmt.Errorf("writing %s: rename from %s failed: %v\n", s.cfname, s.cftmp, err)
+ }
+
+ return nil
+}
+
+// emitCounterDataToWriter emits counter data for this coverage run to an io.Writer.
+func (s *emitState) emitCounterDataToWriter(w io.Writer) error {
+ if err := s.emitCounterDataFile(finalHash, w); err != nil {
+ return err
+ }
+ return nil
+}
+
+// openMetaFile determines whether we need to emit a meta-data output
+// file, or whether we can reuse the existing file in the coverage out
+// dir. It updates mfname/mftmp/mf fields in 's', returning an error
+// if something went wrong. See the comment on the emitState type
+// definition above for more on how file opening is managed.
+func (s *emitState) openMetaFile(metaHash [16]byte, metaLen uint64) error {
+
+ // Open meta-outfile for reading to see if it exists.
+ fn := fmt.Sprintf("%s.%x", coverage.MetaFilePref, metaHash)
+ s.mfname = filepath.Join(s.outdir, fn)
+ fi, err := os.Stat(s.mfname)
+ if err != nil || fi.Size() != int64(metaLen) {
+ // We need a new meta-file.
+ tname := "tmp." + fn + strconv.FormatInt(time.Now().UnixNano(), 10)
+ s.mftmp = filepath.Join(s.outdir, tname)
+ s.mf, err = os.Create(s.mftmp)
+ if err != nil {
+ return fmt.Errorf("creating meta-data file %s: %v", s.mftmp, err)
+ }
+ }
+ return nil
+}
+
+// openCounterFile opens an output file for the counter data portion
+// of a test coverage run. If updates the 'cfname' and 'cf' fields in
+// 's', returning an error if something went wrong.
+func (s *emitState) openCounterFile(metaHash [16]byte) error {
+ processID := os.Getpid()
+ fn := fmt.Sprintf(coverage.CounterFileTempl, coverage.CounterFilePref, metaHash, processID, time.Now().UnixNano())
+ s.cfname = filepath.Join(s.outdir, fn)
+ s.cftmp = filepath.Join(s.outdir, "tmp."+fn)
+ var err error
+ s.cf, err = os.Create(s.cftmp)
+ if err != nil {
+ return fmt.Errorf("creating counter data file %s: %v", s.cftmp, err)
+ }
+ return nil
+}
+
+// openOutputFiles opens output files in preparation for emitting
+// coverage data. In the case of the meta-data file, openOutputFiles
+// may determine that we can reuse an existing meta-data file in the
+// outdir, in which case it will leave the 'mf' field in the state
+// struct as nil. If a new meta-file is needed, the field 'mfname'
+// will be the final desired path of the meta file, 'mftmp' will be a
+// temporary file, and 'mf' will be an open os.File pointer for
+// 'mftmp'. The idea is that the client/caller will write content into
+// 'mf', close it, and then rename 'mftmp' to 'mfname'. This function
+// also opens the counter data output file, setting 'cf' and 'cfname'
+// in the state struct.
+func (s *emitState) openOutputFiles(metaHash [16]byte, metaLen uint64, which fileType) error {
+ fi, err := os.Stat(s.outdir)
+ if err != nil {
+ return fmt.Errorf("output directory %q inaccessible (err: %v); no coverage data written", s.outdir, err)
+ }
+ if !fi.IsDir() {
+ return fmt.Errorf("output directory %q not a directory; no coverage data written", s.outdir)
+ }
+
+ if (which & metaDataFile) != 0 {
+ if err := s.openMetaFile(metaHash, metaLen); err != nil {
+ return err
+ }
+ }
+ if (which & counterDataFile) != 0 {
+ if err := s.openCounterFile(metaHash); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// emitMetaDataFile emits coverage meta-data to a previously opened
+// temporary file (s.mftmp), then renames the generated file to the
+// final path (s.mfname).
+func (s *emitState) emitMetaDataFile(finalHash [16]byte, tlen uint64) error {
+ if err := writeMetaData(s.mf, s.metalist, cmode, cgran, finalHash); err != nil {
+ return fmt.Errorf("writing %s: %v\n", s.mftmp, err)
+ }
+ if err := s.mf.Close(); err != nil {
+ return fmt.Errorf("closing meta data temp file: %v", err)
+ }
+
+ // Temp file has now been flushed and closed. Rename the temp to the
+ // final desired path.
+ if err := os.Rename(s.mftmp, s.mfname); err != nil {
+ return fmt.Errorf("writing %s: rename from %s failed: %v\n", s.mfname, s.mftmp, err)
+ }
+
+ return nil
+}
+
+// needMetaDataFile returns TRUE if we need to emit a meta-data file
+// for this program run. It should be used only after
+// openOutputFiles() has been invoked.
+func (s *emitState) needMetaDataFile() bool {
+ return s.mf != nil
+}
+
+func writeMetaData(w io.Writer, metalist []rtcov.CovMetaBlob, cmode coverage.CounterMode, gran coverage.CounterGranularity, finalHash [16]byte) error {
+ mfw := encodemeta.NewCoverageMetaFileWriter("<io.Writer>", w)
+
+ // Note: "sd" is re-initialized on each iteration of the loop
+ // below, and would normally be declared inside the loop, but
+ // placed here escape analysis since we capture it in bufHdr.
+ var sd []byte
+ bufHdr := (*reflect.SliceHeader)(unsafe.Pointer(&sd))
+
+ var blobs [][]byte
+ for _, e := range metalist {
+ bufHdr.Data = uintptr(unsafe.Pointer(e.P))
+ bufHdr.Len = int(e.Len)
+ bufHdr.Cap = int(e.Len)
+ blobs = append(blobs, sd)
+ }
+ return mfw.Write(finalHash, blobs, cmode, gran)
+}
+
+func (s *emitState) VisitFuncs(f encodecounter.CounterVisitorFn) error {
+ var sd []atomic.Uint32
+ var tcounters []uint32
+ bufHdr := (*reflect.SliceHeader)(unsafe.Pointer(&sd))
+
+ rdCounters := func(actrs []atomic.Uint32, ctrs []uint32) []uint32 {
+ ctrs = ctrs[:0]
+ for i := range actrs {
+ ctrs = append(ctrs, actrs[i].Load())
+ }
+ return ctrs
+ }
+
+ dpkg := uint32(0)
+ for _, c := range s.counterlist {
+ bufHdr.Data = uintptr(unsafe.Pointer(c.Counters))
+ bufHdr.Len = int(c.Len)
+ bufHdr.Cap = int(c.Len)
+ for i := 0; i < len(sd); i++ {
+ // Skip ahead until the next non-zero value.
+ sdi := sd[i].Load()
+ if sdi == 0 {
+ continue
+ }
+
+ // We found a function that was executed.
+ nCtrs := sd[i+coverage.NumCtrsOffset].Load()
+ pkgId := sd[i+coverage.PkgIdOffset].Load()
+ funcId := sd[i+coverage.FuncIdOffset].Load()
+ cst := i + coverage.FirstCtrOffset
+ counters := sd[cst : cst+int(nCtrs)]
+
+ // Check to make sure that we have at least one live
+ // counter. See the implementation note in ClearCoverageCounters
+ // for a description of why this is needed.
+ isLive := false
+ for i := 0; i < len(counters); i++ {
+ if counters[i].Load() != 0 {
+ isLive = true
+ break
+ }
+ }
+ if !isLive {
+ // Skip this function.
+ i += coverage.FirstCtrOffset + int(nCtrs) - 1
+ continue
+ }
+
+ if s.debug {
+ if pkgId != dpkg {
+ dpkg = pkgId
+ fmt.Fprintf(os.Stderr, "\n=+= %d: pk=%d visit live fcn",
+ i, pkgId)
+ }
+ fmt.Fprintf(os.Stderr, " {i=%d F%d NC%d}", i, funcId, nCtrs)
+ }
+
+ // Vet and/or fix up package ID. A package ID of zero
+ // indicates that there is some new package X that is a
+ // runtime dependency, and this package has code that
+ // executes before its corresponding init package runs.
+ // This is a fatal error that we should only see during
+ // Go development (e.g. tip).
+ ipk := int32(pkgId)
+ if ipk == 0 {
+ fmt.Fprintf(os.Stderr, "\n")
+ reportErrorInHardcodedList(int32(i), ipk, funcId, nCtrs)
+ } else if ipk < 0 {
+ if newId, ok := s.pkgmap[int(ipk)]; ok {
+ pkgId = uint32(newId)
+ } else {
+ fmt.Fprintf(os.Stderr, "\n")
+ reportErrorInHardcodedList(int32(i), ipk, funcId, nCtrs)
+ }
+ } else {
+ // The package ID value stored in the counter array
+ // has 1 added to it (so as to preclude the
+ // possibility of a zero value ; see
+ // runtime.addCovMeta), so subtract off 1 here to form
+ // the real package ID.
+ pkgId--
+ }
+
+ tcounters = rdCounters(counters, tcounters)
+ if err := f(pkgId, funcId, tcounters); err != nil {
+ return err
+ }
+
+ // Skip over this function.
+ i += coverage.FirstCtrOffset + int(nCtrs) - 1
+ }
+ if s.debug {
+ fmt.Fprintf(os.Stderr, "\n")
+ }
+ }
+ return nil
+}
+
+// captureOsArgs converts os.Args() into the format we use to store
+// this info in the counter data file (counter data file "args"
+// section is a generic key-value collection). See the 'args' section
+// in internal/coverage/defs.go for more info. The args map
+// is also used to capture GOOS + GOARCH values as well.
+func captureOsArgs() map[string]string {
+ m := make(map[string]string)
+ m["argc"] = strconv.Itoa(len(os.Args))
+ for k, a := range os.Args {
+ m[fmt.Sprintf("argv%d", k)] = a
+ }
+ m["GOOS"] = runtime.GOOS
+ m["GOARCH"] = runtime.GOARCH
+ return m
+}
+
+// emitCounterDataFile emits the counter data portion of a
+// coverage output file (to the file 's.cf').
+func (s *emitState) emitCounterDataFile(finalHash [16]byte, w io.Writer) error {
+ cfw := encodecounter.NewCoverageDataWriter(w, coverage.CtrULeb128)
+ if err := cfw.Write(finalHash, capturedOsArgs, s); err != nil {
+ return err
+ }
+ return nil
+}
+
+// markProfileEmitted signals the runtime/coverage machinery that
+// coverate data output files have already been written out, and there
+// is no need to take any additional action at exit time. This
+// function is called (via linknamed reference) from the
+// coverage-related boilerplate code in _testmain.go emitted for go
+// unit tests.
+func markProfileEmitted(val bool) {
+ covProfileAlreadyEmitted = val
+}
+
+func reportErrorInHardcodedList(slot, pkgID int32, fnID, nCtrs uint32) {
+ metaList := getCovMetaList()
+ pkgMap := getCovPkgMap()
+
+ println("internal error in coverage meta-data tracking:")
+ println("encountered bad pkgID:", pkgID, " at slot:", slot,
+ " fnID:", fnID, " numCtrs:", nCtrs)
+ println("list of hard-coded runtime package IDs needs revising.")
+ println("[see the comment on the 'rtPkgs' var in ")
+ println(" <goroot>/src/internal/coverage/pkid.go]")
+ println("registered list:")
+ for k, b := range metaList {
+ print("slot: ", k, " path='", b.PkgPath, "' ")
+ if b.PkgID != -1 {
+ print(" hard-coded id: ", b.PkgID)
+ }
+ println("")
+ }
+ println("remap table:")
+ for from, to := range pkgMap {
+ println("from ", from, " to ", to)
+ }
+}
diff --git a/src/runtime/coverage/emitdata_test.go b/src/runtime/coverage/emitdata_test.go
new file mode 100644
index 0000000..3558dd2
--- /dev/null
+++ b/src/runtime/coverage/emitdata_test.go
@@ -0,0 +1,550 @@
+// 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 coverage
+
+import (
+ "fmt"
+ "internal/coverage"
+ "internal/goexperiment"
+ "internal/platform"
+ "internal/testenv"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "testing"
+)
+
+// Set to true for debugging (linux only).
+const fixedTestDir = false
+
+func TestCoverageApis(t *testing.T) {
+ if testing.Short() {
+ t.Skipf("skipping test: too long for short mode")
+ }
+ if !goexperiment.CoverageRedesign {
+ t.Skipf("skipping new coverage tests (experiment not enabled)")
+ }
+ testenv.MustHaveGoBuild(t)
+ dir := t.TempDir()
+ if fixedTestDir {
+ dir = "/tmp/qqqzzz"
+ os.RemoveAll(dir)
+ mkdir(t, dir)
+ }
+
+ // Build harness. We need two copies of the harness, one built
+ // with -covermode=atomic and one built non-atomic.
+ bdir1 := mkdir(t, filepath.Join(dir, "build1"))
+ hargs1 := []string{"-covermode=atomic", "-coverpkg=all"}
+ atomicHarnessPath := buildHarness(t, bdir1, hargs1)
+ nonAtomicMode := testing.CoverMode()
+ if testing.CoverMode() == "atomic" {
+ nonAtomicMode = "set"
+ }
+ bdir2 := mkdir(t, filepath.Join(dir, "build2"))
+ hargs2 := []string{"-coverpkg=all", "-covermode=" + nonAtomicMode}
+ nonAtomicHarnessPath := buildHarness(t, bdir2, hargs2)
+
+ t.Logf("atomic harness path is %s", atomicHarnessPath)
+ t.Logf("non-atomic harness path is %s", nonAtomicHarnessPath)
+
+ // Sub-tests for each API we want to inspect, plus
+ // extras for error testing.
+ t.Run("emitToDir", func(t *testing.T) {
+ t.Parallel()
+ testEmitToDir(t, atomicHarnessPath, dir)
+ })
+ t.Run("emitToWriter", func(t *testing.T) {
+ t.Parallel()
+ testEmitToWriter(t, atomicHarnessPath, dir)
+ })
+ t.Run("emitToNonexistentDir", func(t *testing.T) {
+ t.Parallel()
+ testEmitToNonexistentDir(t, atomicHarnessPath, dir)
+ })
+ t.Run("emitToNilWriter", func(t *testing.T) {
+ t.Parallel()
+ testEmitToNilWriter(t, atomicHarnessPath, dir)
+ })
+ t.Run("emitToFailingWriter", func(t *testing.T) {
+ t.Parallel()
+ testEmitToFailingWriter(t, atomicHarnessPath, dir)
+ })
+ t.Run("emitWithCounterClear", func(t *testing.T) {
+ t.Parallel()
+ testEmitWithCounterClear(t, atomicHarnessPath, dir)
+ })
+ t.Run("emitToDirNonAtomic", func(t *testing.T) {
+ t.Parallel()
+ testEmitToDirNonAtomic(t, nonAtomicHarnessPath, nonAtomicMode, dir)
+ })
+ t.Run("emitToWriterNonAtomic", func(t *testing.T) {
+ t.Parallel()
+ testEmitToWriterNonAtomic(t, nonAtomicHarnessPath, nonAtomicMode, dir)
+ })
+ t.Run("emitWithCounterClearNonAtomic", func(t *testing.T) {
+ t.Parallel()
+ testEmitWithCounterClearNonAtomic(t, nonAtomicHarnessPath, nonAtomicMode, dir)
+ })
+}
+
+// upmergeCoverData helps improve coverage data for this package
+// itself. If this test itself is being invoked with "-cover", then
+// what we'd like is for package coverage data (that is, coverage for
+// routines in "runtime/coverage") to be incorporated into the test
+// run from the "harness.exe" runs we've just done. We can accomplish
+// this by doing a merge from the harness gocoverdir's to the test
+// gocoverdir.
+func upmergeCoverData(t *testing.T, gocoverdir string, mode string) {
+ if testing.CoverMode() != mode {
+ return
+ }
+ testGoCoverDir := os.Getenv("GOCOVERDIR")
+ if testGoCoverDir == "" {
+ return
+ }
+ args := []string{"tool", "covdata", "merge", "-pkg=runtime/coverage",
+ "-o", testGoCoverDir, "-i", gocoverdir}
+ t.Logf("up-merge of covdata from %s to %s", gocoverdir, testGoCoverDir)
+ t.Logf("executing: go %+v", args)
+ cmd := exec.Command(testenv.GoToolPath(t), args...)
+ if b, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("covdata merge failed (%v): %s", err, b)
+ }
+}
+
+// buildHarness builds the helper program "harness.exe".
+func buildHarness(t *testing.T, dir string, opts []string) string {
+ harnessPath := filepath.Join(dir, "harness.exe")
+ harnessSrc := filepath.Join("testdata", "harness.go")
+ args := []string{"build", "-o", harnessPath}
+ args = append(args, opts...)
+ args = append(args, harnessSrc)
+ //t.Logf("harness build: go %+v\n", args)
+ cmd := exec.Command(testenv.GoToolPath(t), args...)
+ if b, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("build failed (%v): %s", err, b)
+ }
+ return harnessPath
+}
+
+func mkdir(t *testing.T, d string) string {
+ t.Helper()
+ if err := os.Mkdir(d, 0777); err != nil {
+ t.Fatalf("mkdir failed: %v", err)
+ }
+ return d
+}
+
+// updateGoCoverDir updates the specified environment 'env' to set
+// GOCOVERDIR to 'gcd' (if setGoCoverDir is TRUE) or removes
+// GOCOVERDIR from the environment (if setGoCoverDir is false).
+func updateGoCoverDir(env []string, gcd string, setGoCoverDir bool) []string {
+ rv := []string{}
+ found := false
+ for _, v := range env {
+ if strings.HasPrefix(v, "GOCOVERDIR=") {
+ if !setGoCoverDir {
+ continue
+ }
+ v = "GOCOVERDIR=" + gcd
+ found = true
+ }
+ rv = append(rv, v)
+ }
+ if !found && setGoCoverDir {
+ rv = append(rv, "GOCOVERDIR="+gcd)
+ }
+ return rv
+}
+
+func runHarness(t *testing.T, harnessPath string, tp string, setGoCoverDir bool, rdir, edir string) (string, error) {
+ t.Logf("running: %s -tp %s -o %s with rdir=%s and GOCOVERDIR=%v", harnessPath, tp, edir, rdir, setGoCoverDir)
+ cmd := exec.Command(harnessPath, "-tp", tp, "-o", edir)
+ cmd.Dir = rdir
+ cmd.Env = updateGoCoverDir(os.Environ(), rdir, setGoCoverDir)
+ b, err := cmd.CombinedOutput()
+ //t.Logf("harness run output: %s\n", string(b))
+ return string(b), err
+}
+
+func testForSpecificFunctions(t *testing.T, dir string, want []string, avoid []string) string {
+ args := []string{"tool", "covdata", "debugdump",
+ "-live", "-pkg=command-line-arguments", "-i=" + dir}
+ t.Logf("running: go %v\n", args)
+ cmd := exec.Command(testenv.GoToolPath(t), args...)
+ b, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("'go tool covdata failed (%v): %s", err, b)
+ }
+ output := string(b)
+ rval := ""
+ for _, f := range want {
+ wf := "Func: " + f + "\n"
+ if strings.Contains(output, wf) {
+ continue
+ }
+ rval += fmt.Sprintf("error: output should contain %q but does not\n", wf)
+ }
+ for _, f := range avoid {
+ wf := "Func: " + f + "\n"
+ if strings.Contains(output, wf) {
+ rval += fmt.Sprintf("error: output should not contain %q but does\n", wf)
+ }
+ }
+ if rval != "" {
+ t.Logf("=-= begin output:\n" + output + "\n=-= end output\n")
+ }
+ return rval
+}
+
+func withAndWithoutRunner(f func(setit bool, tag string)) {
+ // Run 'f' with and without GOCOVERDIR set.
+ for i := 0; i < 2; i++ {
+ tag := "x"
+ setGoCoverDir := true
+ if i == 0 {
+ setGoCoverDir = false
+ tag = "y"
+ }
+ f(setGoCoverDir, tag)
+ }
+}
+
+func mktestdirs(t *testing.T, tag, tp, dir string) (string, string) {
+ t.Helper()
+ rdir := mkdir(t, filepath.Join(dir, tp+"-rdir-"+tag))
+ edir := mkdir(t, filepath.Join(dir, tp+"-edir-"+tag))
+ return rdir, edir
+}
+
+func testEmitToDir(t *testing.T, harnessPath string, dir string) {
+ withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
+ tp := "emitToDir"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+ output, err := runHarness(t, harnessPath, tp,
+ setGoCoverDir, rdir, edir)
+ if err != nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp emitDir': %v", err)
+ }
+
+ // Just check to make sure meta-data file and counter data file were
+ // written. Another alternative would be to run "go tool covdata"
+ // or equivalent, but for now, this is what we've got.
+ dents, err := os.ReadDir(edir)
+ if err != nil {
+ t.Fatalf("os.ReadDir(%s) failed: %v", edir, err)
+ }
+ mfc := 0
+ cdc := 0
+ for _, e := range dents {
+ if e.IsDir() {
+ continue
+ }
+ if strings.HasPrefix(e.Name(), coverage.MetaFilePref) {
+ mfc++
+ } else if strings.HasPrefix(e.Name(), coverage.CounterFilePref) {
+ cdc++
+ }
+ }
+ wantmf := 1
+ wantcf := 1
+ if mfc != wantmf {
+ t.Errorf("EmitToDir: want %d meta-data files, got %d\n", wantmf, mfc)
+ }
+ if cdc != wantcf {
+ t.Errorf("EmitToDir: want %d counter-data files, got %d\n", wantcf, cdc)
+ }
+ upmergeCoverData(t, edir, "atomic")
+ upmergeCoverData(t, rdir, "atomic")
+ })
+}
+
+func testEmitToWriter(t *testing.T, harnessPath string, dir string) {
+ withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
+ tp := "emitToWriter"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+ output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
+ if err != nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp %s': %v", tp, err)
+ }
+ want := []string{"main", tp}
+ avoid := []string{"final"}
+ if msg := testForSpecificFunctions(t, edir, want, avoid); msg != "" {
+ t.Errorf("coverage data from %q output match failed: %s", tp, msg)
+ }
+ upmergeCoverData(t, edir, "atomic")
+ upmergeCoverData(t, rdir, "atomic")
+ })
+}
+
+func testEmitToNonexistentDir(t *testing.T, harnessPath string, dir string) {
+ withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
+ tp := "emitToNonexistentDir"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+ output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
+ if err != nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp %s': %v", tp, err)
+ }
+ upmergeCoverData(t, edir, "atomic")
+ upmergeCoverData(t, rdir, "atomic")
+ })
+}
+
+func testEmitToUnwritableDir(t *testing.T, harnessPath string, dir string) {
+ withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
+
+ tp := "emitToUnwritableDir"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+
+ // Make edir unwritable.
+ if err := os.Chmod(edir, 0555); err != nil {
+ t.Fatalf("chmod failed: %v", err)
+ }
+ defer os.Chmod(edir, 0777)
+
+ output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
+ if err != nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp %s': %v", tp, err)
+ }
+ upmergeCoverData(t, edir, "atomic")
+ upmergeCoverData(t, rdir, "atomic")
+ })
+}
+
+func testEmitToNilWriter(t *testing.T, harnessPath string, dir string) {
+ withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
+ tp := "emitToNilWriter"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+ output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
+ if err != nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp %s': %v", tp, err)
+ }
+ upmergeCoverData(t, edir, "atomic")
+ upmergeCoverData(t, rdir, "atomic")
+ })
+}
+
+func testEmitToFailingWriter(t *testing.T, harnessPath string, dir string) {
+ withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
+ tp := "emitToFailingWriter"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+ output, err := runHarness(t, harnessPath, tp, setGoCoverDir, rdir, edir)
+ if err != nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp %s': %v", tp, err)
+ }
+ upmergeCoverData(t, edir, "atomic")
+ upmergeCoverData(t, rdir, "atomic")
+ })
+}
+
+func testEmitWithCounterClear(t *testing.T, harnessPath string, dir string) {
+ withAndWithoutRunner(func(setGoCoverDir bool, tag string) {
+ tp := "emitWithCounterClear"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+ output, err := runHarness(t, harnessPath, tp,
+ setGoCoverDir, rdir, edir)
+ if err != nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp %s': %v", tp, err)
+ }
+ want := []string{tp, "postClear"}
+ avoid := []string{"preClear", "main", "final"}
+ if msg := testForSpecificFunctions(t, edir, want, avoid); msg != "" {
+ t.Logf("%s", output)
+ t.Errorf("coverage data from %q output match failed: %s", tp, msg)
+ }
+ upmergeCoverData(t, edir, "atomic")
+ upmergeCoverData(t, rdir, "atomic")
+ })
+}
+
+func testEmitToDirNonAtomic(t *testing.T, harnessPath string, naMode string, dir string) {
+ tp := "emitToDir"
+ tag := "nonatomdir"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+ output, err := runHarness(t, harnessPath, tp,
+ true, rdir, edir)
+
+ // We expect an error here.
+ if err == nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp %s': did not get expected error", tp)
+ }
+
+ got := strings.TrimSpace(string(output))
+ want := "WriteCountersDir invoked for program built"
+ if !strings.Contains(got, want) {
+ t.Errorf("running 'harness -tp %s': got:\n%s\nwant: %s",
+ tp, got, want)
+ }
+ upmergeCoverData(t, edir, naMode)
+ upmergeCoverData(t, rdir, naMode)
+}
+
+func testEmitToWriterNonAtomic(t *testing.T, harnessPath string, naMode string, dir string) {
+ tp := "emitToWriter"
+ tag := "nonatomw"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+ output, err := runHarness(t, harnessPath, tp,
+ true, rdir, edir)
+
+ // We expect an error here.
+ if err == nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp %s': did not get expected error", tp)
+ }
+
+ got := strings.TrimSpace(string(output))
+ want := "WriteCounters invoked for program built"
+ if !strings.Contains(got, want) {
+ t.Errorf("running 'harness -tp %s': got:\n%s\nwant: %s",
+ tp, got, want)
+ }
+
+ upmergeCoverData(t, edir, naMode)
+ upmergeCoverData(t, rdir, naMode)
+}
+
+func testEmitWithCounterClearNonAtomic(t *testing.T, harnessPath string, naMode string, dir string) {
+ tp := "emitWithCounterClear"
+ tag := "cclear"
+ rdir, edir := mktestdirs(t, tag, tp, dir)
+ output, err := runHarness(t, harnessPath, tp,
+ true, rdir, edir)
+
+ // We expect an error here.
+ if err == nil {
+ t.Logf("%s", output)
+ t.Fatalf("running 'harness -tp %s' nonatomic: did not get expected error", tp)
+ }
+
+ got := strings.TrimSpace(string(output))
+ want := "ClearCounters invoked for program built"
+ if !strings.Contains(got, want) {
+ t.Errorf("running 'harness -tp %s': got:\n%s\nwant: %s",
+ tp, got, want)
+ }
+
+ upmergeCoverData(t, edir, naMode)
+ upmergeCoverData(t, rdir, naMode)
+}
+
+func TestApisOnNocoverBinary(t *testing.T) {
+ if testing.Short() {
+ t.Skipf("skipping test: too long for short mode")
+ }
+ testenv.MustHaveGoBuild(t)
+ dir := t.TempDir()
+
+ // Build harness with no -cover.
+ bdir := mkdir(t, filepath.Join(dir, "nocover"))
+ edir := mkdir(t, filepath.Join(dir, "emitDirNo"))
+ harnessPath := buildHarness(t, bdir, nil)
+ output, err := runHarness(t, harnessPath, "emitToDir", false, edir, edir)
+ if err == nil {
+ t.Fatalf("expected error on TestApisOnNocoverBinary harness run")
+ }
+ const want = "not built with -cover"
+ if !strings.Contains(output, want) {
+ t.Errorf("error output does not contain %q: %s", want, output)
+ }
+}
+
+func TestIssue56006EmitDataRaceCoverRunningGoroutine(t *testing.T) {
+ if testing.Short() {
+ t.Skipf("skipping test: too long for short mode")
+ }
+ if !goexperiment.CoverageRedesign {
+ t.Skipf("skipping new coverage tests (experiment not enabled)")
+ }
+
+ // This test requires "go test -race -cover", meaning that we need
+ // go build, go run, and "-race" support.
+ testenv.MustHaveGoRun(t)
+ if !platform.RaceDetectorSupported(runtime.GOOS, runtime.GOARCH) ||
+ !testenv.HasCGO() {
+ t.Skip("skipped due to lack of race detector support / CGO")
+ }
+
+ // This will run a program with -cover and -race where we have a
+ // goroutine still running (and updating counters) at the point where
+ // the test runtime is trying to write out counter data.
+ cmd := exec.Command(testenv.GoToolPath(t), "test", "-cover", "-race")
+ cmd.Dir = filepath.Join("testdata", "issue56006")
+ b, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("go test -cover -race failed: %v", err)
+ }
+
+ // Don't want to see any data races in output.
+ avoid := []string{"DATA RACE"}
+ for _, no := range avoid {
+ if strings.Contains(string(b), no) {
+ t.Logf("%s\n", string(b))
+ t.Fatalf("found %s in test output, not permitted", no)
+ }
+ }
+}
+
+func TestIssue59563TruncatedCoverPkgAll(t *testing.T) {
+ if testing.Short() {
+ t.Skipf("skipping test: too long for short mode")
+ }
+ testenv.MustHaveGoRun(t)
+
+ tmpdir := t.TempDir()
+ ppath := filepath.Join(tmpdir, "foo.cov")
+
+ cmd := exec.Command(testenv.GoToolPath(t), "test", "-coverpkg=all", "-coverprofile="+ppath)
+ cmd.Dir = filepath.Join("testdata", "issue59563")
+ b, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("go test -cover failed: %v", err)
+ }
+
+ cmd = exec.Command(testenv.GoToolPath(t), "tool", "cover", "-func="+ppath)
+ b, err = cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("go tool cover -func failed: %v", err)
+ }
+
+ lines := strings.Split(string(b), "\n")
+ nfound := 0
+ bad := false
+ for _, line := range lines {
+ f := strings.Fields(line)
+ if len(f) == 0 {
+ continue
+ }
+ // We're only interested in the specific function "large" for
+ // the testcase being built. See the #59563 for details on why
+ // size matters.
+ if !(strings.HasPrefix(f[0], "runtime/coverage/testdata/issue59563/repro.go") && strings.Contains(line, "large")) {
+ continue
+ }
+ nfound++
+ want := "100.0%"
+ if f[len(f)-1] != want {
+ t.Errorf("wanted %s got: %q\n", want, line)
+ bad = true
+ }
+ }
+ if nfound != 1 {
+ t.Errorf("wanted 1 found, got %d\n", nfound)
+ bad = true
+ }
+ if bad {
+ t.Logf("func output:\n%s\n", string(b))
+ }
+}
diff --git a/src/runtime/coverage/hooks.go b/src/runtime/coverage/hooks.go
new file mode 100644
index 0000000..a9fbf9d
--- /dev/null
+++ b/src/runtime/coverage/hooks.go
@@ -0,0 +1,42 @@
+// 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 coverage
+
+import _ "unsafe"
+
+// initHook is invoked from the main package "init" routine in
+// programs built with "-cover". This function is intended to be
+// called only by the compiler.
+//
+// If 'istest' is false, it indicates we're building a regular program
+// ("go build -cover ..."), in which case we immediately try to write
+// out the meta-data file, and register emitCounterData as an exit
+// hook.
+//
+// If 'istest' is true (indicating that the program in question is a
+// Go test binary), then we tentatively queue up both emitMetaData and
+// emitCounterData as exit hooks. In the normal case (e.g. regular "go
+// test -cover" run) the testmain.go boilerplate will run at the end
+// of the test, write out the coverage percentage, and then invoke
+// markProfileEmitted() to indicate that no more work needs to be
+// done. If however that call is never made, this is a sign that the
+// test binary is being used as a replacement binary for the tool
+// being tested, hence we do want to run exit hooks when the program
+// terminates.
+func initHook(istest bool) {
+ // Note: hooks are run in reverse registration order, so
+ // register the counter data hook before the meta-data hook
+ // (in the case where two hooks are needed).
+ runOnNonZeroExit := true
+ runtime_addExitHook(emitCounterData, runOnNonZeroExit)
+ if istest {
+ runtime_addExitHook(emitMetaData, runOnNonZeroExit)
+ } else {
+ emitMetaData()
+ }
+}
+
+//go:linkname runtime_addExitHook runtime.addExitHook
+func runtime_addExitHook(f func(), runOnNonZeroExit bool)
diff --git a/src/runtime/coverage/testdata/harness.go b/src/runtime/coverage/testdata/harness.go
new file mode 100644
index 0000000..5c87e4c
--- /dev/null
+++ b/src/runtime/coverage/testdata/harness.go
@@ -0,0 +1,259 @@
+// 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()
+}
diff --git a/src/runtime/coverage/testdata/issue56006/repro.go b/src/runtime/coverage/testdata/issue56006/repro.go
new file mode 100644
index 0000000..60a4925
--- /dev/null
+++ b/src/runtime/coverage/testdata/issue56006/repro.go
@@ -0,0 +1,26 @@
+package main
+
+//go:noinline
+func blah(x int) int {
+ if x != 0 {
+ return x + 42
+ }
+ return x - 42
+}
+
+func main() {
+ go infloop()
+ println(blah(1) + blah(0))
+}
+
+var G int
+
+func infloop() {
+ for {
+ G += blah(1)
+ G += blah(0)
+ if G > 10000 {
+ G = 0
+ }
+ }
+}
diff --git a/src/runtime/coverage/testdata/issue56006/repro_test.go b/src/runtime/coverage/testdata/issue56006/repro_test.go
new file mode 100644
index 0000000..674d819
--- /dev/null
+++ b/src/runtime/coverage/testdata/issue56006/repro_test.go
@@ -0,0 +1,8 @@
+package main
+
+import "testing"
+
+func TestSomething(t *testing.T) {
+ go infloop()
+ println(blah(1) + blah(0))
+}
diff --git a/src/runtime/coverage/testdata/issue59563/repro.go b/src/runtime/coverage/testdata/issue59563/repro.go
new file mode 100644
index 0000000..d054567
--- /dev/null
+++ b/src/runtime/coverage/testdata/issue59563/repro.go
@@ -0,0 +1,823 @@
+// Copyright 2023 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 repro
+
+import (
+ "fmt"
+ "net/http"
+)
+
+func small() {
+ go func() {
+ fmt.Println(http.ListenAndServe("localhost:7070", nil))
+ }()
+}
+
+func large(x int) int {
+ if x == 0 {
+ x += 0
+ } else if x == 1 {
+ x += 1
+ } else if x == 2 {
+ x += 2
+ } else if x == 3 {
+ x += 3
+ } else if x == 4 {
+ x += 4
+ } else if x == 5 {
+ x += 5
+ } else if x == 6 {
+ x += 6
+ } else if x == 7 {
+ x += 7
+ } else if x == 8 {
+ x += 8
+ } else if x == 9 {
+ x += 9
+ } else if x == 10 {
+ x += 10
+ } else if x == 11 {
+ x += 11
+ } else if x == 12 {
+ x += 12
+ } else if x == 13 {
+ x += 13
+ } else if x == 14 {
+ x += 14
+ } else if x == 15 {
+ x += 15
+ } else if x == 16 {
+ x += 16
+ } else if x == 17 {
+ x += 17
+ } else if x == 18 {
+ x += 18
+ } else if x == 19 {
+ x += 19
+ } else if x == 20 {
+ x += 20
+ } else if x == 21 {
+ x += 21
+ } else if x == 22 {
+ x += 22
+ } else if x == 23 {
+ x += 23
+ } else if x == 24 {
+ x += 24
+ } else if x == 25 {
+ x += 25
+ } else if x == 26 {
+ x += 26
+ } else if x == 27 {
+ x += 27
+ } else if x == 28 {
+ x += 28
+ } else if x == 29 {
+ x += 29
+ } else if x == 30 {
+ x += 30
+ } else if x == 31 {
+ x += 31
+ } else if x == 32 {
+ x += 32
+ } else if x == 33 {
+ x += 33
+ } else if x == 34 {
+ x += 34
+ } else if x == 35 {
+ x += 35
+ } else if x == 36 {
+ x += 36
+ } else if x == 37 {
+ x += 37
+ } else if x == 38 {
+ x += 38
+ } else if x == 39 {
+ x += 39
+ } else if x == 40 {
+ x += 40
+ } else if x == 41 {
+ x += 41
+ } else if x == 42 {
+ x += 42
+ } else if x == 43 {
+ x += 43
+ } else if x == 44 {
+ x += 44
+ } else if x == 45 {
+ x += 45
+ } else if x == 46 {
+ x += 46
+ } else if x == 47 {
+ x += 47
+ } else if x == 48 {
+ x += 48
+ } else if x == 49 {
+ x += 49
+ } else if x == 50 {
+ x += 50
+ } else if x == 51 {
+ x += 51
+ } else if x == 52 {
+ x += 52
+ } else if x == 53 {
+ x += 53
+ } else if x == 54 {
+ x += 54
+ } else if x == 55 {
+ x += 55
+ } else if x == 56 {
+ x += 56
+ } else if x == 57 {
+ x += 57
+ } else if x == 58 {
+ x += 58
+ } else if x == 59 {
+ x += 59
+ } else if x == 60 {
+ x += 60
+ } else if x == 61 {
+ x += 61
+ } else if x == 62 {
+ x += 62
+ } else if x == 63 {
+ x += 63
+ } else if x == 64 {
+ x += 64
+ } else if x == 65 {
+ x += 65
+ } else if x == 66 {
+ x += 66
+ } else if x == 67 {
+ x += 67
+ } else if x == 68 {
+ x += 68
+ } else if x == 69 {
+ x += 69
+ } else if x == 70 {
+ x += 70
+ } else if x == 71 {
+ x += 71
+ } else if x == 72 {
+ x += 72
+ } else if x == 73 {
+ x += 73
+ } else if x == 74 {
+ x += 74
+ } else if x == 75 {
+ x += 75
+ } else if x == 76 {
+ x += 76
+ } else if x == 77 {
+ x += 77
+ } else if x == 78 {
+ x += 78
+ } else if x == 79 {
+ x += 79
+ } else if x == 80 {
+ x += 80
+ } else if x == 81 {
+ x += 81
+ } else if x == 82 {
+ x += 82
+ } else if x == 83 {
+ x += 83
+ } else if x == 84 {
+ x += 84
+ } else if x == 85 {
+ x += 85
+ } else if x == 86 {
+ x += 86
+ } else if x == 87 {
+ x += 87
+ } else if x == 88 {
+ x += 88
+ } else if x == 89 {
+ x += 89
+ } else if x == 90 {
+ x += 90
+ } else if x == 91 {
+ x += 91
+ } else if x == 92 {
+ x += 92
+ } else if x == 93 {
+ x += 93
+ } else if x == 94 {
+ x += 94
+ } else if x == 95 {
+ x += 95
+ } else if x == 96 {
+ x += 96
+ } else if x == 97 {
+ x += 97
+ } else if x == 98 {
+ x += 98
+ } else if x == 99 {
+ x += 99
+ } else if x == 100 {
+ x += 100
+ } else if x == 101 {
+ x += 101
+ } else if x == 102 {
+ x += 102
+ } else if x == 103 {
+ x += 103
+ } else if x == 104 {
+ x += 104
+ } else if x == 105 {
+ x += 105
+ } else if x == 106 {
+ x += 106
+ } else if x == 107 {
+ x += 107
+ } else if x == 108 {
+ x += 108
+ } else if x == 109 {
+ x += 109
+ } else if x == 110 {
+ x += 110
+ } else if x == 111 {
+ x += 111
+ } else if x == 112 {
+ x += 112
+ } else if x == 113 {
+ x += 113
+ } else if x == 114 {
+ x += 114
+ } else if x == 115 {
+ x += 115
+ } else if x == 116 {
+ x += 116
+ } else if x == 117 {
+ x += 117
+ } else if x == 118 {
+ x += 118
+ } else if x == 119 {
+ x += 119
+ } else if x == 120 {
+ x += 120
+ } else if x == 121 {
+ x += 121
+ } else if x == 122 {
+ x += 122
+ } else if x == 123 {
+ x += 123
+ } else if x == 124 {
+ x += 124
+ } else if x == 125 {
+ x += 125
+ } else if x == 126 {
+ x += 126
+ } else if x == 127 {
+ x += 127
+ } else if x == 128 {
+ x += 128
+ } else if x == 129 {
+ x += 129
+ } else if x == 130 {
+ x += 130
+ } else if x == 131 {
+ x += 131
+ } else if x == 132 {
+ x += 132
+ } else if x == 133 {
+ x += 133
+ } else if x == 134 {
+ x += 134
+ } else if x == 135 {
+ x += 135
+ } else if x == 136 {
+ x += 136
+ } else if x == 137 {
+ x += 137
+ } else if x == 138 {
+ x += 138
+ } else if x == 139 {
+ x += 139
+ } else if x == 140 {
+ x += 140
+ } else if x == 141 {
+ x += 141
+ } else if x == 142 {
+ x += 142
+ } else if x == 143 {
+ x += 143
+ } else if x == 144 {
+ x += 144
+ } else if x == 145 {
+ x += 145
+ } else if x == 146 {
+ x += 146
+ } else if x == 147 {
+ x += 147
+ } else if x == 148 {
+ x += 148
+ } else if x == 149 {
+ x += 149
+ } else if x == 150 {
+ x += 150
+ } else if x == 151 {
+ x += 151
+ } else if x == 152 {
+ x += 152
+ } else if x == 153 {
+ x += 153
+ } else if x == 154 {
+ x += 154
+ } else if x == 155 {
+ x += 155
+ } else if x == 156 {
+ x += 156
+ } else if x == 157 {
+ x += 157
+ } else if x == 158 {
+ x += 158
+ } else if x == 159 {
+ x += 159
+ } else if x == 160 {
+ x += 160
+ } else if x == 161 {
+ x += 161
+ } else if x == 162 {
+ x += 162
+ } else if x == 163 {
+ x += 163
+ } else if x == 164 {
+ x += 164
+ } else if x == 165 {
+ x += 165
+ } else if x == 166 {
+ x += 166
+ } else if x == 167 {
+ x += 167
+ } else if x == 168 {
+ x += 168
+ } else if x == 169 {
+ x += 169
+ } else if x == 170 {
+ x += 170
+ } else if x == 171 {
+ x += 171
+ } else if x == 172 {
+ x += 172
+ } else if x == 173 {
+ x += 173
+ } else if x == 174 {
+ x += 174
+ } else if x == 175 {
+ x += 175
+ } else if x == 176 {
+ x += 176
+ } else if x == 177 {
+ x += 177
+ } else if x == 178 {
+ x += 178
+ } else if x == 179 {
+ x += 179
+ } else if x == 180 {
+ x += 180
+ } else if x == 181 {
+ x += 181
+ } else if x == 182 {
+ x += 182
+ } else if x == 183 {
+ x += 183
+ } else if x == 184 {
+ x += 184
+ } else if x == 185 {
+ x += 185
+ } else if x == 186 {
+ x += 186
+ } else if x == 187 {
+ x += 187
+ } else if x == 188 {
+ x += 188
+ } else if x == 189 {
+ x += 189
+ } else if x == 190 {
+ x += 190
+ } else if x == 191 {
+ x += 191
+ } else if x == 192 {
+ x += 192
+ } else if x == 193 {
+ x += 193
+ } else if x == 194 {
+ x += 194
+ } else if x == 195 {
+ x += 195
+ } else if x == 196 {
+ x += 196
+ } else if x == 197 {
+ x += 197
+ } else if x == 198 {
+ x += 198
+ } else if x == 199 {
+ x += 199
+ } else if x == 200 {
+ x += 200
+ } else if x == 201 {
+ x += 201
+ } else if x == 202 {
+ x += 202
+ } else if x == 203 {
+ x += 203
+ } else if x == 204 {
+ x += 204
+ } else if x == 205 {
+ x += 205
+ } else if x == 206 {
+ x += 206
+ } else if x == 207 {
+ x += 207
+ } else if x == 208 {
+ x += 208
+ } else if x == 209 {
+ x += 209
+ } else if x == 210 {
+ x += 210
+ } else if x == 211 {
+ x += 211
+ } else if x == 212 {
+ x += 212
+ } else if x == 213 {
+ x += 213
+ } else if x == 214 {
+ x += 214
+ } else if x == 215 {
+ x += 215
+ } else if x == 216 {
+ x += 216
+ } else if x == 217 {
+ x += 217
+ } else if x == 218 {
+ x += 218
+ } else if x == 219 {
+ x += 219
+ } else if x == 220 {
+ x += 220
+ } else if x == 221 {
+ x += 221
+ } else if x == 222 {
+ x += 222
+ } else if x == 223 {
+ x += 223
+ } else if x == 224 {
+ x += 224
+ } else if x == 225 {
+ x += 225
+ } else if x == 226 {
+ x += 226
+ } else if x == 227 {
+ x += 227
+ } else if x == 228 {
+ x += 228
+ } else if x == 229 {
+ x += 229
+ } else if x == 230 {
+ x += 230
+ } else if x == 231 {
+ x += 231
+ } else if x == 232 {
+ x += 232
+ } else if x == 233 {
+ x += 233
+ } else if x == 234 {
+ x += 234
+ } else if x == 235 {
+ x += 235
+ } else if x == 236 {
+ x += 236
+ } else if x == 237 {
+ x += 237
+ } else if x == 238 {
+ x += 238
+ } else if x == 239 {
+ x += 239
+ } else if x == 240 {
+ x += 240
+ } else if x == 241 {
+ x += 241
+ } else if x == 242 {
+ x += 242
+ } else if x == 243 {
+ x += 243
+ } else if x == 244 {
+ x += 244
+ } else if x == 245 {
+ x += 245
+ } else if x == 246 {
+ x += 246
+ } else if x == 247 {
+ x += 247
+ } else if x == 248 {
+ x += 248
+ } else if x == 249 {
+ x += 249
+ } else if x == 250 {
+ x += 250
+ } else if x == 251 {
+ x += 251
+ } else if x == 252 {
+ x += 252
+ } else if x == 253 {
+ x += 253
+ } else if x == 254 {
+ x += 254
+ } else if x == 255 {
+ x += 255
+ } else if x == 256 {
+ x += 256
+ } else if x == 257 {
+ x += 257
+ } else if x == 258 {
+ x += 258
+ } else if x == 259 {
+ x += 259
+ } else if x == 260 {
+ x += 260
+ } else if x == 261 {
+ x += 261
+ } else if x == 262 {
+ x += 262
+ } else if x == 263 {
+ x += 263
+ } else if x == 264 {
+ x += 264
+ } else if x == 265 {
+ x += 265
+ } else if x == 266 {
+ x += 266
+ } else if x == 267 {
+ x += 267
+ } else if x == 268 {
+ x += 268
+ } else if x == 269 {
+ x += 269
+ } else if x == 270 {
+ x += 270
+ } else if x == 271 {
+ x += 271
+ } else if x == 272 {
+ x += 272
+ } else if x == 273 {
+ x += 273
+ } else if x == 274 {
+ x += 274
+ } else if x == 275 {
+ x += 275
+ } else if x == 276 {
+ x += 276
+ } else if x == 277 {
+ x += 277
+ } else if x == 278 {
+ x += 278
+ } else if x == 279 {
+ x += 279
+ } else if x == 280 {
+ x += 280
+ } else if x == 281 {
+ x += 281
+ } else if x == 282 {
+ x += 282
+ } else if x == 283 {
+ x += 283
+ } else if x == 284 {
+ x += 284
+ } else if x == 285 {
+ x += 285
+ } else if x == 286 {
+ x += 286
+ } else if x == 287 {
+ x += 287
+ } else if x == 288 {
+ x += 288
+ } else if x == 289 {
+ x += 289
+ } else if x == 290 {
+ x += 290
+ } else if x == 291 {
+ x += 291
+ } else if x == 292 {
+ x += 292
+ } else if x == 293 {
+ x += 293
+ } else if x == 294 {
+ x += 294
+ } else if x == 295 {
+ x += 295
+ } else if x == 296 {
+ x += 296
+ } else if x == 297 {
+ x += 297
+ } else if x == 298 {
+ x += 298
+ } else if x == 299 {
+ x += 299
+ } else if x == 300 {
+ x += 300
+ } else if x == 301 {
+ x += 301
+ } else if x == 302 {
+ x += 302
+ } else if x == 303 {
+ x += 303
+ } else if x == 304 {
+ x += 304
+ } else if x == 305 {
+ x += 305
+ } else if x == 306 {
+ x += 306
+ } else if x == 307 {
+ x += 307
+ } else if x == 308 {
+ x += 308
+ } else if x == 309 {
+ x += 309
+ } else if x == 310 {
+ x += 310
+ } else if x == 311 {
+ x += 311
+ } else if x == 312 {
+ x += 312
+ } else if x == 313 {
+ x += 313
+ } else if x == 314 {
+ x += 314
+ } else if x == 315 {
+ x += 315
+ } else if x == 316 {
+ x += 316
+ } else if x == 317 {
+ x += 317
+ } else if x == 318 {
+ x += 318
+ } else if x == 319 {
+ x += 319
+ } else if x == 320 {
+ x += 320
+ } else if x == 321 {
+ x += 321
+ } else if x == 322 {
+ x += 322
+ } else if x == 323 {
+ x += 323
+ } else if x == 324 {
+ x += 324
+ } else if x == 325 {
+ x += 325
+ } else if x == 326 {
+ x += 326
+ } else if x == 327 {
+ x += 327
+ } else if x == 328 {
+ x += 328
+ } else if x == 329 {
+ x += 329
+ } else if x == 330 {
+ x += 330
+ } else if x == 331 {
+ x += 331
+ } else if x == 332 {
+ x += 332
+ } else if x == 333 {
+ x += 333
+ } else if x == 334 {
+ x += 334
+ } else if x == 335 {
+ x += 335
+ } else if x == 336 {
+ x += 336
+ } else if x == 337 {
+ x += 337
+ } else if x == 338 {
+ x += 338
+ } else if x == 339 {
+ x += 339
+ } else if x == 340 {
+ x += 340
+ } else if x == 341 {
+ x += 341
+ } else if x == 342 {
+ x += 342
+ } else if x == 343 {
+ x += 343
+ } else if x == 344 {
+ x += 344
+ } else if x == 345 {
+ x += 345
+ } else if x == 346 {
+ x += 346
+ } else if x == 347 {
+ x += 347
+ } else if x == 348 {
+ x += 348
+ } else if x == 349 {
+ x += 349
+ } else if x == 350 {
+ x += 350
+ } else if x == 351 {
+ x += 351
+ } else if x == 352 {
+ x += 352
+ } else if x == 353 {
+ x += 353
+ } else if x == 354 {
+ x += 354
+ } else if x == 355 {
+ x += 355
+ } else if x == 356 {
+ x += 356
+ } else if x == 357 {
+ x += 357
+ } else if x == 358 {
+ x += 358
+ } else if x == 359 {
+ x += 359
+ } else if x == 360 {
+ x += 360
+ } else if x == 361 {
+ x += 361
+ } else if x == 362 {
+ x += 362
+ } else if x == 363 {
+ x += 363
+ } else if x == 364 {
+ x += 364
+ } else if x == 365 {
+ x += 365
+ } else if x == 366 {
+ x += 366
+ } else if x == 367 {
+ x += 367
+ } else if x == 368 {
+ x += 368
+ } else if x == 369 {
+ x += 369
+ } else if x == 370 {
+ x += 370
+ } else if x == 371 {
+ x += 371
+ } else if x == 372 {
+ x += 372
+ } else if x == 373 {
+ x += 373
+ } else if x == 374 {
+ x += 374
+ } else if x == 375 {
+ x += 375
+ } else if x == 376 {
+ x += 376
+ } else if x == 377 {
+ x += 377
+ } else if x == 378 {
+ x += 378
+ } else if x == 379 {
+ x += 379
+ } else if x == 380 {
+ x += 380
+ } else if x == 381 {
+ x += 381
+ } else if x == 382 {
+ x += 382
+ } else if x == 383 {
+ x += 383
+ } else if x == 384 {
+ x += 384
+ } else if x == 385 {
+ x += 385
+ } else if x == 386 {
+ x += 386
+ } else if x == 387 {
+ x += 387
+ } else if x == 388 {
+ x += 388
+ } else if x == 389 {
+ x += 389
+ } else if x == 390 {
+ x += 390
+ } else if x == 391 {
+ x += 391
+ } else if x == 392 {
+ x += 392
+ } else if x == 393 {
+ x += 393
+ } else if x == 394 {
+ x += 394
+ } else if x == 395 {
+ x += 395
+ } else if x == 396 {
+ x += 396
+ } else if x == 397 {
+ x += 397
+ } else if x == 398 {
+ x += 398
+ } else if x == 399 {
+ x += 399
+ } else if x == 400 {
+ x += 400
+ }
+ return x * x
+}
diff --git a/src/runtime/coverage/testdata/issue59563/repro_test.go b/src/runtime/coverage/testdata/issue59563/repro_test.go
new file mode 100644
index 0000000..15c8e01
--- /dev/null
+++ b/src/runtime/coverage/testdata/issue59563/repro_test.go
@@ -0,0 +1,14 @@
+// Copyright 2023 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 repro
+
+import "testing"
+
+func TestSomething(t *testing.T) {
+ small()
+ for i := 0; i < 1001; i++ {
+ large(i)
+ }
+}
diff --git a/src/runtime/coverage/testsupport.go b/src/runtime/coverage/testsupport.go
new file mode 100644
index 0000000..f169580
--- /dev/null
+++ b/src/runtime/coverage/testsupport.go
@@ -0,0 +1,323 @@
+// 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 coverage
+
+import (
+ "encoding/json"
+ "fmt"
+ "internal/coverage"
+ "internal/coverage/calloc"
+ "internal/coverage/cformat"
+ "internal/coverage/cmerge"
+ "internal/coverage/decodecounter"
+ "internal/coverage/decodemeta"
+ "internal/coverage/pods"
+ "io"
+ "os"
+ "path/filepath"
+ "runtime/internal/atomic"
+ "strings"
+ "unsafe"
+)
+
+// processCoverTestDir is called (via a linknamed reference) from
+// testmain code when "go test -cover" is in effect. It is not
+// intended to be used other than internally by the Go command's
+// generated code.
+func processCoverTestDir(dir string, cfile string, cm string, cpkg string) error {
+ return processCoverTestDirInternal(dir, cfile, cm, cpkg, os.Stdout)
+}
+
+// processCoverTestDirInternal is an io.Writer version of processCoverTestDir,
+// exposed for unit testing.
+func processCoverTestDirInternal(dir string, cfile string, cm string, cpkg string, w io.Writer) error {
+ cmode := coverage.ParseCounterMode(cm)
+ if cmode == coverage.CtrModeInvalid {
+ return fmt.Errorf("invalid counter mode %q", cm)
+ }
+
+ // Emit meta-data and counter data.
+ ml := getCovMetaList()
+ if len(ml) == 0 {
+ // This corresponds to the case where we have a package that
+ // contains test code but no functions (which is fine). In this
+ // case there is no need to emit anything.
+ } else {
+ if err := emitMetaDataToDirectory(dir, ml); err != nil {
+ return err
+ }
+ if err := emitCounterDataToDirectory(dir); err != nil {
+ return err
+ }
+ }
+
+ // Collect pods from test run. For the majority of cases we would
+ // expect to see a single pod here, but allow for multiple pods in
+ // case the test harness is doing extra work to collect data files
+ // from builds that it kicks off as part of the testing.
+ podlist, err := pods.CollectPods([]string{dir}, false)
+ if err != nil {
+ return fmt.Errorf("reading from %s: %v", dir, err)
+ }
+
+ // Open text output file if appropriate.
+ var tf *os.File
+ var tfClosed bool
+ if cfile != "" {
+ var err error
+ tf, err = os.Create(cfile)
+ if err != nil {
+ return fmt.Errorf("internal error: opening coverage data output file %q: %v", cfile, err)
+ }
+ defer func() {
+ if !tfClosed {
+ tfClosed = true
+ tf.Close()
+ }
+ }()
+ }
+
+ // Read/process the pods.
+ ts := &tstate{
+ cm: &cmerge.Merger{},
+ cf: cformat.NewFormatter(cmode),
+ cmode: cmode,
+ }
+ // Generate the expected hash string based on the final meta-data
+ // hash for this test, then look only for pods that refer to that
+ // hash (just in case there are multiple instrumented executables
+ // in play). See issue #57924 for more on this.
+ hashstring := fmt.Sprintf("%x", finalHash)
+ importpaths := make(map[string]struct{})
+ for _, p := range podlist {
+ if !strings.Contains(p.MetaFile, hashstring) {
+ continue
+ }
+ if err := ts.processPod(p, importpaths); err != nil {
+ return err
+ }
+ }
+
+ metafilespath := filepath.Join(dir, coverage.MetaFilesFileName)
+ if _, err := os.Stat(metafilespath); err == nil {
+ if err := ts.readAuxMetaFiles(metafilespath, importpaths); err != nil {
+ return err
+ }
+ }
+
+ // Emit percent.
+ if err := ts.cf.EmitPercent(w, cpkg, true, true); err != nil {
+ return err
+ }
+
+ // Emit text output.
+ if tf != nil {
+ if err := ts.cf.EmitTextual(tf); err != nil {
+ return err
+ }
+ tfClosed = true
+ if err := tf.Close(); err != nil {
+ return fmt.Errorf("closing %s: %v", cfile, err)
+ }
+ }
+
+ return nil
+}
+
+type tstate struct {
+ calloc.BatchCounterAlloc
+ cm *cmerge.Merger
+ cf *cformat.Formatter
+ cmode coverage.CounterMode
+}
+
+// processPod reads coverage counter data for a specific pod.
+func (ts *tstate) processPod(p pods.Pod, importpaths map[string]struct{}) error {
+ // Open meta-data file
+ f, err := os.Open(p.MetaFile)
+ if err != nil {
+ return fmt.Errorf("unable to open meta-data file %s: %v", p.MetaFile, err)
+ }
+ defer func() {
+ f.Close()
+ }()
+ var mfr *decodemeta.CoverageMetaFileReader
+ mfr, err = decodemeta.NewCoverageMetaFileReader(f, nil)
+ if err != nil {
+ return fmt.Errorf("error reading meta-data file %s: %v", p.MetaFile, err)
+ }
+ newmode := mfr.CounterMode()
+ if newmode != ts.cmode {
+ return fmt.Errorf("internal error: counter mode clash: %q from test harness, %q from data file %s", ts.cmode.String(), newmode.String(), p.MetaFile)
+ }
+ newgran := mfr.CounterGranularity()
+ if err := ts.cm.SetModeAndGranularity(p.MetaFile, cmode, newgran); err != nil {
+ return err
+ }
+
+ // A map to store counter data, indexed by pkgid/fnid tuple.
+ pmm := make(map[pkfunc][]uint32)
+
+ // Helper to read a single counter data file.
+ readcdf := func(cdf string) error {
+ cf, err := os.Open(cdf)
+ if err != nil {
+ return fmt.Errorf("opening counter data file %s: %s", cdf, err)
+ }
+ defer cf.Close()
+ var cdr *decodecounter.CounterDataReader
+ cdr, err = decodecounter.NewCounterDataReader(cdf, cf)
+ if err != nil {
+ return fmt.Errorf("reading counter data file %s: %s", cdf, err)
+ }
+ var data decodecounter.FuncPayload
+ for {
+ ok, err := cdr.NextFunc(&data)
+ if err != nil {
+ return fmt.Errorf("reading counter data file %s: %v", cdf, err)
+ }
+ if !ok {
+ break
+ }
+
+ // NB: sanity check on pkg and func IDs?
+ key := pkfunc{pk: data.PkgIdx, fcn: data.FuncIdx}
+ if prev, found := pmm[key]; found {
+ // Note: no overflow reporting here.
+ if err, _ := ts.cm.MergeCounters(data.Counters, prev); err != nil {
+ return fmt.Errorf("processing counter data file %s: %v", cdf, err)
+ }
+ }
+ c := ts.AllocateCounters(len(data.Counters))
+ copy(c, data.Counters)
+ pmm[key] = c
+ }
+ return nil
+ }
+
+ // Read counter data files.
+ for _, cdf := range p.CounterDataFiles {
+ if err := readcdf(cdf); err != nil {
+ return err
+ }
+ }
+
+ // Visit meta-data file.
+ np := uint32(mfr.NumPackages())
+ payload := []byte{}
+ for pkIdx := uint32(0); pkIdx < np; pkIdx++ {
+ var pd *decodemeta.CoverageMetaDataDecoder
+ pd, payload, err = mfr.GetPackageDecoder(pkIdx, payload)
+ if err != nil {
+ return fmt.Errorf("reading pkg %d from meta-file %s: %s", pkIdx, p.MetaFile, err)
+ }
+ ts.cf.SetPackage(pd.PackagePath())
+ importpaths[pd.PackagePath()] = struct{}{}
+ var fd coverage.FuncDesc
+ nf := pd.NumFuncs()
+ for fnIdx := uint32(0); fnIdx < nf; fnIdx++ {
+ if err := pd.ReadFunc(fnIdx, &fd); err != nil {
+ return fmt.Errorf("reading meta-data file %s: %v",
+ p.MetaFile, err)
+ }
+ key := pkfunc{pk: pkIdx, fcn: fnIdx}
+ counters, haveCounters := pmm[key]
+ for i := 0; i < len(fd.Units); i++ {
+ u := fd.Units[i]
+ // Skip units with non-zero parent (no way to represent
+ // these in the existing format).
+ if u.Parent != 0 {
+ continue
+ }
+ count := uint32(0)
+ if haveCounters {
+ count = counters[i]
+ }
+ ts.cf.AddUnit(fd.Srcfile, fd.Funcname, fd.Lit, u, count)
+ }
+ }
+ }
+ return nil
+}
+
+type pkfunc struct {
+ pk, fcn uint32
+}
+
+func (ts *tstate) readAuxMetaFiles(metafiles string, importpaths map[string]struct{}) error {
+ // Unmarshall the information on available aux metafiles into
+ // a MetaFileCollection struct.
+ var mfc coverage.MetaFileCollection
+ data, err := os.ReadFile(metafiles)
+ if err != nil {
+ return fmt.Errorf("error reading auxmetafiles file %q: %v", metafiles, err)
+ }
+ if err := json.Unmarshal(data, &mfc); err != nil {
+ return fmt.Errorf("error reading auxmetafiles file %q: %v", metafiles, err)
+ }
+
+ // Walk through each available aux meta-file. If we've already
+ // seen the package path in question during the walk of the
+ // "regular" meta-data file, then we can skip the package,
+ // otherwise construct a dummy pod with the single meta-data file
+ // (no counters) and invoke processPod on it.
+ for i := range mfc.ImportPaths {
+ p := mfc.ImportPaths[i]
+ if _, ok := importpaths[p]; ok {
+ continue
+ }
+ var pod pods.Pod
+ pod.MetaFile = mfc.MetaFileFragments[i]
+ if err := ts.processPod(pod, importpaths); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// snapshot returns a snapshot of coverage percentage at a moment of
+// time within a running test, so as to support the testing.Coverage()
+// function. This version doesn't examine coverage meta-data, so the
+// result it returns will be less accurate (more "slop") due to the
+// fact that we don't look at the meta data to see how many statements
+// are associated with each counter.
+func snapshot() float64 {
+ cl := getCovCounterList()
+ if len(cl) == 0 {
+ // no work to do here.
+ return 0.0
+ }
+
+ tot := uint64(0)
+ totExec := uint64(0)
+ for _, c := range cl {
+ sd := unsafe.Slice((*atomic.Uint32)(unsafe.Pointer(c.Counters)), c.Len)
+ tot += uint64(len(sd))
+ for i := 0; i < len(sd); i++ {
+ // Skip ahead until the next non-zero value.
+ if sd[i].Load() == 0 {
+ continue
+ }
+ // We found a function that was executed.
+ nCtrs := sd[i+coverage.NumCtrsOffset].Load()
+ cst := i + coverage.FirstCtrOffset
+
+ if cst+int(nCtrs) > len(sd) {
+ break
+ }
+ counters := sd[cst : cst+int(nCtrs)]
+ for i := range counters {
+ if counters[i].Load() != 0 {
+ totExec++
+ }
+ }
+ i += coverage.FirstCtrOffset + int(nCtrs) - 1
+ }
+ }
+ if tot == 0 {
+ return 0.0
+ }
+ return float64(totExec) / float64(tot)
+}
diff --git a/src/runtime/coverage/ts_test.go b/src/runtime/coverage/ts_test.go
new file mode 100644
index 0000000..b4c6e97
--- /dev/null
+++ b/src/runtime/coverage/ts_test.go
@@ -0,0 +1,207 @@
+// 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 coverage
+
+import (
+ "encoding/json"
+ "internal/coverage"
+ "internal/goexperiment"
+ "internal/testenv"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "testing"
+ _ "unsafe"
+)
+
+//go:linkname testing_testGoCoverDir testing.testGoCoverDir
+func testing_testGoCoverDir() string
+
+func testGoCoverDir(t *testing.T) string {
+ tgcd := testing_testGoCoverDir()
+ if tgcd != "" {
+ return tgcd
+ }
+ return t.TempDir()
+}
+
+// TestTestSupport does a basic verification of the functionality in
+// runtime/coverage.processCoverTestDir (doing this here as opposed to
+// relying on other test paths will provide a better signal when
+// running "go test -cover" for this package).
+func TestTestSupport(t *testing.T) {
+ if !goexperiment.CoverageRedesign {
+ return
+ }
+ if testing.CoverMode() == "" {
+ return
+ }
+ tgcd := testGoCoverDir(t)
+ t.Logf("testing.testGoCoverDir() returns %s mode=%s\n",
+ tgcd, testing.CoverMode())
+
+ textfile := filepath.Join(t.TempDir(), "file.txt")
+ var sb strings.Builder
+ err := processCoverTestDirInternal(tgcd, textfile,
+ testing.CoverMode(), "", &sb)
+ if err != nil {
+ t.Fatalf("bad: %v", err)
+ }
+
+ // Check for existence of text file.
+ if inf, err := os.Open(textfile); err != nil {
+ t.Fatalf("problems opening text file %s: %v", textfile, err)
+ } else {
+ inf.Close()
+ }
+
+ // Check for percent output with expected tokens.
+ strout := sb.String()
+ want := "of statements"
+ if !strings.Contains(strout, want) {
+ t.Logf("output from run: %s\n", strout)
+ t.Fatalf("percent output missing token: %q", want)
+ }
+}
+
+var funcInvoked bool
+
+//go:noinline
+func thisFunctionOnlyCalledFromSnapshotTest(n int) int {
+ if funcInvoked {
+ panic("bad")
+ }
+ funcInvoked = true
+
+ // Contents here not especially important, just so long as we
+ // have some statements.
+ t := 0
+ for i := 0; i < n; i++ {
+ for j := 0; j < i; j++ {
+ t += i ^ j
+ }
+ }
+ return t
+}
+
+// Tests runtime/coverage.snapshot() directly. Note that if
+// coverage is not enabled, the hook is designed to just return
+// zero.
+func TestCoverageSnapshot(t *testing.T) {
+ C1 := snapshot()
+ thisFunctionOnlyCalledFromSnapshotTest(15)
+ C2 := snapshot()
+ cond := "C1 > C2"
+ val := C1 > C2
+ if testing.CoverMode() != "" {
+ cond = "C1 >= C2"
+ val = C1 >= C2
+ }
+ t.Logf("%f %f\n", C1, C2)
+ if val {
+ t.Errorf("erroneous snapshots, %s = true C1=%f C2=%f",
+ cond, C1, C2)
+ }
+}
+
+const hellogo = `
+package main
+
+func main() {
+ println("hello")
+}
+`
+
+// Returns a pair F,T where F is a meta-data file generated from
+// "hello.go" above, and T is a token to look for that should be
+// present in the coverage report from F.
+func genAuxMeta(t *testing.T, dstdir string) (string, string) {
+ // Do a GOCOVERDIR=<tmp> go run hello.go
+ src := filepath.Join(dstdir, "hello.go")
+ if err := os.WriteFile(src, []byte(hellogo), 0777); err != nil {
+ t.Fatalf("write failed: %v", err)
+ }
+ args := []string{"run", "-covermode=" + testing.CoverMode(), src}
+ cmd := exec.Command(testenv.GoToolPath(t), args...)
+ cmd.Env = updateGoCoverDir(os.Environ(), dstdir, true)
+ if b, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("go run failed (%v): %s", err, b)
+ }
+
+ // Pick out the generated meta-data file.
+ files, err := os.ReadDir(dstdir)
+ if err != nil {
+ t.Fatalf("reading %s: %v", dstdir, err)
+ }
+ for _, f := range files {
+ if strings.HasPrefix(f.Name(), "covmeta") {
+ return filepath.Join(dstdir, f.Name()), "hello.go:"
+ }
+ }
+ t.Fatalf("could not locate generated meta-data file")
+ return "", ""
+}
+
+func TestAuxMetaDataFiles(t *testing.T) {
+ if !goexperiment.CoverageRedesign {
+ return
+ }
+ if testing.CoverMode() == "" {
+ return
+ }
+ testenv.MustHaveGoRun(t)
+ tgcd := testGoCoverDir(t)
+ t.Logf("testing.testGoCoverDir() returns %s mode=%s\n",
+ tgcd, testing.CoverMode())
+
+ td := t.TempDir()
+
+ // Manufacture a new, separate meta-data file not related to this
+ // test. Contents are not important, just so long as the
+ // packages/paths are different.
+ othermetadir := filepath.Join(td, "othermeta")
+ if err := os.Mkdir(othermetadir, 0777); err != nil {
+ t.Fatalf("mkdir failed: %v", err)
+ }
+ mfile, token := genAuxMeta(t, othermetadir)
+
+ // Write a metafiles file.
+ metafiles := filepath.Join(tgcd, coverage.MetaFilesFileName)
+ mfc := coverage.MetaFileCollection{
+ ImportPaths: []string{"command-line-arguments"},
+ MetaFileFragments: []string{mfile},
+ }
+ jdata, err := json.Marshal(mfc)
+ if err != nil {
+ t.Fatalf("marshal MetaFileCollection: %v", err)
+ }
+ if err := os.WriteFile(metafiles, jdata, 0666); err != nil {
+ t.Fatalf("write failed: %v", err)
+ }
+
+ // Kick off guts of test.
+ var sb strings.Builder
+ textfile := filepath.Join(td, "file2.txt")
+ err = processCoverTestDirInternal(tgcd, textfile,
+ testing.CoverMode(), "", &sb)
+ if err != nil {
+ t.Fatalf("bad: %v", err)
+ }
+ if err = os.Remove(metafiles); err != nil {
+ t.Fatalf("removing metafiles file: %v", err)
+ }
+
+ // Look for the expected things in the coverage profile.
+ contents, err := os.ReadFile(textfile)
+ strc := string(contents)
+ if err != nil {
+ t.Fatalf("problems reading text file %s: %v", textfile, err)
+ }
+ if !strings.Contains(strc, token) {
+ t.Logf("content: %s\n", string(contents))
+ t.Fatalf("cov profile does not contain aux meta content %q", token)
+ }
+}