diff --git a/pkg/common/drop_summary.go b/pkg/common/drop_summary.go index 2d16d24e..b78bb2be 100644 --- a/pkg/common/drop_summary.go +++ b/pkg/common/drop_summary.go @@ -39,8 +39,13 @@ func NewDropSummary() *DropSummary { // Add increments the drop count for the given reason by n. Safe to call on a // zero-value DropSummary (e.g. var d DropSummary); the underlying map is -// lazily initialized on first use. +// lazily initialized on first use. A zero n is a no-op and does not create a +// key for the reason. A negative n is a programming error and panics: it would +// subtract from the tally and silently understate the run's totals. func (d *DropSummary) Add(reason string, n int) { + if n < 0 { + panic(fmt.Sprintf("common.DropSummary.Add: negative count %d for reason %q", n, reason)) + } if d == nil || n == 0 { return } diff --git a/pkg/common/drop_summary_test.go b/pkg/common/drop_summary_test.go index c9610ab1..817ded61 100644 --- a/pkg/common/drop_summary_test.go +++ b/pkg/common/drop_summary_test.go @@ -98,3 +98,40 @@ func TestDropSummary_FormatOneLine(t *testing.T) { }) } } + +func TestDropSummary_Add_PerReasonTallies(t *testing.T) { + d := NewDropSummary() + d.Add(DropMinPoolSize, 3) + d.Add(DropMinPoolSize, 2) + d.Add(DropDuplicateDedup, 4) + d.Add(DropTargetAlreadyMet, 0) + + assert.Equal(t, 9, d.Total()) + assert.Equal(t, map[string]int{DropMinPoolSize: 5, DropDuplicateDedup: 4}, d.counts) +} + +func TestDropSummary_Add_NegativePanics(t *testing.T) { + d := NewDropSummary() + assert.PanicsWithValue(t, + `common.DropSummary.Add: negative count -1 for reason "--min-pool-size"`, + func() { d.Add(DropMinPoolSize, -1) }) + assert.True(t, d.IsEmpty(), "a rejected Add must not touch the tally") +} + +// A negative that would cancel a positive in Total() is the case the guard +// exists for: Total() alone would read 0 and look correct. +func TestDropSummary_Add_NegativeCannotCancelInTotal(t *testing.T) { + d := NewDropSummary() + d.Add(DropMinPoolSize, 2) + d.Add(DropDuplicateDedup, 5) + assert.Panics(t, func() { d.Add(DropDuplicateDedup, -5) }) + assert.Panics(t, func() { d.Add(DropMinPoolSize, -2) }) + + assert.Equal(t, 7, d.Total()) + assert.Equal(t, map[string]int{DropMinPoolSize: 2, DropDuplicateDedup: 5}, d.counts) +} + +func TestDropSummary_Add_NegativePanicsOnNilReceiver(t *testing.T) { + var d *DropSummary + assert.Panics(t, func() { d.Add(DropMinPoolSize, -1) }) +}