...

Source file src/internal/coverage/cfile/testsupport.go

Documentation: internal/coverage/cfile

     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  	"strings"
    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 string, cfile string, cm string, cpkg string, w io.Writer, selpkgs []string) 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([]string{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 := make(map[string]struct{})
    89  	for _, p := range podlist {
    90  		if !strings.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(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 := make(map[pkfunc][]uint32)
   157  
   158  	// Helper to read a single counter data file.
   159  	readcdf := func(cdf string) 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 string, 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  

View as plain text