Skip to content

Commit

Permalink
implement testgen in C
Browse files Browse the repository at this point in the history
  • Loading branch information
jon-codes committed Oct 7, 2024
1 parent 9904ed1 commit e639c7b
Show file tree
Hide file tree
Showing 16 changed files with 550 additions and 916 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ go.work
.vscode/*
!.vscode/extensions.json

.vgcore.*
vgcore.*
*.log
*.local
*.o
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ TMPDIR := tmp
OBJDIR := obj

CC := gcc
CFLAGS_BASE := -std=c23 -Werror -Wall -Wextra -Wpedantic -Wno-unused-parameter -Wshadow -Wwrite-strings -Wstrict-prototypes -Wold-style-definition -Wredundant-decls -Wnested-externs -Wmissing-include-dirs -Wjump-misses-init -Wlogical-op
CFLAGS_BASE := -std=c23 -Wall -Wextra -Wpedantic -Wno-unused-parameter -Wshadow -Wwrite-strings -Wstrict-prototypes -Wold-style-definition -Wredundant-decls -Wnested-externs -Wmissing-include-dirs -Wjump-misses-init -Wlogical-op
CFLAGS := $(CFLAGS_BASE) -O2 $(TESTGEN_INCL)
CFLAGS_DEBUG := $(CFLAGS_BASE) -g -O0 $(TESTGEN_INCL)

Expand Down Expand Up @@ -105,7 +105,7 @@ $(OBJDIR)/%_debug.o: $(TESTGEN_SRCDIR)/%.c | $(OBJDIR)
## testgen-check: run testgen with valgrind
.PHONY: testgen-check
testgen-check: $(TESTGEN_DEBUG_BIN)
valgrind --leak-check=full $< -o $(TESTGEN_OUTPUT) $(TESTGEN_INPUT)
valgrind --leak-check=full --track-origins=yes $< -o $(TESTGEN_OUTPUT) $(TESTGEN_INPUT)

## testgen-debug: run testgen with gdb
.PHONY: testgen-debug
Expand Down
37 changes: 0 additions & 37 deletions cmd/testgen/main.go

This file was deleted.

6 changes: 6 additions & 0 deletions getopt.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,12 @@ func (s *State) readOpt(p Params) (res Result, err error) {
s.OptIndex++
}
} else {
s.argIndex++
if arg[s.argIndex:] == "" {
s.OptIndex++
s.argIndex = 0
} else {
}
err = ErrUnknownOpt
}
}
Expand Down
211 changes: 112 additions & 99 deletions getopt_fixtures_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,118 @@ import (
"fmt"
"os"
"slices"
"strings"
"testing"
"unicode/utf8"

"github.com/jon-codes/getopt/internal/testgen"
)

const fixturePath = "testdata/fixtures.json"

type fixtureRecordIter struct {
Opt int `json:"opt"`
OptInd int `json:"optind"`
OptOpt int `json:"optopt"`
OptArg string `json:"optarg"`
LongIndex int `json:"longindex"`
}

type fixtureIter struct {
Char rune
Name string
OptArg string
Err error
}

func (fri *fixtureRecordIter) toFixtureIter(fixture *fixture) fixtureIter {
fi := fixtureIter{
OptArg: fri.OptArg,
}

switch fri.Opt {
case ':':
fi.Err = ErrUnknownOpt
case '?':
fi.Err = ErrUnknownOpt
if fri.OptOpt > 0 {
fi.Char = rune(fri.OptOpt)
} else if fixture.Function != FuncGetOpt {
name := strings.TrimLeft(fixture.Args[fri.OptInd-1], "-")
name = strings.SplitN(name, "=", 2)[0]
fi.Name = name
}
case -1: // done
fi.Err = ErrDone
case -2:
fi.Name = fixture.LongOpts[fri.LongIndex].Name
default:
fi.Char = rune(fri.Opt)
}

return fi
}

type fixtureRecord struct {
Label string `json:"label"`
Func string `json:"func"`
Mode string `json:"mode"`
Args []string `json:"args"`
Opts string `json:"opts"`
Lopts string `json:"lopts"`
WantArgs []string `json:"want_args"`
WantOptInd int `json:"want_optind"`
WantResults []fixtureRecordIter `json:"want_results"`
}

type fixture struct {
Label string
Function GetOptFunc
Mode GetOptMode
Opts []Opt
LongOpts []LongOpt
Args []string
WantArgs []string
WantResults []fixtureIter
WantOptInd int
}

func (fr *fixtureRecord) toFixture() (fixture, error) {
f := fixture{
Label: fr.Label,
Args: fr.Args,
Opts: OptStr(fr.Opts),
LongOpts: LongOptStr(fr.Lopts),
WantArgs: fr.WantArgs,
WantOptInd: fr.WantOptInd,
}

switch fr.Func {
case "getopt":
f.Function = FuncGetOpt
case "getopt_long":
f.Function = FuncGetOptLong
case "getopt_long_only":
f.Function = FuncGetOptLongOnly
default:
return f, fmt.Errorf("unknown function type %q", fr.Func)
}

switch fr.Mode {
case "gnu":
f.Mode = ModeGNU
case "posix":
f.Mode = ModePosix
case "inorder":
f.Mode = ModeInOrder
default:
return f, fmt.Errorf("unknown mode type %q", fr.Mode)
}

for _, fri := range fr.WantResults {
f.WantResults = append(f.WantResults, fri.toFixtureIter(&f))
}

return f, nil
}

func TestGetOpt_Fixtures(t *testing.T) {
fixtureFile, err := os.Open(fixturePath)
if err != nil {
Expand All @@ -31,15 +135,15 @@ func TestGetOpt_Fixtures(t *testing.T) {

// while the array contains values
for decoder.More() {
var record testgen.FixtureRecord
var record fixtureRecord
if err := decoder.Decode(&record); err != nil {
t.Fatalf("error decoding fixture: %v", err)
}
fixture, err := buildFixture(record)
fixture, err := record.toFixture()
if err != nil {
t.Fatalf("error parsing fixture: %v", err)
}
testName := fmt.Sprintf("Fixture %q (function %q, mode %q)", record.Label, record.FunctionStr, record.ModeStr)
testName := fmt.Sprintf("Fixture %q (function %q, mode %q)", record.Label, record.Func, record.Mode)
t.Run(testName, func(t *testing.T) {
assertFixture(t, fixture)
})
Expand Down Expand Up @@ -89,102 +193,11 @@ func assertFixture(t testing.TB, f fixture) {
}
}

if s.OptIndex != f.WantOptIndex {
t.Errorf("got OptIndex %d, but wanted %d", s.OptIndex, f.WantOptIndex)
if s.OptIndex != f.WantOptInd {
t.Errorf("got OptIndex %d, but wanted %d", s.OptIndex, f.WantOptInd)
}

if !slices.Equal(s.Args, f.WantArgs) {
t.Errorf("got Args %+q, but wanted %+q", s.Args, f.WantArgs)
}
}

type fixtureIter struct {
Char rune
Name string
OptArg string
Err error
}

type fixture struct {
Label string
Args []string
Opts []Opt
LongOpts []LongOpt
Function GetOptFunc
Mode GetOptMode
WantArgs []string
WantOptIndex int
WantResults []fixtureIter
}

func buildFixture(fr testgen.FixtureRecord) (f fixture, err error) {
var function GetOptFunc
switch fr.FunctionStr {
case "getopt":
function = FuncGetOpt
case "getopt_long":
function = FuncGetOptLong
case "getopt_long_only":
function = FuncGetOptLongOnly
default:
return f, fmt.Errorf("unknown function type %q", fr.FunctionStr)
}

var mode GetOptMode
switch fr.ModeStr {
case "gnu":
mode = ModeGNU
case "posix":
mode = ModePosix
case "inorder":
mode = ModeInOrder
default:
return f, fmt.Errorf("unknown mode type %q", fr.ModeStr)
}

var wantResults []fixtureIter
for _, fi := range fr.WantResults {
var char rune
if fi.CharStr != "" {
char, _ = utf8.DecodeRuneInString(fi.CharStr)
}

var err error
switch fi.ErrStr {
case "":
err = nil
case "-1":
err = ErrDone
case ":":
err = ErrMissingOptArg
case "?":
_, found := findLongOpt(fi.Name, false, Params{LongOpts: LongOptStr(fr.LongOptStr), Function: function, Mode: mode})
if fi.Name != "" && found {
err = ErrIllegalOptArg
} else {
err = ErrUnknownOpt
}
default:
return f, fmt.Errorf("unknown error type %q", fi.ErrStr)
}

wantResults = append(wantResults, fixtureIter{
Char: char,
Name: fi.Name,
OptArg: fi.OptArg,
Err: err,
})
}

f.Label = fr.Label
f.Args = argsStr(fr.ArgsStr)
f.Opts = OptStr(fr.OptStr)
f.LongOpts = LongOptStr(fr.LongOptStr)
f.Function = function
f.Mode = mode
f.WantArgs = argsStr(fr.WantArgsStr)
f.WantOptIndex = fr.WantOptIndex
f.WantResults = wantResults

return f, nil
}
18 changes: 17 additions & 1 deletion getopt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ func TestGetOpt_FuncGetOpt(t *testing.T) {
p := Params{Opts: OptStr(`b`), Function: function}

wants := []assertion{
{char: 'a', err: ErrUnknownOpt, args: argsStr(`prgm -a`), optIndex: 1},
{char: 'a', err: ErrUnknownOpt, args: argsStr(`prgm -a`), optIndex: 2},
}

assertSeq(t, s, p, wants)
Expand Down Expand Up @@ -573,6 +573,12 @@ func TestGetOpt_FuncGetOpt(t *testing.T) {

wants := []assertion{
{char: '-', err: ErrUnknownOpt, args: argsStr(`prgm --longa`), optIndex: 1},
{char: 'l', err: ErrUnknownOpt, args: argsStr(`prgm --longa`), optIndex: 1},
{char: 'o', err: ErrUnknownOpt, args: argsStr(`prgm --longa`), optIndex: 1},
{char: 'n', err: ErrUnknownOpt, args: argsStr(`prgm --longa`), optIndex: 1},
{char: 'g', err: ErrUnknownOpt, args: argsStr(`prgm --longa`), optIndex: 1},
{char: 'a', err: ErrUnknownOpt, args: argsStr(`prgm --longa`), optIndex: 2},
{err: ErrDone, args: argsStr(`prgm --longa`), optIndex: 2},
}

assertSeq(t, s, p, wants)
Expand All @@ -584,6 +590,11 @@ func TestGetOpt_FuncGetOpt(t *testing.T) {

wants := []assertion{
{char: 'l', err: ErrUnknownOpt, args: argsStr(`prgm -longa`), optIndex: 1},
{char: 'o', err: ErrUnknownOpt, args: argsStr(`prgm -longa`), optIndex: 1},
{char: 'n', err: ErrUnknownOpt, args: argsStr(`prgm -longa`), optIndex: 1},
{char: 'g', err: ErrUnknownOpt, args: argsStr(`prgm -longa`), optIndex: 1},
{char: 'a', err: ErrUnknownOpt, args: argsStr(`prgm -longa`), optIndex: 2},
{err: ErrDone, args: argsStr(`prgm -longa`), optIndex: 2},
}

assertSeq(t, s, p, wants)
Expand Down Expand Up @@ -637,6 +648,11 @@ func TestGetOpt_FuncGetOptLong(t *testing.T) {

wants := []assertion{
{char: 'l', err: ErrUnknownOpt, args: argsStr(`prgm -longa`), optIndex: 1},
{char: 'o', err: ErrUnknownOpt, args: argsStr(`prgm -longa`), optIndex: 1},
{char: 'n', err: ErrUnknownOpt, args: argsStr(`prgm -longa`), optIndex: 1},
{char: 'g', err: ErrUnknownOpt, args: argsStr(`prgm -longa`), optIndex: 1},
{char: 'a', err: ErrUnknownOpt, args: argsStr(`prgm -longa`), optIndex: 2},
{err: ErrDone, args: argsStr(`prgm -longa`), optIndex: 2},
}

assertSeq(t, s, p, wants)
Expand Down
Loading

0 comments on commit e639c7b

Please sign in to comment.