From 26c9f33bc61a70d2a75a4d433e797cd3d17d3e32 Mon Sep 17 00:00:00 2001 From: leocavalcante Date: Sun, 22 Oct 2023 21:31:15 -0300 Subject: [PATCH 1/6] tests: Setup --- lib/bashunit | 1451 ++++++++++++++++++++++++++++++++++++++++++++++++++ tests.sh | 14 + 2 files changed, 1465 insertions(+) create mode 100755 lib/bashunit create mode 100644 tests.sh diff --git a/lib/bashunit b/lib/bashunit new file mode 100755 index 0000000..66201d6 --- /dev/null +++ b/lib/bashunit @@ -0,0 +1,1451 @@ +#!/bin/bash + +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_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_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 + +#!/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 + +# shellcheck disable=SC2034 +_COLOR_DEFAULT=$'\e[0m' +_COLOR_BOLD=$'\e[1m' +_COLOR_FAINT=$'\e[2m' +_COLOR_FAILED=$'\e[31m' +_COLOR_PASSED=$'\e[32m' +_COLOR_SKIPPED=$'\e[33m' +_COLOR_INCOMPLETE=$'\e[36m' +_COLOR_RETURN_ERROR=$'\e[41m' +_COLOR_RETURN_SUCCESS=$'\e[42m' +_COLOR_RETURN_SKIPPED=$'\e[43m' +_COLOR_RETURN_INCOMPLETE=$'\e[46m' +#!/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. + + --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}" + 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_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_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_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_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 "%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 "%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 "%s%s%s\n" "$_COLOR_RETURN_SKIPPED" "Some tests skipped" "$_COLOR_DEFAULT" + console_results::print_execution_time + exit 0 + fi + + printf "%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 [[ "$_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 + printf "%s✓ Passed%s: %s\n" "$_COLOR_PASSED" "$_COLOR_DEFAULT" "${test_name}" + fi +} + +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_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_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 +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 +#!/bin/bash + +# +# @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_test_functions_found + 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() { + local function_name=$1 + + if declare -F | awk '{print $3}' | grep -Eq "^${function_name}$"; then + "$function_name" + fi +} + +# +# @param $1 string Eg: "do_something" +# +function helper::unset_if_exists() { + local function_name=$1 + + if declare -F | awk '{print $3}' | grep -Eq "^${function_name}$"; then + unset "$function_name" + fi +} + +function helper::find_files_recursive() { + local path="$1" + + if [[ -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 +} +#!/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 + printf "%sError: At least one file path is required.%s\n" "${_COLOR_FAILED}" "${_COLOR_DEFAULT}" + console_header::print_help + exit 1 + 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::call_test_functions() { + local script="$1" + local filter="$2" + local prefix="test" + # Use declare -F to list all function names + local function_names + function_names=$(declare -F | awk '{print $3}') + local functions_to_run + # shellcheck disable=SC2207 + functions_to_run=($(helper::get_functions_to_run "$prefix" "$filter" "$function_names")) + + 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 + runner::run_test "$function_name" + 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'\ + ) + + _ASSERTIONS_PASSED=$((_ASSERTIONS_PASSED + assertions_passed)) + _ASSERTIONS_FAILED=$((_ASSERTIONS_FAILED + assertions_failed)) + _ASSERTIONS_SKIPPED=$((_ASSERTIONS_SKIPPED + assertions_skipped)) + _ASSERTIONS_INCOMPLETE=$((_ASSERTIONS_INCOMPLETE + assertions_incomplete)) +} + +function runner::run_test() { + local function_name="$1" + local data="$2" + local current_assertions_failed + current_assertions_failed="$(state::get_assertions_failed)" + 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" "$data" 2>&1 1>&3 + + runner::run_tear_down + 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_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 'setUp' # Deprecated: please use set_up instead. + helper::execute_function_if_exists 'set_up' +} + +function runner::run_set_up_before_script() { + helper::execute_function_if_exists 'setUpBeforeScript' # Deprecated: please use set_up_before_script instead. + helper::execute_function_if_exists 'set_up_before_script' +} + +function runner::run_tear_down() { + helper::execute_function_if_exists 'tearDown' # Deprecated: please use tear_down instead. + helper::execute_function_if_exists 'tear_down' +} + +function runner::run_tear_down_after_script() { + helper::execute_function_if_exists 'tearDownAfterScript' # Deprecated: please use tear_down_after_script instead. + helper::execute_function_if_exists 'tear_down_after_script' +} + +function runner::clean_set_up_and_tear_down_after_script() { + helper::unset_if_exists 'setUp' # Deprecated: please use set_up instead. + helper::unset_if_exists 'set_up' + helper::unset_if_exists 'tearDown' # Deprecated: please use tear_down instead. + helper::unset_if_exists 'tear_down' + helper::unset_if_exists 'setUpBeforeScript' # Deprecated: please use set_up_before_script instead. + helper::unset_if_exists 'set_up_before_script' + helper::unset_if_exists 'tearDownAfterScript' # Deprecated: please use tear_down_after_script instead. + 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 +_ASSERTIONS_PASSED=0 +_ASSERTIONS_FAILED=0 +_ASSERTIONS_SKIPPED=0 +_ASSERTIONS_INCOMPLETE=0 +_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_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::is_duplicated_test_functions_found() { + echo "$_DUPLICATED_TEST_FUNCTIONS_FOUND" +} + +function state::set_duplicated_test_functions_found() { + _DUPLICATED_TEST_FUNCTIONS_FOUND=true +} + +function state::initialize_assertions_count() { + _ASSERTIONS_PASSED=0 + _ASSERTIONS_FAILED=0 + _ASSERTIONS_SKIPPED=0 + _ASSERTIONS_INCOMPLETE=0 +} + +function state::export_assertions_count() { + echo "##ASSERTIONS_FAILED=$_ASSERTIONS_FAILED\ +##ASSERTIONS_PASSED=$_ASSERTIONS_PASSED\ +##ASSERTIONS_SKIPPED=$_ASSERTIONS_SKIPPED\ +##ASSERTIONS_INCOMPLETE=$_ASSERTIONS_INCOMPLETE\ +##" +} +#!/bin/bash + +function mock() { + local command=$1 + shift + + if [[ $# -gt 0 ]]; then + eval "function $command() { $* ; }" + else + eval "function $command() { echo \"$($CAT)\" ; }" + fi + + export -f "${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?}" +} + +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 + +# shellcheck disable=SC2034 +declare -r BASHUNIT_VERSION="0.9.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 + ;; + --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 diff --git a/tests.sh b/tests.sh new file mode 100644 index 0000000..163cee1 --- /dev/null +++ b/tests.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +HFCTL=hfctl +DIR_NAME=test-project + +test_create() { + $HFCTL create $DIR_NAME + assert_directory_exists $DIR_NAME + assert_file_exists $DIR_NAME/bin/hyperf.php +} + +tear_down() { + rm -rf $DIR_NAME +} From c05a315d1ac87995ec0e9f409495ded65a7fcf7b Mon Sep 17 00:00:00 2001 From: leocavalcante Date: Sun, 22 Oct 2023 21:38:38 -0300 Subject: [PATCH 2/6] chore(docs): Contributing --- CONTRIBUTING.md | 10 ++++++++++ README.md | 4 ++++ 2 files changed, 14 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..425e3ad --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# Contributing + +Hi, feel free to send any issues or pull requests. + +## Testing + +This project uses [bashunit](https://bashunit.typeddevs.com/) for testing. Tests are located at `./tests.sh` and you can run them with: +```shell +./lib/bashunit tests.sh +``` diff --git a/README.md b/README.md index 1d6b1d8..258a39b 100644 --- a/README.md +++ b/README.md @@ -31,3 +31,7 @@ git pull | `hfctl watch` | Starts the Hyperf server with hyperf/watcher | | `hfctl bin or cmd or command`| Runs Hyperf commands | | `hfctl logs` | Shows the Hyperf container logs | + +## Contributing + +Please visit [CONTRIBUTING.md](CONTRIBUTING.md). From 264274d2c874850d049b9e7671d697b8856f6b86 Mon Sep 17 00:00:00 2001 From: leocavalcante Date: Tue, 24 Oct 2023 09:41:09 -0300 Subject: [PATCH 3/6] refactor: test suite --- CONTRIBUTING.md | 4 +-- lib/bashunit => bashunit | 0 hfctl | 30 ++++++++++---------- test | 60 ++++++++++++++++++++++++++++++++++++++++ tests.sh | 14 ---------- 5 files changed, 77 insertions(+), 31 deletions(-) rename lib/bashunit => bashunit (100%) create mode 100755 test delete mode 100644 tests.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 425e3ad..bbb5fd3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Hi, feel free to send any issues or pull requests. ## Testing -This project uses [bashunit](https://bashunit.typeddevs.com/) for testing. Tests are located at `./tests.sh` and you can run them with: +This project uses [bashunit](https://bashunit.typeddevs.com/) for testing. Tests are located at `./test` and you can run them with: ```shell -./lib/bashunit tests.sh +./test ``` diff --git a/lib/bashunit b/bashunit similarity index 100% rename from lib/bashunit rename to bashunit diff --git a/hfctl b/hfctl index 72af322..48f81d0 100755 --- a/hfctl +++ b/hfctl @@ -15,17 +15,17 @@ help() { echo -e " hfctl [arguments]" echo -e "" echo -e "\033[1;33mCommands:\033[0m" - echo -e " \033[1;32mhelp\033[0m Shows this help message" - echo -e " \033[1;32mversion\033[0m Displays hfctl and image version" - echo -e " \033[1;32mself-update\033[0m Updates hfctl to the latest version" - echo -e " \033[1;32mcreate [project-name]\033[0m Creates a new Hyperf project" - echo -e " \033[1;32mcomposer [subcommand]\033[0m Runs composer commands" - echo -e " \033[1;32mstart [port]\033[0m Starts the Hyperf server \033[1;37m(default port 9501)\033[0m" - echo -e " \033[1;32mstop\033[0m Stops the Hyperf server" - echo -e " \033[1;32mrestart\033[0m Restart the Hyperf server" - echo -e " \033[1;32mwatch [port]\033[0m Starts the Hyperf watcher \033[1;37m(default port 9501)\033[0m" - echo -e " \033[1;32mbin | cmd | command\033[0m Runs Hyperf commands" - echo -e " \033[1;32mlogs\033[0m Shows the Hyperf container logs" + echo -e " \033[1;32mhelp\033[0m Shows this help message" + echo -e " \033[1;32mversion\033[0m Displays hfctl and image version" + echo -e " \033[1;32mself-update\033[0m Updates hfctl to the latest version" + echo -e " \033[1;32mcreate [-it]\033[0m Creates a new Hyperf project \033[1;37m(use -it for interactive mode)\033[0m" + echo -e " \033[1;32mcomposer \033[0m Runs composer commands" + echo -e " \033[1;32mstart [port]\033[0m Starts the Hyperf server \033[1;37m(default port 9501)\033[0m" + echo -e " \033[1;32mstop\033[0m Stops the Hyperf server" + echo -e " \033[1;32mrestart\033[0m Restart the Hyperf server" + echo -e " \033[1;32mwatch [port]\033[0m Starts the Hyperf watcher \033[1;37m(default port 9501)\033[0m" + echo -e " \033[1;32mbin (cmd or command) \033[0m Runs Hyperf commands" + echo -e " \033[1;32mlogs\033[0m Shows the Hyperf container logs" echo -e "" } @@ -43,7 +43,7 @@ self-update() { } create() { - docker-run -it composer create-project hyperf/hyperf-skeleton:dev-master $1 + docker-run ${2:--i} composer create-project hyperf/hyperf-skeleton:dev-master $1 sudo chown -R $UID:$UID $1 } @@ -55,14 +55,14 @@ composer() { start() { pre-check echo $(docker-run -dp${1:-9501}:9501 php bin/hyperf.php start) > $HFCID - logs } watch() { pre-check - composer show hyperf/watcher + if [[ $(composer show hyperf/watcher) == *"\"hyperf/watcher\" not found"* ]]; then + docker-run -t composer require --dev hyperf/watcher + fi echo $(docker-run -dp${1:-9501}:9501 php bin/hyperf.php server:watch) > $HFCID - logs } stop() { diff --git a/test b/test new file mode 100755 index 0000000..8cd829c --- /dev/null +++ b/test @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +HFCTL=$(dirname $(realpath $0))/hfctl +HFDIR=test-project +HFCID=/tmp/hfctl.cid + +function set_up_before_script() { + if [ -d $HFDIR ]; then + sudo rm -rf $HFDIR + fi + $HFCTL create $HFDIR +} + +function tear_down_after_script() { + sudo rm -rf $HFDIR +} + +function test_should_test_project_been_created() { + assert_file_exists $HFDIR/vendor/autoload.php +} + +function test_should_pre_check_fail_if_not_in_project_directory() { + assert_contains "Are you in a Hyperf project?" "$($HFCTL pre-check)" + assert_exit_code 1 "$($HFCTL pre-check)" +} + +function test_should_pre_check_pass_if_in_project_directory() { + cd $HFDIR + assert_empty "$($HFCTL pre-check)" +} + +function test_should_composer_commands_be_available() { + cd $HFDIR + assert_contains "composer.json is valid" "$($HFCTL composer validate)" +} + +function test_should_start_project_at_port_9501() { + cd $HFDIR + $HFCTL start + sleep 1 + assert_file_exists $HFCID + assert_equals "{\"method\":\"GET\",\"message\":\"Hello Hyperf.\"}" "$(curl -s http://localhost:9501)" + assert_equals "$(cat $HFCID)" "$($HFCTL stop)" + assert_file_not_exists $HFCID +} + +function test_should_install_watcher_and_watch_for_changes() { + cd $HFDIR + assert_contains "Running composer update hyperf/watcher" "$($HFCTL watch)" + sleep 1 + assert_file_exists $HFCID + assert_contains "Hot reload watcher for Hyperf" "$($HFCTL composer show hyperf/watcher)" + assert_equals "{\"method\":\"GET\",\"message\":\"Hello Hyperf.\"}" "$(curl -s http://localhost:9501)" + assert_equals "$(cat $HFCID)" "$($HFCTL stop)" + assert_file_not_exists $HFCID +} + +if [[ $0 == "./test" ]]; then + ./bashunit ./test +fi diff --git a/tests.sh b/tests.sh deleted file mode 100644 index 163cee1..0000000 --- a/tests.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -HFCTL=hfctl -DIR_NAME=test-project - -test_create() { - $HFCTL create $DIR_NAME - assert_directory_exists $DIR_NAME - assert_file_exists $DIR_NAME/bin/hyperf.php -} - -tear_down() { - rm -rf $DIR_NAME -} From 52ab11d8c2d6721d6ad3a9c51ddc0560a5359323 Mon Sep 17 00:00:00 2001 From: leocavalcante Date: Tue, 24 Oct 2023 10:23:24 -0300 Subject: [PATCH 4/6] refactor: Improving start server output and tests --- hfctl | 18 +++++++++++++----- test | 21 +++++++++++++++++---- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/hfctl b/hfctl index 48f81d0..24619a5 100755 --- a/hfctl +++ b/hfctl @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -e -HFCTL=v0.2.0 +HFCTL=v0.3.0 IMAGE=hyperf/hyperf:8.2-alpine-v3.18-swoole-v5.1 HFCID=/tmp/hfctl.cid @@ -25,7 +25,7 @@ help() { echo -e " \033[1;32mrestart\033[0m Restart the Hyperf server" echo -e " \033[1;32mwatch [port]\033[0m Starts the Hyperf watcher \033[1;37m(default port 9501)\033[0m" echo -e " \033[1;32mbin (cmd or command) \033[0m Runs Hyperf commands" - echo -e " \033[1;32mlogs\033[0m Shows the Hyperf container logs" + echo -e " \033[1;32mlogs [-f|--follow]\033[0m Shows the Hyperf container logs \033[1;37m(use -f or --follow to follow logs)\033[0m" echo -e "" } @@ -54,7 +54,15 @@ composer() { start() { pre-check - echo $(docker-run -dp${1:-9501}:9501 php bin/hyperf.php start) > $HFCID + PORT=${1:-9501} + if [ -n "$(docker ps --format 'table {{.Ports}}' | grep $PORT)" ]; then + echo -e "\033[0;31mPort $PORT is already in use by another container\033[0m" + exit 1 + fi + + echo $(docker-run -dp$PORT:9501 php bin/hyperf.php ${2:-start}) > $HFCID + echo -e "\033[1;32mServer is running at $PORT\033[0m" + echo -e "http://localhost:$PORT" } watch() { @@ -62,7 +70,7 @@ watch() { if [[ $(composer show hyperf/watcher) == *"\"hyperf/watcher\" not found"* ]]; then docker-run -t composer require --dev hyperf/watcher fi - echo $(docker-run -dp${1:-9501}:9501 php bin/hyperf.php server:watch) > $HFCID + start ${1:-9501} server:watch } stop() { @@ -82,7 +90,7 @@ bin() { logs() { pre-check - docker logs -f $(cat $HFCID) + docker logs $@ $(cat $HFCID) } docker-run() { diff --git a/test b/test index 8cd829c..ff25940 100755 --- a/test +++ b/test @@ -15,6 +15,11 @@ function tear_down_after_script() { sudo rm -rf $HFDIR } +function test_should_display_help_information() { + assert_contains "Shows this help message" "$($HFCTL)" + assert_contains "Shows this help message" "$($HFCTL help)" +} + function test_should_test_project_been_created() { assert_file_exists $HFDIR/vendor/autoload.php } @@ -34,11 +39,12 @@ function test_should_composer_commands_be_available() { assert_contains "composer.json is valid" "$($HFCTL composer validate)" } -function test_should_start_project_at_port_9501() { +function test_should_start_show_logs_and_stop() { cd $HFDIR - $HFCTL start - sleep 1 + assert_contains "Server is running at 9501" "$($HFCTL start)" + sleep 2 assert_file_exists $HFCID + assert_contains "[INFO] HTTP Server listening at 0.0.0.0:9501" "$($HFCTL logs)" assert_equals "{\"method\":\"GET\",\"message\":\"Hello Hyperf.\"}" "$(curl -s http://localhost:9501)" assert_equals "$(cat $HFCID)" "$($HFCTL stop)" assert_file_not_exists $HFCID @@ -47,7 +53,7 @@ function test_should_start_project_at_port_9501() { function test_should_install_watcher_and_watch_for_changes() { cd $HFDIR assert_contains "Running composer update hyperf/watcher" "$($HFCTL watch)" - sleep 1 + sleep 2 assert_file_exists $HFCID assert_contains "Hot reload watcher for Hyperf" "$($HFCTL composer show hyperf/watcher)" assert_equals "{\"method\":\"GET\",\"message\":\"Hello Hyperf.\"}" "$(curl -s http://localhost:9501)" @@ -55,6 +61,13 @@ function test_should_install_watcher_and_watch_for_changes() { assert_file_not_exists $HFCID } +function test_should_run_hyperf_commands() { + cd $HFDIR + assert_contains "Console Tool" "$($HFCTL bin)" + assert_contains "Console Tool" "$($HFCTL cmd)" + assert_contains "Console Tool" "$($HFCTL command)" +} + if [[ $0 == "./test" ]]; then ./bashunit ./test fi From 2aeed186e7020d86da469d9105e4fd19e80a1f10 Mon Sep 17 00:00:00 2001 From: leocavalcante Date: Tue, 24 Oct 2023 10:30:02 -0300 Subject: [PATCH 5/6] feat(ci): Adding CI manifest --- .github/workflows/ci.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..de8c097 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,11 @@ +on: + - push + - pull_request + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Run tests + run: ./test From acffe37b04c0a5bad4a3f5bfc175451a1894163f Mon Sep 17 00:00:00 2001 From: leocavalcante Date: Tue, 24 Oct 2023 12:36:04 -0300 Subject: [PATCH 6/6] refactor: Tests --- .github/workflows/ci.yml | 3 +-- CONTRIBUTING.md | 4 ++-- hfctl | 41 ++++++++++++++++++++++------------------ test => hfctl_test | 26 ++++++++++++++----------- 4 files changed, 41 insertions(+), 33 deletions(-) rename test => hfctl_test (72%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de8c097..382e763 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,4 @@ on: - - push - pull_request jobs: @@ -8,4 +7,4 @@ jobs: steps: - uses: actions/checkout@v2 - name: Run tests - run: ./test + run: ./hfctl_test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bbb5fd3..6e170f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Hi, feel free to send any issues or pull requests. ## Testing -This project uses [bashunit](https://bashunit.typeddevs.com/) for testing. Tests are located at `./test` and you can run them with: +This project uses [bashunit](https://bashunit.typeddevs.com/) for testing. Tests are located at `./hfctl_test` and you can run them with: ```shell -./test +./hfctl_test ``` diff --git a/hfctl b/hfctl index 24619a5..b429f13 100755 --- a/hfctl +++ b/hfctl @@ -1,9 +1,8 @@ #!/usr/bin/env bash -set -e HFCTL=v0.3.0 IMAGE=hyperf/hyperf:8.2-alpine-v3.18-swoole-v5.1 -HFCID=/tmp/hfctl.cid +HFCID=runtime/hfctl.cid version() { echo -e "hfctl: \033[1;32m$HFCTL\033[0m" @@ -24,7 +23,7 @@ help() { echo -e " \033[1;32mstop\033[0m Stops the Hyperf server" echo -e " \033[1;32mrestart\033[0m Restart the Hyperf server" echo -e " \033[1;32mwatch [port]\033[0m Starts the Hyperf watcher \033[1;37m(default port 9501)\033[0m" - echo -e " \033[1;32mbin (cmd or command) \033[0m Runs Hyperf commands" + echo -e " \033[1;32mconsole \033[0m Runs Hyperf console commands \033[1;37m(php bin/hyperf.php )\033[0m" echo -e " \033[1;32mlogs [-f|--follow]\033[0m Shows the Hyperf container logs \033[1;37m(use -f or --follow to follow logs)\033[0m" echo -e "" } @@ -43,13 +42,13 @@ self-update() { } create() { - docker-run ${2:--i} composer create-project hyperf/hyperf-skeleton:dev-master $1 + docker-run ${HFTTY:--it} composer create-project hyperf/hyperf-skeleton:dev-master $1 sudo chown -R $UID:$UID $1 } composer() { pre-check - docker-run -it composer $@ + docker-run ${HFTTY:--it} composer $@ } start() { @@ -59,37 +58,37 @@ start() { echo -e "\033[0;31mPort $PORT is already in use by another container\033[0m" exit 1 fi - - echo $(docker-run -dp$PORT:9501 php bin/hyperf.php ${2:-start}) > $HFCID - echo -e "\033[1;32mServer is running at $PORT\033[0m" + echo -e "Container ID:" $(docker-run "-dp$PORT:9501 --cidfile $HFCID" php bin/hyperf.php ${2:-start}) + echo -e "\033[1;32mHyperf server started at $PORT\033[0m" echo -e "http://localhost:$PORT" } watch() { pre-check - if [[ $(composer show hyperf/watcher) == *"\"hyperf/watcher\" not found"* ]]; then - docker-run -t composer require --dev hyperf/watcher + if [[ "$(composer show hyperf/watcher 2>&1)" == *"\"hyperf/watcher\" not found"* ]]; then + docker-run ${HFTTY:--it} composer require --dev hyperf/watcher fi start ${1:-9501} server:watch } stop() { - pre-check - docker stop $(cat $HFCID) && rm $HFCID + pre-check cid + docker stop $(cat $HFCID) + rm $HFCID } restart() { - pre-check - docker restart $(cat $HFCID) && rm $HFCID + pre-check cid + echo -e "Container ID:" $(docker restart $(cat $HFCID)) } -bin() { +console() { pre-check - docker-run -it php bin/hyperf.php $@ + docker-run ${HFTTY:--it} php bin/hyperf.php $@ } logs() { - pre-check + pre-check cid docker logs $@ $(cat $HFCID) } @@ -102,9 +101,15 @@ pre-check() { echo -e "\033[0;31mAre you in a Hyperf project?\033[0m" exit 1 fi + + if [[ $1 == cid ]]; then + if ! [ -f $HFCID ]; then + echo -e "\033[0;31mThe Hyperf server is not running\033[0m" + exit 1 + fi + fi } CMD=${1:-help} ARGS=${@:2} -if [[ $CMD =~ command|cmd ]]; then CMD=bin; fi $CMD $ARGS diff --git a/test b/hfctl_test similarity index 72% rename from test rename to hfctl_test index ff25940..5480d5d 100755 --- a/test +++ b/hfctl_test @@ -2,13 +2,13 @@ HFCTL=$(dirname $(realpath $0))/hfctl HFDIR=test-project -HFCID=/tmp/hfctl.cid +HFCID=runtime/hfctl.cid function set_up_before_script() { if [ -d $HFDIR ]; then sudo rm -rf $HFDIR fi - $HFCTL create $HFDIR + $HFCTL create $HFDIR &> /dev/null } function tear_down_after_script() { @@ -34,14 +34,19 @@ function test_should_pre_check_pass_if_in_project_directory() { assert_empty "$($HFCTL pre-check)" } +function test_should_pre_check_fail_if_there_is_no_cid() { + cd $HFDIR + assert_contains "Hyperf server is not running" "$($HFCTL pre-check cid)" +} + function test_should_composer_commands_be_available() { cd $HFDIR - assert_contains "composer.json is valid" "$($HFCTL composer validate)" + assert_contains "Display help for a command" "$($HFCTL composer help)" } function test_should_start_show_logs_and_stop() { cd $HFDIR - assert_contains "Server is running at 9501" "$($HFCTL start)" + assert_contains "Hyperf server started at 9501" "$($HFCTL start)" sleep 2 assert_file_exists $HFCID assert_contains "[INFO] HTTP Server listening at 0.0.0.0:9501" "$($HFCTL logs)" @@ -52,7 +57,7 @@ function test_should_start_show_logs_and_stop() { function test_should_install_watcher_and_watch_for_changes() { cd $HFDIR - assert_contains "Running composer update hyperf/watcher" "$($HFCTL watch)" + assert_contains "Hyperf server started at 9501" "$($HFCTL watch 2> /dev/null)" sleep 2 assert_file_exists $HFCID assert_contains "Hot reload watcher for Hyperf" "$($HFCTL composer show hyperf/watcher)" @@ -61,13 +66,12 @@ function test_should_install_watcher_and_watch_for_changes() { assert_file_not_exists $HFCID } -function test_should_run_hyperf_commands() { +function test_should_run_hyperf_console_commands() { cd $HFDIR - assert_contains "Console Tool" "$($HFCTL bin)" - assert_contains "Console Tool" "$($HFCTL cmd)" - assert_contains "Console Tool" "$($HFCTL command)" + assert_contains "Console Tool" "$($HFCTL console)" } -if [[ $0 == "./test" ]]; then - ./bashunit ./test +if [[ $0 == "./hfctl_test" ]]; then + export HFTTY="-lno-tty" + ./bashunit ./hfctl_test $@ fi