aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/os.sh19
-rw-r--r--lib/test.sh179
-rw-r--r--lib/test/asserts.sh68
-rw-r--r--lib/test/output.sh130
-rw-r--r--lib/vars.sh5
5 files changed, 396 insertions, 5 deletions
diff --git a/lib/os.sh b/lib/os.sh
index 211bfaa..2f5e1b5 100644
--- a/lib/os.sh
+++ b/lib/os.sh
@@ -5,8 +5,8 @@ export JSON_OS=
if [ -f /etc/os-release ]; then
. /etc/os-release
- _os_type="${NAME}"
- _os_name="${ID}"
+ _os_type="${ID}"
+ _os_name="${NAME}"
_os_pretty_name="${PRETTY_NAME}"
_os_version="${VERSION}"
_os_version_id="${VERSION_ID}"
@@ -15,9 +15,18 @@ else
uname=$(uname | tr '[:upper:]' '[:lower:]')
case "${uname}" in
darwin)
- _os_type="macos"
- >&2 echo "${0}: error: macos not done yet!"
- exit 1;;
+ _os_type="darwin"
+ _vers=$(sw_vers | awk '{print $2}')
+ if [ "$?" = "0" ]; then
+ _os_name=$(echo "${_vers}" | tail -n +1 | head -n 1)
+ _os_version=$(echo "${_vers}" | tail -n +2 | head -n 1)
+ _os_pretty_name="${_os_name} ${_os_version}"
+ _os_version_id="${_os_version}"
+ _os_supported=1
+ else
+ >&2 echo "cloyster/os_detect.sh: error: unknown darwin system: '${uname}'"
+ fi
+ ;;
*)
_os_type="${uname}"
>&2 echo "cloyster/os_detect.sh: error: unknown operating system: '${uname}'"
diff --git a/lib/test.sh b/lib/test.sh
new file mode 100644
index 0000000..1da470b
--- /dev/null
+++ b/lib/test.sh
@@ -0,0 +1,179 @@
+. "${CLOYSTER_SCRIPT_ROOT}/lib/test/output.sh"
+
+cloyster_test_varname() {
+ if [ -n "${1}" ]; then echo "_cloyster_test_${1}"; else echo "_cloyster_test"; fi
+}
+
+cloyster_test_init() {
+ export _cloyster_test_tests_counter_count=0
+ export _cloyster_test_tests_counter_success=0
+ export _cloyster_test_tests_counter_skipped=0
+ export _cloyster_test_tests_counter_failed=0
+ export _cloyster_test_tests_counter_ignored=0
+ export _cloyster_test_tests_failed=
+ export _cloyster_test_tests_skipped=
+ export _cloyster_test_tests_success=
+ export _cloyster_test_tests_ignored=
+ export _cloyster_test_tests=
+ export _cloyster_test_groups=
+ export _cloyster_test_init=y
+}
+
+cloyster_test_add_shellcheck() {
+ ignore=
+ if ! command -v shellcheck >/dev/null; then ign=".ignore:SHELLCHECK_REQUIRED"; fi
+ _cloyster__test__shellcheck() {
+ shellcheck -e SC1008,SC2239 "${CLOYSTER_COMMAND_FILE}"
+ }
+ cloyster_test_add -a shellcheck shellcheck "_cloyster__test__shellcheck" "${ignore}"
+}
+
+cloyster_test_add() {
+ append=
+ while getopts :a _opt; do case "${_opt}" in
+ a) append=y;;
+ esac; done; shift $((OPTIND - 1))
+ id=$1
+ name=$2
+ fun=$3
+ args=$4
+ if [ -z "${id}" ]; then return 1; fi
+ if [ -z "${name}" ]; then return 1; fi
+ list=$(cloyster_test_varname "tests")
+ var=$(cloyster_test_varname "tests_${id}")
+ if [ -n "$(readvar "$var")" ]; then
+ echo "ERROR: A test '${id}' has already been defined"
+ return 1
+ fi
+ if is_true "${append}"; then _cloyster_test_tests="${name} ${_cloyster_test_tests}"; else _cloyster_test_tests="${_cloyster_test_tests} ${name}"; fi
+ setvar "${var}" "${var}"
+ setvar "${var}__name" "${name}"
+ setvar "${var}__fun" "${fun}"
+ setvar "${var}__args" "${args}"
+}
+
+cloyster_test_run() {
+ cloyster_test_log "${_green}${_bold}⚡⚡⚡ Running cloyster tests for ${_bold}${_yellow}${CLOYSTER_COMMAND}${_rst} ⚡⚡⚡"
+ cloyster_test_log "${_darkgray} (did you knew that #!/bin/sh is twenty years older than emojis ?)${_rst}\n"
+ cloyster_test_add_shellcheck #|| s=$?; _error "Failed to lol $s"; exit 1
+
+ incr() {
+ var="_cloyster_test_tests_counter_${1}"
+ val=$(($(readvar "$var") + 1))
+ setvar "$var" "$val"
+ }
+
+ _echo_block_init
+ all_start_us=$(posix_time_microseconds)
+ for id in ${_cloyster_test_tests}; do
+ incr "count"
+ name=$(readvar "_cloyster_test_tests_${id}__name")
+ args=$(readvar "_cloyster_test_tests_${id}__args")
+ line=$(readvar "_cloyster_test_tests_${id}__line")
+ fun=$(readvar "_cloyster_test_tests_${id}__fun")
+ fun="${fun:-"_test_${id}"}"
+ testlog="${_tmp_dir}/test_${id}.log"
+ _debug "Running test '${name}' line=${line} fun=${fun} args=${args}"
+ refute=; skip=; tags=; status=; precond_status=; ignore=; fatal=;
+ skip_reason=; ignore_reason= fatal_reason=
+ for arg in $args; do
+ tag=$(echo "$arg" | cut -d ":" -f 1)
+ targ=$(echo "$arg" | sed "s/^${tag}://g")
+ case $tag in
+ .refute) refute=y;;
+ .skip) skip=y; skip_reason=${targ:-.skip};;
+ .ignore) ignore=y; ignore_reason=${targ:-.ignore};;
+ .fatal) fatal=y; fatal_reason=${targ:-.fatal};;
+ .tag) if [ -n "${targ}" ]; then tags="${tags} ${targ}"; fi;;
+ *) ;;
+ esac
+ done
+
+## -- Preconditions
+if [ ! "$skip" = "y" ]; then
+ (
+ preconds=$(
+ for arg in $args; do
+ cond=$(echo "$arg" | cut -d ":" -f 2,100)
+ case $arg in
+ .if:*) if $($cond); then miss=y; break; fi;;
+ .not:*) if ! $($cond); then miss=y; break; fi;;
+ *) ;;
+ esac
+ done
+ if [ "${miss}" = "y" ]; then
+ echo "$(tput AF 105)Precondition failed: ${arg}" >> "${testlog}"
+ exit 2
+ fi
+ )
+ precond_status=$?
+ exit $precond_status
+ ) 2>> "${testlog}" >> "${testlog}"
+ precond_status=$?
+else
+ echo "$(tput AF 105)${skip_reason}" >> "${testlog}"
+fi
+
+# -- TEST SUBSHELL
+if [ "${precond_status}" = "0" ] && [ ! "${skip}" = "y" ]; then
+(
+. "${CLOYSTER_SCRIPT_ROOT}/lib/test/asserts.sh"
+_assert_reset_count
+start_us=$(posix_time_microseconds)
+(
+ "$fun"
+ s=$?
+ asserts=$(_assert_count)
+ if [ ! "${asserts}" = "0" ]; then echo "☀️ Passed ${asserts} assertions."; fi
+ exit $s
+)
+status=$?
+end_us=$(posix_time_microseconds)
+duration=$(bc -e "$end_us - $start_us")
+if is_true "$refute" && [ "${status}" = "0" ]; then echo "⚠️ Exited with code ${_bold}${status}${_rst} (refuting, expected >0)"; fi
+if is_true "$refute" && [ ! "${status}" = "0" ]; then echo "🌗 Exited with code ${status} (refuting)"; fi
+if ! is_true "$refute" && [ ! "${status}" = "0" ]; then echo "⚠️ Exited with code ${_bold}${status}${_rst} (expected 0)"; fi
+echo "$(tput AF 59)Ran in ${duration}ms${_rst}"
+exit $status
+) 2>> "${testlog}" >> "${testlog}"
+status=$?
+fi
+
+## -- TEST FINISHED
+
+ cmpstatus="$status"
+ if is_true "$refute"; then
+ if [ "$status" = "0" ]; then cmpstatus=1; else cmpstatus=0; fi
+ fi
+ if [ "$precond_status" = "0" ] && [ "$cmpstatus" = "0" ]; then
+ _echo_block success
+ _cloyster_test_tests_success="${_cloyster_test_tests_success}${id} "
+ incr "success"
+ elif [ "$precond_status" = "2" ] || [ "$skip" = "y" ]; then
+ _echo_block skipped
+ _cloyster_test_tests_skipped="${_cloyster_test_tests_skipped} ${id}"
+ incr "skipped"
+ else
+ if [ ! "$precond_status" = "0" ]; then
+ err="precondition_error:${precond_status}"
+ else
+ err="${status}"
+ fi
+ _action=failed
+ if is_true "${ignore}"; then _action="ignored"; fi
+ _echo_block "${_action}"
+ setvar "_cloyster_test_tests_${id}__status" "$err"
+ setvar "_cloyster_test_tests_${id}__ignore" "$ignore"
+ setvar "_cloyster_test_tests_${_action}" "$(readvar "_cloyster_test_tests_${_action}") ${id}"
+ incr "${_action}"
+ fi
+
+ done
+ _echo_block_flush
+ all_end_us=$(posix_time_microseconds)
+ duration=$(bc -e "$all_end_us - $all_start_us")
+
+ _test_output_results
+ echo ""
+ _test_output_overview
+}
diff --git a/lib/test/asserts.sh b/lib/test/asserts.sh
new file mode 100644
index 0000000..b1f538e
--- /dev/null
+++ b/lib/test/asserts.sh
@@ -0,0 +1,68 @@
+#!/bin/sh
+
+fail() {
+ message=${1:-"fail called without message"}
+ echo -e " ${red}fail:${_rst} ${reason}"
+ exit 1
+}
+
+_assert_warn=
+
+assert_warn() {
+ if ! is_true "${_assert_warn}"; then _assert_warn=y; fi
+}
+
+assert_exited() {
+ status=${1}
+ expected=${2:-0}
+ message=${3:-"exit status"}
+ assert_compare "=" "$status" "$expected" "$message"
+}
+
+assert_compare() {
+ expression=$1
+ left=$2
+ right=$3
+ message=$4
+ #cloyster_test_log "assert_compare 'test \"$left\" $expression \"$right\" && echo \"OK\" || echo \"$right\"'"
+ assert "test \"$left\" $expression \"$right\" && echo \"\$?\" || echo \"\$?\"" "0" "${message}"
+}
+
+assert() {
+ assertion=$1
+ expected=$2
+ message=${3:-assertion}
+ status=
+ assertion_eval="$(eval $(echo "$1"))" && status=0 || status=$?; true
+ cloyster_test_log "assert: assertion={$(echo "$1" | head -c 30 | sed "s:\\n: :g")} expected=${expected} evaluated=${assertion_eval} status=${status}"
+ if [ ! "$status" = "0" ]; then
+ _assert_failed "exited: ${status}"
+ _assert_exit
+ elif [ ! "$assertion_eval" = "$expected" ]; then
+ _assert_failed "${message}"
+ echo -e "${_bold}Expected:${_rst} ${expected}\n${_bold}Got :${_rst} ${assertion_eval}"
+ _assert_exit
+ fi
+ _test_asserts_counter=$(( ${_test_asserts_counter:-0} + 1 ))
+ _assert_warn=
+}
+
+_assert_count() {
+ echo "$_test_asserts_counter"
+}
+_assert_reset_count() {
+ _test_asserts_counter=0
+}
+_assert_failed() {
+ name=$1
+ reason=$2; if [ -n "${reason}" ]; then reason=" ${reason}"; fi
+ line=; if [ -n "${_assert_line}" ]; then line=" at line ${_bold}${_assert_line}"; fi
+ emoji="💥"; text=
+ if is_true "$_assert_warn"; then emoji="🔥"; text=" (WARNING)"; fi
+ echo -e "${emoji} $(tput AF 214)Assertion ${_test_asserts_counter} failed${text}${line}: ${_bold}$name${_rst}${reason}"
+}
+
+_assert_exit() {
+ if ! is_true "$_assert_warn"; then exit 99; fi
+ _assert_warn=
+}
diff --git a/lib/test/output.sh b/lib/test/output.sh
new file mode 100644
index 0000000..4b09685
--- /dev/null
+++ b/lib/test/output.sh
@@ -0,0 +1,130 @@
+#!/bin/sh
+
+ _bold=$(tput md)
+ _rst=$(tput me)
+ _red=$(tput AF 202)
+ _redb=$(tput AF 202)
+ _green=$(tput AF 2)
+ _yellow=$(tput AF 3)
+ _blue=$(tput AF 111)
+ _darkgray=$(tput AF 8)
+ _gray=$(tput AF 102)
+ _orange=$(tput AF 214)
+
+cloyster_test_log() {
+# >&2 echo -e "${_ctx}[${_logcontext}]${_rst} $@${_rst}"
+ >&2 echo -e "$@${_rst}"
+ }
+ cloyster_test_debug() {
+ if is_true "${DEBUG}"; then cloyster_test_log "🔵 ${_blue}${@}"; fi
+ }
+ cloyster_test_info() {
+ cloyster_test_log "👍 ${@}"
+ }
+ cloyster_test_warn() {
+ cloyster_test_log "⚠️ ${_bold}${_yellow}${@}"
+ }
+ cloyster_test_error() {
+ cloyster_test_log "❌ ${_redb}${@}"
+ }
+
+
+_echo_block=
+_echo_block_per_line=25
+_echo_block_count=
+_echo_block_init() {
+ _echo_block=y
+ _echo_block_count=1
+}
+_echo_block() {
+ if is_true "${_echo_block}"; then
+ _echo_block_count=$((${_echo_block_count} + 1))
+ case $1 in
+ success) e="🟩";;
+ skipped) e="🟦";;
+ failed) e="🟥";;
+ ignored) e="🟧";;
+ *) e="⬛";;
+ esac
+ if [ "${_echo_block_count}" -le "${_echo_block_per_line}" ]; then
+ echo -n "${e}"
+ else
+ _echo_block_count=1
+ echo "${e}"
+ fi
+ fi
+}
+_echo_block_flush() {
+ if is_true "${_echo_block}"; then
+ _echo_block=
+ _echo_block_count=
+ echo ""
+ fi
+}
+
+_test_output_overview() {
+ if [ -n "${_cloyster_test_tests_failed}" ]; then
+ color="${_redb}"
+ accent="${_bold}${_redb}"
+ emoji="❌"
+ title="FAILED"
+ else
+ color="${_green}"
+ accent="${_bold}${_green}"
+ emoji="✅"
+ title="PASSED"
+ fi
+
+ cloyster_test_log "${emoji} ${_bold}${_yellow}${CLOYSTER_COMMAND}${_rst} ${color}tests${_rst} ${accent}${title}${_rst}"
+ _greenb=$(tput AF 154)
+ _gray=$(tput AF 8)
+ echo -n "${emoji} ${_bold}${_cloyster_test_tests_counter_count} tests${_rst}, ${_bold}${_greenb}${_cloyster_test_tests_counter_success} ok${_rst}"
+ if [ ! "${_cloyster_test_tests_counter_failed}" = "0" ]; then
+ echo -n ", ${_bold}${_redb}${_cloyster_test_tests_counter_failed} failed${_rst}"
+ fi
+ if [ ! "${_cloyster_test_tests_counter_ignored}" = "0" ]; then
+ echo -n ", ${_bold}${_orange}${_cloyster_test_tests_counter_ignored} ignored${_rst}"
+ fi
+ if [ ! "${_cloyster_test_tests_counter_skipped}" = "0" ]; then
+ echo -n ", ${_bold}${_blue}${_cloyster_test_tests_counter_skipped} skipped${_rst}"
+ fi
+ echo -n " ${_gray}in ${duration}ms${_rst}"
+ echo -e "${_rst}"
+}
+
+_test_output_results() {
+ list=${1:-"success skipped ignored failed"}
+ for t in ${list}; do _test_output_results_one "${t}"; done
+}
+
+_test_output_results_one() {
+ key=$1
+ cnt=0
+ count=$(readvar "_cloyster_test_tests_counter_${key}")
+
+ case $key in
+ success) color="${_green}"; type="SUCCESS"; emoji="👍"; logcolor=$(tput AF 64);;
+ skipped) color="${_blue}"; type="SKIPPED"; emoji="🙈"; logcolor=$(tput AF 111);;
+ failed) color="${_red}"; type="FAILED"; emoji="😡"; logcolor=$(tput AF 181);;
+ ignored) color="${_orange}"; type="IGNORED"; emoji="🥲"; logcolor=$(tput 130);;
+ *) color="${_yellow}"; type="TEST"; emoji="💩"; logcolor=${_yellow};;
+ esac
+
+ for id in $(readvar "_cloyster_test_tests_${key}"); do
+ cnt=$(( $cnt + 1 ))
+ name=$(readvar "_cloyster_test_tests_${id}__name")
+ args=$(readvar "_cloyster_test_tests_${id}__args")
+ lineno=$(readvar "_cloyster_test_tests_${id}__line")
+ status=$(readvar "_cloyster_test_tests_${id}__status")
+ args_s=; lineno_s=; status_s=; line=
+ args_s=" [${args}]"
+ if [ -n "${lineno}" ] && [ ! "${lineno}" = "0" ]; then lineno_s=" L#${lineno}"; fi
+ if [ -n "${status}" ]; then status_s=" !${status}"; fi
+ line="\n${emoji} ${_bold}${color}${type} ${cnt}/${count}:${_rst} ${_bold}${name}${_rst}"
+ line="${line}${_gray}${args_s}${_darkgray}${lineno_s}${status_s}${_rst}"
+ echo -e "${line}"
+ cat "${_tmp_dir}/test_${id}.log" | while read -r line; do
+ echo -e " ${logcolor}> ${line}${_rst}"
+ done
+ done
+}
diff --git a/lib/vars.sh b/lib/vars.sh
new file mode 100644
index 0000000..4ae2ee7
--- /dev/null
+++ b/lib/vars.sh
@@ -0,0 +1,5 @@
+readvar() {
+ local var=$1;
+ if [ ! "$var" ]; then _debug "readvar: missing arg: var"; exit 1; fi
+ eval "echo \$$var"
+}