// Copyright 2018 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package testutil import ( "fmt" "net/http" "net/http/httptest" "strings" "testing" "github.com/prometheus/common/expfmt" "github.com/prometheus/client_golang/prometheus" ) type untypedCollector struct{} func (u untypedCollector) Describe(c chan<- *prometheus.Desc) { c <- prometheus.NewDesc("name", "help", nil, nil) } func (u untypedCollector) Collect(c chan<- prometheus.Metric) { c <- prometheus.MustNewConstMetric( prometheus.NewDesc("name", "help", nil, nil), prometheus.UntypedValue, 2001, ) } func TestToFloat64(t *testing.T) { gaugeWithAValueSet := prometheus.NewGauge(prometheus.GaugeOpts{}) gaugeWithAValueSet.Set(3.14) counterVecWithOneElement := prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{"foo"}) counterVecWithOneElement.WithLabelValues("bar").Inc() counterVecWithTwoElements := prometheus.NewCounterVec(prometheus.CounterOpts{}, []string{"foo"}) counterVecWithTwoElements.WithLabelValues("bar").Add(42) counterVecWithTwoElements.WithLabelValues("baz").Inc() histogramVecWithOneElement := prometheus.NewHistogramVec(prometheus.HistogramOpts{}, []string{"foo"}) histogramVecWithOneElement.WithLabelValues("bar").Observe(2.7) scenarios := map[string]struct { collector prometheus.Collector panics bool want float64 }{ "simple counter": { collector: prometheus.NewCounter(prometheus.CounterOpts{}), panics: false, want: 0, }, "simple gauge": { collector: prometheus.NewGauge(prometheus.GaugeOpts{}), panics: false, want: 0, }, "simple untyped": { collector: untypedCollector{}, panics: false, want: 2001, }, "simple histogram": { collector: prometheus.NewHistogram(prometheus.HistogramOpts{}), panics: true, }, "simple summary": { collector: prometheus.NewSummary(prometheus.SummaryOpts{}), panics: true, }, "simple gauge with an actual value set": { collector: gaugeWithAValueSet, panics: false, want: 3.14, }, "counter vec with zero elements": { collector: prometheus.NewCounterVec(prometheus.CounterOpts{}, nil), panics: true, }, "counter vec with one element": { collector: counterVecWithOneElement, panics: false, want: 1, }, "counter vec with two elements": { collector: counterVecWithTwoElements, panics: true, }, "histogram vec with one element": { collector: histogramVecWithOneElement, panics: true, }, } for n, s := range scenarios { t.Run(n, func(t *testing.T) { defer func() { r := recover() if r == nil && s.panics { t.Error("expected panic") } else if r != nil && !s.panics { t.Error("unexpected panic: ", r) } // Any other combination is the expected outcome. }() if got := ToFloat64(s.collector); got != s.want { t.Errorf("want %f, got %f", s.want, got) } }) } } func TestCollectAndCompare(t *testing.T) { const metadata = ` # HELP some_total A value that represents a counter. # TYPE some_total counter ` c := prometheus.NewCounter(prometheus.CounterOpts{ Name: "some_total", Help: "A value that represents a counter.", ConstLabels: prometheus.Labels{ "label1": "value1", }, }) c.Inc() expected := ` some_total{ label1 = "value1" } 1 ` if err := CollectAndCompare(c, strings.NewReader(metadata+expected), "some_total"); err != nil { t.Errorf("unexpected collecting result:\n%s", err) } } func TestCollectAndCompareNoLabel(t *testing.T) { const metadata = ` # HELP some_total A value that represents a counter. # TYPE some_total counter ` c := prometheus.NewCounter(prometheus.CounterOpts{ Name: "some_total", Help: "A value that represents a counter.", }) c.Inc() expected := ` some_total 1 ` if err := CollectAndCompare(c, strings.NewReader(metadata+expected), "some_total"); err != nil { t.Errorf("unexpected collecting result:\n%s", err) } } func TestCollectAndCompareNoHelp(t *testing.T) { const metadata = ` # TYPE some_total counter ` c := prometheus.NewCounter(prometheus.CounterOpts{ Name: "some_total", }) c.Inc() expected := ` some_total 1 ` if err := CollectAndCompare(c, strings.NewReader(metadata+expected), "some_total"); err != nil { t.Errorf("unexpected collecting result:\n%s", err) } } func TestCollectAndCompareHistogram(t *testing.T) { inputs := []struct { name string c prometheus.Collector metadata string expect string observation float64 }{ { name: "Testing Histogram Collector", c: prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "some_histogram", Help: "An example of a histogram", Buckets: []float64{1, 2, 3}, }), metadata: ` # HELP some_histogram An example of a histogram # TYPE some_histogram histogram `, expect: ` some_histogram{le="1"} 0 some_histogram{le="2"} 0 some_histogram{le="3"} 1 some_histogram_bucket{le="+Inf"} 1 some_histogram_sum 2.5 some_histogram_count 1 `, observation: 2.5, }, { name: "Testing HistogramVec Collector", c: prometheus.NewHistogramVec(prometheus.HistogramOpts{ Name: "some_histogram", Help: "An example of a histogram", Buckets: []float64{1, 2, 3}, }, []string{"test"}), metadata: ` # HELP some_histogram An example of a histogram # TYPE some_histogram histogram `, expect: ` some_histogram_bucket{test="test",le="1"} 0 some_histogram_bucket{test="test",le="2"} 0 some_histogram_bucket{test="test",le="3"} 1 some_histogram_bucket{test="test",le="+Inf"} 1 some_histogram_sum{test="test"} 2.5 some_histogram_count{test="test"} 1 `, observation: 2.5, }, } for _, input := range inputs { switch collector := input.c.(type) { case prometheus.Histogram: collector.Observe(input.observation) case *prometheus.HistogramVec: collector.WithLabelValues("test").Observe(input.observation) default: t.Fatalf("unsupported collector tested") } t.Run(input.name, func(t *testing.T) { if err := CollectAndCompare(input.c, strings.NewReader(input.metadata+input.expect)); err != nil { t.Errorf("unexpected collecting result:\n%s", err) } }) } } func TestNoMetricFilter(t *testing.T) { const metadata = ` # HELP some_total A value that represents a counter. # TYPE some_total counter ` c := prometheus.NewCounter(prometheus.CounterOpts{ Name: "some_total", Help: "A value that represents a counter.", ConstLabels: prometheus.Labels{ "label1": "value1", }, }) c.Inc() expected := ` some_total{label1="value1"} 1 ` if err := CollectAndCompare(c, strings.NewReader(metadata+expected)); err != nil { t.Errorf("unexpected collecting result:\n%s", err) } } func TestMetricNotFound(t *testing.T) { const metadata = ` # HELP some_other_metric A value that represents a counter. # TYPE some_other_metric counter ` c := prometheus.NewCounter(prometheus.CounterOpts{ Name: "some_total", Help: "A value that represents a counter.", ConstLabels: prometheus.Labels{ "label1": "value1", }, }) c.Inc() expected := ` some_other_metric{label1="value1"} 1 ` expectedError := `-# HELP some_total A value that represents a counter. -# TYPE some_total counter -some_total{label1="value1"} 1 +# HELP some_other_metric A value that represents a counter. +# TYPE some_other_metric counter +some_other_metric{label1="value1"} 1 ` err := CollectAndCompare(c, strings.NewReader(metadata+expected)) if err == nil { t.Error("Expected error, got no error.") } if err.Error() != expectedError { t.Errorf("Expected\n%#+v\nGot:\n%#+v", expectedError, err.Error()) } } func TestScrapeAndCompare(t *testing.T) { const expected = ` # HELP some_total A value that represents a counter. # TYPE some_total counter some_total{ label1 = "value1" } 1 ` expectedReader := strings.NewReader(expected) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, expected) })) defer ts.Close() if err := ScrapeAndCompare(ts.URL, expectedReader, "some_total"); err != nil { t.Errorf("unexpected scraping result:\n%s", err) } } func TestScrapeAndCompareWithMultipleExpected(t *testing.T) { const expected = ` # HELP some_total A value that represents a counter. # TYPE some_total counter some_total{ label1 = "value1" } 1 # HELP some_total2 A value that represents a counter. # TYPE some_total2 counter some_total2{ label2 = "value2" } 1 ` expectedReader := strings.NewReader(expected) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, expected) })) defer ts.Close() if err := ScrapeAndCompare(ts.URL, expectedReader, "some_total2"); err != nil { t.Errorf("unexpected scraping result:\n%s", err) } } func TestScrapeAndCompareFetchingFail(t *testing.T) { err := ScrapeAndCompare("some_url", strings.NewReader("some expectation"), "some_total") if err == nil { t.Errorf("expected an error but got nil") } if !strings.HasPrefix(err.Error(), "scraping metrics failed") { t.Errorf("unexpected error happened: %s", err) } } func TestScrapeAndCompareBadStatusCode(t *testing.T) { const expected = ` # HELP some_total A value that represents a counter. # TYPE some_total counter some_total{ label1 = "value1" } 1 ` expectedReader := strings.NewReader(expected) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadGateway) fmt.Fprintln(w, expected) })) defer ts.Close() err := ScrapeAndCompare(ts.URL, expectedReader, "some_total") if err == nil { t.Errorf("expected an error but got nil") } if !strings.HasPrefix(err.Error(), "the scraping target returned a status code other than 200") { t.Errorf("unexpected error happened: %s", err) } } func TestCollectAndCount(t *testing.T) { c := prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "some_total", Help: "A value that represents a counter.", }, []string{"foo"}, ) if got, want := CollectAndCount(c), 0; got != want { t.Errorf("unexpected metric count, got %d, want %d", got, want) } c.WithLabelValues("bar") if got, want := CollectAndCount(c), 1; got != want { t.Errorf("unexpected metric count, got %d, want %d", got, want) } c.WithLabelValues("baz") if got, want := CollectAndCount(c), 2; got != want { t.Errorf("unexpected metric count, got %d, want %d", got, want) } if got, want := CollectAndCount(c, "some_total"), 2; got != want { t.Errorf("unexpected metric count, got %d, want %d", got, want) } if got, want := CollectAndCount(c, "some_other_total"), 0; got != want { t.Errorf("unexpected metric count, got %d, want %d", got, want) } } func TestCollectAndFormat(t *testing.T) { const expected = `# HELP foo_bar A value that represents the number of bars in foo. # TYPE foo_bar counter foo_bar{fizz="bang"} 1 ` c := prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "foo_bar", Help: "A value that represents the number of bars in foo.", }, []string{"fizz"}, ) c.WithLabelValues("bang").Inc() got, err := CollectAndFormat(c, expfmt.TypeTextPlain, "foo_bar") if err != nil { t.Errorf("unexpected error: %s", err.Error()) } gotS := string(got) if err != nil { t.Errorf("unexpected error: %s", err.Error()) } if gotS != expected { t.Errorf("unexpected metric output, got %q, expected %q", gotS, expected) } }