package promtest_test import ( "bytes" "errors" "fmt" "net/http" "net/http/httptest" "strings" "testing" "github.com/influxdata/influxdb/kit/prom" "github.com/influxdata/influxdb/kit/prom/promtest" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" "go.uber.org/zap/zaptest" ) func helperCollectors() []prometheus.Collector { myCounter := prometheus.NewCounter(prometheus.CounterOpts{ Namespace: "my", Subsystem: "random", Name: "counter", Help: "Just a random counter.", }) myGaugeVec := prometheus.NewGaugeVec(prometheus.GaugeOpts{ Namespace: "my", Subsystem: "random", Name: "gaugevec", Help: "Just a random gauge vector.", }, []string{"label1", "label2"}) myCounter.Inc() myGaugeVec.WithLabelValues("one", "two").Set(3) return []prometheus.Collector{myCounter, myGaugeVec} } func TestFindMetric(t *testing.T) { reg := prom.NewRegistry(zaptest.NewLogger(t)) reg.MustRegister(helperCollectors()...) mfs, err := reg.Gather() if err != nil { t.Fatal(err) } c := promtest.MustFindMetric(t, mfs, "my_random_counter", nil) if got := c.GetCounter().GetValue(); got != 1 { t.Fatalf("expected counter to be 1, got %v", got) } g := promtest.MustFindMetric(t, mfs, "my_random_gaugevec", map[string]string{"label1": "one", "label2": "two"}) if got := g.GetGauge().GetValue(); got != 3 { t.Fatalf("expected gauge to be 3, got %v", got) } } // fakeT helps us to assert that MustFindMetric calls FailNow when the metric isn't found. type fakeT struct { // Embed a T so we don't have to reimplement everything. // It's fine to leave T nil - fakeT will panic if calling a method we haven't implemented. *testing.T logBuf bytes.Buffer failed bool } func (t *fakeT) Helper() {} func (t *fakeT) Log(args ...interface{}) { fmt.Fprint(&t.logBuf, args...) t.logBuf.WriteString("\n") } func (t *fakeT) Logf(format string, args ...interface{}) { fmt.Fprintf(&t.logBuf, format, args...) t.logBuf.WriteString("\n") } func (t *fakeT) FailNow() { t.failed = true } func (t *fakeT) Fatalf(format string, args ...interface{}) { t.Logf(format, args...) t.FailNow() } func TestMustFindMetric(t *testing.T) { reg := prom.NewRegistry(zaptest.NewLogger(t)) reg.MustRegister(helperCollectors()...) mfs, err := reg.Gather() if err != nil { t.Fatal(err) } ft := new(fakeT) // Doesn't log when metric is found. _ = promtest.MustFindMetric(ft, mfs, "my_random_counter", nil) if ft.failed { t.Fatalf("MustFindMetric failed when it should not have. message: %s", ft.logBuf.String()) } // Logs and fails when family name not found. ft = new(fakeT) _ = promtest.MustFindMetric(ft, mfs, "missing_name", nil) if !ft.failed { t.Fatal("MustFindMetric should have failed but didn't") } logged := ft.logBuf.String() if !strings.Contains(logged, `name "missing_name" not found`) { t.Fatalf("did not log the looked up name which was not found. message: %s", logged) } if !strings.Contains(logged, "my_random_counter") || !strings.Contains(logged, "my_random_gaugevec") { t.Fatalf("did not log the available metric names. message: %s", logged) } // Logs and fails when family name found but metric labels mismatch. ft = new(fakeT) _ = promtest.MustFindMetric(ft, mfs, "my_random_counter", map[string]string{"unknown": "label"}) if !ft.failed { t.Fatal("MustFindMetric should have failed but didn't") } ft = new(fakeT) _ = promtest.MustFindMetric(ft, mfs, "my_random_gaugevec", map[string]string{"unknown": "label"}) if !ft.failed { t.Fatal("MustFindMetric should have failed but didn't") } logged = ft.logBuf.String() if !strings.Contains(logged, `"label1": "one"`) || !strings.Contains(logged, `"label2": "two"`) { t.Fatalf("did not log the available label names. message: %s", logged) } ft = new(fakeT) _ = promtest.MustFindMetric(ft, mfs, "my_random_gaugevec", map[string]string{"label1": "one", "label2": "two", "label3": "imaginary"}) if !ft.failed { t.Fatal("MustFindMetric should have failed but didn't") } logged = ft.logBuf.String() if !strings.Contains(logged, `"label1": "one"`) || !strings.Contains(logged, `"label2": "two"`) { t.Fatalf("did not log the available label names. message: %s", logged) } } func TestMustGather(t *testing.T) { expErr := errors.New("failed to gather") g := prometheus.GathererFunc(func() ([]*dto.MetricFamily, error) { return nil, expErr }) ft := new(fakeT) _ = promtest.MustGather(ft, g) if !ft.failed { t.Fatal("MustGather should have failed but didn't") } logged := ft.logBuf.String() if !strings.HasPrefix(logged, "error while gathering metrics:") || !strings.Contains(logged, expErr.Error()) { t.Fatalf("did not log the expected error message: %s", logged) } expMF := []*dto.MetricFamily{} // Use a non-nil, zero-length slice for a simple-ish check. g = prometheus.GathererFunc(func() ([]*dto.MetricFamily, error) { return expMF, nil }) ft = new(fakeT) gotMF := promtest.MustGather(ft, g) if ft.failed { t.Fatalf("MustGather should not have failed") } if gotMF == nil || len(gotMF) != 0 { t.Fatalf("exp: %v, got: %v", expMF, gotMF) } } func TestFromHTTPResponse(t *testing.T) { reg := prom.NewRegistry(zaptest.NewLogger(t)) reg.MustRegister(helperCollectors()...) s := httptest.NewServer(reg.HTTPHandler()) defer s.Close() resp, err := http.Get(s.URL) // Didn't specify a path for the handler, so any path should be fine. if err != nil { t.Fatal(err) } mfs, err := promtest.FromHTTPResponse(resp) if err != nil { t.Fatal(err) } if len(mfs) != 2 { t.Fatalf("expected 2 metrics but got %d", len(mfs)) } c := promtest.MustFindMetric(t, mfs, "my_random_counter", nil) if got := c.GetCounter().GetValue(); got != 1 { t.Fatalf("expected counter to be 1, got %v", got) } g := promtest.MustFindMetric(t, mfs, "my_random_gaugevec", map[string]string{"label1": "one", "label2": "two"}) if got := g.GetGauge().GetValue(); got != 3 { t.Fatalf("expected gauge to be 3, got %v", got) } }