Skip to content

Commit

Permalink
add property/fuzz tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jon-codes committed Oct 8, 2024
1 parent f972601 commit 41cf08b
Show file tree
Hide file tree
Showing 12 changed files with 886 additions and 138 deletions.
283 changes: 203 additions & 80 deletions getopt.go

Large diffs are not rendered by default.

32 changes: 29 additions & 3 deletions getopt_fixtures_test.go → getopt_fixture_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestGetOpt_Fixtures(t *testing.T) {
if err := decoder.Decode(&f); err != nil {
t.Fatalf("error decoding fixture: %v", err)
}
testName := fmt.Sprintf("%s %s %s)", f.Label, f.Func.String(), f.Mode.String())
testName := fmt.Sprintf("%s %s %s)", f.Label, funcString(f.Func), modeString(f.Mode))
t.Run(testName, func(t *testing.T) {
assertFixture(t, f)
})
Expand All @@ -45,19 +45,45 @@ func TestGetOpt_Fixtures(t *testing.T) {
}
}

func funcString(f Func) string {
switch f {
case FuncGetOpt:
return "getopt"
case FuncGetOptLong:
return "getopt_long"
case FuncGetOptLongOnly:
return "getopt_long_only"
default:
return "unknown"
}
}

func modeString(m Mode) string {
switch m {
case ModeGNU:
return "gnu"
case ModePosix:
return "posix"
case ModeInOrder:
return "inorder"
default:
return "unknown"
}
}

func assertFixture(t testing.TB, f fixture) {
t.Helper()

s := NewState(f.Args)
p := Params{
c := Config{
Opts: f.Opts,
LongOpts: f.LongOpts,
Mode: f.Mode,
Func: f.Func,
}

for iter, want := range f.WantResults {
res, err := s.GetOpt(p)
res, err := s.GetOpt(c)

if want.Err == nil {
if err != nil {
Expand Down
79 changes: 79 additions & 0 deletions getopt_prop_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package getopt_test

import (
"testing"

"github.com/jon-codes/getopt"
"pgregory.net/rapid"
)

var (
genHasArg = rapid.SampledFrom([]getopt.HasArg{getopt.NoArgument, getopt.RequiredArgument, getopt.OptionalArgument})
funcGen = rapid.SampledFrom([]getopt.Func{getopt.FuncGetOpt, getopt.FuncGetOptLong, getopt.FuncGetOptLongOnly})
modeGen = rapid.SampledFrom([]getopt.Mode{getopt.ModeGNU, getopt.ModePosix, getopt.ModeInOrder})
)

var optGen = rapid.Custom(func(t *rapid.T) getopt.Opt {
return getopt.Opt{Char: rapid.Rune().Draw(t, "char"), HasArg: genHasArg.Draw(t, "has_arg")}
})

var longOptGen = rapid.Custom(func(t *rapid.T) getopt.LongOpt {
return getopt.LongOpt{Name: rapid.String().Draw(t, "name"), HasArg: genHasArg.Draw(t, "has_arg")}
})

var configGen = rapid.Custom(func(t *rapid.T) getopt.Config {
return getopt.Config{
Opts: rapid.SliceOf(optGen).Draw(t, "opts"),
LongOpts: rapid.SliceOf(longOptGen).Draw(t, "long_opts"),
Func: funcGen.Draw(t, "func"),
Mode: modeGen.Draw(t, "mode"),
}
})

func propTarget(t *rapid.T) {
args := rapid.SliceOfN(rapid.String(), 0, -1).Draw(t, "args")

c := configGen.Draw(t, "config")
s := getopt.NewState(args)

prevOptInd := s.OptInd()
for res, err := range s.All(c) {
if err != nil && !(err == getopt.ErrMissingOptArg || err == getopt.ErrIllegalOptArg || err == getopt.ErrUnknownOpt) {
t.Fatalf("unknown err returned: %v", err)
}

if err != nil {
if err == getopt.ErrMissingOptArg && res.OptArg != "" {
t.Fatalf("result has OptArg %q, but err claims it is missing", res.OptArg)
}
}

if res.Char != 0 {
if res.Name != "" {
t.Fatalf("result has both Char %q and Name %q", res.Char, res.Name)
}
}

if res.Name != "" {
if res.Char != 0 {
t.Fatalf("result has both Char %q and Name %q", res.Char, res.Name)
}
}

if s.OptInd() < prevOptInd {
t.Fatalf("OptInd decreased from %d to %d", prevOptInd, s.OptInd())
}
}

if s.OptInd() > 1 && s.OptInd() > len(s.Args())+1 {
t.Fatalf("OptInd exceeded last arg + 1: args len is %d, bug OptInd is %d", len(args), s.OptInd())
}
}

func TestGetOpt_Property(t *testing.T) {
rapid.Check(t, propTarget)
}

func FuzzGetOpt(f *testing.F) {
f.Fuzz(rapid.MakeFuzz(propTarget))
}
Loading

0 comments on commit 41cf08b

Please sign in to comment.