treerack/eskip_test.go

749 lines
14 KiB
Go
Raw Normal View History

2017-07-15 21:49:08 +02:00
package treerack
2017-06-25 17:51:08 +02:00
import (
"bytes"
"errors"
"fmt"
"math/rand"
"strconv"
"strings"
"testing"
"github.com/zalando/skipper/eskip"
)
const (
maxID = 27
meanID = 9
setPathChance = 0.72
maxPathTags = 12
meanPathTags = 2
maxPathTag = 24
meanPathTag = 9
setHostChance = 0.5
maxHost = 48
meanHost = 24
setPathRegexpChance = 0.45
maxPathRegexp = 36
meanPathRegexp = 12
setMethodChance = 0.1
setHeadersChance = 0.3
maxHeadersLength = 6
meanHeadersLength = 1
maxHeaderKeyLength = 18
meanHeaderKeyLength = 12
maxHeaderValueLength = 48
meanHeaderValueLength = 6
setHeaderRegexpChance = 0.05
maxHeaderRegexpsLength = 3
meanHeaderRegexpsLength = 1
maxHeaderRegexpLength = 12
meanHeaderRegexpLength = 6
maxTermNameLength = 15
meanTermNameLength = 6
maxTermArgsLength = 6
meanTermArgsLength = 1
floatArgChance = 0.1
intArgChance = 0.3
maxTermStringLength = 24
meanTermStringLength = 6
maxPredicatesLength = 4
meanPredicatesLength = 1
maxFiltersLength = 18
meanFiltersLength = 3
loopBackendChance = 0.05
shuntBackendChance = 0.1
maxBackend = 48
meanBackend = 15
)
func takeChance(c float64) bool {
return rand.Float64() < c
}
func generateID() string {
return generateString(maxID, meanID)
}
func generatePath() string {
if !takeChance(setPathChance) {
return ""
}
l := randomLength(maxPathTags, meanPathTags)
p := append(make([]string, 0, l+1), "")
for i := 0; i < l; i++ {
p = append(p, generateString(maxPathTag, meanPathTag))
}
return strings.Join(p, "/")
}
func generateHostRegexps() []string {
if !takeChance(setHostChance) {
return nil
}
return []string{generateString(maxHost, meanHost)}
}
func generatePathRegexps() []string {
if !takeChance(setPathRegexpChance) {
return nil
}
return []string{generateString(maxPathRegexp, meanPathRegexp)}
}
func generateMethod() string {
if !takeChance(setMethodChance) {
return ""
}
methods := []string{"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"}
return methods[rand.Intn(len(methods))]
}
func generateHeaders() map[string]string {
if !takeChance(setHeadersChance) {
return nil
}
h := make(map[string]string)
for i := 0; i < randomLength(maxHeadersLength, meanHeadersLength); i++ {
h[generateString(maxHeaderKeyLength, meanHeaderKeyLength)] =
generateString(maxHeaderValueLength, meanHeaderValueLength)
}
return h
}
func generateHeaderRegexps() map[string][]string {
if !takeChance(setHeaderRegexpChance) {
return nil
}
h := make(map[string][]string)
for i := 0; i < randomLength(maxHeaderRegexpsLength, meanHeaderRegexpsLength); i++ {
k := generateString(maxHeaderKeyLength, meanHeaderKeyLength)
for i := 0; i < randomLength(maxHeaderRegexpLength, meanHeaderRegexpLength); i++ {
h[k] = append(h[k], generateString(maxHeaderValueLength, meanHeaderValueLength))
}
}
return h
}
func generateTerm() (string, []interface{}) {
n := generateString(maxTermNameLength, meanTermNameLength)
al := randomLength(maxTermArgsLength, meanTermArgsLength)
a := make([]interface{}, 0, al)
for i := 0; i < al; i++ {
at := rand.Float64()
switch {
case at < floatArgChance:
a = append(a, rand.NormFloat64())
case at < intArgChance:
a = append(a, rand.Int())
default:
a = append(a, generateString(maxTermStringLength, meanTermStringLength))
}
}
return n, a
}
func generatePredicates() []*eskip.Predicate {
l := randomLength(maxPredicatesLength, meanPredicatesLength)
p := make([]*eskip.Predicate, 0, l)
for i := 0; i < l; i++ {
pi := &eskip.Predicate{}
pi.Name, pi.Args = generateTerm()
p = append(p, pi)
}
return p
}
func generateFilters() []*eskip.Filter {
l := randomLength(maxFiltersLength, meanFiltersLength)
f := make([]*eskip.Filter, 0, l)
for i := 0; i < l; i++ {
fi := &eskip.Filter{}
fi.Name, fi.Args = generateTerm()
f = append(f, fi)
}
return f
}
func generateBackend() (eskip.BackendType, string) {
t := rand.Float64()
switch {
case t < loopBackendChance:
return eskip.LoopBackend, ""
case t < loopBackendChance+shuntBackendChance:
return eskip.ShuntBackend, ""
default:
return eskip.NetworkBackend, generateString(maxBackend, meanBackend)
}
}
func generateRoute() *eskip.Route {
r := &eskip.Route{}
r.Id = generateID()
r.Path = generatePath()
r.HostRegexps = generateHostRegexps()
r.PathRegexps = generatePathRegexps()
r.Method = generateMethod()
r.Headers = generateHeaders()
r.HeaderRegexps = generateHeaderRegexps()
r.Predicates = generatePredicates()
r.Filters = generateFilters()
r.BackendType, r.Backend = generateBackend()
return r
}
func generateEskip(l int) []*eskip.Route {
r := make([]*eskip.Route, 0, l)
for i := 0; i < l; i++ {
r = append(r, generateRoute())
}
return r
}
func parseEskipInt(s string) (int, error) {
i, err := strconv.ParseInt(s, 0, 64)
return int(i), err
}
func parseEskipFloat(s string) (float64, error) {
f, err := strconv.ParseFloat(s, 64)
return f, err
}
func unquote(s string, escapedChars string) (string, error) {
if len(s) < 2 {
return "", nil
}
b := make([]byte, 0, len(s)-2)
var escaped bool
for _, bi := range []byte(s[1 : len(s)-1]) {
if escaped {
switch bi {
case 'b':
bi = '\b'
case 'f':
bi = '\f'
case 'n':
bi = '\n'
case 'r':
bi = '\r'
case 't':
bi = '\t'
case 'v':
bi = '\v'
}
b = append(b, bi)
escaped = false
continue
}
for _, ec := range []byte(escapedChars) {
if ec == bi {
return "", errors.New("invalid quote")
}
}
if bi == '\\' {
escaped = true
continue
}
b = append(b, bi)
}
return string(b), nil
}
func unquoteString(s string) (string, error) {
return unquote(s, "\"")
}
func unquoteRegexp(s string) (string, error) {
return unquote(s, "/")
}
func nodeToArg(n *Node) (interface{}, error) {
switch n.Name {
case "int":
return parseEskipInt(n.Text())
case "float":
return parseEskipFloat(n.Text())
case "string":
return unquoteString(n.Text())
case "regexp":
return unquoteRegexp(n.Text())
default:
return nil, errors.New("invalid arg")
}
}
func nodeToTerm(n *Node) (string, []interface{}, error) {
if len(n.Nodes) < 1 || n.Nodes[0].Name != "symbol" {
return "", nil, errors.New("invalid term")
}
name := n.Nodes[0].Text()
var args []interface{}
for _, ni := range n.Nodes[1:] {
a, err := nodeToArg(ni)
if err != nil {
return "", nil, err
}
args = append(args, a)
}
return name, args, nil
}
func nodeToPredicate(r *eskip.Route, n *Node) error {
name, args, err := nodeToTerm(n)
if err != nil {
return err
}
switch name {
case "Path":
if len(args) != 1 {
return errors.New("invalid path predicate")
}
p, ok := args[0].(string)
if !ok {
return errors.New("invalid path predicate")
}
r.Path = p
case "Host":
if len(args) != 1 {
return errors.New("invalid host predicate")
}
h, ok := args[0].(string)
if !ok {
return errors.New("invalid host predicate")
}
r.HostRegexps = append(r.HostRegexps, h)
case "PathRegexp":
if len(args) != 1 {
return errors.New("invalid path regexp predicate")
}
p, ok := args[0].(string)
if !ok {
return errors.New("invalid path regexp predicate")
}
r.PathRegexps = append(r.PathRegexps, p)
case "Method":
if len(args) != 1 {
return errors.New("invalid method predicate")
}
m, ok := args[0].(string)
if !ok {
return errors.New("invalid method predicate")
}
r.Method = m
case "Header":
if len(args) != 2 {
return errors.New("invalid header predicate")
}
name, ok := args[0].(string)
if !ok {
return errors.New("invalid header predicate")
}
value, ok := args[1].(string)
if !ok {
return errors.New("invalid header predicate")
}
if r.Headers == nil {
r.Headers = make(map[string]string)
}
r.Headers[name] = value
case "HeaderRegexp":
if len(args) != 2 {
return errors.New("invalid header regexp predicate")
}
name, ok := args[0].(string)
if !ok {
return errors.New("invalid header regexp predicate")
}
value, ok := args[1].(string)
if !ok {
return errors.New("invalid header regexp predicate")
}
if r.HeaderRegexps == nil {
r.HeaderRegexps = make(map[string][]string)
}
r.HeaderRegexps[name] = append(r.HeaderRegexps[name], value)
default:
r.Predicates = append(r.Predicates, &eskip.Predicate{Name: name, Args: args})
}
return nil
}
func nodeToFilter(n *Node) (*eskip.Filter, error) {
name, args, err := nodeToTerm(n)
if err != nil {
return nil, err
}
return &eskip.Filter{Name: name, Args: args}, nil
}
func nodeToBackend(r *eskip.Route, n *Node) error {
switch n.Name {
case "string":
b, err := unquoteString(n.Text())
if err != nil {
return err
}
r.BackendType = eskip.NetworkBackend
r.Backend = b
case "shunt":
r.BackendType = eskip.ShuntBackend
case "loopback":
r.BackendType = eskip.LoopBackend
default:
return errors.New("invalid backend type")
}
return nil
}
func nodeToEskipDefinition(n *Node) (*eskip.Route, error) {
ns := n.Nodes
if len(ns) < 2 || len(ns[1].Nodes) == 0 {
return nil, fmt.Errorf("invalid definition length: %d", len(ns))
}
r := &eskip.Route{}
if ns[0].Name != "symbol" {
return nil, errors.New("invalid definition id")
}
r.Id, ns = ns[0].Text(), ns[1].Nodes
predicates:
for i, ni := range ns {
switch ni.Name {
case "predicate":
if err := nodeToPredicate(r, ni); err != nil {
return nil, err
}
case "filter", "string", "shunt", "loopback":
ns = ns[i:]
break predicates
default:
return nil, errors.New("invalid definition item among predicates")
}
}
filters:
for i, ni := range ns {
switch ni.Name {
case "filter":
f, err := nodeToFilter(ni)
if err != nil {
return nil, err
}
r.Filters = append(r.Filters, f)
case "string", "shunt", "loopback":
ns = ns[i:]
break filters
default:
return nil, errors.New("invalid definition item among filters")
}
}
if len(ns) != 1 {
return nil, fmt.Errorf("invalid definition backend, remaining definition length: %d, %s",
len(ns), n.Text())
}
if err := nodeToBackend(r, ns[0]); err != nil {
return nil, err
}
return r, nil
}
2017-10-27 17:30:24 +02:00
func eskipTreeToEskip(n []*Node) ([]*eskip.Route, error) {
2017-06-25 17:51:08 +02:00
r := make([]*eskip.Route, 0, len(n))
for _, ni := range n {
d, err := nodeToEskipDefinition(ni)
if err != nil {
return nil, err
}
r = append(r, d)
}
return r, nil
}
func checkTerm(t *testing.T, gotName, expectedName string, gotArgs, expectedArgs []interface{}) {
if gotName != expectedName {
t.Error("invalid term name")
return
}
// legacy bug support
for i := len(expectedArgs) - 1; i >= 0; i-- {
if _, ok := expectedArgs[i].(int); ok {
expectedArgs = append(expectedArgs[:i], expectedArgs[i+1:]...)
continue
}
if v, ok := expectedArgs[i].(float64); ok && v < 0 {
gotArgs = append(gotArgs[:i], gotArgs[i+1:]...)
expectedArgs = append(expectedArgs[:i], expectedArgs[i+1:]...)
}
}
if len(gotArgs) != len(expectedArgs) {
t.Error("invalid term args length", len(gotArgs), len(expectedArgs))
return
}
for i, a := range gotArgs {
if a != expectedArgs[i] {
t.Error("invalid term arg")
return
}
}
}
func checkPredicates(t *testing.T, got, expected *eskip.Route) {
if got.Path != expected.Path {
t.Error("invalid path")
return
}
if len(got.HostRegexps) != len(expected.HostRegexps) {
t.Error("invalid host length")
return
}
for i, h := range got.HostRegexps {
if h != expected.HostRegexps[i] {
t.Error("invalid host")
return
}
}
if len(got.PathRegexps) != len(expected.PathRegexps) {
t.Error("invalid path regexp length", len(got.PathRegexps), len(expected.PathRegexps))
return
}
for i, h := range got.PathRegexps {
if h != expected.PathRegexps[i] {
t.Error("invalid path regexp")
return
}
}
if got.Method != expected.Method {
t.Error("invalid method")
return
}
if len(got.Headers) != len(expected.Headers) {
t.Error("invalid headers length")
return
}
for n, h := range got.Headers {
he, ok := expected.Headers[n]
if !ok {
t.Error("invalid header name")
return
}
if he != h {
t.Error("invalid header")
return
}
}
if len(got.HeaderRegexps) != len(expected.HeaderRegexps) {
t.Error("invalid header regexp length")
return
}
for n, h := range got.HeaderRegexps {
he, ok := expected.HeaderRegexps[n]
if !ok {
t.Error("invalid header regexp name")
return
}
if len(h) != len(he) {
t.Error("invalid header regexp item length")
return
}
for i, hi := range h {
if hi != he[i] {
t.Error("invalid header regexp")
return
}
}
}
if len(got.Predicates) != len(expected.Predicates) {
t.Error("invalid predicates length")
return
}
for i, p := range got.Predicates {
checkTerm(
t,
p.Name, expected.Predicates[i].Name,
p.Args, expected.Predicates[i].Args,
)
if t.Failed() {
t.Log(p.Name, expected.Predicates[i].Name)
t.Log(p.Args, expected.Predicates[i].Args)
return
}
}
}
func checkFilters(t *testing.T, got, expected []*eskip.Filter) {
if len(got) != len(expected) {
t.Error("invalid filters length")
return
}
for i, f := range got {
checkTerm(
t,
f.Name, expected[i].Name,
f.Args, expected[i].Args,
)
if t.Failed() {
return
}
}
}
func checkBackend(t *testing.T, got, expected *eskip.Route) {
if got.BackendType != expected.BackendType {
t.Error("invalid backend type")
return
}
if got.Backend != expected.Backend {
t.Error("invalid backend")
return
}
}
func checkRoute(t *testing.T, got, expected *eskip.Route) {
if got.Id != expected.Id {
t.Error("invalid route id")
return
}
checkPredicates(t, got, expected)
if t.Failed() {
return
}
checkFilters(t, got.Filters, expected.Filters)
if t.Failed() {
return
}
checkBackend(t, got, expected)
}
func checkEskip(t *testing.T, got, expected []*eskip.Route) {
if len(got) != len(expected) {
t.Error("invalid length", len(got), len(expected))
return
}
for i, ri := range got {
checkRoute(t, ri, expected[i])
if t.Failed() {
t.Log(ri.String())
t.Log(expected[i].String())
return
}
}
}
func TestEskip(t *testing.T) {
2017-10-27 17:25:20 +02:00
const count = 1 << 9
r := generateEskip(count)
2017-06-25 17:51:08 +02:00
e := eskip.Print(true, r...)
b := bytes.NewBufferString(e)
2017-10-27 17:30:24 +02:00
2017-11-05 04:25:42 +01:00
s, err := openSyntaxFile("examples/eskip.treerack")
2017-06-25 17:51:08 +02:00
if err != nil {
t.Error(err)
return
}
n, err := s.Parse(b)
if err != nil {
t.Error(err)
return
}
2017-10-27 17:30:24 +02:00
rback, err := eskipTreeToEskip(n.Nodes)
2017-06-25 17:51:08 +02:00
if err != nil {
t.Error(err)
return
}
checkEskip(t, rback, r)
}