1// Copyright 2013 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package rules
15
16import (
17	"context"
18	"fmt"
19	"testing"
20	"time"
21
22	"github.com/prometheus/prometheus/pkg/labels"
23	"github.com/prometheus/prometheus/pkg/timestamp"
24	"github.com/prometheus/prometheus/promql"
25	"github.com/prometheus/prometheus/util/teststorage"
26	"github.com/prometheus/prometheus/util/testutil"
27)
28
29func TestRuleEval(t *testing.T) {
30	storage := teststorage.New(t)
31	defer storage.Close()
32
33	opts := promql.EngineOpts{
34		Logger:        nil,
35		Reg:           nil,
36		MaxConcurrent: 10,
37		MaxSamples:    10,
38		Timeout:       10 * time.Second,
39	}
40
41	engine := promql.NewEngine(opts)
42	ctx, cancelCtx := context.WithCancel(context.Background())
43	defer cancelCtx()
44
45	now := time.Now()
46
47	suite := []struct {
48		name   string
49		expr   promql.Expr
50		labels labels.Labels
51		result promql.Vector
52	}{
53		{
54			name:   "nolabels",
55			expr:   &promql.NumberLiteral{Val: 1},
56			labels: labels.Labels{},
57			result: promql.Vector{promql.Sample{
58				Metric: labels.FromStrings("__name__", "nolabels"),
59				Point:  promql.Point{V: 1, T: timestamp.FromTime(now)},
60			}},
61		},
62		{
63			name:   "labels",
64			expr:   &promql.NumberLiteral{Val: 1},
65			labels: labels.FromStrings("foo", "bar"),
66			result: promql.Vector{promql.Sample{
67				Metric: labels.FromStrings("__name__", "labels", "foo", "bar"),
68				Point:  promql.Point{V: 1, T: timestamp.FromTime(now)},
69			}},
70		},
71	}
72
73	for _, test := range suite {
74		rule := NewRecordingRule(test.name, test.expr, test.labels)
75		result, err := rule.Eval(ctx, now, EngineQueryFunc(engine, storage), nil)
76		testutil.Ok(t, err)
77		testutil.Equals(t, result, test.result)
78	}
79}
80
81func TestRecordingRuleHTMLSnippet(t *testing.T) {
82	expr, err := promql.ParseExpr(`foo{html="<b>BOLD<b>"}`)
83	testutil.Ok(t, err)
84	rule := NewRecordingRule("testrule", expr, labels.FromStrings("html", "<b>BOLD</b>"))
85
86	const want = `record: <a href="/test/prefix/graph?g0.expr=testrule&g0.tab=1">testrule</a>
87expr: <a href="/test/prefix/graph?g0.expr=foo%7Bhtml%3D%22%3Cb%3EBOLD%3Cb%3E%22%7D&g0.tab=1">foo{html=&#34;&lt;b&gt;BOLD&lt;b&gt;&#34;}</a>
88labels:
89  html: '&lt;b&gt;BOLD&lt;/b&gt;'
90`
91
92	got := rule.HTMLSnippet("/test/prefix")
93	testutil.Assert(t, want == got, "incorrect HTML snippet; want:\n\n%s\n\ngot:\n\n%s", want, got)
94}
95
96// TestRuleEvalDuplicate tests for duplicate labels in recorded metrics, see #5529.
97func TestRuleEvalDuplicate(t *testing.T) {
98	storage := teststorage.New(t)
99	defer storage.Close()
100
101	opts := promql.EngineOpts{
102		Logger:        nil,
103		Reg:           nil,
104		MaxConcurrent: 10,
105		MaxSamples:    10,
106		Timeout:       10 * time.Second,
107	}
108
109	engine := promql.NewEngine(opts)
110	ctx, cancelCtx := context.WithCancel(context.Background())
111	defer cancelCtx()
112
113	now := time.Now()
114
115	expr, _ := promql.ParseExpr(`vector(0) or label_replace(vector(0),"test","x","","")`)
116	rule := NewRecordingRule("foo", expr, labels.FromStrings("test", "test"))
117	_, err := rule.Eval(ctx, now, EngineQueryFunc(engine, storage), nil)
118	testutil.NotOk(t, err)
119	e := fmt.Errorf("vector contains metrics with the same labelset after applying rule labels")
120	testutil.ErrorEqual(t, e, err)
121}
122