| // 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=<dir>\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() { |
| } |