mcfg: refactor cli test to use mtest.Checker
This commit is contained in:
parent
9ccc787066
commit
1964add0ed
434
mcfg/cli_test.go
434
mcfg/cli_test.go
@ -3,286 +3,19 @@ package mcfg
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strconv"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
. "testing"
|
. "testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/mediocregopher/mediocre-go-lib/mrand"
|
"github.com/mediocregopher/mediocre-go-lib/mrand"
|
||||||
|
"github.com/mediocregopher/mediocre-go-lib/mtest"
|
||||||
|
"github.com/mediocregopher/mediocre-go-lib/mtest/massert"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// * dimension
|
|
||||||
// - dimension value
|
|
||||||
//
|
|
||||||
// * Cfg path
|
|
||||||
// - New()
|
|
||||||
// - New.Child("a")
|
|
||||||
// - New.Child("a-b")
|
|
||||||
// - New.Child("a=b")
|
|
||||||
// * Param name
|
|
||||||
// - normal
|
|
||||||
// - w/ "-"
|
|
||||||
// - w/ "=" ?
|
|
||||||
// * Param type
|
|
||||||
// - bool
|
|
||||||
// - non-bool
|
|
||||||
// * non-bool type
|
|
||||||
// - int
|
|
||||||
// - string
|
|
||||||
// * Str value
|
|
||||||
// - empty
|
|
||||||
// - normal
|
|
||||||
// - w/ -
|
|
||||||
// - w/ =
|
|
||||||
// * Value format
|
|
||||||
// - w/ =
|
|
||||||
// - w/o =
|
|
||||||
|
|
||||||
func combinate(slices ...[]string) [][]string {
|
|
||||||
out := [][]string{{}}
|
|
||||||
for _, slice := range slices {
|
|
||||||
if len(slice) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
prev := out
|
|
||||||
out = make([][]string, 0, len(prev)*len(slice))
|
|
||||||
for _, prevSet := range prev {
|
|
||||||
for _, sliceElem := range slice {
|
|
||||||
prevSetCp := make([]string, len(prevSet), len(prevSet)+1)
|
|
||||||
copy(prevSetCp, prevSet)
|
|
||||||
prevSetCp = append(prevSetCp, sliceElem)
|
|
||||||
out = append(out, prevSetCp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCombinate(t *T) {
|
|
||||||
type combTest struct {
|
|
||||||
args [][]string
|
|
||||||
exp [][]string
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []combTest{
|
|
||||||
{
|
|
||||||
args: [][]string{},
|
|
||||||
exp: [][]string{{}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
args: [][]string{{"a"}},
|
|
||||||
exp: [][]string{{"a"}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
args: [][]string{{"a"}, {"b"}},
|
|
||||||
exp: [][]string{{"a", "b"}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
args: [][]string{{"a", "aa"}, {"b"}},
|
|
||||||
exp: [][]string{
|
|
||||||
{"a", "b"},
|
|
||||||
{"aa", "b"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
args: [][]string{{"a"}, {"b", "bb"}},
|
|
||||||
exp: [][]string{
|
|
||||||
{"a", "b"},
|
|
||||||
{"a", "bb"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
args: [][]string{{"a", "aa"}, {"b", "bb"}},
|
|
||||||
exp: [][]string{
|
|
||||||
{"a", "b"},
|
|
||||||
{"aa", "b"},
|
|
||||||
{"a", "bb"},
|
|
||||||
{"aa", "bb"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
msgAndArgs := []interface{}{"test:%d args:%v", i, test.args}
|
|
||||||
got := combinate(test.args...)
|
|
||||||
assert.Len(t, got, len(test.exp), msgAndArgs...)
|
|
||||||
for _, expHas := range test.exp {
|
|
||||||
assert.Contains(t, got, expHas, msgAndArgs...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSourceCLI(t *T) {
|
|
||||||
var (
|
|
||||||
paths = []string{
|
|
||||||
"root",
|
|
||||||
"child",
|
|
||||||
"childDash",
|
|
||||||
"childEq",
|
|
||||||
}
|
|
||||||
|
|
||||||
paramNames = []string{
|
|
||||||
"normal",
|
|
||||||
"wDash",
|
|
||||||
"wEq",
|
|
||||||
}
|
|
||||||
|
|
||||||
isBool = []string{
|
|
||||||
"isBool",
|
|
||||||
"isNotBool",
|
|
||||||
}
|
|
||||||
|
|
||||||
nonBoolTypes = []string{
|
|
||||||
"int",
|
|
||||||
"str",
|
|
||||||
}
|
|
||||||
|
|
||||||
nonBoolFmts = []string{
|
|
||||||
"wEq",
|
|
||||||
"woEq",
|
|
||||||
}
|
|
||||||
|
|
||||||
nonBoolStrValues = []string{
|
|
||||||
"empty",
|
|
||||||
"normal",
|
|
||||||
"wDash",
|
|
||||||
"wEq",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type cliTest struct {
|
|
||||||
path string
|
|
||||||
name string
|
|
||||||
isBool bool
|
|
||||||
nonBoolType string
|
|
||||||
nonBoolStrValue string
|
|
||||||
nonBoolFmt string
|
|
||||||
|
|
||||||
// it's kinda hacky to make this a pointer, but it makes the code a lot
|
|
||||||
// easier to read later
|
|
||||||
exp *ParamValue
|
|
||||||
}
|
|
||||||
|
|
||||||
var tests []cliTest
|
|
||||||
for _, comb := range combinate(paths, paramNames, isBool) {
|
|
||||||
var test cliTest
|
|
||||||
test.path = comb[0]
|
|
||||||
test.name = comb[1]
|
|
||||||
test.isBool = comb[2] == "isBool"
|
|
||||||
if test.isBool {
|
|
||||||
tests = append(tests, test)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, nonBoolComb := range combinate(nonBoolTypes, nonBoolFmts) {
|
|
||||||
test.nonBoolType = nonBoolComb[0]
|
|
||||||
test.nonBoolFmt = nonBoolComb[1]
|
|
||||||
if test.nonBoolType != "str" {
|
|
||||||
tests = append(tests, test)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, nonBoolStrValue := range nonBoolStrValues {
|
|
||||||
test.nonBoolStrValue = nonBoolStrValue
|
|
||||||
tests = append(tests, test)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
childName := mrand.Hex(8)
|
|
||||||
childDashName := mrand.Hex(4) + "-" + mrand.Hex(4)
|
|
||||||
childEqName := mrand.Hex(4) + "=" + mrand.Hex(4)
|
|
||||||
|
|
||||||
var args []string
|
|
||||||
rootCfg := New()
|
|
||||||
childCfg := rootCfg.Child(childName)
|
|
||||||
childDashCfg := rootCfg.Child(childDashName)
|
|
||||||
childEqCfg := rootCfg.Child(childEqName)
|
|
||||||
|
|
||||||
for i := range tests {
|
|
||||||
var pv ParamValue
|
|
||||||
tests[i].exp = &pv
|
|
||||||
|
|
||||||
switch tests[i].name {
|
|
||||||
case "normal":
|
|
||||||
pv.Name = mrand.Hex(8)
|
|
||||||
case "wDash":
|
|
||||||
pv.Name = mrand.Hex(4) + "-" + mrand.Hex(4)
|
|
||||||
case "wEq":
|
|
||||||
pv.Name = mrand.Hex(4) + "=" + mrand.Hex(4)
|
|
||||||
}
|
|
||||||
|
|
||||||
pv.IsBool = tests[i].isBool
|
|
||||||
pv.IsString = !tests[i].isBool && tests[i].nonBoolType == "str"
|
|
||||||
|
|
||||||
var arg string
|
|
||||||
switch tests[i].path {
|
|
||||||
case "root":
|
|
||||||
rootCfg.ParamAdd(pv.Param)
|
|
||||||
arg = "--" + pv.Name
|
|
||||||
case "child":
|
|
||||||
childCfg.ParamAdd(pv.Param)
|
|
||||||
pv.Path = append(pv.Path, childName)
|
|
||||||
arg = "--" + childName + "-" + pv.Name
|
|
||||||
case "childDash":
|
|
||||||
childDashCfg.ParamAdd(pv.Param)
|
|
||||||
pv.Path = append(pv.Path, childDashName)
|
|
||||||
arg = "--" + childDashName + "-" + pv.Name
|
|
||||||
case "childEq":
|
|
||||||
childEqCfg.ParamAdd(pv.Param)
|
|
||||||
pv.Path = append(pv.Path, childEqName)
|
|
||||||
arg = "--" + childEqName + "-" + pv.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
if pv.IsBool {
|
|
||||||
pv.Value = json.RawMessage("true")
|
|
||||||
args = append(args, arg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var val string
|
|
||||||
switch tests[i].nonBoolType {
|
|
||||||
case "int":
|
|
||||||
val = strconv.Itoa(mrand.Int())
|
|
||||||
pv.Value = json.RawMessage(val)
|
|
||||||
case "str":
|
|
||||||
switch tests[i].nonBoolStrValue {
|
|
||||||
case "empty":
|
|
||||||
// ez
|
|
||||||
case "normal":
|
|
||||||
val = mrand.Hex(8)
|
|
||||||
case "wDash":
|
|
||||||
val = mrand.Hex(4) + "-" + mrand.Hex(4)
|
|
||||||
case "wEq":
|
|
||||||
val = mrand.Hex(4) + "=" + mrand.Hex(4)
|
|
||||||
}
|
|
||||||
pv.Value = json.RawMessage(`"` + val + `"`)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tests[i].nonBoolFmt {
|
|
||||||
case "wEq":
|
|
||||||
arg += "=" + val
|
|
||||||
args = append(args, arg)
|
|
||||||
case "woEq":
|
|
||||||
args = append(args, arg, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
src := SourceCLI{Args: args}
|
|
||||||
pvals, err := src.Parse(rootCfg)
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.Len(t, pvals, len(tests))
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
assert.Contains(t, pvals, *test.exp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// an extra bogus param or value should generate an error
|
|
||||||
src = SourceCLI{Args: append(args, "foo")}
|
|
||||||
_, err = src.Parse(rootCfg)
|
|
||||||
require.Error(t, err)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSourceCLIHelp(t *T) {
|
func TestSourceCLIHelp(t *T) {
|
||||||
cfg := New()
|
cfg := New()
|
||||||
cfg.ParamInt("foo", 5, "Test int param")
|
cfg.ParamInt("foo", 5, "Test int param")
|
||||||
@ -311,3 +44,158 @@ func TestSourceCLIHelp(t *T) {
|
|||||||
`
|
`
|
||||||
assert.Equal(t, exp, buf.String())
|
assert.Equal(t, exp, buf.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type testCLIState struct {
|
||||||
|
cfg *Cfg
|
||||||
|
availCfgs []*Cfg
|
||||||
|
|
||||||
|
SourceCLI
|
||||||
|
expPVs []ParamValue
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCLIApplyer struct {
|
||||||
|
name string
|
||||||
|
availCfgI int // not technically needed, but makes subsequent steps easier
|
||||||
|
path []string
|
||||||
|
isBool bool
|
||||||
|
nonBoolType string // "int", "str", "duration", "json"
|
||||||
|
unset bool
|
||||||
|
nonBoolWEq bool // use equal sign when setting value
|
||||||
|
nonBoolVal string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tca testCLIApplyer) Apply(ss mtest.State) (mtest.State, error) {
|
||||||
|
s := ss.(testCLIState)
|
||||||
|
|
||||||
|
// the tca needs to get added to its cfg as a Param
|
||||||
|
thisCfg := s.availCfgs[tca.availCfgI]
|
||||||
|
p := Param{
|
||||||
|
Name: tca.name,
|
||||||
|
IsString: tca.nonBoolType == "str" || tca.nonBoolType == "duration",
|
||||||
|
IsBool: tca.isBool,
|
||||||
|
// the cli parser doesn't actually care about the other fields of Param,
|
||||||
|
// those are only used by Cfg once it has all ParamValues together
|
||||||
|
}
|
||||||
|
thisCfg.ParamAdd(p)
|
||||||
|
|
||||||
|
// if the arg is set then add it to the cli args and the expected output pvs
|
||||||
|
if !tca.unset {
|
||||||
|
arg := cliKeyPrefix
|
||||||
|
if len(tca.path) > 0 {
|
||||||
|
arg += strings.Join(tca.path, cliKeyJoin) + cliKeyJoin
|
||||||
|
}
|
||||||
|
arg += tca.name
|
||||||
|
if !tca.isBool {
|
||||||
|
if tca.nonBoolWEq {
|
||||||
|
arg += "="
|
||||||
|
} else {
|
||||||
|
s.SourceCLI.Args = append(s.SourceCLI.Args, arg)
|
||||||
|
arg = ""
|
||||||
|
}
|
||||||
|
arg += tca.nonBoolVal
|
||||||
|
}
|
||||||
|
s.SourceCLI.Args = append(s.SourceCLI.Args, arg)
|
||||||
|
log.Print(strings.Join(s.SourceCLI.Args, " "))
|
||||||
|
|
||||||
|
pv := ParamValue{
|
||||||
|
Param: p,
|
||||||
|
Path: tca.path,
|
||||||
|
}
|
||||||
|
if tca.isBool {
|
||||||
|
pv.Value = json.RawMessage("true")
|
||||||
|
} else {
|
||||||
|
switch tca.nonBoolType {
|
||||||
|
case "str", "duration":
|
||||||
|
pv.Value = json.RawMessage(fmt.Sprintf("%q", tca.nonBoolVal))
|
||||||
|
case "int", "json":
|
||||||
|
pv.Value = json.RawMessage(tca.nonBoolVal)
|
||||||
|
default:
|
||||||
|
panic("shouldn't get here")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.expPVs = append(s.expPVs, pv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// and finally the state needs to be checked
|
||||||
|
gotPVs, err := s.SourceCLI.Parse(s.cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s, massert.All(
|
||||||
|
massert.Len(gotPVs, len(s.expPVs)),
|
||||||
|
massert.Subset(s.expPVs, gotPVs),
|
||||||
|
).Assert()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSourceCLI(t *T) {
|
||||||
|
chk := mtest.Checker{
|
||||||
|
Init: func() mtest.State {
|
||||||
|
var s testCLIState
|
||||||
|
s.cfg = New()
|
||||||
|
{
|
||||||
|
a := s.cfg.Child("a")
|
||||||
|
b := s.cfg.Child("b")
|
||||||
|
c := s.cfg.Child("c")
|
||||||
|
ab := a.Child("b")
|
||||||
|
bc := b.Child("c")
|
||||||
|
abc := ab.Child("c")
|
||||||
|
s.availCfgs = []*Cfg{s.cfg, a, b, c, ab, bc, abc}
|
||||||
|
}
|
||||||
|
s.SourceCLI.Args = make([]string, 0, 16)
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
Actions: func(ss mtest.State) []mtest.Action {
|
||||||
|
s := ss.(testCLIState)
|
||||||
|
var tca testCLIApplyer
|
||||||
|
if i := mrand.Intn(8); i == 0 {
|
||||||
|
tca.name = mrand.Hex(1) + "-" + mrand.Hex(8)
|
||||||
|
} else if i == 1 {
|
||||||
|
tca.name = mrand.Hex(1) + "=" + mrand.Hex(8)
|
||||||
|
} else {
|
||||||
|
tca.name = mrand.Hex(8)
|
||||||
|
}
|
||||||
|
|
||||||
|
tca.availCfgI = mrand.Intn(len(s.availCfgs))
|
||||||
|
thisCfg := s.availCfgs[tca.availCfgI]
|
||||||
|
tca.path = thisCfg.Path
|
||||||
|
|
||||||
|
tca.isBool = mrand.Intn(2) == 0
|
||||||
|
if !tca.isBool {
|
||||||
|
tca.nonBoolType = mrand.Element([]string{
|
||||||
|
"int",
|
||||||
|
"str",
|
||||||
|
"duration",
|
||||||
|
"json",
|
||||||
|
}, nil).(string)
|
||||||
|
}
|
||||||
|
tca.unset = mrand.Intn(10) == 0
|
||||||
|
|
||||||
|
if tca.isBool || tca.unset {
|
||||||
|
return []mtest.Action{{Applyer: tca}}
|
||||||
|
}
|
||||||
|
|
||||||
|
tca.nonBoolWEq = mrand.Intn(2) == 0
|
||||||
|
switch tca.nonBoolType {
|
||||||
|
case "int":
|
||||||
|
tca.nonBoolVal = fmt.Sprint(mrand.Int())
|
||||||
|
case "str":
|
||||||
|
tca.nonBoolVal = mrand.Hex(16)
|
||||||
|
case "duration":
|
||||||
|
dur := time.Duration(mrand.Intn(86400)) * time.Second
|
||||||
|
tca.nonBoolVal = dur.String()
|
||||||
|
case "json":
|
||||||
|
b, _ := json.Marshal(map[string]int{
|
||||||
|
mrand.Hex(4): mrand.Int(),
|
||||||
|
mrand.Hex(4): mrand.Int(),
|
||||||
|
mrand.Hex(4): mrand.Int(),
|
||||||
|
})
|
||||||
|
tca.nonBoolVal = string(b)
|
||||||
|
}
|
||||||
|
return []mtest.Action{{Applyer: tca}}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := chk.RunUntil(5 * time.Second); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user