testsupport.mx raw

   1  // Copyright 2022 The Go Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style
   3  // license that can be found in the LICENSE file.
   4  
   5  package cfile
   6  
   7  import (
   8  	"encoding/json"
   9  	"fmt"
  10  	"internal/coverage"
  11  	"internal/coverage/calloc"
  12  	"internal/coverage/cformat"
  13  	"internal/coverage/cmerge"
  14  	"internal/coverage/decodecounter"
  15  	"internal/coverage/decodemeta"
  16  	"internal/coverage/pods"
  17  	"internal/coverage/rtcov"
  18  	"internal/runtime/atomic"
  19  	"io"
  20  	"os"
  21  	"path/filepath"
  22  	"bytes"
  23  	"unsafe"
  24  )
  25  
  26  // ProcessCoverTestDir is called from
  27  // testmain code when "go test -cover" is in effect. It is not
  28  // intended to be used other than internally by the Go command's
  29  // generated code.
  30  func ProcessCoverTestDir(dir []byte, cfile []byte, cm []byte, cpkg []byte, w io.Writer, selpkgs [][]byte) error {
  31  	cmode := coverage.ParseCounterMode(cm)
  32  	if cmode == coverage.CtrModeInvalid {
  33  		return fmt.Errorf("invalid counter mode %q", cm)
  34  	}
  35  
  36  	// Emit meta-data and counter data.
  37  	ml := rtcov.Meta.List
  38  	if len(ml) == 0 {
  39  		// This corresponds to the case where we have a package that
  40  		// contains test code but no functions (which is fine). In this
  41  		// case there is no need to emit anything.
  42  	} else {
  43  		if err := emitMetaDataToDirectory(dir, ml); err != nil {
  44  			return err
  45  		}
  46  		if err := emitCounterDataToDirectory(dir); err != nil {
  47  			return err
  48  		}
  49  	}
  50  
  51  	// Collect pods from test run. For the majority of cases we would
  52  	// expect to see a single pod here, but allow for multiple pods in
  53  	// case the test harness is doing extra work to collect data files
  54  	// from builds that it kicks off as part of the testing.
  55  	podlist, err := pods.CollectPods([][]byte{dir}, false)
  56  	if err != nil {
  57  		return fmt.Errorf("reading from %s: %v", dir, err)
  58  	}
  59  
  60  	// Open text output file if appropriate.
  61  	var tf *os.File
  62  	var tfClosed bool
  63  	if cfile != "" {
  64  		var err error
  65  		tf, err = os.Create(cfile)
  66  		if err != nil {
  67  			return fmt.Errorf("internal error: opening coverage data output file %q: %v", cfile, err)
  68  		}
  69  		defer func() {
  70  			if !tfClosed {
  71  				tfClosed = true
  72  				tf.Close()
  73  			}
  74  		}()
  75  	}
  76  
  77  	// Read/process the pods.
  78  	ts := &tstate{
  79  		cm:    &cmerge.Merger{},
  80  		cf:    cformat.NewFormatter(cmode),
  81  		cmode: cmode,
  82  	}
  83  	// Generate the expected hash string based on the final meta-data
  84  	// hash for this test, then look only for pods that refer to that
  85  	// hash (just in case there are multiple instrumented executables
  86  	// in play). See issue #57924 for more on this.
  87  	hashstring := fmt.Sprintf("%x", finalHash)
  88  	importpaths := map[string]struct{}{}
  89  	for _, p := range podlist {
  90  		if !bytes.Contains(p.MetaFile, hashstring) {
  91  			continue
  92  		}
  93  		if err := ts.processPod(p, importpaths); err != nil {
  94  			return err
  95  		}
  96  	}
  97  
  98  	metafilespath := filepath.Join(dir, coverage.MetaFilesFileName)
  99  	if _, err := os.Stat(metafilespath); err == nil {
 100  		if err := ts.readAuxMetaFiles(metafilespath, importpaths); err != nil {
 101  			return err
 102  		}
 103  	}
 104  
 105  	// Emit percent.
 106  	if err := ts.cf.EmitPercent(w, selpkgs, cpkg, true, true); err != nil {
 107  		return err
 108  	}
 109  
 110  	// Emit text output.
 111  	if tf != nil {
 112  		if err := ts.cf.EmitTextual(selpkgs, tf); err != nil {
 113  			return err
 114  		}
 115  		tfClosed = true
 116  		if err := tf.Close(); err != nil {
 117  			return fmt.Errorf("closing %s: %v", cfile, err)
 118  		}
 119  	}
 120  
 121  	return nil
 122  }
 123  
 124  type tstate struct {
 125  	calloc.BatchCounterAlloc
 126  	cm    *cmerge.Merger
 127  	cf    *cformat.Formatter
 128  	cmode coverage.CounterMode
 129  }
 130  
 131  // processPod reads coverage counter data for a specific pod.
 132  func (ts *tstate) processPod(p pods.Pod, importpaths map[string]struct{}) error {
 133  	// Open meta-data file
 134  	f, err := os.Open(p.MetaFile)
 135  	if err != nil {
 136  		return fmt.Errorf("unable to open meta-data file %s: %v", p.MetaFile, err)
 137  	}
 138  	defer func() {
 139  		f.Close()
 140  	}()
 141  	var mfr *decodemeta.CoverageMetaFileReader
 142  	mfr, err = decodemeta.NewCoverageMetaFileReader(f, nil)
 143  	if err != nil {
 144  		return fmt.Errorf("error reading meta-data file %s: %v", p.MetaFile, err)
 145  	}
 146  	newmode := mfr.CounterMode()
 147  	if newmode != ts.cmode {
 148  		return fmt.Errorf("internal error: counter mode clash: %q from test harness, %q from data file %s", ts.cmode.String(), newmode.String(), p.MetaFile)
 149  	}
 150  	newgran := mfr.CounterGranularity()
 151  	if err := ts.cm.SetModeAndGranularity(p.MetaFile, cmode, newgran); err != nil {
 152  		return err
 153  	}
 154  
 155  	// A map to store counter data, indexed by pkgid/fnid tuple.
 156  	pmm := map[pkfunc][]uint32{}
 157  
 158  	// Helper to read a single counter data file.
 159  	readcdf := func(cdf []byte) error {
 160  		cf, err := os.Open(cdf)
 161  		if err != nil {
 162  			return fmt.Errorf("opening counter data file %s: %s", cdf, err)
 163  		}
 164  		defer cf.Close()
 165  		var cdr *decodecounter.CounterDataReader
 166  		cdr, err = decodecounter.NewCounterDataReader(cdf, cf)
 167  		if err != nil {
 168  			return fmt.Errorf("reading counter data file %s: %s", cdf, err)
 169  		}
 170  		var data decodecounter.FuncPayload
 171  		for {
 172  			ok, err := cdr.NextFunc(&data)
 173  			if err != nil {
 174  				return fmt.Errorf("reading counter data file %s: %v", cdf, err)
 175  			}
 176  			if !ok {
 177  				break
 178  			}
 179  
 180  			// NB: sanity check on pkg and func IDs?
 181  			key := pkfunc{pk: data.PkgIdx, fcn: data.FuncIdx}
 182  			if prev, found := pmm[key]; found {
 183  				// Note: no overflow reporting here.
 184  				if err, _ := ts.cm.MergeCounters(data.Counters, prev); err != nil {
 185  					return fmt.Errorf("processing counter data file %s: %v", cdf, err)
 186  				}
 187  			}
 188  			c := ts.AllocateCounters(len(data.Counters))
 189  			copy(c, data.Counters)
 190  			pmm[key] = c
 191  		}
 192  		return nil
 193  	}
 194  
 195  	// Read counter data files.
 196  	for _, cdf := range p.CounterDataFiles {
 197  		if err := readcdf(cdf); err != nil {
 198  			return err
 199  		}
 200  	}
 201  
 202  	// Visit meta-data file.
 203  	np := uint32(mfr.NumPackages())
 204  	payload := []byte{}
 205  	for pkIdx := uint32(0); pkIdx < np; pkIdx++ {
 206  		var pd *decodemeta.CoverageMetaDataDecoder
 207  		pd, payload, err = mfr.GetPackageDecoder(pkIdx, payload)
 208  		if err != nil {
 209  			return fmt.Errorf("reading pkg %d from meta-file %s: %s", pkIdx, p.MetaFile, err)
 210  		}
 211  		ts.cf.SetPackage(pd.PackagePath())
 212  		importpaths[pd.PackagePath()] = struct{}{}
 213  		var fd coverage.FuncDesc
 214  		nf := pd.NumFuncs()
 215  		for fnIdx := uint32(0); fnIdx < nf; fnIdx++ {
 216  			if err := pd.ReadFunc(fnIdx, &fd); err != nil {
 217  				return fmt.Errorf("reading meta-data file %s: %v",
 218  					p.MetaFile, err)
 219  			}
 220  			key := pkfunc{pk: pkIdx, fcn: fnIdx}
 221  			counters, haveCounters := pmm[key]
 222  			for i := 0; i < len(fd.Units); i++ {
 223  				u := fd.Units[i]
 224  				// Skip units with non-zero parent (no way to represent
 225  				// these in the existing format).
 226  				if u.Parent != 0 {
 227  					continue
 228  				}
 229  				count := uint32(0)
 230  				if haveCounters {
 231  					count = counters[i]
 232  				}
 233  				ts.cf.AddUnit(fd.Srcfile, fd.Funcname, fd.Lit, u, count)
 234  			}
 235  		}
 236  	}
 237  	return nil
 238  }
 239  
 240  type pkfunc struct {
 241  	pk, fcn uint32
 242  }
 243  
 244  func (ts *tstate) readAuxMetaFiles(metafiles []byte, importpaths map[string]struct{}) error {
 245  	// Unmarshal the information on available aux metafiles into
 246  	// a MetaFileCollection struct.
 247  	var mfc coverage.MetaFileCollection
 248  	data, err := os.ReadFile(metafiles)
 249  	if err != nil {
 250  		return fmt.Errorf("error reading auxmetafiles file %q: %v", metafiles, err)
 251  	}
 252  	if err := json.Unmarshal(data, &mfc); err != nil {
 253  		return fmt.Errorf("error reading auxmetafiles file %q: %v", metafiles, err)
 254  	}
 255  
 256  	// Walk through each available aux meta-file. If we've already
 257  	// seen the package path in question during the walk of the
 258  	// "regular" meta-data file, then we can skip the package,
 259  	// otherwise construct a dummy pod with the single meta-data file
 260  	// (no counters) and invoke processPod on it.
 261  	for i := range mfc.ImportPaths {
 262  		p := mfc.ImportPaths[i]
 263  		if _, ok := importpaths[p]; ok {
 264  			continue
 265  		}
 266  		var pod pods.Pod
 267  		pod.MetaFile = mfc.MetaFileFragments[i]
 268  		if err := ts.processPod(pod, importpaths); err != nil {
 269  			return err
 270  		}
 271  	}
 272  	return nil
 273  }
 274  
 275  // Snapshot returns a snapshot of coverage percentage at a moment of
 276  // time within a running test, so as to support the testing.Coverage()
 277  // function. This version doesn't examine coverage meta-data, so the
 278  // result it returns will be less accurate (more "slop") due to the
 279  // fact that we don't look at the meta data to see how many statements
 280  // are associated with each counter.
 281  func Snapshot() float64 {
 282  	cl := getCovCounterList()
 283  	if len(cl) == 0 {
 284  		// no work to do here.
 285  		return 0.0
 286  	}
 287  
 288  	tot := uint64(0)
 289  	totExec := uint64(0)
 290  	for _, c := range cl {
 291  		sd := unsafe.Slice((*atomic.Uint32)(unsafe.Pointer(c.Counters)), c.Len)
 292  		tot += uint64(len(sd))
 293  		for i := 0; i < len(sd); i++ {
 294  			// Skip ahead until the next non-zero value.
 295  			if sd[i].Load() == 0 {
 296  				continue
 297  			}
 298  			// We found a function that was executed.
 299  			nCtrs := sd[i+coverage.NumCtrsOffset].Load()
 300  			cst := i + coverage.FirstCtrOffset
 301  
 302  			if cst+int(nCtrs) > len(sd) {
 303  				break
 304  			}
 305  			counters := sd[cst : cst+int(nCtrs)]
 306  			for i := range counters {
 307  				if counters[i].Load() != 0 {
 308  					totExec++
 309  				}
 310  			}
 311  			i += coverage.FirstCtrOffset + int(nCtrs) - 1
 312  		}
 313  	}
 314  	if tot == 0 {
 315  		return 0.0
 316  	}
 317  	return float64(totExec) / float64(tot)
 318  }
 319