Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions identification.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ func identify(cmd Command, args []string) (Command, []string, error) {
return def, args, nil
}
return nullCommand{parent: cmd, name: name}, rest, nil
} else if len(rest) == 0 {
// With no remaining args, there is nothing to descend into, so we don't
// resolve found's subcommands. This lets callers like `which` identify
// a command even when it doesn't fulfill a discovery contract.
return found, rest, nil
} else if subcmds, err := found.Subcommands(); err != nil {
return found, rest, err
} else if len(subcmds) > 0 {
Expand Down
37 changes: 37 additions & 0 deletions identification_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package exoskeleton

import (
"errors"
"fmt"
"testing"

Expand Down Expand Up @@ -43,6 +44,42 @@ func TestIdentifyByAlias(t *testing.T) {
}
}

// A command discovered via a describe-based contract (e.g. OpenCLI) resolves
// its subcommands lazily by shelling out. When the target doesn't implement the
// describe flag, that resolution fails. Identifying such a command with no
// remaining args must not trigger the resolution — locating the command should
// not require running its describe flag.
func TestIdentifyDoesNotResolveSubcommandsWithoutRemainingArgs(t *testing.T) {
described := false
failing := &executableCommand{
name: "plain",
discoverer: &discoverer{},
cache: nullCache{},
describe: func(cmd *executableCommand) (*commandDescriptor, error) {
described = true
return nil, errors.New("plain: error: unknown flag --help-opencli")
},
}

help := &builtinCommand{definition: &EmbeddedCommand{Name: `help`}}
complete := &builtinCommand{definition: &EmbeddedCommand{Name: `complete`}}
entrypoint := &Entrypoint{cmds: Commands{help, complete, failing}}

// No remaining args: subcommands must not be resolved.
cmd, rest, err := entrypoint.Identify([]string{"plain"})
assert.NoError(t, err)
assert.Equal(t, failing, cmd)
assert.Equal(t, []string{}, rest)
assert.False(t, described, "should not have attempted to describe the command")

// With a remaining positional arg, subcommands are resolved to look for a
// match — confirming the short-circuit is specific to the empty-rest case.
described = false
_, _, err = entrypoint.Identify([]string{"plain", "sub"})
assert.Error(t, err)
assert.True(t, described, "should have attempted to describe the command")
}

func TestIdentify(t *testing.T) {
// all
// ├── a
Expand Down
Loading