Files
task/completion/tests/engine.sh
Valentin Maerten 7c90ee35f8 test(completion): add cross-shell completion test suite and CI job
Add completion/tests/ harnesses that exercise the engine protocol and every
shell wrapper without a TTY: the engine via `task __complete`, bash/zsh by
stubbing the completion-system helpers, fish via `complete -C`, and PowerShell
via the completion API. A `test:completion` task and a matching CI job (with a
strict mode that fails when an expected shell is missing) run them all.

Writing the suite surfaced and fixed real wrapper bugs:
- fish: `math` has no bitwise `&`, so every directive check errored; test bits
  with integer division + modulo instead. Also filter FilterFileExt results
  ourselves, as __fish_complete_suffix only prioritizes the extension.
- powershell: `Get-ChildItem -Include` is ignored without -Recurse, so file
  extension filtering returned nothing; filter with Where-Object instead.
- bash: guard empty-array expansion so completion with zero candidates does not
  trip `set -u` on bash 3.2.

Also name the completion directive bits (NO_SPACE, FILTER_FILE_EXT, …) in every
wrapper instead of using raw numbers.
2026-07-03 22:29:42 +02:00

86 lines
2.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# Tests the `task __complete` protocol directly (shell-agnostic). This is the
# backbone: it validates the candidates and directive the engine emits, which
# is what drives every shell wrapper.
#
# Requires: TASK_BIN (path to the task binary), TASK_FIXTURE (dir with a
# Taskfile.yml). Exits non-zero on the first failure.
set -u
: "${TASK_BIN:?TASK_BIN must point to the task binary}"
: "${TASK_FIXTURE:?TASK_FIXTURE must point to the fixture directory}"
cd "$TASK_FIXTURE" || exit 1
fails=0
out() { "$TASK_BIN" __complete "$@" 2>/dev/null; }
vals() { out "$@" | sed '$d' | cut -f1; } # candidate values, sans the :N line
dirv() { out "$@" | tail -1; } # the :N directive line
has() { # LABEL VALUE ARGS...
local label=$1 value=$2; shift 2
if vals "$@" | grep -qxF -- "$value"; then
echo " ok $label"
else
echo " FAIL $label — expected value '$value' among: $(vals "$@" | tr '\n' ' ')"
fails=$((fails + 1))
fi
}
hasnot() { # LABEL VALUE ARGS...
local label=$1 value=$2; shift 2
if vals "$@" | grep -qxF -- "$value"; then
echo " FAIL $label — value '$value' should be absent"
fails=$((fails + 1))
else
echo " ok $label"
fi
}
directive() { # LABEL EXPECTED ARGS...
local label=$1 expected=$2; shift 2
local got; got=$(dirv "$@")
if [[ "$got" == "$expected" ]]; then
echo " ok $label"
else
echo " FAIL $label — expected directive '$expected', got '$got'"
fails=$((fails + 1))
fi
}
echo "engine: task names"
has "lists tasks" build ''
has "lists aliases" dep ''
directive "NoFileComp" ':4' ''
echo "engine: completion-control flags"
hasnot "--no-aliases drops aliases" dep --no-aliases ''
has "--no-aliases keeps tasks" deploy --no-aliases ''
echo "engine: flags"
has "lists flags" --taskfile -
directive "flags NoFileComp" ':4' -
echo "engine: flag values"
has "inline --output= is full form" --output=interleaved --output=
directive "inline NoFileComp" ':4' --output=
has "separate --output is bare" interleaved --output ''
has "--sort values" alphanumeric --sort ''
echo "engine: file/dir directives"
has "--taskfile emits yml" yml --taskfile ''
has "--taskfile emits yaml" yaml --taskfile ''
directive "--taskfile FilterFileExt" ':8' --taskfile ''
directive "--dir FilterDirs" ':16' --dir ''
echo "engine: task variables"
has "required var with enum" ENV=dev deploy ''
has "required var without enum" REGION= deploy ''
directive "vars NoSpace|NoFileComp|KeepOrder" ':38' deploy ''
echo "engine: after --"
directive "after -- is default" ':0' deploy -- ''
if (( fails )); then
echo "engine: $fails failure(s)"
exit 1
fi
echo "engine: all passed"