package testappender12import (3"bufio"4"bytes"5"fmt"6"strings"78"github.com/pmezard/go-difflib/difflib"9dto "github.com/prometheus/client_model/go"10"github.com/prometheus/common/expfmt"11)1213// NOTE(rfratto): this file is only needed because client_golang's testutil14// package currently enforces the Prometheus text exposition format, due to15// there not being a OpenMetrics parser in prometheus/common yet.16//17// This means that, unlike with testutil, callers are forced to compare line18// order of comments and metrics. This can be a good thing, in some cases, but19// is fairly tedious if you don't care about the order.2021// Comparer can compare a slice of dto.MetricFamily to an expected list of22// metrics.23type Comparer struct {24// OpenMetrics indicates that the Comparer should test the OpenMetrics25// representation instead of the Prometheus text exposition format.26OpenMetrics bool27}2829// Compare compares the text representation of families to an expected input30// string. If the OpenMetrics field of the Comparer is true, families is31// converted into the OpenMetrics text exposition format. Otherwise, families32// is converted into the Prometheus text exposition format.33//34// To make testing less error-prone, expect is cleaned by removing leading35// whitespace, trailing whitespace, and empty lines. The cleaned version of36// expect is then compared directly against the text representation of37// families.38func (c Comparer) Compare(families []*dto.MetricFamily, expect string) error {39expect = cleanExpositionString(expect)4041var (42enc expfmt.Encoder43buf bytes.Buffer44)45if c.OpenMetrics {46enc = expfmt.NewEncoder(&buf, expfmt.FmtOpenMetrics)47} else {48enc = expfmt.NewEncoder(&buf, expfmt.FmtText)49}50for _, f := range families {51if err := enc.Encode(f); err != nil {52return fmt.Errorf("error encoding family %s: %w", f.GetName(), err)53}54}5556if expect != buf.String() {57diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{58A: difflib.SplitLines(expect),59B: difflib.SplitLines(buf.String()),60FromFile: "Expected",61ToFile: "Actual",62Context: 1,63})64return fmt.Errorf("metric data does not match:\n\n%s", diff)65}6667return nil68}6970func cleanExpositionString(s string) string {71scanner := bufio.NewScanner(strings.NewReader(s))7273var res strings.Builder74for scanner.Scan() {75line := scanner.Text()76line = strings.TrimSpace(line)77if len(line) == 0 {78continue79}80fmt.Fprint(&res, line, "\n")81}8283return res.String()84}8586// Compare compares the text representation of families to an expected input87// string. Families is converted into the Prometheus text exposition format.88//89// To make testing less error-prone, expect is cleaned by removing leading90// whitespace, trailing whitespace, and empty lines. The cleaned version of91// expect is then compared directly against the text representation of92// families.93func Compare(families []*dto.MetricFamily, expect string) error {94var c Comparer95return c.Compare(families, expect)96}979899