Source file src/cmd/covdata/subtractintersect.go

     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 main
     6  
     7  // This file contains functions and apis to support the "subtract" and
     8  // "intersect" subcommands of "go tool covdata".
     9  
    10  import (
    11  	"flag"
    12  	"fmt"
    13  	"internal/coverage"
    14  	"internal/coverage/decodecounter"
    15  	"internal/coverage/decodemeta"
    16  	"internal/coverage/pods"
    17  	"os"
    18  	"strings"
    19  )
    20  
    21  // makeSubtractIntersectOp creates a subtract or intersect operation.
    22  // 'mode' here must be either "subtract" or "intersect".
    23  func makeSubtractIntersectOp(mode string) covOperation {
    24  	outdirflag = flag.String("o", "", "Output directory to write")
    25  	s := &sstate{
    26  		mode:  mode,
    27  		mm:    newMetaMerge(),
    28  		inidx: -1,
    29  	}
    30  	return s
    31  }
    32  
    33  // sstate holds state needed to implement subtraction and intersection
    34  // operations on code coverage data files. This type provides methods
    35  // to implement the CovDataVisitor interface, and is designed to be
    36  // used in concert with the CovDataReader utility, which abstracts
    37  // away most of the grubby details of reading coverage data files.
    38  type sstate struct {
    39  	mm    *metaMerge
    40  	inidx int
    41  	mode  string
    42  	// Used only for intersection; keyed by pkg/fn ID, it keeps track of
    43  	// just the set of functions for which we have data in the current
    44  	// input directory.
    45  	imm map[pkfunc]struct{}
    46  }
    47  
    48  func (s *sstate) Usage(msg string) {
    49  	if len(msg) > 0 {
    50  		fmt.Fprintf(os.Stderr, "error: %s\n", msg)
    51  	}
    52  	fmt.Fprintf(os.Stderr, "usage: go tool covdata %s -i=dir1,dir2 -o=<dir>\n\n", s.mode)
    53  	flag.PrintDefaults()
    54  	fmt.Fprintf(os.Stderr, "\nExamples:\n\n")
    55  	op := "from"
    56  	if s.mode == intersectMode {
    57  		op = "with"
    58  	}
    59  	fmt.Fprintf(os.Stderr, "  go tool covdata %s -i=dir1,dir2 -o=outdir\n\n", s.mode)
    60  	fmt.Fprintf(os.Stderr, "  \t%ss dir2 %s dir1, writing result\n", s.mode, op)
    61  	fmt.Fprintf(os.Stderr, "  \tinto output dir outdir.\n")
    62  	os.Exit(2)
    63  }
    64  
    65  func (s *sstate) Setup() {
    66  	if *indirsflag == "" {
    67  		usage("select input directories with '-i' option")
    68  	}
    69  	indirs := strings.Split(*indirsflag, ",")
    70  	if s.mode == subtractMode && len(indirs) != 2 {
    71  		usage("supply exactly two input dirs for subtract operation")
    72  	}
    73  	if *outdirflag == "" {
    74  		usage("select output directory with '-o' option")
    75  	}
    76  }
    77  
    78  func (s *sstate) BeginPod(p pods.Pod) {
    79  	s.mm.beginPod()
    80  }
    81  
    82  func (s *sstate) EndPod(p pods.Pod) {
    83  	const pcombine = false
    84  	s.mm.endPod(pcombine)
    85  }
    86  
    87  func (s *sstate) EndCounters() {
    88  	if s.imm != nil {
    89  		s.pruneCounters()
    90  	}
    91  }
    92  
    93  // pruneCounters performs a function-level partial intersection using the
    94  // current POD counter data (s.mm.pod.pmm) and the intersected data from
    95  // PODs in previous dirs (s.imm).
    96  func (s *sstate) pruneCounters() {
    97  	pkeys := make([]pkfunc, 0, len(s.mm.pod.pmm))
    98  	for k := range s.mm.pod.pmm {
    99  		pkeys = append(pkeys, k)
   100  	}
   101  	// Remove anything from pmm not found in imm. We don't need to
   102  	// go the other way (removing things from imm not found in pmm)
   103  	// since we don't add anything to imm if there is no pmm entry.
   104  	for _, k := range pkeys {
   105  		if _, found := s.imm[k]; !found {
   106  			delete(s.mm.pod.pmm, k)
   107  		}
   108  	}
   109  	s.imm = nil
   110  }
   111  
   112  func (s *sstate) BeginCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int) {
   113  	dbgtrace(2, "visiting counter data file %s diridx %d", cdf, dirIdx)
   114  	if s.inidx != dirIdx {
   115  		if s.inidx > dirIdx {
   116  			// We're relying on having data files presented in
   117  			// the order they appear in the inputs (e.g. first all
   118  			// data files from input dir 0, then dir 1, etc).
   119  			panic("decreasing dir index, internal error")
   120  		}
   121  		if dirIdx == 0 {
   122  			// No need to keep track of the functions in the first
   123  			// directory, since that info will be replicated in
   124  			// s.mm.pod.pmm.
   125  			s.imm = nil
   126  		} else {
   127  			// We're now starting to visit the Nth directory, N != 0.
   128  			if s.mode == intersectMode {
   129  				if s.imm != nil {
   130  					s.pruneCounters()
   131  				}
   132  				s.imm = make(map[pkfunc]struct{})
   133  			}
   134  		}
   135  		s.inidx = dirIdx
   136  	}
   137  }
   138  
   139  func (s *sstate) EndCounterDataFile(cdf string, cdr *decodecounter.CounterDataReader, dirIdx int) {
   140  }
   141  
   142  func (s *sstate) VisitFuncCounterData(data decodecounter.FuncPayload) {
   143  	key := pkfunc{pk: data.PkgIdx, fcn: data.FuncIdx}
   144  
   145  	if *verbflag >= 5 {
   146  		fmt.Printf("ctr visit fid=%d pk=%d inidx=%d data.Counters=%+v\n", data.FuncIdx, data.PkgIdx, s.inidx, data.Counters)
   147  	}
   148  
   149  	// If we're processing counter data from the initial (first) input
   150  	// directory, then just install it into the counter data map
   151  	// as usual.
   152  	if s.inidx == 0 {
   153  		s.mm.visitFuncCounterData(data)
   154  		return
   155  	}
   156  
   157  	// If we're looking at counter data from a dir other than
   158  	// the first, then perform the intersect/subtract.
   159  	if val, ok := s.mm.pod.pmm[key]; ok {
   160  		if s.mode == subtractMode {
   161  			for i := 0; i < len(data.Counters); i++ {
   162  				if data.Counters[i] != 0 {
   163  					val.Counters[i] = 0
   164  				}
   165  			}
   166  		} else if s.mode == intersectMode {
   167  			s.imm[key] = struct{}{}
   168  			for i := 0; i < len(data.Counters); i++ {
   169  				if data.Counters[i] == 0 {
   170  					val.Counters[i] = 0
   171  				}
   172  			}
   173  		}
   174  	}
   175  }
   176  
   177  func (s *sstate) VisitMetaDataFile(mdf string, mfr *decodemeta.CoverageMetaFileReader) {
   178  	if s.mode == intersectMode {
   179  		s.imm = make(map[pkfunc]struct{})
   180  	}
   181  	s.mm.visitMetaDataFile(mdf, mfr)
   182  }
   183  
   184  func (s *sstate) BeginPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) {
   185  	s.mm.visitPackage(pd, pkgIdx, false)
   186  }
   187  
   188  func (s *sstate) EndPackage(pd *decodemeta.CoverageMetaDataDecoder, pkgIdx uint32) {
   189  }
   190  
   191  func (s *sstate) VisitFunc(pkgIdx uint32, fnIdx uint32, fd *coverage.FuncDesc) {
   192  	s.mm.visitFunc(pkgIdx, fnIdx, fd, s.mode, false)
   193  }
   194  
   195  func (s *sstate) Finish() {
   196  }
   197  

View as plain text