github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/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 "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 "io" 18 "os" 19 "path/filepath" 20 "runtime/internal/atomic" 21 "strings" 22 "unsafe" 23 ) 24 25 // processCoverTestDir is called (via a linknamed reference) from 26 // testmain code when "go test -cover" is in effect. It is not 27 // intended to be used other than internally by the Go command's 28 // generated code. 29 func processCoverTestDir(dir string, cfile string, cm string, cpkg string) error { 30 return processCoverTestDirInternal(dir, cfile, cm, cpkg, os.Stdout) 31 } 32 33 // processCoverTestDirInternal is an io.Writer version of processCoverTestDir, 34 // exposed for unit testing. 35 func processCoverTestDirInternal(dir string, cfile string, cm string, cpkg string, w io.Writer) error { 36 cmode := coverage.ParseCounterMode(cm) 37 if cmode == coverage.CtrModeInvalid { 38 return fmt.Errorf("invalid counter mode %q", cm) 39 } 40 41 // Emit meta-data and counter data. 42 ml := getCovMetaList() 43 if len(ml) == 0 { 44 // This corresponds to the case where we have a package that 45 // contains test code but no functions (which is fine). In this 46 // case there is no need to emit anything. 47 } else { 48 if err := emitMetaDataToDirectory(dir, ml); err != nil { 49 return err 50 } 51 if err := emitCounterDataToDirectory(dir); err != nil { 52 return err 53 } 54 } 55 56 // Collect pods from test run. For the majority of cases we would 57 // expect to see a single pod here, but allow for multiple pods in 58 // case the test harness is doing extra work to collect data files 59 // from builds that it kicks off as part of the testing. 60 podlist, err := pods.CollectPods([]string{dir}, false) 61 if err != nil { 62 return fmt.Errorf("reading from %s: %v", dir, err) 63 } 64 65 // Open text output file if appropriate. 66 var tf *os.File 67 var tfClosed bool 68 if cfile != "" { 69 var err error 70 tf, err = os.Create(cfile) 71 if err != nil { 72 return fmt.Errorf("internal error: opening coverage data output file %q: %v", cfile, err) 73 } 74 defer func() { 75 if !tfClosed { 76 tfClosed = true 77 tf.Close() 78 } 79 }() 80 } 81 82 // Read/process the pods. 83 ts := &tstate{ 84 cm: &cmerge.Merger{}, 85 cf: cformat.NewFormatter(cmode), 86 cmode: cmode, 87 } 88 // Generate the expected hash string based on the final meta-data 89 // hash for this test, then look only for pods that refer to that 90 // hash (just in case there are multiple instrumented executables 91 // in play). See issue #57924 for more on this. 92 hashstring := fmt.Sprintf("%x", finalHash) 93 importpaths := make(map[string]struct{}) 94 for _, p := range podlist { 95 if !strings.Contains(p.MetaFile, hashstring) { 96 continue 97 } 98 if err := ts.processPod(p, importpaths); err != nil { 99 return err 100 } 101 } 102 103 metafilespath := filepath.Join(dir, coverage.MetaFilesFileName) 104 if _, err := os.Stat(metafilespath); err == nil { 105 if err := ts.readAuxMetaFiles(metafilespath, importpaths); err != nil { 106 return err 107 } 108 } 109 110 // Emit percent. 111 if err := ts.cf.EmitPercent(w, cpkg, true, true); err != nil { 112 return err 113 } 114 115 // Emit text output. 116 if tf != nil { 117 if err := ts.cf.EmitTextual(tf); err != nil { 118 return err 119 } 120 tfClosed = true 121 if err := tf.Close(); err != nil { 122 return fmt.Errorf("closing %s: %v", cfile, err) 123 } 124 } 125 126 return nil 127 } 128 129 type tstate struct { 130 calloc.BatchCounterAlloc 131 cm *cmerge.Merger 132 cf *cformat.Formatter 133 cmode coverage.CounterMode 134 } 135 136 // processPod reads coverage counter data for a specific pod. 137 func (ts *tstate) processPod(p pods.Pod, importpaths map[string]struct{}) error { 138 // Open meta-data file 139 f, err := os.Open(p.MetaFile) 140 if err != nil { 141 return fmt.Errorf("unable to open meta-data file %s: %v", p.MetaFile, err) 142 } 143 defer func() { 144 f.Close() 145 }() 146 var mfr *decodemeta.CoverageMetaFileReader 147 mfr, err = decodemeta.NewCoverageMetaFileReader(f, nil) 148 if err != nil { 149 return fmt.Errorf("error reading meta-data file %s: %v", p.MetaFile, err) 150 } 151 newmode := mfr.CounterMode() 152 if newmode != ts.cmode { 153 return fmt.Errorf("internal error: counter mode clash: %q from test harness, %q from data file %s", ts.cmode.String(), newmode.String(), p.MetaFile) 154 } 155 newgran := mfr.CounterGranularity() 156 if err := ts.cm.SetModeAndGranularity(p.MetaFile, cmode, newgran); err != nil { 157 return err 158 } 159 160 // A map to store counter data, indexed by pkgid/fnid tuple. 161 pmm := make(map[pkfunc][]uint32) 162 163 // Helper to read a single counter data file. 164 readcdf := func(cdf string) error { 165 cf, err := os.Open(cdf) 166 if err != nil { 167 return fmt.Errorf("opening counter data file %s: %s", cdf, err) 168 } 169 defer cf.Close() 170 var cdr *decodecounter.CounterDataReader 171 cdr, err = decodecounter.NewCounterDataReader(cdf, cf) 172 if err != nil { 173 return fmt.Errorf("reading counter data file %s: %s", cdf, err) 174 } 175 var data decodecounter.FuncPayload 176 for { 177 ok, err := cdr.NextFunc(&data) 178 if err != nil { 179 return fmt.Errorf("reading counter data file %s: %v", cdf, err) 180 } 181 if !ok { 182 break 183 } 184 185 // NB: sanity check on pkg and func IDs? 186 key := pkfunc{pk: data.PkgIdx, fcn: data.FuncIdx} 187 if prev, found := pmm[key]; found { 188 // Note: no overflow reporting here. 189 if err, _ := ts.cm.MergeCounters(data.Counters, prev); err != nil { 190 return fmt.Errorf("processing counter data file %s: %v", cdf, err) 191 } 192 } 193 c := ts.AllocateCounters(len(data.Counters)) 194 copy(c, data.Counters) 195 pmm[key] = c 196 } 197 return nil 198 } 199 200 // Read counter data files. 201 for _, cdf := range p.CounterDataFiles { 202 if err := readcdf(cdf); err != nil { 203 return err 204 } 205 } 206 207 // Visit meta-data file. 208 np := uint32(mfr.NumPackages()) 209 payload := []byte{} 210 for pkIdx := uint32(0); pkIdx < np; pkIdx++ { 211 var pd *decodemeta.CoverageMetaDataDecoder 212 pd, payload, err = mfr.GetPackageDecoder(pkIdx, payload) 213 if err != nil { 214 return fmt.Errorf("reading pkg %d from meta-file %s: %s", pkIdx, p.MetaFile, err) 215 } 216 ts.cf.SetPackage(pd.PackagePath()) 217 importpaths[pd.PackagePath()] = struct{}{} 218 var fd coverage.FuncDesc 219 nf := pd.NumFuncs() 220 for fnIdx := uint32(0); fnIdx < nf; fnIdx++ { 221 if err := pd.ReadFunc(fnIdx, &fd); err != nil { 222 return fmt.Errorf("reading meta-data file %s: %v", 223 p.MetaFile, err) 224 } 225 key := pkfunc{pk: pkIdx, fcn: fnIdx} 226 counters, haveCounters := pmm[key] 227 for i := 0; i < len(fd.Units); i++ { 228 u := fd.Units[i] 229 // Skip units with non-zero parent (no way to represent 230 // these in the existing format). 231 if u.Parent != 0 { 232 continue 233 } 234 count := uint32(0) 235 if haveCounters { 236 count = counters[i] 237 } 238 ts.cf.AddUnit(fd.Srcfile, fd.Funcname, fd.Lit, u, count) 239 } 240 } 241 } 242 return nil 243 } 244 245 type pkfunc struct { 246 pk, fcn uint32 247 } 248 249 func (ts *tstate) readAuxMetaFiles(metafiles string, importpaths map[string]struct{}) error { 250 // Unmarshall the information on available aux metafiles into 251 // a MetaFileCollection struct. 252 var mfc coverage.MetaFileCollection 253 data, err := os.ReadFile(metafiles) 254 if err != nil { 255 return fmt.Errorf("error reading auxmetafiles file %q: %v", metafiles, err) 256 } 257 if err := json.Unmarshal(data, &mfc); err != nil { 258 return fmt.Errorf("error reading auxmetafiles file %q: %v", metafiles, err) 259 } 260 261 // Walk through each available aux meta-file. If we've already 262 // seen the package path in question during the walk of the 263 // "regular" meta-data file, then we can skip the package, 264 // otherwise construct a dummy pod with the single meta-data file 265 // (no counters) and invoke processPod on it. 266 for i := range mfc.ImportPaths { 267 p := mfc.ImportPaths[i] 268 if _, ok := importpaths[p]; ok { 269 continue 270 } 271 var pod pods.Pod 272 pod.MetaFile = mfc.MetaFileFragments[i] 273 if err := ts.processPod(pod, importpaths); err != nil { 274 return err 275 } 276 } 277 return nil 278 } 279 280 // snapshot returns a snapshot of coverage percentage at a moment of 281 // time within a running test, so as to support the testing.Coverage() 282 // function. This version doesn't examine coverage meta-data, so the 283 // result it returns will be less accurate (more "slop") due to the 284 // fact that we don't look at the meta data to see how many statements 285 // are associated with each counter. 286 func snapshot() float64 { 287 cl := getCovCounterList() 288 if len(cl) == 0 { 289 // no work to do here. 290 return 0.0 291 } 292 293 tot := uint64(0) 294 totExec := uint64(0) 295 for _, c := range cl { 296 sd := unsafe.Slice((*atomic.Uint32)(unsafe.Pointer(c.Counters)), c.Len) 297 tot += uint64(len(sd)) 298 for i := 0; i < len(sd); i++ { 299 // Skip ahead until the next non-zero value. 300 if sd[i].Load() == 0 { 301 continue 302 } 303 // We found a function that was executed. 304 nCtrs := sd[i+coverage.NumCtrsOffset].Load() 305 cst := i + coverage.FirstCtrOffset 306 307 if cst+int(nCtrs) > len(sd) { 308 break 309 } 310 counters := sd[cst : cst+int(nCtrs)] 311 for i := range counters { 312 if counters[i].Load() != 0 { 313 totExec++ 314 } 315 } 316 i += coverage.FirstCtrOffset + int(nCtrs) - 1 317 } 318 } 319 if tot == 0 { 320 return 0.0 321 } 322 return float64(totExec) / float64(tot) 323 }