Skip to content

Commit 1638da9

Browse files
sazarykakkoyun
andauthored
testutil: Add ScrapeAndCompare (#1043)
* testutil: Add ScrapeAndCompare Signed-off-by: sazary <soroosh@azary.ir> * testutil: Use %w verb wherever we're using an error in fmt.Errorf Signed-off-by: sazary <soroosh@azary.ir> * Format Signed-off-by: Kemal Akkoyun <kakkoyun@gmail.com> Co-authored-by: Kemal Akkoyun <kakkoyun@users.noreply.github.com> Co-authored-by: Kemal Akkoyun <kakkoyun@gmail.com>
1 parent c576b95 commit 1638da9

File tree

2 files changed

+112
-8
lines changed

2 files changed

+112
-8
lines changed

prometheus/testutil/testutil.go

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ import (
4141
"bytes"
4242
"fmt"
4343
"io"
44+
"net/http"
4445
"reflect"
4546

4647
"github.com/davecgh/go-spew/spew"
47-
"github.com/prometheus/common/expfmt"
48-
4948
dto "github.com/prometheus/client_model/go"
49+
"github.com/prometheus/common/expfmt"
5050

5151
"github.com/prometheus/client_golang/prometheus"
5252
"github.com/prometheus/client_golang/prometheus/internal"
@@ -155,6 +155,34 @@ func GatherAndCount(g prometheus.Gatherer, metricNames ...string) (int, error) {
155155
return result, nil
156156
}
157157

158+
// ScrapeAndCompare calls a remote exporter's endpoint which is expected to return some metrics in
159+
// plain text format. Then it compares it with the results that the `expected` would return.
160+
// If the `metricNames` is not empty it would filter the comparison only to the given metric names.
161+
func ScrapeAndCompare(url string, expected io.Reader, metricNames ...string) error {
162+
resp, err := http.Get(url)
163+
if err != nil {
164+
return fmt.Errorf("scraping metrics failed: %w", err)
165+
}
166+
defer resp.Body.Close()
167+
168+
if resp.StatusCode != http.StatusOK {
169+
return fmt.Errorf("the scraping target returned a status code other than 200: %d",
170+
resp.StatusCode)
171+
}
172+
173+
scraped, err := convertReaderToMetricFamily(resp.Body)
174+
if err != nil {
175+
return err
176+
}
177+
178+
wanted, err := convertReaderToMetricFamily(expected)
179+
if err != nil {
180+
return err
181+
}
182+
183+
return compareMetricFamilies(scraped, wanted, metricNames...)
184+
}
185+
158186
// CollectAndCompare registers the provided Collector with a newly created
159187
// pedantic Registry. It then calls GatherAndCompare with that Registry and with
160188
// the provided metricNames.
@@ -184,17 +212,35 @@ func TransactionalGatherAndCompare(g prometheus.TransactionalGatherer, expected
184212
if err != nil {
185213
return fmt.Errorf("gathering metrics failed: %w", err)
186214
}
187-
if metricNames != nil {
188-
got = filterMetrics(got, metricNames)
215+
216+
wanted, err := convertReaderToMetricFamily(expected)
217+
if err != nil {
218+
return err
189219
}
220+
221+
return compareMetricFamilies(got, wanted, metricNames...)
222+
}
223+
224+
// convertReaderToMetricFamily would read from a io.Reader object and convert it to a slice of
225+
// dto.MetricFamily.
226+
func convertReaderToMetricFamily(reader io.Reader) ([]*dto.MetricFamily, error) {
190227
var tp expfmt.TextParser
191-
wantRaw, err := tp.TextToMetricFamilies(expected)
228+
notNormalized, err := tp.TextToMetricFamilies(reader)
192229
if err != nil {
193-
return fmt.Errorf("parsing expected metrics failed: %w", err)
230+
return nil, fmt.Errorf("converting reader to metric families failed: %w", err)
231+
}
232+
233+
return internal.NormalizeMetricFamilies(notNormalized), nil
234+
}
235+
236+
// compareMetricFamilies would compare 2 slices of metric families, and optionally filters both of
237+
// them to the `metricNames` provided.
238+
func compareMetricFamilies(got, expected []*dto.MetricFamily, metricNames ...string) error {
239+
if metricNames != nil {
240+
got = filterMetrics(got, metricNames)
194241
}
195-
want := internal.NormalizeMetricFamilies(wantRaw)
196242

197-
return compare(got, want)
243+
return compare(got, expected)
198244
}
199245

200246
// compare encodes both provided slices of metric families into the text format,

prometheus/testutil/testutil_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
package testutil
1515

1616
import (
17+
"fmt"
18+
"net/http"
19+
"net/http/httptest"
1720
"strings"
1821
"testing"
1922

@@ -308,6 +311,61 @@ Diff:
308311
}
309312
}
310313

314+
func TestScrapeAndCompare(t *testing.T) {
315+
const expected = `
316+
# HELP some_total A value that represents a counter.
317+
# TYPE some_total counter
318+
319+
some_total{ label1 = "value1" } 1
320+
`
321+
322+
expectedReader := strings.NewReader(expected)
323+
324+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
325+
fmt.Fprintln(w, expected)
326+
}))
327+
defer ts.Close()
328+
329+
if err := ScrapeAndCompare(ts.URL, expectedReader, "some_total"); err != nil {
330+
t.Errorf("unexpected scraping result:\n%s", err)
331+
}
332+
}
333+
334+
func TestScrapeAndCompareFetchingFail(t *testing.T) {
335+
err := ScrapeAndCompare("some_url", strings.NewReader("some expectation"), "some_total")
336+
if err == nil {
337+
t.Errorf("expected an error but got nil")
338+
}
339+
if !strings.HasPrefix(err.Error(), "scraping metrics failed") {
340+
t.Errorf("unexpected error happened: %s", err)
341+
}
342+
}
343+
344+
func TestScrapeAndCompareBadStatusCode(t *testing.T) {
345+
const expected = `
346+
# HELP some_total A value that represents a counter.
347+
# TYPE some_total counter
348+
349+
some_total{ label1 = "value1" } 1
350+
`
351+
352+
expectedReader := strings.NewReader(expected)
353+
354+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
355+
w.WriteHeader(http.StatusBadGateway)
356+
fmt.Fprintln(w, expected)
357+
}))
358+
defer ts.Close()
359+
360+
err := ScrapeAndCompare(ts.URL, expectedReader, "some_total")
361+
if err == nil {
362+
t.Errorf("expected an error but got nil")
363+
}
364+
if !strings.HasPrefix(err.Error(), "the scraping target returned a status code other than 200") {
365+
t.Errorf("unexpected error happened: %s", err)
366+
}
367+
}
368+
311369
func TestCollectAndCount(t *testing.T) {
312370
c := prometheus.NewCounterVec(
313371
prometheus.CounterOpts{

0 commit comments

Comments
 (0)