#!/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 (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 (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 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" "" "$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 \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"