feat: add the check_output_matches
plugin #5
2 changed files with 177 additions and 0 deletions
|
@ -1,5 +1,10 @@
|
|||
package perfdata // import nocternity.net/gomonop/pkg/perfdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Performance data range.
|
||||
type Range struct {
|
||||
start string
|
||||
|
@ -60,3 +65,116 @@ func (r *Range) String() string {
|
|||
|
||||
return inside + start + ":" + r.end
|
||||
}
|
||||
|
||||
// A state of the range parser.
|
||||
type rangeParserState int
|
||||
|
||||
const (
|
||||
rpsInit = iota // Initial state
|
||||
rpsExpectStart // Expect the start of the range
|
||||
rpsInStart // Reading the start of the range
|
||||
rpsExpectColon // Expect the colon that separates the start and end
|
||||
rpsExpectEnd // Expect the end of the range
|
||||
rpsInEnd // Reading the end of the range
|
||||
)
|
||||
|
||||
// An error emitted by the range parser.
|
||||
type rangeParserError struct {
|
||||
input string
|
||||
position int
|
||||
message string
|
||||
}
|
||||
|
||||
// parseError creates a new range parser error.
|
||||
func parseError(input string, position int, message string) *rangeParserError {
|
||||
return &rangeParserError{
|
||||
input: input,
|
||||
position: position,
|
||||
message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// Error implements the error interface for the range parser error type.
|
||||
func (rpe *rangeParserError) Error() string {
|
||||
return fmt.Sprintf("in `%s' at position %d: %s", rpe.input, rpe.position, rpe.message)
|
||||
}
|
||||
|
||||
// Try to parse a string into a performance data range.
|
||||
func ParseRange(input string) (*Range, error) {
|
||||
runes := []rune(input)
|
||||
index := 0
|
||||
state := rpsInit
|
||||
parsed := Range{}
|
||||
startOfStart := 0
|
||||
startOfEnd := 0
|
||||
strBuilder := strings.Builder{}
|
||||
|
||||
for index < len(runes) {
|
||||
curRune := runes[index]
|
||||
|
||||
switch state {
|
||||
case rpsInit:
|
||||
if curRune == '@' {
|
||||
parsed.inside = true
|
||||
index += 1
|
||||
}
|
||||
state = rpsExpectStart
|
||||
|
||||
case rpsExpectStart:
|
||||
switch curRune {
|
||||
case ':':
|
||||
parsed.start = "0"
|
||||
state = rpsExpectEnd
|
||||
case '~':
|
||||
state = rpsExpectColon
|
||||
default:
|
||||
strBuilder.WriteRune(curRune)
|
||||
startOfStart = index
|
||||
state = rpsInStart
|
||||
}
|
||||
index += 1
|
||||
|
||||
case rpsInStart:
|
||||
switch curRune {
|
||||
case ':':
|
||||
parsed.start = strBuilder.String()
|
||||
if !valueCheck.MatchString(parsed.start) {
|
||||
return nil, parseError(input, startOfStart, "invalid start value")
|
||||
}
|
||||
strBuilder.Reset()
|
||||
state = rpsExpectEnd
|
||||
default:
|
||||
strBuilder.WriteRune(curRune)
|
||||
}
|
||||
index += 1
|
||||
|
||||
case rpsExpectColon:
|
||||
switch curRune {
|
||||
case ':':
|
||||
state = rpsExpectEnd
|
||||
index += 1
|
||||
default:
|
||||
return nil, parseError(input, index, "expected ':'")
|
||||
}
|
||||
|
||||
case rpsExpectEnd:
|
||||
startOfEnd = index
|
||||
state = rpsInEnd
|
||||
|
||||
case rpsInEnd:
|
||||
strBuilder.WriteRune(curRune)
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
||||
if state != rpsInEnd {
|
||||
return nil, parseError(input, index, "unexpected end of input")
|
||||
}
|
||||
|
||||
parsed.end = strBuilder.String()
|
||||
if !valueCheck.MatchString(parsed.end) {
|
||||
return nil, parseError(input, startOfEnd, "invalid end value")
|
||||
}
|
||||
|
||||
return &parsed, nil
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package perfdata // import nocternity.net/gomonop/pkg/perfdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRangeMaxInvalid(t *testing.T) {
|
||||
|
@ -65,3 +68,59 @@ func TestRangeString(t *testing.T) {
|
|||
assert.Equal(t, test.out, result, "Expected '%s', got '%s'", test.out, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRangeParserOk(t *testing.T) {
|
||||
type Test struct {
|
||||
in string
|
||||
out Range
|
||||
}
|
||||
tests := []Test{
|
||||
{in: ":0", out: Range{start: "0", end: "0"}},
|
||||
{in: "0:0", out: Range{start: "0", end: "0"}},
|
||||
{in: "~:0", out: Range{start: "", end: "0"}},
|
||||
{in: ":123", out: Range{start: "0", end: "123"}},
|
||||
{in: "0:123", out: Range{start: "0", end: "123"}},
|
||||
{in: "~:123", out: Range{start: "", end: "123"}},
|
||||
{in: "@:0", out: Range{start: "0", end: "0", inside: true}},
|
||||
{in: "@0:0", out: Range{start: "0", end: "0", inside: true}},
|
||||
{in: "@~:0", out: Range{start: "", end: "0", inside: true}},
|
||||
{in: "@:123", out: Range{start: "0", end: "123", inside: true}},
|
||||
{in: "@0:123", out: Range{start: "0", end: "123", inside: true}},
|
||||
{in: "@~:123", out: Range{start: "", end: "123", inside: true}},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result, err := ParseRange(test.in)
|
||||
require.NoError(t, err, "Expected no error, got '%v'", err)
|
||||
assert.Equal(t, test.out, *result, "Expected '%v', got '%v'", test.out, *result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRangeParserError(t *testing.T) {
|
||||
type Test struct {
|
||||
in string
|
||||
errPos int
|
||||
}
|
||||
tests := []Test{
|
||||
{in: "", errPos: 0},
|
||||
{in: ":", errPos: 1},
|
||||
{in: "x:1", errPos: 0},
|
||||
{in: ":~", errPos: 1},
|
||||
{in: "1", errPos: 1},
|
||||
{in: "@", errPos: 1},
|
||||
{in: "@:", errPos: 2},
|
||||
{in: "@x:1", errPos: 1},
|
||||
{in: "@:~", errPos: 2},
|
||||
{in: "@1", errPos: 2},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result, err := ParseRange(test.in)
|
||||
require.Error(t, err, "Expected error, got '%v'", err)
|
||||
assert.Nil(t, result, "Expected nil result, got '%v'", result)
|
||||
assert.True(
|
||||
t, strings.Contains(err.Error(), fmt.Sprintf("at position %d", test.errPos)),
|
||||
"Expected error to contain '%s', got '%s'", fmt.Sprintf("at position %d", test.errPos), err,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue