Files
2026-10-24 09:53:48 -03:00

188 lines
11 KiB
Bash

#!/usr/bin/env bash
# End-to-end test of the breeze CLI: drives the built program through every command group against a
# throw-away home directory, SDK source, tool and project. Needs cmake, a compiler and git; no network.
#
# tests/e2e.sh [path/to/breeze] (default: out/build/linux-x64-gcc/bin/breeze)
#
# Exits with the number of failed checks.
set -u
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
B="$(cd "$(dirname "${1:-$HERE/out/build/linux-x64-gcc/bin/breeze}")" && pwd)/$(basename "${1:-$HERE/out/build/linux-x64-gcc/bin/breeze}")"
[ -x "$B" ] || { echo "no executable at $B (build first, or pass the path)"; exit 2; }
WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
export BREEZE_HOME="$WORK/home"
unset BREEZE_NO_FORWARD BREEZE_EDITOR BREEZE_SDK_SOURCE BREEZE_CLI_SOURCE
passed=0
failed=0
report() { # ok|FAIL description [detail]
if [ "$1" = ok ]; then passed=$((passed + 1)); printf 'ok %s\n' "$2"; else failed=$((failed + 1)); printf 'FAIL %s\n' "$2"; [ -n "${3:-}" ] && printf ' %s\n' "$3"; fi
}
# expect <description> <substring that must be in the output> <command...> (the command must succeed)
expect() {
local d="$1" want="$2"; shift 2
local out; out="$("$@" 2>&1)"; local rc=$?
if [ $rc -eq 0 ] && [[ "$out" == *"$want"* ]]; then report ok "$d"; else report FAIL "$d" "rc=$rc, wanted '$want', got: $(echo "$out" | head -3 | tr '\n' '|')"; fi
}
# refuse <description> <substring> <command...> (the command must fail, saying that)
refuse() {
local d="$1" want="$2"; shift 2
local out; out="$("$@" 2>&1)"; local rc=$?
if [ $rc -ne 0 ] && [[ "$out" == *"$want"* ]]; then report ok "$d"; else report FAIL "$d" "rc=$rc, wanted '$want', got: $(echo "$out" | head -3 | tr '\n' '|')"; fi
}
check() { # check <description> <test command...>
local d="$1"; shift
if "$@" >/dev/null 2>&1; then report ok "$d"; else report FAIL "$d"; fi
}
# ------------------------------------------------------------------------------ an SDK source
# Two versions of the template: this project's own SDK and settings, without its modules.
mkdir -p "$WORK/sdks"
for v in 1.0.0 1.0.1; do
d="$WORK/sdks/$v"; mkdir -p "$d"
cp -a "$HERE/breeze" "$HERE/project" "$HERE/CMakeLists.txt" "$HERE/CMakePresets.json" "$d/"
for f in .clang-format .clang-tidy .editorconfig .gitattributes .gitignore; do cp "$HERE/$f" "$d/" 2>/dev/null; done
echo "$v" > "$d/breeze/VERSION"
done
printf '\n# New in 1.0.1.\nset(PROJECT_E2E_SETTING "added")\n' >> "$WORK/sdks/1.0.1/project/Configuration.cmake"
echo "# changed" >> "$WORK/sdks/1.0.1/.clang-format"
cd "$WORK" || exit 2
echo "== cli =="
expect "cli version prints the version" "0." "$B" cli version
expect "cli ls with nothing installed says so" "no versions installed" "$B" cli ls
refuse "no SDK source configured is explained" "breeze config sdk-source" "$B" project new Nope
expect "config sdk-source is stored" "$WORK/sdks" "$B" config sdk-source --set "$WORK/sdks"
expect "config editor round trip" "code --wait" bash -c "'$B' config editor --set 'code --wait' >/dev/null && '$B' config editor"
echo "== project new =="
expect "project new creates the project" "created E2eProj with SDK 1.0.0" "$B" project new E2eProj --sdk 1.0.0
refuse "project new refuses a bad name" "not a valid project name" "$B" project new "bad name"
refuse "project new refuses an existing directory" "already exists" "$B" project new E2eProj
refuse "project new refuses an unknown SDK" "no SDK 9.9.9" "$B" project new Other --sdk 9.9.9
cd E2eProj || exit 2
expect "sdk version" "1.0.0" "$B" sdk version
expect "project name" "E2eProj" "$B" project name
expect "project id defaults to the lower-case name" "e2eproj" "$B" project id
check "the demo code is gone, one module is there" bash -c "[ \"\$('$B' ls | wc -l)\" = 2 ]"
echo "== project settings =="
expect "project version --set" "0.1.0 -> 3.2.1" "$B" project version --set 3.2.1
expect "project version reads it back" "3.2.1" "$B" project version
refuse "project version --set rejects junk" "not a valid project version" "$B" project version --set 1.x
expect "project id --set" "-> e2e_id" "$B" project id --set e2e_id
echo "== modules =="
expect "add lib --static" "added lib module 'strings_lib'" "$B" add lib Strings --static
expect "add obj" "added obj module 'pieces_obj'" "$B" add obj Pieces
expect "add exec with deps" "added exec module 'tool_exe'" "$B" add exec Tool --deps strings_lib pieces_obj
expect "add test (suffix not doubled)" "'strings_test'" "$B" add test StringsTest --deps strings_lib
expect "add example" "'demo_example'" "$B" add example Demo --deps strings_lib
refuse "add lib needs --static or --shared" "--static or --shared" "$B" add lib Broken
refuse "add refuses a duplicate id" "already taken" "$B" add exec Other --id tool_exe
refuse "add refuses a dependency on an executable" "only libraries and objects" "$B" add exec Other2 --deps tool_exe
expect "ls lists them all" "pieces_obj" "$B" ls
expect "ls --obj filters" "pieces_obj" "$B" ls --obj
expect "ls --lib --static filters" "strings_lib" "$B" ls --lib --static
expect "ls --filter" "tool_exe" "$B" ls --filter tool
expect "deps of" "pieces_obj" "$B" deps of tool_exe
expect "deps on, recursive" "demo_example" "$B" deps on strings_lib --recursive
refuse "delete refuses while depended on" "depend" "$B" delete strings_lib --yes
expect "rename id and directory" "id strings_lib -> text_lib" "$B" rename strings_lib --id text_lib --name Text
expect "rename updated the dependents" "text_lib" "$B" deps of tool_exe
check "rename moved the directory" test -d source/Text
echo "== build, run, defaults, clean =="
expect "build" "Done" "$B" build --cargs -DBUILD_TESTS=ON -DBUILD_EXAMPLES=ON
expect "run an executable by id with arguments" "Hello from Tool" "$B" run tool_exe -x one two
expect "run by directory name" "Hello from Tool" "$B" run Tool
expect "run a test" "Running" "$B" run strings_test
refuse "run without an id and several executables" "which executable" "$B" run
expect "defaults run --set" "default run = tool_exe" "$B" defaults run --set tool_exe
expect "run uses the default" "Hello from Tool" "$B" run
expect "defaults build mode --set" "Debug" "$B" defaults build mode --set debug
expect "defaults build mode reads back" "Debug" "$B" defaults build mode
expect "defaults file is in .breeze" "tool_exe" cat .breeze/defaults.json
expect "build --no-configure builds the configured directory" "ninja" bash -c "'$B' build --no-configure --bargs --verbose 2>&1 | head -50 | grep -m1 -o ninja || '$B' build --no-configure 2>&1"
refuse "build rejects a mode it does not know" "not a build mode" "$B" build -M fancy
refuse "build rejects a preset that doesn't exist" "no preset" "$B" build -P linux -A x64 -C nonexistent
expect "defaults build mode --set none" "<not set>" "$B" defaults build mode --set none
expect "clean removes the build directory" "removed out/build" "$B" clean
check "clean really did" test ! -d out/build
echo "== sdk =="
expect "sdk update sees the newer version" "update available: 1.0.1" "$B" sdk update
expect "sdk upgrade" "SDK 1.0.0 -> 1.0.1" "$B" sdk upgrade
expect "sdk version after" "1.0.1" "$B" sdk version
check "the new setting was added to project/Configuration.cmake" grep -q PROJECT_E2E_SETTING project/Configuration.cmake
check "the old breeze/ was kept aside" test -d .breeze/backup/breeze-1.0.0
check "the scripts stayed executable" test -x breeze/scripts/build.sh
expect "sdk upgrade again is a no-op" "newest of its major version" "$B" sdk upgrade
expect "sdk migrate back" "1.0.1 -> 1.0.0" "$B" sdk migrate 1.0.0
refuse "sdk migrate to an unknown version" "no SDK 7.0.0" "$B" sdk migrate 7.0.0
expect "the project still builds after moving SDKs" "Done" "$B" build
echo "== tools =="
mkdir -p "$WORK/tool/bin"
printf '{ "id": "echo", "version": "1.0.0", "description": "Echoes", "entry": "bin/echo.sh" }\n' > "$WORK/tool/breeze-tool.json"
printf '#!/bin/sh\necho "echo-tool [$*] in $(basename "$PWD") project=$(basename "$BREEZE_PROJECT_ROOT")"\nexit ${ECHO_EXIT:-0}\n' > "$WORK/tool/bin/echo.sh"
expect "tool install from a directory" "installed echo 1.0.0" "$B" tool install "$WORK/tool"
expect "tool ls" "Echoes" "$B" tool ls
expect "tool run passes arguments, cwd and environment" "echo-tool [say hi --flag] in E2eProj project=E2eProj" "$B" tool echo say hi --flag
expect "tool --help goes to the tool" "echo-tool [--help]" "$B" tool echo --help
check "tool exit code propagates" bash -c "ECHO_EXIT=7 '$B' tool echo >/dev/null 2>&1; [ \$? = 7 ]"
refuse "tool install twice" "already installed" "$B" tool install "$WORK/tool"
refuse "unknown tool" "no tool 'ghost'" "$B" tool ghost
expect "tool uninstall" "uninstalled echo" "$B" tool uninstall echo
check "tools.json is gone with the last tool" test ! -e .breeze/tools.json
echo "== cli versions =="
cd "$WORK" || exit 2
V="$("$B" cli version)"
expect "cli install (this program)" "installed $V (picked)" "$B" cli install "$V"
mkdir -p "$WORK/releases/9.0.0"
printf '#!/bin/sh\nif [ "$1" = cli ] && [ "$2" = version ]; then echo 9.0.0; else echo "ninety: $*"; fi\n' > "$WORK/releases/9.0.0/breeze-linux-x64"
"$B" config cli-source --set "$WORK/releases" >/dev/null
expect "cli install from the CLI source" "installed 9.0.0" "$B" cli install 9.0.0
expect "cli ls marks the picked version" "* $V" "$B" cli ls
expect "cli pick" "picked 9.0.0" "$B" cli pick 9.0.0
expect "commands now run in the picked version" "ninety: ls" "$B" ls
expect "cli commands never forward" "$V" "$B" cli version
expect "the copy in bin/ forwards too" "ninety: build" "$BREEZE_HOME/bin/breeze" build
expect "cli pick back" "picked $V" "$B" cli pick "$V"
expect "cli uninstall" "uninstalled 9.0.0" "$B" cli uninstall 9.0.0
refuse "cli pick of what is not installed" "not installed" "$B" cli pick 9.0.0
refuse "cli install of what the source lacks" "is not in the CLI source" "$B" cli install 8.0.0
echo "== legacy layout migration =="
L="$WORK/Legacy"; mkdir -p "$L/cmake" "$L/scripts" "$L/source/App"
printf 'cmake_minimum_required(VERSION 3.25)\nproject(Legacy VERSION 0.9.0 DESCRIPTION "An old project" LANGUAGES CXX)\nset(CMAKE_CXX_STANDARD 17)\n' > "$L/CMakeLists.txt"
printf 'set(PROJECT_CODE_NAME "legacy" CACHE STRING "id")\n' > "$L/Configuration.cmake"
printf '# the old template\n' > "$L/cmake/BuildHelpers.cmake"
printf '#!/bin/sh\n' > "$L/scripts/build.sh"
printf 'add_exe_module(app_exe)\n' > "$L/source/App/CMakeLists.txt"
printf '#include <iostream>\nint main() { std::cout << "legacy app\\n"; }\n' > "$L/source/App/main.cpp"
printf '# Legacy\nMy own readme.\n' > "$L/README.md"
cd "$L" || exit 2
expect "a legacy project is recognised" "legacy (flat layout, unversioned)" "$B" sdk version
expect "its name and version are read from project()" "Legacy" "$B" project name
refuse "upgrade of a legacy project points at migrate" "sdk migrate" "$B" sdk upgrade
expect "migrate moves it to breeze/" "legacy layout -> SDK 1.0.1" "$B" sdk migrate latest
check "breeze/ and project/ exist" test -f breeze/cmake/Breeze.cmake -a -f project/Configuration.cmake
check "the old template went to the backup" test -f .breeze/backup/legacy-to-1.0.1/Configuration.cmake
check "the project's README stayed" grep -q "My own readme" README.md
expect "name carried over" "Legacy" "$B" project name
expect "version carried over" "0.9.0" "$B" project version
expect "id carried over" "legacy" "$B" project id
check "C++ standard carried over" grep -q 'PROJECT_CXX_STANDARD *17' project/Configuration.cmake
expect "the migrated project builds" "Done" "$B" build
expect "and runs" "legacy app" "$B" run app_exe
echo
echo "$passed passed, $failed failed"
exit "$failed"