Skip to content

test: Implement fakes for prometheus api #1798

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions api/prometheus/v1/api_fake.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Copyright 2019 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 v1

import (
"context"
"time"

"github.com/prometheus/common/model"
)

type FakeAPI struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we follow this path we absolutely need to ensure this struct is not disconnected from API interface.

Suggested change
type FakeAPI struct {
var _ API = &FakeAPI{}
type FakeAPI struct {

// FakeAPI is a mock API for testing purposes.
// It implements the API interface and provides fake data for testing.
ExpectedAlertsResult []*Alert
ExpectedAlertsError error

ExpectedAlertManagersResult AlertManagersResult
ExpectedAlertManagersError error

ExpectedCleanTombstonesError error

ExpectedConfigResult ConfigResult
ExpectedConfigError error

ExpectedDeleteSeriesError error

ExpectedFlagsResult FlagsResult
ExpectedFlagsError error

ExpectedLabelNamesResult []string
ExpectedLabelNamesWarnings Warnings
ExpectedLabelNamesError error
Comment on lines +41 to +43
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder, wouldn't function based approach be much more flexible?

Suggested change
ExpectedLabelNamesResult []string
ExpectedLabelNamesWarnings Warnings
ExpectedLabelNamesError error
LabelNamesFn(ctx context.Context, matches []string, startTime, endTime time.Time, opts ...Option) ([]string, Warnings, error)


ExpectedLabelValuesResult model.LabelValues
ExpectedLabelValuesWarnings Warnings
ExpectedLabelValuesError error

ExpectedQueryResult model.Value
ExpectedQueryWarnings Warnings
ExpectedQueryError error

ExpectedQueryRangeResult model.Value
ExpectedQueryRangeWarnings Warnings
ExpectedQueryRangeError error

ExpectedQueryExemplarsResult []ExemplarQueryResult
ExpectedQueryExemplarsError error

ExpectedBuildinfoResult BuildinfoResult
ExpectedBuildinfoError error

ExpectedRuntimeinfoResult RuntimeinfoResult
ExpectedRuntimeinfoError error

ExpectedSeriesResult []model.LabelSet
ExpectedSeriesWarnings Warnings
ExpectedSeriesError error

ExpectedSnapshotResult SnapshotResult
ExpectedSnapshotError error

ExpectedRulesResult RulesResult
ExpectedRulesError error

ExpectedTargetsResult TargetsResult
ExpectedTargetsError error

ExpectedTargetsMetadataResult []MetricMetadata
ExpectedTargetsMetadataError error

ExpectedMetadataResult map[string][]Metadata
ExpectedMetadataError error

ExpectedTSDBResult TSDBResult
ExpectedTSDBError error

ExpectedWalReplayResult WalReplayStatus
ExpectedWalReplayError error
}

func (f *FakeAPI) Alerts(ctx context.Context) ([]*Alert, error) {
return f.ExpectedAlertsResult, f.ExpectedAlertsError
}

func (f *FakeAPI) AlertManagers(ctx context.Context) (AlertManagersResult, error) {
return f.ExpectedAlertManagersResult, f.ExpectedAlertManagersError
}

func (f *FakeAPI) CleanTombstones(ctx context.Context) error {
return f.ExpectedCleanTombstonesError
}

func (f *FakeAPI) Config(ctx context.Context) (ConfigResult, error) {
return f.ExpectedConfigResult, f.ExpectedConfigError
}

func (f *FakeAPI) DeleteSeries(ctx context.Context, matches []string, startTime, endTime time.Time) error {
return f.ExpectedDeleteSeriesError
}

func (f *FakeAPI) Flags(ctx context.Context) (FlagsResult, error) {
return f.ExpectedFlagsResult, f.ExpectedFlagsError
}

func (f *FakeAPI) LabelNames(ctx context.Context, matches []string, startTime, endTime time.Time, opts ...Option) ([]string, Warnings, error) {
return f.ExpectedLabelNamesResult, f.ExpectedLabelNamesWarnings, f.ExpectedLabelNamesError
}

func (f *FakeAPI) LabelValues(ctx context.Context, label string, matches []string, startTime, endTime time.Time, opts ...Option) (model.LabelValues, Warnings, error) {
return f.ExpectedLabelValuesResult, f.ExpectedLabelValuesWarnings, f.ExpectedLabelValuesError
}

func (f *FakeAPI) Query(ctx context.Context, query string, ts time.Time, opts ...Option) (model.Value, Warnings, error) {
return f.ExpectedQueryResult, f.ExpectedQueryWarnings, f.ExpectedQueryError
}

func (f *FakeAPI) QueryRange(ctx context.Context, query string, r Range, opts ...Option) (model.Value, Warnings, error) {
return f.ExpectedQueryRangeResult, f.ExpectedQueryRangeWarnings, f.ExpectedQueryRangeError
}

func (f *FakeAPI) QueryExemplars(ctx context.Context, query string, startTime, endTime time.Time) ([]ExemplarQueryResult, error) {
return f.ExpectedQueryExemplarsResult, f.ExpectedQueryExemplarsError
}

func (f *FakeAPI) Buildinfo(ctx context.Context) (BuildinfoResult, error) {
return f.ExpectedBuildinfoResult, f.ExpectedBuildinfoError
}

func (f *FakeAPI) Runtimeinfo(ctx context.Context) (RuntimeinfoResult, error) {
return f.ExpectedRuntimeinfoResult, f.ExpectedRuntimeinfoError
}

func (f *FakeAPI) Series(ctx context.Context, matches []string, startTime, endTime time.Time, opts ...Option) ([]model.LabelSet, Warnings, error) {
return f.ExpectedSeriesResult, f.ExpectedSeriesWarnings, f.ExpectedSeriesError
}

func (f *FakeAPI) Snapshot(ctx context.Context, skipHead bool) (SnapshotResult, error) {
return f.ExpectedSnapshotResult, f.ExpectedSnapshotError
}

func (f *FakeAPI) Rules(ctx context.Context) (RulesResult, error) {
return f.ExpectedRulesResult, f.ExpectedRulesError
}

func (f *FakeAPI) Targets(ctx context.Context) (TargetsResult, error) {
return f.ExpectedTargetsResult, f.ExpectedTargetsError
}

func (f *FakeAPI) TargetsMetadata(ctx context.Context, matchTarget, metric, limit string) ([]MetricMetadata, error) {
return f.ExpectedTargetsMetadataResult, f.ExpectedTargetsMetadataError
}

func (f *FakeAPI) Metadata(ctx context.Context, metric, limit string) (map[string][]Metadata, error) {
return f.ExpectedMetadataResult, f.ExpectedMetadataError
}

func (f *FakeAPI) TSDB(ctx context.Context, opts ...Option) (TSDBResult, error) {
return f.ExpectedTSDBResult, f.ExpectedTSDBError
}

func (f *FakeAPI) WalReplay(ctx context.Context) (WalReplayStatus, error) {
return f.ExpectedWalReplayResult, f.ExpectedWalReplayError
}
114 changes: 114 additions & 0 deletions api/prometheus/v1/api_fake_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2019 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 v1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we do this, let's show/test how it can be used for importers.

Suggested change
package v1
package v1_test


import (
"context"
"errors"
"reflect"
"testing"
"time"

"github.com/prometheus/common/model"
)

func assertEqual(t *testing.T, a, b interface{}) {
Copy link
Member

@bwplotka bwplotka Apr 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cmp.Diff can be used (from github.com/google/go-cmp)

if !reflect.DeepEqual(a, b) {
t.Errorf("%v != %v", a, b)
}
}

func TestFakeAPI_Query(t *testing.T) {
tests := []struct {
name string
query string
expectedResult model.Value
expectedWarnings Warnings
expectedError error
}{
{
name: "Valid query",
query: "up == 1",
expectedResult: &model.String{Value: "1"},
},
{
name: "Query with no results, warning present",
query: "up == 0",
expectedResult: nil,
expectedWarnings: Warnings{"Warning: No data found for query, check if the time range is correct"},
expectedError: nil,
},
{
name: "Error query",
query: "invalid_query",
expectedError: errors.New("mock error"),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Setup FakeAPI
fakeAPI := &FakeAPI{
ExpectedQueryResult: tt.expectedResult,
ExpectedQueryWarnings: tt.expectedWarnings,
ExpectedQueryError: tt.expectedError,
}

result, warnings, err := fakeAPI.Query(context.Background(), tt.query, time.Now())
assertEqual(t, tt.expectedResult, result)
assertEqual(t, tt.expectedWarnings, warnings)
assertEqual(t, tt.expectedError, err)
})
}
}

func TestFakeAPI_LabelNames(t *testing.T) {
tests := []struct {
name string
matches []string
expectedLabels []string
expectedWarnings Warnings
expectedError error
}{
{
name: "Valid label names",
matches: []string{"up"},
expectedLabels: []string{"label1", "label2"},
expectedWarnings: nil,
expectedError: nil,
},
{
name: "Error in label names",
matches: []string{"error"},
expectedLabels: nil,
expectedWarnings: nil,
expectedError: errors.New("mock label error"),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Setup FakeAPI
fakeAPI := &FakeAPI{
ExpectedLabelNamesResult: tt.expectedLabels,
ExpectedLabelNamesWarnings: tt.expectedWarnings,
ExpectedLabelNamesError: tt.expectedError,
}
Comment on lines +102 to +106
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to highlight what's currently possible. Instead of the referenced example one could write a following code

// Somewhere before the test
type myFakeAPI struct {
     v1.API

    labelNamesFn(ctx context.Context, matches []string, startTime, endTime time.Time, opts ...Option) ([]string, Warnings, error)
}

func (f *myFakeAPI) LabelNames(ctx context.Context, matches []string, startTime, endTime time.Time, opts ...Option) ([]string, Warnings, error) {
	return f.labelNamesFn()
}

// No more methods needs to implemented, if the test uses only LabelNames....

			// In your test...
			fakeAPI := &myFakeAPI{
 						labelNamesFn: func(ctx context.Context, matches []string, startTime, endTime time.Time, opts ...Option) ([]string, Warnings, error) { return  tt.expectedLabels,tt.expectedWarnings, tt.expectedError}
			}

Then the question is... is it worth to host fake impl if it's as simple as this?


result, warnings, err := fakeAPI.LabelNames(context.Background(), tt.matches, time.Now(), time.Now())
assertEqual(t, tt.expectedLabels, result)
assertEqual(t, tt.expectedWarnings, warnings)
assertEqual(t, tt.expectedError, err)
})
}
}