VictoriaMetrics/lib/protoparser/vmimport/parser_test.go
Aliaksandr Valialkin d9282027e6
app: follow-up after ec04fcac93
* Optimize fast path for /api/v1/import when importing numeric values
* Move the docs about the change from features to bugfixes at docs/CHANGELOG.md
* Update tests at lib/protoparser/vmimport

Updates https://github.com/VictoriaMetrics/VictoriaMetrics/issues/3161
2022-10-06 14:52:02 +03:00

280 lines
7.4 KiB
Go

package vmimport
import (
"fmt"
"math"
"reflect"
"testing"
)
func TestRowsUnmarshalFailure(t *testing.T) {
f := func(s string) {
t.Helper()
var rows Rows
rows.Unmarshal(s)
if len(rows.Rows) != 0 {
t.Fatalf("expecting zero rows; got %d rows", len(rows.Rows))
}
// Try again
rows.Unmarshal(s)
if len(rows.Rows) != 0 {
t.Fatalf("expecting zero rows; got %d rows", len(rows.Rows))
}
}
// Invalid json line
f("")
f("\n")
f("foo\n")
f("123")
f("[1,3]")
f("{}")
f("[]")
f(`{"foo":"bar"}`)
// Invalid metric
f(`{"metric":123,"values":[1,2],"timestamps":[3,4]}`)
f(`{"metric":[123],"values":[1,2],"timestamps":[3,4]}`)
f(`{"metric":[],"values":[1,2],"timestamps":[3,4]}`)
f(`{"metric":{},"values":[1,2],"timestamps":[3,4]}`)
f(`{"metric":null,"values":[1,2],"timestamps":[3,4]}`)
f(`{"values":[1,2],"timestamps":[3,4]}`)
// Invalid values
f(`{"metric":{"foo":"bar"},"values":1,"timestamps":[3,4]}`)
f(`{"metric":{"foo":"bar"},"values":{"x":1},"timestamps":[3,4]}`)
f(`{"metric":{"foo":"bar"},"values":{"x":1},"timestamps":[3,4]}`)
f(`{"metric":{"foo":"bar"},"values":null,"timestamps":[3,4]}`)
f(`{"metric":{"foo":"bar"},"timestamps":[3,4]}`)
f(`{"metric":{"foo":"bar"},"values":["foo"],"timestamps":[3]}`)
f(`{"metric":{"foo":"bar"},"values":null,"timestamps":[3,4]}`)
f(`{"metric":{"foo":"bar"},"values":"null","timestamps":[3,4]}`)
f(`{"metric":{"foo":"bar"},"values":"NaN","timestamps":[3,4]}`)
f(`{"metric":{"foo":"bar"},"values":[["NaN"]],"timestamps":[3,4]}`)
// Invalid timestamps
f(`{"metric":{"foo":"bar"},"values":[1,2],"timestamps":3}`)
f(`{"metric":{"foo":"bar"},"values":[1,2],"timestamps":false}`)
f(`{"metric":{"foo":"bar"},"values":[1,2],"timestamps":{}}`)
f(`{"metric":{"foo":"bar"},"values":[1,2]}`)
f(`{"metric":{"foo":"bar"},"values":[1,2],"timestamps":[1,"foo"]}`)
// values and timestamps count mismatch
f(`{"metric":{"foo":"bar"},"values":[],"timestamps":[]}`)
f(`{"metric":{"foo":"bar"},"values":[],"timestamps":[1]}`)
f(`{"metric":{"foo":"bar"},"values":[2],"timestamps":[]}`)
f(`{"metric":{"foo":"bar"},"values":[2],"timestamps":[3,4]}`)
f(`{"metric":{"foo":"bar"},"values":[2,3],"timestamps":[4]}`)
// Garbage after the line
f(`{"metric":{"foo":"bar"},"values":[2],"timestamps":[4]}{}`)
}
func TestRowsUnmarshalSuccess(t *testing.T) {
f := func(s string, rowsExpected *Rows) {
t.Helper()
var rows Rows
rows.Unmarshal(s)
if err := compareRows(&rows, rowsExpected); err != nil {
t.Fatalf("unexpected rows: %s;\ngot\n%+v;\nwant\n%+v", err, rows.Rows, rowsExpected.Rows)
}
// Try unmarshaling again
rows.Unmarshal(s)
if err := compareRows(&rows, rowsExpected); err != nil {
t.Fatalf("unexpected rows at second unmarshal: %s;\ngot\n%+v;\nwant\n%+v", err, rows.Rows, rowsExpected.Rows)
}
rows.Reset()
if len(rows.Rows) != 0 {
t.Fatalf("non-empty rows after reset: %+v", rows.Rows)
}
}
// Empty line
f("", &Rows{})
f("\n\n", &Rows{})
f("\n\r\n", &Rows{})
// Single line with a single tag
f(`{"metric":{"foo":"bar"},"values":[1.23],"timestamps":[456]}`, &Rows{
Rows: []Row{{
Tags: []Tag{{
Key: []byte("foo"),
Value: []byte("bar"),
}},
Values: []float64{1.23},
Timestamps: []int64{456},
}},
})
// Inf and nan, null values
f(`{"metric":{"foo":"bar"},"values":[Inf, -Inf, "Infinity", "-Infinity", NaN, "NaN", null, "null", 1.2],"timestamps":[456, 789, 123, 0, 1, 42, 2, 3, 7]}`, &Rows{
Rows: []Row{{
Tags: []Tag{{
Key: []byte("foo"),
Value: []byte("bar"),
}},
Values: []float64{inf, -inf, inf, -inf, nan, nan, nan, nan, 1.2},
Timestamps: []int64{456, 789, 123, 0, 1, 42, 2, 3, 7},
}},
})
// Line with multiple tags
f(`{"metric":{"foo":"bar","baz":"xx"},"values":[1.23, -3.21],"timestamps" : [456,789]}`, &Rows{
Rows: []Row{{
Tags: []Tag{
{
Key: []byte("foo"),
Value: []byte("bar"),
},
{
Key: []byte("baz"),
Value: []byte("xx"),
},
},
Values: []float64{1.23, -3.21},
Timestamps: []int64{456, 789},
}},
})
// Multiple lines
f(`{"metric":{"foo":"bar","baz":"xx"},"values":[1.23, -3.21],"timestamps" : [456,789]}
{"metric":{"__name__":"xx"},"values":[34],"timestamps" : [11]}
`, &Rows{
Rows: []Row{
{
Tags: []Tag{
{
Key: []byte("foo"),
Value: []byte("bar"),
},
{
Key: []byte("baz"),
Value: []byte("xx"),
},
},
Values: []float64{1.23, -3.21},
Timestamps: []int64{456, 789},
},
{
Tags: []Tag{
{
Key: []byte("__name__"),
Value: []byte("xx"),
},
},
Values: []float64{34},
Timestamps: []int64{11},
},
},
})
// Multiple lines with invalid line in the middle.
f(`{"metric":{"xfoo":"bar","baz":"xx"},"values":[1.232, -3.21],"timestamps" : [456,7890]}
garbage here
{"metric":{"__name__":"xxy"},"values":[34],"timestamps" : [111]}`, &Rows{
Rows: []Row{
{
Tags: []Tag{
{
Key: []byte("xfoo"),
Value: []byte("bar"),
},
{
Key: []byte("baz"),
Value: []byte("xx"),
},
},
Values: []float64{1.232, -3.21},
Timestamps: []int64{456, 7890},
},
{
Tags: []Tag{
{
Key: []byte("__name__"),
Value: []byte("xxy"),
},
},
Values: []float64{34},
Timestamps: []int64{111},
},
},
})
// No newline after the second line.
f(`{"metric":{"foo":"bar","baz":"xx"},"values":[1.23, -3.21],"timestamps" : [456,789]}
{"metric":{"__name__":"xx"},"values":[34],"timestamps" : [11]}`, &Rows{
Rows: []Row{
{
Tags: []Tag{
{
Key: []byte("foo"),
Value: []byte("bar"),
},
{
Key: []byte("baz"),
Value: []byte("xx"),
},
},
Values: []float64{1.23, -3.21},
Timestamps: []int64{456, 789},
},
{
Tags: []Tag{
{
Key: []byte("__name__"),
Value: []byte("xx"),
},
},
Values: []float64{34},
Timestamps: []int64{11},
},
},
})
}
func compareRows(rows, rowsExpected *Rows) error {
if len(rows.Rows) != len(rowsExpected.Rows) {
return fmt.Errorf("unexpected number of rows; got %d; want %d", len(rows.Rows), len(rowsExpected.Rows))
}
for i, row := range rows.Rows {
rowExpected := rowsExpected.Rows[i]
if err := compareSingleRow(&row, &rowExpected); err != nil {
return fmt.Errorf("unexpected row at position #%d: %w", i, err)
}
}
return nil
}
func compareSingleRow(row, rowExpected *Row) error {
if !reflect.DeepEqual(row.Tags, rowExpected.Tags) {
return fmt.Errorf("unexpected tags; got %q; want %q", row.Tags, rowExpected.Tags)
}
if !reflect.DeepEqual(row.Timestamps, rowExpected.Timestamps) {
return fmt.Errorf("unexpected timestamps; got %d; want %d", row.Timestamps, rowExpected.Timestamps)
}
if err := compareValues(row.Values, rowExpected.Values); err != nil {
return fmt.Errorf("unexpected values; got %v; want %v", row.Values, rowExpected.Values)
}
return nil
}
func compareValues(values, valuesExpected []float64) error {
if len(values) != len(valuesExpected) {
return fmt.Errorf("unexpected number of values; got %d; want %d", len(values), len(valuesExpected))
}
for i, v := range values {
vExpected := valuesExpected[i]
if math.IsNaN(v) {
if !math.IsNaN(vExpected) {
return fmt.Errorf("expecting NaN at position #%d; got %v", i, v)
}
} else if v != vExpected {
return fmt.Errorf("unepxected value at position #%d; got %v; want %v", i, v, vExpected)
}
}
return nil
}