diff options
| author | Russ Cox <rsc@golang.org> | 2024-05-15 16:06:23 -0400 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2024-05-21 19:41:02 +0000 |
| commit | 180ea45566d19e60aa2d660f6139b7f6e18ff56b (patch) | |
| tree | e14f155a336e4f5888334d2e0c8076ac8353f49a /src/runtime/coverage | |
| parent | 647870becc230b022b431a4ef8b7c9b31382db6c (diff) | |
| download | go-180ea45566d19e60aa2d660f6139b7f6e18ff56b.tar.xz | |
runtime/coverage: remove uses of //go:linkname
Move code to internal/coverage/cfile, making it possible to
access directly from testing/internal/testdeps, so that we can
avoid needing //go:linkname hacks.
For #67401.
Change-Id: I10b23a9970164afd2165e718ef3b2d9e86783883
Reviewed-on: https://go-review.googlesource.com/c/go/+/585820
Auto-Submit: Russ Cox <rsc@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Than McIntosh <thanm@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Diffstat (limited to 'src/runtime/coverage')
| -rw-r--r-- | src/runtime/coverage/apis.go | 178 | ||||
| -rw-r--r-- | src/runtime/coverage/coverage.go | 66 | ||||
| -rw-r--r-- | src/runtime/coverage/dummy.s | 8 | ||||
| -rw-r--r-- | src/runtime/coverage/emit.go | 612 | ||||
| -rw-r--r-- | src/runtime/coverage/emitdata_test.go | 550 | ||||
| -rw-r--r-- | src/runtime/coverage/hooks.go | 42 | ||||
| -rw-r--r-- | src/runtime/coverage/testdata/harness.go | 259 | ||||
| -rw-r--r-- | src/runtime/coverage/testdata/issue56006/repro.go | 26 | ||||
| -rw-r--r-- | src/runtime/coverage/testdata/issue56006/repro_test.go | 8 | ||||
| -rw-r--r-- | src/runtime/coverage/testdata/issue59563/repro.go | 823 | ||||
| -rw-r--r-- | src/runtime/coverage/testdata/issue59563/repro_test.go | 14 | ||||
| -rw-r--r-- | src/runtime/coverage/testsupport.go | 329 | ||||
| -rw-r--r-- | src/runtime/coverage/ts_test.go | 207 |
13 files changed, 66 insertions, 3056 deletions
diff --git a/src/runtime/coverage/apis.go b/src/runtime/coverage/apis.go deleted file mode 100644 index 15ba04a86f..0000000000 --- a/src/runtime/coverage/apis.go +++ /dev/null @@ -1,178 +0,0 @@ -// 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" - "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. - - for _, c := range cl { - sd := unsafe.Slice((*atomic.Uint32)(unsafe.Pointer(c.Counters)), 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/coverage.go b/src/runtime/coverage/coverage.go new file mode 100644 index 0000000000..6b99a0bce6 --- /dev/null +++ b/src/runtime/coverage/coverage.go @@ -0,0 +1,66 @@ +// 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 ( + "internal/coverage/cfile" + "io" +) + +// initHook is invoked from main.init in programs built with -cover. +// The call is emitted by the compiler. +func initHook(istest bool) { + cfile.InitHook(istest) +} + +// 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 { + return cfile.WriteMetaDir(dir) +} + +// 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 { + return cfile.WriteMeta(w) +} + +// 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 { + return cfile.WriteCountersDir(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 { + return cfile.WriteCounters(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 { + return cfile.ClearCounters() +} diff --git a/src/runtime/coverage/dummy.s b/src/runtime/coverage/dummy.s deleted file mode 100644 index 75928593a0..0000000000 --- a/src/runtime/coverage/dummy.s +++ /dev/null @@ -1,8 +0,0 @@ -// 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 deleted file mode 100644 index 6510c889ea..0000000000 --- a/src/runtime/coverage/emit.go +++ /dev/null @@ -1,612 +0,0 @@ -// 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" - "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) - - var blobs [][]byte - for _, e := range metalist { - sd := unsafe.Slice(e.P, int(e.Len)) - blobs = append(blobs, sd) - } - return mfw.Write(finalHash, blobs, cmode, gran) -} - -func (s *emitState) VisitFuncs(f encodecounter.CounterVisitorFn) error { - var tcounters []uint32 - - 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 { - sd := unsafe.Slice((*atomic.Uint32)(unsafe.Pointer(c.Counters)), 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 is injected to testmain via linkname. -//go:linkname markProfileEmitted - -// markProfileEmitted signals the runtime/coverage machinery that -// coverage 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 deleted file mode 100644 index 3558dd2d88..0000000000 --- a/src/runtime/coverage/emitdata_test.go +++ /dev/null @@ -1,550 +0,0 @@ -// 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 deleted file mode 100644 index a9fbf9d7dd..0000000000 --- a/src/runtime/coverage/hooks.go +++ /dev/null @@ -1,42 +0,0 @@ -// 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 deleted file mode 100644 index 03969da426..0000000000 --- a/src/runtime/coverage/testdata/harness.go +++ /dev/null @@ -1,259 +0,0 @@ -// 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" - "log" - "os" - "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 := os.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 := os.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 deleted file mode 100644 index 60a4925143..0000000000 --- a/src/runtime/coverage/testdata/issue56006/repro.go +++ /dev/null @@ -1,26 +0,0 @@ -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 deleted file mode 100644 index 674d819c3b..0000000000 --- a/src/runtime/coverage/testdata/issue56006/repro_test.go +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index d054567dc5..0000000000 --- a/src/runtime/coverage/testdata/issue59563/repro.go +++ /dev/null @@ -1,823 +0,0 @@ -// 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 deleted file mode 100644 index 15c8e01a28..0000000000 --- a/src/runtime/coverage/testdata/issue59563/repro_test.go +++ /dev/null @@ -1,14 +0,0 @@ -// 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 deleted file mode 100644 index b673d3cd2c..0000000000 --- a/src/runtime/coverage/testsupport.go +++ /dev/null @@ -1,329 +0,0 @@ -// 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" - "internal/runtime/atomic" - "io" - "os" - "path/filepath" - "strings" - "unsafe" -) - -// processCoverTestDir is injected in testmain. -//go:linkname processCoverTestDir - -// 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 { - // Unmarshal 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 is injected in testmain. -//go:linkname snapshot - -// 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 deleted file mode 100644 index b4c6e9716c..0000000000 --- a/src/runtime/coverage/ts_test.go +++ /dev/null @@ -1,207 +0,0 @@ -// 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) - } -} |
