Source file src/cmd/internal/cov/readcovdata.go

     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 cov
     6  
     7  import (
     8  	"cmd/internal/bio"
     9  	"fmt"
    10  	"internal/coverage"
    11  	"internal/coverage/decodecounter"
    12  	"internal/coverage/decodemeta"
    13  	"internal/coverage/pods"
    14  	"io"
    15  	"os"
    16  )
    17  
    18  // CovDataReader is a general-purpose helper/visitor object for
    19  // reading coverage data files in a structured way. Clients create a
    20  // CovDataReader to process a given collection of coverage data file
    21  // directories, then pass in a visitor object with methods that get
    22  // invoked at various important points. CovDataReader is intended
    23  // to facilitate common coverage data file operations such as
    24  // merging or intersecting data files, analyzing data files, or
    25  // dumping data files.
    26  type CovDataReader struct {
    27  	vis            CovDataVisitor
    28  	indirs         []string
    29  	matchpkg       func(name string) bool
    30  	flags          CovDataReaderFlags
    31  	err            error
    32  	verbosityLevel int
    33  }
    34  
    35  // MakeCovDataReader creates a CovDataReader object to process the
    36  // given set of input directories. Here 'vis' is a visitor object
    37  // providing methods to be invoked as we walk through the data,
    38  // 'indirs' is the set of coverage data directories to examine,
    39  // 'verbosityLevel' controls the level of debugging trace messages
    40  // (zero for off, higher for more output), 'flags' stores flags that
    41  // indicate what to do if errors are detected, and 'matchpkg' is a
    42  // caller-provided function that can be used to select specific
    43  // packages by name (if nil, then all packages are included).
    44  func MakeCovDataReader(vis CovDataVisitor, indirs []string, verbosityLevel int, flags CovDataReaderFlags, matchpkg func(name string) bool) *CovDataReader {
    45  	return &CovDataReader{
    46  		vis:            vis,
    47  		indirs:         indirs,
    48  		matchpkg:       matchpkg,
    49  		verbosityLevel: verbosityLevel,
    50  		flags:          flags,
    51  	}
    52  }
    53  
    54  // CovDataVisitor defines hooks for clients of CovDataReader. When the
    55  // coverage data reader makes its way through a coverage meta-data
    56  // file and counter data files, it will invoke the methods below to
    57  // hand off info to the client. The normal sequence of expected
    58  // visitor method invocations is:
    59  //
    60  //	for each pod P {
    61  //		BeginPod(p)
    62  //		let MF be the meta-data file for P
    63  //		VisitMetaDataFile(MF)
    64  //		for each counter data file D in P {
    65  //			BeginCounterDataFile(D)
    66  //			for each live function F in D {
    67  //				VisitFuncCounterData(F)
    68  //			}
    69  //			EndCounterDataFile(D)
    70  //		}
    71  //		EndCounters(MF)
    72  //		for each package PK in MF {
    73  //			BeginPackage(PK)
    74  //			if <PK matched according to package pattern and/or modpath> {
    75  //				for each function PF in PK {
    76  //					VisitFunc(PF)
    77  //				}
    78  //			}
    79  //			EndPackage(PK)
    80  //		}
    81  //		EndPod(p)
    82  //	}
    83  //	Finish()
    84  
    85  type CovDataVisitor interface {
    86  	// Invoked at the start and end of a given pod (a pod here is a
    87  	// specific coverage meta-data files with the counter data files
    88  	// that correspond to it).
    89  	BeginPod(p pods.Pod)
    90  	EndPod(p pods.Pod)
    91  
    92  	// Invoked when the reader is starting to examine the meta-data
    93  	// file for a pod. Here 'mdf' is the path of the file, and 'mfr'
    94  	// is an open meta-data reader.
    95  	VisitMetaDataFile(mdf string, mfr *decodemeta.CoverageMetaFileReader)
    96  
    97  	// Invoked when the reader processes a counter data file, first
    98  	// the 'begin' method at the start, then the 'end' method when
    99  	// we're done with the file.
   100  	BeginCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int)
   101  	EndCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int)
   102  
   103  	// Invoked once for each live function in the counter data file.
   104  	VisitFuncCounterData(payload decodecounter.FuncPayload)
   105  
   106  	// Invoked when we've finished processing the counter files in a
   107  	// POD (e.g. no more calls to VisitFuncCounterData).
   108  	EndCounters()
   109  
   110  	// Invoked for each package in the meta-data file for the pod,
   111  	// first the 'begin' method when processing of the package starts,
   112  	// then the 'end' method when we're done
   113  	BeginPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32)
   114  	EndPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32)
   115  
   116  	// Invoked for each function  the package being visited.
   117  	VisitFunc(pkgIdx uint32, fnIdx uint32, fd *coverage.FuncDesc)
   118  
   119  	// Invoked when all counter + meta-data file processing is complete.
   120  	Finish()
   121  }
   122  
   123  type CovDataReaderFlags uint32
   124  
   125  const (
   126  	CovDataReaderNoFlags CovDataReaderFlags = 0
   127  	PanicOnError                            = 1 << iota
   128  	PanicOnWarning
   129  )
   130  
   131  func (r *CovDataReader) Visit() error {
   132  	podlist, err := pods.CollectPods(r.indirs, false)
   133  	if err != nil {
   134  		return fmt.Errorf("reading inputs: %v", err)
   135  	}
   136  	if len(podlist) == 0 {
   137  		r.warn("no applicable files found in input directories")
   138  	}
   139  	for _, p := range podlist {
   140  		if err := r.visitPod(p); err != nil {
   141  			return err
   142  		}
   143  	}
   144  	r.vis.Finish()
   145  	return nil
   146  }
   147  
   148  func (r *CovDataReader) verb(vlevel int, s string, a ...interface{}) {
   149  	if r.verbosityLevel >= vlevel {
   150  		fmt.Fprintf(os.Stderr, s, a...)
   151  		fmt.Fprintf(os.Stderr, "\n")
   152  	}
   153  }
   154  
   155  func (r *CovDataReader) warn(s string, a ...interface{}) {
   156  	fmt.Fprintf(os.Stderr, "warning: ")
   157  	fmt.Fprintf(os.Stderr, s, a...)
   158  	fmt.Fprintf(os.Stderr, "\n")
   159  	if (r.flags & PanicOnWarning) != 0 {
   160  		panic("unexpected warning")
   161  	}
   162  }
   163  
   164  func (r *CovDataReader) fatal(s string, a ...interface{}) error {
   165  	if r.err != nil {
   166  		return nil
   167  	}
   168  	errstr := "error: " + fmt.Sprintf(s, a...) + "\n"
   169  	if (r.flags & PanicOnError) != 0 {
   170  		fmt.Fprintf(os.Stderr, "%s", errstr)
   171  		panic("fatal error")
   172  	}
   173  	r.err = fmt.Errorf("%s", errstr)
   174  	return r.err
   175  }
   176  
   177  // visitPod examines a coverage data 'pod', that is, a meta-data file and
   178  // zero or more counter data files that refer to that meta-data file.
   179  func (r *CovDataReader) visitPod(p pods.Pod) error {
   180  	r.verb(1, "visiting pod: metafile %s with %d counter files",
   181  		p.MetaFile, len(p.CounterDataFiles))
   182  	r.vis.BeginPod(p)
   183  
   184  	// Open meta-file
   185  	f, err := os.Open(p.MetaFile)
   186  	if err != nil {
   187  		return r.fatal("unable to open meta-file %s", p.MetaFile)
   188  	}
   189  	defer f.Close()
   190  	br := bio.NewReader(f)
   191  	fi, err := f.Stat()
   192  	if err != nil {
   193  		return r.fatal("unable to stat metafile %s: %v", p.MetaFile, err)
   194  	}
   195  	fileView := br.SliceRO(uint64(fi.Size()))
   196  	br.MustSeek(0, io.SeekStart)
   197  
   198  	r.verb(1, "fileView for pod is length %d", len(fileView))
   199  
   200  	var mfr *decodemeta.CoverageMetaFileReader
   201  	mfr, err = decodemeta.NewCoverageMetaFileReader(f, fileView)
   202  	if err != nil {
   203  		return r.fatal("decoding meta-file %s: %s", p.MetaFile, err)
   204  	}
   205  	r.vis.VisitMetaDataFile(p.MetaFile, mfr)
   206  
   207  	// Read counter data files.
   208  	for k, cdf := range p.CounterDataFiles {
   209  		cf, err := os.Open(cdf)
   210  		if err != nil {
   211  			return r.fatal("opening counter data file %s: %s", cdf, err)
   212  		}
   213  		defer func(f *os.File) {
   214  			f.Close()
   215  		}(cf)
   216  		var mr *MReader
   217  		mr, err = NewMreader(cf)
   218  		if err != nil {
   219  			return r.fatal("creating reader for counter data file %s: %s", cdf, err)
   220  		}
   221  		var cdr *decodecounter.CounterDataReader
   222  		cdr, err = decodecounter.NewCounterDataReader(cdf, mr)
   223  		if err != nil {
   224  			return r.fatal("reading counter data file %s: %s", cdf, err)
   225  		}
   226  		r.vis.BeginCounterDataFile(cdf, cdr, p.Origins[k])
   227  		var data decodecounter.FuncPayload
   228  		for {
   229  			ok, err := cdr.NextFunc(&data)
   230  			if err != nil {
   231  				return r.fatal("reading counter data file %s: %v", cdf, err)
   232  			}
   233  			if !ok {
   234  				break
   235  			}
   236  			r.vis.VisitFuncCounterData(data)
   237  		}
   238  		r.vis.EndCounterDataFile(cdf, cdr, p.Origins[k])
   239  	}
   240  	r.vis.EndCounters()
   241  
   242  	// NB: packages in the meta-file will be in dependency order (basically
   243  	// the order in which init files execute). Do we want an additional sort
   244  	// pass here, say by packagepath?
   245  	np := uint32(mfr.NumPackages())
   246  	payload := []byte{}
   247  	for pkIdx := uint32(0); pkIdx < np; pkIdx++ {
   248  		var pd *decodemeta.CoverageMetaDataDecoder
   249  		pd, payload, err = mfr.GetPackageDecoder(pkIdx, payload)
   250  		if err != nil {
   251  			return r.fatal("reading pkg %d from meta-file %s: %s", pkIdx, p.MetaFile, err)
   252  		}
   253  		r.processPackage(p.MetaFile, pd, pkIdx)
   254  	}
   255  	r.vis.EndPod(p)
   256  
   257  	return nil
   258  }
   259  
   260  func (r *CovDataReader) processPackage(mfname string, pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) error {
   261  	if r.matchpkg != nil {
   262  		if !r.matchpkg(pd.PackagePath()) {
   263  			return nil
   264  		}
   265  	}
   266  	r.vis.BeginPackage(pd, pkgIdx)
   267  	nf := pd.NumFuncs()
   268  	var fd coverage.FuncDesc
   269  	for fidx := uint32(0); fidx < nf; fidx++ {
   270  		if err := pd.ReadFunc(fidx, &fd); err != nil {
   271  			return r.fatal("reading meta-data file %s: %v", mfname, err)
   272  		}
   273  		r.vis.VisitFunc(pkgIdx, fidx, &fd)
   274  	}
   275  	r.vis.EndPackage(pd, pkgIdx)
   276  	return nil
   277  }
   278  

View as plain text