mirror of
https://github.com/go-task/task.git
synced 2026-09-02 12:16:04 +02:00
refactor(completion): move the shell test harness out of completion/
Packaging globs completion/ into the release archives, so the harness added alongside the engine was about to ship run.sh and five wrapper scripts to every user. The previous commit worked around it by listing the packaged directories one by one, which quietly stops packaging any shell added later. Moving the harness to testdata/completion/ leaves completion/ holding only what we ship, so the glob can go back to completion/**/* and needs no maintenance when a shell is added.
This commit is contained in:
98
testdata/completion/run.sh
vendored
Executable file
98
testdata/completion/run.sh
vendored
Executable file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds the task binary and a fixture Taskfile, then runs every installed shell
|
||||
# wrapper against them. The engine itself is covered by the Go tests.
|
||||
set -u
|
||||
|
||||
# fish, Nushell and PowerShell resolve the binary through these; an ambient value
|
||||
# would silently test something other than the binary built below.
|
||||
unset TASK_EXE GO_TASK_PROGNAME
|
||||
|
||||
here=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
root=$(cd "$here/../.." && pwd)
|
||||
|
||||
bindir=$(mktemp -d)
|
||||
fixture=$(mktemp -d)
|
||||
trap 'rm -rf "$bindir" "$fixture"' EXIT
|
||||
|
||||
if ! go build -o "$bindir/task" "$root/cmd/task"; then
|
||||
echo "failed to build task binary" >&2
|
||||
exit 1
|
||||
fi
|
||||
export TASK_BIN="$bindir/task"
|
||||
# fish and PowerShell register completion for the command name `task`.
|
||||
export PATH="$bindir:$PATH"
|
||||
|
||||
cat > "$fixture/Taskfile.yml" <<'YML'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
build:
|
||||
desc: Build it
|
||||
deploy:
|
||||
desc: Deploy it
|
||||
aliases: [dep]
|
||||
requires:
|
||||
vars:
|
||||
- name: ENV
|
||||
enum: [dev, prod]
|
||||
- REGION
|
||||
docs:serve:
|
||||
desc: Serve docs
|
||||
YML
|
||||
touch "$fixture/extra.yaml" "$fixture/notes.txt"
|
||||
mkdir -p "$fixture/sub" "$fixture/other"
|
||||
# Nested path completion must keep the directory prefix.
|
||||
touch "$fixture/sub/nested.yml"
|
||||
# Shells must pass a quoted `--dir` value to the engine unquoted, and quote it
|
||||
# back on insert.
|
||||
mkdir -p "$fixture/with space"
|
||||
cat > "$fixture/with space/Taskfile.yml" <<'YML'
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
spaced:
|
||||
desc: Task from the spaced dir
|
||||
YML
|
||||
export TASK_FIXTURE="$fixture"
|
||||
|
||||
# Strict mode (CI) turns a missing shell into a failure instead of a skip, so an
|
||||
# absent pwsh never reads as a pass.
|
||||
strict=${TASK_COMPLETION_STRICT:-}
|
||||
|
||||
fails=0
|
||||
run() { # LABEL COMMAND...
|
||||
echo "== $1 =="
|
||||
"${@:2}" || fails=$((fails + 1))
|
||||
echo
|
||||
}
|
||||
run_if() { # BIN LABEL COMMAND...
|
||||
if command -v "$1" >/dev/null 2>&1; then run "${@:2}"; else skip "$2"; fi
|
||||
}
|
||||
skip() { # LABEL
|
||||
if [[ -n "$strict" ]]; then
|
||||
echo "== $1 == (MISSING — required under TASK_COMPLETION_STRICT)"
|
||||
fails=$((fails + 1))
|
||||
else
|
||||
echo "== $1 == (skipped: not installed)"
|
||||
fi
|
||||
echo
|
||||
}
|
||||
|
||||
run "bash wrapper" bash "$here/wrapper.bash"
|
||||
run_if zsh "zsh wrapper" zsh "$here/wrapper.zsh"
|
||||
run_if fish "fish wrapper" fish "$here/wrapper.fish"
|
||||
# --no-config-file: the user's own external completer must not interfere.
|
||||
run_if nu "nu wrapper" nu --no-config-file "$here/wrapper.nu"
|
||||
|
||||
pwsh_bin=$(command -v pwsh || command -v pwsh-preview || true)
|
||||
if [[ -n "$pwsh_bin" ]]; then
|
||||
run "powershell wrapper" "$pwsh_bin" -NoProfile -File "$here/wrapper.ps1"
|
||||
else
|
||||
skip "powershell wrapper"
|
||||
fi
|
||||
|
||||
if ((fails)); then
|
||||
echo "completion tests: $fails suite(s) failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "completion tests: all suites passed"
|
||||
81
testdata/completion/wrapper.bash
vendored
Executable file
81
testdata/completion/wrapper.bash
vendored
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
# Smoke-tests how the bash wrapper routes each directive, by stubbing the
|
||||
# bash-completion helpers. Requires TASK_BIN and TASK_FIXTURE.
|
||||
set -u
|
||||
|
||||
: "${TASK_BIN:?}"; : "${TASK_FIXTURE:?}"
|
||||
export TASK_EXE="$TASK_BIN"
|
||||
cd "$TASK_FIXTURE" || exit 1
|
||||
|
||||
fails=0
|
||||
CAP=""
|
||||
|
||||
_init_completion() {
|
||||
words=("${TEST_WORDS[@]}")
|
||||
cword=$TEST_CWORD
|
||||
cur="${TEST_WORDS[$TEST_CWORD]}"
|
||||
prev="${TEST_WORDS[$((TEST_CWORD - 1))]}"
|
||||
return 0
|
||||
}
|
||||
# Records $cur so a test can assert the inline `--flag=` prefix was stripped.
|
||||
_filedir() { CAP+="filedir:$* cur=$cur"$'\n'; }
|
||||
compopt() { CAP+="compopt:$*"$'\n'; }
|
||||
__ltrim_colon_completions() { :; }
|
||||
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/../../completion/bash/task.bash"
|
||||
|
||||
run() {
|
||||
CAP=""
|
||||
TEST_WORDS=("$@")
|
||||
TEST_CWORD=$((${#TEST_WORDS[@]} - 1))
|
||||
COMPREPLY=()
|
||||
_task
|
||||
}
|
||||
|
||||
reply_has() { # LABEL VALUE
|
||||
local v
|
||||
for v in "${COMPREPLY[@]}"; do [[ "$v" == "$2" ]] && { echo " ok $1"; return; }; done
|
||||
echo " FAIL $1 — '$2' missing from COMPREPLY: ${COMPREPLY[*]}"
|
||||
fails=$((fails + 1))
|
||||
}
|
||||
cap_has() { # LABEL PATTERN
|
||||
if [[ "$CAP" == *"$2"* ]]; then echo " ok $1"; else
|
||||
echo " FAIL $1 — expected '$2' in: $CAP"; fails=$((fails + 1)); fi
|
||||
}
|
||||
cap_hasnot() { # LABEL PATTERN
|
||||
if [[ "$CAP" == *"$2"* ]]; then
|
||||
echo " FAIL $1 — '$2' should be absent in: $CAP"; fails=$((fails + 1)); else
|
||||
echo " ok $1"; fi
|
||||
}
|
||||
|
||||
echo "bash: :4 (NoFileComp) forwards candidates, no file fallback"
|
||||
run task ''
|
||||
reply_has "candidate forwarded" build
|
||||
cap_hasnot "no file fallback" "filedir:"
|
||||
|
||||
echo "bash: :2|:32 (NoSpace|KeepOrder) disable the trailing space and the sort"
|
||||
run task deploy ''
|
||||
cap_has "nospace applied" "compopt:-o nospace"
|
||||
cap_has "keeporder applied" "compopt:-o nosort"
|
||||
|
||||
echo "bash: :8 (FilterFileExt) routes to extension-filtered files"
|
||||
run task --taskfile ''
|
||||
cap_has "filedir ext glob" "filedir:@(yml|yaml)"
|
||||
|
||||
echo "bash: :16 (FilterDirs) routes to directory completion"
|
||||
run task --dir ''
|
||||
cap_has "filedir -d" "filedir:-d"
|
||||
|
||||
echo "bash: :0 (Default) falls back to files"
|
||||
run task build -- ''
|
||||
cap_has "filedir default" "filedir:"
|
||||
|
||||
echo "bash: inline --flag= strips the prefix before file completion"
|
||||
run task --taskfile=sub/x
|
||||
cap_has "inline cur stripped" "cur=sub/x"
|
||||
|
||||
if ((fails)); then
|
||||
echo "bash: $fails failure(s)"
|
||||
exit 1
|
||||
fi
|
||||
echo "bash: all passed"
|
||||
55
testdata/completion/wrapper.fish
vendored
Executable file
55
testdata/completion/wrapper.fish
vendored
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env fish
|
||||
# Smoke-tests how the fish wrapper routes each directive, via `complete -C`.
|
||||
# Set up by run.sh: TASK_FIXTURE, and `task` on PATH = the binary under test.
|
||||
|
||||
cd $TASK_FIXTURE
|
||||
source (dirname (status -f))/../../completion/fish/task.fish
|
||||
|
||||
set -g fails 0
|
||||
|
||||
function cands
|
||||
complete -C $argv[1] | string split -f1 \t
|
||||
end
|
||||
|
||||
function has # LABEL LINE VALUE
|
||||
if contains -- $argv[3] (cands $argv[2])
|
||||
echo " ok $argv[1]"
|
||||
else
|
||||
echo " FAIL $argv[1] — '$argv[3]' missing from: "(cands $argv[2])
|
||||
set fails (math $fails + 1)
|
||||
end
|
||||
end
|
||||
|
||||
function hasnot # LABEL LINE VALUE
|
||||
if contains -- $argv[3] (cands $argv[2])
|
||||
echo " FAIL $argv[1] — '$argv[3]' should be absent"
|
||||
set fails (math $fails + 1)
|
||||
else
|
||||
echo " ok $argv[1]"
|
||||
end
|
||||
end
|
||||
|
||||
echo "fish: :4 (NoFileComp) forwards candidates, offers no files"
|
||||
has "candidate forwarded" 'task ' build
|
||||
hasnot "no file fallback" 'task ' notes.txt
|
||||
|
||||
echo "fish: :16 (FilterDirs) offers directories only"
|
||||
has "dir offered" 'task --dir ' sub/
|
||||
hasnot "no plain file" 'task --dir ' notes.txt
|
||||
|
||||
echo "fish: :8 (FilterFileExt) filters by extension"
|
||||
has "matching file" 'task --taskfile ' Taskfile.yml
|
||||
hasnot "non-matching file" 'task --taskfile ' notes.txt
|
||||
|
||||
echo "fish: :0 (Default) falls back to files"
|
||||
has "file offered" 'task build -- ' notes.txt
|
||||
|
||||
echo "fish: inline --flag=path keeps the --flag= prefix"
|
||||
has "inline nested" 'task --taskfile=sub/' --taskfile=sub/nested.yml
|
||||
hasnot "inline non-matching" 'task --taskfile=' --taskfile=notes.txt
|
||||
|
||||
if test $fails -ne 0
|
||||
echo "fish: $fails failure(s)"
|
||||
exit 1
|
||||
end
|
||||
echo "fish: all passed"
|
||||
94
testdata/completion/wrapper.nu
vendored
Normal file
94
testdata/completion/wrapper.nu
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env nu
|
||||
# Smoke-tests how the Nushell wrapper routes each directive. External completers
|
||||
# only run in the interactive REPL, so the closure is called directly.
|
||||
# Set up by run.sh: $env.TASK_FIXTURE, and `task` on PATH = the binary under test.
|
||||
|
||||
# `source` needs a parse-time constant path.
|
||||
const TASK_NU = (path self "../../completion/nu/task-completions.nu")
|
||||
|
||||
# Installed before the wrapper is sourced, to assert the delegation path.
|
||||
$env.config.completions.external.completer = {|spans| [{ value: $"prev:($spans | first)" }] }
|
||||
|
||||
source $TASK_NU
|
||||
|
||||
cd $env.TASK_FIXTURE
|
||||
|
||||
let completer = $env.config.completions.external.completer
|
||||
|
||||
def cands [spans: list<string>] {
|
||||
let out = (do $completer $spans)
|
||||
if $out == null { [] } else { $out | get value }
|
||||
}
|
||||
|
||||
def has [label: string, spans: list<string>, value: string] {
|
||||
let values = (cands $spans)
|
||||
if $value in $values {
|
||||
print $" ok ($label)"
|
||||
0
|
||||
} else {
|
||||
print $" FAIL ($label) — '($value)' missing from: ($values | str join ' ')"
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
def hasnot [label: string, spans: list<string>, value: string] {
|
||||
if $value in (cands $spans) {
|
||||
print $" FAIL ($label) — '($value)' should be absent"
|
||||
1
|
||||
} else {
|
||||
print $" ok ($label)"
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
def check [label: string, ok: bool] {
|
||||
if $ok {
|
||||
print $" ok ($label)"
|
||||
0
|
||||
} else {
|
||||
print $" FAIL ($label)"
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
mut fails = 0
|
||||
|
||||
print "nu: :4 (NoFileComp) forwards candidates, offers no files"
|
||||
$fails += (has "candidate forwarded" [task ""] "build")
|
||||
$fails += (hasnot "no file fallback" [task ""] "notes.txt")
|
||||
|
||||
print "nu: filters candidates by the current word"
|
||||
$fails += (has "prefix keeps match" [task b] "build")
|
||||
$fails += (hasnot "prefix drops others" [task b] "deploy")
|
||||
|
||||
print "nu: :16 (FilterDirs) offers directories only"
|
||||
$fails += (has "dir offered" [task --dir ""] $"sub(char path_sep)")
|
||||
$fails += (hasnot "no plain file" [task --dir ""] "notes.txt")
|
||||
|
||||
print "nu: :8 (FilterFileExt) filters by extension"
|
||||
$fails += (has "matching file" [task --taskfile ""] "Taskfile.yml")
|
||||
$fails += (hasnot "non-matching file" [task --taskfile ""] "notes.txt")
|
||||
|
||||
print "nu: nested path completion keeps the directory prefix"
|
||||
$fails += (has "prefix kept" [task --taskfile $"sub(char path_sep)"] $"sub(char path_sep)nested.yml")
|
||||
|
||||
print "nu: inline --flag=path keeps the --flag= prefix"
|
||||
$fails += (has "inline nested" [task $"--taskfile=sub(char path_sep)"] $"--taskfile=sub(char path_sep)nested.yml")
|
||||
$fails += (hasnot "inline non-matching" [task "--taskfile="] "--taskfile=notes.txt")
|
||||
|
||||
print "nu: :2|:32 (NoSpace|KeepOrder) keep the order the engine emitted"
|
||||
let vars = (cands [task deploy ""])
|
||||
$fails += (has "required var offered" [task deploy ""] "ENV=dev")
|
||||
$fails += (check "declaration order kept" (($vars | enumerate | where item == "ENV=dev" | get 0.index) < ($vars | enumerate | where item == "REGION=" | get 0.index)))
|
||||
|
||||
print "nu: :0 (Default) returns null so Nushell completes files itself"
|
||||
$fails += (check "null returned" ((do $completer [task build "--" ""]) == null))
|
||||
|
||||
print "nu: other commands go to the previously installed completer"
|
||||
$fails += (has "delegated" [git status ""] "prev:git")
|
||||
|
||||
if $fails != 0 {
|
||||
print $"nu: ($fails) failure\(s\)"
|
||||
exit 1
|
||||
}
|
||||
print "nu: all passed"
|
||||
67
testdata/completion/wrapper.ps1
vendored
Normal file
67
testdata/completion/wrapper.ps1
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
# Smoke-tests how the PowerShell wrapper routes each directive, via the
|
||||
# completion API. Set up by run.sh: $env:TASK_FIXTURE, and `task` on PATH =
|
||||
# the binary under test.
|
||||
|
||||
Set-Location $env:TASK_FIXTURE
|
||||
. "$PSScriptRoot/../../completion/ps/task.ps1"
|
||||
|
||||
$fails = 0
|
||||
|
||||
function Cands($line) {
|
||||
([System.Management.Automation.CommandCompletion]::CompleteInput($line, $line.Length, $null)).CompletionMatches |
|
||||
ForEach-Object { $_.CompletionText }
|
||||
}
|
||||
|
||||
function Has($label, $line, $value) {
|
||||
if ((Cands $line) -contains $value) {
|
||||
Write-Output " ok $label"
|
||||
} else {
|
||||
Write-Output " FAIL $label — '$value' missing from: $((Cands $line) -join ' ')"
|
||||
$script:fails++
|
||||
}
|
||||
}
|
||||
|
||||
function HasNot($label, $line, $value) {
|
||||
if ((Cands $line) -contains $value) {
|
||||
Write-Output " FAIL $label — '$value' should be absent"
|
||||
$script:fails++
|
||||
} else {
|
||||
Write-Output " ok $label"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output "powershell: :4 (NoFileComp) forwards candidates, offers no files"
|
||||
Has "candidate forwarded" 'task ' 'build'
|
||||
HasNot "no file fallback" 'task ' 'notes.txt'
|
||||
|
||||
Write-Output "powershell: filters candidates by the current word"
|
||||
Has "prefix keeps match" 'task b' 'build'
|
||||
HasNot "prefix drops others" 'task b' 'deploy'
|
||||
|
||||
Write-Output "powershell: :16 (FilterDirs) offers directories only"
|
||||
Has "dir offered" 'task --dir ' 'sub'
|
||||
HasNot "no plain file" 'task --dir ' 'notes.txt'
|
||||
|
||||
Write-Output "powershell: :8 (FilterFileExt) filters by extension"
|
||||
Has "matching file" 'task --taskfile ' 'Taskfile.yml'
|
||||
HasNot "non-matching file" 'task --taskfile ' 'notes.txt'
|
||||
|
||||
Write-Output "powershell: nested path completion keeps the directory prefix"
|
||||
Has "prefix kept" 'task --taskfile sub/' 'sub/nested.yml'
|
||||
|
||||
Write-Output "powershell: inline --flag=path keeps the --flag= prefix"
|
||||
Has "inline nested" 'task --taskfile=sub/' '--taskfile=sub/nested.yml'
|
||||
HasNot "inline non-matching" 'task --taskfile=' '--taskfile=notes.txt'
|
||||
|
||||
Write-Output "powershell: a quoted argument reaches the engine unquoted"
|
||||
Has "single-quoted dir" "task --dir 'with space' " 'spaced'
|
||||
Has "double-quoted dir" 'task --dir "with space" ' 'spaced'
|
||||
|
||||
Write-Output "powershell: a candidate holding a space is quoted for insertion"
|
||||
Has "dir quoted" 'task --dir w' "'with space'"
|
||||
|
||||
if ($fails -ne 0) {
|
||||
Write-Output "powershell: $fails failure(s)"
|
||||
exit 1
|
||||
}
|
||||
Write-Output "powershell: all passed"
|
||||
88
testdata/completion/wrapper.zsh
vendored
Executable file
88
testdata/completion/wrapper.zsh
vendored
Executable file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env zsh
|
||||
# Smoke-tests how the zsh wrapper routes each directive, by stubbing _describe,
|
||||
# _files and _path_files. Requires TASK_BIN and TASK_FIXTURE.
|
||||
|
||||
export TASK_EXE=$TASK_BIN
|
||||
cd $TASK_FIXTURE
|
||||
|
||||
integer fails=0
|
||||
local CAP
|
||||
compdef() { } # no-op: we call _task directly, not through compinit
|
||||
|
||||
# Mirrors the real signature — `_describe [-12JVoOx] [-t tag] descr array
|
||||
# [compadd-opt ...]` — so an option landing in the wrong zone is visible: the
|
||||
# trailing zone goes to compadd, where -J and -V swallow the next argument.
|
||||
_describe() {
|
||||
local -a flags
|
||||
while [[ $1 == -* ]]; do
|
||||
case $1 in
|
||||
(-t) flags+=($1 $2); shift 2 ;;
|
||||
(*) flags+=($1); shift ;;
|
||||
esac
|
||||
done
|
||||
local arr=$2 # $1 is descr
|
||||
CAP+="describe_flags:[${flags[*]}]"$'\n'
|
||||
CAP+="compadd_opts:[${@[3,-1]}]"$'\n'
|
||||
local c; for c in ${(P)arr}; do CAP+="cand:$c"$'\n'; done
|
||||
}
|
||||
_files() { CAP+="files:$*"$'\n' }
|
||||
_path_files() { CAP+="path_files:$*"$'\n' }
|
||||
|
||||
# Sourcing avoids the autoload first-call quirk; `compdef` is stubbed above.
|
||||
source ${0:A:h}/../../completion/zsh/_task
|
||||
|
||||
run() {
|
||||
CAP=""
|
||||
local -a words=("$@")
|
||||
integer CURRENT=$#words
|
||||
local curcontext=":completion:complete:task:"
|
||||
_task
|
||||
}
|
||||
|
||||
has() { # LABEL PATTERN
|
||||
if [[ "$CAP" == *"$2"* ]]; then
|
||||
echo " ok $1"
|
||||
else
|
||||
echo " FAIL $1 — expected '$2' in:"$'\n'"$CAP"
|
||||
(( fails++ ))
|
||||
fi
|
||||
}
|
||||
hasnot() { # LABEL PATTERN
|
||||
if [[ "$CAP" == *"$2"* ]]; then
|
||||
echo " FAIL $1 — '$2' should be absent in:"$'\n'"$CAP"
|
||||
(( fails++ ))
|
||||
else
|
||||
echo " ok $1"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "zsh: :4 (NoFileComp) forwards candidates, no file fallback"
|
||||
run task ''
|
||||
has "candidate forwarded" "cand:build"
|
||||
hasnot "no file fallback" "files:"
|
||||
|
||||
# In the compadd zone, -V would take the next argument as a group name and
|
||||
# swallow _describe's own `-d`, offering its internal variables as candidates.
|
||||
echo "zsh: :2|:32 (NoSpace|KeepOrder) reach the right option zones"
|
||||
run task deploy ''
|
||||
has "KeepOrder -> _describe -V" "describe_flags:[-V"
|
||||
has "NoSpace -> compadd -S" "compadd_opts:[-S ]"
|
||||
|
||||
echo "zsh: :8 (FilterFileExt) routes to extension-filtered files"
|
||||
run task --taskfile ''
|
||||
has "files glob" "files:"
|
||||
has "yml in glob" "yml"
|
||||
|
||||
echo "zsh: :16 (FilterDirs) routes to directory completion"
|
||||
run task --dir ''
|
||||
has "path_files -/" "path_files:-/"
|
||||
|
||||
echo "zsh: :0 (Default) falls back to files"
|
||||
run task build -- ''
|
||||
has "files default" "files:"
|
||||
|
||||
if (( fails )); then
|
||||
echo "zsh: $fails failure(s)"
|
||||
exit 1
|
||||
fi
|
||||
echo "zsh: all passed"
|
||||
Reference in New Issue
Block a user