github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/runtime/coverage/testsupport.go (about)

     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 coverage
     6  
     7  import (
     8  	"fmt"
     9  	"internal/coverage"
    10  	"internal/coverage/calloc"
    11  	"internal/coverage/cformat"
    12  	"internal/coverage/cmerge"
    13  	"internal/coverage/decodecounter"
    14  	"internal/coverage/decodemeta"
    15  	"internal/coverage/pods"
    16  	"io"
    17  	"os"
    18  	"strings"
    19  )
    20  
    21  // processCoverTestDir is called (via a linknamed reference) from
    22  // testmain code when "go test -cover" is in effect. It is not
    23  // intended to be used other than internally by the Go command's
    24  // generated code.
    25  func processCoverTestDir(dir string, cfile string, cm string, cpkg string) error {
    26  	return processCoverTestDirInternal(dir, cfile, cm, cpkg, os.Stdout)
    27  }
    28  
    29  // processCoverTestDirInternal is an io.Writer version of processCoverTestDir,
    30  // exposed for unit testing.
    31  func processCoverTestDirInternal(dir string, cfile string, cm string, cpkg string, w io.Writer) error {
    32  	cmode := coverage.ParseCounterMode(cm)
    33  	if cmode == coverage.CtrModeInvalid {
    34  		return fmt.Errorf("invalid counter mode %q", cm)
    35  	}
    36  
    37  	// Emit meta-data and counter data.
    38  	ml := getCovMetaList()
    39  	if len(ml) == 0 {
    40  		// This corresponds to the case where we have a package that
    41  		// contains test code but no functions (which is fine). In this
    42  		// case there is no need to emit anything.
    43  	} else {
    44  		if err := emitMetaDataToDirectory(dir, ml); err != nil {
    45  			return err
    46  		}
    47  		if err := emitCounterDataToDirectory(dir); err != nil {
    48  			return err
    49  		}
    50  	}
    51  
    52  	// Collect pods from test run. For the majority of cases we would
    53  	// expect to see a single pod here, but allow for multiple pods in
    54  	// case the test harness is doing extra work to collect data files
    55  	// from builds that it kicks off as part of the testing.
    56  	podlist, err := pods.CollectPods([]string{dir}, false)
    57  	if err != nil {
    58  		return fmt.Errorf("reading from %s: %v", dir, err)
    59  	}
    60  
    61  	// Open text output file if appropriate.
    62  	var tf *os.File
    63  	var tfClosed bool
    64  	if cfile != "" {
    65  		var err error
    66  		tf, err = os.Create(cfile)
    67  		if err != nil {
    68  			return fmt.Errorf("internal error: opening coverage data output file %q: %v", cfile, err)
    69  		}
    70  		defer func() {
    71  			if !tfClosed {
    72  				tfClosed = true
    73  				tf.Close()
    74  			}
    75  		}()
    76  	}
    77  
    78  	// Read/process the pods.
    79  	ts := &tstate{
    80  		cm:    &cmerge.Merger{},
    81  		cf:    cformat.NewFormatter(cmode),
    82  		cmode: cmode,
    83  	}
    84  	// Generate the expected hash string based on the final meta-data
    85  	// hash for this test, then look only for pods that refer to that
    86  	// hash (just in case there are multiple instrumented executables
    87  	// in play). See issue #57924 for more on this.
    88  	hashstring := fmt.Sprintf("%x", finalHash)
    89  	for _, p := range podlist {
    90  		if !strings.Contains(p.MetaFile, hashstring) {
    91  			continue
    92  		}
    93  		if err := ts.processPod(p); err != nil {
    94  			return err
    95  		}
    96  	}
    97  
    98  	// Emit percent.
    99  	if err := ts.cf.EmitPercent(w, cpkg, true); err != nil {
   100  		return err
   101  	}
   102  
   103  	// Emit text output.
   104  	if tf != nil {
   105  		if err := ts.cf.EmitTextual(tf); err != nil {
   106  			return err
   107  		}
   108  		tfClosed = true
   109  		if err := tf.Close(); err != nil {
   110  			return fmt.Errorf("closing %s: %v", cfile, err)
   111  		}
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  type tstate struct {
   118  	calloc.BatchCounterAlloc
   119  	cm    *cmerge.Merger
   120  	cf    *cformat.Formatter
   121  	cmode coverage.CounterMode
   122  }
   123  
   124  // processPod reads coverage counter data for a specific pod.
   125  func (ts *tstate) processPod(p pods.Pod) error {
   126  	// Open meta-data file
   127  	f, err := os.Open(p.MetaFile)
   128  	if err != nil {
   129  		return fmt.Errorf("unable to open meta-data file %s: %v", p.MetaFile, err)
   130  	}
   131  	defer func() {
   132  		f.Close()
   133  	}()
   134  	var mfr *decodemeta.CoverageMetaFileReader
   135  	mfr, err = decodemeta.NewCoverageMetaFileReader(f, nil)
   136  	if err != nil {
   137  		return fmt.Errorf("error reading meta-data file %s: %v", p.MetaFile, err)
   138  	}
   139  	newmode := mfr.CounterMode()
   140  	if newmode != ts.cmode {
   141  		return fmt.Errorf("internal error: counter mode clash: %q from test harness, %q from data file %s", ts.cmode.String(), newmode.String(), p.MetaFile)
   142  	}
   143  	newgran := mfr.CounterGranularity()
   144  	if err := ts.cm.SetModeAndGranularity(p.MetaFile, cmode, newgran); err != nil {
   145  		return err
   146  	}
   147  
   148  	// A map to store counter data, indexed by pkgid/fnid tuple.
   149  	pmm := make(map[pkfunc][]uint32)
   150  
   151  	// Helper to read a single counter data file.
   152  	readcdf := func(cdf string) error {
   153  		cf, err := os.Open(cdf)
   154  		if err != nil {
   155  			return fmt.Errorf("opening counter data file %s: %s", cdf, err)
   156  		}
   157  		defer cf.Close()
   158  		var cdr *decodecounter.CounterDataReader
   159  		cdr, err = decodecounter.NewCounterDataReader(cdf, cf)
   160  		if err != nil {
   161  			return fmt.Errorf("reading counter data file %s: %s", cdf, err)
   162  		}
   163  		var data decodecounter.FuncPayload
   164  		for {
   165  			ok, err := cdr.NextFunc(&data)
   166  			if err != nil {
   167  				return fmt.Errorf("reading counter data file %s: %v", cdf, err)
   168  			}
   169  			if !ok {
   170  				break
   171  			}
   172  
   173  			// NB: sanity check on pkg and func IDs?
   174  			key := pkfunc{pk: data.PkgIdx, fcn: data.FuncIdx}
   175  			if prev, found := pmm[key]; found {
   176  				// Note: no overflow reporting here.
   177  				if err, _ := ts.cm.MergeCounters(data.Counters, prev); err != nil {
   178  					return fmt.Errorf("processing counter data file %s: %v", cdf, err)
   179  				}
   180  			}
   181  			c := ts.AllocateCounters(len(data.Counters))
   182  			copy(c, data.Counters)
   183  			pmm[key] = c
   184  		}
   185  		return nil
   186  	}
   187  
   188  	// Read counter data files.
   189  	for _, cdf := range p.CounterDataFiles {
   190  		if err := readcdf(cdf); err != nil {
   191  			return err
   192  		}
   193  	}
   194  
   195  	// Visit meta-data file.
   196  	np := uint32(mfr.NumPackages())
   197  	payload := []byte{}
   198  	for pkIdx := uint32(0); pkIdx < np; pkIdx++ {
   199  		var pd *decodemeta.CoverageMetaDataDecoder
   200  		pd, payload, err = mfr.GetPackageDecoder(pkIdx, payload)
   201  		if err != nil {
   202  			return fmt.Errorf("reading pkg %d from meta-file %s: %s", pkIdx, p.MetaFile, err)
   203  		}
   204  		ts.cf.SetPackage(pd.PackagePath())
   205  		var fd coverage.FuncDesc
   206  		nf := pd.NumFuncs()
   207  		for fnIdx := uint32(0); fnIdx < nf; fnIdx++ {
   208  			if err := pd.ReadFunc(fnIdx, &fd); err != nil {
   209  				return fmt.Errorf("reading meta-data file %s: %v",
   210  					p.MetaFile, err)
   211  			}
   212  			key := pkfunc{pk: pkIdx, fcn: fnIdx}
   213  			counters, haveCounters := pmm[key]
   214  			for i := 0; i < len(fd.Units); i++ {
   215  				u := fd.Units[i]
   216  				// Skip units with non-zero parent (no way to represent
   217  				// these in the existing format).
   218  				if u.Parent != 0 {
   219  					continue
   220  				}
   221  				count := uint32(0)
   222  				if haveCounters {
   223  					count = counters[i]
   224  				}
   225  				ts.cf.AddUnit(fd.Srcfile, fd.Funcname, fd.Lit, u, count)
   226  			}
   227  		}
   228  	}
   229  	return nil
   230  }
   231  
   232  type pkfunc struct {
   233  	pk, fcn uint32
   234  }