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