diff options
author | Paul Holzinger <paul.holzinger@web.de> | 2020-12-07 15:50:56 +0100 |
---|---|---|
committer | Paul Holzinger <paul.holzinger@web.de> | 2020-12-09 19:13:28 +0100 |
commit | 2870a0b0a6b94528b82df7cee57c8c6a0252fc85 (patch) | |
tree | 92f41fa56baaf9ce3393478d04b1f93e521c6582 | |
parent | 7caef9c497da0c2a16caf4f15152bc543dfb6008 (diff) | |
download | podman-2870a0b0a6b94528b82df7cee57c8c6a0252fc85.tar.gz podman-2870a0b0a6b94528b82df7cee57c8c6a0252fc85.tar.bz2 podman-2870a0b0a6b94528b82df7cee57c8c6a0252fc85.zip |
Add system test for shell completion
There exists a unit test to ensure that shell completion functions are
defined. However there was no check about the quality of the provided
shell completions. Lets change that.
The idea is to create a general test that makes sure we are suggesting
containers,pods,images... for the correct commands. This works by
reading the command use line and checking for each arg if we provide
the correct suggestions for this arg.
It includes the following tests:
- flag suggestions if [options] is set
- container, pod, image, network, volume, registry completion
- path completion for the appropriate arg KEYWORDS (`PATH`,`CONTEXT`,etc.)
- no completion if there are no args
- completion for more than one arg if it ends with `...]`
The test does not cover completion values for flags and not every arg KEYWORD
is supported. This is still a huge improvement and covers most use cases.
This test spotted several inconsistencies between the completion and the
command use line. All of them have been adjusted to make the test pass.
The biggest advantage is that the completions always match the latest
command changes. So if someone changes the arguments for a command this
ensures that the completions must be adjusted.
Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
-rw-r--r-- | cmd/podman/common/completion.go | 78 | ||||
-rw-r--r-- | cmd/podman/containers/commit.go | 2 | ||||
-rw-r--r-- | cmd/podman/containers/cp.go | 2 | ||||
-rw-r--r-- | cmd/podman/containers/exec.go | 2 | ||||
-rw-r--r-- | cmd/podman/containers/logs.go | 6 | ||||
-rw-r--r-- | cmd/podman/containers/port.go | 2 | ||||
-rw-r--r-- | cmd/podman/containers/runlabel.go | 2 | ||||
-rw-r--r-- | cmd/podman/diff.go | 2 | ||||
-rw-r--r-- | cmd/podman/generate/kube.go | 2 | ||||
-rw-r--r-- | cmd/podman/generate/systemd.go | 2 | ||||
-rw-r--r-- | cmd/podman/images/build.go | 3 | ||||
-rw-r--r-- | cmd/podman/images/import.go | 5 | ||||
-rw-r--r-- | cmd/podman/images/push.go | 2 | ||||
-rw-r--r-- | cmd/podman/images/save.go | 2 | ||||
-rw-r--r-- | cmd/podman/images/untag.go | 2 | ||||
-rw-r--r-- | cmd/podman/inspect.go | 4 | ||||
-rw-r--r-- | cmd/podman/manifest/push.go | 2 | ||||
-rw-r--r-- | cmd/podman/networks/create.go | 2 | ||||
-rw-r--r-- | cmd/podman/play/kube.go | 2 | ||||
-rw-r--r-- | cmd/podman/pods/prune.go | 4 | ||||
-rw-r--r-- | cmd/podman/system/service.go | 3 | ||||
-rw-r--r-- | cmd/podman/system/unshare.go | 2 | ||||
-rw-r--r-- | test/system/015-help.bats | 13 | ||||
-rw-r--r-- | test/system/600-completion.bats | 272 | ||||
-rw-r--r-- | test/system/helpers.bash | 10 |
25 files changed, 392 insertions, 36 deletions
diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index 25f4d0f79..f792b2713 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -278,7 +278,6 @@ func validCurrentCmdLine(cmd *cobra.Command, args []string, toComplete string) b return true } } - cobra.CompDebugln(err.Error(), true) return false } return true @@ -445,6 +444,29 @@ func AutocompleteNetworks(cmd *cobra.Command, args []string, toComplete string) return getNetworks(cmd, toComplete) } +// AutocompleteDefaultOneArg - Autocomplete path only for the first argument. +func AutocompleteDefaultOneArg(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return nil, cobra.ShellCompDirectiveDefault + } + return nil, cobra.ShellCompDirectiveNoFileComp +} + +// AutocompleteCommitCommand - Autocomplete podman commit command args. +func AutocompleteCommitCommand(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + if len(args) == 0 { + return getContainers(cmd, toComplete, completeDefault) + } + if len(args) == 1 { + return getImages(cmd, toComplete) + } + // don't complete more than 2 args + return nil, cobra.ShellCompDirectiveNoFileComp +} + // AutocompleteCpCommand - Autocomplete podman cp command args. func AutocompleteCpCommand(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if !validCurrentCmdLine(cmd, args, toComplete) { @@ -465,6 +487,43 @@ func AutocompleteCpCommand(cmd *cobra.Command, args []string, toComplete string) return nil, cobra.ShellCompDirectiveNoFileComp } +// AutocompleteExecCommand - Autocomplete podman exec command args. +func AutocompleteExecCommand(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + if len(args) == 0 { + return getContainers(cmd, toComplete, completeDefault, "running") + } + return nil, cobra.ShellCompDirectiveDefault +} + +// AutocompleteRunlabelCommand - Autocomplete podman container runlabel command args. +func AutocompleteRunlabelCommand(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + if len(args) == 0 { + // FIXME: What labels can we recommend here? + return nil, cobra.ShellCompDirectiveNoFileComp + } + if len(args) == 1 { + return getImages(cmd, toComplete) + } + return nil, cobra.ShellCompDirectiveDefault +} + +// AutocompletePortCommand - Autocomplete podman port command args. +func AutocompletePortCommand(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + if len(args) == 0 { + return getContainers(cmd, toComplete, completeDefault) + } + return nil, cobra.ShellCompDirectiveNoFileComp +} + // AutocompleteNetworkConnectCmd - Autocomplete podman network connect/disconnect command args. func AutocompleteNetworkConnectCmd(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { @@ -496,6 +555,23 @@ func AutocompleteTopCmd(cmd *cobra.Command, args []string, toComplete string) ([ return descriptors, cobra.ShellCompDirectiveNoFileComp } +// AutocompleteInspect - Autocomplete podman inspect. +func AutocompleteInspect(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if !validCurrentCmdLine(cmd, args, toComplete) { + return nil, cobra.ShellCompDirectiveNoFileComp + } + containers, _ := getContainers(cmd, toComplete, completeDefault) + images, _ := getImages(cmd, toComplete) + pods, _ := getPods(cmd, toComplete, completeDefault) + networks, _ := getNetworks(cmd, toComplete) + volumes, _ := getVolumes(cmd, toComplete) + suggestions := append(containers, images...) + suggestions = append(suggestions, pods...) + suggestions = append(suggestions, networks...) + suggestions = append(suggestions, volumes...) + return suggestions, cobra.ShellCompDirectiveNoFileComp +} + // AutocompleteSystemConnections - Autocomplete system connections. func AutocompleteSystemConnections(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if !validCurrentCmdLine(cmd, args, toComplete) { diff --git a/cmd/podman/containers/commit.go b/cmd/podman/containers/commit.go index c5c7673b2..ff06e10f7 100644 --- a/cmd/podman/containers/commit.go +++ b/cmd/podman/containers/commit.go @@ -24,7 +24,7 @@ var ( Long: commitDescription, RunE: commit, Args: cobra.RangeArgs(1, 2), - ValidArgsFunction: common.AutocompleteContainers, + ValidArgsFunction: common.AutocompleteCommitCommand, Example: `podman commit -q --message "committing container to image" reverent_golick image-committed podman commit -q --author "firstName lastName" reverent_golick image-committed podman commit -q --pause=false containerID image-committed diff --git a/cmd/podman/containers/cp.go b/cmd/podman/containers/cp.go index fd3aa7680..9b0a01a2f 100644 --- a/cmd/podman/containers/cp.go +++ b/cmd/podman/containers/cp.go @@ -13,7 +13,7 @@ var ( You can copy from the container's file system to the local machine or the reverse, from the local filesystem to the container. If "-" is specified for either the SRC_PATH or DEST_PATH, you can also stream a tar archive from STDIN or to STDOUT. The CONTAINER can be a running or stopped container. The SRC_PATH or DEST_PATH can be a file or directory. ` cpCommand = &cobra.Command{ - Use: "cp [options] SRC_PATH DEST_PATH", + Use: "cp [options] [CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH", Short: "Copy files/folders between a container and the local filesystem", Long: cpDescription, Args: cobra.ExactArgs(2), diff --git a/cmd/podman/containers/exec.go b/cmd/podman/containers/exec.go index 306bae58e..3d4918d50 100644 --- a/cmd/podman/containers/exec.go +++ b/cmd/podman/containers/exec.go @@ -26,7 +26,7 @@ var ( Long: execDescription, RunE: exec, DisableFlagsInUseLine: true, - ValidArgsFunction: common.AutocompleteContainersRunning, + ValidArgsFunction: common.AutocompleteExecCommand, Example: `podman exec -it ctrID ls podman exec -it -w /tmp myCtr pwd podman exec --user root ctrID ls`, diff --git a/cmd/podman/containers/logs.go b/cmd/podman/containers/logs.go index 1fa4ac11f..d4ede370a 100644 --- a/cmd/podman/containers/logs.go +++ b/cmd/podman/containers/logs.go @@ -69,6 +69,12 @@ var ( ) func init() { + // if run remotely we only allow one container arg + if registry.IsRemote() { + logsCommand.Use = "logs [options] CONTAINER" + containerLogsCommand.Use = logsCommand.Use + } + // logs registry.Commands = append(registry.Commands, registry.CliCommand{ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, diff --git a/cmd/podman/containers/port.go b/cmd/podman/containers/port.go index ac31e158e..d59161149 100644 --- a/cmd/podman/containers/port.go +++ b/cmd/podman/containers/port.go @@ -26,7 +26,7 @@ var ( Args: func(cmd *cobra.Command, args []string) error { return validate.CheckAllLatestAndCIDFile(cmd, args, true, false) }, - ValidArgsFunction: common.AutocompleteContainers, + ValidArgsFunction: common.AutocompletePortCommand, Example: `podman port --all podman port ctrID 80/tcp podman port --latest 80`, diff --git a/cmd/podman/containers/runlabel.go b/cmd/podman/containers/runlabel.go index 2f6d2eb05..6ebba4935 100644 --- a/cmd/podman/containers/runlabel.go +++ b/cmd/podman/containers/runlabel.go @@ -30,7 +30,7 @@ var ( Long: runlabelDescription, RunE: runlabel, Args: cobra.MinimumNArgs(2), - ValidArgsFunction: common.AutocompleteImages, + ValidArgsFunction: common.AutocompleteRunlabelCommand, Example: `podman container runlabel run imageID podman container runlabel install imageID arg1 arg2 podman container runlabel --display run myImage`, diff --git a/cmd/podman/diff.go b/cmd/podman/diff.go index 5e6abe243..e094e6bdd 100644 --- a/cmd/podman/diff.go +++ b/cmd/podman/diff.go @@ -18,7 +18,7 @@ var ( // Command: podman _diff_ Object_ID diffDescription = `Displays changes on a container or image's filesystem. The container or image will be compared to its parent layer.` diffCmd = &cobra.Command{ - Use: "diff [options] {CONTAINER_ID | IMAGE_ID}", + Use: "diff [options] {CONTAINER|IMAGE}", Args: validate.IDOrLatestArgs, Short: "Display the changes to the object's file system", Long: diffDescription, diff --git a/cmd/podman/generate/kube.go b/cmd/podman/generate/kube.go index 0517db19a..cb608e7b5 100644 --- a/cmd/podman/generate/kube.go +++ b/cmd/podman/generate/kube.go @@ -22,7 +22,7 @@ var ( Whether the input is for a container or pod, Podman will always generate the specification as a pod.` kubeCmd = &cobra.Command{ - Use: "kube [options] CONTAINER... | POD", + Use: "kube [options] {CONTAINER...|POD}", Short: "Generate Kubernetes YAML from a container or pod.", Long: kubeDescription, RunE: kube, diff --git a/cmd/podman/generate/systemd.go b/cmd/podman/generate/systemd.go index e9cf76aae..f9099d3b8 100644 --- a/cmd/podman/generate/systemd.go +++ b/cmd/podman/generate/systemd.go @@ -26,7 +26,7 @@ var ( The generated units can later be controlled via systemctl(1).` systemdCmd = &cobra.Command{ - Use: "systemd [options] CTR|POD", + Use: "systemd [options] {CONTAINER|POD}", Short: "Generate systemd units.", Long: systemdDescription, RunE: systemd, diff --git a/cmd/podman/images/build.go b/cmd/podman/images/build.go index 739e1c265..48b355ecd 100644 --- a/cmd/podman/images/build.go +++ b/cmd/podman/images/build.go @@ -11,6 +11,7 @@ import ( "github.com/containers/buildah/pkg/parse" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/config" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/pkg/domain/entities" @@ -44,7 +45,7 @@ var ( Long: buildDescription, Args: cobra.MaximumNArgs(1), RunE: build, - ValidArgsFunction: completion.AutocompleteDefault, + ValidArgsFunction: common.AutocompleteDefaultOneArg, Example: `podman build . podman build --creds=username:password -t imageName -f Containerfile.simple . podman build --layers --force-rm --tag imageName .`, diff --git a/cmd/podman/images/import.go b/cmd/podman/images/import.go index f38ab3b19..ac59935ad 100644 --- a/cmd/podman/images/import.go +++ b/cmd/podman/images/import.go @@ -25,18 +25,19 @@ var ( Short: "Import a tarball to create a filesystem image", Long: importDescription, RunE: importCon, - ValidArgsFunction: completion.AutocompleteDefault, + Args: cobra.RangeArgs(1, 2), + ValidArgsFunction: common.AutocompleteDefaultOneArg, Example: `podman import http://example.com/ctr.tar url-image cat ctr.tar | podman -q import --message "importing the ctr.tar tarball" - image-imported cat ctr.tar | podman import -`, } imageImportCommand = &cobra.Command{ - Args: cobra.MinimumNArgs(1), Use: importCommand.Use, Short: importCommand.Short, Long: importCommand.Long, RunE: importCommand.RunE, + Args: importCommand.Args, ValidArgsFunction: importCommand.ValidArgsFunction, Example: `podman image import http://example.com/ctr.tar url-image cat ctr.tar | podman -q image import --message "importing the ctr.tar tarball" - image-imported diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go index 447b02fbe..d82083cd8 100644 --- a/cmd/podman/images/push.go +++ b/cmd/podman/images/push.go @@ -29,7 +29,7 @@ var ( // Command: podman push pushCmd = &cobra.Command{ - Use: "push [options] SOURCE [DESTINATION]", + Use: "push [options] IMAGE [DESTINATION]", Short: "Push an image to a specified destination", Long: pushDescription, RunE: imagePush, diff --git a/cmd/podman/images/save.go b/cmd/podman/images/save.go index 9ef2d0c91..3a35c4fad 100644 --- a/cmd/podman/images/save.go +++ b/cmd/podman/images/save.go @@ -43,7 +43,7 @@ var ( } return nil }, - ValidArgsFunction: completion.AutocompleteNone, + ValidArgsFunction: common.AutocompleteImages, Example: `podman save --quiet -o myimage.tar imageID podman save --format docker-dir -o ubuntu-dir ubuntu podman save > alpine-all.tar alpine:latest`, diff --git a/cmd/podman/images/untag.go b/cmd/podman/images/untag.go index 17dc21203..3cf62713b 100644 --- a/cmd/podman/images/untag.go +++ b/cmd/podman/images/untag.go @@ -9,7 +9,7 @@ import ( var ( untagCommand = &cobra.Command{ - Use: "untag IMAGE [NAME...]", + Use: "untag IMAGE [IMAGE...]", Short: "Remove a name from a local image", Long: "Removes one or more names from a locally-stored image.", RunE: untag, diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index f62abe931..64daae951 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -20,12 +20,12 @@ var ( // Command: podman _inspect_ Object_ID inspectCmd = &cobra.Command{ - Use: "inspect [options] {CONTAINER_ID | IMAGE_ID} [...]", + Use: "inspect [options] {CONTAINER|IMAGE|POD|NETWORK|VOLUME} [...]", Short: "Display the configuration of object denoted by ID", RunE: inspectExec, Long: inspectDescription, TraverseChildren: true, - ValidArgsFunction: common.AutocompleteContainersAndImages, + ValidArgsFunction: common.AutocompleteInspect, Example: `podman inspect fedora podman inspect --type image fedora podman inspect CtrID ImgID diff --git a/cmd/podman/manifest/push.go b/cmd/podman/manifest/push.go index a3b469491..89faa42a2 100644 --- a/cmd/podman/manifest/push.go +++ b/cmd/podman/manifest/push.go @@ -24,7 +24,7 @@ type manifestPushOptsWrapper struct { var ( manifestPushOpts = manifestPushOptsWrapper{} pushCmd = &cobra.Command{ - Use: "push [options] SOURCE DESTINATION", + Use: "push [options] LIST DESTINATION", Short: "Push a manifest list or image index to a registry", Long: "Pushes manifest lists and image indexes to registries.", RunE: push, diff --git a/cmd/podman/networks/create.go b/cmd/podman/networks/create.go index 8db4bb89a..1a091f111 100644 --- a/cmd/podman/networks/create.go +++ b/cmd/podman/networks/create.go @@ -17,7 +17,7 @@ import ( var ( networkCreateDescription = `create CNI networks for containers and pods` networkCreateCommand = &cobra.Command{ - Use: "create [options] [NETWORK]", + Use: "create [options] [NAME]", Short: "network create", Long: networkCreateDescription, RunE: networkCreate, diff --git a/cmd/podman/play/kube.go b/cmd/podman/play/kube.go index db70ad7d4..5e227d05a 100644 --- a/cmd/podman/play/kube.go +++ b/cmd/podman/play/kube.go @@ -39,7 +39,7 @@ var ( Long: kubeDescription, RunE: kube, Args: cobra.ExactArgs(1), - ValidArgsFunction: completion.AutocompleteDefault, + ValidArgsFunction: common.AutocompleteDefaultOneArg, Example: `podman play kube nginx.yml podman play kube --creds user:password --seccomp-profile-root /custom/path apache.yml`, } diff --git a/cmd/podman/pods/prune.go b/cmd/podman/pods/prune.go index 444b0f5e0..e5e8679aa 100644 --- a/cmd/podman/pods/prune.go +++ b/cmd/podman/pods/prune.go @@ -7,7 +7,7 @@ import ( "os" "strings" - "github.com/containers/podman/v2/cmd/podman/common" + "github.com/containers/common/pkg/completion" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/validate" @@ -28,7 +28,7 @@ var ( Short: "Remove all stopped pods and their containers", Long: pruneDescription, RunE: prune, - ValidArgsFunction: common.AutocompletePods, + ValidArgsFunction: completion.AutocompleteNone, Example: `podman pod prune`, } ) diff --git a/cmd/podman/system/service.go b/cmd/podman/system/service.go index 42482b5d9..f8bdbfa10 100644 --- a/cmd/podman/system/service.go +++ b/cmd/podman/system/service.go @@ -10,6 +10,7 @@ import ( "time" "github.com/containers/common/pkg/completion" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/rootless" @@ -32,7 +33,7 @@ Enable a listening service for API access to Podman commands. Short: "Run API service", Long: srvDescription, RunE: service, - ValidArgsFunction: completion.AutocompleteDefault, + ValidArgsFunction: common.AutocompleteDefaultOneArg, Example: `podman system service --time=0 unix:///tmp/podman.sock`, } diff --git a/cmd/podman/system/unshare.go b/cmd/podman/system/unshare.go index 437cf7b2e..364852979 100644 --- a/cmd/podman/system/unshare.go +++ b/cmd/podman/system/unshare.go @@ -14,7 +14,7 @@ import ( var ( unshareDescription = "Runs a command in a modified user namespace." unshareCommand = &cobra.Command{ - Use: "unshare [COMMAND [ARG ...]]", + Use: "unshare [COMMAND [ARG...]]", DisableFlagsInUseLine: true, Short: "Run a command in a modified user namespace", Long: unshareDescription, diff --git a/test/system/015-help.bats b/test/system/015-help.bats index 22db8be8a..5f38c34a1 100644 --- a/test/system/015-help.bats +++ b/test/system/015-help.bats @@ -12,22 +12,11 @@ # load helpers -# run 'podman help', parse the output looking for 'Available Commands'; -# return that list. -function podman_commands() { - dprint "$@" - run_podman help "$@" |\ - awk '/^Available Commands:/{ok=1;next}/^Options:/{ok=0}ok { print $1 }' |\ - grep . - "$output" -} - - function check_help() { local count=0 local -A found - for cmd in $(podman_commands "$@"); do + for cmd in $(_podman_commands "$@"); do # Human-readable podman command string, with multiple spaces collapsed command_string="podman $* $cmd" command_string=${command_string// / } # 'podman x' -> 'podman x' diff --git a/test/system/600-completion.bats b/test/system/600-completion.bats new file mode 100644 index 000000000..1e43cdc41 --- /dev/null +++ b/test/system/600-completion.bats @@ -0,0 +1,272 @@ +#!/usr/bin/env bats -*- bats -*- +# +# Test podman shell completion +# +# Shell completion is provided via the cobra library +# It is implement by calling a hidden subcommand called "__complete" +# + +load helpers + +function check_shell_completion() { + local count=0 + + # Newline character; used for confirming string output + local nl=" +" + + for cmd in $(_podman_commands "$@"); do + # Human-readable podman command string, with multiple spaces collapsed + name="podman" + if is_remote; then + name="podman-remote" + fi + command_string="$name $* $cmd" + command_string=${command_string// / } # 'podman x' -> 'podman x' + + run_podman "$@" $cmd --help + local full_help="$output" + + # The line immediately after 'Usage:' gives us a 1-line synopsis + usage=$(echo "$full_help" | grep -A1 '^Usage:' | tail -1) + [ -n "$usage" ] || die "podman $cmd: no Usage message found" + + # If usage ends in '[command]', recurse into subcommands + if expr "$usage" : '.*\[command\]$' >/dev/null; then + check_shell_completion "$@" $cmd + continue + fi + + # Trim to command path so we only have the args + args="${usage/$command_string/}" + # Trim leading whitespaces + args="${args#"${args%%[![:space:]]*}"}" + + # Extra args is used to match the correct argument number for the command + # This is important because some commands provide different suggestions based + # on the number of arguments. + extra_args=() + + for arg in $args; do + + match=false + i=0 + while true; do + + case $arg in + + # If we have options than we need to check if we are getting flag completion + "[options]") + # skip this for remote it fails if a command only has the latest flag e.g podman top + if ! is_remote; then + run_completion "$@" $cmd "--" + # If this fails there is most likely a problem with the cobra library + is "${lines[0]}" "--.*" "Found flag in suggestions" + [ ${#lines[@]} -gt 2 ] || die "No flag suggestions" + _check_completion_end NoFileComp + fi + # continue the outer for args loop + continue 2 + ;; + + *CONTAINER*) + run_completion "$@" $cmd "${extra_args[@]}" "" + is "$output" ".*-$random_container_name${nl}" "Found expected container in suggestions" + + match=true + # resume + ;;& + + *POD*) + run_completion "$@" $cmd "${extra_args[@]}" "" + is "$output" ".*-$random_pod_name${nl}" "Found expected pod in suggestions" + _check_completion_end NoFileComp + + match=true + # resume + ;;& + + *IMAGE*) + run_completion "$@" $cmd "${extra_args[@]}" "" + is "$output" ".*localhost/$random_image_name:$random_image_tag${nl}" "Found expected image in suggestions" + + # check that we complete the image with and without tag after at least one char is typed + run_completion "$@" $cmd "${extra_args[@]}" "${random_image_name:0:1}" + is "$output" ".*$random_image_name:$random_image_tag${nl}" "Found expected image with tag in suggestions" + is "$output" ".*$random_image_name${nl}" "Found expected image without tag in suggestions" + + # check that we complete the image id after at least two chars are typed + run_completion "$@" $cmd "${extra_args[@]}" "${random_image_id:0:2}" + is "$output" ".*$random_image_id${nl}" "Found expected image id in suggestions" + + match=true + # resume + ;;& + + *NETWORK*) + run_completion "$@" $cmd "${extra_args[@]}" "" + is "$output" ".*$random_network_name${nl}" "Found network in suggestions" + _check_completion_end NoFileComp + + match=true + # resume + ;;& + + *VOLUME*) + run_completion "$@" $cmd "${extra_args[@]}" "" + is "$output" ".*$random_volume_name${nl}" "Found volume in suggestions" + _check_completion_end NoFileComp + + match=true + # resume + ;;& + + *REGISTRY*) + run_completion "$@" $cmd "${extra_args[@]}" "" + ### FIXME how can we get the configured registries? + _check_completion_end NoFileComp + ### FIXME this fails if no registries are configured + [[ ${#lines[@]} -gt 2 ]] || die "No registries found in suggestions" + + match=true + # resume + ;;& + + *PATH* | *CONTEXT* | *KUBEFILE* | *COMMAND* | *ARG...* | *URI*) + # default shell completion should be done for everthing which accepts a path + run_completion "$@" $cmd "${extra_args[@]}" "" + + # cp is a special case it returns ShellCompDirectiveNoSpace + if [[ "$cmd" == "cp" ]]; then + _check_completion_end NoSpace + else + _check_completion_end Default + [[ ${#lines[@]} -eq 2 ]] || die "Suggestions are in the output" + fi + ;; + + *) + if [[ "$match" == "false" ]]; then + dprint "UNKNOWN arg: $arg for $command_string ${extra_args[*]}" + fi + ;; + + esac + + # Increment the argument array + extra_args+=("arg") + + i=$(($i + 1)) + # If the argument ends with ...] than we accept 0...n args + # Loop three times to make sure we are not only completing the first arg + if [[ ! ${arg} =~ "..." ]] || [[ i -gt 3 ]]; then + break + fi + + done + + done + + # If the command takes no more parameters make sure we are getting no completion + if [[ ! ${args##* } =~ "..." ]]; then + run_completion "$@" $cmd "${extra_args[@]}" "" + _check_completion_end NoFileComp + if [ ${#lines[@]} -gt 2 ]; then + # checking for line count is not enough since we may inlcude additional debug output + # lines starting with [Debug] are allowed + i=0 + length=$(( ${#lines[@]} - 2 )) + while [[ i -lt length ]]; do + [[ "${lines[$i]:0:7}" == "[Debug]" ]] || die "Suggestions are in the output" + i=$(( i + 1 )) + done + fi + fi + + done + +} + +# run the completion cmd +function run_completion() { + PODMAN="$PODMAN_COMPLETION" run_podman "$@" +} + +# check for the given ShellCompDirective (always last line) +function _check_completion_end() { + is "${lines[-1]}" "Completion ended with directive: ShellCompDirective$1" "Completion has wrong ShellCompDirective set" +} + + +@test "podman shell completion test" { + + random_container_name=$(random_string 30) + random_pod_name=$(random_string 30) + random_image_name=$(random_string 30) + random_image_name=${random_image_name,,} # name must be lowercase + random_image_tag=$(random_string 5) + random_network_name=$(random_string 30) + random_volume_name=$(random_string 30) + + # create a container for each state since some commands are only suggesting running container for example + run_podman create --name created-$random_container_name $IMAGE + run_podman run --name running-$random_container_name -d $IMAGE top + run_podman run --name pause-$random_container_name -d $IMAGE top + run_podman pause pause-$random_container_name + run_podman run --name exited-$random_container_name -d $IMAGE echo exited + + # create pods for each state + run_podman pod create --name created-$random_pod_name + run_podman pod create --name running-$random_pod_name + run_podman run -d --name running-$random_pod_name-con --pod running-$random_pod_name $IMAGE top + run_podman pod create --name degraded-$random_pod_name + run_podman run -d --name degraded-$random_pod_name-con --pod degraded-$random_pod_name $IMAGE echo degraded + run_podman pod create --name exited-$random_pod_name + run_podman run -d --name exited-$random_pod_name-con --pod exited-$random_pod_name $IMAGE echo exited + run_podman pod stop exited-$random_pod_name + + # create image name (just tag with new names no need to pull) + run_podman image tag $IMAGE $random_image_name:$random_image_tag + run_podman image list --format '{{.ID}}' --filter reference=$random_image_name + random_image_id="${lines[0]}" + + # create network + run_podman network create $random_network_name + + # create volume + run_podman volume create $random_volume_name + + + # $PODMAN may be a space-separated string, e.g. if we include a --url. + local -a podman_as_array=($PODMAN) + # __completeNoDesc must be the first arg if we running the completion cmd + PODMAN_COMPLETION="${podman_as_array[0]} __completeNoDesc ${podman_as_array[@]:1}" + + # Called with no args -- start with 'podman --help'. check_shell_completion() will + # recurse for any subcommands. + check_shell_completion + + # cleanup + run_podman volume rm $random_volume_name + + run_podman network rm $random_network_name + + run_podman image untag $IMAGE $random_image_name:$random_image_tag + + for state in created running degraded exited; do + run_podman pod rm --force $state-$random_pod_name + done + + for state in created running pause exited; do + run_podman rm --force $state-$random_container_name + done + + # Clean up the pod pause image + run_podman image list --format '{{.ID}} {{.Repository}}' + while read id name; do + if [[ "$name" =~ /pause ]]; then + run_podman rmi $id + fi + done <<<"$output" + +} diff --git a/test/system/helpers.bash b/test/system/helpers.bash index 2cced10c2..6a7c6cc42 100644 --- a/test/system/helpers.bash +++ b/test/system/helpers.bash @@ -521,5 +521,15 @@ function remove_same_dev_warning() { output=$(printf '%s\n' "${lines[@]}") } +# run 'podman help', parse the output looking for 'Available Commands'; +# return that list. +function _podman_commands() { + dprint "$@" + run_podman help "$@" | + awk '/^Available Commands:/{ok=1;next}/^Options:/{ok=0}ok { print $1 }' | + grep . + "$output" +} + # END miscellaneous tools ############################################################################### |