From 3c3df7b2b7799ec91af62741b7860d4fe46a457b Mon Sep 17 00:00:00 2001 From: xieyuschen Date: Wed, 27 Mar 2024 18:15:16 +0800 Subject: [PATCH] test: add test cases for overflow and pricision lost --- prometheus/counter.go | 2 +- prometheus/counter_test.go | 123 +++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) diff --git a/prometheus/counter.go b/prometheus/counter.go index 8ec6033..18aaefd 100644 --- a/prometheus/counter.go +++ b/prometheus/counter.go @@ -128,7 +128,7 @@ type counter struct { // // note that if a part of addend is added on base but the left causes a rounding error, // we don't respect this case -func addWithRoundingErrorChecking(base float64, addend float64) (float64, bool) { +func addWithRoundingErrorChecking(base, addend float64) (float64, bool) { if addend == 0 { return base, false } diff --git a/prometheus/counter_test.go b/prometheus/counter_test.go index 3587865..90e3f01 100644 --- a/prometheus/counter_test.go +++ b/prometheus/counter_test.go @@ -79,6 +79,114 @@ func TestCounterAdd(t *testing.T) { } } +func TestCounterOverflowAdd(t *testing.T) { + now := time.Now() + + counter := NewCounter(CounterOpts{ + Name: "test", + Help: "test help", + ConstLabels: Labels{"a": "1", "b": "2"}, + now: func() time.Time { return now }, + }).(*counter) + counter.change = math.MaxUint64 + counter.Inc() + + // this is expected due to the overflow + if expected, got := uint64(0), counter.change; expected != got { + t.Errorf("Expected %d, got %d.", expected, got) + } + + m := &dto.Metric{} + counter.Write(m) + + expected := &dto.Metric{ + Label: []*dto.LabelPair{ + {Name: proto.String("a"), Value: proto.String("1")}, + {Name: proto.String("b"), Value: proto.String("2")}, + }, + Counter: &dto.Counter{ + Value: proto.Float64(0), + CreatedTimestamp: timestamppb.New(now), + }, + } + if !proto.Equal(expected, m) { + t.Errorf("expected %q, got %q", expected, m) + } +} + +func TestCounterPrecisionLosingAdd(t *testing.T) { + now := time.Now() + + counter := NewCounter(CounterOpts{ + Name: "test", + Help: "test help", + ConstLabels: Labels{"a": "1", "b": "2"}, + now: func() time.Time { return now }, + }).(*counter) + + counter.Add(1 << 53) + if expected, got := float64(1<<53), math.Float64frombits(counter.valBits); expected != got { + t.Errorf("Expected %f, got %f.", expected, got) + } + + counter.Inc() + if expected, got := uint64(1), counter.change; expected != got { + t.Errorf("Expected %d, got %d.", expected, got) + } + + // along with the value 1 inside change, the sum could be added into the float without losing precision + counter.Add(1) + if expected, got := float64(1<<53+2), math.Float64frombits(counter.valBits); expected != got { + t.Errorf("Expected %f, got %f.", expected, got) + } + // the change is reset to 0 + if expected, got := uint64(0), counter.change; expected != got { + t.Errorf("Expected %d, got %d.", expected, got) + } + + // the value will flip with 2 + counter.Add(1) + if expected, got := float64(1<<53+4), math.Float64frombits(counter.valBits); expected != got { + t.Errorf("Expected %f, got %f.", expected, got) + } + if expected, got := uint64(0), counter.change; expected != got { + t.Errorf("Expected %d, got %d.", expected, got) + } + + counter.Add(1) + if expected, got := float64(1<<53+4), math.Float64frombits(counter.valBits); expected != got { + t.Errorf("Expected %f, got %f.", expected, got) + } + if expected, got := uint64(1), counter.change; expected != got { + t.Errorf("Expected %d, got %d.", expected, got) + } + + counter.Add(1) + if expected, got := float64(1<<53+6), math.Float64frombits(counter.valBits); expected != got { + t.Errorf("Expected %f, got %f.", expected, got) + } + if expected, got := uint64(0), counter.change; expected != got { + t.Errorf("Expected %d, got %d.", expected, got) + } + + m := &dto.Metric{} + counter.Write(m) + + expected := &dto.Metric{ + Label: []*dto.LabelPair{ + {Name: proto.String("a"), Value: proto.String("1")}, + {Name: proto.String("b"), Value: proto.String("2")}, + }, + Counter: &dto.Counter{ + Value: proto.Float64(1<<53 + 6), + CreatedTimestamp: timestamppb.New(now), + }, + } + if !proto.Equal(expected, m) { + t.Errorf("expected %q, got %q", expected, m) + } +} + func decreaseCounter(c *counter) (err error) { defer func() { if e := recover(); e != nil { @@ -418,6 +526,21 @@ func Test_hasRoundingError(t *testing.T) { wantRoundingError: true, wantNumber: 1 << 53, }, + { + name: "no rounding error", + base: 1 << 53, + delta: 2, + wantRoundingError: true, + wantNumber: 1<<53 + 2, + }, + { + name: "no rounding error", + base: 1<<53 + 2, + delta: 1, + wantRoundingError: false, + // there is a precision losing + wantNumber: 1<<53 + 4, + }, { name: "rounding error", base: 1 << 54,