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 5package cov 6 7import ( 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. 26type 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). 44func 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 85type 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 123type CovDataReaderFlags uint32 124 125const ( 126 CovDataReaderNoFlags CovDataReaderFlags = 0 127 PanicOnError = 1 << iota 128 PanicOnWarning 129) 130 131func (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 148func (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 155func (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 164func (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. 179func (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 processCounterDataFile := func(cdf string, k int) error { 208 cf, err := os.Open(cdf) 209 if err != nil { 210 return r.fatal("opening counter data file %s: %s", cdf, err) 211 } 212 defer cf.Close() 213 var mr *MReader 214 mr, err = NewMreader(cf) 215 if err != nil { 216 return r.fatal("creating reader for counter data file %s: %s", cdf, err) 217 } 218 var cdr *decodecounter.CounterDataReader 219 cdr, err = decodecounter.NewCounterDataReader(cdf, mr) 220 if err != nil { 221 return r.fatal("reading counter data file %s: %s", cdf, err) 222 } 223 r.vis.BeginCounterDataFile(cdf, cdr, p.Origins[k]) 224 var data decodecounter.FuncPayload 225 for { 226 ok, err := cdr.NextFunc(&data) 227 if err != nil { 228 return r.fatal("reading counter data file %s: %v", cdf, err) 229 } 230 if !ok { 231 break 232 } 233 r.vis.VisitFuncCounterData(data) 234 } 235 r.vis.EndCounterDataFile(cdf, cdr, p.Origins[k]) 236 return nil 237 } 238 239 // Read counter data files. 240 for k, cdf := range p.CounterDataFiles { 241 if err := processCounterDataFile(cdf, k); err != nil { 242 return err 243 } 244 } 245 r.vis.EndCounters() 246 247 // NB: packages in the meta-file will be in dependency order (basically 248 // the order in which init files execute). Do we want an additional sort 249 // pass here, say by packagepath? 250 np := uint32(mfr.NumPackages()) 251 payload := []byte{} 252 for pkIdx := uint32(0); pkIdx < np; pkIdx++ { 253 var pd *decodemeta.CoverageMetaDataDecoder 254 pd, payload, err = mfr.GetPackageDecoder(pkIdx, payload) 255 if err != nil { 256 return r.fatal("reading pkg %d from meta-file %s: %s", pkIdx, p.MetaFile, err) 257 } 258 r.processPackage(p.MetaFile, pd, pkIdx) 259 } 260 r.vis.EndPod(p) 261 262 return nil 263} 264 265func (r *CovDataReader) processPackage(mfname string, pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) error { 266 if r.matchpkg != nil { 267 if !r.matchpkg(pd.PackagePath()) { 268 return nil 269 } 270 } 271 r.vis.BeginPackage(pd, pkgIdx) 272 nf := pd.NumFuncs() 273 var fd coverage.FuncDesc 274 for fidx := uint32(0); fidx < nf; fidx++ { 275 if err := pd.ReadFunc(fidx, &fd); err != nil { 276 return r.fatal("reading meta-data file %s: %v", mfname, err) 277 } 278 r.vis.VisitFunc(pkgIdx, fidx, &fd) 279 } 280 r.vis.EndPackage(pd, pkgIdx) 281 return nil 282} 283