зеркало из https://github.com/github/vitess-gh.git
898 строки
32 KiB
Go
898 строки
32 KiB
Go
// Copyright 2012, Google Inc. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package tabletserver
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/youtube/vitess/go/vt/key"
|
|
"github.com/youtube/vitess/go/vt/tabletserver/planbuilder"
|
|
|
|
topodatapb "github.com/youtube/vitess/go/vt/proto/topodata"
|
|
)
|
|
|
|
func TestQueryRules(t *testing.T) {
|
|
qrs := NewQueryRules()
|
|
qr1 := NewQueryRule("rule 1", "r1", QRFail)
|
|
qr2 := NewQueryRule("rule 2", "r2", QRFail)
|
|
qrs.Add(qr1)
|
|
qrs.Add(qr2)
|
|
|
|
qrf := qrs.Find("r1")
|
|
if qrf != qr1 {
|
|
t.Errorf("want:\n%#v\ngot:\n%#v", qr1, qrf)
|
|
}
|
|
|
|
qrf = qrs.Find("r2")
|
|
if qrf != qr2 {
|
|
t.Errorf("want:\n%#v\ngot:\n%#v", qr2, qrf)
|
|
}
|
|
|
|
qrf = qrs.Find("unknown_rule")
|
|
if qrf != nil {
|
|
t.Fatalf("rule: unknown_rule does not exist, should get nil")
|
|
}
|
|
|
|
if qrs.rules[0] != qr1 {
|
|
t.Errorf("want:\n%#v\ngot:\n%#v", qr1, qrs.rules[0])
|
|
}
|
|
|
|
qrf = qrs.Delete("r1")
|
|
if qrf != qr1 {
|
|
t.Errorf("want:\n%#v\ngot:\n%#v", qr1, qrf)
|
|
}
|
|
|
|
if len(qrs.rules) != 1 {
|
|
t.Errorf("want 1, got %d", len(qrs.rules))
|
|
}
|
|
|
|
if qrs.rules[0] != qr2 {
|
|
t.Errorf("want:\n%#v\ngot:\n%#v", qr2, qrf)
|
|
}
|
|
|
|
qrf = qrs.Delete("unknown_rule")
|
|
if qrf != nil {
|
|
t.Fatalf("delete an unknown_rule, should return nil")
|
|
}
|
|
}
|
|
|
|
// TestCopy tests for deep copy
|
|
func TestCopy(t *testing.T) {
|
|
qrs1 := NewQueryRules()
|
|
qr1 := NewQueryRule("rule 1", "r1", QRFail)
|
|
qr1.AddPlanCond(planbuilder.PlanPassSelect)
|
|
qr1.AddTableCond("aa")
|
|
qr1.AddBindVarCond("a", true, false, QRNoOp, nil)
|
|
|
|
qr2 := NewQueryRule("rule 2", "r2", QRFail)
|
|
qrs1.Add(qr1)
|
|
qrs1.Add(qr2)
|
|
|
|
qrs2 := qrs1.Copy()
|
|
if !reflect.DeepEqual(qrs2, qrs1) {
|
|
t.Errorf("qrs1: %+v, not equal to %+v", qrs2, qrs1)
|
|
}
|
|
|
|
qrs1 = NewQueryRules()
|
|
qrs2 = qrs1.Copy()
|
|
if !reflect.DeepEqual(qrs2, qrs1) {
|
|
t.Errorf("qrs1: %+v, not equal to %+v", qrs2, qrs1)
|
|
}
|
|
}
|
|
|
|
func TestFilterByPlan(t *testing.T) {
|
|
qrs := NewQueryRules()
|
|
|
|
qr1 := NewQueryRule("rule 1", "r1", QRFail)
|
|
qr1.SetIPCond("123")
|
|
qr1.SetQueryCond("select")
|
|
qr1.AddPlanCond(planbuilder.PlanPassSelect)
|
|
qr1.AddBindVarCond("a", true, false, QRNoOp, nil)
|
|
|
|
qr2 := NewQueryRule("rule 2", "r2", QRFail)
|
|
qr2.AddPlanCond(planbuilder.PlanPassSelect)
|
|
qr2.AddPlanCond(planbuilder.PlanPKIn)
|
|
qr2.AddBindVarCond("a", true, false, QRNoOp, nil)
|
|
|
|
qr3 := NewQueryRule("rule 3", "r3", QRFail)
|
|
qr3.SetQueryCond("sele.*")
|
|
qr3.AddBindVarCond("a", true, false, QRNoOp, nil)
|
|
|
|
qr4 := NewQueryRule("rule 4", "r4", QRFail)
|
|
qr4.AddTableCond("b")
|
|
qr4.AddTableCond("c")
|
|
|
|
qrs.Add(qr1)
|
|
qrs.Add(qr2)
|
|
qrs.Add(qr3)
|
|
qrs.Add(qr4)
|
|
|
|
qrs1 := qrs.filterByPlan("select", planbuilder.PlanPassSelect, "a")
|
|
want := compacted(`[{
|
|
"Description":"rule 1",
|
|
"Name":"r1",
|
|
"RequestIP":"123",
|
|
"BindVarConds":[{
|
|
"Name":"a",
|
|
"OnAbsent":true,
|
|
"Operator":""
|
|
}],
|
|
"Action":"FAIL"
|
|
},{
|
|
"Description":"rule 2",
|
|
"Name":"r2",
|
|
"BindVarConds":[{
|
|
"Name":"a",
|
|
"OnAbsent":true,
|
|
"Operator":""
|
|
}],
|
|
"Action":"FAIL"
|
|
},{
|
|
"Description":"rule 3",
|
|
"Name":"r3",
|
|
"BindVarConds":[{
|
|
"Name":"a",
|
|
"OnAbsent":true,
|
|
"Operator":""
|
|
}],
|
|
"Action":"FAIL"
|
|
}]`)
|
|
got := marshalled(qrs1)
|
|
if got != want {
|
|
t.Errorf("qrs1:\n%s, want\n%s", got, want)
|
|
}
|
|
|
|
qrs1 = qrs.filterByPlan("insert", planbuilder.PlanPassSelect, "a")
|
|
want = compacted(`[{
|
|
"Description":"rule 2",
|
|
"Name":"r2",
|
|
"BindVarConds":[{
|
|
"Name":"a",
|
|
"OnAbsent":true,
|
|
"Operator":""
|
|
}],
|
|
"Action":"FAIL"
|
|
}]`)
|
|
got = marshalled(qrs1)
|
|
if got != want {
|
|
t.Errorf("qrs1:\n%s, want\n%s", got, want)
|
|
}
|
|
|
|
qrs1 = qrs.filterByPlan("insert", planbuilder.PlanPKIn, "a")
|
|
got = marshalled(qrs1)
|
|
if got != want {
|
|
t.Errorf("qrs1:\n%s, want\n%s", got, want)
|
|
}
|
|
|
|
qrs1 = qrs.filterByPlan("select", planbuilder.PlanInsertPK, "a")
|
|
want = compacted(`[{
|
|
"Description":"rule 3",
|
|
"Name":"r3",
|
|
"BindVarConds":[{
|
|
"Name":"a",
|
|
"OnAbsent":true,
|
|
"Operator":""
|
|
}],
|
|
"Action":"FAIL"
|
|
}]`)
|
|
got = marshalled(qrs1)
|
|
if got != want {
|
|
t.Errorf("qrs1:\n%s, want\n%s", got, want)
|
|
}
|
|
|
|
qrs1 = qrs.filterByPlan("sel", planbuilder.PlanInsertPK, "a")
|
|
if qrs1.rules != nil {
|
|
t.Errorf("want nil, got non-nil")
|
|
}
|
|
|
|
qrs1 = qrs.filterByPlan("table", planbuilder.PlanPassDML, "b")
|
|
want = compacted(`[{
|
|
"Description":"rule 4",
|
|
"Name":"r4",
|
|
"Action":"FAIL"
|
|
}]`)
|
|
got = marshalled(qrs1)
|
|
if got != want {
|
|
t.Errorf("qrs1:\n%s, want\n%s", got, want)
|
|
}
|
|
|
|
qr5 := NewQueryRule("rule 5", "r5", QRFail)
|
|
qrs.Add(qr5)
|
|
|
|
qrs1 = qrs.filterByPlan("sel", planbuilder.PlanInsertPK, "a")
|
|
want = compacted(`[{
|
|
"Description":"rule 5",
|
|
"Name":"r5",
|
|
"Action":"FAIL"
|
|
}]`)
|
|
got = marshalled(qrs1)
|
|
if got != want {
|
|
t.Errorf("qrs1:\n%s, want\n%s", got, want)
|
|
}
|
|
|
|
qrsnil1 := NewQueryRules()
|
|
if qrsnil2 := qrsnil1.filterByPlan("", planbuilder.PlanPassSelect, "a"); qrsnil2.rules != nil {
|
|
t.Errorf("want nil, got non-nil")
|
|
}
|
|
}
|
|
|
|
func TestQueryRule(t *testing.T) {
|
|
qr := NewQueryRule("rule 1", "r1", QRFail)
|
|
err := qr.SetIPCond("123")
|
|
if err != nil {
|
|
t.Errorf("unexpected: %v", err)
|
|
}
|
|
if !qr.requestIP.MatchString("123") {
|
|
t.Errorf("want match")
|
|
}
|
|
if qr.requestIP.MatchString("1234") {
|
|
t.Errorf("want no match")
|
|
}
|
|
if qr.requestIP.MatchString("12") {
|
|
t.Errorf("want no match")
|
|
}
|
|
err = qr.SetIPCond("[")
|
|
if err == nil {
|
|
t.Errorf("want error")
|
|
}
|
|
|
|
qr.AddPlanCond(planbuilder.PlanPassSelect)
|
|
qr.AddPlanCond(planbuilder.PlanInsertPK)
|
|
|
|
if qr.plans[0] != planbuilder.PlanPassSelect {
|
|
t.Errorf("want PASS_SELECT, got %s", qr.plans[0].String())
|
|
}
|
|
if qr.plans[1] != planbuilder.PlanInsertPK {
|
|
t.Errorf("want INSERT_PK, got %s", qr.plans[1].String())
|
|
}
|
|
|
|
qr.AddTableCond("a")
|
|
if qr.tableNames[0] != "a" {
|
|
t.Errorf("want a, got %s", qr.tableNames[0])
|
|
}
|
|
}
|
|
|
|
func TestBindVarStruct(t *testing.T) {
|
|
qr := NewQueryRule("rule 1", "r1", QRFail)
|
|
|
|
var err error
|
|
err = qr.AddBindVarCond("b", false, true, QRNoOp, nil)
|
|
err = qr.AddBindVarCond("a", true, false, QRNoOp, nil)
|
|
if err != nil {
|
|
t.Errorf("unexpected: %v", err)
|
|
}
|
|
if qr.bindVarConds[1].name != "a" {
|
|
t.Errorf("want a, got %s", qr.bindVarConds[1].name)
|
|
}
|
|
if !qr.bindVarConds[1].onAbsent {
|
|
t.Errorf("want true, got false")
|
|
}
|
|
if qr.bindVarConds[1].onMismatch {
|
|
t.Errorf("want false, got true")
|
|
}
|
|
if qr.bindVarConds[1].op != QRNoOp {
|
|
t.Errorf("exepecting no-op, got %v", qr.bindVarConds[1])
|
|
}
|
|
if qr.bindVarConds[1].value != nil {
|
|
t.Errorf("want nil, got %#v", qr.bindVarConds[1].value)
|
|
}
|
|
}
|
|
|
|
type BVCreation struct {
|
|
name string
|
|
onAbsent bool
|
|
onMismatch bool
|
|
op Operator
|
|
value interface{}
|
|
expecterr bool
|
|
}
|
|
|
|
var creationCases = []BVCreation{
|
|
{"a", true, true, QREqual, uint64(1), false},
|
|
{"a", true, true, QRNotEqual, uint64(1), false},
|
|
{"a", true, true, QRLessThan, uint64(1), false},
|
|
{"a", true, true, QRGreaterEqual, uint64(1), false},
|
|
{"a", true, true, QRGreaterThan, uint64(1), false},
|
|
{"a", true, true, QRLessEqual, uint64(1), false},
|
|
|
|
{"a", true, true, QREqual, int64(1), false},
|
|
{"a", true, true, QRNotEqual, int64(1), false},
|
|
{"a", true, true, QRLessThan, int64(1), false},
|
|
{"a", true, true, QRGreaterEqual, int64(1), false},
|
|
{"a", true, true, QRGreaterThan, int64(1), false},
|
|
{"a", true, true, QRLessEqual, int64(1), false},
|
|
|
|
{"a", true, true, QREqual, "a", false},
|
|
{"a", true, true, QRNotEqual, "a", false},
|
|
{"a", true, true, QRLessThan, "a", false},
|
|
{"a", true, true, QRGreaterEqual, "a", false},
|
|
{"a", true, true, QRGreaterThan, "a", false},
|
|
{"a", true, true, QRLessEqual, "a", false},
|
|
{"a", true, true, QRMatch, "a", false},
|
|
{"a", true, true, QRNoMatch, "a", false},
|
|
|
|
{"a", true, true, QRIn, &topodatapb.KeyRange{}, false},
|
|
{"a", true, true, QRNotIn, &topodatapb.KeyRange{}, false},
|
|
|
|
{"a", true, true, QRMatch, int64(1), true},
|
|
{"a", true, true, QRNoMatch, int64(1), true},
|
|
{"a", true, true, QRMatch, "[", true},
|
|
{"a", true, true, QRNoMatch, "[", true},
|
|
|
|
{"a", true, true, QRIn, int64(1), true},
|
|
{"a", true, true, QRNotIn, int64(1), true},
|
|
|
|
{"a", true, true, QREqual, int32(1), true},
|
|
}
|
|
|
|
func TestBVCreation(t *testing.T) {
|
|
qr := NewQueryRule("rule 1", "r1", QRFail)
|
|
for i, tcase := range creationCases {
|
|
err := qr.AddBindVarCond(tcase.name, tcase.onAbsent, tcase.onMismatch, tcase.op, tcase.value)
|
|
haserr := (err != nil)
|
|
if haserr != tcase.expecterr {
|
|
t.Errorf("test %d: got %v for %#v", i, haserr, tcase)
|
|
}
|
|
}
|
|
}
|
|
|
|
type BindVarTestCase struct {
|
|
bvc BindVarCond
|
|
bvval interface{}
|
|
expected bool
|
|
}
|
|
|
|
var bvtestcases = []BindVarTestCase{
|
|
{BindVarCond{"b", true, true, QRNoOp, nil}, 1, true},
|
|
{BindVarCond{"b", false, true, QRNoOp, nil}, 1, false},
|
|
{BindVarCond{"a", true, true, QRNoOp, nil}, 1, false},
|
|
{BindVarCond{"a", false, true, QRNoOp, nil}, 1, true},
|
|
|
|
{BindVarCond{"a", true, true, QREqual, bvcuint64(10)}, int(1), false},
|
|
{BindVarCond{"a", true, true, QREqual, bvcuint64(10)}, int8(1), false},
|
|
{BindVarCond{"a", true, true, QREqual, bvcuint64(10)}, int8(10), true},
|
|
{BindVarCond{"a", true, true, QREqual, bvcuint64(10)}, int16(1), false},
|
|
{BindVarCond{"a", true, true, QREqual, bvcuint64(10)}, int32(1), false},
|
|
{BindVarCond{"a", true, true, QREqual, bvcuint64(10)}, int64(1), false},
|
|
{BindVarCond{"a", true, true, QREqual, bvcuint64(10)}, uint64(1), false},
|
|
{BindVarCond{"a", true, true, QREqual, bvcuint64(10)}, int8(-1), false},
|
|
{BindVarCond{"a", true, true, QREqual, bvcuint64(10)}, "abc", true},
|
|
|
|
{BindVarCond{"a", true, true, QRNotEqual, bvcuint64(10)}, int8(1), true},
|
|
{BindVarCond{"a", true, true, QRNotEqual, bvcuint64(10)}, int8(10), false},
|
|
{BindVarCond{"a", true, true, QRNotEqual, bvcuint64(10)}, int8(11), true},
|
|
{BindVarCond{"a", true, true, QRNotEqual, bvcuint64(10)}, int8(-1), true},
|
|
|
|
{BindVarCond{"a", true, true, QRLessThan, bvcuint64(10)}, int8(1), true},
|
|
{BindVarCond{"a", true, true, QRLessThan, bvcuint64(10)}, int8(10), false},
|
|
{BindVarCond{"a", true, true, QRLessThan, bvcuint64(10)}, int8(11), false},
|
|
{BindVarCond{"a", true, true, QRLessThan, bvcuint64(10)}, int8(-1), true},
|
|
|
|
{BindVarCond{"a", true, true, QRGreaterEqual, bvcuint64(10)}, int8(1), false},
|
|
{BindVarCond{"a", true, true, QRGreaterEqual, bvcuint64(10)}, int8(10), true},
|
|
{BindVarCond{"a", true, true, QRGreaterEqual, bvcuint64(10)}, int8(11), true},
|
|
{BindVarCond{"a", true, true, QRGreaterEqual, bvcuint64(10)}, int8(-1), false},
|
|
|
|
{BindVarCond{"a", true, true, QRGreaterThan, bvcuint64(10)}, int8(1), false},
|
|
{BindVarCond{"a", true, true, QRGreaterThan, bvcuint64(10)}, int8(10), false},
|
|
{BindVarCond{"a", true, true, QRGreaterThan, bvcuint64(10)}, int8(11), true},
|
|
{BindVarCond{"a", true, true, QRGreaterThan, bvcuint64(10)}, int8(-1), false},
|
|
|
|
{BindVarCond{"a", true, true, QRLessEqual, bvcuint64(10)}, int8(1), true},
|
|
{BindVarCond{"a", true, true, QRLessEqual, bvcuint64(10)}, int8(10), true},
|
|
{BindVarCond{"a", true, true, QRLessEqual, bvcuint64(10)}, int8(11), false},
|
|
{BindVarCond{"a", true, true, QRLessEqual, bvcuint64(10)}, int8(-1), true},
|
|
|
|
{BindVarCond{"a", true, true, QREqual, bvcint64(10)}, int(1), false},
|
|
{BindVarCond{"a", true, true, QREqual, bvcint64(10)}, int8(1), false},
|
|
{BindVarCond{"a", true, true, QREqual, bvcint64(10)}, int8(10), true},
|
|
{BindVarCond{"a", true, true, QREqual, bvcint64(10)}, int16(1), false},
|
|
{BindVarCond{"a", true, true, QREqual, bvcint64(10)}, int32(1), false},
|
|
{BindVarCond{"a", true, true, QREqual, bvcint64(10)}, int64(1), false},
|
|
{BindVarCond{"a", true, true, QREqual, bvcint64(10)}, uint64(1), false},
|
|
{BindVarCond{"a", true, true, QREqual, bvcint64(10)}, uint64(0xFFFFFFFFFFFFFFFF), false},
|
|
{BindVarCond{"a", true, true, QREqual, bvcint64(10)}, "abc", true},
|
|
|
|
{BindVarCond{"a", true, true, QRNotEqual, bvcint64(10)}, int8(1), true},
|
|
{BindVarCond{"a", true, true, QRNotEqual, bvcint64(10)}, int8(10), false},
|
|
{BindVarCond{"a", true, true, QRNotEqual, bvcint64(10)}, int8(11), true},
|
|
{BindVarCond{"a", true, true, QRNotEqual, bvcint64(10)}, uint64(0xFFFFFFFFFFFFFFFF), true},
|
|
|
|
{BindVarCond{"a", true, true, QRLessThan, bvcint64(10)}, int8(1), true},
|
|
{BindVarCond{"a", true, true, QRLessThan, bvcint64(10)}, int8(10), false},
|
|
{BindVarCond{"a", true, true, QRLessThan, bvcint64(10)}, int8(11), false},
|
|
{BindVarCond{"a", true, true, QRLessThan, bvcint64(10)}, uint64(0xFFFFFFFFFFFFFFFF), false},
|
|
|
|
{BindVarCond{"a", true, true, QRGreaterEqual, bvcint64(10)}, int8(1), false},
|
|
{BindVarCond{"a", true, true, QRGreaterEqual, bvcint64(10)}, int8(10), true},
|
|
{BindVarCond{"a", true, true, QRGreaterEqual, bvcint64(10)}, int8(11), true},
|
|
{BindVarCond{"a", true, true, QRGreaterEqual, bvcint64(10)}, uint64(0xFFFFFFFFFFFFFFFF), true},
|
|
|
|
{BindVarCond{"a", true, true, QRGreaterThan, bvcint64(10)}, int8(1), false},
|
|
{BindVarCond{"a", true, true, QRGreaterThan, bvcint64(10)}, int8(10), false},
|
|
{BindVarCond{"a", true, true, QRGreaterThan, bvcint64(10)}, int8(11), true},
|
|
{BindVarCond{"a", true, true, QRGreaterThan, bvcint64(10)}, uint64(0xFFFFFFFFFFFFFFFF), true},
|
|
|
|
{BindVarCond{"a", true, true, QRLessEqual, bvcint64(10)}, int8(1), true},
|
|
{BindVarCond{"a", true, true, QRLessEqual, bvcint64(10)}, int8(10), true},
|
|
{BindVarCond{"a", true, true, QRLessEqual, bvcint64(10)}, int8(11), false},
|
|
{BindVarCond{"a", true, true, QRLessEqual, bvcint64(10)}, uint64(0xFFFFFFFFFFFFFFFF), false},
|
|
|
|
{BindVarCond{"a", true, true, QREqual, bvcstring("b")}, "a", false},
|
|
{BindVarCond{"a", true, true, QREqual, bvcstring("b")}, "b", true},
|
|
{BindVarCond{"a", true, true, QREqual, bvcstring("b")}, "c", false},
|
|
{BindVarCond{"a", true, true, QREqual, bvcstring("b")}, []byte("a"), false},
|
|
{BindVarCond{"a", true, true, QREqual, bvcstring("b")}, []byte("b"), true},
|
|
{BindVarCond{"a", true, true, QREqual, bvcstring("b")}, []byte("c"), false},
|
|
{BindVarCond{"a", true, true, QREqual, bvcstring("b")}, int8(1), true},
|
|
|
|
{BindVarCond{"a", true, true, QRNotEqual, bvcstring("b")}, "a", true},
|
|
{BindVarCond{"a", true, true, QRNotEqual, bvcstring("b")}, "b", false},
|
|
{BindVarCond{"a", true, true, QRNotEqual, bvcstring("b")}, "c", true},
|
|
|
|
{BindVarCond{"a", true, true, QRLessThan, bvcstring("b")}, "a", true},
|
|
{BindVarCond{"a", true, true, QRLessThan, bvcstring("b")}, "b", false},
|
|
{BindVarCond{"a", true, true, QRLessThan, bvcstring("b")}, "c", false},
|
|
|
|
{BindVarCond{"a", true, true, QRGreaterEqual, bvcstring("b")}, "a", false},
|
|
{BindVarCond{"a", true, true, QRGreaterEqual, bvcstring("b")}, "b", true},
|
|
{BindVarCond{"a", true, true, QRGreaterEqual, bvcstring("b")}, "c", true},
|
|
|
|
{BindVarCond{"a", true, true, QRGreaterThan, bvcstring("b")}, "a", false},
|
|
{BindVarCond{"a", true, true, QRGreaterThan, bvcstring("b")}, "b", false},
|
|
{BindVarCond{"a", true, true, QRGreaterThan, bvcstring("b")}, "c", true},
|
|
|
|
{BindVarCond{"a", true, true, QRLessEqual, bvcstring("b")}, "a", true},
|
|
{BindVarCond{"a", true, true, QRLessEqual, bvcstring("b")}, "b", true},
|
|
{BindVarCond{"a", true, true, QRLessEqual, bvcstring("b")}, "c", false},
|
|
|
|
{BindVarCond{"a", true, true, QRMatch, makere("a.*")}, "c", false},
|
|
{BindVarCond{"a", true, true, QRMatch, makere("a.*")}, "a", true},
|
|
{BindVarCond{"a", true, true, QRMatch, makere("a.*")}, int8(1), true},
|
|
|
|
{BindVarCond{"a", true, true, QRNoMatch, makere("a.*")}, "c", true},
|
|
{BindVarCond{"a", true, true, QRNoMatch, makere("a.*")}, "a", false},
|
|
{BindVarCond{"a", true, true, QRNoMatch, makere("a.*")}, int8(1), true},
|
|
|
|
{BindVarCond{"a", true, true, QRIn, numKeyRange(0x4000000000000000, 0x6000000000000000)}, uint64(0), false},
|
|
{BindVarCond{"a", true, true, QRIn, numKeyRange(0x4000000000000000, 0x6000000000000000)}, uint64(0x5000000000000000), true},
|
|
{BindVarCond{"a", true, true, QRIn, numKeyRange(0x4000000000000000, 0x6000000000000000)}, uint64(0x7000000000000000), false},
|
|
{BindVarCond{"a", true, true, QRIn, numKeyRange(0x4000000000000000, 0x6000000000000000)}, int(-1), false},
|
|
{BindVarCond{"a", true, true, QRIn, numKeyRange(0x4000000000000000, 0x6000000000000000)}, int16(-1), false},
|
|
{BindVarCond{"a", true, true, QRIn, numKeyRange(0x4000000000000000, 0x6000000000000000)}, int32(-1), false},
|
|
{BindVarCond{"a", true, true, QRIn, numKeyRange(0x4000000000000000, 0x6000000000000000)}, int64(-1), false},
|
|
{BindVarCond{"a", true, true, QRIn, strKeyRange("b", "d")}, "a", false},
|
|
{BindVarCond{"a", true, true, QRIn, strKeyRange("b", "d")}, "c", true},
|
|
{BindVarCond{"a", true, true, QRIn, strKeyRange("b", "d")}, "e", false},
|
|
{BindVarCond{"a", true, true, QRIn, strKeyRange("b", "d")}, float64(1.0), true},
|
|
|
|
{BindVarCond{"a", true, true, QRNotIn, numKeyRange(0x4000000000000000, 0x6000000000000000)}, uint64(0), true},
|
|
{BindVarCond{"a", true, true, QRNotIn, numKeyRange(0x4000000000000000, 0x6000000000000000)}, uint64(0x5000000000000000), false},
|
|
{BindVarCond{"a", true, true, QRNotIn, numKeyRange(0x4000000000000000, 0x6000000000000000)}, uint64(0x7000000000000000), true},
|
|
{BindVarCond{"a", true, true, QRNotIn, numKeyRange(0x4000000000000000, 0x6000000000000000)}, int(-1), true},
|
|
{BindVarCond{"a", true, true, QRNotIn, numKeyRange(0x4000000000000000, 0x6000000000000000)}, int16(-1), true},
|
|
{BindVarCond{"a", true, true, QRNotIn, numKeyRange(0x4000000000000000, 0x6000000000000000)}, int32(-1), true},
|
|
{BindVarCond{"a", true, true, QRNotIn, numKeyRange(0x4000000000000000, 0x6000000000000000)}, int64(-1), true},
|
|
{BindVarCond{"a", true, true, QRNotIn, strKeyRange("b", "d")}, "a", true},
|
|
{BindVarCond{"a", true, true, QRNotIn, strKeyRange("b", "d")}, "c", false},
|
|
{BindVarCond{"a", true, true, QRNotIn, strKeyRange("b", "d")}, "e", true},
|
|
{BindVarCond{"a", true, true, QRNotIn, strKeyRange("b", "d")}, float64(1.0), true},
|
|
}
|
|
|
|
func makere(s string) bvcre {
|
|
re, _ := regexp.Compile(s)
|
|
return bvcre{re}
|
|
}
|
|
|
|
func numKeyRange(start, end uint64) *bvcKeyRange {
|
|
kr := topodatapb.KeyRange{
|
|
Start: key.Uint64Key(start).Bytes(),
|
|
End: key.Uint64Key(end).Bytes(),
|
|
}
|
|
b := bvcKeyRange(kr)
|
|
return &b
|
|
}
|
|
|
|
func strKeyRange(start, end string) *bvcKeyRange {
|
|
kr := topodatapb.KeyRange{
|
|
Start: []byte(start),
|
|
End: []byte(end),
|
|
}
|
|
b := bvcKeyRange(kr)
|
|
return &b
|
|
}
|
|
|
|
func TestBVConditions(t *testing.T) {
|
|
bv := make(map[string]interface{})
|
|
for i, tcase := range bvtestcases {
|
|
bv["a"] = tcase.bvval
|
|
if bvMatch(tcase.bvc, bv) != tcase.expected {
|
|
t.Errorf("test %d: want %v for %#v, %#v", i, tcase.expected, tcase.bvc, tcase.bvval)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAction(t *testing.T) {
|
|
qrs := NewQueryRules()
|
|
|
|
qr1 := NewQueryRule("rule 1", "r1", QRFail)
|
|
qr1.SetIPCond("123")
|
|
|
|
qr2 := NewQueryRule("rule 2", "r2", QRFailRetry)
|
|
qr2.SetUserCond("user")
|
|
|
|
qr3 := NewQueryRule("rule 3", "r3", QRFail)
|
|
qr3.AddBindVarCond("a", true, true, QREqual, uint64(1))
|
|
|
|
qrs.Add(qr1)
|
|
qrs.Add(qr2)
|
|
qrs.Add(qr3)
|
|
|
|
bv := make(map[string]interface{})
|
|
bv["a"] = uint64(0)
|
|
action, desc := qrs.getAction("123", "user1", bv)
|
|
if action != QRFail {
|
|
t.Errorf("want fail")
|
|
}
|
|
if desc != "rule 1" {
|
|
t.Errorf("want rule 1, got %s", desc)
|
|
}
|
|
action, desc = qrs.getAction("1234", "user", bv)
|
|
if action != QRFailRetry {
|
|
t.Errorf("want fail_retry")
|
|
}
|
|
if desc != "rule 2" {
|
|
t.Errorf("want rule 2, got %s", desc)
|
|
}
|
|
action, desc = qrs.getAction("1234", "user1", bv)
|
|
if action != QRContinue {
|
|
t.Errorf("want continue")
|
|
}
|
|
bv["a"] = uint64(1)
|
|
action, desc = qrs.getAction("1234", "user1", bv)
|
|
if action != QRFail {
|
|
t.Errorf("want fail")
|
|
}
|
|
if desc != "rule 3" {
|
|
t.Errorf("want rule 2, got %s", desc)
|
|
}
|
|
}
|
|
|
|
func TestImport(t *testing.T) {
|
|
var qrs = NewQueryRules()
|
|
jsondata := `[{
|
|
"Description": "desc1",
|
|
"Name": "name1",
|
|
"RequestIP": "123.123.123",
|
|
"User": "user",
|
|
"Query": "query",
|
|
"Plans": ["PASS_SELECT", "INSERT_PK"],
|
|
"TableNames":["a", "b"],
|
|
"BindVarConds": [{
|
|
"Name": "bvname1",
|
|
"OnAbsent": true,
|
|
"Operator": ""
|
|
},{
|
|
"Name": "bvname2",
|
|
"OnAbsent": true,
|
|
"OnMismatch": true,
|
|
"Operator": "==",
|
|
"Value": 123
|
|
}],
|
|
"Action": "FAIL_RETRY"
|
|
},{
|
|
"Description": "desc2",
|
|
"Name": "name2",
|
|
"Action": "FAIL"
|
|
}]`
|
|
err := qrs.UnmarshalJSON([]byte(jsondata))
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
got := marshalled(qrs)
|
|
want := compacted(jsondata)
|
|
if got != want {
|
|
t.Errorf("qrs:\n%s, want\n%s", got, want)
|
|
}
|
|
}
|
|
|
|
type ValidJSONCase struct {
|
|
input string
|
|
op Operator
|
|
typ int
|
|
}
|
|
|
|
const (
|
|
UINT = iota
|
|
INT
|
|
STR
|
|
REGEXP
|
|
KEYRANGE
|
|
)
|
|
|
|
var validjsons = []ValidJSONCase{
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": "==", "Value": 18446744073709551615}]}]`, QREqual, UINT},
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": "!=", "Value": 18446744073709551615}]}]`, QRNotEqual, UINT},
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": "<", "Value": 18446744073709551615}]}]`, QRLessThan, UINT},
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": ">=", "Value": 18446744073709551615}]}]`, QRGreaterEqual, UINT},
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": ">", "Value": 18446744073709551615}]}]`, QRGreaterThan, UINT},
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": "<=", "Value": 18446744073709551615}]}]`, QRLessEqual, UINT},
|
|
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": "==", "Value": -123}]}]`, QREqual, INT},
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": "!=", "Value": -123}]}]`, QRNotEqual, INT},
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": "<", "Value": -123}]}]`, QRLessThan, INT},
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": ">=", "Value": -123}]}]`, QRGreaterEqual, INT},
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": ">", "Value": -123}]}]`, QRGreaterThan, INT},
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": "<=", "Value": -123}]}]`, QRLessEqual, INT},
|
|
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": "==", "Value": "123"}]}]`, QREqual, STR},
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": "!=", "Value": "123"}]}]`, QRNotEqual, STR},
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": "<", "Value": "123"}]}]`, QRLessThan, STR},
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": ">=", "Value": "123"}]}]`, QRGreaterEqual, STR},
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": ">", "Value": "123"}]}]`, QRGreaterThan, STR},
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": "<=", "Value": "123"}]}]`, QRLessEqual, STR},
|
|
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": "MATCH", "Value": "123"}]}]`, QRMatch, REGEXP},
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": "NOMATCH", "Value": "123"}]}]`, QRNoMatch, REGEXP},
|
|
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": "IN", "Value": {"Start": "1", "End": "2"}}]}]`, QRIn, KEYRANGE},
|
|
{`[{"BindVarConds": [{"Name": "bvname1", "OnAbsent": true, "OnMismatch": true, "Operator": "NOTIN", "Value": {"Start": "1", "End": "2"}}]}]`, QRNotIn, KEYRANGE},
|
|
}
|
|
|
|
func TestValidJSON(t *testing.T) {
|
|
for i, tcase := range validjsons {
|
|
qrs := NewQueryRules()
|
|
err := qrs.UnmarshalJSON([]byte(tcase.input))
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error for case %d: %v", i, err)
|
|
}
|
|
bvc := qrs.rules[0].bindVarConds[0]
|
|
if bvc.op != tcase.op {
|
|
t.Errorf("want %v, got %v", tcase.op, bvc.op)
|
|
}
|
|
switch tcase.typ {
|
|
case UINT:
|
|
if bvc.value.(bvcuint64) != bvcuint64(18446744073709551615) {
|
|
t.Errorf("want %v, got %v", uint64(18446744073709551615), bvc.value.(bvcuint64))
|
|
}
|
|
case INT:
|
|
if bvc.value.(bvcint64) != -123 {
|
|
t.Errorf("want %v, got %v", -123, bvc.value.(bvcint64))
|
|
}
|
|
case STR:
|
|
if bvc.value.(bvcstring) != "123" {
|
|
t.Errorf("want %v, got %v", "123", bvc.value.(bvcint64))
|
|
}
|
|
case REGEXP:
|
|
if bvc.value.(bvcre).re == nil {
|
|
t.Errorf("want non-nil")
|
|
}
|
|
case KEYRANGE:
|
|
if kr := bvc.value.(*bvcKeyRange); string(kr.Start) != "1" || string(kr.End) != "2" {
|
|
t.Errorf(`Expecting {"1", "2"}, got %v`, kr)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
type InvalidJSONCase struct {
|
|
input, err string
|
|
}
|
|
|
|
var invalidjsons = []InvalidJSONCase{
|
|
{`[{"Name": 1 }]`, "want string for Name"},
|
|
{`[{"Description": 1 }]`, "want string for Description"},
|
|
{`[{"RequestIP": 1 }]`, "want string for RequestIP"},
|
|
{`[{"User": 1 }]`, "want string for User"},
|
|
{`[{"Query": 1 }]`, "want string for Query"},
|
|
{`[{"Plans": 1 }]`, "want list for Plans"},
|
|
{`[{"TableNames": 1 }]`, "want list for TableNames"},
|
|
{`[{"BindVarConds": 1 }]`, "want list for BindVarConds"},
|
|
{`[{"RequestIP": "[" }]`, "could not set IP condition: ["},
|
|
{`[{"User": "[" }]`, "could not set User condition: ["},
|
|
{`[{"Query": "[" }]`, "could not set Query condition: ["},
|
|
{`[{"Plans": [1] }]`, "want string for Plans"},
|
|
{`[{"Plans": ["invalid"] }]`, "invalid plan name: invalid"},
|
|
{`[{"TableNames": [1] }]`, "want string for TableNames"},
|
|
{`[{"BindVarConds": [1] }]`, "want json object for bind var conditions"},
|
|
{`[{"BindVarConds": [{}] }]`, "Name missing in BindVarConds"},
|
|
{`[{"BindVarConds": [{"Name": 1}] }]`, "want string for Name in BindVarConds"},
|
|
{`[{"BindVarConds": [{"Name": "a"}] }]`, "OnAbsent missing in BindVarConds"},
|
|
{`[{"BindVarConds": [{"Name": "a", "OnAbsent": 1}] }]`, "want bool for OnAbsent"},
|
|
{`[{"BindVarConds": [{"Name": "a", "OnAbsent": true}]}]`, "Operator missing in BindVarConds"},
|
|
{`[{"BindVarConds": [{"Name": "a", "OnAbsent": true, "Operator": "a"}]}]`, "invalid Operator a"},
|
|
{`[{"BindVarConds": [{"Name": "a", "OnAbsent": true, "Operator": "=="}]}]`, "Value missing in BindVarConds"},
|
|
{`[{"BindVarConds": [{"Name": "a", "OnAbsent": true, "Operator": "==", "Value": 1.2}]}]`, "want int64/uint64: 1.2"},
|
|
{`[{"BindVarConds": [{"Name": "a", "OnAbsent": true, "Operator": "==", "Value": {}}]}]`, "want string or number: map[]"},
|
|
{`[{"BindVarConds": [{"Name": "a", "OnAbsent": true, "Operator": "MATCH", "Value": 1}]}]`, "want string: 1"},
|
|
{`[{"BindVarConds": [{"Name": "a", "OnAbsent": true, "Operator": "NOMATCH", "Value": 1}]}]`, "want string: 1"},
|
|
{`[{"BindVarConds": [{"Name": "a", "OnAbsent": true, "Operator": 123, "Value": "1"}]}]`, "want string for Operator"},
|
|
{`[{"Unknown": [{"Name": "a"}]}]`, "unrecognized tag Unknown"},
|
|
{`[{"BindVarConds": [{"Name": "a", "OnAbsent": true, "Operator": "<=", "Value": "1"}]}]`, "OnMismatch missing in BindVarConds"},
|
|
{`[{"BindVarConds": [{"Name": "a", "OnAbsent": true, "OnMismatch": true, "Operator": "MATCH", "Value": "["}]}]`, "processing [: error parsing regexp: missing closing ]: `[$`"},
|
|
{`[{"BindVarConds": [{"Name": "a", "OnAbsent": true, "OnMismatch": true, "Operator": "NOMATCH", "Value": "["}]}]`, "processing [: error parsing regexp: missing closing ]: `[$`"},
|
|
{`[{"Action": 1 }]`, "want string for Action"},
|
|
{`[{"Action": "foo" }]`, "invalid Action foo"},
|
|
}
|
|
|
|
func TestInvalidJSON(t *testing.T) {
|
|
for _, tcase := range invalidjsons {
|
|
qrs := NewQueryRules()
|
|
err := qrs.UnmarshalJSON([]byte(tcase.input))
|
|
if err == nil {
|
|
t.Errorf("want error for case %q", tcase.input)
|
|
continue
|
|
}
|
|
recvd := strings.Replace(err.Error(), "error: ", "", 1)
|
|
if recvd != tcase.err {
|
|
t.Errorf("invalid json: %s, want '%v', got '%v'", tcase.input, tcase.err, recvd)
|
|
}
|
|
}
|
|
qrs := NewQueryRules()
|
|
err := qrs.UnmarshalJSON([]byte(`{`))
|
|
terr, ok := err.(*TabletError)
|
|
if !ok {
|
|
t.Fatalf("invalid json, should get a tablet error")
|
|
}
|
|
if terr.ErrorType != ErrFail {
|
|
t.Fatalf("should get ErrFail")
|
|
}
|
|
}
|
|
|
|
func TestBuildQueryRuleActionFail(t *testing.T) {
|
|
var ruleInfo map[string]interface{}
|
|
err := json.Unmarshal([]byte(`{"Action": "FAIL" }`), &ruleInfo)
|
|
if err != nil {
|
|
t.Fatalf("failed to unmarshal json, got error: %v", err)
|
|
}
|
|
qr, err := BuildQueryRule(ruleInfo)
|
|
if err != nil {
|
|
t.Fatalf("build query rule should succeed")
|
|
}
|
|
if qr.act != QRFail {
|
|
t.Fatalf("action should fail")
|
|
}
|
|
}
|
|
|
|
func TestBuildQueryRuleFailureModes(t *testing.T) {
|
|
var err error
|
|
var errStr string
|
|
|
|
_, err = BuildQueryRule(map[string]interface{}{
|
|
"BindVarConds": []interface{}{map[string]interface{}{"Name": "a", "OnAbsent": true, "Operator": QRIn, "Value": &topodatapb.KeyRange{}}},
|
|
})
|
|
if err == nil {
|
|
t.Fatalf("should get an error")
|
|
}
|
|
errStr = strings.Replace(err.Error(), "error: ", "", 1)
|
|
if errStr != "want string for Operator" {
|
|
t.Fatalf("expect to get error: want string for Operator, but got: %v", err)
|
|
}
|
|
|
|
_, err = BuildQueryRule(map[string]interface{}{
|
|
"BindVarConds": []interface{}{map[string]interface{}{"Name": "a", "OnAbsent": true, "Operator": "IN", "Value": 1}},
|
|
})
|
|
if err == nil {
|
|
t.Fatalf("should get an error")
|
|
}
|
|
errStr = strings.Replace(err.Error(), "error: ", "", 1)
|
|
if errStr != "want keyrange for Value" {
|
|
t.Fatalf("expect to get error: want keyrange for Value, but got: %v", err)
|
|
}
|
|
|
|
_, err = BuildQueryRule(map[string]interface{}{
|
|
"BindVarConds": []interface{}{map[string]interface{}{"Name": "a", "OnAbsent": true, "Operator": "IN", "Value": map[string]interface{}{}}},
|
|
})
|
|
if err == nil {
|
|
t.Fatalf("should get an error")
|
|
}
|
|
errStr = strings.Replace(err.Error(), "error: ", "", 1)
|
|
if errStr != "Start missing in KeyRange" {
|
|
t.Fatalf("expect to get error: Start missing in KeyRange, but got: %v", err)
|
|
}
|
|
|
|
_, err = BuildQueryRule(map[string]interface{}{
|
|
"BindVarConds": []interface{}{map[string]interface{}{"Name": "a", "OnAbsent": true, "Operator": "IN", "Value": map[string]interface{}{"Start": 1}}},
|
|
})
|
|
if err == nil {
|
|
t.Fatalf("should get an error")
|
|
}
|
|
errStr = strings.Replace(err.Error(), "error: ", "", 1)
|
|
if errStr != "want string for Start" {
|
|
t.Fatalf("expect to get error: want string for Start, but got: %v", err)
|
|
}
|
|
|
|
_, err = BuildQueryRule(map[string]interface{}{
|
|
"BindVarConds": []interface{}{map[string]interface{}{"Name": "a", "OnAbsent": true, "Operator": "IN", "Value": map[string]interface{}{"Start": "1"}}},
|
|
})
|
|
if err == nil {
|
|
t.Fatalf("should get an error")
|
|
}
|
|
errStr = strings.Replace(err.Error(), "error: ", "", 1)
|
|
if errStr != "End missing in KeyRange" {
|
|
t.Fatalf("expect to get error: End missing in KeyRange, but got: %v", err)
|
|
}
|
|
|
|
_, err = BuildQueryRule(map[string]interface{}{
|
|
"BindVarConds": []interface{}{map[string]interface{}{"Name": "a", "OnAbsent": true, "Operator": "IN", "Value": map[string]interface{}{"Start": "1", "End": 2}}},
|
|
})
|
|
if err == nil {
|
|
t.Fatalf("should get an error")
|
|
}
|
|
errStr = strings.Replace(err.Error(), "error: ", "", 1)
|
|
if errStr != "want string for End" {
|
|
t.Fatalf("expect to get error: want string for End, but got: %v", err)
|
|
}
|
|
|
|
_, err = BuildQueryRule(map[string]interface{}{
|
|
"BindVarConds": []interface{}{map[string]interface{}{"Name": "a", "OnAbsent": true, "OnMismatch": "invalid", "Operator": "IN", "Value": map[string]interface{}{"Start": "1", "End": "2"}}},
|
|
})
|
|
if err == nil {
|
|
t.Fatalf("should get an error")
|
|
}
|
|
errStr = strings.Replace(err.Error(), "error: ", "", 1)
|
|
if errStr != "want bool for OnMismatch" {
|
|
t.Fatalf("expect to get error: want bool for OnMismatch, but got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestBadAddBindVarCond(t *testing.T) {
|
|
qr1 := NewQueryRule("rule 1", "r1", QRFail)
|
|
err := qr1.AddBindVarCond("a", true, false, QRMatch, uint64(1))
|
|
if err == nil {
|
|
t.Fatalf("invalid op: QRMatch for value type: uint64")
|
|
}
|
|
err = qr1.AddBindVarCond("a", true, false, QRNotIn, "test")
|
|
if err == nil {
|
|
t.Fatalf("invalid op: QRNotIn for value type: string")
|
|
}
|
|
err = qr1.AddBindVarCond("a", true, false, QRLessThan, &topodatapb.KeyRange{})
|
|
if err == nil {
|
|
t.Fatalf("invalid op: QRLessThan for value type: *topodatapb.KeyRange")
|
|
}
|
|
}
|
|
|
|
func TestOpNames(t *testing.T) {
|
|
want := []string{
|
|
"",
|
|
"==",
|
|
"!=",
|
|
"<",
|
|
">=",
|
|
">",
|
|
"<=",
|
|
"MATCH",
|
|
"NOMATCH",
|
|
"IN",
|
|
"NOTIN",
|
|
}
|
|
if !reflect.DeepEqual(opnames, want) {
|
|
t.Errorf("opnames: \n%v, want \n%v", opnames, want)
|
|
}
|
|
}
|
|
|
|
func compacted(in string) string {
|
|
dst := bytes.NewBuffer(nil)
|
|
err := json.Compact(dst, []byte(in))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return dst.String()
|
|
}
|
|
|
|
func marshalled(in interface{}) string {
|
|
b, err := json.Marshal(in)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return string(b)
|
|
}
|