// Copyright 2022 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package cformat// This package provides apis for producing human-readable summaries// of coverage data (e.g. a coverage percentage for a given package or// set of packages) and for writing data in the legacy test format// emitted by "go test -coverprofile=<outfile>".//// The model for using these apis is to create a Formatter object,// then make a series of calls to SetPackage and AddUnit passing in// data read from coverage meta-data and counter-data files. E.g.//// myformatter := cformat.NewFormatter()// ...// for each package P in meta-data file: {// myformatter.SetPackage(P)// for each function F in P: {// for each coverable unit U in F: {// myformatter.AddUnit(U)// }// }// }// myformatter.EmitPercent(os.Stdout, nil, "", true, true)// myformatter.EmitTextual(somefile)//// These apis are linked into tests that are built with "-cover", and// called at the end of test execution to produce text output or// emit coverage percentages.import ()typeFormatterstruct {// Maps import path to package state. pm map[string]*pstate// Records current package being visited. pkg string// Pointer to current package state. p *pstate// Counter mode. cm coverage.CounterMode}// pstate records package-level coverage data state:// - a table of functions (file/fname/literal)// - a map recording the index/ID of each func encountered so far// - a table storing execution count for the coverable units in each functype pstate struct {// slice of unique functions funcs []fnfile// maps function to index in slice above (index acts as function ID) funcTable map[fnfile]uint32// A table storing coverage counts for each coverable unit. unitTable map[extcu]uint32}// extcu encapsulates a coverable unit within some function.type extcu struct { fnfid uint32// index into p.funcs slicecoverage.CoverableUnit}// fnfile is a function-name/file-name tuple.type fnfile struct { file string fname string lit bool}func ( coverage.CounterMode) *Formatter {return &Formatter{pm: make(map[string]*pstate),cm: , }}// SetPackage tells the formatter that we're about to visit the// coverage data for the package with the specified import path.// Note that it's OK to call SetPackage more than once with the// same import path; counter data values will be accumulated.func ( *Formatter) ( string) {if == .pkg {return } .pkg = , := .pm[]if ! { = new(pstate) .pm[] = .unitTable = make(map[extcu]uint32) .funcTable = make(map[fnfile]uint32) } .p = }// AddUnit passes info on a single coverable unit (file, funcname,// literal flag, range of lines, and counter value) to the formatter.// Counter values will be accumulated where appropriate.func ( *Formatter) ( string, string, bool, coverage.CoverableUnit, uint32) {if .p == nil {panic("AddUnit invoked before SetPackage") } := fnfile{file: , fname: , lit: } , := .p.funcTable[]if ! { = uint32(len(.p.funcs)) .p.funcs = append(.p.funcs, ) .p.funcTable[] = } := extcu{fnfid: , CoverableUnit: } := .p.unitTable[]varuint32if .cm == coverage.CtrModeSet {if != 0 || != 0 { = 1 } } else {// Use saturating arithmetic. , _ = cmerge.SaturatingAdd(, ) } .p.unitTable[] = }// sortUnits sorts a slice of extcu objects in a package according to// source position information (e.g. file and line). Note that we don't// include function name as part of the sorting criteria, the thinking// being that is better to provide things in the original source order.func ( *pstate) ( []extcu) {slices.SortFunc(, func(, extcu) int { := .funcs[.fnfid].file := .funcs[.fnfid].fileif := strings.Compare(, ); != 0 {return }// NB: not taking function literal flag into account here (no // need, since other fields are guaranteed to be distinct).if := cmp.Compare(.StLine, .StLine); != 0 {return }if := cmp.Compare(.EnLine, .EnLine); != 0 {return }if := cmp.Compare(.StCol, .StCol); != 0 {return }if := cmp.Compare(.EnCol, .EnCol); != 0 {return }returncmp.Compare(.NxStmts, .NxStmts) })}// EmitTextual writes the accumulated coverage data in the legacy// cmd/cover text format to the writer 'w'. We sort the data items by// importpath, source file, and line number before emitting (this sorting// is not explicitly mandated by the format, but seems like a good idea// for repeatable/deterministic dumps).func ( *Formatter) ( io.Writer) error {if .cm == coverage.CtrModeInvalid {panic("internal error, counter mode unset") }if , := fmt.Fprintf(, "mode: %s\n", .cm.String()); != nil {return } := make([]string, 0, len(.pm))for := range .pm { = append(, ) }slices.Sort()for , := range { := .pm[] := make([]extcu, 0, len(.unitTable))for := range .unitTable { = append(, ) } .sortUnits()for , := range { := .unitTable[] := .funcs[.fnfid].fileif , := fmt.Fprintf(, "%s:%d.%d,%d.%d %d %d\n", , .StLine, .StCol, .EnLine, .EnCol, .NxStmts, ); != nil {return } } }returnnil}// EmitPercent writes out a "percentage covered" string to the writer// 'w', selecting the set of packages in 'pkgs' and suffixing the// printed string with 'inpkgs'.func ( *Formatter) ( io.Writer, []string, string, bool, bool) error {iflen() == 0 { = make([]string, 0, len(.pm))for := range .pm { = append(, ) } } := func(, uint64) error {if != 0 {if , := fmt.Fprintf(, "coverage: %.1f%% of statements%s\n",100.0*float64()/float64(), ); != nil {return } } elseif {if , := fmt.Fprintf(, "coverage: [no statements]\n"); != nil {return } }returnnil }slices.Sort()var , uint64for , := range { := .pm[]if == nil {continue }if ! { , = 0, 0 }for , := range .unitTable { := uint64(.NxStmts) += if != 0 { += } }if ! {if , := fmt.Fprintf(, "\t%s\t\t", ); != nil {return }if := (, ); != nil {return } } }if {if := (, ); != nil {return } }returnnil}// EmitFuncs writes out a function-level summary to the writer 'w'. A// note on handling function literals: although we collect coverage// data for unnamed literals, it probably does not make sense to// include them in the function summary since there isn't any good way// to name them (this is also consistent with the legacy cmd/cover// implementation). We do want to include their counts in the overall// summary however.func ( *Formatter) ( io.Writer) error {if .cm == coverage.CtrModeInvalid {panic("internal error, counter mode unset") } := func(, uint64) float64 {if == 0 { = 1 }return100.0 * float64() / float64() } := tabwriter.NewWriter(, 1, 8, 1, '\t', 0)defer .Flush() := uint64(0) := uint64(0) := make([]string, 0, len(.pm))for := range .pm { = append(, ) }slices.Sort()// Emit functions for each package, sorted by import path.for , := range { := .pm[]iflen(.unitTable) == 0 {continue } := make([]extcu, 0, len(.unitTable))for := range .unitTable { = append(, ) }// Within a package, sort the units, then walk through the // sorted array. Each time we hit a new function, emit the // summary entry for the previous function, then make one last // emit call at the end of the loop. .sortUnits() := "" := "" := falsevaruint32var , uint64 := func( extcu) { = .funcs[.fnfid].fname = .funcs[.fnfid].file = .funcs[.fnfid].lit = .StLine } := func( extcu) error {// Don't emit entries for function literals (see discussion // in function header comment above).if ! {if , := fmt.Fprintf(, "%s:%d:\t%s\t%.1f%%\n", , , , (, )); != nil {return } } () += += = 0 = 0returnnil }for , := range {if == 0 { () } else {if != .funcs[.fnfid].fname {// New function; emit entry for previous one.if := (); != nil {return } } } += uint64(.NxStmts) := .unitTable[]if != 0 { += uint64(.NxStmts) } }if := (extcu{}); != nil {return } }if , := fmt.Fprintf(, "%s\t%s\t%.1f%%\n","total", "(statements)", (, )); != nil {return }returnnil}
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.