github.com/bir3/gocompiler@v0.9.2202/src/internal/coverage/cformat/format.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 cformat 6 7 // This package provides apis for producing human-readable summaries 8 // of coverage data (e.g. a coverage percentage for a given package or 9 // set of packages) and for writing data in the legacy test format 10 // emitted by "go test -coverprofile=<outfile>". 11 // 12 // The model for using these apis is to create a Formatter object, 13 // then make a series of calls to SetPackage and AddUnit passing in 14 // data read from coverage meta-data and counter-data files. E.g. 15 // 16 // myformatter := cformat.NewFormatter() 17 // ... 18 // for each package P in meta-data file: { 19 // myformatter.SetPackage(P) 20 // for each function F in P: { 21 // for each coverable unit U in F: { 22 // myformatter.AddUnit(U) 23 // } 24 // } 25 // } 26 // myformatter.EmitPercent(os.Stdout, "", true, true) 27 // myformatter.EmitTextual(somefile) 28 // 29 // These apis are linked into tests that are built with "-cover", and 30 // called at the end of test execution to produce text output or 31 // emit coverage percentages. 32 33 import ( 34 "fmt" 35 "github.com/bir3/gocompiler/src/internal/coverage" 36 "github.com/bir3/gocompiler/src/internal/coverage/cmerge" 37 "io" 38 "sort" 39 "text/tabwriter" 40 ) 41 42 type Formatter struct { 43 // Maps import path to package state. 44 pm map[string]*pstate 45 // Records current package being visited. 46 pkg string 47 // Pointer to current package state. 48 p *pstate 49 // Counter mode. 50 cm coverage.CounterMode 51 } 52 53 // pstate records package-level coverage data state: 54 // - a table of functions (file/fname/literal) 55 // - a map recording the index/ID of each func encountered so far 56 // - a table storing execution count for the coverable units in each func 57 type pstate struct { 58 // slice of unique functions 59 funcs []fnfile 60 // maps function to index in slice above (index acts as function ID) 61 funcTable map[fnfile]uint32 62 63 // A table storing coverage counts for each coverable unit. 64 unitTable map[extcu]uint32 65 } 66 67 // extcu encapsulates a coverable unit within some function. 68 type extcu struct { 69 fnfid uint32 // index into p.funcs slice 70 coverage.CoverableUnit 71 } 72 73 // fnfile is a function-name/file-name tuple. 74 type fnfile struct { 75 file string 76 fname string 77 lit bool 78 } 79 80 func NewFormatter(cm coverage.CounterMode) *Formatter { 81 return &Formatter{ 82 pm: make(map[string]*pstate), 83 cm: cm, 84 } 85 } 86 87 // SetPackage tells the formatter that we're about to visit the 88 // coverage data for the package with the specified import path. 89 // Note that it's OK to call SetPackage more than once with the 90 // same import path; counter data values will be accumulated. 91 func (fm *Formatter) SetPackage(importpath string) { 92 if importpath == fm.pkg { 93 return 94 } 95 fm.pkg = importpath 96 ps, ok := fm.pm[importpath] 97 if !ok { 98 ps = new(pstate) 99 fm.pm[importpath] = ps 100 ps.unitTable = make(map[extcu]uint32) 101 ps.funcTable = make(map[fnfile]uint32) 102 } 103 fm.p = ps 104 } 105 106 // AddUnit passes info on a single coverable unit (file, funcname, 107 // literal flag, range of lines, and counter value) to the formatter. 108 // Counter values will be accumulated where appropriate. 109 func (fm *Formatter) AddUnit(file string, fname string, isfnlit bool, unit coverage.CoverableUnit, count uint32) { 110 if fm.p == nil { 111 panic("AddUnit invoked before SetPackage") 112 } 113 fkey := fnfile{file: file, fname: fname, lit: isfnlit} 114 idx, ok := fm.p.funcTable[fkey] 115 if !ok { 116 idx = uint32(len(fm.p.funcs)) 117 fm.p.funcs = append(fm.p.funcs, fkey) 118 fm.p.funcTable[fkey] = idx 119 } 120 ukey := extcu{fnfid: idx, CoverableUnit: unit} 121 pcount := fm.p.unitTable[ukey] 122 var result uint32 123 if fm.cm == coverage.CtrModeSet { 124 if count != 0 || pcount != 0 { 125 result = 1 126 } 127 } else { 128 // Use saturating arithmetic. 129 result, _ = cmerge.SaturatingAdd(pcount, count) 130 } 131 fm.p.unitTable[ukey] = result 132 } 133 134 // sortUnits sorts a slice of extcu objects in a package according to 135 // source position information (e.g. file and line). Note that we don't 136 // include function name as part of the sorting criteria, the thinking 137 // being that is better to provide things in the original source order. 138 func (p *pstate) sortUnits(units []extcu) { 139 sort.Slice(units, func(i, j int) bool { 140 ui := units[i] 141 uj := units[j] 142 ifile := p.funcs[ui.fnfid].file 143 jfile := p.funcs[uj.fnfid].file 144 if ifile != jfile { 145 return ifile < jfile 146 } 147 // NB: not taking function literal flag into account here (no 148 // need, since other fields are guaranteed to be distinct). 149 if units[i].StLine != units[j].StLine { 150 return units[i].StLine < units[j].StLine 151 } 152 if units[i].EnLine != units[j].EnLine { 153 return units[i].EnLine < units[j].EnLine 154 } 155 if units[i].StCol != units[j].StCol { 156 return units[i].StCol < units[j].StCol 157 } 158 if units[i].EnCol != units[j].EnCol { 159 return units[i].EnCol < units[j].EnCol 160 } 161 return units[i].NxStmts < units[j].NxStmts 162 }) 163 } 164 165 // EmitTextual writes the accumulated coverage data in the legacy 166 // cmd/cover text format to the writer 'w'. We sort the data items by 167 // importpath, source file, and line number before emitting (this sorting 168 // is not explicitly mandated by the format, but seems like a good idea 169 // for repeatable/deterministic dumps). 170 func (fm *Formatter) EmitTextual(w io.Writer) error { 171 if fm.cm == coverage.CtrModeInvalid { 172 panic("internal error, counter mode unset") 173 } 174 if _, err := fmt.Fprintf(w, "mode: %s\n", fm.cm.String()); err != nil { 175 return err 176 } 177 pkgs := make([]string, 0, len(fm.pm)) 178 for importpath := range fm.pm { 179 pkgs = append(pkgs, importpath) 180 } 181 sort.Strings(pkgs) 182 for _, importpath := range pkgs { 183 p := fm.pm[importpath] 184 units := make([]extcu, 0, len(p.unitTable)) 185 for u := range p.unitTable { 186 units = append(units, u) 187 } 188 p.sortUnits(units) 189 for _, u := range units { 190 count := p.unitTable[u] 191 file := p.funcs[u.fnfid].file 192 if _, err := fmt.Fprintf(w, "%s:%d.%d,%d.%d %d %d\n", 193 file, u.StLine, u.StCol, 194 u.EnLine, u.EnCol, u.NxStmts, count); err != nil { 195 return err 196 } 197 } 198 } 199 return nil 200 } 201 202 // EmitPercent writes out a "percentage covered" string to the writer 'w'. 203 func (fm *Formatter) EmitPercent(w io.Writer, covpkgs string, noteEmpty bool, aggregate bool) error { 204 pkgs := make([]string, 0, len(fm.pm)) 205 for importpath := range fm.pm { 206 pkgs = append(pkgs, importpath) 207 } 208 209 rep := func(cov, tot uint64) error { 210 if tot != 0 { 211 if _, err := fmt.Fprintf(w, "coverage: %.1f%% of statements%s\n", 212 100.0*float64(cov)/float64(tot), covpkgs); err != nil { 213 return err 214 } 215 } else if noteEmpty { 216 if _, err := fmt.Fprintf(w, "coverage: [no statements]\n"); err != nil { 217 return err 218 } 219 } 220 return nil 221 } 222 223 sort.Strings(pkgs) 224 var totalStmts, coveredStmts uint64 225 for _, importpath := range pkgs { 226 p := fm.pm[importpath] 227 if !aggregate { 228 totalStmts, coveredStmts = 0, 0 229 } 230 for unit, count := range p.unitTable { 231 nx := uint64(unit.NxStmts) 232 totalStmts += nx 233 if count != 0 { 234 coveredStmts += nx 235 } 236 } 237 if !aggregate { 238 if _, err := fmt.Fprintf(w, "\t%s\t\t", importpath); err != nil { 239 return err 240 } 241 if err := rep(coveredStmts, totalStmts); err != nil { 242 return err 243 } 244 } 245 } 246 if aggregate { 247 if err := rep(coveredStmts, totalStmts); err != nil { 248 return err 249 } 250 } 251 252 return nil 253 } 254 255 // EmitFuncs writes out a function-level summary to the writer 'w'. A 256 // note on handling function literals: although we collect coverage 257 // data for unnamed literals, it probably does not make sense to 258 // include them in the function summary since there isn't any good way 259 // to name them (this is also consistent with the legacy cmd/cover 260 // implementation). We do want to include their counts in the overall 261 // summary however. 262 func (fm *Formatter) EmitFuncs(w io.Writer) error { 263 if fm.cm == coverage.CtrModeInvalid { 264 panic("internal error, counter mode unset") 265 } 266 perc := func(covered, total uint64) float64 { 267 if total == 0 { 268 total = 1 269 } 270 return 100.0 * float64(covered) / float64(total) 271 } 272 tabber := tabwriter.NewWriter(w, 1, 8, 1, '\t', 0) 273 defer tabber.Flush() 274 allStmts := uint64(0) 275 covStmts := uint64(0) 276 277 pkgs := make([]string, 0, len(fm.pm)) 278 for importpath := range fm.pm { 279 pkgs = append(pkgs, importpath) 280 } 281 sort.Strings(pkgs) 282 283 // Emit functions for each package, sorted by import path. 284 for _, importpath := range pkgs { 285 p := fm.pm[importpath] 286 if len(p.unitTable) == 0 { 287 continue 288 } 289 units := make([]extcu, 0, len(p.unitTable)) 290 for u := range p.unitTable { 291 units = append(units, u) 292 } 293 294 // Within a package, sort the units, then walk through the 295 // sorted array. Each time we hit a new function, emit the 296 // summary entry for the previous function, then make one last 297 // emit call at the end of the loop. 298 p.sortUnits(units) 299 fname := "" 300 ffile := "" 301 flit := false 302 var fline uint32 303 var cstmts, tstmts uint64 304 captureFuncStart := func(u extcu) { 305 fname = p.funcs[u.fnfid].fname 306 ffile = p.funcs[u.fnfid].file 307 flit = p.funcs[u.fnfid].lit 308 fline = u.StLine 309 } 310 emitFunc := func(u extcu) error { 311 // Don't emit entries for function literals (see discussion 312 // in function header comment above). 313 if !flit { 314 if _, err := fmt.Fprintf(tabber, "%s:%d:\t%s\t%.1f%%\n", 315 ffile, fline, fname, perc(cstmts, tstmts)); err != nil { 316 return err 317 } 318 } 319 captureFuncStart(u) 320 allStmts += tstmts 321 covStmts += cstmts 322 tstmts = 0 323 cstmts = 0 324 return nil 325 } 326 for k, u := range units { 327 if k == 0 { 328 captureFuncStart(u) 329 } else { 330 if fname != p.funcs[u.fnfid].fname { 331 // New function; emit entry for previous one. 332 if err := emitFunc(u); err != nil { 333 return err 334 } 335 } 336 } 337 tstmts += uint64(u.NxStmts) 338 count := p.unitTable[u] 339 if count != 0 { 340 cstmts += uint64(u.NxStmts) 341 } 342 } 343 if err := emitFunc(extcu{}); err != nil { 344 return err 345 } 346 } 347 if _, err := fmt.Fprintf(tabber, "%s\t%s\t%.1f%%\n", 348 "total", "(statements)", perc(covStmts, allStmts)); err != nil { 349 return err 350 } 351 return nil 352 }