aboutsummaryrefslogtreecommitdiff
path: root/test/system/600-completion.bats
blob: cb4a2c5f814851eaa940c1215b63b6e26b68282b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
#!/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
        # 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[@]}" ""
                    ### FIXME how can we get the configured registries?
                    _check_completion_end NoFileComp
                    ### FIXME this fails if no registries are configured
                    assert "${#lines[@]}" -gt 2 "$* $cmd: No REGISTRIES found in suggestions"

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


@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)
    random_secret_name=$(random_string 30)
    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

    # Called with no args -- start with 'podman --help'. check_shell_completion() will
    # recurse for any subcommands.
    check_shell_completion

    # 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

    # Clean up the pod pause image
    run_podman image list --format '{{.ID}} {{.Repository}}'
    while read id name; do
        if [[ "$name" =~ /podman-pause ]]; then
            run_podman rmi $id
        fi
    done <<<"$output"

}

@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-)"

        # 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

    random_name=$(random_string 30)
    random_file=$(random_string 30)
    run_podman run --name $random_name $IMAGE sh -c "touch /tmp/$random_file && touch /tmp/${random_file}2 && mkdir /emptydir"

    # check completion for podman cp
    run_completion cp ""
    assert "$output" =~ ".*^$random_name\:\$.*" "podman cp suggest container names"

    run_completion cp "$random_name:"
    assert "$output" =~ ".*^$random_name\:/etc/\$.*" "podman cp suggest paths in container"

    run_completion cp "$random_name:/tmp"
    assert "$output" =~ ".*^$random_name\:/tmp/$random_file\$.*" "podman cp suggest custom file in container"

    run_completion cp "$random_name:/tmp/$random_file"
    assert "$output" =~ ".*^$random_name\:/tmp/$random_file\$.*" "podman cp suggest /tmp/$random_file file in container"
    assert "$output" =~ ".*^$random_name\:/tmp/${random_file}2\$.*" "podman cp suggest /tmp/${random_file}2 file in container"

    run_completion cp "$random_name:/emptydir"
    assert "$output" =~ ".*^$random_name\:/emptydir/\$.*ShellCompDirectiveNoSpace" "podman cp suggest empty dir with no space directive (:2)"

    # cleanup container
    run_podman rm $random_name
}