summaryrefslogtreecommitdiffstats
path: root/src/cmd/internal/cov
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:25:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:25:22 +0000
commitf6ad4dcef54c5ce997a4bad5a6d86de229015700 (patch)
tree7cfa4e31ace5c2bd95c72b154d15af494b2bcbef /src/cmd/internal/cov
parentInitial commit. (diff)
downloadgolang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.tar.xz
golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.zip
Adding upstream version 1.22.1.upstream/1.22.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/internal/cov')
-rw-r--r--src/cmd/internal/cov/covcmd/cmddefs.go97
-rw-r--r--src/cmd/internal/cov/mreader.go85
-rw-r--r--src/cmd/internal/cov/read_test.go102
-rw-r--r--src/cmd/internal/cov/readcovdata.go277
-rw-r--r--src/cmd/internal/cov/testdata/small.go7
5 files changed, 568 insertions, 0 deletions
diff --git a/src/cmd/internal/cov/covcmd/cmddefs.go b/src/cmd/internal/cov/covcmd/cmddefs.go
new file mode 100644
index 0000000..cb848d3
--- /dev/null
+++ b/src/cmd/internal/cov/covcmd/cmddefs.go
@@ -0,0 +1,97 @@
+// 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 covcmd
+
+import (
+ "crypto/sha256"
+ "fmt"
+ "internal/coverage"
+)
+
+// CoverPkgConfig is a bundle of information passed from the Go
+// command to the cover command during "go build -cover" runs. The
+// Go command creates and fills in a struct as below, then passes
+// file containing the encoded JSON for the struct to the "cover"
+// tool when instrumenting the source files in a Go package.
+type CoverPkgConfig struct {
+ // File into which cmd/cover should emit summary info
+ // when instrumentation is complete.
+ OutConfig string
+
+ // Import path for the package being instrumented.
+ PkgPath string
+
+ // Package name.
+ PkgName string
+
+ // Instrumentation granularity: one of "perfunc" or "perblock" (default)
+ Granularity string
+
+ // Module path for this package (empty if no go.mod in use)
+ ModulePath string
+
+ // Local mode indicates we're doing a coverage build or test of a
+ // package selected via local import path, e.g. "./..." or
+ // "./foo/bar" as opposed to a non-relative import path. See the
+ // corresponding field in cmd/go's PackageInternal struct for more
+ // info.
+ Local bool
+
+ // EmitMetaFile if non-empty is the path to which the cover tool should
+ // directly emit a coverage meta-data file for the package, if the
+ // package has any functions in it. The go command will pass in a value
+ // here if we've been asked to run "go test -cover" on a package that
+ // doesn't have any *_test.go files.
+ EmitMetaFile string
+}
+
+// CoverFixupConfig contains annotations/notes generated by the
+// cmd/cover tool (during instrumentation) to be passed on to the
+// compiler when the instrumented code is compiled. The cmd/cover tool
+// creates a struct of this type, JSON-encodes it, and emits the
+// result to a file, which the Go command then passes to the compiler
+// when the instrumented package is built.
+type CoverFixupConfig struct {
+ // Name of the variable (created by cmd/cover) containing the
+ // encoded meta-data for the package.
+ MetaVar string
+
+ // Length of the meta-data.
+ MetaLen int
+
+ // Hash computed by cmd/cover of the meta-data.
+ MetaHash string
+
+ // Instrumentation strategy. For now this is always set to
+ // "normal", but in the future we may add new values (for example,
+ // if panic paths are instrumented, or if the instrumenter
+ // eliminates redundant counters).
+ Strategy string
+
+ // Prefix assigned to the names of counter variables generated
+ // during instrumentation by cmd/cover.
+ CounterPrefix string
+
+ // Name chosen for the package ID variable generated during
+ // instrumentation.
+ PkgIdVar string
+
+ // Counter mode (e.g. set/count/atomic)
+ CounterMode string
+
+ // Counter granularity (perblock or perfunc).
+ CounterGranularity string
+}
+
+// MetaFileForPackage returns the expected name of the meta-data file
+// for the package whose import path is 'importPath' in cases where
+// we're using meta-data generated by the cover tool, as opposed to a
+// meta-data file created at runtime.
+func MetaFileForPackage(importPath string) string {
+ var r [32]byte
+ sum := sha256.Sum256([]byte(importPath))
+ copy(r[:], sum[:])
+ return coverage.MetaFilePref + fmt.Sprintf(".%x", r)
+}
diff --git a/src/cmd/internal/cov/mreader.go b/src/cmd/internal/cov/mreader.go
new file mode 100644
index 0000000..30f53d6
--- /dev/null
+++ b/src/cmd/internal/cov/mreader.go
@@ -0,0 +1,85 @@
+// 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 cov
+
+import (
+ "cmd/internal/bio"
+ "io"
+ "os"
+)
+
+// This file contains the helper "MReader", a wrapper around bio plus
+// an "mmap'd read-only" view of the file obtained from bio.SliceRO().
+// MReader is designed to implement the io.ReaderSeeker interface.
+// Since bio.SliceOS() is not guaranteed to succeed, MReader falls back
+// on explicit reads + seeks provided by bio.Reader if needed.
+
+type MReader struct {
+ f *os.File
+ rdr *bio.Reader
+ fileView []byte
+ off int64
+}
+
+func NewMreader(f *os.File) (*MReader, error) {
+ rdr := bio.NewReader(f)
+ fi, err := f.Stat()
+ if err != nil {
+ return nil, err
+ }
+ r := MReader{
+ f: f,
+ rdr: rdr,
+ fileView: rdr.SliceRO(uint64(fi.Size())),
+ }
+ return &r, nil
+}
+
+func (r *MReader) Read(p []byte) (int, error) {
+ if r.fileView != nil {
+ amt := len(p)
+ toread := r.fileView[r.off:]
+ if len(toread) < 1 {
+ return 0, io.EOF
+ }
+ if len(toread) < amt {
+ amt = len(toread)
+ }
+ copy(p, toread)
+ r.off += int64(amt)
+ return amt, nil
+ }
+ return io.ReadFull(r.rdr, p)
+}
+
+func (r *MReader) ReadByte() (byte, error) {
+ if r.fileView != nil {
+ toread := r.fileView[r.off:]
+ if len(toread) < 1 {
+ return 0, io.EOF
+ }
+ rv := toread[0]
+ r.off++
+ return rv, nil
+ }
+ return r.rdr.ReadByte()
+}
+
+func (r *MReader) Seek(offset int64, whence int) (int64, error) {
+ if r.fileView == nil {
+ return r.rdr.MustSeek(offset, whence), nil
+ }
+ switch whence {
+ case io.SeekStart:
+ r.off = offset
+ return offset, nil
+ case io.SeekCurrent:
+ return r.off, nil
+ case io.SeekEnd:
+ r.off = int64(len(r.fileView)) + offset
+ return r.off, nil
+ }
+ panic("other modes not implemented")
+}
diff --git a/src/cmd/internal/cov/read_test.go b/src/cmd/internal/cov/read_test.go
new file mode 100644
index 0000000..fa2151a
--- /dev/null
+++ b/src/cmd/internal/cov/read_test.go
@@ -0,0 +1,102 @@
+// 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 cov_test
+
+import (
+ "cmd/internal/cov"
+ "fmt"
+ "internal/coverage"
+ "internal/coverage/decodecounter"
+ "internal/coverage/decodemeta"
+ "internal/coverage/pods"
+ "internal/goexperiment"
+ "internal/testenv"
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+// visitor implements the CovDataVisitor interface in a very stripped
+// down way, just keeps track of interesting events.
+type visitor struct {
+ metaFileCount int
+ counterFileCount int
+ funcCounterData int
+ metaFuncCount int
+}
+
+func (v *visitor) BeginPod(p pods.Pod) {}
+func (v *visitor) EndPod(p pods.Pod) {}
+func (v *visitor) VisitMetaDataFile(mdf string, mfr *decodemeta.CoverageMetaFileReader) {
+ v.metaFileCount++
+}
+func (v *visitor) BeginCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int) {
+ v.counterFileCount++
+}
+func (v *visitor) EndCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int) {}
+func (v *visitor) VisitFuncCounterData(payload decodecounter.FuncPayload) { v.funcCounterData++ }
+func (v *visitor) EndCounters() {}
+func (v *visitor) BeginPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) {}
+func (v *visitor) EndPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) {}
+func (v *visitor) VisitFunc(pkgIdx uint32, fnIdx uint32, fd *coverage.FuncDesc) { v.metaFuncCount++ }
+func (v *visitor) Finish() {}
+
+func TestIssue58411(t *testing.T) {
+ testenv.MustHaveGoBuild(t)
+ if !goexperiment.CoverageRedesign {
+ t.Skipf("skipping since this test requires 'go build -cover'")
+ }
+
+ // Build a tiny test program with -cover. Smallness is important;
+ // it is one of the factors that triggers issue 58411.
+ d := t.TempDir()
+ exepath := filepath.Join(d, "small.exe")
+ path := filepath.Join("testdata", "small.go")
+ cmd := testenv.Command(t, testenv.GoToolPath(t), "build",
+ "-o", exepath, "-cover", path)
+ b, err := cmd.CombinedOutput()
+ if len(b) != 0 {
+ t.Logf("## build output:\n%s", b)
+ }
+ if err != nil {
+ t.Fatalf("build error: %v", err)
+ }
+
+ // Run to produce coverage data. Note the large argument; we need a large
+ // argument (more than 4k) to trigger the bug, but the overall file
+ // has to remain small (since large files will be read with mmap).
+ covdir := filepath.Join(d, "covdata")
+ if err = os.Mkdir(covdir, 0777); err != nil {
+ t.Fatalf("creating covdir: %v", err)
+ }
+ large := fmt.Sprintf("%07999d", 0)
+ cmd = testenv.Command(t, exepath, "1", "2", "3", large)
+ cmd.Dir = covdir
+ cmd.Env = append(os.Environ(), "GOCOVERDIR="+covdir)
+ b, err = cmd.CombinedOutput()
+ if err != nil {
+ t.Logf("## run output:\n%s", b)
+ t.Fatalf("build error: %v", err)
+ }
+
+ vis := &visitor{}
+
+ // Read resulting coverage data. Without the fix, this would
+ // yield a "short read" error.
+ const verbosityLevel = 0
+ const flags = 0
+ cdr := cov.MakeCovDataReader(vis, []string{covdir}, verbosityLevel, flags, nil)
+ err = cdr.Visit()
+ if err != nil {
+ t.Fatalf("visit failed: %v", err)
+ }
+
+ // make sure we saw a few things just for grins
+ const want = "{metaFileCount:1 counterFileCount:1 funcCounterData:1 metaFuncCount:1}"
+ got := fmt.Sprintf("%+v", *vis)
+ if want != got {
+ t.Errorf("visitor contents: want %v got %v\n", want, got)
+ }
+}
diff --git a/src/cmd/internal/cov/readcovdata.go b/src/cmd/internal/cov/readcovdata.go
new file mode 100644
index 0000000..086be40
--- /dev/null
+++ b/src/cmd/internal/cov/readcovdata.go
@@ -0,0 +1,277 @@
+// 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 cov
+
+import (
+ "cmd/internal/bio"
+ "fmt"
+ "internal/coverage"
+ "internal/coverage/decodecounter"
+ "internal/coverage/decodemeta"
+ "internal/coverage/pods"
+ "io"
+ "os"
+)
+
+// CovDataReader is a general-purpose helper/visitor object for
+// reading coverage data files in a structured way. Clients create a
+// CovDataReader to process a given collection of coverage data file
+// directories, then pass in a visitor object with methods that get
+// invoked at various important points. CovDataReader is intended
+// to facilitate common coverage data file operations such as
+// merging or intersecting data files, analyzing data files, or
+// dumping data files.
+type CovDataReader struct {
+ vis CovDataVisitor
+ indirs []string
+ matchpkg func(name string) bool
+ flags CovDataReaderFlags
+ err error
+ verbosityLevel int
+}
+
+// MakeCovDataReader creates a CovDataReader object to process the
+// given set of input directories. Here 'vis' is a visitor object
+// providing methods to be invoked as we walk through the data,
+// 'indirs' is the set of coverage data directories to examine,
+// 'verbosityLevel' controls the level of debugging trace messages
+// (zero for off, higher for more output), 'flags' stores flags that
+// indicate what to do if errors are detected, and 'matchpkg' is a
+// caller-provided function that can be used to select specific
+// packages by name (if nil, then all packages are included).
+func MakeCovDataReader(vis CovDataVisitor, indirs []string, verbosityLevel int, flags CovDataReaderFlags, matchpkg func(name string) bool) *CovDataReader {
+ return &CovDataReader{
+ vis: vis,
+ indirs: indirs,
+ matchpkg: matchpkg,
+ verbosityLevel: verbosityLevel,
+ flags: flags,
+ }
+}
+
+// CovDataVisitor defines hooks for clients of CovDataReader. When the
+// coverage data reader makes its way through a coverage meta-data
+// file and counter data files, it will invoke the methods below to
+// hand off info to the client. The normal sequence of expected
+// visitor method invocations is:
+//
+// for each pod P {
+// BeginPod(p)
+// let MF be the meta-data file for P
+// VisitMetaDataFile(MF)
+// for each counter data file D in P {
+// BeginCounterDataFile(D)
+// for each live function F in D {
+// VisitFuncCounterData(F)
+// }
+// EndCounterDataFile(D)
+// }
+// EndCounters(MF)
+// for each package PK in MF {
+// BeginPackage(PK)
+// if <PK matched according to package pattern and/or modpath> {
+// for each function PF in PK {
+// VisitFunc(PF)
+// }
+// }
+// EndPackage(PK)
+// }
+// EndPod(p)
+// }
+// Finish()
+
+type CovDataVisitor interface {
+ // Invoked at the start and end of a given pod (a pod here is a
+ // specific coverage meta-data files with the counter data files
+ // that correspond to it).
+ BeginPod(p pods.Pod)
+ EndPod(p pods.Pod)
+
+ // Invoked when the reader is starting to examine the meta-data
+ // file for a pod. Here 'mdf' is the path of the file, and 'mfr'
+ // is an open meta-data reader.
+ VisitMetaDataFile(mdf string, mfr *decodemeta.CoverageMetaFileReader)
+
+ // Invoked when the reader processes a counter data file, first
+ // the 'begin' method at the start, then the 'end' method when
+ // we're done with the file.
+ BeginCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int)
+ EndCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int)
+
+ // Invoked once for each live function in the counter data file.
+ VisitFuncCounterData(payload decodecounter.FuncPayload)
+
+ // Invoked when we've finished processing the counter files in a
+ // POD (e.g. no more calls to VisitFuncCounterData).
+ EndCounters()
+
+ // Invoked for each package in the meta-data file for the pod,
+ // first the 'begin' method when processing of the package starts,
+ // then the 'end' method when we're done
+ BeginPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32)
+ EndPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32)
+
+ // Invoked for each function the package being visited.
+ VisitFunc(pkgIdx uint32, fnIdx uint32, fd *coverage.FuncDesc)
+
+ // Invoked when all counter + meta-data file processing is complete.
+ Finish()
+}
+
+type CovDataReaderFlags uint32
+
+const (
+ CovDataReaderNoFlags CovDataReaderFlags = 0
+ PanicOnError = 1 << iota
+ PanicOnWarning
+)
+
+func (r *CovDataReader) Visit() error {
+ podlist, err := pods.CollectPods(r.indirs, false)
+ if err != nil {
+ return fmt.Errorf("reading inputs: %v", err)
+ }
+ if len(podlist) == 0 {
+ r.warn("no applicable files found in input directories")
+ }
+ for _, p := range podlist {
+ if err := r.visitPod(p); err != nil {
+ return err
+ }
+ }
+ r.vis.Finish()
+ return nil
+}
+
+func (r *CovDataReader) verb(vlevel int, s string, a ...interface{}) {
+ if r.verbosityLevel >= vlevel {
+ fmt.Fprintf(os.Stderr, s, a...)
+ fmt.Fprintf(os.Stderr, "\n")
+ }
+}
+
+func (r *CovDataReader) warn(s string, a ...interface{}) {
+ fmt.Fprintf(os.Stderr, "warning: ")
+ fmt.Fprintf(os.Stderr, s, a...)
+ fmt.Fprintf(os.Stderr, "\n")
+ if (r.flags & PanicOnWarning) != 0 {
+ panic("unexpected warning")
+ }
+}
+
+func (r *CovDataReader) fatal(s string, a ...interface{}) error {
+ if r.err != nil {
+ return nil
+ }
+ errstr := "error: " + fmt.Sprintf(s, a...) + "\n"
+ if (r.flags & PanicOnError) != 0 {
+ fmt.Fprintf(os.Stderr, "%s", errstr)
+ panic("fatal error")
+ }
+ r.err = fmt.Errorf("%s", errstr)
+ return r.err
+}
+
+// visitPod examines a coverage data 'pod', that is, a meta-data file and
+// zero or more counter data files that refer to that meta-data file.
+func (r *CovDataReader) visitPod(p pods.Pod) error {
+ r.verb(1, "visiting pod: metafile %s with %d counter files",
+ p.MetaFile, len(p.CounterDataFiles))
+ r.vis.BeginPod(p)
+
+ // Open meta-file
+ f, err := os.Open(p.MetaFile)
+ if err != nil {
+ return r.fatal("unable to open meta-file %s", p.MetaFile)
+ }
+ defer f.Close()
+ br := bio.NewReader(f)
+ fi, err := f.Stat()
+ if err != nil {
+ return r.fatal("unable to stat metafile %s: %v", p.MetaFile, err)
+ }
+ fileView := br.SliceRO(uint64(fi.Size()))
+ br.MustSeek(0, io.SeekStart)
+
+ r.verb(1, "fileView for pod is length %d", len(fileView))
+
+ var mfr *decodemeta.CoverageMetaFileReader
+ mfr, err = decodemeta.NewCoverageMetaFileReader(f, fileView)
+ if err != nil {
+ return r.fatal("decoding meta-file %s: %s", p.MetaFile, err)
+ }
+ r.vis.VisitMetaDataFile(p.MetaFile, mfr)
+
+ // Read counter data files.
+ for k, cdf := range p.CounterDataFiles {
+ cf, err := os.Open(cdf)
+ if err != nil {
+ return r.fatal("opening counter data file %s: %s", cdf, err)
+ }
+ defer func(f *os.File) {
+ f.Close()
+ }(cf)
+ var mr *MReader
+ mr, err = NewMreader(cf)
+ if err != nil {
+ return r.fatal("creating reader for counter data file %s: %s", cdf, err)
+ }
+ var cdr *decodecounter.CounterDataReader
+ cdr, err = decodecounter.NewCounterDataReader(cdf, mr)
+ if err != nil {
+ return r.fatal("reading counter data file %s: %s", cdf, err)
+ }
+ r.vis.BeginCounterDataFile(cdf, cdr, p.Origins[k])
+ var data decodecounter.FuncPayload
+ for {
+ ok, err := cdr.NextFunc(&data)
+ if err != nil {
+ return r.fatal("reading counter data file %s: %v", cdf, err)
+ }
+ if !ok {
+ break
+ }
+ r.vis.VisitFuncCounterData(data)
+ }
+ r.vis.EndCounterDataFile(cdf, cdr, p.Origins[k])
+ }
+ r.vis.EndCounters()
+
+ // NB: packages in the meta-file will be in dependency order (basically
+ // the order in which init files execute). Do we want an additional sort
+ // pass here, say by packagepath?
+ 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 r.fatal("reading pkg %d from meta-file %s: %s", pkIdx, p.MetaFile, err)
+ }
+ r.processPackage(p.MetaFile, pd, pkIdx)
+ }
+ r.vis.EndPod(p)
+
+ return nil
+}
+
+func (r *CovDataReader) processPackage(mfname string, pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) error {
+ if r.matchpkg != nil {
+ if !r.matchpkg(pd.PackagePath()) {
+ return nil
+ }
+ }
+ r.vis.BeginPackage(pd, pkgIdx)
+ nf := pd.NumFuncs()
+ var fd coverage.FuncDesc
+ for fidx := uint32(0); fidx < nf; fidx++ {
+ if err := pd.ReadFunc(fidx, &fd); err != nil {
+ return r.fatal("reading meta-data file %s: %v", mfname, err)
+ }
+ r.vis.VisitFunc(pkgIdx, fidx, &fd)
+ }
+ r.vis.EndPackage(pd, pkgIdx)
+ return nil
+}
diff --git a/src/cmd/internal/cov/testdata/small.go b/src/cmd/internal/cov/testdata/small.go
new file mode 100644
index 0000000..d81cb70
--- /dev/null
+++ b/src/cmd/internal/cov/testdata/small.go
@@ -0,0 +1,7 @@
+package main
+
+import "os"
+
+func main() {
+ println(len(os.Args))
+}