// 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 main
// This file contains functions and apis to support the "subtract" and
// "intersect" subcommands of "go tool covdata".
import (
"flag"
"fmt"
"internal/coverage"
"internal/coverage/decodecounter"
"internal/coverage/decodemeta"
"internal/coverage/pods"
"os"
"strings"
)
// makeSubtractIntersectOp creates a subtract or intersect operation.
// 'mode' here must be either "subtract" or "intersect".
func makeSubtractIntersectOp(mode string) covOperation {
outdirflag = flag.String("o", "", "Output directory to write")
s := &sstate{
mode: mode,
mm: newMetaMerge(),
inidx: -1,
}
return s
}
// sstate holds state needed to implement subtraction and intersection
// operations on code coverage data files. This type provides methods
// to implement the CovDataVisitor interface, and is designed to be
// used in concert with the CovDataReader utility, which abstracts
// away most of the grubby details of reading coverage data files.
type sstate struct {
mm *metaMerge
inidx int
mode string
// Used only for intersection; keyed by pkg/fn ID, it keeps track of
// just the set of functions for which we have data in the current
// input directory.
imm map[pkfunc]struct{}
}
func (s *sstate) Usage(msg string) {
if len(msg) > 0 {
fmt.Fprintf(os.Stderr, "error: %s\n", msg)
}
fmt.Fprintf(os.Stderr, "usage: go tool covdata %s -i=dir1,dir2 -o=
\n\n", s.mode)
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\nExamples:\n\n")
op := "from"
if s.mode == intersectMode {
op = "with"
}
fmt.Fprintf(os.Stderr, " go tool covdata %s -i=dir1,dir2 -o=outdir\n\n", s.mode)
fmt.Fprintf(os.Stderr, " \t%ss dir2 %s dir1, writing result\n", s.mode, op)
fmt.Fprintf(os.Stderr, " \tinto output dir outdir.\n")
os.Exit(2)
}
func (s *sstate) Setup() {
if *indirsflag == "" {
usage("select input directories with '-i' option")
}
indirs := strings.Split(*indirsflag, ",")
if s.mode == subtractMode && len(indirs) != 2 {
usage("supply exactly two input dirs for subtract operation")
}
if *outdirflag == "" {
usage("select output directory with '-o' option")
}
}
func (s *sstate) BeginPod(p pods.Pod) {
s.mm.beginPod()
}
func (s *sstate) EndPod(p pods.Pod) {
const pcombine = false
s.mm.endPod(pcombine)
}
func (s *sstate) EndCounters() {
if s.imm != nil {
s.pruneCounters()
}
}
// pruneCounters performs a function-level partial intersection using the
// current POD counter data (s.mm.pod.pmm) and the intersected data from
// PODs in previous dirs (s.imm).
func (s *sstate) pruneCounters() {
pkeys := make([]pkfunc, 0, len(s.mm.pod.pmm))
for k := range s.mm.pod.pmm {
pkeys = append(pkeys, k)
}
// Remove anything from pmm not found in imm. We don't need to
// go the other way (removing things from imm not found in pmm)
// since we don't add anything to imm if there is no pmm entry.
for _, k := range pkeys {
if _, found := s.imm[k]; !found {
delete(s.mm.pod.pmm, k)
}
}
s.imm = nil
}
func (s *sstate) BeginCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int) {
dbgtrace(2, "visiting counter data file %s diridx %d", cdf, dirIdx)
if s.inidx != dirIdx {
if s.inidx > dirIdx {
// We're relying on having data files presented in
// the order they appear in the inputs (e.g. first all
// data files from input dir 0, then dir 1, etc).
panic("decreasing dir index, internal error")
}
if dirIdx == 0 {
// No need to keep track of the functions in the first
// directory, since that info will be replicated in
// s.mm.pod.pmm.
s.imm = nil
} else {
// We're now starting to visit the Nth directory, N != 0.
if s.mode == intersectMode {
if s.imm != nil {
s.pruneCounters()
}
s.imm = make(map[pkfunc]struct{})
}
}
s.inidx = dirIdx
}
}
func (s *sstate) EndCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int) {
}
func (s *sstate) VisitFuncCounterData(data decodecounter.FuncPayload) {
key := pkfunc{pk: data.PkgIdx, fcn: data.FuncIdx}
if *verbflag >= 5 {
fmt.Printf("ctr visit fid=%d pk=%d inidx=%d data.Counters=%+v\n", data.FuncIdx, data.PkgIdx, s.inidx, data.Counters)
}
// If we're processing counter data from the initial (first) input
// directory, then just install it into the counter data map
// as usual.
if s.inidx == 0 {
s.mm.visitFuncCounterData(data)
return
}
// If we're looking at counter data from a dir other than
// the first, then perform the intersect/subtract.
if val, ok := s.mm.pod.pmm[key]; ok {
if s.mode == subtractMode {
for i := 0; i < len(data.Counters); i++ {
if data.Counters[i] != 0 {
val.Counters[i] = 0
}
}
} else if s.mode == intersectMode {
s.imm[key] = struct{}{}
for i := 0; i < len(data.Counters); i++ {
if data.Counters[i] == 0 {
val.Counters[i] = 0
}
}
}
}
}
func (s *sstate) VisitMetaDataFile(mdf string, mfr *decodemeta.CoverageMetaFileReader) {
if s.mode == intersectMode {
s.imm = make(map[pkfunc]struct{})
}
s.mm.visitMetaDataFile(mdf, mfr)
}
func (s *sstate) BeginPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) {
s.mm.visitPackage(pd, pkgIdx, false)
}
func (s *sstate) EndPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) {
}
func (s *sstate) VisitFunc(pkgIdx uint32, fnIdx uint32, fd *coverage.FuncDesc) {
s.mm.visitFunc(pkgIdx, fnIdx, fd, s.mode, false)
}
func (s *sstate) Finish() {
}