mediocre-go-lib/mcfg/cli_test.go

200 lines
4.5 KiB
Go
Raw Normal View History

package mcfg
import (
"bytes"
"encoding/json"
"fmt"
"strings"
. "testing"
"time"
"github.com/mediocregopher/mediocre-go-lib/mrand"
"github.com/mediocregopher/mediocre-go-lib/mtest/massert"
"github.com/mediocregopher/mediocre-go-lib/mtest/mchk"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSourceCLIHelp(t *T) {
cfg := New()
cfg.ParamInt("foo", 5, "Test int param")
cfg.ParamBool("bar", "Test bool param")
cfg.ParamString("baz", "baz", "Test string param")
cfg.ParamString("baz2", "", "")
src := SourceCLI{}
buf := new(bytes.Buffer)
pvM, err := src.cliParamVals(cfg)
require.NoError(t, err)
SourceCLI{}.printHelp(buf, pvM)
exp := `
--bar (Flag)
Test bool param
--baz (Default: "baz")
Test string param
--baz2
--foo (Default: 5)
Test int param
`
assert.Equal(t, exp, buf.String())
}
func TestSourceCLI(t *T) {
type state struct {
cfg *Cfg
availCfgs []*Cfg
SourceCLI
expPVs []ParamValue
}
type params 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
}
chk := mchk.Checker{
Init: func() mchk.State {
var s state
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
},
Next: func(ss mchk.State) mchk.Action {
s := ss.(state)
var p params
if i := mrand.Intn(8); i == 0 {
p.name = mrand.Hex(1) + "-" + mrand.Hex(8)
} else if i == 1 {
p.name = mrand.Hex(1) + "=" + mrand.Hex(8)
} else {
p.name = mrand.Hex(8)
}
p.availCfgI = mrand.Intn(len(s.availCfgs))
thisCfg := s.availCfgs[p.availCfgI]
p.path = thisCfg.Path
p.isBool = mrand.Intn(2) == 0
if !p.isBool {
p.nonBoolType = mrand.Element([]string{
"int",
"str",
"duration",
"json",
}, nil).(string)
}
p.unset = mrand.Intn(10) == 0
if p.isBool || p.unset {
return mchk.Action{Params: p}
}
p.nonBoolWEq = mrand.Intn(2) == 0
switch p.nonBoolType {
case "int":
p.nonBoolVal = fmt.Sprint(mrand.Int())
case "str":
p.nonBoolVal = mrand.Hex(16)
case "duration":
dur := time.Duration(mrand.Intn(86400)) * time.Second
p.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(),
})
p.nonBoolVal = string(b)
}
return mchk.Action{Params: p}
},
Apply: func(ss mchk.State, a mchk.Action) (mchk.State, error) {
s := ss.(state)
p := a.Params.(params)
// the param needs to get added to its cfg as a Param
thisCfg := s.availCfgs[p.availCfgI]
cfgP := Param{
Name: p.name,
IsString: p.nonBoolType == "str" || p.nonBoolType == "duration",
IsBool: p.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(cfgP)
// if the arg is set then add it to the cli args and the expected output pvs
if !p.unset {
arg := cliKeyPrefix
if len(p.path) > 0 {
arg += strings.Join(p.path, cliKeyJoin) + cliKeyJoin
}
arg += p.name
if !p.isBool {
if p.nonBoolWEq {
arg += "="
} else {
s.SourceCLI.Args = append(s.SourceCLI.Args, arg)
arg = ""
}
arg += p.nonBoolVal
}
s.SourceCLI.Args = append(s.SourceCLI.Args, arg)
pv := ParamValue{
Param: cfgP,
Path: p.path,
}
if p.isBool {
pv.Value = json.RawMessage("true")
} else {
switch p.nonBoolType {
case "str", "duration":
pv.Value = json.RawMessage(fmt.Sprintf("%q", p.nonBoolVal))
case "int", "json":
pv.Value = json.RawMessage(p.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()
},
}
if err := chk.RunFor(5 * time.Second); err != nil {
t.Fatal(err)
}
}