Skip to content

Commit

Permalink
move fixture tests to getopt_fixtures_test.go
Browse files Browse the repository at this point in the history
  • Loading branch information
jon-codes committed Sep 28, 2024
1 parent f39aa70 commit 98fb412
Show file tree
Hide file tree
Showing 5 changed files with 490 additions and 208 deletions.
1 change: 0 additions & 1 deletion .golangci.yml

This file was deleted.

15 changes: 9 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
.DEFAULT_GOAL = all

GOCC ?= /usr/local/musl/bin/musl-gcc
GOFLAGS = -ldflags '-linkmode external -extldflags "-static"'
BINDIR = bin
TMPDIR = tmp
Expand All @@ -13,7 +12,7 @@ GOPKG = github.com/jon-codes/getopt
all: deps fmt vet test

.PHONY: check
check: deps-check fmt-check vet test
check: deps-check fmt-check vet test-check

## deps: clean deps
.PHONY: deps
Expand Down Expand Up @@ -42,6 +41,10 @@ vet:
## test: go test
.PHONY: test
test:
go test ./...

.PHONY: test-check
test-check:
go test -v ./...

## cover: go test coverage
Expand All @@ -50,10 +53,10 @@ cover: temp
go test -v -coverprofile $(TMPDIR)/cover.out $(GOPKG)
go tool cover -html=$(TMPDIR)/cover.out

## build-testgen: build test generator
.PHONY: build-testgen
build-testgen:
CC=$(GOCC) go build $(GOFLAGS) -o $(TESTGEN_BIN) $(TESTGEN_SRC)
## testgen-build: build testgen binary
.PHONY: testgen-build
testgen-build:
CC=$(shell which musl-gcc) go build $(GOFLAGS) -o $(TESTGEN_BIN) $(TESTGEN_SRC)

## testgen: generate tests
.PHONY: testgen
Expand Down
36 changes: 18 additions & 18 deletions getopt.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ type GetOptError string
// Errors that can be returned by GetOpt.
const (
ErrDone = GetOptError("done")
ErrUnknownOpt = GetOptError("unknown option")
ErrMissingOptArg = GetOptError("missing required option argument")
ErrUnknownOpt = GetOptError("unrecognized option")
ErrIllegalOptArg = GetOptError("option does not take an argument")
ErrMissingOptArg = GetOptError("option requires an argument")
)

func (e GetOptError) Error() string {
Expand Down Expand Up @@ -152,7 +153,7 @@ func (s *State) GetOpt(p Params) (res Result, err error) {

// TODO: cite permutation algo source (musl libc)
pStart := s.OptIndex
if []rune(s.Args[s.OptIndex])[0] != '-' {
if s.Args[s.OptIndex] == "-" || []rune(s.Args[s.OptIndex])[0] != '-' {
switch p.Mode {
case ModePosix:
return res, ErrDone
Expand All @@ -174,6 +175,10 @@ func (s *State) GetOpt(p Params) (res Result, err error) {
}
pEnd := s.OptIndex

// if s.Args[s.OptIndex] == "-" {
// return res, ErrUnknownOpt
// }

res, err = s.readOpt(p)

if pEnd > pStart {
Expand Down Expand Up @@ -202,14 +207,17 @@ func (s *State) readOpt(p Params) (res Result, err error) {
checkNext := true

if checkLong {
maybeOpt := s.argIndex == 1 && p.Function == FuncGetOptLongOnly
overrideOpt := s.argIndex == 1 && p.Function == FuncGetOptLongOnly
name, inline, foundInline := strings.Cut(arg[s.argIndex:], "=")
opt, found := findLongOpt(name, maybeOpt, p)
opt, found := findLongOpt(name, overrideOpt, p)
if found {
s.OptIndex++
hasArg = opt.HasArg
res.Name = opt.Name
if foundInline {
if opt.HasArg == NoArgument {
err = ErrIllegalOptArg
}
res.OptArg = inline
checkNext = false
}
Expand Down Expand Up @@ -240,10 +248,8 @@ func (s *State) readOpt(p Params) (res Result, err error) {
}

if checkNext && hasArg != NoArgument && s.OptIndex < len(s.Args) {
if s.Args[s.OptIndex] != "--" {
res.OptArg = s.Args[s.OptIndex]
s.OptIndex++
}
res.OptArg = s.Args[s.OptIndex]
s.OptIndex++
}

if res.Char == 0 && res.Name == "" {
Expand Down Expand Up @@ -278,9 +284,9 @@ func findOpt(char rune, p Params) (opt Opt, found bool) {
}
}

func findLongOpt(name string, maybeOpt bool, p Params) (longOpt LongOpt, found bool) {
if len([]rune(name)) == 1 && maybeOpt {
_, found := findOpt(rune(name[0]), p)
func findLongOpt(name string, overrideOpt bool, p Params) (longOpt LongOpt, found bool) {
if len([]rune(name)) == 1 && overrideOpt {
_, found := findOpt([]rune(name)[0], p)
if found {
return longOpt, false
}
Expand All @@ -289,12 +295,6 @@ func findLongOpt(name string, maybeOpt bool, p Params) (longOpt LongOpt, found b
matched := []LongOpt{}

for _, lo := range p.LongOpts {
// if len([]rune(name)) == 1 {
// i := slices.IndexFunc(p.Opts, func(s Opt) bool { return []rune(name)[0] == s.Char })
// if i >= 0 {
// return lo, true
// }
// }
if strings.HasPrefix(lo.Name, name) {
matched = append(matched, lo)
}
Expand Down
190 changes: 190 additions & 0 deletions getopt_fixtures_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package getopt

import (
"encoding/json"
"errors"
"fmt"
"os"
"slices"
"testing"
"unicode/utf8"

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

const fixturePath = "testdata/fixtures.json"

func TestGetOpt_Fixtures(t *testing.T) {
fixtureFile, err := os.Open(fixturePath)
if err != nil {
t.Fatalf("error opening fixtures file: %v", err)
}
defer fixtureFile.Close()

decoder := json.NewDecoder(fixtureFile)

// read open bracket
_, err = decoder.Token()
if err != nil {
t.Fatalf("error decoding cases: %v", err)
}

// while the array contains values
for decoder.More() {
var record testgen.FixtureRecord
if err := decoder.Decode(&record); err != nil {
t.Fatalf("error decoding fixture: %v", err)
}
fixture, err := buildFixture(record)
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)
t.Run(testName, func(t *testing.T) {
assertFixture(t, fixture)
})
}

// read closing bracket
_, err = decoder.Token()
if err != nil {
t.Fatalf("error decoding cases: %v", err)
}
}

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

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

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

if want.Err == nil {
if err != nil {
t.Errorf("iter %d, wanted no error, but got %q", iter, err)
}
} else {
if err == nil {
t.Errorf("iter %d, wanted an error, but didn't get one", iter)
} else if !errors.Is(err, want.Err) {
t.Errorf("iter %d, got error %q, but wanted %q", iter, err, want.Err)
}
}

if res.Char != want.Char {
t.Errorf("iter %d, got Char %q, but wanted %q", iter, res.Char, want.Char)
}
if res.Name != want.Name {
t.Errorf("iter %d, got Name %q, but wanted %q", iter, res.Name, want.Name)
}
if res.OptArg != want.OptArg {
t.Errorf("iter %d, got OptArg %q, but wanted %q", iter, res.OptArg, want.OptArg)
}
}

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

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
}
Loading

0 comments on commit 98fb412

Please sign in to comment.