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 cfile 6 7import ( 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. 30func 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 124type 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. 132func (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 240type pkfunc struct { 241 pk, fcn uint32 242} 243 244func (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. 281func 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