Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/util/testappender/compare.go
4096 views
1
package testappender
2
3
import (
4
"bufio"
5
"bytes"
6
"fmt"
7
"strings"
8
9
"github.com/pmezard/go-difflib/difflib"
10
dto "github.com/prometheus/client_model/go"
11
"github.com/prometheus/common/expfmt"
12
)
13
14
// NOTE(rfratto): this file is only needed because client_golang's testutil
15
// package currently enforces the Prometheus text exposition format, due to
16
// there not being a OpenMetrics parser in prometheus/common yet.
17
//
18
// This means that, unlike with testutil, callers are forced to compare line
19
// order of comments and metrics. This can be a good thing, in some cases, but
20
// is fairly tedious if you don't care about the order.
21
22
// Comparer can compare a slice of dto.MetricFamily to an expected list of
23
// metrics.
24
type Comparer struct {
25
// OpenMetrics indicates that the Comparer should test the OpenMetrics
26
// representation instead of the Prometheus text exposition format.
27
OpenMetrics bool
28
}
29
30
// Compare compares the text representation of families to an expected input
31
// string. If the OpenMetrics field of the Comparer is true, families is
32
// converted into the OpenMetrics text exposition format. Otherwise, families
33
// is converted into the Prometheus text exposition format.
34
//
35
// To make testing less error-prone, expect is cleaned by removing leading
36
// whitespace, trailing whitespace, and empty lines. The cleaned version of
37
// expect is then compared directly against the text representation of
38
// families.
39
func (c Comparer) Compare(families []*dto.MetricFamily, expect string) error {
40
expect = cleanExpositionString(expect)
41
42
var (
43
enc expfmt.Encoder
44
buf bytes.Buffer
45
)
46
if c.OpenMetrics {
47
enc = expfmt.NewEncoder(&buf, expfmt.FmtOpenMetrics)
48
} else {
49
enc = expfmt.NewEncoder(&buf, expfmt.FmtText)
50
}
51
for _, f := range families {
52
if err := enc.Encode(f); err != nil {
53
return fmt.Errorf("error encoding family %s: %w", f.GetName(), err)
54
}
55
}
56
57
if expect != buf.String() {
58
diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
59
A: difflib.SplitLines(expect),
60
B: difflib.SplitLines(buf.String()),
61
FromFile: "Expected",
62
ToFile: "Actual",
63
Context: 1,
64
})
65
return fmt.Errorf("metric data does not match:\n\n%s", diff)
66
}
67
68
return nil
69
}
70
71
func cleanExpositionString(s string) string {
72
scanner := bufio.NewScanner(strings.NewReader(s))
73
74
var res strings.Builder
75
for scanner.Scan() {
76
line := scanner.Text()
77
line = strings.TrimSpace(line)
78
if len(line) == 0 {
79
continue
80
}
81
fmt.Fprint(&res, line, "\n")
82
}
83
84
return res.String()
85
}
86
87
// Compare compares the text representation of families to an expected input
88
// string. Families is converted into the Prometheus text exposition format.
89
//
90
// To make testing less error-prone, expect is cleaned by removing leading
91
// whitespace, trailing whitespace, and empty lines. The cleaned version of
92
// expect is then compared directly against the text representation of
93
// families.
94
func Compare(families []*dto.MetricFamily, expect string) error {
95
var c Comparer
96
return c.Compare(families, expect)
97
}
98
99