summaryrefslogtreecommitdiff
path: root/microprom
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2026-08-18 13:40:20 +0200
committerStefan Majewsky <majewsky@gmx.net>2026-08-18 13:40:20 +0200
commita3ccdd9f491c20771cfda343ebebd4b5d0ea6602 (patch)
treef0ad530dd072c55c6f46dfea23af993fb31333d4 /microprom
parent5d17264c8ab8042042162f3b968557aabc4976b3 (diff)
downloadgo-gg-a3ccdd9f491c20771cfda343ebebd4b5d0ea6602.tar.gz
test coverage for microprom
Diffstat (limited to 'microprom')
-rw-r--r--microprom/handler_test.go142
-rw-r--r--microprom/labels.go2
-rw-r--r--microprom/microprom.go2
3 files changed, 144 insertions, 2 deletions
diff --git a/microprom/handler_test.go b/microprom/handler_test.go
new file mode 100644
index 0000000..7278945
--- /dev/null
+++ b/microprom/handler_test.go
@@ -0,0 +1,142 @@
+// SPDX-FileCopyrightText: 2026 Stefan Majewsky <majewsky@gmx.net>
+// SPDX-License-Identifier: Apache-2.0
+
+package microprom_test
+
+import (
+ "context"
+ "errors"
+ "io"
+ "maps"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "go.xyrillian.de/gg/assert"
+ "go.xyrillian.de/gg/microprom"
+)
+
+func TestHandlerBasic(t *testing.T) {
+ // NOTE: Most happy path coverage is in `./testing/microprom`.
+ // This only covers the SortOutput = false case.
+
+ h := microprom.Handler{
+ Families: map[microprom.MetricFamilyName]microprom.MetricFamilyInfo{
+ "process": {
+ Type: microprom.MetricTypeInfo,
+ Help: "Information about this process.",
+ },
+ "foo": {
+ Type: microprom.MetricTypeGauge,
+ Help: "This metric family will not have any collected metrics and thus go unreported.",
+ },
+ },
+ Collect: func(ctx context.Context, ms *microprom.MetricSet) error {
+ names := microprom.NewLabelNames("version")
+ labels := ms.FormatLabels(names, "1.2.3")
+ ms.Add("process", labels, 1.0)
+ return nil
+ },
+ }
+
+ // test normal behavior
+ status, body, headers := getMetrics(t, h, nil)
+ assert.Equal(t, status, http.StatusOK)
+ assert.Equal(t, headers, http.Header{
+ "Content-Type": {"text/plain; version=0.0.4; charset=utf-8; escaping=underscores"},
+ })
+ assert.Equal(t, body, strings.TrimSpace(`
+# HELP process_info Information about this process.
+# TYPE process_info info
+process_info{version="1.2.3"} 1
+ `)+"\n")
+}
+
+func TestHandlerErrors(t *testing.T) {
+ h := microprom.Handler{
+ Families: map[microprom.MetricFamilyName]microprom.MetricFamilyInfo{
+ "process": {
+ Type: microprom.MetricTypeInfo,
+ Help: "Information about this process.",
+ },
+ },
+ Collect: func(ctx context.Context, ms *microprom.MetricSet) error {
+ return errors.New("kaboom")
+ },
+ }
+
+ // test unacceptable content negotiation
+ status, body, headers := getMetrics(t, h, http.Header{"Accept": {"application/json"}})
+ assert.Equal(t, status, http.StatusNotAcceptable)
+ assert.Equal(t, headers.Get("Content-Type"), "text/plain; charset=utf-8")
+ assert.Equal(t, body, "supported formats are text/plain and application/openmetrics-text\n")
+
+ // test error during h.Collect()
+ status, body, headers = getMetrics(t, h, nil)
+ assert.Equal(t, status, http.StatusInternalServerError)
+ assert.Equal(t, headers.Get("Content-Type"), "text/plain; charset=utf-8")
+ assert.Equal(t, body, "kaboom\n")
+
+ // test panic from invalid metric family name
+ h.Families["what is this?"] = microprom.MetricFamilyInfo{
+ Type: microprom.MetricTypeGauge,
+ Help: "invalid metric family name",
+ }
+ msg := assert.PanicsWith[string](t, func() { getMetrics(t, h, nil) })
+ assert.Equal(t, msg, `in family "what is this?": invalid family name (does not match /^[a-zA-Z_:][a-zA-Z0-9_:]*$/)`)
+ delete(h.Families, "what is this?")
+
+ // test panic from invalid metric type
+ h.Families["invalid"] = microprom.MetricFamilyInfo{
+ Type: 100,
+ Help: "invalid metric type",
+ }
+ msg = assert.PanicsWith[string](t, func() { getMetrics(t, h, nil) })
+ assert.Equal(t, msg, `in family "invalid": invalid value for microprom.MetricType: 100`)
+ delete(h.Families, "invalid")
+
+ // test panic from invalid label name
+ h.Collect = func(ctx context.Context, ms *microprom.MetricSet) error {
+ names := microprom.NewLabelNames("app:version")
+ labels := ms.FormatLabels(names, "1.2.3")
+ ms.Add("process", labels, 1.0)
+ return nil
+ }
+ msg = assert.PanicsWith[string](t, func() { getMetrics(t, h, nil) })
+ assert.Equal(t, msg, `invalid label name: "app:version"`)
+
+ // test panic from wrong number of label values
+ h.Collect = func(ctx context.Context, ms *microprom.MetricSet) error {
+ names := microprom.NewLabelNames("version", "build_date")
+ labels := ms.FormatLabels(names, "1.2.3") // forgot build_date
+ ms.Add("process", labels, 1.0)
+ return nil
+ }
+ msg = assert.PanicsWith[string](t, func() { getMetrics(t, h, nil) })
+ assert.Equal(t, msg, `expected 2 label values, but got 1`)
+
+ // test panic from using an undeclared metric family
+ h.Collect = func(ctx context.Context, ms *microprom.MetricSet) error {
+ ms.Add("invalid", "", 1.0)
+ return nil
+ }
+ msg = assert.PanicsWith[string](t, func() { getMetrics(t, h, nil) })
+ assert.Equal(t, msg, `no such family: invalid`)
+}
+
+func getMetrics(t *testing.T, h http.Handler, requestHeaders http.Header) (status int, responseBody string, responseHeaders http.Header) {
+ t.Helper()
+ r := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/metrics", nil)
+ maps.Copy(r.Header, requestHeaders)
+
+ w := httptest.NewRecorder()
+ h.ServeHTTP(w, r)
+ resp := w.Result()
+
+ buf, err := io.ReadAll(resp.Body)
+ if err != nil {
+ t.Fatal(err.Error())
+ }
+ return resp.StatusCode, string(buf), resp.Header
+}
diff --git a/microprom/labels.go b/microprom/labels.go
index 51cdbb1..5b56c9c 100644
--- a/microprom/labels.go
+++ b/microprom/labels.go
@@ -54,7 +54,7 @@ func (ms *MetricSet) FormatLabels(n LabelNames, values ...string) Labels {
// NOTE on API structure: This is not part of ms.Add() to allow reusing label sets for multiple metrics.
if len(n.names) != len(values) {
- panic("arguments are not of equal length")
+ panic(fmt.Sprintf("expected %d label values, but got %d", len(n.names), len(values)))
}
if len(n.names) == 0 {
return ""
diff --git a/microprom/microprom.go b/microprom/microprom.go
index 86a5419..4055c8b 100644
--- a/microprom/microprom.go
+++ b/microprom/microprom.go
@@ -135,7 +135,7 @@ func NewMetricSet(syntax Syntax, families map[MetricFamilyName]MetricFamilyInfo)
func (ms *MetricSet) Add(name MetricFamilyName, labels Labels, value float64) {
_, ok := ms.metrics[name]
if !ok {
- panic("no such family: " + name)
+ panic("no such family: " + string(name))
}
ms.metrics[name] = append(ms.metrics[name], metric{labels, value})
}