cmd/covdata: relax mode clash policy for selected operations

Relax the policy on counter mode clashes in certain cases for "go tool
covdata" operations. Specifically, when generating 'percent',
'pkglist' or 'func' reports, we only care about whether a given
statement is executed, thus counter mode clashes are irrelevant; there
is no need to report clashes for these ops.

Example:

  $ go build -covermode=count -o myprog.count.exe myprog
  $ go build -covermode=set -o myprog.set.exe myprog
  $ GOCOVERDIR=dir1 ./myprog.count.exe
  ...
  $ GOCOVERDIR=dir2 ./myprog.set.exe
  ...
  $ go tool covdata percent i=dir1,dir2
  error: counter mode clash while reading meta-data file dir2/covmeta.1a0cd0c8ccab07d3179f0ac3dd98159a: previous file had count, new file has set
  $

With this patch the command above will "do the right thing" and work
properly, and in addition merges using the "-pcombine" flag will also
operate with relaxed rules. Note that textfmt operations still require
inputs with consistent coverage modes.

Change-Id: I01e97530d9780943c99b399d03d4cfff05aafd8c
Reviewed-on: https://go-review.googlesource.com/c/go/+/495440
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: David Chase <drchase@google.com>
Run-TryBot: Than McIntosh <thanm@google.com>
This commit is contained in:
Than McIntosh 2022-12-27 14:34:11 -05:00
parent 5322e66a12
commit a371fa5e70
5 changed files with 75 additions and 15 deletions

View file

@ -13,6 +13,13 @@ import (
"math"
)
type ModeMergePolicy uint8
const (
ModeMergeStrict ModeMergePolicy = iota
ModeMergeRelaxed
)
// Merger provides state and methods to help manage the process of
// merging together coverage counter data for a given function, for
// tools that need to implicitly merge counter as they read multiple
@ -20,9 +27,14 @@ import (
type Merger struct {
cmode coverage.CounterMode
cgran coverage.CounterGranularity
policy ModeMergePolicy
overflow bool
}
func (cm *Merger) SetModeMergePolicy(policy ModeMergePolicy) {
cm.policy = policy
}
// MergeCounters takes the counter values in 'src' and merges them
// into 'dst' according to the correct counter mode.
func (m *Merger) MergeCounters(dst, src []uint32) (error, bool) {
@ -72,20 +84,31 @@ func SaturatingAdd(dst, src uint32) (uint32, bool) {
// SetModeAndGranularity records the counter mode and granularity for
// the current merge. In the specific case of merging across coverage
// data files from different binaries, where we're combining data from
// more than one meta-data file, we need to check for mode/granularity
// clashes.
// more than one meta-data file, we need to check for and resolve
// mode/granularity clashes.
func (cm *Merger) SetModeAndGranularity(mdf string, cmode coverage.CounterMode, cgran coverage.CounterGranularity) error {
// Collect counter mode and granularity so as to detect clashes.
if cm.cmode != coverage.CtrModeInvalid {
if cm.cmode != cmode {
return fmt.Errorf("counter mode clash while reading meta-data file %s: previous file had %s, new file has %s", mdf, cm.cmode.String(), cmode.String())
}
if cm.cmode == coverage.CtrModeInvalid {
// Set merger mode based on what we're seeing here.
cm.cmode = cmode
cm.cgran = cgran
} else {
// Granularity clashes are always errors.
if cm.cgran != cgran {
return fmt.Errorf("counter granularity clash while reading meta-data file %s: previous file had %s, new file has %s", mdf, cm.cgran.String(), cgran.String())
}
// Mode clashes are treated as errors if we're using the
// default strict policy.
if cm.cmode != cmode {
if cm.policy == ModeMergeStrict {
return fmt.Errorf("counter mode clash while reading meta-data file %s: previous file had %s, new file has %s", mdf, cm.cmode.String(), cmode.String())
}
// In the case of a relaxed mode merge policy, upgrade
// mode if needed.
if cm.cmode < cmode {
cm.cmode = cmode
}
}
}
cm.cmode = cmode
cm.cgran = cgran
return nil
}

View file

@ -15,11 +15,11 @@ func TestClash(t *testing.T) {
m := &cmerge.Merger{}
err := m.SetModeAndGranularity("mdf1.data", coverage.CtrModeSet, coverage.CtrGranularityPerBlock)
if err != nil {
t.Fatalf("unexpected clash")
t.Fatalf("unexpected clash: %v", err)
}
err = m.SetModeAndGranularity("mdf1.data", coverage.CtrModeSet, coverage.CtrGranularityPerBlock)
if err != nil {
t.Fatalf("unexpected clash")
t.Fatalf("unexpected clash: %v", err)
}
err = m.SetModeAndGranularity("mdf1.data", coverage.CtrModeCount, coverage.CtrGranularityPerBlock)
if err == nil {
@ -29,10 +29,23 @@ func TestClash(t *testing.T) {
if err == nil {
t.Fatalf("expected granularity clash, not found")
}
m.SetModeMergePolicy(cmerge.ModeMergeRelaxed)
err = m.SetModeAndGranularity("mdf1.data", coverage.CtrModeCount, coverage.CtrGranularityPerBlock)
if err != nil {
t.Fatalf("unexpected clash: %v", err)
}
err = m.SetModeAndGranularity("mdf1.data", coverage.CtrModeSet, coverage.CtrGranularityPerBlock)
if err != nil {
t.Fatalf("unexpected clash: %v", err)
}
err = m.SetModeAndGranularity("mdf1.data", coverage.CtrModeAtomic, coverage.CtrGranularityPerBlock)
if err != nil {
t.Fatalf("unexpected clash: %v", err)
}
m.ResetModeAndGranularity()
err = m.SetModeAndGranularity("mdf1.data", coverage.CtrModeCount, coverage.CtrGranularityPerFunc)
if err != nil {
t.Fatalf("unexpected clash after reset")
t.Fatalf("unexpected clash after reset: %v", err)
}
}