// 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 cov import ( "cmd/internal/bio" "fmt" "internal/coverage" "internal/coverage/decodecounter" "internal/coverage/decodemeta" "internal/coverage/pods" "io" "os" ) // CovDataReader is a general-purpose helper/visitor object for // reading coverage data files in a structured way. Clients create a // CovDataReader to process a given collection of coverage data file // directories, then pass in a visitor object with methods that get // invoked at various important points. CovDataReader is intended // to facilitate common coverage data file operations such as // merging or intersecting data files, analyzing data files, or // dumping data files. type CovDataReader struct { vis CovDataVisitor indirs []string matchpkg func(name string) bool flags CovDataReaderFlags err error verbosityLevel int } // MakeCovDataReader creates a CovDataReader object to process the // given set of input directories. Here 'vis' is a visitor object // providing methods to be invoked as we walk through the data, // 'indirs' is the set of coverage data directories to examine, // 'verbosityLevel' controls the level of debugging trace messages // (zero for off, higher for more output), 'flags' stores flags that // indicate what to do if errors are detected, and 'matchpkg' is a // caller-provided function that can be used to select specific // packages by name (if nil, then all packages are included). func MakeCovDataReader(vis CovDataVisitor, indirs []string, verbosityLevel int, flags CovDataReaderFlags, matchpkg func(name string) bool) *CovDataReader { return &CovDataReader{ vis: vis, indirs: indirs, matchpkg: matchpkg, verbosityLevel: verbosityLevel, flags: flags, } } // CovDataVisitor defines hooks for clients of CovDataReader. When the // coverage data reader makes its way through a coverage meta-data // file and counter data files, it will invoke the methods below to // hand off info to the client. The normal sequence of expected // visitor method invocations is: // // for each pod P { // BeginPod(p) // let MF be the meta-data file for P // VisitMetaDataFile(MF) // for each counter data file D in P { // BeginCounterDataFile(D) // for each live function F in D { // VisitFuncCounterData(F) // } // EndCounterDataFile(D) // } // EndCounters(MF) // for each package PK in MF { // BeginPackage(PK) // if { // for each function PF in PK { // VisitFunc(PF) // } // } // EndPackage(PK) // } // EndPod(p) // } // Finish() type CovDataVisitor interface { // Invoked at the start and end of a given pod (a pod here is a // specific coverage meta-data files with the counter data files // that correspond to it). BeginPod(p pods.Pod) EndPod(p pods.Pod) // Invoked when the reader is starting to examine the meta-data // file for a pod. Here 'mdf' is the path of the file, and 'mfr' // is an open meta-data reader. VisitMetaDataFile(mdf string, mfr *decodemeta.CoverageMetaFileReader) // Invoked when the reader processes a counter data file, first // the 'begin' method at the start, then the 'end' method when // we're done with the file. BeginCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int) EndCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int) // Invoked once for each live function in the counter data file. VisitFuncCounterData(payload decodecounter.FuncPayload) // Invoked when we've finished processing the counter files in a // POD (e.g. no more calls to VisitFuncCounterData). EndCounters() // Invoked for each package in the meta-data file for the pod, // first the 'begin' method when processing of the package starts, // then the 'end' method when we're done BeginPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) EndPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) // Invoked for each function the package being visited. VisitFunc(pkgIdx uint32, fnIdx uint32, fd *coverage.FuncDesc) // Invoked when all counter + meta-data file processing is complete. Finish() } type CovDataReaderFlags uint32 const ( CovDataReaderNoFlags CovDataReaderFlags = 0 PanicOnError = 1 << iota PanicOnWarning ) func (r *CovDataReader) Visit() error { podlist, err := pods.CollectPods(r.indirs, false) if err != nil { return fmt.Errorf("reading inputs: %v", err) } if len(podlist) == 0 { r.warn("no applicable files found in input directories") } for _, p := range podlist { if err := r.visitPod(p); err != nil { return err } } r.vis.Finish() return nil } func (r *CovDataReader) verb(vlevel int, s string, a ...interface{}) { if r.verbosityLevel >= vlevel { fmt.Fprintf(os.Stderr, s, a...) fmt.Fprintf(os.Stderr, "\n") } } func (r *CovDataReader) warn(s string, a ...interface{}) { fmt.Fprintf(os.Stderr, "warning: ") fmt.Fprintf(os.Stderr, s, a...) fmt.Fprintf(os.Stderr, "\n") if (r.flags & PanicOnWarning) != 0 { panic("unexpected warning") } } func (r *CovDataReader) fatal(s string, a ...interface{}) error { if r.err != nil { return nil } errstr := "error: " + fmt.Sprintf(s, a...) + "\n" if (r.flags & PanicOnError) != 0 { fmt.Fprintf(os.Stderr, "%s", errstr) panic("fatal error") } r.err = fmt.Errorf("%s", errstr) return r.err } // visitPod examines a coverage data 'pod', that is, a meta-data file and // zero or more counter data files that refer to that meta-data file. func (r *CovDataReader) visitPod(p pods.Pod) error { r.verb(1, "visiting pod: metafile %s with %d counter files", p.MetaFile, len(p.CounterDataFiles)) r.vis.BeginPod(p) // Open meta-file f, err := os.Open(p.MetaFile) if err != nil { return r.fatal("unable to open meta-file %s", p.MetaFile) } defer f.Close() br := bio.NewReader(f) fi, err := f.Stat() if err != nil { return r.fatal("unable to stat metafile %s: %v", p.MetaFile, err) } fileView := br.SliceRO(uint64(fi.Size())) br.MustSeek(0, io.SeekStart) r.verb(1, "fileView for pod is length %d", len(fileView)) var mfr *decodemeta.CoverageMetaFileReader mfr, err = decodemeta.NewCoverageMetaFileReader(f, fileView) if err != nil { return r.fatal("decoding meta-file %s: %s", p.MetaFile, err) } r.vis.VisitMetaDataFile(p.MetaFile, mfr) // Read counter data files. for k, cdf := range p.CounterDataFiles { cf, err := os.Open(cdf) if err != nil { return r.fatal("opening counter data file %s: %s", cdf, err) } defer func(f *os.File) { f.Close() }(cf) var mr *MReader mr, err = NewMreader(cf) if err != nil { return r.fatal("creating reader for counter data file %s: %s", cdf, err) } var cdr *decodecounter.CounterDataReader cdr, err = decodecounter.NewCounterDataReader(cdf, mr) if err != nil { return r.fatal("reading counter data file %s: %s", cdf, err) } r.vis.BeginCounterDataFile(cdf, cdr, p.Origins[k]) var data decodecounter.FuncPayload for { ok, err := cdr.NextFunc(&data) if err != nil { return r.fatal("reading counter data file %s: %v", cdf, err) } if !ok { break } r.vis.VisitFuncCounterData(data) } r.vis.EndCounterDataFile(cdf, cdr, p.Origins[k]) } r.vis.EndCounters() // NB: packages in the meta-file will be in dependency order (basically // the order in which init files execute). Do we want an additional sort // pass here, say by packagepath? 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 r.fatal("reading pkg %d from meta-file %s: %s", pkIdx, p.MetaFile, err) } r.processPackage(p.MetaFile, pd, pkIdx) } r.vis.EndPod(p) return nil } func (r *CovDataReader) processPackage(mfname string, pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) error { if r.matchpkg != nil { if !r.matchpkg(pd.PackagePath()) { return nil } } r.vis.BeginPackage(pd, pkgIdx) nf := pd.NumFuncs() var fd coverage.FuncDesc for fidx := uint32(0); fidx < nf; fidx++ { if err := pd.ReadFunc(fidx, &fd); err != nil { return r.fatal("reading meta-data file %s: %v", mfname, err) } r.vis.VisitFunc(pkgIdx, fidx, &fd) } r.vis.EndPackage(pd, pkgIdx) return nil }