From 75a8fcabafe3e49cae3317348d7ef6f269a98202 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 15 Jun 2024 10:32:34 +0200 Subject: [PATCH 01/28] Use bashunit for e2e tests --- .github/workflows/e2e-tests.yml | 43 +++++++++++++++++++++++++-------- e2e/result-cache-1/test.sh | 12 +++++++++ 2 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 e2e/result-cache-1/test.sh diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 2a82225b77..414b8c8146 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -64,16 +64,6 @@ jobs: fail-fast: false matrix: include: - - script: | - cd e2e/result-cache-1 - echo -n > phpstan-baseline.neon - ../../bin/phpstan -vvv - patch -b src/Bar.php < patch-1.patch - cat baseline-1.neon > phpstan-baseline.neon - ../../bin/phpstan -vvv - mv src/Bar.php.orig src/Bar.php - echo -n > phpstan-baseline.neon - ../../bin/phpstan -vvv - script: | cd e2e/result-cache-2 echo -n > phpstan-baseline.neon @@ -285,3 +275,36 @@ jobs: - name: "Test" run: ${{ matrix.script }} + + result-cache-e2e-tests-bashunit: + name: "Result cache E2E tests" + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + include: + - script: | + cd e2e/result-cache-1 + bashunit . + + steps: + - name: "Checkout" + uses: actions/checkout@v4 + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + php-version: "8.1" + extensions: mbstring + ini-values: memory_limit=256M + + - name: "Install dependencies" + run: "composer install --no-interaction --no-progress" + + - name: "Patch PHPStan" + run: "patch src/Analyser/Error.php < e2e/PHPStanErrorPatch.patch" + + - name: "Test" + run: "${{ matrix.script }}" diff --git a/e2e/result-cache-1/test.sh b/e2e/result-cache-1/test.sh new file mode 100644 index 0000000000..6d3ff48166 --- /dev/null +++ b/e2e/result-cache-1/test.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +function test_result_cache1() { + echo -n > phpstan-baseline.neon + ../../bin/phpstan -vvv + patch -b src/Bar.php < patch-1.patch + cat baseline-1.neon > phpstan-baseline.neon + ../../bin/phpstan -vvv + mv src/Bar.php.orig src/Bar.php + echo -n > phpstan-baseline.neon + ../../bin/phpstan -vvv +} 2>&1 From 2c588d71f1663a6f124a23b5123bf987e321b338 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 15 Jun 2024 10:42:44 +0200 Subject: [PATCH 02/28] fix --- .github/workflows/e2e-tests.yml | 2 +- e2e/bashunit | 1797 +++++++++++++++++++++++++++++++ 2 files changed, 1798 insertions(+), 1 deletion(-) create mode 100755 e2e/bashunit diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 414b8c8146..5bc3e17645 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -286,7 +286,7 @@ jobs: include: - script: | cd e2e/result-cache-1 - bashunit . + ../bashunit . steps: - name: "Checkout" diff --git a/e2e/bashunit b/e2e/bashunit new file mode 100755 index 0000000000..77cb85f5ff --- /dev/null +++ b/e2e/bashunit @@ -0,0 +1,1797 @@ +#!/usr/bin/env bash +#!/bin/bash + +function fail() { + local message=$1 + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + state::add_assertions_failed + console_results::print_failure_message "${label}" "$message" +} + +function assert_equals() { + local expected="$1" + local actual="$2" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ "$expected" != "$actual" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${expected}" "but got" "${actual}" + return + fi + + state::add_assertions_passed +} + +function assert_equals_ignore_colors() { + local expected="$1" + local actual="$2" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + local actual_without_colors + actual_without_colors=$(echo -e "$actual" | sed "s/\x1B\[[0-9;]*[JKmsu]//g") + + assert_equals "$expected" "$actual_without_colors" "$label" +} + +function assert_empty() { + local expected="$1" + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ "$expected" != "" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "to be empty" "but got" "${expected}" + return + fi + + state::add_assertions_passed +} + +function assert_not_empty() { + local expected="$1" + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ "$expected" == "" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "to not be empty" "but got" "${expected}" + return + fi + + state::add_assertions_passed +} + +function assert_not_equals() { + local expected="$1" + local actual="$2" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ "$expected" == "$actual" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${expected}" "but got" "${actual}" + return + fi + + state::add_assertions_passed +} + +function assert_contains() { + local expected="$1" + local actual="$2" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if ! [[ $actual == *"$expected"* ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual}" "to contain" "${expected}" + return + fi + + state::add_assertions_passed +} + +function assert_contains_ignore_case() { + local expected="$1" + local actual="$2" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + shopt -s nocasematch + + if ! [[ $actual =~ $expected ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual}" "to contain" "${expected}" + + shopt -u nocasematch + return + fi + + shopt -u nocasematch + state::add_assertions_passed +} + +function assert_not_contains() { + local expected="$1" + local actual="$2" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ $actual == *"$expected"* ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual}" "to not contain" "${expected}" + return + fi + + state::add_assertions_passed +} + +function assert_matches() { + local expected="$1" + local actual="$2" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if ! [[ $actual =~ $expected ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual}" "to match" "${expected}" + return + fi + + state::add_assertions_passed +} + +function assert_not_matches() { + local expected="$1" + local actual="$2" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ $actual =~ $expected ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual}" "to not match" "${expected}" + return + fi + + state::add_assertions_passed +} + +function assert_exit_code() { + local actual_exit_code=${3-"$?"} + local expected_exit_code="$1" + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ "$actual_exit_code" -ne "$expected_exit_code" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual_exit_code}" "to be" "${expected_exit_code}" + return + fi + + state::add_assertions_passed +} + +function assert_successful_code() { + local actual_exit_code=${3-"$?"} + local expected_exit_code=0 + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ "$actual_exit_code" -ne "$expected_exit_code" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual_exit_code}" "to be exactly" "${expected_exit_code}" + return + fi + + state::add_assertions_passed +} + +function assert_general_error() { + local actual_exit_code=${3-"$?"} + local expected_exit_code=1 + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ $actual_exit_code -ne "$expected_exit_code" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual_exit_code}" "to be exactly" "${expected_exit_code}" + return + fi + + state::add_assertions_passed +} + +function assert_command_not_found() { + local actual_exit_code=${3-"$?"} + local expected_exit_code=127 + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ $actual_exit_code -ne "$expected_exit_code" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual_exit_code}" "to be exactly" "${expected_exit_code}" + return + fi + + state::add_assertions_passed +} + +function assert_string_starts_with() { + local expected="$1" + local actual="$2" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if ! [[ $actual =~ ^"$expected"* ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual}" "to start with" "${expected}" + return + fi + + state::add_assertions_passed +} + +function assert_string_not_starts_with() { + local expected="$1" + local actual="$2" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ $actual =~ ^"$expected"* ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual}" "to not start with" "${expected}" + return + fi + + state::add_assertions_passed +} + +function assert_string_ends_with() { + local expected="$1" + local actual="$2" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if ! [[ $actual =~ .*"$expected"$ ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual}" "to end with" "${expected}" + return + fi + + state::add_assertions_passed +} + +function assert_string_not_ends_with() { + local expected="$1" + local actual="$2" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ $actual =~ .*"$expected"$ ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual}" "to not end with" "${expected}" + return + fi + + state::add_assertions_passed +} + +function assert_less_than() { + local expected="$1" + local actual="$2" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if ! [[ "$actual" -lt "$expected" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual}" "to be less than" "${expected}" + return + fi + + state::add_assertions_passed +} + +function assert_less_or_equal_than() { + local expected="$1" + local actual="$2" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if ! [[ "$actual" -le "$expected" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual}" "to be less or equal than" "${expected}" + return + fi + + state::add_assertions_passed +} + +function assert_greater_than() { + local expected="$1" + local actual="$2" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if ! [[ "$actual" -gt "$expected" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual}" "to be greater than" "${expected}" + return + fi + + state::add_assertions_passed +} + +function assert_greater_or_equal_than() { + local expected="$1" + local actual="$2" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if ! [[ "$actual" -ge "$expected" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual}" "to be greater or equal than" "${expected}" + return + fi + + state::add_assertions_passed +} +#!/bin/bash + +function assert_array_contains() { + local expected="$1" + local label + label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")" + shift + + local actual=("${@}") + + if ! [[ "${actual[*]}" == *"$expected"* ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual[*]}" "to contain" "${expected}" + return + fi + + state::add_assertions_passed +} + +function assert_array_not_contains() { + local expected="$1" + label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")" + shift + local actual=("$@") + + if [[ "${actual[*]}" == *"$expected"* ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${actual[*]}" "to not contain" "${expected}" + return + fi + + state::add_assertions_passed +} +#!/bin/bash + +function assert_file_exists() { + local expected="$1" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ ! -f "$expected" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${expected}" "to exist but" "do not exist" + return + fi + + state::add_assertions_passed +} + +function assert_file_not_exists() { + local expected="$1" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ -f "$expected" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${expected}" "to not exist but" "the file exists" + return + fi + + state::add_assertions_passed +} + +function assert_is_file() { + local expected="$1" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ ! -f "$expected" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${expected}" "to be a file" "but is not a file" + return + fi + + state::add_assertions_passed +} + +function assert_is_file_empty() { + local expected="$1" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ -s "$expected" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${expected}" "to be empty" "but is not empty" + return + fi + + state::add_assertions_passed +} +#!/bin/bash + +function assert_directory_exists() { + local expected="$1" + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ ! -d "$expected" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${expected}" "to exist but" "do not exist" + return + fi + + state::add_assertions_passed +} + +function assert_directory_not_exists() { + local expected="$1" + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ -d "$expected" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${expected}" "to not exist but" "the directory exists" + return + fi + + state::add_assertions_passed +} + +function assert_is_directory() { + local expected="$1" + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ ! -d "$expected" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${expected}" "to be a directory" "but is not a directory" + return + fi + + state::add_assertions_passed +} + +function assert_is_directory_empty() { + local expected="$1" + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ ! -d "$expected" || -n "$(ls -A "$expected")" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${expected}" "to be empty" "but is not empty" + return + fi + + state::add_assertions_passed +} + +function assert_is_directory_not_empty() { + local expected="$1" + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ ! -d "$expected" || -z "$(ls -A "$expected")" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${expected}" "to not be empty" "but is empty" + return + fi + + state::add_assertions_passed +} + +function assert_is_directory_readable() { + local expected="$1" + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ ! -d "$expected" || ! -r "$expected" || ! -x "$expected" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${expected}" "to be readable" "but is not readable" + return + fi + + state::add_assertions_passed +} + +function assert_is_directory_not_readable() { + local expected="$1" + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ ! -d "$expected" ]] || [[ -r "$expected" && -x "$expected" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${expected}" "to be not readable" "but is readable" + return + fi + + state::add_assertions_passed +} + +function assert_is_directory_writable() { + local expected="$1" + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ ! -d "$expected" || ! -w "$expected" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${expected}" "to be writable" "but is not writable" + return + fi + + state::add_assertions_passed +} + +function assert_is_directory_not_writable() { + local expected="$1" + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ ! -d "$expected" || -w "$expected" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${expected}" "to be not writable" "but is writable" + return + fi + + state::add_assertions_passed +} +#!/bin/bash + +function assert_match_snapshot() { + local actual + actual=$(echo -n "$1" | tr -d '\r') + local directory + directory="./$(dirname "${BASH_SOURCE[1]}")/snapshots" + local test_file + test_file="$(helper::normalize_variable_name "$(basename "${BASH_SOURCE[1]}")")" + local snapshot_name + snapshot_name="$(helper::normalize_variable_name "${FUNCNAME[1]}").snapshot" + local snapshot_file + snapshot_file="${directory}/${test_file}.${snapshot_name}" + + if [[ ! -f "$snapshot_file" ]]; then + mkdir -p "$directory" + echo "$actual" > "$snapshot_file" + + state::add_assertions_snapshot + return + fi + + local snapshot + snapshot=$(tr -d '\r' < "$snapshot_file") + + if [[ "$actual" != "$snapshot" ]]; then + local label + label=$(helper::normalize_test_function_name "${FUNCNAME[1]}") + + state::add_assertions_failed + console_results::print_failed_snapshot_test "$label" "$snapshot_file" + + return + fi + + state::add_assertions_passed +} + +#!/bin/bash + +#!/bin/bash + +# shellcheck disable=SC2034 +_OS="Unknown" + +if [[ "$(uname)" == "Linux" ]]; then + _OS="Linux" +elif [[ "$(uname)" == "Darwin" ]]; then + _OS="OSX" +elif [[ $(uname) == *"MINGW"* ]]; then + _OS="Windows" +fi +#!/bin/bash + +# Pass in any number of ANSI SGR codes. +# +# Code reference: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters +# Credit: +# https://superuser.com/a/1119396 +sgr() { + local codes=${1:-0} + shift + + for c in "$@"; do + codes="$codes;$c" + done + + echo $'\e'"[${codes}m" +} + +_COLOR_DEFAULT="$(sgr 0)" +_COLOR_BOLD="$(sgr 1)" +_COLOR_FAINT="$(sgr 2)" +_COLOR_BLACK="$(sgr 30)" +_COLOR_FAILED="$(sgr 31)" +_COLOR_PASSED="$(sgr 32)" +_COLOR_SKIPPED="$(sgr 33)" +_COLOR_INCOMPLETE="$(sgr 36)" +_COLOR_SNAPSHOT="$(sgr 34)" +_COLOR_RETURN_ERROR="$(sgr 41)$_COLOR_BLACK$_COLOR_BOLD" +_COLOR_RETURN_SUCCESS="$(sgr 42)$_COLOR_BLACK$_COLOR_BOLD" +_COLOR_RETURN_SKIPPED="$(sgr 43)$_COLOR_BLACK$_COLOR_BOLD" +_COLOR_RETURN_INCOMPLETE="$(sgr 46)$_COLOR_BLACK$_COLOR_BOLD" +_COLOR_RETURN_SNAPSHOT="$(sgr 44)$_COLOR_BLACK$_COLOR_BOLD" +#!/bin/bash + +function console_header::print_version() { + if [[ $HEADER_ASCII_ART == true ]]; then + cat < + Filters the tests to run based on the test name. + + -s|simple || -v|verbose + Enables simplified or verbose output to the console. + + -S|--stop-on-failure + Force to stop the runner right after encountering one failing test. + + -e|--env + Load a custom env file overriding the .env environment variables. + + --version + Displays the current version of bashunit. + + --upgrade + Upgrade to latest version of bashunit. + + --help + This message. + +See more: https://bashunit.typeddevs.com/command-line +EOF +} +#!/bin/bash + +_START_TIME=$(date +%s%N); +_SUCCESSFUL_TEST_COUNT=0 + +function console_results::render_result() { + if [[ "$(state::is_duplicated_test_functions_found)" == true ]]; then + console_results::print_execution_time + printf "%s%s%s\n" "${_COLOR_RETURN_ERROR}" "Duplicate test functions found" "${_COLOR_DEFAULT}" + printf "File with duplicate functions: %s\n" "$(state::get_file_with_duplicated_function_names)" + printf "Duplicate functions: %s\n" "$(state::get_duplicated_function_names)" + exit 1 + fi + + echo "" + + local total_tests=0 + ((total_tests+=$(state::get_tests_passed))) + ((total_tests+=$(state::get_tests_skipped))) + ((total_tests+=$(state::get_tests_incomplete))) + ((total_tests+=$(state::get_tests_snapshot))) + ((total_tests+=$(state::get_tests_failed))) + + local total_assertions=0 + ((total_assertions+=$(state::get_assertions_passed))) + ((total_assertions+=$(state::get_assertions_skipped))) + ((total_assertions+=$(state::get_assertions_incomplete))) + ((total_assertions+=$(state::get_assertions_snapshot))) + ((total_assertions+=$(state::get_assertions_failed))) + + printf "%sTests: %s" "$_COLOR_FAINT" "$_COLOR_DEFAULT" + if [[ "$(state::get_tests_passed)" -gt 0 ]] || [[ "$(state::get_assertions_passed)" -gt 0 ]]; then + printf " %s%s passed%s," "$_COLOR_PASSED" "$(state::get_tests_passed)" "$_COLOR_DEFAULT" + fi + if [[ "$(state::get_tests_skipped)" -gt 0 ]] || [[ "$(state::get_assertions_skipped)" -gt 0 ]]; then + printf " %s%s skipped%s," "$_COLOR_SKIPPED" "$(state::get_tests_skipped)" "$_COLOR_DEFAULT" + fi + if [[ "$(state::get_tests_incomplete)" -gt 0 ]] || [[ "$(state::get_assertions_incomplete)" -gt 0 ]]; then + printf " %s%s incomplete%s," "$_COLOR_INCOMPLETE" "$(state::get_tests_incomplete)" "$_COLOR_DEFAULT" + fi + if [[ "$(state::get_tests_snapshot)" -gt 0 ]] || [[ "$(state::get_assertions_snapshot)" -gt 0 ]]; then + printf " %s%s snapshot%s," "$_COLOR_SNAPSHOT" "$(state::get_tests_snapshot)" "$_COLOR_DEFAULT" + fi + if [[ "$(state::get_tests_failed)" -gt 0 ]] || [[ "$(state::get_assertions_failed)" -gt 0 ]]; then + printf " %s%s failed%s," "$_COLOR_FAILED" "$(state::get_tests_failed)" "$_COLOR_DEFAULT" + fi + printf " %s total\n" "$total_tests" + + printf "%sAssertions:%s" "$_COLOR_FAINT" "$_COLOR_DEFAULT" + if [[ "$(state::get_tests_passed)" -gt 0 ]] || [[ "$(state::get_assertions_passed)" -gt 0 ]]; then + printf " %s%s passed%s," "$_COLOR_PASSED" "$(state::get_assertions_passed)" "$_COLOR_DEFAULT" + fi + if [[ "$(state::get_tests_skipped)" -gt 0 ]] || [[ "$(state::get_assertions_skipped)" -gt 0 ]]; then + printf " %s%s skipped%s," "$_COLOR_SKIPPED" "$(state::get_assertions_skipped)" "$_COLOR_DEFAULT" + fi + if [[ "$(state::get_tests_incomplete)" -gt 0 ]] || [[ "$(state::get_assertions_incomplete)" -gt 0 ]]; then + printf " %s%s incomplete%s," "$_COLOR_INCOMPLETE" "$(state::get_assertions_incomplete)" "$_COLOR_DEFAULT" + fi + if [[ "$(state::get_tests_snapshot)" -gt 0 ]] || [[ "$(state::get_assertions_snapshot)" -gt 0 ]]; then + printf " %s%s snapshot%s," "$_COLOR_SNAPSHOT" "$(state::get_assertions_snapshot)" "$_COLOR_DEFAULT" + fi + if [[ "$(state::get_tests_failed)" -gt 0 ]] || [[ "$(state::get_assertions_failed)" -gt 0 ]]; then + printf " %s%s failed%s," "$_COLOR_FAILED" "$(state::get_assertions_failed)" "$_COLOR_DEFAULT" + fi + printf " %s total\n" "$total_assertions" + + if [[ "$(state::get_tests_failed)" -gt 0 ]]; then + printf "\n%s%s%s\n" "$_COLOR_RETURN_ERROR" " Some tests failed " "$_COLOR_DEFAULT" + console_results::print_execution_time + exit 1 + fi + + if [[ "$(state::get_tests_incomplete)" -gt 0 ]]; then + printf "\n%s%s%s\n" "$_COLOR_RETURN_INCOMPLETE" " Some tests incomplete " "$_COLOR_DEFAULT" + console_results::print_execution_time + exit 0 + fi + + if [[ "$(state::get_tests_skipped)" -gt 0 ]]; then + printf "\n%s%s%s\n" "$_COLOR_RETURN_SKIPPED" " Some tests skipped " "$_COLOR_DEFAULT" + console_results::print_execution_time + exit 0 + fi + + if [[ "$(state::get_tests_snapshot)" -gt 0 ]]; then + printf "\n%s%s%s\n" "$_COLOR_RETURN_SNAPSHOT" " Some snapshots created " "$_COLOR_DEFAULT" + console_results::print_execution_time + exit 0 + fi + + if [[ $total_tests -eq 0 ]]; then + printf "\n%s%s%s\n" "$_COLOR_RETURN_ERROR" " No tests found " "$_COLOR_DEFAULT" + console_results::print_execution_time + exit 1 + fi + + printf "\n%s%s%s\n" "$_COLOR_RETURN_SUCCESS" " All tests passed " "$_COLOR_DEFAULT" + console_results::print_execution_time + exit 0 +} + +function console_results::print_execution_time() { + if [[ $SHOW_EXECUTION_TIME == false ]]; then + return + fi + + if [[ "$_OS" != "OSX" ]]; then + _EXECUTION_TIME=$((($(date +%s%N) - "$_START_TIME") / 1000000)) + printf "${_COLOR_BOLD}%s${_COLOR_DEFAULT}\n" "Time taken: ${_EXECUTION_TIME} ms" + fi +} + +function console_results::print_successful_test() { + ((_SUCCESSFUL_TEST_COUNT++)) + + if [[ "$SIMPLE_OUTPUT" == true ]]; then + if (( _SUCCESSFUL_TEST_COUNT % 50 != 0 )); then + printf "." + else + echo "." + fi + else + local test_name=$1 + shift + + if [[ -z "$*" ]]; then + printf "%s✓ Passed%s: %s\n" "$_COLOR_PASSED" "$_COLOR_DEFAULT" "${test_name}" + else + printf "%s✓ Passed%s: %s (%s)\n" "$_COLOR_PASSED" "$_COLOR_DEFAULT" "${test_name}" "$*" + fi + fi +} + +function console_results::print_failure_message() { + local test_name=$1 + local failure_message=$2 + + printf "\ +${_COLOR_FAILED}✗ Failed${_COLOR_DEFAULT}: %s + ${_COLOR_FAINT}Message:${_COLOR_DEFAULT} ${_COLOR_BOLD}'%s'${_COLOR_DEFAULT}\n"\ + "${test_name}" "${failure_message}" +} + +function console_results::print_failed_test() { + local test_name=$1 + local expected=$2 + local failure_condition_message=$3 + local actual=$4 + + printf "\ +${_COLOR_FAILED}✗ Failed${_COLOR_DEFAULT}: %s + ${_COLOR_FAINT}Expected${_COLOR_DEFAULT} ${_COLOR_BOLD}'%s'${_COLOR_DEFAULT} + ${_COLOR_FAINT}%s${_COLOR_DEFAULT} ${_COLOR_BOLD}'%s'${_COLOR_DEFAULT}\n"\ + "${test_name}" "${expected}" "${failure_condition_message}" "${actual}" +} + +function console_results::print_failed_snapshot_test() { + local test_name=$1 + local snapshot_file=$2 + + printf "${_COLOR_FAILED}✗ Failed${_COLOR_DEFAULT}: %s + ${_COLOR_FAINT}Expected to match the snapshot${_COLOR_DEFAULT}\n" "$test_name" + + if command -v git > /dev//null; then + local actual_file + actual_file="${snapshot_file}.tmp" + echo "$actual" > "$actual_file" + git diff --no-index --word-diff --color=always "$snapshot_file" "$actual_file" 2>/dev/null\ + | tail -n +6 | sed "s/^/ /" + rm "$actual_file" + fi +} + +function console_results::print_skipped_test() { + local test_name=$1 + local reason=$2 + + printf "${_COLOR_SKIPPED}↷ Skipped${_COLOR_DEFAULT}: %s\n" "${test_name}" + + if [[ -n "$reason" ]]; then + printf "${_COLOR_FAINT} %s${_COLOR_DEFAULT}\n" "${reason}" + fi +} + +function console_results::print_incomplete_test() { + local test_name=$1 + local pending=$2 + + printf "${_COLOR_INCOMPLETE}✒ Incomplete${_COLOR_DEFAULT}: %s\n" "${test_name}" + + if [[ -n "$pending" ]]; then + printf "${_COLOR_FAINT} %s${_COLOR_DEFAULT}\n" "${pending}" + fi +} + +function console_results::print_snapshot_test() { + local test_name + test_name=$(helper::normalize_test_function_name "$1") + + printf "${_COLOR_SNAPSHOT}✎ Snapshot${_COLOR_DEFAULT}: %s\n" "${test_name}" +} + +function console_results::print_error_test() { + local test_name + test_name=$(helper::normalize_test_function_name "$1") + local error="$2" + + printf "${_COLOR_FAILED}✗ Failed${_COLOR_DEFAULT}: %s + ${_COLOR_FAINT}%s${_COLOR_DEFAULT}\n" "${test_name}" "${error}" +} +#!/bin/bash + +# shellcheck disable=SC2034 +_DEFAULT_PARALLEL_RUN=false +_DEFAULT_SHOW_HEADER=true +_DEFAULT_HEADER_ASCII_ART=false +_DEFAULT_SIMPLE_OUTPUT=false +_DEFAULT_STOP_ON_FAILURE=false +_DEFAULT_SHOW_EXECUTION_TIME=true +_DEFAULT_DEFAULT_PATH= +CAT="$(which cat)" +#!/bin/bash + +# Deprecated: Please use assert_equals instead. +function assertEquals() { + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + assert_equals "$1" "$2" "$label" +} + +# Deprecated: Please use assert_empty instead. +function assertEmpty() { + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + assert_empty "$1" "$label" +} + +# Deprecated: Please use assert_not_empty instead. +function assertNotEmpty() { + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + assert_not_empty "$1" "$label" +} + +# Deprecated: Please use assert_not_equals instead. +function assertNotEquals() { + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + assert_not_equals "$1" "$2" "$label" +} + +# Deprecated: Please use assert_contains instead. +function assertContains() { + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + assert_contains "$1" "$2" "$label" +} + +# Deprecated: Please use assert_not_contains instead. +function assertNotContains() { + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + assert_not_contains "$1" "$2" "$label" +} + +# Deprecated: Please use assert_matches instead. +function assertMatches() { + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + assert_matches "$1" "$2" "$label" +} + +# Deprecated: Please use assert_not_matches instead. +function assertNotMatches() { + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + assert_not_matches "$1" "$2" "$label" +} + +# Deprecated: Please use assert_exit_code instead. +function assertExitCode() { + local actual_exit_code=$? + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + assert_exit_code "$1" "$label" "$actual_exit_code" +} + +# Deprecated: Please use assert_successful_code instead. +function assertSuccessfulCode() { + local actual_exit_code=$? + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + assert_successful_code "$1" "$label" "$actual_exit_code" +} + +# Deprecated: Please use assert_general_error instead. +function assertGeneralError() { + local actual_exit_code=$? + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + assert_general_error "$1" "$label" "$actual_exit_code" +} + +# Deprecated: Please use assert_command_not_found instead. +function assertCommandNotFound() { + local actual_exit_code=$? + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + assert_command_not_found "{command}" "$label" "$actual_exit_code" +} + +# Deprecated: Please use assert_array_contains instead. +function assertArrayContains() { + assert_array_contains "$1" "${@:2}" +} + +# Deprecated: Please use assert_array_not_contains instead. +function assertArrayNotContains() { + assert_array_not_contains "$1" "${@:1}" +} +#!/bin/bash + +set -o allexport +# shellcheck source=/dev/null +[[ -f ".env" ]] && source .env set +set +o allexport + +if [[ -z "$PARALLEL_RUN" ]]; then + PARALLEL_RUN=$_DEFAULT_PARALLEL_RUN +fi + +if [[ -z "$SHOW_HEADER" ]]; then + SHOW_HEADER=$_DEFAULT_SHOW_HEADER +fi + +if [[ -z "$HEADER_ASCII_ART" ]]; then + HEADER_ASCII_ART=$_DEFAULT_HEADER_ASCII_ART +fi + +if [[ -z "$SIMPLE_OUTPUT" ]]; then + SIMPLE_OUTPUT=$_DEFAULT_SIMPLE_OUTPUT +fi + +if [[ -z "$STOP_ON_FAILURE" ]]; then + STOP_ON_FAILURE=$_DEFAULT_STOP_ON_FAILURE +fi + +if [[ -z "$SHOW_EXECUTION_TIME" ]]; then + SHOW_EXECUTION_TIME=$_DEFAULT_SHOW_EXECUTION_TIME +fi + +if [[ -z "$DEFAULT_PATH" ]]; then + DEFAULT_PATH=$_DEFAULT_DEFAULT_PATH +fi +#!/bin/bash + +declare -r BASHUNIT_GIT_REPO="https://github.com/TypedDevs/bashunit" + +# +# @param $1 string Eg: "test_some_logic_camelCase" +# +# @return string Eg: "Some logic camelCase" +# +function helper::normalize_test_function_name() { + local original_function_name="$1" + local result + + # Remove "test_" prefix + result="${original_function_name#test_}" + # Replace underscores with spaces + result="${result//_/ }" + # Remove "test" prefix + result="${result#test}" + # Capitalize the first letter + result="$(tr '[:lower:]' '[:upper:]' <<< "${result:0:1}")${result:1}" + + echo "$result" +} + +function helper::check_duplicate_functions() { + local script="$1" + + local filtered_lines + filtered_lines=$(grep -E '^\s*(function)?\s*test[a-zA-Z_][a-zA-Z_0-9]*\s*\(\)?\s*{' "$script") + + local function_names + function_names=$(echo "$filtered_lines" | awk '{gsub(/\(|\)/, ""); print $2}') + + local sorted_names + sorted_names=$(echo "$function_names" | sort) + + local duplicates + duplicates=$(echo "$sorted_names" | uniq -d) + if [ -n "$duplicates" ]; then + state::set_duplicated_functions_merged "$script" "$duplicates" + return 1 + fi +} + +# +# @param $1 string Eg: "prefix" +# @param $2 string Eg: "filter" +# @param $3 array Eg: "[fn1, fn2, prefix_filter_fn3, fn4, ...]" +# +# @return array Eg: "[prefix_filter_fn3, ...]" The filtered functions with prefix +# +function helper::get_functions_to_run() { + local prefix=$1 + local filter=$2 + local function_names=$3 + + local functions_to_run=() + + for function_name in $function_names; do + if [[ $function_name != ${prefix}* ]]; then + continue + fi + + local lower_case_function_name + lower_case_function_name=$(echo "$function_name" | tr '[:upper:]' '[:lower:]') + local lower_case_filter + lower_case_filter=$(echo "$filter" | tr '[:upper:]' '[:lower:]') + + if [[ -n $filter && $lower_case_function_name != *"$lower_case_filter"* ]]; then + continue + fi + + if [[ "${functions_to_run[*]}" =~ ${function_name} ]]; then + return 1 + fi + + functions_to_run+=("$function_name") + done + + echo "${functions_to_run[@]}" +} + +# +# @param $1 string Eg: "do_something" +# +function helper::execute_function_if_exists() { + "$1" 2>/dev/null +} + +# +# @param $1 string Eg: "do_something" +# +function helper::unset_if_exists() { + unset "$1" 2>/dev/null +} + +function helper::find_files_recursive() { + local path="$1" + + if [[ "$path" == *"*"* ]]; then + eval find "$path" -type f -name '*[tT]est.sh' | sort | uniq + elif [[ -d "$path" ]]; then + find "$path" -type f -name '*[tT]est.sh' | sort | uniq + else + echo "$path" + fi +} + +helper::normalize_variable_name() { + local input_string="$1" + local normalized_string + + normalized_string="${input_string//[^a-zA-Z0-9_]/_}" + + if [[ ! $normalized_string =~ ^[a-zA-Z_] ]]; then + normalized_string="_$normalized_string" + fi + + echo "$normalized_string" +} + +function helper::get_provider_data() { + local function_name="$1" + local script="$2" + local data_provider_function + + if [[ ! -f "$script" ]]; then + return + fi + + data_provider_function=$(\ + grep -B 1 "function $function_name()" "$script" |\ + grep "# data_provider " |\ + sed -E -e 's/\ *# data_provider (.*)$/\1/g'\ + ) + + if [[ -n "$data_provider_function" ]]; then + helper::execute_function_if_exists "$data_provider_function" + fi +} + +function helper::get_multi_invoker_function() { + local function_name="$1" + local script="$2" + local multi_invoker_function + + if [[ ! -f "$script" ]]; then + return + fi + + multi_invoker_function=$(\ + grep -B 1 "function $function_name()" "$script" |\ + grep "# multi_invoker " |\ + sed -E -e 's/\ *# multi_invoker (.*)$/\1/g'\ + ) + func_exists=$(declare -f "$multi_invoker_function") + if [[ -n "$func_exists" ]]; then + echo "$multi_invoker_function" + fi +} + +function helper::trim() { + local input_string="$1" + local trimmed_string + + trimmed_string="${input_string#"${input_string%%[![:space:]]*}"}" + trimmed_string="${trimmed_string%"${trimmed_string##*[![:space:]]}"}" + + echo "$trimmed_string" +} + +function helpers::get_latest_tag() { + git ls-remote --tags "$BASHUNIT_GIT_REPO" | + awk '{print $2}' | + sed 's|^refs/tags/||' | + sort -Vr | + head -n 1 +} +#!/bin/bash + +function runner::load_test_files() { + local filter=$1 + local files=("${@:2}") # Store all arguments starting from the second as an array + + if [[ "${#files[@]}" == 0 ]]; then + if [[ -n "${DEFAULT_PATH}" ]]; then + while IFS='' read -r line; do + files+=("$line"); + done < <(helper::find_files_recursive "$DEFAULT_PATH") + else + printf "%sError: At least one file path is required.%s\n" "${_COLOR_FAILED}" "${_COLOR_DEFAULT}" + console_header::print_help + exit 1 + fi + fi + + for test_file in "${files[@]}"; do + if [[ ! -f $test_file ]]; then + continue + fi + + # shellcheck source=/dev/null + source "$test_file" + + runner::run_set_up_before_script + runner::call_test_functions "$test_file" "$filter" + if [ "$PARALLEL_RUN" = true ] ; then + wait + fi + runner::run_tear_down_after_script + runner::clean_set_up_and_tear_down_after_script + done +} + +function runner::functions_for_script() { + local script="$1" + local all_function_names="$2" + + # Filter the names down to the ones defined in the script, sort them by line + # number + shopt -s extdebug + for f in $all_function_names; do + declare -F "$f" | grep "$script" + done | sort -k2 -n | awk '{print $1}' + shopt -u extdebug +} + +# Helper function for test authors to invoke a named test case +function run_test() { + runner::run_test "$function_name" "$@" +} + +function runner::call_test_functions() { + local script="$1" + local filter="$2" + local prefix="test" + # Use declare -F to list all function names + local all_function_names + all_function_names=$(declare -F | awk '{print $3}') + local filtered_functions + # shellcheck disable=SC2207 + filtered_functions=$(helper::get_functions_to_run "$prefix" "$filter" "$all_function_names") + + local functions_to_run + # shellcheck disable=SC2207 + functions_to_run=($(runner::functions_for_script "$script" "$filtered_functions")) + + if [[ "${#functions_to_run[@]}" -gt 0 ]]; then + if [[ "$SIMPLE_OUTPUT" == false ]]; then + echo "Running $script" + fi + + helper::check_duplicate_functions "$script" + + for function_name in "${functions_to_run[@]}"; do + local provider_data=() + IFS=" " read -r -a provider_data <<< "$(helper::get_provider_data "$function_name" "$script")" + + if [[ "${#provider_data[@]}" -gt 0 ]]; then + for data in "${provider_data[@]}"; do + runner::run_test "$function_name" "$data" + done + else + local multi_invoker + multi_invoker=$(helper::get_multi_invoker_function "$function_name" "$script") + if [[ -n "${multi_invoker}" ]]; then + helper::execute_function_if_exists "${multi_invoker}" + else + runner::run_test "$function_name" + fi + fi + + unset function_name + done + fi +} + +function runner::parse_execution_result() { + local execution_result=$1 + + local assertions_failed + assertions_failed=$(\ + echo "$execution_result" |\ + tail -n 1 |\ + sed -E -e 's/.*##ASSERTIONS_FAILED=([0-9]*)##.*/\1/g'\ + ) + + local assertions_passed + assertions_passed=$(\ + echo "$execution_result" |\ + tail -n 1 |\ + sed -E -e 's/.*##ASSERTIONS_PASSED=([0-9]*)##.*/\1/g'\ + ) + + local assertions_skipped + assertions_skipped=$(\ + echo "$execution_result" |\ + tail -n 1 |\ + sed -E -e 's/.*##ASSERTIONS_SKIPPED=([0-9]*)##.*/\1/g'\ + ) + + local assertions_incomplete + assertions_incomplete=$(\ + echo "$execution_result" |\ + tail -n 1 |\ + sed -E -e 's/.*##ASSERTIONS_INCOMPLETE=([0-9]*)##.*/\1/g'\ + ) + + local assertions_snapshot + assertions_snapshot=$(\ + echo "$execution_result" |\ + tail -n 1 |\ + sed -E -e 's/.*##ASSERTIONS_SNAPSHOT=([0-9]*)##.*/\1/g'\ + ) + + _ASSERTIONS_PASSED=$((_ASSERTIONS_PASSED + assertions_passed)) + _ASSERTIONS_FAILED=$((_ASSERTIONS_FAILED + assertions_failed)) + _ASSERTIONS_SKIPPED=$((_ASSERTIONS_SKIPPED + assertions_skipped)) + _ASSERTIONS_INCOMPLETE=$((_ASSERTIONS_INCOMPLETE + assertions_incomplete)) + _ASSERTIONS_SNAPSHOT=$((_ASSERTIONS_SNAPSHOT + assertions_snapshot)) +} + +function runner::run_test() { + local function_name="$1" + shift + local current_assertions_failed + current_assertions_failed="$(state::get_assertions_failed)" + local current_assertions_snapshot + current_assertions_snapshot="$(state::get_assertions_snapshot)" + local current_assertions_incomplete + current_assertions_incomplete="$(state::get_assertions_incomplete)" + local current_assertions_skipped + current_assertions_skipped="$(state::get_assertions_skipped)" + + exec 3>&1 + + local test_execution_result + test_execution_result=$( + state::initialize_assertions_count + runner::run_set_up + + "$function_name" "$@" 2>&1 1>&3 + + runner::run_tear_down + runner::clear_mocks + state::export_assertions_count + ) + + exec 3>&- + + runner::parse_execution_result "$test_execution_result" + + local runtime_error + runtime_error=$(\ + echo "$test_execution_result" |\ + head -n 1 |\ + sed -E -e 's/(.*)##ASSERTIONS_FAILED=.*/\1/g'\ + ) + + if [[ -n $runtime_error ]]; then + state::add_tests_failed + console_results::print_error_test "$function_name" "$runtime_error" + return + fi + + if [[ "$current_assertions_failed" != "$(state::get_assertions_failed)" ]]; then + state::add_tests_failed + + if [ "$STOP_ON_FAILURE" = true ]; then + exit 1 + fi + + return + fi + + if [[ "$current_assertions_snapshot" != "$(state::get_assertions_snapshot)" ]]; then + state::add_tests_snapshot + console_results::print_snapshot_test "$function_name" + return + fi + + if [[ "$current_assertions_incomplete" != "$(state::get_assertions_incomplete)" ]]; then + state::add_tests_incomplete + return + fi + + if [[ "$current_assertions_skipped" != "$(state::get_assertions_skipped)" ]]; then + state::add_tests_skipped + return + fi + + local label + label="$(helper::normalize_test_function_name "$function_name")" + + console_results::print_successful_test "${label}" "$@" + state::add_tests_passed +} + +function runner::run_set_up() { + helper::execute_function_if_exists 'set_up' +} + +function runner::run_set_up_before_script() { + helper::execute_function_if_exists 'set_up_before_script' +} + +function runner::run_tear_down() { + helper::execute_function_if_exists 'tear_down' +} + +function runner::clear_mocks() { + for i in "${!MOCKED_FUNCTIONS[@]}"; do + unmock "${MOCKED_FUNCTIONS[$i]}" + done +} + +function runner::run_tear_down_after_script() { + helper::execute_function_if_exists 'tear_down_after_script' +} + +function runner::clean_set_up_and_tear_down_after_script() { + helper::unset_if_exists 'set_up' + helper::unset_if_exists 'tear_down' + helper::unset_if_exists 'set_up_before_script' + helper::unset_if_exists 'tear_down_after_script' +} +#!/bin/bash + +function skip() { + local reason=$1 + local label + label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")" + + console_results::print_skipped_test "${label}" "${reason}" + + state::add_assertions_skipped +} + +function todo() { + local pending=$1 + local label + label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")" + + console_results::print_incomplete_test "${label}" "${pending}" + + state::add_assertions_incomplete +} +#!/bin/bash + +_TESTS_PASSED=0 +_TESTS_FAILED=0 +_TESTS_SKIPPED=0 +_TESTS_INCOMPLETE=0 +_TESTS_SNAPSHOT=0 +_ASSERTIONS_PASSED=0 +_ASSERTIONS_FAILED=0 +_ASSERTIONS_SKIPPED=0 +_ASSERTIONS_INCOMPLETE=0 +_ASSERTIONS_SNAPSHOT=0 +_DUPLICATED_FUNCTION_NAMES="" +_FILE_WITH_DUPLICATED_FUNCTION_NAMES="" +_DUPLICATED_TEST_FUNCTIONS_FOUND=false + +function state::get_tests_passed() { + echo "$_TESTS_PASSED" +} + +function state::add_tests_passed() { + ((_TESTS_PASSED++)) || true +} + +function state::get_tests_failed() { + echo "$_TESTS_FAILED" +} + +function state::add_tests_failed() { + ((_TESTS_FAILED++)) || true +} + +function state::get_tests_skipped() { + echo "$_TESTS_SKIPPED" +} + +function state::add_tests_skipped() { + ((_TESTS_SKIPPED++)) || true +} + +function state::get_tests_incomplete() { + echo "$_TESTS_INCOMPLETE" +} + +function state::add_tests_incomplete() { + ((_TESTS_INCOMPLETE++)) || true +} + +function state::get_tests_snapshot() { + echo "$_TESTS_SNAPSHOT" +} + +function state::add_tests_snapshot() { + ((_TESTS_SNAPSHOT++)) || true +} + +function state::get_assertions_passed() { + echo "$_ASSERTIONS_PASSED" +} + +function state::add_assertions_passed() { + ((_ASSERTIONS_PASSED++)) || true +} + +function state::get_assertions_failed() { + echo "$_ASSERTIONS_FAILED" +} + +function state::add_assertions_failed() { + ((_ASSERTIONS_FAILED++)) || true +} + +function state::get_assertions_skipped() { + echo "$_ASSERTIONS_SKIPPED" +} + +function state::add_assertions_skipped() { + ((_ASSERTIONS_SKIPPED++)) || true +} + +function state::get_assertions_incomplete() { + echo "$_ASSERTIONS_INCOMPLETE" +} + +function state::add_assertions_incomplete() { + ((_ASSERTIONS_INCOMPLETE++)) || true +} + +function state::get_assertions_snapshot() { + echo "$_ASSERTIONS_SNAPSHOT" +} + +function state::add_assertions_snapshot() { + ((_ASSERTIONS_SNAPSHOT++)) || true +} + +function state::is_duplicated_test_functions_found() { + echo "$_DUPLICATED_TEST_FUNCTIONS_FOUND" +} + +function state::set_duplicated_test_functions_found() { + _DUPLICATED_TEST_FUNCTIONS_FOUND=true +} + +function state::get_duplicated_function_names() { + echo "$_DUPLICATED_FUNCTION_NAMES" +} + +function state::set_duplicated_function_names() { + _DUPLICATED_FUNCTION_NAMES="$1" +} + +function state::get_file_with_duplicated_function_names() { + echo "$_FILE_WITH_DUPLICATED_FUNCTION_NAMES" +} + +function state::set_file_with_duplicated_function_names() { + _FILE_WITH_DUPLICATED_FUNCTION_NAMES="$1" +} + +function state::set_duplicated_functions_merged() { + state::set_duplicated_test_functions_found + state::set_file_with_duplicated_function_names "$1" + state::set_duplicated_function_names "$2" + +} + +function state::initialize_assertions_count() { + _ASSERTIONS_PASSED=0 + _ASSERTIONS_FAILED=0 + _ASSERTIONS_SKIPPED=0 + _ASSERTIONS_INCOMPLETE=0 + _ASSERTIONS_SNAPSHOT=0 +} + +function state::export_assertions_count() { + echo "##ASSERTIONS_FAILED=$_ASSERTIONS_FAILED\ +##ASSERTIONS_PASSED=$_ASSERTIONS_PASSED\ +##ASSERTIONS_SKIPPED=$_ASSERTIONS_SKIPPED\ +##ASSERTIONS_INCOMPLETE=$_ASSERTIONS_INCOMPLETE\ +##ASSERTIONS_SNAPSHOT=$_ASSERTIONS_SNAPSHOT\ +##" +} +#!/bin/bash + +declare -a MOCKED_FUNCTIONS=() + +function unmock() { + local command=$1 + + for i in "${!MOCKED_FUNCTIONS[@]}"; do + if [[ "${MOCKED_FUNCTIONS[$i]}" == "$command" ]]; then + unset "MOCKED_FUNCTIONS[$i]" + unset -f "$command" + break + fi + done +} + +function mock() { + local command=$1 + shift + + if [[ $# -gt 0 ]]; then + eval "function $command() { $* ; }" + else + eval "function $command() { echo \"$($CAT)\" ; }" + fi + + export -f "${command?}" + + MOCKED_FUNCTIONS+=("$command") +} + +function spy() { + local command=$1 + local variable + variable="$(helper::normalize_variable_name "$command")" + + export "${variable}_times"=0 + export "${variable}_params" + + eval "function $command() { ${variable}_params=(\"\$*\"); ((${variable}_times++)) || true; }" + + export -f "${command?}" + + MOCKED_FUNCTIONS+=("$command") +} + +function assert_have_been_called() { + local command=$1 + local variable + variable="$(helper::normalize_variable_name "$command")" + local actual + actual="${variable}_times" + local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ ${!actual} -eq 0 ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${command}" "to has been called" "once" + return + fi + + state::add_assertions_passed +} + +function assert_have_been_called_with() { + local expected=$1 + local command=$2 + local variable + variable="$(helper::normalize_variable_name "$command")" + local actual + actual="${variable}_params" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ "$expected" != "${!actual}" ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${expected}" "but got" "${!actual}" + return + fi + + state::add_assertions_passed +} + +function assert_have_been_called_times() { + local expected=$1 + local command=$2 + local variable + variable="$(helper::normalize_variable_name "$command")" + local actual + actual="${variable}_times" + local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" + + if [[ ${!actual} -ne $expected ]]; then + state::add_assertions_failed + console_results::print_failed_test "${label}" "${command}" "to has been called" "${expected} times" + return + fi + + state::add_assertions_passed +} +#!/bin/bash + +function upgrade::upgrade() { + local script_path + script_path="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + local latest_tag + latest_tag="$(helpers::get_latest_tag)" + + if [[ "$BASHUNIT_VERSION" == "$latest_tag" ]]; then + echo "> You are already on latest version" + return + fi + + echo "> Upgrading bashunit to latest version" + cd "$script_path" || exit + curl -L -J -o bashunit "https://github.com/TypedDevs/bashunit/releases/download/$latest_tag/bashunit" 2>/dev/null + chmod u+x "bashunit" + + echo "> bashunit upgraded successfully to latest version $latest_tag" +} +#!/bin/bash + +# shellcheck disable=SC2034 +declare -r BASHUNIT_VERSION="0.12.0" + +readonly BASHUNIT_ROOT_DIR="$(dirname "${BASH_SOURCE[0]}")" +export BASHUNIT_ROOT_DIR + + +############### +#### MAIN ##### +############### + +_FILTER="" +_FILES=() + +while [[ $# -gt 0 ]]; do + argument="$1" + case $argument in + -f|--filter) + _FILTER="$2" + shift + shift + ;; + -s|--simple) + SIMPLE_OUTPUT=true + shift + ;; + -v|--verbose) + SIMPLE_OUTPUT=false + shift + ;; + -S|--stop-on-failure) + STOP_ON_FAILURE=true + shift + ;; + -e|--env) + # shellcheck disable=SC1090 + source "$2" + shift + shift + ;; + --version) + console_header::print_version + trap '' EXIT && exit 0 + ;; + --upgrade) + upgrade::upgrade + trap '' EXIT && exit 0 + ;; + --help) + console_header::print_help + trap '' EXIT && exit 0 + ;; + *) + while IFS='' read -r line; do + _FILES+=("$line"); + done < <(helper::find_files_recursive "$argument") + shift + ;; + esac +done + +console_header::print_version_with_env +runner::load_test_files "$_FILTER" "${_FILES[@]}" +console_results::render_result + +exit 0 From 1328bc94416b97671a6a92de6f518524b9db5afc Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 15 Jun 2024 10:46:11 +0200 Subject: [PATCH 03/28] Update test.sh --- e2e/result-cache-1/test.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/e2e/result-cache-1/test.sh b/e2e/result-cache-1/test.sh index 6d3ff48166..c44d1fd223 100644 --- a/e2e/result-cache-1/test.sh +++ b/e2e/result-cache-1/test.sh @@ -3,10 +3,15 @@ function test_result_cache1() { echo -n > phpstan-baseline.neon ../../bin/phpstan -vvv + assert_successful_code + patch -b src/Bar.php < patch-1.patch cat baseline-1.neon > phpstan-baseline.neon ../../bin/phpstan -vvv + assert_successful_code + mv src/Bar.php.orig src/Bar.php echo -n > phpstan-baseline.neon ../../bin/phpstan -vvv + assert_successful_code } 2>&1 From 80e5419436f8ea8ba4e28b596cac236d1fb6f327 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 15 Jun 2024 10:57:35 +0200 Subject: [PATCH 04/28] Create test.sh --- e2e/trait-caching/test.sh | 57 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 e2e/trait-caching/test.sh diff --git a/e2e/trait-caching/test.sh b/e2e/trait-caching/test.sh new file mode 100644 index 0000000000..e004df458a --- /dev/null +++ b/e2e/trait-caching/test.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +function set_up() { + git restore . +} 2>&1 + +function test_trait_caching() { + ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ + assert_successful_code + + ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ + assert_successful_code +} 2>&1 + +function test_trait_caching_one_changed() { + ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ + assert_successful_code + + patch -b data/TraitOne.php < TraitOne.patch + + OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) + echo "$OUTPUT" + [ $(echo "$OUTPUT" | wc -l) -eq 1 ] + assert_successful_code + + assert_contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" +} 2>&1 + +function test_trait_caching_two_changed() { + ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ + assert_successful_code + + patch -b data/TraitTwo.php < TraitTwo.patch + + OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) + echo "$OUTPUT" + [ $(echo "$OUTPUT" | wc -l) -eq 1 ] + assert_successful_code + + assert_contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" +} 2>&1 + +function test_trait_caching_both_changed() { + ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ + assert_successful_code + + patch -b data/TraitOne.php < TraitOne.patch + patch -b data/TraitTwo.php < TraitTwo.patch + + OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) + echo "$OUTPUT" + [ $(echo "$OUTPUT" | wc -l) -eq 2 ] + assert_successful_code + + assert_contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" + assert_contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" +} 2>&1 From 995d940c485b7d581affa0f4b8ea18ef1e14d9b7 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 15 Jun 2024 10:58:35 +0200 Subject: [PATCH 05/28] Update test.sh --- e2e/trait-caching/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/trait-caching/test.sh b/e2e/trait-caching/test.sh index e004df458a..8fe59cc1ed 100644 --- a/e2e/trait-caching/test.sh +++ b/e2e/trait-caching/test.sh @@ -1,7 +1,7 @@ #!/bin/bash function set_up() { - git restore . + git clean -fd } 2>&1 function test_trait_caching() { From 33fb5b60a1b1adff104195c88e519d310dd9dbfd Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 15 Jun 2024 10:59:55 +0200 Subject: [PATCH 06/28] Update test.sh --- e2e/trait-caching/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/trait-caching/test.sh b/e2e/trait-caching/test.sh index 8fe59cc1ed..1037d66c6d 100644 --- a/e2e/trait-caching/test.sh +++ b/e2e/trait-caching/test.sh @@ -2,7 +2,7 @@ function set_up() { git clean -fd -} 2>&1 +} function test_trait_caching() { ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ From e5165579a9d107104c893832e015748f7ece2ff6 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 15 Jun 2024 11:01:02 +0200 Subject: [PATCH 07/28] Update test.sh --- e2e/trait-caching/test.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/trait-caching/test.sh b/e2e/trait-caching/test.sh index 1037d66c6d..932bceede7 100644 --- a/e2e/trait-caching/test.sh +++ b/e2e/trait-caching/test.sh @@ -1,6 +1,7 @@ #!/bin/bash function set_up() { + git restore . git clean -fd } From da1d6d16adeab270e73f81022e180f68ea1cb491 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 15 Jun 2024 11:02:01 +0200 Subject: [PATCH 08/28] Update test.sh --- e2e/trait-caching/test.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/trait-caching/test.sh b/e2e/trait-caching/test.sh index 932bceede7..7771f5584f 100644 --- a/e2e/trait-caching/test.sh +++ b/e2e/trait-caching/test.sh @@ -38,7 +38,7 @@ function test_trait_caching_two_changed() { [ $(echo "$OUTPUT" | wc -l) -eq 1 ] assert_successful_code - assert_contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" + assert_contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" } 2>&1 function test_trait_caching_both_changed() { @@ -54,5 +54,5 @@ function test_trait_caching_both_changed() { assert_successful_code assert_contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" - assert_contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" + assert_contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" } 2>&1 From 08e23b8993f19030eec3406e97ff081b0e61efbb Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 15 Jun 2024 11:02:39 +0200 Subject: [PATCH 09/28] Update test.sh --- e2e/trait-caching/test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/trait-caching/test.sh b/e2e/trait-caching/test.sh index 7771f5584f..b5f4f085a3 100644 --- a/e2e/trait-caching/test.sh +++ b/e2e/trait-caching/test.sh @@ -3,7 +3,7 @@ function set_up() { git restore . git clean -fd -} +} 2>&1 function test_trait_caching() { ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ From 67316372cf13c1220551e8a98467f49555393d27 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 15 Jun 2024 11:03:54 +0200 Subject: [PATCH 10/28] Update test.sh --- e2e/trait-caching/test.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/e2e/trait-caching/test.sh b/e2e/trait-caching/test.sh index b5f4f085a3..94df417c04 100644 --- a/e2e/trait-caching/test.sh +++ b/e2e/trait-caching/test.sh @@ -1,9 +1,9 @@ #!/bin/bash function set_up() { - git restore . - git clean -fd -} 2>&1 + git restore . 2>&1 + git clean -fd 2>&1 +} function test_trait_caching() { ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ From e6f8b03cc981271aad31b287d39df4e10f288bb8 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 15 Jun 2024 11:06:46 +0200 Subject: [PATCH 11/28] Update test.sh --- e2e/trait-caching/test.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/e2e/trait-caching/test.sh b/e2e/trait-caching/test.sh index 94df417c04..f5e4e17de3 100644 --- a/e2e/trait-caching/test.sh +++ b/e2e/trait-caching/test.sh @@ -1,8 +1,7 @@ #!/bin/bash function set_up() { - git restore . 2>&1 - git clean -fd 2>&1 + git restore data/ } function test_trait_caching() { From f4f5a3cf2d7b1b354f0c91bd27252df03b374c10 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 15 Jun 2024 11:08:49 +0200 Subject: [PATCH 12/28] Update e2e-tests.yml --- .github/workflows/e2e-tests.yml | 33 +++------------------------------ 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 5bc3e17645..3269b4cbba 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -174,36 +174,6 @@ jobs: - script: | cd e2e/env-int-key env 1=1 ../../bin/phpstan analyse test.php - - script: | - cd e2e/trait-caching - ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ - ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ - - script: | - cd e2e/trait-caching - ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ - patch -b data/TraitOne.php < TraitOne.patch - OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) - echo "$OUTPUT" - [ $(echo "$OUTPUT" | wc -l) -eq 1 ] - grep 'Method TraitsCachingIssue\\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" - - script: | - cd e2e/trait-caching - ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ - patch -b data/TraitTwo.php < TraitTwo.patch - OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) - echo "$OUTPUT" - [ $(echo "$OUTPUT" | wc -l) -eq 1 ] - grep 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" - - script: | - cd e2e/trait-caching - ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ - patch -b data/TraitOne.php < TraitOne.patch - patch -b data/TraitTwo.php < TraitTwo.patch - OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) - echo "$OUTPUT" - [ $(echo "$OUTPUT" | wc -l) -eq 2 ] - grep 'Method TraitsCachingIssue\\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" - grep 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" steps: - name: "Checkout" @@ -287,6 +257,9 @@ jobs: - script: | cd e2e/result-cache-1 ../bashunit . + - script: | + cd e2e/trait-caching + ../bashunit . steps: - name: "Checkout" From 357137be0613d5a3cefea3e15a811bf58d5c3c68 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sat, 15 Jun 2024 11:10:41 +0200 Subject: [PATCH 13/28] install bashunit in CI --- .github/workflows/e2e-tests.yml | 3 + e2e/bashunit | 1797 ------------------------------- 2 files changed, 3 insertions(+), 1797 deletions(-) delete mode 100755 e2e/bashunit diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 3269b4cbba..ae2edc9d60 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -279,5 +279,8 @@ jobs: - name: "Patch PHPStan" run: "patch src/Analyser/Error.php < e2e/PHPStanErrorPatch.patch" + - name: "Install bashunit" + run: "curl -s https://bashunit.typeddevs.com/install.sh | bash -s e2e/ 0.12.0" + - name: "Test" run: "${{ matrix.script }}" diff --git a/e2e/bashunit b/e2e/bashunit deleted file mode 100755 index 77cb85f5ff..0000000000 --- a/e2e/bashunit +++ /dev/null @@ -1,1797 +0,0 @@ -#!/usr/bin/env bash -#!/bin/bash - -function fail() { - local message=$1 - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - state::add_assertions_failed - console_results::print_failure_message "${label}" "$message" -} - -function assert_equals() { - local expected="$1" - local actual="$2" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ "$expected" != "$actual" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${expected}" "but got" "${actual}" - return - fi - - state::add_assertions_passed -} - -function assert_equals_ignore_colors() { - local expected="$1" - local actual="$2" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - local actual_without_colors - actual_without_colors=$(echo -e "$actual" | sed "s/\x1B\[[0-9;]*[JKmsu]//g") - - assert_equals "$expected" "$actual_without_colors" "$label" -} - -function assert_empty() { - local expected="$1" - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ "$expected" != "" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "to be empty" "but got" "${expected}" - return - fi - - state::add_assertions_passed -} - -function assert_not_empty() { - local expected="$1" - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ "$expected" == "" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "to not be empty" "but got" "${expected}" - return - fi - - state::add_assertions_passed -} - -function assert_not_equals() { - local expected="$1" - local actual="$2" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ "$expected" == "$actual" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${expected}" "but got" "${actual}" - return - fi - - state::add_assertions_passed -} - -function assert_contains() { - local expected="$1" - local actual="$2" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if ! [[ $actual == *"$expected"* ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual}" "to contain" "${expected}" - return - fi - - state::add_assertions_passed -} - -function assert_contains_ignore_case() { - local expected="$1" - local actual="$2" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - shopt -s nocasematch - - if ! [[ $actual =~ $expected ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual}" "to contain" "${expected}" - - shopt -u nocasematch - return - fi - - shopt -u nocasematch - state::add_assertions_passed -} - -function assert_not_contains() { - local expected="$1" - local actual="$2" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ $actual == *"$expected"* ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual}" "to not contain" "${expected}" - return - fi - - state::add_assertions_passed -} - -function assert_matches() { - local expected="$1" - local actual="$2" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if ! [[ $actual =~ $expected ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual}" "to match" "${expected}" - return - fi - - state::add_assertions_passed -} - -function assert_not_matches() { - local expected="$1" - local actual="$2" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ $actual =~ $expected ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual}" "to not match" "${expected}" - return - fi - - state::add_assertions_passed -} - -function assert_exit_code() { - local actual_exit_code=${3-"$?"} - local expected_exit_code="$1" - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ "$actual_exit_code" -ne "$expected_exit_code" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual_exit_code}" "to be" "${expected_exit_code}" - return - fi - - state::add_assertions_passed -} - -function assert_successful_code() { - local actual_exit_code=${3-"$?"} - local expected_exit_code=0 - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ "$actual_exit_code" -ne "$expected_exit_code" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual_exit_code}" "to be exactly" "${expected_exit_code}" - return - fi - - state::add_assertions_passed -} - -function assert_general_error() { - local actual_exit_code=${3-"$?"} - local expected_exit_code=1 - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ $actual_exit_code -ne "$expected_exit_code" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual_exit_code}" "to be exactly" "${expected_exit_code}" - return - fi - - state::add_assertions_passed -} - -function assert_command_not_found() { - local actual_exit_code=${3-"$?"} - local expected_exit_code=127 - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ $actual_exit_code -ne "$expected_exit_code" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual_exit_code}" "to be exactly" "${expected_exit_code}" - return - fi - - state::add_assertions_passed -} - -function assert_string_starts_with() { - local expected="$1" - local actual="$2" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if ! [[ $actual =~ ^"$expected"* ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual}" "to start with" "${expected}" - return - fi - - state::add_assertions_passed -} - -function assert_string_not_starts_with() { - local expected="$1" - local actual="$2" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ $actual =~ ^"$expected"* ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual}" "to not start with" "${expected}" - return - fi - - state::add_assertions_passed -} - -function assert_string_ends_with() { - local expected="$1" - local actual="$2" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if ! [[ $actual =~ .*"$expected"$ ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual}" "to end with" "${expected}" - return - fi - - state::add_assertions_passed -} - -function assert_string_not_ends_with() { - local expected="$1" - local actual="$2" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ $actual =~ .*"$expected"$ ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual}" "to not end with" "${expected}" - return - fi - - state::add_assertions_passed -} - -function assert_less_than() { - local expected="$1" - local actual="$2" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if ! [[ "$actual" -lt "$expected" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual}" "to be less than" "${expected}" - return - fi - - state::add_assertions_passed -} - -function assert_less_or_equal_than() { - local expected="$1" - local actual="$2" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if ! [[ "$actual" -le "$expected" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual}" "to be less or equal than" "${expected}" - return - fi - - state::add_assertions_passed -} - -function assert_greater_than() { - local expected="$1" - local actual="$2" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if ! [[ "$actual" -gt "$expected" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual}" "to be greater than" "${expected}" - return - fi - - state::add_assertions_passed -} - -function assert_greater_or_equal_than() { - local expected="$1" - local actual="$2" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if ! [[ "$actual" -ge "$expected" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual}" "to be greater or equal than" "${expected}" - return - fi - - state::add_assertions_passed -} -#!/bin/bash - -function assert_array_contains() { - local expected="$1" - local label - label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")" - shift - - local actual=("${@}") - - if ! [[ "${actual[*]}" == *"$expected"* ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual[*]}" "to contain" "${expected}" - return - fi - - state::add_assertions_passed -} - -function assert_array_not_contains() { - local expected="$1" - label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")" - shift - local actual=("$@") - - if [[ "${actual[*]}" == *"$expected"* ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${actual[*]}" "to not contain" "${expected}" - return - fi - - state::add_assertions_passed -} -#!/bin/bash - -function assert_file_exists() { - local expected="$1" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ ! -f "$expected" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${expected}" "to exist but" "do not exist" - return - fi - - state::add_assertions_passed -} - -function assert_file_not_exists() { - local expected="$1" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ -f "$expected" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${expected}" "to not exist but" "the file exists" - return - fi - - state::add_assertions_passed -} - -function assert_is_file() { - local expected="$1" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ ! -f "$expected" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${expected}" "to be a file" "but is not a file" - return - fi - - state::add_assertions_passed -} - -function assert_is_file_empty() { - local expected="$1" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ -s "$expected" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${expected}" "to be empty" "but is not empty" - return - fi - - state::add_assertions_passed -} -#!/bin/bash - -function assert_directory_exists() { - local expected="$1" - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ ! -d "$expected" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${expected}" "to exist but" "do not exist" - return - fi - - state::add_assertions_passed -} - -function assert_directory_not_exists() { - local expected="$1" - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ -d "$expected" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${expected}" "to not exist but" "the directory exists" - return - fi - - state::add_assertions_passed -} - -function assert_is_directory() { - local expected="$1" - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ ! -d "$expected" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${expected}" "to be a directory" "but is not a directory" - return - fi - - state::add_assertions_passed -} - -function assert_is_directory_empty() { - local expected="$1" - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ ! -d "$expected" || -n "$(ls -A "$expected")" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${expected}" "to be empty" "but is not empty" - return - fi - - state::add_assertions_passed -} - -function assert_is_directory_not_empty() { - local expected="$1" - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ ! -d "$expected" || -z "$(ls -A "$expected")" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${expected}" "to not be empty" "but is empty" - return - fi - - state::add_assertions_passed -} - -function assert_is_directory_readable() { - local expected="$1" - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ ! -d "$expected" || ! -r "$expected" || ! -x "$expected" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${expected}" "to be readable" "but is not readable" - return - fi - - state::add_assertions_passed -} - -function assert_is_directory_not_readable() { - local expected="$1" - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ ! -d "$expected" ]] || [[ -r "$expected" && -x "$expected" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${expected}" "to be not readable" "but is readable" - return - fi - - state::add_assertions_passed -} - -function assert_is_directory_writable() { - local expected="$1" - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ ! -d "$expected" || ! -w "$expected" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${expected}" "to be writable" "but is not writable" - return - fi - - state::add_assertions_passed -} - -function assert_is_directory_not_writable() { - local expected="$1" - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ ! -d "$expected" || -w "$expected" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${expected}" "to be not writable" "but is writable" - return - fi - - state::add_assertions_passed -} -#!/bin/bash - -function assert_match_snapshot() { - local actual - actual=$(echo -n "$1" | tr -d '\r') - local directory - directory="./$(dirname "${BASH_SOURCE[1]}")/snapshots" - local test_file - test_file="$(helper::normalize_variable_name "$(basename "${BASH_SOURCE[1]}")")" - local snapshot_name - snapshot_name="$(helper::normalize_variable_name "${FUNCNAME[1]}").snapshot" - local snapshot_file - snapshot_file="${directory}/${test_file}.${snapshot_name}" - - if [[ ! -f "$snapshot_file" ]]; then - mkdir -p "$directory" - echo "$actual" > "$snapshot_file" - - state::add_assertions_snapshot - return - fi - - local snapshot - snapshot=$(tr -d '\r' < "$snapshot_file") - - if [[ "$actual" != "$snapshot" ]]; then - local label - label=$(helper::normalize_test_function_name "${FUNCNAME[1]}") - - state::add_assertions_failed - console_results::print_failed_snapshot_test "$label" "$snapshot_file" - - return - fi - - state::add_assertions_passed -} - -#!/bin/bash - -#!/bin/bash - -# shellcheck disable=SC2034 -_OS="Unknown" - -if [[ "$(uname)" == "Linux" ]]; then - _OS="Linux" -elif [[ "$(uname)" == "Darwin" ]]; then - _OS="OSX" -elif [[ $(uname) == *"MINGW"* ]]; then - _OS="Windows" -fi -#!/bin/bash - -# Pass in any number of ANSI SGR codes. -# -# Code reference: -# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters -# Credit: -# https://superuser.com/a/1119396 -sgr() { - local codes=${1:-0} - shift - - for c in "$@"; do - codes="$codes;$c" - done - - echo $'\e'"[${codes}m" -} - -_COLOR_DEFAULT="$(sgr 0)" -_COLOR_BOLD="$(sgr 1)" -_COLOR_FAINT="$(sgr 2)" -_COLOR_BLACK="$(sgr 30)" -_COLOR_FAILED="$(sgr 31)" -_COLOR_PASSED="$(sgr 32)" -_COLOR_SKIPPED="$(sgr 33)" -_COLOR_INCOMPLETE="$(sgr 36)" -_COLOR_SNAPSHOT="$(sgr 34)" -_COLOR_RETURN_ERROR="$(sgr 41)$_COLOR_BLACK$_COLOR_BOLD" -_COLOR_RETURN_SUCCESS="$(sgr 42)$_COLOR_BLACK$_COLOR_BOLD" -_COLOR_RETURN_SKIPPED="$(sgr 43)$_COLOR_BLACK$_COLOR_BOLD" -_COLOR_RETURN_INCOMPLETE="$(sgr 46)$_COLOR_BLACK$_COLOR_BOLD" -_COLOR_RETURN_SNAPSHOT="$(sgr 44)$_COLOR_BLACK$_COLOR_BOLD" -#!/bin/bash - -function console_header::print_version() { - if [[ $HEADER_ASCII_ART == true ]]; then - cat < - Filters the tests to run based on the test name. - - -s|simple || -v|verbose - Enables simplified or verbose output to the console. - - -S|--stop-on-failure - Force to stop the runner right after encountering one failing test. - - -e|--env - Load a custom env file overriding the .env environment variables. - - --version - Displays the current version of bashunit. - - --upgrade - Upgrade to latest version of bashunit. - - --help - This message. - -See more: https://bashunit.typeddevs.com/command-line -EOF -} -#!/bin/bash - -_START_TIME=$(date +%s%N); -_SUCCESSFUL_TEST_COUNT=0 - -function console_results::render_result() { - if [[ "$(state::is_duplicated_test_functions_found)" == true ]]; then - console_results::print_execution_time - printf "%s%s%s\n" "${_COLOR_RETURN_ERROR}" "Duplicate test functions found" "${_COLOR_DEFAULT}" - printf "File with duplicate functions: %s\n" "$(state::get_file_with_duplicated_function_names)" - printf "Duplicate functions: %s\n" "$(state::get_duplicated_function_names)" - exit 1 - fi - - echo "" - - local total_tests=0 - ((total_tests+=$(state::get_tests_passed))) - ((total_tests+=$(state::get_tests_skipped))) - ((total_tests+=$(state::get_tests_incomplete))) - ((total_tests+=$(state::get_tests_snapshot))) - ((total_tests+=$(state::get_tests_failed))) - - local total_assertions=0 - ((total_assertions+=$(state::get_assertions_passed))) - ((total_assertions+=$(state::get_assertions_skipped))) - ((total_assertions+=$(state::get_assertions_incomplete))) - ((total_assertions+=$(state::get_assertions_snapshot))) - ((total_assertions+=$(state::get_assertions_failed))) - - printf "%sTests: %s" "$_COLOR_FAINT" "$_COLOR_DEFAULT" - if [[ "$(state::get_tests_passed)" -gt 0 ]] || [[ "$(state::get_assertions_passed)" -gt 0 ]]; then - printf " %s%s passed%s," "$_COLOR_PASSED" "$(state::get_tests_passed)" "$_COLOR_DEFAULT" - fi - if [[ "$(state::get_tests_skipped)" -gt 0 ]] || [[ "$(state::get_assertions_skipped)" -gt 0 ]]; then - printf " %s%s skipped%s," "$_COLOR_SKIPPED" "$(state::get_tests_skipped)" "$_COLOR_DEFAULT" - fi - if [[ "$(state::get_tests_incomplete)" -gt 0 ]] || [[ "$(state::get_assertions_incomplete)" -gt 0 ]]; then - printf " %s%s incomplete%s," "$_COLOR_INCOMPLETE" "$(state::get_tests_incomplete)" "$_COLOR_DEFAULT" - fi - if [[ "$(state::get_tests_snapshot)" -gt 0 ]] || [[ "$(state::get_assertions_snapshot)" -gt 0 ]]; then - printf " %s%s snapshot%s," "$_COLOR_SNAPSHOT" "$(state::get_tests_snapshot)" "$_COLOR_DEFAULT" - fi - if [[ "$(state::get_tests_failed)" -gt 0 ]] || [[ "$(state::get_assertions_failed)" -gt 0 ]]; then - printf " %s%s failed%s," "$_COLOR_FAILED" "$(state::get_tests_failed)" "$_COLOR_DEFAULT" - fi - printf " %s total\n" "$total_tests" - - printf "%sAssertions:%s" "$_COLOR_FAINT" "$_COLOR_DEFAULT" - if [[ "$(state::get_tests_passed)" -gt 0 ]] || [[ "$(state::get_assertions_passed)" -gt 0 ]]; then - printf " %s%s passed%s," "$_COLOR_PASSED" "$(state::get_assertions_passed)" "$_COLOR_DEFAULT" - fi - if [[ "$(state::get_tests_skipped)" -gt 0 ]] || [[ "$(state::get_assertions_skipped)" -gt 0 ]]; then - printf " %s%s skipped%s," "$_COLOR_SKIPPED" "$(state::get_assertions_skipped)" "$_COLOR_DEFAULT" - fi - if [[ "$(state::get_tests_incomplete)" -gt 0 ]] || [[ "$(state::get_assertions_incomplete)" -gt 0 ]]; then - printf " %s%s incomplete%s," "$_COLOR_INCOMPLETE" "$(state::get_assertions_incomplete)" "$_COLOR_DEFAULT" - fi - if [[ "$(state::get_tests_snapshot)" -gt 0 ]] || [[ "$(state::get_assertions_snapshot)" -gt 0 ]]; then - printf " %s%s snapshot%s," "$_COLOR_SNAPSHOT" "$(state::get_assertions_snapshot)" "$_COLOR_DEFAULT" - fi - if [[ "$(state::get_tests_failed)" -gt 0 ]] || [[ "$(state::get_assertions_failed)" -gt 0 ]]; then - printf " %s%s failed%s," "$_COLOR_FAILED" "$(state::get_assertions_failed)" "$_COLOR_DEFAULT" - fi - printf " %s total\n" "$total_assertions" - - if [[ "$(state::get_tests_failed)" -gt 0 ]]; then - printf "\n%s%s%s\n" "$_COLOR_RETURN_ERROR" " Some tests failed " "$_COLOR_DEFAULT" - console_results::print_execution_time - exit 1 - fi - - if [[ "$(state::get_tests_incomplete)" -gt 0 ]]; then - printf "\n%s%s%s\n" "$_COLOR_RETURN_INCOMPLETE" " Some tests incomplete " "$_COLOR_DEFAULT" - console_results::print_execution_time - exit 0 - fi - - if [[ "$(state::get_tests_skipped)" -gt 0 ]]; then - printf "\n%s%s%s\n" "$_COLOR_RETURN_SKIPPED" " Some tests skipped " "$_COLOR_DEFAULT" - console_results::print_execution_time - exit 0 - fi - - if [[ "$(state::get_tests_snapshot)" -gt 0 ]]; then - printf "\n%s%s%s\n" "$_COLOR_RETURN_SNAPSHOT" " Some snapshots created " "$_COLOR_DEFAULT" - console_results::print_execution_time - exit 0 - fi - - if [[ $total_tests -eq 0 ]]; then - printf "\n%s%s%s\n" "$_COLOR_RETURN_ERROR" " No tests found " "$_COLOR_DEFAULT" - console_results::print_execution_time - exit 1 - fi - - printf "\n%s%s%s\n" "$_COLOR_RETURN_SUCCESS" " All tests passed " "$_COLOR_DEFAULT" - console_results::print_execution_time - exit 0 -} - -function console_results::print_execution_time() { - if [[ $SHOW_EXECUTION_TIME == false ]]; then - return - fi - - if [[ "$_OS" != "OSX" ]]; then - _EXECUTION_TIME=$((($(date +%s%N) - "$_START_TIME") / 1000000)) - printf "${_COLOR_BOLD}%s${_COLOR_DEFAULT}\n" "Time taken: ${_EXECUTION_TIME} ms" - fi -} - -function console_results::print_successful_test() { - ((_SUCCESSFUL_TEST_COUNT++)) - - if [[ "$SIMPLE_OUTPUT" == true ]]; then - if (( _SUCCESSFUL_TEST_COUNT % 50 != 0 )); then - printf "." - else - echo "." - fi - else - local test_name=$1 - shift - - if [[ -z "$*" ]]; then - printf "%s✓ Passed%s: %s\n" "$_COLOR_PASSED" "$_COLOR_DEFAULT" "${test_name}" - else - printf "%s✓ Passed%s: %s (%s)\n" "$_COLOR_PASSED" "$_COLOR_DEFAULT" "${test_name}" "$*" - fi - fi -} - -function console_results::print_failure_message() { - local test_name=$1 - local failure_message=$2 - - printf "\ -${_COLOR_FAILED}✗ Failed${_COLOR_DEFAULT}: %s - ${_COLOR_FAINT}Message:${_COLOR_DEFAULT} ${_COLOR_BOLD}'%s'${_COLOR_DEFAULT}\n"\ - "${test_name}" "${failure_message}" -} - -function console_results::print_failed_test() { - local test_name=$1 - local expected=$2 - local failure_condition_message=$3 - local actual=$4 - - printf "\ -${_COLOR_FAILED}✗ Failed${_COLOR_DEFAULT}: %s - ${_COLOR_FAINT}Expected${_COLOR_DEFAULT} ${_COLOR_BOLD}'%s'${_COLOR_DEFAULT} - ${_COLOR_FAINT}%s${_COLOR_DEFAULT} ${_COLOR_BOLD}'%s'${_COLOR_DEFAULT}\n"\ - "${test_name}" "${expected}" "${failure_condition_message}" "${actual}" -} - -function console_results::print_failed_snapshot_test() { - local test_name=$1 - local snapshot_file=$2 - - printf "${_COLOR_FAILED}✗ Failed${_COLOR_DEFAULT}: %s - ${_COLOR_FAINT}Expected to match the snapshot${_COLOR_DEFAULT}\n" "$test_name" - - if command -v git > /dev//null; then - local actual_file - actual_file="${snapshot_file}.tmp" - echo "$actual" > "$actual_file" - git diff --no-index --word-diff --color=always "$snapshot_file" "$actual_file" 2>/dev/null\ - | tail -n +6 | sed "s/^/ /" - rm "$actual_file" - fi -} - -function console_results::print_skipped_test() { - local test_name=$1 - local reason=$2 - - printf "${_COLOR_SKIPPED}↷ Skipped${_COLOR_DEFAULT}: %s\n" "${test_name}" - - if [[ -n "$reason" ]]; then - printf "${_COLOR_FAINT} %s${_COLOR_DEFAULT}\n" "${reason}" - fi -} - -function console_results::print_incomplete_test() { - local test_name=$1 - local pending=$2 - - printf "${_COLOR_INCOMPLETE}✒ Incomplete${_COLOR_DEFAULT}: %s\n" "${test_name}" - - if [[ -n "$pending" ]]; then - printf "${_COLOR_FAINT} %s${_COLOR_DEFAULT}\n" "${pending}" - fi -} - -function console_results::print_snapshot_test() { - local test_name - test_name=$(helper::normalize_test_function_name "$1") - - printf "${_COLOR_SNAPSHOT}✎ Snapshot${_COLOR_DEFAULT}: %s\n" "${test_name}" -} - -function console_results::print_error_test() { - local test_name - test_name=$(helper::normalize_test_function_name "$1") - local error="$2" - - printf "${_COLOR_FAILED}✗ Failed${_COLOR_DEFAULT}: %s - ${_COLOR_FAINT}%s${_COLOR_DEFAULT}\n" "${test_name}" "${error}" -} -#!/bin/bash - -# shellcheck disable=SC2034 -_DEFAULT_PARALLEL_RUN=false -_DEFAULT_SHOW_HEADER=true -_DEFAULT_HEADER_ASCII_ART=false -_DEFAULT_SIMPLE_OUTPUT=false -_DEFAULT_STOP_ON_FAILURE=false -_DEFAULT_SHOW_EXECUTION_TIME=true -_DEFAULT_DEFAULT_PATH= -CAT="$(which cat)" -#!/bin/bash - -# Deprecated: Please use assert_equals instead. -function assertEquals() { - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - assert_equals "$1" "$2" "$label" -} - -# Deprecated: Please use assert_empty instead. -function assertEmpty() { - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - assert_empty "$1" "$label" -} - -# Deprecated: Please use assert_not_empty instead. -function assertNotEmpty() { - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - assert_not_empty "$1" "$label" -} - -# Deprecated: Please use assert_not_equals instead. -function assertNotEquals() { - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - assert_not_equals "$1" "$2" "$label" -} - -# Deprecated: Please use assert_contains instead. -function assertContains() { - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - assert_contains "$1" "$2" "$label" -} - -# Deprecated: Please use assert_not_contains instead. -function assertNotContains() { - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - assert_not_contains "$1" "$2" "$label" -} - -# Deprecated: Please use assert_matches instead. -function assertMatches() { - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - assert_matches "$1" "$2" "$label" -} - -# Deprecated: Please use assert_not_matches instead. -function assertNotMatches() { - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - assert_not_matches "$1" "$2" "$label" -} - -# Deprecated: Please use assert_exit_code instead. -function assertExitCode() { - local actual_exit_code=$? - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - assert_exit_code "$1" "$label" "$actual_exit_code" -} - -# Deprecated: Please use assert_successful_code instead. -function assertSuccessfulCode() { - local actual_exit_code=$? - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - assert_successful_code "$1" "$label" "$actual_exit_code" -} - -# Deprecated: Please use assert_general_error instead. -function assertGeneralError() { - local actual_exit_code=$? - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - assert_general_error "$1" "$label" "$actual_exit_code" -} - -# Deprecated: Please use assert_command_not_found instead. -function assertCommandNotFound() { - local actual_exit_code=$? - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - assert_command_not_found "{command}" "$label" "$actual_exit_code" -} - -# Deprecated: Please use assert_array_contains instead. -function assertArrayContains() { - assert_array_contains "$1" "${@:2}" -} - -# Deprecated: Please use assert_array_not_contains instead. -function assertArrayNotContains() { - assert_array_not_contains "$1" "${@:1}" -} -#!/bin/bash - -set -o allexport -# shellcheck source=/dev/null -[[ -f ".env" ]] && source .env set -set +o allexport - -if [[ -z "$PARALLEL_RUN" ]]; then - PARALLEL_RUN=$_DEFAULT_PARALLEL_RUN -fi - -if [[ -z "$SHOW_HEADER" ]]; then - SHOW_HEADER=$_DEFAULT_SHOW_HEADER -fi - -if [[ -z "$HEADER_ASCII_ART" ]]; then - HEADER_ASCII_ART=$_DEFAULT_HEADER_ASCII_ART -fi - -if [[ -z "$SIMPLE_OUTPUT" ]]; then - SIMPLE_OUTPUT=$_DEFAULT_SIMPLE_OUTPUT -fi - -if [[ -z "$STOP_ON_FAILURE" ]]; then - STOP_ON_FAILURE=$_DEFAULT_STOP_ON_FAILURE -fi - -if [[ -z "$SHOW_EXECUTION_TIME" ]]; then - SHOW_EXECUTION_TIME=$_DEFAULT_SHOW_EXECUTION_TIME -fi - -if [[ -z "$DEFAULT_PATH" ]]; then - DEFAULT_PATH=$_DEFAULT_DEFAULT_PATH -fi -#!/bin/bash - -declare -r BASHUNIT_GIT_REPO="https://github.com/TypedDevs/bashunit" - -# -# @param $1 string Eg: "test_some_logic_camelCase" -# -# @return string Eg: "Some logic camelCase" -# -function helper::normalize_test_function_name() { - local original_function_name="$1" - local result - - # Remove "test_" prefix - result="${original_function_name#test_}" - # Replace underscores with spaces - result="${result//_/ }" - # Remove "test" prefix - result="${result#test}" - # Capitalize the first letter - result="$(tr '[:lower:]' '[:upper:]' <<< "${result:0:1}")${result:1}" - - echo "$result" -} - -function helper::check_duplicate_functions() { - local script="$1" - - local filtered_lines - filtered_lines=$(grep -E '^\s*(function)?\s*test[a-zA-Z_][a-zA-Z_0-9]*\s*\(\)?\s*{' "$script") - - local function_names - function_names=$(echo "$filtered_lines" | awk '{gsub(/\(|\)/, ""); print $2}') - - local sorted_names - sorted_names=$(echo "$function_names" | sort) - - local duplicates - duplicates=$(echo "$sorted_names" | uniq -d) - if [ -n "$duplicates" ]; then - state::set_duplicated_functions_merged "$script" "$duplicates" - return 1 - fi -} - -# -# @param $1 string Eg: "prefix" -# @param $2 string Eg: "filter" -# @param $3 array Eg: "[fn1, fn2, prefix_filter_fn3, fn4, ...]" -# -# @return array Eg: "[prefix_filter_fn3, ...]" The filtered functions with prefix -# -function helper::get_functions_to_run() { - local prefix=$1 - local filter=$2 - local function_names=$3 - - local functions_to_run=() - - for function_name in $function_names; do - if [[ $function_name != ${prefix}* ]]; then - continue - fi - - local lower_case_function_name - lower_case_function_name=$(echo "$function_name" | tr '[:upper:]' '[:lower:]') - local lower_case_filter - lower_case_filter=$(echo "$filter" | tr '[:upper:]' '[:lower:]') - - if [[ -n $filter && $lower_case_function_name != *"$lower_case_filter"* ]]; then - continue - fi - - if [[ "${functions_to_run[*]}" =~ ${function_name} ]]; then - return 1 - fi - - functions_to_run+=("$function_name") - done - - echo "${functions_to_run[@]}" -} - -# -# @param $1 string Eg: "do_something" -# -function helper::execute_function_if_exists() { - "$1" 2>/dev/null -} - -# -# @param $1 string Eg: "do_something" -# -function helper::unset_if_exists() { - unset "$1" 2>/dev/null -} - -function helper::find_files_recursive() { - local path="$1" - - if [[ "$path" == *"*"* ]]; then - eval find "$path" -type f -name '*[tT]est.sh' | sort | uniq - elif [[ -d "$path" ]]; then - find "$path" -type f -name '*[tT]est.sh' | sort | uniq - else - echo "$path" - fi -} - -helper::normalize_variable_name() { - local input_string="$1" - local normalized_string - - normalized_string="${input_string//[^a-zA-Z0-9_]/_}" - - if [[ ! $normalized_string =~ ^[a-zA-Z_] ]]; then - normalized_string="_$normalized_string" - fi - - echo "$normalized_string" -} - -function helper::get_provider_data() { - local function_name="$1" - local script="$2" - local data_provider_function - - if [[ ! -f "$script" ]]; then - return - fi - - data_provider_function=$(\ - grep -B 1 "function $function_name()" "$script" |\ - grep "# data_provider " |\ - sed -E -e 's/\ *# data_provider (.*)$/\1/g'\ - ) - - if [[ -n "$data_provider_function" ]]; then - helper::execute_function_if_exists "$data_provider_function" - fi -} - -function helper::get_multi_invoker_function() { - local function_name="$1" - local script="$2" - local multi_invoker_function - - if [[ ! -f "$script" ]]; then - return - fi - - multi_invoker_function=$(\ - grep -B 1 "function $function_name()" "$script" |\ - grep "# multi_invoker " |\ - sed -E -e 's/\ *# multi_invoker (.*)$/\1/g'\ - ) - func_exists=$(declare -f "$multi_invoker_function") - if [[ -n "$func_exists" ]]; then - echo "$multi_invoker_function" - fi -} - -function helper::trim() { - local input_string="$1" - local trimmed_string - - trimmed_string="${input_string#"${input_string%%[![:space:]]*}"}" - trimmed_string="${trimmed_string%"${trimmed_string##*[![:space:]]}"}" - - echo "$trimmed_string" -} - -function helpers::get_latest_tag() { - git ls-remote --tags "$BASHUNIT_GIT_REPO" | - awk '{print $2}' | - sed 's|^refs/tags/||' | - sort -Vr | - head -n 1 -} -#!/bin/bash - -function runner::load_test_files() { - local filter=$1 - local files=("${@:2}") # Store all arguments starting from the second as an array - - if [[ "${#files[@]}" == 0 ]]; then - if [[ -n "${DEFAULT_PATH}" ]]; then - while IFS='' read -r line; do - files+=("$line"); - done < <(helper::find_files_recursive "$DEFAULT_PATH") - else - printf "%sError: At least one file path is required.%s\n" "${_COLOR_FAILED}" "${_COLOR_DEFAULT}" - console_header::print_help - exit 1 - fi - fi - - for test_file in "${files[@]}"; do - if [[ ! -f $test_file ]]; then - continue - fi - - # shellcheck source=/dev/null - source "$test_file" - - runner::run_set_up_before_script - runner::call_test_functions "$test_file" "$filter" - if [ "$PARALLEL_RUN" = true ] ; then - wait - fi - runner::run_tear_down_after_script - runner::clean_set_up_and_tear_down_after_script - done -} - -function runner::functions_for_script() { - local script="$1" - local all_function_names="$2" - - # Filter the names down to the ones defined in the script, sort them by line - # number - shopt -s extdebug - for f in $all_function_names; do - declare -F "$f" | grep "$script" - done | sort -k2 -n | awk '{print $1}' - shopt -u extdebug -} - -# Helper function for test authors to invoke a named test case -function run_test() { - runner::run_test "$function_name" "$@" -} - -function runner::call_test_functions() { - local script="$1" - local filter="$2" - local prefix="test" - # Use declare -F to list all function names - local all_function_names - all_function_names=$(declare -F | awk '{print $3}') - local filtered_functions - # shellcheck disable=SC2207 - filtered_functions=$(helper::get_functions_to_run "$prefix" "$filter" "$all_function_names") - - local functions_to_run - # shellcheck disable=SC2207 - functions_to_run=($(runner::functions_for_script "$script" "$filtered_functions")) - - if [[ "${#functions_to_run[@]}" -gt 0 ]]; then - if [[ "$SIMPLE_OUTPUT" == false ]]; then - echo "Running $script" - fi - - helper::check_duplicate_functions "$script" - - for function_name in "${functions_to_run[@]}"; do - local provider_data=() - IFS=" " read -r -a provider_data <<< "$(helper::get_provider_data "$function_name" "$script")" - - if [[ "${#provider_data[@]}" -gt 0 ]]; then - for data in "${provider_data[@]}"; do - runner::run_test "$function_name" "$data" - done - else - local multi_invoker - multi_invoker=$(helper::get_multi_invoker_function "$function_name" "$script") - if [[ -n "${multi_invoker}" ]]; then - helper::execute_function_if_exists "${multi_invoker}" - else - runner::run_test "$function_name" - fi - fi - - unset function_name - done - fi -} - -function runner::parse_execution_result() { - local execution_result=$1 - - local assertions_failed - assertions_failed=$(\ - echo "$execution_result" |\ - tail -n 1 |\ - sed -E -e 's/.*##ASSERTIONS_FAILED=([0-9]*)##.*/\1/g'\ - ) - - local assertions_passed - assertions_passed=$(\ - echo "$execution_result" |\ - tail -n 1 |\ - sed -E -e 's/.*##ASSERTIONS_PASSED=([0-9]*)##.*/\1/g'\ - ) - - local assertions_skipped - assertions_skipped=$(\ - echo "$execution_result" |\ - tail -n 1 |\ - sed -E -e 's/.*##ASSERTIONS_SKIPPED=([0-9]*)##.*/\1/g'\ - ) - - local assertions_incomplete - assertions_incomplete=$(\ - echo "$execution_result" |\ - tail -n 1 |\ - sed -E -e 's/.*##ASSERTIONS_INCOMPLETE=([0-9]*)##.*/\1/g'\ - ) - - local assertions_snapshot - assertions_snapshot=$(\ - echo "$execution_result" |\ - tail -n 1 |\ - sed -E -e 's/.*##ASSERTIONS_SNAPSHOT=([0-9]*)##.*/\1/g'\ - ) - - _ASSERTIONS_PASSED=$((_ASSERTIONS_PASSED + assertions_passed)) - _ASSERTIONS_FAILED=$((_ASSERTIONS_FAILED + assertions_failed)) - _ASSERTIONS_SKIPPED=$((_ASSERTIONS_SKIPPED + assertions_skipped)) - _ASSERTIONS_INCOMPLETE=$((_ASSERTIONS_INCOMPLETE + assertions_incomplete)) - _ASSERTIONS_SNAPSHOT=$((_ASSERTIONS_SNAPSHOT + assertions_snapshot)) -} - -function runner::run_test() { - local function_name="$1" - shift - local current_assertions_failed - current_assertions_failed="$(state::get_assertions_failed)" - local current_assertions_snapshot - current_assertions_snapshot="$(state::get_assertions_snapshot)" - local current_assertions_incomplete - current_assertions_incomplete="$(state::get_assertions_incomplete)" - local current_assertions_skipped - current_assertions_skipped="$(state::get_assertions_skipped)" - - exec 3>&1 - - local test_execution_result - test_execution_result=$( - state::initialize_assertions_count - runner::run_set_up - - "$function_name" "$@" 2>&1 1>&3 - - runner::run_tear_down - runner::clear_mocks - state::export_assertions_count - ) - - exec 3>&- - - runner::parse_execution_result "$test_execution_result" - - local runtime_error - runtime_error=$(\ - echo "$test_execution_result" |\ - head -n 1 |\ - sed -E -e 's/(.*)##ASSERTIONS_FAILED=.*/\1/g'\ - ) - - if [[ -n $runtime_error ]]; then - state::add_tests_failed - console_results::print_error_test "$function_name" "$runtime_error" - return - fi - - if [[ "$current_assertions_failed" != "$(state::get_assertions_failed)" ]]; then - state::add_tests_failed - - if [ "$STOP_ON_FAILURE" = true ]; then - exit 1 - fi - - return - fi - - if [[ "$current_assertions_snapshot" != "$(state::get_assertions_snapshot)" ]]; then - state::add_tests_snapshot - console_results::print_snapshot_test "$function_name" - return - fi - - if [[ "$current_assertions_incomplete" != "$(state::get_assertions_incomplete)" ]]; then - state::add_tests_incomplete - return - fi - - if [[ "$current_assertions_skipped" != "$(state::get_assertions_skipped)" ]]; then - state::add_tests_skipped - return - fi - - local label - label="$(helper::normalize_test_function_name "$function_name")" - - console_results::print_successful_test "${label}" "$@" - state::add_tests_passed -} - -function runner::run_set_up() { - helper::execute_function_if_exists 'set_up' -} - -function runner::run_set_up_before_script() { - helper::execute_function_if_exists 'set_up_before_script' -} - -function runner::run_tear_down() { - helper::execute_function_if_exists 'tear_down' -} - -function runner::clear_mocks() { - for i in "${!MOCKED_FUNCTIONS[@]}"; do - unmock "${MOCKED_FUNCTIONS[$i]}" - done -} - -function runner::run_tear_down_after_script() { - helper::execute_function_if_exists 'tear_down_after_script' -} - -function runner::clean_set_up_and_tear_down_after_script() { - helper::unset_if_exists 'set_up' - helper::unset_if_exists 'tear_down' - helper::unset_if_exists 'set_up_before_script' - helper::unset_if_exists 'tear_down_after_script' -} -#!/bin/bash - -function skip() { - local reason=$1 - local label - label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")" - - console_results::print_skipped_test "${label}" "${reason}" - - state::add_assertions_skipped -} - -function todo() { - local pending=$1 - local label - label="$(helper::normalize_test_function_name "${FUNCNAME[1]}")" - - console_results::print_incomplete_test "${label}" "${pending}" - - state::add_assertions_incomplete -} -#!/bin/bash - -_TESTS_PASSED=0 -_TESTS_FAILED=0 -_TESTS_SKIPPED=0 -_TESTS_INCOMPLETE=0 -_TESTS_SNAPSHOT=0 -_ASSERTIONS_PASSED=0 -_ASSERTIONS_FAILED=0 -_ASSERTIONS_SKIPPED=0 -_ASSERTIONS_INCOMPLETE=0 -_ASSERTIONS_SNAPSHOT=0 -_DUPLICATED_FUNCTION_NAMES="" -_FILE_WITH_DUPLICATED_FUNCTION_NAMES="" -_DUPLICATED_TEST_FUNCTIONS_FOUND=false - -function state::get_tests_passed() { - echo "$_TESTS_PASSED" -} - -function state::add_tests_passed() { - ((_TESTS_PASSED++)) || true -} - -function state::get_tests_failed() { - echo "$_TESTS_FAILED" -} - -function state::add_tests_failed() { - ((_TESTS_FAILED++)) || true -} - -function state::get_tests_skipped() { - echo "$_TESTS_SKIPPED" -} - -function state::add_tests_skipped() { - ((_TESTS_SKIPPED++)) || true -} - -function state::get_tests_incomplete() { - echo "$_TESTS_INCOMPLETE" -} - -function state::add_tests_incomplete() { - ((_TESTS_INCOMPLETE++)) || true -} - -function state::get_tests_snapshot() { - echo "$_TESTS_SNAPSHOT" -} - -function state::add_tests_snapshot() { - ((_TESTS_SNAPSHOT++)) || true -} - -function state::get_assertions_passed() { - echo "$_ASSERTIONS_PASSED" -} - -function state::add_assertions_passed() { - ((_ASSERTIONS_PASSED++)) || true -} - -function state::get_assertions_failed() { - echo "$_ASSERTIONS_FAILED" -} - -function state::add_assertions_failed() { - ((_ASSERTIONS_FAILED++)) || true -} - -function state::get_assertions_skipped() { - echo "$_ASSERTIONS_SKIPPED" -} - -function state::add_assertions_skipped() { - ((_ASSERTIONS_SKIPPED++)) || true -} - -function state::get_assertions_incomplete() { - echo "$_ASSERTIONS_INCOMPLETE" -} - -function state::add_assertions_incomplete() { - ((_ASSERTIONS_INCOMPLETE++)) || true -} - -function state::get_assertions_snapshot() { - echo "$_ASSERTIONS_SNAPSHOT" -} - -function state::add_assertions_snapshot() { - ((_ASSERTIONS_SNAPSHOT++)) || true -} - -function state::is_duplicated_test_functions_found() { - echo "$_DUPLICATED_TEST_FUNCTIONS_FOUND" -} - -function state::set_duplicated_test_functions_found() { - _DUPLICATED_TEST_FUNCTIONS_FOUND=true -} - -function state::get_duplicated_function_names() { - echo "$_DUPLICATED_FUNCTION_NAMES" -} - -function state::set_duplicated_function_names() { - _DUPLICATED_FUNCTION_NAMES="$1" -} - -function state::get_file_with_duplicated_function_names() { - echo "$_FILE_WITH_DUPLICATED_FUNCTION_NAMES" -} - -function state::set_file_with_duplicated_function_names() { - _FILE_WITH_DUPLICATED_FUNCTION_NAMES="$1" -} - -function state::set_duplicated_functions_merged() { - state::set_duplicated_test_functions_found - state::set_file_with_duplicated_function_names "$1" - state::set_duplicated_function_names "$2" - -} - -function state::initialize_assertions_count() { - _ASSERTIONS_PASSED=0 - _ASSERTIONS_FAILED=0 - _ASSERTIONS_SKIPPED=0 - _ASSERTIONS_INCOMPLETE=0 - _ASSERTIONS_SNAPSHOT=0 -} - -function state::export_assertions_count() { - echo "##ASSERTIONS_FAILED=$_ASSERTIONS_FAILED\ -##ASSERTIONS_PASSED=$_ASSERTIONS_PASSED\ -##ASSERTIONS_SKIPPED=$_ASSERTIONS_SKIPPED\ -##ASSERTIONS_INCOMPLETE=$_ASSERTIONS_INCOMPLETE\ -##ASSERTIONS_SNAPSHOT=$_ASSERTIONS_SNAPSHOT\ -##" -} -#!/bin/bash - -declare -a MOCKED_FUNCTIONS=() - -function unmock() { - local command=$1 - - for i in "${!MOCKED_FUNCTIONS[@]}"; do - if [[ "${MOCKED_FUNCTIONS[$i]}" == "$command" ]]; then - unset "MOCKED_FUNCTIONS[$i]" - unset -f "$command" - break - fi - done -} - -function mock() { - local command=$1 - shift - - if [[ $# -gt 0 ]]; then - eval "function $command() { $* ; }" - else - eval "function $command() { echo \"$($CAT)\" ; }" - fi - - export -f "${command?}" - - MOCKED_FUNCTIONS+=("$command") -} - -function spy() { - local command=$1 - local variable - variable="$(helper::normalize_variable_name "$command")" - - export "${variable}_times"=0 - export "${variable}_params" - - eval "function $command() { ${variable}_params=(\"\$*\"); ((${variable}_times++)) || true; }" - - export -f "${command?}" - - MOCKED_FUNCTIONS+=("$command") -} - -function assert_have_been_called() { - local command=$1 - local variable - variable="$(helper::normalize_variable_name "$command")" - local actual - actual="${variable}_times" - local label="${2:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ ${!actual} -eq 0 ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${command}" "to has been called" "once" - return - fi - - state::add_assertions_passed -} - -function assert_have_been_called_with() { - local expected=$1 - local command=$2 - local variable - variable="$(helper::normalize_variable_name "$command")" - local actual - actual="${variable}_params" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ "$expected" != "${!actual}" ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${expected}" "but got" "${!actual}" - return - fi - - state::add_assertions_passed -} - -function assert_have_been_called_times() { - local expected=$1 - local command=$2 - local variable - variable="$(helper::normalize_variable_name "$command")" - local actual - actual="${variable}_times" - local label="${3:-$(helper::normalize_test_function_name "${FUNCNAME[1]}")}" - - if [[ ${!actual} -ne $expected ]]; then - state::add_assertions_failed - console_results::print_failed_test "${label}" "${command}" "to has been called" "${expected} times" - return - fi - - state::add_assertions_passed -} -#!/bin/bash - -function upgrade::upgrade() { - local script_path - script_path="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - local latest_tag - latest_tag="$(helpers::get_latest_tag)" - - if [[ "$BASHUNIT_VERSION" == "$latest_tag" ]]; then - echo "> You are already on latest version" - return - fi - - echo "> Upgrading bashunit to latest version" - cd "$script_path" || exit - curl -L -J -o bashunit "https://github.com/TypedDevs/bashunit/releases/download/$latest_tag/bashunit" 2>/dev/null - chmod u+x "bashunit" - - echo "> bashunit upgraded successfully to latest version $latest_tag" -} -#!/bin/bash - -# shellcheck disable=SC2034 -declare -r BASHUNIT_VERSION="0.12.0" - -readonly BASHUNIT_ROOT_DIR="$(dirname "${BASH_SOURCE[0]}")" -export BASHUNIT_ROOT_DIR - - -############### -#### MAIN ##### -############### - -_FILTER="" -_FILES=() - -while [[ $# -gt 0 ]]; do - argument="$1" - case $argument in - -f|--filter) - _FILTER="$2" - shift - shift - ;; - -s|--simple) - SIMPLE_OUTPUT=true - shift - ;; - -v|--verbose) - SIMPLE_OUTPUT=false - shift - ;; - -S|--stop-on-failure) - STOP_ON_FAILURE=true - shift - ;; - -e|--env) - # shellcheck disable=SC1090 - source "$2" - shift - shift - ;; - --version) - console_header::print_version - trap '' EXIT && exit 0 - ;; - --upgrade) - upgrade::upgrade - trap '' EXIT && exit 0 - ;; - --help) - console_header::print_help - trap '' EXIT && exit 0 - ;; - *) - while IFS='' read -r line; do - _FILES+=("$line"); - done < <(helper::find_files_recursive "$argument") - shift - ;; - esac -done - -console_header::print_version_with_env -runner::load_test_files "$_FILTER" "${_FILES[@]}" -console_results::render_result - -exit 0 From 1e78af9c9d65af4b4c981ef4bbb56bb56884ae2c Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 16 Jun 2024 08:58:08 +0200 Subject: [PATCH 14/28] Discard changes to .github/workflows/e2e-tests.yml --- .github/workflows/e2e-tests.yml | 79 +++++++++++++++++---------------- 1 file changed, 40 insertions(+), 39 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index ae2edc9d60..2a82225b77 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -64,6 +64,16 @@ jobs: fail-fast: false matrix: include: + - script: | + cd e2e/result-cache-1 + echo -n > phpstan-baseline.neon + ../../bin/phpstan -vvv + patch -b src/Bar.php < patch-1.patch + cat baseline-1.neon > phpstan-baseline.neon + ../../bin/phpstan -vvv + mv src/Bar.php.orig src/Bar.php + echo -n > phpstan-baseline.neon + ../../bin/phpstan -vvv - script: | cd e2e/result-cache-2 echo -n > phpstan-baseline.neon @@ -174,6 +184,36 @@ jobs: - script: | cd e2e/env-int-key env 1=1 ../../bin/phpstan analyse test.php + - script: | + cd e2e/trait-caching + ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ + ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ + - script: | + cd e2e/trait-caching + ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ + patch -b data/TraitOne.php < TraitOne.patch + OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) + echo "$OUTPUT" + [ $(echo "$OUTPUT" | wc -l) -eq 1 ] + grep 'Method TraitsCachingIssue\\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" + - script: | + cd e2e/trait-caching + ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ + patch -b data/TraitTwo.php < TraitTwo.patch + OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) + echo "$OUTPUT" + [ $(echo "$OUTPUT" | wc -l) -eq 1 ] + grep 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" + - script: | + cd e2e/trait-caching + ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ + patch -b data/TraitOne.php < TraitOne.patch + patch -b data/TraitTwo.php < TraitTwo.patch + OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) + echo "$OUTPUT" + [ $(echo "$OUTPUT" | wc -l) -eq 2 ] + grep 'Method TraitsCachingIssue\\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" + grep 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" steps: - name: "Checkout" @@ -245,42 +285,3 @@ jobs: - name: "Test" run: ${{ matrix.script }} - - result-cache-e2e-tests-bashunit: - name: "Result cache E2E tests" - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - include: - - script: | - cd e2e/result-cache-1 - ../bashunit . - - script: | - cd e2e/trait-caching - ../bashunit . - - steps: - - name: "Checkout" - uses: actions/checkout@v4 - - - name: "Install PHP" - uses: "shivammathur/setup-php@v2" - with: - coverage: "none" - php-version: "8.1" - extensions: mbstring - ini-values: memory_limit=256M - - - name: "Install dependencies" - run: "composer install --no-interaction --no-progress" - - - name: "Patch PHPStan" - run: "patch src/Analyser/Error.php < e2e/PHPStanErrorPatch.patch" - - - name: "Install bashunit" - run: "curl -s https://bashunit.typeddevs.com/install.sh | bash -s e2e/ 0.12.0" - - - name: "Test" - run: "${{ matrix.script }}" From 06b377fb5d159a00334d181b55236eea2ece5f3e Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 16 Jun 2024 08:59:52 +0200 Subject: [PATCH 15/28] added assert.sh wrapper script --- .github/workflows/e2e-tests.yml | 11 +++++++---- e2e/assert.sh | 11 +++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) create mode 100755 e2e/assert.sh diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 2a82225b77..a53316db2b 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -195,7 +195,7 @@ jobs: OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" [ $(echo "$OUTPUT" | wc -l) -eq 1 ] - grep 'Method TraitsCachingIssue\\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" + assert.sh contains 'Method TraitsCachingIssue\\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" - script: | cd e2e/trait-caching ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ @@ -203,7 +203,7 @@ jobs: OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" [ $(echo "$OUTPUT" | wc -l) -eq 1 ] - grep 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" + assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" - script: | cd e2e/trait-caching ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ @@ -212,8 +212,8 @@ jobs: OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" [ $(echo "$OUTPUT" | wc -l) -eq 2 ] - grep 'Method TraitsCachingIssue\\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" - grep 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" + assert.sh contains 'Method TraitsCachingIssue\\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" + assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" steps: - name: "Checkout" @@ -233,6 +233,9 @@ jobs: - name: "Patch PHPStan" run: "patch src/Analyser/Error.php < e2e/PHPStanErrorPatch.patch" + - name: "Install bashunit" + run: "curl -s https://bashunit.typeddevs.com/install.sh | bash -s e2e/ 0.12.0" + - name: "Test" run: "${{ matrix.script }}" diff --git a/e2e/assert.sh b/e2e/assert.sh new file mode 100755 index 0000000000..e05453c504 --- /dev/null +++ b/e2e/assert.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +shopt -s expand_aliases # enable alias expansion (off by default in noninteractive shells) +alias exit=return # ...and alias 'exit' to 'return' +source bashunit --version > /dev/null 2>&1; +unalias exit # disable the alias... + +# the next line calls the function passed as the first parameter to the script. +# the remaining script arguments can be passed to this function. + +"assert_$1" $2 $3 $4 $5 $6 $7 $8 $9 From ec5d625f0b4da1289bdae6f18cffff04a5cdf159 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 16 Jun 2024 09:02:07 +0200 Subject: [PATCH 16/28] cleanup --- .github/workflows/e2e-tests.yml | 8 ++--- e2e/result-cache-1/test.sh | 17 ---------- e2e/trait-caching/test.sh | 57 --------------------------------- 3 files changed, 4 insertions(+), 78 deletions(-) delete mode 100644 e2e/result-cache-1/test.sh delete mode 100644 e2e/trait-caching/test.sh diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index a53316db2b..a1c7c9211c 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -195,7 +195,7 @@ jobs: OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" [ $(echo "$OUTPUT" | wc -l) -eq 1 ] - assert.sh contains 'Method TraitsCachingIssue\\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" + ../assert.sh contains 'Method TraitsCachingIssue\\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" - script: | cd e2e/trait-caching ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ @@ -203,7 +203,7 @@ jobs: OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" [ $(echo "$OUTPUT" | wc -l) -eq 1 ] - assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" + ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" - script: | cd e2e/trait-caching ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ @@ -212,8 +212,8 @@ jobs: OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" [ $(echo "$OUTPUT" | wc -l) -eq 2 ] - assert.sh contains 'Method TraitsCachingIssue\\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" - assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" + ../assert.sh contains 'Method TraitsCachingIssue\\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" + ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" steps: - name: "Checkout" diff --git a/e2e/result-cache-1/test.sh b/e2e/result-cache-1/test.sh deleted file mode 100644 index c44d1fd223..0000000000 --- a/e2e/result-cache-1/test.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -function test_result_cache1() { - echo -n > phpstan-baseline.neon - ../../bin/phpstan -vvv - assert_successful_code - - patch -b src/Bar.php < patch-1.patch - cat baseline-1.neon > phpstan-baseline.neon - ../../bin/phpstan -vvv - assert_successful_code - - mv src/Bar.php.orig src/Bar.php - echo -n > phpstan-baseline.neon - ../../bin/phpstan -vvv - assert_successful_code -} 2>&1 diff --git a/e2e/trait-caching/test.sh b/e2e/trait-caching/test.sh deleted file mode 100644 index f5e4e17de3..0000000000 --- a/e2e/trait-caching/test.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash - -function set_up() { - git restore data/ -} - -function test_trait_caching() { - ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ - assert_successful_code - - ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ - assert_successful_code -} 2>&1 - -function test_trait_caching_one_changed() { - ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ - assert_successful_code - - patch -b data/TraitOne.php < TraitOne.patch - - OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) - echo "$OUTPUT" - [ $(echo "$OUTPUT" | wc -l) -eq 1 ] - assert_successful_code - - assert_contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" -} 2>&1 - -function test_trait_caching_two_changed() { - ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ - assert_successful_code - - patch -b data/TraitTwo.php < TraitTwo.patch - - OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) - echo "$OUTPUT" - [ $(echo "$OUTPUT" | wc -l) -eq 1 ] - assert_successful_code - - assert_contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" -} 2>&1 - -function test_trait_caching_both_changed() { - ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ - assert_successful_code - - patch -b data/TraitOne.php < TraitOne.patch - patch -b data/TraitTwo.php < TraitTwo.patch - - OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) - echo "$OUTPUT" - [ $(echo "$OUTPUT" | wc -l) -eq 2 ] - assert_successful_code - - assert_contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" - assert_contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" -} 2>&1 From eb0b8934847215a630c57b16dc7cc18f8bbba5c5 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 16 Jun 2024 09:03:47 +0200 Subject: [PATCH 17/28] Update e2e-tests.yml --- .github/workflows/e2e-tests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index a1c7c9211c..0b9dc2f590 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -195,7 +195,7 @@ jobs: OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" [ $(echo "$OUTPUT" | wc -l) -eq 1 ] - ../assert.sh contains 'Method TraitsCachingIssue\\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" + ../assert.sh contains 'Method TraitsCachingIssue\\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" - script: | cd e2e/trait-caching ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ @@ -203,7 +203,7 @@ jobs: OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" [ $(echo "$OUTPUT" | wc -l) -eq 1 ] - ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" + ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" - script: | cd e2e/trait-caching ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ @@ -212,8 +212,8 @@ jobs: OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" [ $(echo "$OUTPUT" | wc -l) -eq 2 ] - ../assert.sh contains 'Method TraitsCachingIssue\\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" - ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' <<< "$OUTPUT" + ../assert.sh contains 'Method TraitsCachingIssue\\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" + ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" steps: - name: "Checkout" From 4b69f4571569d8e602594a719dfe4d2feb984767 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 16 Jun 2024 09:11:49 +0200 Subject: [PATCH 18/28] Update assert.sh --- e2e/assert.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/e2e/assert.sh b/e2e/assert.sh index e05453c504..9c8f38c854 100755 --- a/e2e/assert.sh +++ b/e2e/assert.sh @@ -9,3 +9,9 @@ unalias exit # disable the alias... # the remaining script arguments can be passed to this function. "assert_$1" $2 $3 $4 $5 $6 $7 $8 $9 + +if [[ "$(state::get_tests_failed)" -gt 0 ]] || [[ "$(state::get_assertions_failed)" -gt 0 ]]; then + exit 1 +fi + +exit 0 From 4cb78ea852b8f4a5b840cd2d9cb43323799b9960 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 16 Jun 2024 09:12:41 +0200 Subject: [PATCH 19/28] Update e2e-tests.yml --- .github/workflows/e2e-tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 0b9dc2f590..a270e70945 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -195,7 +195,7 @@ jobs: OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" [ $(echo "$OUTPUT" | wc -l) -eq 1 ] - ../assert.sh contains 'Method TraitsCachingIssue\\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" + ../assert.sh contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" - script: | cd e2e/trait-caching ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ @@ -212,7 +212,7 @@ jobs: OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" [ $(echo "$OUTPUT" | wc -l) -eq 2 ] - ../assert.sh contains 'Method TraitsCachingIssue\\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" + ../assert.sh contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" steps: From 67d6f64e2ccde1063a6c1e654f033b69edb16a6d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 16 Jun 2024 09:18:22 +0200 Subject: [PATCH 20/28] Update assert.sh --- e2e/assert.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/e2e/assert.sh b/e2e/assert.sh index 9c8f38c854..aa8d5610e8 100755 --- a/e2e/assert.sh +++ b/e2e/assert.sh @@ -10,8 +10,10 @@ unalias exit # disable the alias... "assert_$1" $2 $3 $4 $5 $6 $7 $8 $9 +AsserExit=$? + if [[ "$(state::get_tests_failed)" -gt 0 ]] || [[ "$(state::get_assertions_failed)" -gt 0 ]]; then exit 1 fi -exit 0 +exit $AsserExit From 1d27a21f73c975a1b353d8e1c7bffca2ba26f1aa Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 16 Jun 2024 09:26:56 +0200 Subject: [PATCH 21/28] fix --- .gitignore | 1 + e2e/assert.sh | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index f138e3cb50..47f19ba656 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ /tests/.phpunit.result.cache /tests/PHPStan/Reflection/data/golden/ tmp/.memory_limit +e2e/bashunit diff --git a/e2e/assert.sh b/e2e/assert.sh index aa8d5610e8..fe64ad3ec2 100755 --- a/e2e/assert.sh +++ b/e2e/assert.sh @@ -8,12 +8,12 @@ unalias exit # disable the alias... # the next line calls the function passed as the first parameter to the script. # the remaining script arguments can be passed to this function. -"assert_$1" $2 $3 $4 $5 $6 $7 $8 $9 +set -e -AsserExit=$? +"assert_$1" $2 $3 $4 $5 $6 $7 $8 $9 if [[ "$(state::get_tests_failed)" -gt 0 ]] || [[ "$(state::get_assertions_failed)" -gt 0 ]]; then exit 1 fi -exit $AsserExit +exit 0 From 0c740f79659cf6e3b0ae645ec2b1b7d6dd20a4e3 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 16 Jun 2024 09:32:12 +0200 Subject: [PATCH 22/28] debug --- e2e/assert.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/e2e/assert.sh b/e2e/assert.sh index fe64ad3ec2..1fb2bc4684 100755 --- a/e2e/assert.sh +++ b/e2e/assert.sh @@ -1,8 +1,17 @@ #!/bin/bash +# Wrapper script around bashunit, to make assertion functions available for cli executable testing. +# +# Usage examples: +# ./assert.sh contains 'ab' "$VARIABLE" +# ./assert.sh equals 'ab' "$VARIABLE" +# +# Fin all supported bashunit assertions on https://bashunit.typeddevs.com/assertions + + shopt -s expand_aliases # enable alias expansion (off by default in noninteractive shells) alias exit=return # ...and alias 'exit' to 'return' -source bashunit --version > /dev/null 2>&1; +source bashunit --version; unalias exit # disable the alias... # the next line calls the function passed as the first parameter to the script. From e17509865ef3d2825af03956d6d7f7a50878e64a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 16 Jun 2024 09:49:17 +0200 Subject: [PATCH 23/28] fix --- e2e/assert.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/e2e/assert.sh b/e2e/assert.sh index 1fb2bc4684..e5bd8c96cf 100755 --- a/e2e/assert.sh +++ b/e2e/assert.sh @@ -6,12 +6,13 @@ # ./assert.sh contains 'ab' "$VARIABLE" # ./assert.sh equals 'ab' "$VARIABLE" # -# Fin all supported bashunit assertions on https://bashunit.typeddevs.com/assertions +# Find all supported bashunit assertions on https://bashunit.typeddevs.com/assertions +__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" shopt -s expand_aliases # enable alias expansion (off by default in noninteractive shells) alias exit=return # ...and alias 'exit' to 'return' -source bashunit --version; +source ${__dir}/bashunit --version > /dev/null 2>&1; unalias exit # disable the alias... # the next line calls the function passed as the first parameter to the script. From fe5ccbe443bd3a7facccd6f0a49647b8f3cd5910 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 16 Jun 2024 09:52:41 +0200 Subject: [PATCH 24/28] fix --- .github/workflows/e2e-tests.yml | 6 +++--- e2e/assert.sh | 8 ++++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index a270e70945..c208453587 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -203,7 +203,7 @@ jobs: OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" [ $(echo "$OUTPUT" | wc -l) -eq 1 ] - ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" + ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' '$OUTPUT' - script: | cd e2e/trait-caching ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ @@ -212,8 +212,8 @@ jobs: OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" [ $(echo "$OUTPUT" | wc -l) -eq 2 ] - ../assert.sh contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" - ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" + ../assert.sh contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' '$OUTPUT' + ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' '$OUTPUT' steps: - name: "Checkout" diff --git a/e2e/assert.sh b/e2e/assert.sh index e5bd8c96cf..bcde5879f1 100755 --- a/e2e/assert.sh +++ b/e2e/assert.sh @@ -10,18 +10,22 @@ __dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# import all bashunit functions into the current process shopt -s expand_aliases # enable alias expansion (off by default in noninteractive shells) alias exit=return # ...and alias 'exit' to 'return' source ${__dir}/bashunit --version > /dev/null 2>&1; unalias exit # disable the alias... -# the next line calls the function passed as the first parameter to the script. -# the remaining script arguments can be passed to this function. +# return non-zero exit code if unknown assertion is called set -e +# the next line calls the function passed as the first parameter to the script. +# the remaining script arguments can be passed to this function. "assert_$1" $2 $3 $4 $5 $6 $7 $8 $9 +# return non-zero exit code when assertion fails if [[ "$(state::get_tests_failed)" -gt 0 ]] || [[ "$(state::get_assertions_failed)" -gt 0 ]]; then exit 1 fi From 7d37b275279dec5fbc5ea0f206c733d2fdfe32ab Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 16 Jun 2024 10:02:11 +0200 Subject: [PATCH 25/28] fix --- .github/workflows/e2e-tests.yml | 6 +++--- e2e/assert.sh | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index c208453587..a270e70945 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -203,7 +203,7 @@ jobs: OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" [ $(echo "$OUTPUT" | wc -l) -eq 1 ] - ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' '$OUTPUT' + ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" - script: | cd e2e/trait-caching ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ @@ -212,8 +212,8 @@ jobs: OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" [ $(echo "$OUTPUT" | wc -l) -eq 2 ] - ../assert.sh contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' '$OUTPUT' - ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' '$OUTPUT' + ../assert.sh contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" + ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" steps: - name: "Checkout" diff --git a/e2e/assert.sh b/e2e/assert.sh index bcde5879f1..277c615b87 100755 --- a/e2e/assert.sh +++ b/e2e/assert.sh @@ -23,7 +23,7 @@ set -e # the next line calls the function passed as the first parameter to the script. # the remaining script arguments can be passed to this function. -"assert_$1" $2 $3 $4 $5 $6 $7 $8 $9 +"assert_$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" # return non-zero exit code when assertion fails if [[ "$(state::get_tests_failed)" -gt 0 ]] || [[ "$(state::get_assertions_failed)" -gt 0 ]]; then From 51fe9c57222b3040368d4c3e2fa397d6ae1580ef Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 16 Jun 2024 10:06:44 +0200 Subject: [PATCH 26/28] use equals assertion --- .github/workflows/e2e-tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index a270e70945..e08de2241e 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -194,7 +194,7 @@ jobs: patch -b data/TraitOne.php < TraitOne.patch OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" - [ $(echo "$OUTPUT" | wc -l) -eq 1 ] + ../assert.sh equals `echo "$OUTPUT" | wc -l` 1 ../assert.sh contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" - script: | cd e2e/trait-caching @@ -202,7 +202,7 @@ jobs: patch -b data/TraitTwo.php < TraitTwo.patch OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" - [ $(echo "$OUTPUT" | wc -l) -eq 1 ] + ../assert.sh equals `echo "$OUTPUT" | wc -l` 1 ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" - script: | cd e2e/trait-caching @@ -211,7 +211,7 @@ jobs: patch -b data/TraitTwo.php < TraitTwo.patch OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" - [ $(echo "$OUTPUT" | wc -l) -eq 2 ] + ../assert.sh equals `echo "$OUTPUT" | wc -l` 2 ../assert.sh contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" From 17479bab2d40977a0c28f31b67387b16264e348a Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 17 Jun 2024 17:38:31 +0200 Subject: [PATCH 27/28] try bashunit beta --- .github/workflows/e2e-tests.yml | 16 ++++++++-------- e2e/assert.sh | 33 --------------------------------- 2 files changed, 8 insertions(+), 41 deletions(-) delete mode 100755 e2e/assert.sh diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index e08de2241e..8785fc2193 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -194,16 +194,16 @@ jobs: patch -b data/TraitOne.php < TraitOne.patch OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" - ../assert.sh equals `echo "$OUTPUT" | wc -l` 1 - ../assert.sh contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" + ../bashunit.sh -a line_count 1 "$OUTPUT" + ../bashunit.sh -a contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" - script: | cd e2e/trait-caching ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ patch -b data/TraitTwo.php < TraitTwo.patch OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" - ../assert.sh equals `echo "$OUTPUT" | wc -l` 1 - ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" + ../bashunit.sh -a line_count 1 "$OUTPUT" + ../bashunit.sh -a contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" - script: | cd e2e/trait-caching ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ @@ -211,9 +211,9 @@ jobs: patch -b data/TraitTwo.php < TraitTwo.patch OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" - ../assert.sh equals `echo "$OUTPUT" | wc -l` 2 - ../assert.sh contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" - ../assert.sh contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" + ../bashunit.sh -a line_count 2 "$OUTPUT" + ../bashunit.sh -a contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" + ../bashunit.sh -a contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" steps: - name: "Checkout" @@ -234,7 +234,7 @@ jobs: run: "patch src/Analyser/Error.php < e2e/PHPStanErrorPatch.patch" - name: "Install bashunit" - run: "curl -s https://bashunit.typeddevs.com/install.sh | bash -s e2e/ 0.12.0" + run: "curl -s https://bashunit.typeddevs.com/install.sh | bash -s e2e/ beta" - name: "Test" run: "${{ matrix.script }}" diff --git a/e2e/assert.sh b/e2e/assert.sh deleted file mode 100755 index 277c615b87..0000000000 --- a/e2e/assert.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -# Wrapper script around bashunit, to make assertion functions available for cli executable testing. -# -# Usage examples: -# ./assert.sh contains 'ab' "$VARIABLE" -# ./assert.sh equals 'ab' "$VARIABLE" -# -# Find all supported bashunit assertions on https://bashunit.typeddevs.com/assertions - -__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - - -# import all bashunit functions into the current process -shopt -s expand_aliases # enable alias expansion (off by default in noninteractive shells) -alias exit=return # ...and alias 'exit' to 'return' -source ${__dir}/bashunit --version > /dev/null 2>&1; -unalias exit # disable the alias... - - -# return non-zero exit code if unknown assertion is called -set -e - -# the next line calls the function passed as the first parameter to the script. -# the remaining script arguments can be passed to this function. -"assert_$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" - -# return non-zero exit code when assertion fails -if [[ "$(state::get_tests_failed)" -gt 0 ]] || [[ "$(state::get_assertions_failed)" -gt 0 ]]; then - exit 1 -fi - -exit 0 From 93ef01ab741fafabc86855660704d368e9e4a810 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 17 Jun 2024 17:40:33 +0200 Subject: [PATCH 28/28] fix typo --- .github/workflows/e2e-tests.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 8785fc2193..12ca1ea57d 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -194,16 +194,16 @@ jobs: patch -b data/TraitOne.php < TraitOne.patch OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" - ../bashunit.sh -a line_count 1 "$OUTPUT" - ../bashunit.sh -a contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" + ../bashunit -a line_count 1 "$OUTPUT" + ../bashunit -a contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" - script: | cd e2e/trait-caching ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ patch -b data/TraitTwo.php < TraitTwo.patch OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" - ../bashunit.sh -a line_count 1 "$OUTPUT" - ../bashunit.sh -a contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" + ../bashunit -a line_count 1 "$OUTPUT" + ../bashunit -a contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" - script: | cd e2e/trait-caching ../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ @@ -211,9 +211,9 @@ jobs: patch -b data/TraitTwo.php < TraitTwo.patch OUTPUT=$(../../bin/phpstan analyze --no-progress --level 8 --error-format raw data/ || true) echo "$OUTPUT" - ../bashunit.sh -a line_count 2 "$OUTPUT" - ../bashunit.sh -a contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" - ../bashunit.sh -a contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" + ../bashunit -a line_count 2 "$OUTPUT" + ../bashunit -a contains 'Method TraitsCachingIssue\TestClassUsingTrait::doBar() should return stdClass but returns Exception.' "$OUTPUT" + ../bashunit -a contains 'Method class@anonymous/TestClassUsingTrait.php:20::doBar() should return stdClass but returns Exception.' "$OUTPUT" steps: - name: "Checkout"