blob: 3260b9dfb7b732625ed8b6875eac6656d55caf90 [file] [log] [blame]
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package filter
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"slices"
"testing"
"time"
"gopkg.in/yaml.v3"
)
// yamlTests holds the contents of a testdata/*_test.yaml file.
type yamlTests struct {
Tests []yamlTest `yaml:"tests"`
}
// yamlTest holds the contents of a single test.
type yamlTest struct {
Description string `yaml:"description"`
Expr string `yaml:"expr"`
Error string `yaml:"error"`
Matches []int `yaml:"matches"`
Skip bool `yaml:"skip"`
}
type filterBasicTestData struct {
Resources []resource `json:"resources"`
}
type resource struct {
BoolField bool `json:"bool_field"`
CaseField string `json:"case_field"`
IntField int64 `json:"int_field"`
FloatField float64 `json:"float_field"`
EnumField string `json:"enum_field"`
StringField string `json:"string_field"`
TimestampField time.Time `json:"timestamp_field"`
Compound compound `json:"compound"`
False bool `json:"false"`
True bool `json:"true"`
Undefined *string `json:"undefined"`
Text string `json:"text"`
URL string `json:"url"`
Members []member `json:"members"`
Logical string `json:"logical"`
None *string `json:"none"`
QuoteDouble string `json:"quote_double"`
QuoteSingle string `json:"quote_single"`
Subject string `json:"subject"`
Words string `json:"words"`
UnicodeField string `json:"unicode_field"`
ExistsScalarInt int64 `json:"exists_scalar_int"`
ExistsScalarString string `json:"exists_scalar_string"`
ExistsScalarMessage member `json:"exists_scalar_message"`
ExistsArrayInt []int64 `json:"exists_array_int"`
ExistsArrayString []string `json:"exists_array_string"`
ExistsArrayMessage []member `json:"exists_array_message"`
}
type member struct {
First string `json:"first"`
Last string `json:"last"`
ID int64 `json:"id"`
}
type compound struct {
IntField int64 `json:"int_field"`
StringField string `json:"string_field"`
Num numberObjects `json:"num"`
Str stringObjects `json:"str"`
}
type numberObjects struct {
Array []float64 `json:"array"`
Dictionary map[string]float64 `json:"dictionary"`
}
type stringObjects struct {
Array []string `json:"array"`
Dictionary map[string]string `json:"dictionary"`
Value string `json:"value"`
}
func TestEvalBasic(t *testing.T) {
var data filterBasicTestData
unmarshalJSON(t, "basic_test.json", &data)
var tests yamlTests
unmarshalYAML(t, "basic_test.yaml", &tests)
runTests(t, tests.Tests, data.Resources)
}
// runTests runs a set of YAML tests on a set of input data.
func runTests[T any](t *testing.T, tests []yamlTest, data []T) {
var desc string
var idx int
for _, test := range tests {
if test.Description != "" {
desc = test.Description
idx = 1
if test.Expr == "" {
continue
}
}
if test.Skip {
idx++
continue
}
t.Run(fmt.Sprintf("%s %d", desc, idx), func(t *testing.T) {
runOneTest(t, &test, data)
})
idx++
}
}
// runOneTest runs on YAML test.
func runOneTest[T any](t *testing.T, test *yamlTest, data []T) {
e, err := ParseFilter(test.Expr)
if err != nil {
if test.Error != "" {
t.Logf("parse of %q failed (%v) when error is expected (%s)", test.Expr, err, test.Error)
return
}
t.Fatalf("can't parse %q: %v", test.Expr, err)
}
eval, msgs := Evaluator[T](e, nil)
if len(msgs) > 0 {
if test.Error != "" {
t.Logf("evaluation of %q failed when error is expected (%s)", test.Expr, test.Error)
} else {
t.Errorf("%d messages reported when building evaluator for %q", len(msgs), test.Expr)
}
for _, msg := range msgs {
t.Log(msg)
}
if test.Error != "" {
return
}
}
var matches []int
for i, d := range data {
if eval(d) {
matches = append(matches, i+1)
}
}
if !slices.Equal(matches, test.Matches) {
t.Errorf("got matches %v, want %v", matches, test.Matches)
}
}
// unmarshalJSON reads JSON encoded data from a testdata file into v.
func unmarshalJSON(t *testing.T, filename string, v any) {
f, err := os.Open(filepath.Join("testdata", filename))
if err != nil {
t.Fatal(err)
}
defer f.Close()
dec := json.NewDecoder(f)
dec.DisallowUnknownFields()
if err := dec.Decode(v); err != nil {
t.Fatal(err)
}
}
// unmarshalYAML reads YAML encoded data from a testdata file into v.
func unmarshalYAML(t *testing.T, filename string, v any) {
f, err := os.Open(filepath.Join("testdata", filename))
if err != nil {
t.Fatal(err)
}
defer f.Close()
dec := yaml.NewDecoder(f)
dec.KnownFields(true)
if err := dec.Decode(v); err != nil {
t.Fatal(err)
}
}