|  | // 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 cfile | 
|  |  | 
|  | 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/coverage/rtcov" | 
|  | "internal/runtime/atomic" | 
|  | "io" | 
|  | "os" | 
|  | "path/filepath" | 
|  | "strings" | 
|  | "unsafe" | 
|  | ) | 
|  |  | 
|  | // ProcessCoverTestDir is called 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, w io.Writer, selpkgs []string) error { | 
|  | cmode := coverage.ParseCounterMode(cm) | 
|  | if cmode == coverage.CtrModeInvalid { | 
|  | return fmt.Errorf("invalid counter mode %q", cm) | 
|  | } | 
|  |  | 
|  | // Emit meta-data and counter data. | 
|  | ml := rtcov.Meta.List | 
|  | 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, selpkgs, cpkg, true, true); err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | // Emit text output. | 
|  | if tf != nil { | 
|  | if err := ts.cf.EmitTextual(selpkgs, 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 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) | 
|  | } |