github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/covdata/subtractintersect.go (about)

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