From: Daniel Swarbrick <dswarbrick@debian.org>
Date: Thu, 22 Jun 2023 17:08:06 +0200
Subject: Fix prometheus tests

Since protobuf APIv2, the text serialization format is not deterministic,
and thus cannot be relied upon for overly brittle "golden tests". The
workaround is to unmarshal the golden test "want" string, and compare it
via proto.Equal().
---
 prometheusmetrics_test.go | 66 ++++++++++++++++++++++++++---------------------
 1 file changed, 37 insertions(+), 29 deletions(-)

diff --git a/prometheusmetrics_test.go b/prometheusmetrics_test.go
index 31f9a9d..7d90c43 100644
--- a/prometheusmetrics_test.go
+++ b/prometheusmetrics_test.go
@@ -6,8 +6,10 @@ import (
 	"time"
 
 	"github.com/prometheus/client_golang/prometheus"
+	dto "github.com/prometheus/client_model/go"
 	"github.com/rcrowley/go-metrics"
-	"github.com/stretchr/testify/assert"
+	"google.golang.org/protobuf/encoding/prototext"
+	"google.golang.org/protobuf/proto"
 )
 
 func TestPrometheusRegistration(t *testing.T) {
@@ -68,9 +70,8 @@ func TestPrometheusCounterGetUpdated(t *testing.T) {
 	cntr.Inc(13)
 	time.Sleep(5 * time.Second)
 	metrics, _ := prometheusRegistry.Gather()
-	serialized := fmt.Sprint(metrics[0])
 	expected := fmt.Sprintf("name:\"test_subsys_counter\" help:\"counter\" type:GAUGE metric:<gauge:<value:%d > > ", cntr.Count())
-	if serialized != expected {
+	if err := compareProtoAndMetricFamily(expected, metrics[0]); err != nil {
 		t.Fatalf("Go-metrics value and prometheus metrics value do not match")
 	}
 }
@@ -89,9 +90,8 @@ func TestPrometheusGaugeGetUpdated(t *testing.T) {
 	if len(metrics) == 0 {
 		t.Fatalf("prometheus was unable to register the metric")
 	}
-	serialized := fmt.Sprint(metrics[0])
 	expected := fmt.Sprintf("name:\"test_subsys_gauge\" help:\"gauge\" type:GAUGE metric:<gauge:<value:%d > > ", gm.Value())
-	if serialized != expected {
+	if err := compareProtoAndMetricFamily(expected, metrics[0]); err != nil {
 		t.Fatalf("Go-metrics value and prometheus metrics value do not match")
 	}
 }
@@ -122,12 +122,9 @@ func TestPrometheusMeterGetUpdated(t *testing.T) {
 		snap.Count(), snap.Rate1(), snap.Rate15(), snap.Rate5(), snap.RateMean(),
 	)
 
-	assert.Equal(
-		t,
-		expected,
-		fmt.Sprint(metrics[0]),
-		"Go-metrics value and prometheus metrics value do not match",
-	)
+	if err := compareProtoAndMetricFamily(expected, metrics[0]); err != nil {
+		t.Fatalf("Go-metrics value and prometheus metrics value do not match")
+	}
 }
 
 func TestPrometheusHistogramGetUpdated(t *testing.T) {
@@ -149,23 +146,34 @@ func TestPrometheusHistogramGetUpdated(t *testing.T) {
 	time.Sleep(5 * time.Second)
 	metrics, _ := prometheusRegistry.Gather()
 
-	assert.Equal(
-		t,
-		fmt.Sprintf(
-			"name:\"test_subsys_metric\" help:\"metric\" type:GAUGE metric:<label:<name:\"type\" value:\"count\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"max\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"mean\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"min\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"perc75\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"perc95\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"perc99\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"perc999\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"stddev\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"sum\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"variance\" > gauge:<value:%v > > ",
-			gm.Count(),
-			gm.Max(),
-			gm.Mean(),
-			gm.Min(),
-			gm.Percentile(75),
-			gm.Percentile(95),
-			gm.Percentile(99),
-			gm.Percentile(999),
-			gm.StdDev(),
-			gm.Sum(),
-			gm.Variance(),
-		),
-		fmt.Sprint(metrics[0]),
-		"Go-metrics value and prometheus metrics value do not match",
+	expected := fmt.Sprintf(
+		"name:\"test_subsys_metric\" help:\"metric\" type:GAUGE metric:<label:<name:\"type\" value:\"count\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"max\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"mean\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"min\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"perc75\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"perc95\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"perc99\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"perc999\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"stddev\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"sum\" > gauge:<value:%v > > metric:<label:<name:\"type\" value:\"variance\" > gauge:<value:%v > > ",
+		gm.Count(),
+		gm.Max(),
+		gm.Mean(),
+		gm.Min(),
+		gm.Percentile(75),
+		gm.Percentile(95),
+		gm.Percentile(99),
+		gm.Percentile(999),
+		gm.StdDev(),
+		gm.Sum(),
+		gm.Variance(),
 	)
+
+	if err := compareProtoAndMetricFamily(expected, metrics[0]); err != nil {
+		t.Fatalf("Go-metrics value and prometheus metrics value do not match")
+	}
+}
+
+// compareProtoAndMetricFamily compares a text proto to a MetricFamily.
+func compareProtoAndMetricFamily(wantText string, got *dto.MetricFamily) error {
+	want := &dto.MetricFamily{}
+	if err := prototext.Unmarshal([]byte(wantText), want); err != nil {
+		return fmt.Errorf("unexpected error unmarshaling MetricFamily text %v", wantText)
+	}
+	if !proto.Equal(want, got) {
+		return fmt.Errorf("wanted MetricFamily %v, got %v.", want, got)
+	}
+	return nil
 }
