blob: 74330a90948b49651f99f018f66e270985071a2b [file] [log] [blame]
// Copyright 2020 The LUCI 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 main
import (
"bytes"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestParseStatsdMetric(t *testing.T) {
t.Parallel()
Convey("Happy path", t, func() {
roundTrip := func(line string) bool {
m := &StatsdMetric{}
read, err := ParseStatsdMetric([]byte(line), m)
So(err, ShouldBeNil)
So(read, ShouldEqual, len(line))
// There should be no special chars in parsed portions.
for _, n := range m.Name {
So(n, ShouldNotContain, '.')
So(n, ShouldNotContain, ':')
So(n, ShouldNotContain, '|')
}
So(m.Value, ShouldNotContain, '.')
So(m.Value, ShouldNotContain, ':')
So(m.Value, ShouldNotContain, '|')
// Reconstruct the original line from parsed components.
buf := bytes.Join(m.Name, []byte{'.'})
buf = append(buf, ':')
buf = append(buf, m.Value...)
buf = append(buf, '|')
switch m.Type {
case StatsdMetricGauge:
buf = append(buf, 'g')
case StatsdMetricCounter:
buf = append(buf, 'c')
case StatsdMetricTimer:
buf = append(buf, 'm', 's')
}
return string(buf) == line
}
So(roundTrip("a.bcd.xyz:123|g"), ShouldBeTrue)
So(roundTrip("a.b.c:123|c"), ShouldBeTrue)
So(roundTrip("a.b.c:123|ms"), ShouldBeTrue)
So(roundTrip("a:123|g"), ShouldBeTrue)
})
Convey("Parses one line only", t, func() {
buf := []byte("a:123|g\nstuff")
m := &StatsdMetric{}
read, err := ParseStatsdMetric(buf, m)
So(err, ShouldBeNil)
So(buf[read:], ShouldResemble, []byte("stuff"))
So(m, ShouldResemble, &StatsdMetric{
Name: [][]byte{{'a'}},
Type: StatsdMetricGauge,
Value: []byte("123"),
})
})
Convey("Skips junk", t, func() {
buf := []byte("a:123|g|skipped junk\nstuff")
m := &StatsdMetric{}
read, err := ParseStatsdMetric(buf, m)
So(err, ShouldBeNil)
So(buf[read:], ShouldResemble, []byte("stuff"))
So(m, ShouldResemble, &StatsdMetric{
Name: [][]byte{{'a'}},
Type: StatsdMetricGauge,
Value: []byte("123"),
})
})
Convey("ErrMalformedStatsdLine", t, func() {
buf := []byte("a:b:c\nstuff")
m := &StatsdMetric{}
read, err := ParseStatsdMetric(buf, m)
So(err, ShouldEqual, ErrMalformedStatsdLine)
So(buf[read:], ShouldResemble, []byte("stuff"))
})
Convey("ErrMalformedStatsdLine on empty line", t, func() {
buf := []byte("\nstuff")
m := &StatsdMetric{}
read, err := ParseStatsdMetric(buf, m)
So(err, ShouldEqual, ErrMalformedStatsdLine)
So(buf[read:], ShouldResemble, []byte("stuff"))
})
Convey("ErrMalformedStatsdLine on empty name component", t, func() {
call := func(pat string) error {
buf := []byte(pat + "\nother stuff")
read, err := ParseStatsdMetric(buf, &StatsdMetric{})
So(buf[read:], ShouldResemble, []byte("other stuff"))
return err
}
So(call("ab..cd:123|g"), ShouldEqual, ErrMalformedStatsdLine)
So(call("a.:123|g"), ShouldEqual, ErrMalformedStatsdLine)
So(call(".a:123|g"), ShouldEqual, ErrMalformedStatsdLine)
So(call(".:123|g"), ShouldEqual, ErrMalformedStatsdLine)
So(call(":|g"), ShouldEqual, ErrMalformedStatsdLine)
})
Convey("ErrUnsupportedType", t, func() {
buf := []byte("a:123|h\nstuff")
m := &StatsdMetric{}
read, err := ParseStatsdMetric(buf, m)
So(err, ShouldEqual, ErrUnsupportedType)
So(buf[read:], ShouldResemble, []byte("stuff"))
})
}