github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/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 "fmt" 9 "internal/coverage" 10 "internal/coverage/calloc" 11 "internal/coverage/cformat" 12 "internal/coverage/cmerge" 13 "internal/coverage/decodecounter" 14 "internal/coverage/decodemeta" 15 "internal/coverage/pods" 16 "io" 17 "os" 18 "strings" 19 ) 20 21 // processCoverTestDir is called (via a linknamed reference) from 22 // testmain code when "go test -cover" is in effect. It is not 23 // intended to be used other than internally by the Go command's 24 // generated code. 25 func processCoverTestDir(dir string, cfile string, cm string, cpkg string) error { 26 return processCoverTestDirInternal(dir, cfile, cm, cpkg, os.Stdout) 27 } 28 29 // processCoverTestDirInternal is an io.Writer version of processCoverTestDir, 30 // exposed for unit testing. 31 func processCoverTestDirInternal(dir string, cfile string, cm string, cpkg string, w io.Writer) error { 32 cmode := coverage.ParseCounterMode(cm) 33 if cmode == coverage.CtrModeInvalid { 34 return fmt.Errorf("invalid counter mode %q", cm) 35 } 36 37 // Emit meta-data and counter data. 38 ml := getCovMetaList() 39 if len(ml) == 0 { 40 // This corresponds to the case where we have a package that 41 // contains test code but no functions (which is fine). In this 42 // case there is no need to emit anything. 43 } else { 44 if err := emitMetaDataToDirectory(dir, ml); err != nil { 45 return err 46 } 47 if err := emitCounterDataToDirectory(dir); err != nil { 48 return err 49 } 50 } 51 52 // Collect pods from test run. For the majority of cases we would 53 // expect to see a single pod here, but allow for multiple pods in 54 // case the test harness is doing extra work to collect data files 55 // from builds that it kicks off as part of the testing. 56 podlist, err := pods.CollectPods([]string{dir}, false) 57 if err != nil { 58 return fmt.Errorf("reading from %s: %v", dir, err) 59 } 60 61 // Open text output file if appropriate. 62 var tf *os.File 63 var tfClosed bool 64 if cfile != "" { 65 var err error 66 tf, err = os.Create(cfile) 67 if err != nil { 68 return fmt.Errorf("internal error: opening coverage data output file %q: %v", cfile, err) 69 } 70 defer func() { 71 if !tfClosed { 72 tfClosed = true 73 tf.Close() 74 } 75 }() 76 } 77 78 // Read/process the pods. 79 ts := &tstate{ 80 cm: &cmerge.Merger{}, 81 cf: cformat.NewFormatter(cmode), 82 cmode: cmode, 83 } 84 // Generate the expected hash string based on the final meta-data 85 // hash for this test, then look only for pods that refer to that 86 // hash (just in case there are multiple instrumented executables 87 // in play). See issue #57924 for more on this. 88 hashstring := fmt.Sprintf("%x", finalHash) 89 for _, p := range podlist { 90 if !strings.Contains(p.MetaFile, hashstring) { 91 continue 92 } 93 if err := ts.processPod(p); err != nil { 94 return err 95 } 96 } 97 98 // Emit percent. 99 if err := ts.cf.EmitPercent(w, cpkg, true); err != nil { 100 return err 101 } 102 103 // Emit text output. 104 if tf != nil { 105 if err := ts.cf.EmitTextual(tf); err != nil { 106 return err 107 } 108 tfClosed = true 109 if err := tf.Close(); err != nil { 110 return fmt.Errorf("closing %s: %v", cfile, err) 111 } 112 } 113 114 return nil 115 } 116 117 type tstate struct { 118 calloc.BatchCounterAlloc 119 cm *cmerge.Merger 120 cf *cformat.Formatter 121 cmode coverage.CounterMode 122 } 123 124 // processPod reads coverage counter data for a specific pod. 125 func (ts *tstate) processPod(p pods.Pod) error { 126 // Open meta-data file 127 f, err := os.Open(p.MetaFile) 128 if err != nil { 129 return fmt.Errorf("unable to open meta-data file %s: %v", p.MetaFile, err) 130 } 131 defer func() { 132 f.Close() 133 }() 134 var mfr *decodemeta.CoverageMetaFileReader 135 mfr, err = decodemeta.NewCoverageMetaFileReader(f, nil) 136 if err != nil { 137 return fmt.Errorf("error reading meta-data file %s: %v", p.MetaFile, err) 138 } 139 newmode := mfr.CounterMode() 140 if newmode != ts.cmode { 141 return fmt.Errorf("internal error: counter mode clash: %q from test harness, %q from data file %s", ts.cmode.String(), newmode.String(), p.MetaFile) 142 } 143 newgran := mfr.CounterGranularity() 144 if err := ts.cm.SetModeAndGranularity(p.MetaFile, cmode, newgran); err != nil { 145 return err 146 } 147 148 // A map to store counter data, indexed by pkgid/fnid tuple. 149 pmm := make(map[pkfunc][]uint32) 150 151 // Helper to read a single counter data file. 152 readcdf := func(cdf string) error { 153 cf, err := os.Open(cdf) 154 if err != nil { 155 return fmt.Errorf("opening counter data file %s: %s", cdf, err) 156 } 157 defer cf.Close() 158 var cdr *decodecounter.CounterDataReader 159 cdr, err = decodecounter.NewCounterDataReader(cdf, cf) 160 if err != nil { 161 return fmt.Errorf("reading counter data file %s: %s", cdf, err) 162 } 163 var data decodecounter.FuncPayload 164 for { 165 ok, err := cdr.NextFunc(&data) 166 if err != nil { 167 return fmt.Errorf("reading counter data file %s: %v", cdf, err) 168 } 169 if !ok { 170 break 171 } 172 173 // NB: sanity check on pkg and func IDs? 174 key := pkfunc{pk: data.PkgIdx, fcn: data.FuncIdx} 175 if prev, found := pmm[key]; found { 176 // Note: no overflow reporting here. 177 if err, _ := ts.cm.MergeCounters(data.Counters, prev); err != nil { 178 return fmt.Errorf("processing counter data file %s: %v", cdf, err) 179 } 180 } 181 c := ts.AllocateCounters(len(data.Counters)) 182 copy(c, data.Counters) 183 pmm[key] = c 184 } 185 return nil 186 } 187 188 // Read counter data files. 189 for _, cdf := range p.CounterDataFiles { 190 if err := readcdf(cdf); err != nil { 191 return err 192 } 193 } 194 195 // Visit meta-data file. 196 np := uint32(mfr.NumPackages()) 197 payload := []byte{} 198 for pkIdx := uint32(0); pkIdx < np; pkIdx++ { 199 var pd *decodemeta.CoverageMetaDataDecoder 200 pd, payload, err = mfr.GetPackageDecoder(pkIdx, payload) 201 if err != nil { 202 return fmt.Errorf("reading pkg %d from meta-file %s: %s", pkIdx, p.MetaFile, err) 203 } 204 ts.cf.SetPackage(pd.PackagePath()) 205 var fd coverage.FuncDesc 206 nf := pd.NumFuncs() 207 for fnIdx := uint32(0); fnIdx < nf; fnIdx++ { 208 if err := pd.ReadFunc(fnIdx, &fd); err != nil { 209 return fmt.Errorf("reading meta-data file %s: %v", 210 p.MetaFile, err) 211 } 212 key := pkfunc{pk: pkIdx, fcn: fnIdx} 213 counters, haveCounters := pmm[key] 214 for i := 0; i < len(fd.Units); i++ { 215 u := fd.Units[i] 216 // Skip units with non-zero parent (no way to represent 217 // these in the existing format). 218 if u.Parent != 0 { 219 continue 220 } 221 count := uint32(0) 222 if haveCounters { 223 count = counters[i] 224 } 225 ts.cf.AddUnit(fd.Srcfile, fd.Funcname, fd.Lit, u, count) 226 } 227 } 228 } 229 return nil 230 } 231 232 type pkfunc struct { 233 pk, fcn uint32 234 }