Add --run flag to promtool test rules

Signed-off-by: João Vilaça <jvilaca@redhat.com>
This commit is contained in:
João Vilaça 2023-04-01 00:40:04 +01:00
parent f55ab22179
commit 81394ea1c5
5 changed files with 116 additions and 4 deletions

View file

@ -170,6 +170,7 @@ func main() {
testCmd := app.Command("test", "Unit testing.") testCmd := app.Command("test", "Unit testing.")
testRulesCmd := testCmd.Command("rules", "Unit tests for rules.") testRulesCmd := testCmd.Command("rules", "Unit tests for rules.")
testRulesRun := testRulesCmd.Flag("run", "If set, will only run test groups whose names match the regular expression. Can be specified multiple times.").Strings()
testRulesFiles := testRulesCmd.Arg( testRulesFiles := testRulesCmd.Arg(
"test-rule-file", "test-rule-file",
"The unit test file.", "The unit test file.",
@ -312,6 +313,7 @@ func main() {
EnableAtModifier: true, EnableAtModifier: true,
EnableNegativeOffset: true, EnableNegativeOffset: true,
}, },
*testRulesRun,
*testRulesFiles...), *testRulesFiles...),
) )

30
cmd/promtool/testdata/rules_run.yml vendored Normal file
View file

@ -0,0 +1,30 @@
rule_files:
- rules.yml
evaluation_interval: 1m
# Minimal test cases to check focus on a rule group.
tests:
- name: correct test
input_series:
- series: test
values: 1
promql_expr_test:
- expr: test
eval_time: 0
exp_samples:
- value: 1
labels: test
- name: wrong test
input_series:
- series: test
values: 0
promql_expr_test:
- expr: test
eval_time: 0
exp_samples:
- value: 1
labels: test

View file

@ -26,6 +26,7 @@ import (
"time" "time"
"github.com/go-kit/log" "github.com/go-kit/log"
"github.com/grafana/regexp"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
@ -38,11 +39,16 @@ import (
// RulesUnitTest does unit testing of rules based on the unit testing files provided. // RulesUnitTest does unit testing of rules based on the unit testing files provided.
// More info about the file format can be found in the docs. // More info about the file format can be found in the docs.
func RulesUnitTest(queryOpts promql.LazyLoaderOpts, files ...string) int { func RulesUnitTest(queryOpts promql.LazyLoaderOpts, runStrings []string, files ...string) int {
failed := false failed := false
var run *regexp.Regexp
if runStrings != nil {
run = regexp.MustCompile(strings.Join(runStrings, "|"))
}
for _, f := range files { for _, f := range files {
if errs := ruleUnitTest(f, queryOpts); errs != nil { if errs := ruleUnitTest(f, queryOpts, run); errs != nil {
fmt.Fprintln(os.Stderr, " FAILED:") fmt.Fprintln(os.Stderr, " FAILED:")
for _, e := range errs { for _, e := range errs {
fmt.Fprintln(os.Stderr, e.Error()) fmt.Fprintln(os.Stderr, e.Error())
@ -60,7 +66,7 @@ func RulesUnitTest(queryOpts promql.LazyLoaderOpts, files ...string) int {
return successExitCode return successExitCode
} }
func ruleUnitTest(filename string, queryOpts promql.LazyLoaderOpts) []error { func ruleUnitTest(filename string, queryOpts promql.LazyLoaderOpts, run *regexp.Regexp) []error {
fmt.Println("Unit Testing: ", filename) fmt.Println("Unit Testing: ", filename)
b, err := os.ReadFile(filename) b, err := os.ReadFile(filename)
@ -95,6 +101,10 @@ func ruleUnitTest(filename string, queryOpts promql.LazyLoaderOpts) []error {
// Testing. // Testing.
var errs []error var errs []error
for _, t := range unitTestInp.Tests { for _, t := range unitTestInp.Tests {
if !matchesRun(t.TestGroupName, run) {
continue
}
ers := t.test(evalInterval, groupOrderMap, queryOpts, unitTestInp.RuleFiles...) ers := t.test(evalInterval, groupOrderMap, queryOpts, unitTestInp.RuleFiles...)
if ers != nil { if ers != nil {
errs = append(errs, ers...) errs = append(errs, ers...)
@ -107,6 +117,14 @@ func ruleUnitTest(filename string, queryOpts promql.LazyLoaderOpts) []error {
return nil return nil
} }
func matchesRun(name string, run *regexp.Regexp) bool {
if run == nil {
return true
}
return run.MatchString(name)
}
// unitTestFile holds the contents of a single unit test file. // unitTestFile holds the contents of a single unit test file.
type unitTestFile struct { type unitTestFile struct {
RuleFiles []string `yaml:"rule_files"` RuleFiles []string `yaml:"rule_files"`

View file

@ -115,7 +115,60 @@ func TestRulesUnitTest(t *testing.T) {
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if got := RulesUnitTest(tt.queryOpts, tt.args.files...); got != tt.want { if got := RulesUnitTest(tt.queryOpts, nil, tt.args.files...); got != tt.want {
t.Errorf("RulesUnitTest() = %v, want %v", got, tt.want)
}
})
}
}
func TestRulesUnitTestRun(t *testing.T) {
type args struct {
run []string
files []string
}
tests := []struct {
name string
args args
queryOpts promql.LazyLoaderOpts
want int
}{
{
name: "Test all without run arg",
args: args{
run: nil,
files: []string{"./testdata/rules_run.yml"},
},
want: 1,
},
{
name: "Test all with run arg",
args: args{
run: []string{"correct", "wrong"},
files: []string{"./testdata/rules_run.yml"},
},
want: 1,
},
{
name: "Test correct",
args: args{
run: []string{"correct"},
files: []string{"./testdata/rules_run.yml"},
},
want: 0,
},
{
name: "Test wrong",
args: args{
run: []string{"wrong"},
files: []string{"./testdata/rules_run.yml"},
},
want: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := RulesUnitTest(tt.queryOpts, tt.args.run, tt.args.files...); got != tt.want {
t.Errorf("RulesUnitTest() = %v, want %v", got, tt.want) t.Errorf("RulesUnitTest() = %v, want %v", got, tt.want)
} }
}) })

View file

@ -352,6 +352,15 @@ Unit tests for rules.
###### Flags
| Flag | Description |
| --- | --- |
| <code class="text-nowrap">--run</code> | If set, will only run test groups whose names match the regular expression. Can be specified multiple times. |
###### Arguments ###### Arguments
| Argument | Description | Required | | Argument | Description | Required |