Files
podman/test/system/600-completion.bats
Paul Holzinger 0fb78905c1 Revert "Instrument cleanup tracer to log weird volume removal flake"
This reverts commit d633824a9527b9ec937cdfc8aacc890ec3249127.

The issue has been fixed in commit 9a0c0b2eef and I have not seen it
since so remove this special case.

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
2025-04-11 15:12:33 +02:00

413 lines
16 KiB
Bash

#!/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 setup() {
# $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
# set the var for the run_completion function
PODMAN_COMPLETION="${podman_as_array[0]} __completeNoDesc ${podman_as_array[@]:1}"
basic_setup
}
# Returns true if we are able to podman-pause
function _can_pause() {
# Even though we're just trying completion, not an actual unpause,
# podman barfs with:
# Error: unpause is not supported for cgroupv1 rootless containers
if is_rootless && is_cgroupsv1; then
return 1
fi
return 0
}
function check_shell_completion() {
local count=0
# Newline character; used for confirming string output
local nl="
"
for cmd in $(_podman_commands "$@"); do
# Skip the compose command which is calling `docker-compose --help`
# and hence won't match the assumptions made below.
if [[ "$cmd" == "compose" ]]; then
continue
fi
# 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)
assert "$usage" != "" "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]}" "--.*" \
"$* $cmd: flag(s) listed in suggestions"
assert "${#lines[@]}" -gt 2 \
"$* $cmd: No flag suggestions"
_check_completion_end NoFileComp
fi
# continue the outer for args loop
continue 2
;;
*CONTAINER*)
# podman unpause fails early on rootless cgroupsv1
if [[ $cmd = "unpause" ]] && ! _can_pause; then
continue 2
fi
name=$random_container_name
# special case podman cp suggest containers names with a colon
if [[ $cmd = "cp" ]]; then
name="$name:"
fi
run_completion "$@" $cmd "${extra_args[@]}" ""
is "$output" ".*-$name${nl}" \
"$* $cmd: actual container listed in suggestions"
match=true
# resume
;;&
*POD*)
run_completion "$@" $cmd "${extra_args[@]}" ""
is "$output" ".*-$random_pod_name${nl}" \
"$* $cmd: actual pod listed 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}" \
"$* $cmd: actual image listed in suggestions"
# check that we complete the image with 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}" \
"$* $cmd: image name:tag included 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}" \
"$* $cmd: image id included in suggestions when two leading characters present in command line"
match=true
# resume
;;&
*NETWORK*)
run_completion "$@" $cmd "${extra_args[@]}" ""
is "$output" ".*$random_network_name${nl}" \
"$* $cmd: actual network listed in suggestions"
_check_completion_end NoFileComp
match=true
# resume
;;&
*VOLUME*)
run_completion "$@" $cmd "${extra_args[@]}" ""
is "$output" ".*$random_volume_name${nl}" \
"$* $cmd: actual volume listed in suggestions"
_check_completion_end NoFileComp
match=true
# resume
;;&
*REGISTRY*)
run_completion "$@" $cmd "${extra_args[@]}" ""
_check_completion_end NoFileComp
assert "${#lines[@]}" -gt 2 "$* $cmd: No REGISTRIES found in suggestions"
# We can assume quay.io as we force our own CONTAINERS_REGISTRIES_CONF below.
assert "${lines[0]}" == "quay.io" "unqualified-search-registries from registries.conf listed"
match=true
# resume
;;&
*SECRET*)
run_completion "$@" $cmd "${extra_args[@]}" ""
is "$output" ".*$random_secret_name${nl}" \
"$* $cmd: actual secret listed in suggestions"
_check_completion_end NoFileComp
match=true
# resume
;;&
*PATH* | *CONTEXT* | *FILE* | *COMMAND* | *ARG...* | *URI*)
# default shell completion should be done for everything 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
_check_no_suggestions
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 two times to make sure we are not only completing the first arg
if [[ ! ${arg} =~ "..." ]] || [[ i -gt 1 ]]; 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
_check_no_suggestions
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"
}
# Check that there are no suggestions in the output.
# We could only check stdout and not stderr but this is not possible with bats.
# By default we always have two extra lines at the end for the ShellCompDirective.
# Then we could also have other extra lines for debugging, they will always start
# with [Debug], e.g. `[Debug] [Error] no container with name or ID "t12" found: no such container`.
function _check_no_suggestions() {
if [ ${#lines[@]} -gt 2 ]; then
# Checking for line count is not enough since we may include additional debug output.
# Lines starting with [Debug] are allowed.
local i=0
length=$((${#lines[@]} - 2))
while [[ i -lt length ]]; do
assert "${lines[$i]:0:7}" == "[Debug]" "Unexpected non-Debug output line: ${lines[$i]}"
i=$((i + 1))
done
fi
}
# bats test_tags=ci:parallel
@test "podman shell completion test" {
random_container_name="c-$(safename)"
random_pod_name="p-$(safename)"
random_image_name="i-$(safename)"
random_image_tag=$(random_string 5)
random_network_name="n-$(safename)"
random_volume_name="v-$(safename)"
random_secret_name="s-$(safename)"
random_secret_content=$(random_string 30)
secret_file=$PODMAN_TMPDIR/$(random_string 10)
echo $random_secret_content > $secret_file
# 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
if _can_pause; then
run_podman pause pause-$random_container_name
fi
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 pod create --name degraded-$random_pod_name
run_podman pod create --name exited-$random_pod_name
run_podman run -d --name running-$random_pod_name-con --pod running-$random_pod_name $IMAGE top
run_podman run -d --name degraded-$random_pod_name-con --pod degraded-$random_pod_name $IMAGE echo degraded
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
# create secret
run_podman secret create $random_secret_name $secret_file
# create our own registries.conf so we know what registry is set
local CONTAINERS_REGISTRIES_CONF="$PODMAN_TMPDIR/registries.conf"
echo 'unqualified-search-registries = ["quay.io"]' > "$CONTAINERS_REGISTRIES_CONF"
export CONTAINERS_REGISTRIES_CONF
# Called with no args -- start with 'podman --help'. check_shell_completion() will
# recurse for any subcommands.
check_shell_completion
# check inspect with format flag
run_completion inspect -f "{{."
assert "$output" =~ ".*^\{\{\.Args\}\}\$.*" "Defaulting to container type is completed"
run_completion inspect created-$random_container_name -f "{{."
assert "$output" =~ ".*^\{\{\.Args\}\}\$.*" "Container type is completed"
run_completion inspect $random_image_name -f "{{."
assert "$output" =~ ".*^\{\{\.Digest\}\}\$.*" "Image type is completed"
run_completion inspect $random_volume_name -f "{{."
assert "$output" =~ ".*^\{\{\.Anonymous\}\}\$.*" "Volume type is completed"
run_completion inspect created-$random_pod_name -f "{{."
assert "$output" =~ ".*^\{\{\.BlkioDeviceReadBps\}\}\$.*" "Pod type is completed"
run_completion inspect $random_network_name -f "{{."
assert "$output" =~ ".*^\{\{\.DNSEnabled\}\}\$.*" "Network type is completed"
# cleanup
run_podman secret rm $random_secret_name
rm -f $secret_file
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 -t 0 --force $state-$random_pod_name
done
for state in created running pause exited; do
run_podman rm --force $state-$random_container_name
done
}
# bats test_tags=ci:parallel
@test "podman shell completion for paths in container/image" {
skip_if_remote "mounting via remote does not work"
for cmd in create run; do
run_completion $cmd $IMAGE ""
assert "$output" =~ ".*^/etc/\$.*" "etc directory suggested (cmd: podman $cmd)"
assert "$output" =~ ".*^/home/\$.*" "home directory suggested (cmd: podman $cmd)"
assert "$output" =~ ".*^/root/\$.*" "root directory suggested (cmd: podman $cmd)"
# check completion for subdirectory
run_completion $cmd $IMAGE "/etc"
# It should be safe to assume the os-release file always exists in $IMAGE
assert "$output" =~ ".*^/etc/os-release\$.*" "/etc files suggested (cmd: podman $cmd /etc)"
# check completion for partial file name
run_completion $cmd $IMAGE "/etc/os-"
assert "$output" =~ ".*^/etc/os-release\$.*" "/etc files suggested (cmd: podman $cmd /etc/os-)"
# regression check for https://bugzilla.redhat.com/show_bug.cgi?id=2209809
# check for relative directory without slash in path.
run_completion $cmd $IMAGE "e"
assert "$output" =~ ".*^etc/\$.*" "etc dir suggested (cmd: podman $cmd e)"
# check completion with relative path components
# It is important the we will still use the image root and not escape to the host
run_completion $cmd $IMAGE "../../"
assert "$output" =~ ".*^../../etc/\$.*" "relative etc directory suggested (cmd: podman $cmd ../../)"
assert "$output" =~ ".*^../../home/\$.*" "relative home directory suggested (cmd: podman $cmd ../../)"
done
ctrname="c-$(safename)"
random_file=$(random_string 30)
run_podman run --name $ctrname $IMAGE sh -c "touch /tmp/$random_file && touch /tmp/${random_file}2 && mkdir /emptydir"
# check completion for podman cp
run_completion cp ""
assert "$output" =~ ".*^$ctrname\:\$.*" "podman cp suggest container names"
run_completion cp "$ctrname:"
assert "$output" =~ ".*^$ctrname\:/etc/\$.*" "podman cp suggest paths in container"
run_completion cp "$ctrname:/tmp"
assert "$output" =~ ".*^$ctrname\:/tmp/$random_file\$.*" "podman cp suggest custom file in container"
run_completion cp "$ctrname:/tmp/$random_file"
assert "$output" =~ ".*^$ctrname\:/tmp/$random_file\$.*" "podman cp suggest /tmp/$random_file file in container"
assert "$output" =~ ".*^$ctrname\:/tmp/${random_file}2\$.*" "podman cp suggest /tmp/${random_file}2 file in container"
run_completion cp "$ctrname:/emptydir"
assert "$output" =~ ".*^$ctrname\:/emptydir/\$.*ShellCompDirectiveNoSpace" "podman cp suggest empty dir with no space directive (:2)"
# cleanup container
run_podman rm $ctrname
}