Skip to content

Commit

Permalink
Merge pull request urfave#1988 from dearchap/add_test_cov
Browse files Browse the repository at this point in the history
Fix: Increase test coverage
  • Loading branch information
dearchap authored Oct 22, 2024
2 parents 5053ec7 + f4c5bda commit 4770e1d
Show file tree
Hide file tree
Showing 15 changed files with 435 additions and 157 deletions.
4 changes: 2 additions & 2 deletions args.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,11 @@ func (a *ArgumentBase[T, C, VC]) Usage() string {
func (a *ArgumentBase[T, C, VC]) Parse(s []string) ([]string, error) {
tracef("calling arg%[1] parse with args %[2]", &a.Name, s)
if a.Max == 0 {
fmt.Printf("WARNING args %s has max 0, not parsing argument", a.Name)
fmt.Printf("WARNING args %s has max 0, not parsing argument\n", a.Name)
return s, nil
}
if a.Max != -1 && a.Min > a.Max {
fmt.Printf("WARNING args %s has min[%d] > max[%d], not parsing argument", a.Name, a.Min, a.Max)
fmt.Printf("WARNING args %s has min[%d] > max[%d], not parsing argument\n", a.Name, a.Min, a.Max)
return s, nil
}

Expand Down
23 changes: 22 additions & 1 deletion args_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ func TestArgumentsRootCommand(t *testing.T) {
require.Error(t, errors.New("No help topic for '12.1"), cmd.Run(context.Background(), []string{"foo", "13", "10.1", "11.09", "12.1"}))
require.Equal(t, int64(13), ival)
require.Equal(t, []float64{10.1, 11.09}, fvals)

cmd.Arguments = append(cmd.Arguments,
&StringArg{
Name: "sa",
},
&UintArg{
Name: "ua",
Min: 2,
Max: 1, // max is less than min
},
)

require.NoError(t, cmd.Run(context.Background(), []string{"foo", "10"}))
}

func TestArgumentsSubcommand(t *testing.T) {
Expand Down Expand Up @@ -103,6 +116,7 @@ func TestArgsUsage(t *testing.T) {
name string
min int
max int
usage string
expected string
}{
{
Expand All @@ -111,6 +125,13 @@ func TestArgsUsage(t *testing.T) {
max: 1,
expected: "[ia]",
},
{
name: "optional",
min: 0,
max: 1,
usage: "[my optional usage]",
expected: "[my optional usage]",
},
{
name: "zero or more",
min: 0,
Expand Down Expand Up @@ -144,7 +165,7 @@ func TestArgsUsage(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
arg.Min, arg.Max = test.min, test.max
arg.Min, arg.Max, arg.UsageText = test.min, test.max, test.usage
require.Equal(t, test.expected, arg.Usage())
})
}
Expand Down
31 changes: 31 additions & 0 deletions cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand All @@ -26,3 +27,33 @@ func buildTestContext(t *testing.T) context.Context {

return ctx
}

func TestTracing(t *testing.T) {
olderr := os.Stderr
oldtracing := isTracingOn
defer func() {
os.Stderr = olderr
isTracingOn = oldtracing
}()

file, err := os.CreateTemp(os.TempDir(), "cli*")
assert.NoError(t, err)
os.Stderr = file

// Note we cant really set the env since the isTracingOn
// is read at module startup so any changes mid code
// wont take effect
isTracingOn = false
tracef("something")

isTracingOn = true
tracef("foothing")

assert.NoError(t, file.Close())

b, err := os.ReadFile(file.Name())
assert.NoError(t, err)

assert.Contains(t, string(b), "foothing")
assert.NotContains(t, string(b), "something")
}
15 changes: 15 additions & 0 deletions command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3975,6 +3975,21 @@ func TestZeroValueCommand(t *testing.T) {
assert.NoError(t, cmd.Run(context.Background(), []string{"foo"}))
}

func TestCommandInvalidName(t *testing.T) {
var cmd Command
assert.Equal(t, int64(0), cmd.Int("foo"))
assert.Equal(t, uint64(0), cmd.Uint("foo"))
assert.Equal(t, float64(0), cmd.Float("foo"))
assert.Equal(t, "", cmd.String("foo"))
assert.Equal(t, time.Time{}, cmd.Timestamp("foo"))
assert.Equal(t, time.Duration(0), cmd.Duration("foo"))

assert.Equal(t, []int64(nil), cmd.IntSlice("foo"))
assert.Equal(t, []uint64(nil), cmd.UintSlice("foo"))
assert.Equal(t, []float64(nil), cmd.FloatSlice("foo"))
assert.Equal(t, []string(nil), cmd.StringSlice("foo"))
}

func TestJSONExportCommand(t *testing.T) {
cmd := buildExtendedTestCommand()
cmd.Arguments = []Argument{
Expand Down
24 changes: 0 additions & 24 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ func (m *multiError) Errors() []error {

type requiredFlagsErr interface {
error
getMissingFlags() []string
}

type errRequiredFlags struct {
Expand All @@ -62,10 +61,6 @@ func (e *errRequiredFlags) Error() string {
return fmt.Sprintf("Required flags %q not set", joinedMissingFlags)
}

func (e *errRequiredFlags) getMissingFlags() []string {
return e.missingFlags
}

type mutuallyExclusiveGroup struct {
flag1Name string
flag2Name string
Expand All @@ -86,12 +81,6 @@ func (e *mutuallyExclusiveGroupRequiredFlag) Error() string {
for _, f := range grpf {
grpString = append(grpString, f.Names()...)
}
if len(e.flags.Flags) == 1 {
err := errRequiredFlags{
missingFlags: grpString,
}
return err.Error()
}
missingFlags = append(missingFlags, strings.Join(grpString, " "))
}

Expand Down Expand Up @@ -148,10 +137,6 @@ func (ee *exitError) ExitCode() int {
return ee.exitCode
}

func (ee *exitError) Unwrap() error {
return ee.err
}

// HandleExitCoder handles errors implementing ExitCoder by printing their
// message and calling OsExiter with the given exit code.
//
Expand Down Expand Up @@ -198,12 +183,3 @@ func handleMultiError(multiErr MultiError) int {
}
return code
}

type typeError[T any] struct {
other any
}

func (te *typeError[T]) Error() string {
var t T
return fmt.Sprintf("Expected type %T got instead %T", t, te.other)
}
49 changes: 49 additions & 0 deletions errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,62 @@ func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) {

exitErr := Exit("galactic perimeter breach", 9)
exitErr2 := Exit("last ExitCoder", 11)

err := newMultiError(errors.New("wowsa"), errors.New("egad"), exitErr, exitErr2)
HandleExitCoder(err)

assert.Equal(t, 11, exitCode)
assert.True(t, called)
}

type exitFormatter struct {
code int
}

func (f *exitFormatter) Format(s fmt.State, verb rune) {
_, _ = s.Write([]byte("some other special"))
}

func (f *exitFormatter) ExitCode() int {
return f.code
}

func (f *exitFormatter) Error() string {
return fmt.Sprintf("my special error code %d", f.code)
}

func TestHandleExitCoder_ErrorFormatter(t *testing.T) {
exitCode := 0
called := false

OsExiter = func(rc int) {
if !called {
exitCode = rc
called = true
}
}

oldWriter := ErrWriter
var buf bytes.Buffer
ErrWriter = &buf
defer func() {
OsExiter = fakeOsExiter
ErrWriter = oldWriter
}()

exitErr := Exit("galactic perimeter breach", 9)
exitErr2 := Exit("last ExitCoder", 11)
exitErr3 := &exitFormatter{code: 12}

// add some recursion for multi error to fix test coverage
err := newMultiError(errors.New("wowsa"), errors.New("egad"), exitErr3, newMultiError(exitErr, exitErr2))
HandleExitCoder(err)

assert.Equal(t, 11, exitCode)
assert.True(t, called)
assert.Contains(t, buf.String(), "some other special")
}

func TestHandleExitCoder_MultiErrorWithoutExitCoder(t *testing.T) {
exitCode := 0
called := false
Expand Down
4 changes: 0 additions & 4 deletions flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,6 @@ type DocGenerationFlag interface {
// GetUsage returns the usage string for the flag
GetUsage() string

// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
GetValue() string

// GetDefaultText returns the default text for this flag
GetDefaultText() string

Expand Down
23 changes: 4 additions & 19 deletions flag_float_slice.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package cli

import (
"flag"
)

type (
FloatSlice = SliceBase[float64, NoConfig, floatValue]
FloatSliceFlag = FlagBase[[]float64, NoConfig, FloatSlice]
Expand All @@ -14,22 +10,11 @@ var NewFloatSlice = NewSliceBase[float64, NoConfig, floatValue]
// FloatSlice looks up the value of a local FloatSliceFlag, returns
// nil if not found
func (cmd *Command) FloatSlice(name string) []float64 {
if flSet := cmd.lookupFlagSet(name); flSet != nil {
return lookupFloatSlice(name, flSet, cmd.Name)
}

return nil
}

func lookupFloatSlice(name string, set *flag.FlagSet, cmdName string) []float64 {
fl := set.Lookup(name)
if fl != nil {
if v, ok := fl.Value.(flag.Getter).Get().([]float64); ok {
tracef("float slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmdName)
return v
}
if v, ok := cmd.Value(name).([]float64); ok {
tracef("float slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
return v
}

tracef("float slice NOT available for flag name %[1]q (cmd=%[2]q)", name, cmdName)
tracef("float slice NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
return nil
}
47 changes: 4 additions & 43 deletions flag_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,6 @@ func (f *fnValue) String() string {
return f.v.String()
}

func (f *fnValue) Serialize() string {
if s, ok := f.v.(Serializer); ok {
return s.Serialize()
}
return f.v.String()
}

func (f *fnValue) IsBoolFlag() bool { return f.isBool }
func (f *fnValue) Count() int {
if s, ok := f.v.(Countable); ok {
Expand Down Expand Up @@ -96,15 +89,6 @@ type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
value Value // value representing this flag's value
}

// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *FlagBase[T, C, V]) GetValue() string {
if reflect.TypeOf(f.Value).Kind() == reflect.Bool {
return ""
}
return fmt.Sprintf("%v", f.Value)
}

// Apply populates the flag given the flag set and environment
func (f *FlagBase[T, C, V]) Apply(set *flag.FlagSet) error {
tracef("apply (flag=%[1]q)", f.Name)
Expand All @@ -128,13 +112,7 @@ func (f *FlagBase[T, C, V]) Apply(set *flag.FlagSet) error {
)
}
} else if val == "" && reflect.TypeOf(f.Value).Kind() == reflect.Bool {
val = "false"
if err := tmpVal.Set(val); err != nil {
return fmt.Errorf(
"could not parse %[1]q as %[2]T value from %[3]s for flag %[4]s: %[5]s",
val, f.Value, source, f.Name, err,
)
}
_ = tmpVal.Set("false")
}

newVal = tmpVal.Get().(T)
Expand All @@ -149,11 +127,7 @@ func (f *FlagBase[T, C, V]) Apply(set *flag.FlagSet) error {

// Validate the given default or values set from external sources as well
if f.Validator != nil && f.ValidateDefaults {
if v, ok := f.value.Get().(T); !ok {
return &typeError[T]{
other: f.value.Get(),
}
} else if err := f.Validator(v); err != nil {
if err := f.Validator(f.value.Get().(T)); err != nil {
return err
}
}
Expand All @@ -176,11 +150,7 @@ func (f *FlagBase[T, C, V]) Apply(set *flag.FlagSet) error {
}
f.hasBeenSet = true
if f.Validator != nil {
if v, ok := f.value.Get().(T); !ok {
return &typeError[T]{
other: f.value.Get(),
}
} else if err := f.Validator(v); err != nil {
if err := f.Validator(f.value.Get().(T)); err != nil {
return err
}
}
Expand Down Expand Up @@ -254,19 +224,10 @@ func (f *FlagBase[T, C, V]) GetDefaultText() string {
return v.ToString(f.Value)
}

// Get returns the flag’s value in the given Command.
func (f *FlagBase[T, C, V]) Get(cmd *Command) T {
if v, ok := cmd.Value(f.Name).(T); ok {
return v
}
var t T
return t
}

// RunAction executes flag action if set
func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error {
if f.Action != nil {
return f.Action(ctx, cmd, f.Get(cmd))
return f.Action(ctx, cmd, cmd.Value(f.Name).(T))
}

return nil
Expand Down
Loading

0 comments on commit 4770e1d

Please sign in to comment.