diff options
author | Jordan Bracco <href@random.sh> | 2022-03-25 15:44:44 +0000 |
---|---|---|
committer | Jordan Bracco <href@random.sh> | 2022-03-25 15:44:44 +0000 |
commit | babd144393c0cbfdc28fb475190d9a552070b240 (patch) | |
tree | 927edd0818be95134ece7b819311b0269b3c5fdc | |
parent | Initial PoC (diff) |
-rwxr-xr-x | cloyster | 239 | ||||
-rw-r--r-- | commands/freebsd/_freebsd.sh | 21 | ||||
-rwxr-xr-x | commands/freebsd/ifconfig/list-interfaces-names (renamed from commands/ifconfig/interfaces-names) | 0 | ||||
-rwxr-xr-x | commands/freebsd/jails/list (renamed from commands/jails/list) | 0 | ||||
-rw-r--r-- | commands/freebsd/netgraph/.mock.ngctl.list.l.txt | 380 | ||||
-rw-r--r-- | commands/freebsd/netgraph/_netgraph.sh | 7 | ||||
-rwxr-xr-x | commands/freebsd/netgraph/list-nodes | 111 | ||||
-rw-r--r-- | lib/os.sh | 19 | ||||
-rw-r--r-- | lib/test.sh | 179 | ||||
-rw-r--r-- | lib/test/asserts.sh | 68 | ||||
-rw-r--r-- | lib/test/output.sh | 130 | ||||
-rw-r--r-- | lib/vars.sh | 5 | ||||
m--------- | vendor/shellspec | 0 |
13 files changed, 1095 insertions, 64 deletions
@@ -6,6 +6,9 @@ hostname="${HOSTNAME:-$(hostname)}" _executing= _exec_error= _script_name=$(basename "$0" | cut -d '.' -f 1) +CLOYSTER_SCRIPT_ROOT=$(dirname "$0" | realpath) +_testing= +_silent= # is_true VARIABLE # return 0 if VARIABLE is "y", "yes", "t", "true", "1". Otherwise returns 1. @@ -42,11 +45,14 @@ _debug() { fi } +# Shadowed in the command subshell _execution_error() { _debug "fun=_execution_error() executing=${_executing}" >&2 jo error="$(jo type=execution "$@")" } +. ./lib/vars.sh + . ./lib/dependencies.sh ensure_command_exists jq ensure_command_exists bc @@ -60,21 +66,7 @@ fi . ./lib/identifiers.sh . ./lib/datetime.sh -## getopts -input_decode= -output_encode= -stdin= -while getopts :Bbs _opt; do - case "${_opt}" in - b) input_decode="base64";; - B) output_encode="base64";; - s) stdin=yes;; - *) _execution_error error=invalid_option option="${_opt}";; - esac -done -shift $((OPTIND - 1)) - -if is_true "${stdin}" || [ "${1}" = "-" ]; then +if is_true "${stdin}" || [ "${1}" = "\-" ]; then if [ "${1}" = "-" ]; then shift; fi stdin=yes _exec="STDIN" @@ -90,6 +82,42 @@ else fi fi +## getopts +input_decode= +output_encode= +stdin= +while getopts :BbsTqVh _opt; do + case "${_opt}" in + b) input_decode="base64";; + B) output_encode="base64";; + s) stdin=yes;; + T) _testing=yes;; + q) _silent=yes;; + V) + echo "cloyster vXXX??" + exit 0 + ;; + h) + if [ -n "{_exec}" ] && [ ! "${_exec}" = "stdin" ]; then + echo "${_exec}: cloyster command" + echo "${_exec} [exec_options] [command json arguments]" + else + echo "cloyster help:" + fi + echo "execution options:" + echo -e " -s, -\texecute command from stdin" + echo -e " -q\tquiet (do not copy stdout/stderr to stderr)" + echo -e " -b\tdecode input from base64" + echo -e " -B\tencode output to base64" + echo -e " -T\trun tests" + echo -e " -V\tcloyster version" + exit 0 + ;; + *) _execution_error error=invalid_option option="${_opt}";; + esac +done +shift $((OPTIND - 1)) + _args_json= if [ ! "${#}" = "0" ]; then _args_json="$(jo -d. $@)" @@ -113,49 +141,61 @@ json_unquote() { echo "$1" | jq -r '.' } -_trap_exit() { - _debug "TRAPPED EXIT >:( $@ executing? ${_executing}" - if is_true "${_executing}"; then _exec_post "TRAPEXIT"; fi - _debug "TRAPPED EXIT >:( $@ executing? ${_executing}" - #rm "${_tmp_base}"* > /dev/null 2>&1 || true - ( _json_loggers_stop ) > /dev/null 2>&1 - _debug "$(jobs)" - _debug "bye" - if is_true "${_executing}"; then exit 1; else exit 0; fi +_cloyster_trap_exit() { + if is_true "${_executing}"; then + _debug "_cloyster_trap_exit: ignoring: is executing" + else + _debug "_cloyster_trap_exit: bye" + ( _json_loggers_stop ) > /dev/null 2>&1 || true + ( _cloyster_delete_tmp_dir ) > /dev/null 2>&1 || true + fi } -#trap _trap_exit EXIT +trap _cloyster_trap_exit EXIT _exec_name=$(basename "$_exec") -_tmp_base="/tmp/${_script_name}@${_exec_name}@$(make_random_identifier 16)" -_stdout_dest="${_tmp_base}_stdout.log" -_stderr_dest="${_tmp_base}_stderr.log" -_cloyster_dest="${_tmp_base}_cloyster.jsonstream" -_output_dest="${_tmp_base}_output.jsonstream" -_cntrl_dest="${_tmp_base}_cntrl" +_tmp_dir="/tmp/${_script_name}@${_exec_name}@$(make_random_identifier 16)" +_stdout_dest="${_tmp_dir}/stdout.log" +_stderr_dest="${_tmp_dir}/stderr.log" +_cloyster_dest="${_tmp_dir}/cloyster.jsonstream" +_output_dest="${_tmp_dir}/output.jsonstream" +_cntrl_dest="${_tmp_dir}/return" _tmp_files="${_stdout_dest} ${_stderr_dest} ${_output_dest} ${_cloyster_dest} ${_cntrl_dest}" -_debug "tmp_files: ${_tmp_files}" +_debug "tmp_dir: ${_tmp_dir} tmp_files: ${_tmp_files}" +mkdir "${_tmp_dir}" for _f in ${_tmp_files}; do if ! touch "${_f}"; then _execution_error error=tmpfile_creation_failed tmpfile="${_f}"; fi done +_cloyster_delete_tmp_dir() { + if [ -n "${_tmp_dir}" ] && ! is_true "$_tmp_dir_deleted" && [ -d "${_tmp_dir}" ]; then + _debug "Cleaning ${_tmp_dir}" + rm -rfx "${_tmp_dir}" + _tmp_dir_deleted=yes + else + _debug "Tmpdir already cleaned!" + fi +} + # _logger_pretty_check NAME ISO806ZDATE LINE... _logger_pretty_echo() { - _name=$1 - _date=$2 - shift 2 - _line=$@ - _time=$(echo "$_date" | cut -d 'T' -f 2) - _bold=$(tput md) - _format_rst=$(tput me) - _color=$(tput AF 3) - _time_color=$(tput AF 59) - case "${_name}" in - stdout) _color=$(tput AF 2);; - stderr) _color=$(tput AF 1);; - cloyster) _color=$(tput AF 111);; - esac - >&2 echo -e "${_time_color}${_time} ${_color}${_bold}[${_name}]${_format_rst} ${_line}" + if ! is_true "${_silent}"; then + _name=$1 + _date=$2 + shift 2 + _line=$@ + _time=$(echo "$_date" | cut -d 'T' -f 2) + _bold=$(tput md) + _format_rst=$(tput me) + _color=$(tput AF 3) + _time_color=$(tput AF 59) + case "${_name}" in + stdout) _color=$(tput AF 2);; + stderr) _color=$(tput AF 1);; + cloyster) _color=$(tput AF 111);; + esac + >&2 echo -e "${_time_color}${_time} ${_color}${_bold}[${_name}]${_format_rst} ${_line}" + fi } _logger_pids="" @@ -165,8 +205,9 @@ _json_logger() { _format=$3 _combined_log=${4:-${_output_dest}} _json_logger_job() { + trap - EXIT _debug "_json_logger ${_name} ${_log}" - tail -F "${_log}" | while read -r _line; do + stdbuf -oL tail -F "${_log}" | while read -r _line; do _date=$(date_now_iso_utc) case "${_format}" in text) @@ -181,24 +222,28 @@ _json_logger() { esac done } - $(_json_logger_job) & + _json_logger_job & _logger_pids="${_logger_pids} $!" } _json_loggers_stop() { - for tailpid in $(ps auxww | grep "tail -F ${_tmp_base}" | grep -v grep | awk '{ print $2 }'); do + if ! is_true "$_json_loggers_stop_done"; then + for tailpid in $(ps auxww | grep "tail -F ${_tmp_dir}" | grep -v grep | awk '{ print $2 }'); do kill -s PIPE "${tailpid}" || true - done - for pid in ${_logger_pids}; do + done + for pid in ${_logger_pids}; do kill "${pid}" > /dev/null 2>&1 || true wait "${pid}" 2>/dev/null || true; - done + done + _logger_pids= + _json_loggers_stop_done=yes + fi + #sleep 4 } _json_logger "cloyster" "${_cloyster_dest}" "rawjson" _json_logger "stdout" "${_stdout_dest}" "text" _json_logger "stderr" "${_stderr_dest}" "text" _exec_post() { - trap - EXIT _arg=$1 _executing= _debug "_exec_post $@" @@ -210,7 +255,7 @@ _exec_post() { cat ${_output_dest} | jq -sr '. | @json' > "${_output_dest}.json" eval $(cat $_cntrl_dest) _format_output - rm "${_tmp_base}"* || true + _cloyster_delete_tmp_dir if [ ! "${_arg}" = "TRAPEXIT" ]; then exit "${_exec_exit_status:-1}"; fi } _format_output() { @@ -253,24 +298,100 @@ _exec__put_return() { echo "_exec_${_key}=${_value}" >> ${_cntrl_dest} } +_cloyster_exec() { _debug "executing: command=${_exec} args=${@}" _exec_pre if is_true "$stdin"; then eval "$(cat -)" 2> "${_stderr_dest}" > "${_stdout_dest}" else SCRIPT="${_exec}" + if [ "${_exec}" = "stdin" ]; then + CLOYSTER_COMMAND=stdin + CLOYSTER_COMMAND_FILE=/dev/null + CLOYSTER_COMMAND_DIR=/dev/null + _debug "Running: stdin" + else + CLOYSTER_COMMAND="$(basename "${_exec}")" + CLOYSTER_COMMAND_DIR="$(realpath "$(dirname "${_exec}")")" + CLOYSTER_COMMAND_FILE="${CLOYSTER_COMMAND_DIR}/${CLOYSTER_COMMAND}" + _debug "Running: ${CLOYSTER_COMMAND} (${CLOYSTER_COMMAND_FILE})" + fi + CLOYSTER_TMP_DIR="${_tmp_dir}" + + _cloyster_test_tests= + # shellcheck source=/dev/null - eval "$( + ( trap _exec__trap_exit EXIT _execution_error() { _exec__put_return "error" "$( jo type=cmd $@)" "json" exit 2 } - return_result() { + + include_cmd_dir() { + if [ "${CLOYSTER_COMMAND}" = "stdin" ]; then + _execution_error function=include_cmd_dir error=include_cmd_dir_with_stdin msg="cannot be used with stdin commands" + exit 1 + else + if [ -z "${1}" ]; then _execution_error function=include_cmd_dir error=missing_argument argument=file; exit 1; fi + _debug "Including ${CLOYSTER_COMMAND_DIR}/${1}" + . "${CLOYSTER_COMMAND_DIR}/${1}" 2>"${_stderr_dest}" 1>"${_stdout_dest}" + fi + } + + return_result() { _exec__put_return "result" "$1" "json" exit 0 } - . "${SCRIPT}" 2>"${_stderr_dest}" 1>"${_stdout_dest}" - )" || true + + return_error() { + _exec__put_return "error" "$(jo type=cmd "$@")" "json" + exit 1 + } + + alias RUN="{ runcmd() {" + alias ENDR="}; }" + + +. "${CLOYSTER_SCRIPT_ROOT}/lib/test.sh" +cloyster_test_init +# if is_true "${_testing}"; then + _test() { + id=$(make_random_identifier 12) + export _cloyster_test_tests="${_cloyster_test_tests} ${id}" + setvar "_cloyster_test_tests_${id}__name" "$1" + shift + setvar "_cloyster_test_tests_${id}__args" "$*" + alias TEST="{ setvar \"_cloyster_test_tests_${id}__line\" \$LINENO; _test_${id}() {" + alias ENDT="}; unalias TEST; unalias ENDT; }" + } +# else +# _test() { +# alias TEST="{ __cloyster_test_disabled() {" +# alias ENDT="}; unalias TEST; unalias ENDT; }" +# } +# fi + + cd "${CLOYSTER_TMP_DIR}" + + _debug "Including command ${CLOYSTER_COMMAND_FILE}" + . "${CLOYSTER_COMMAND_FILE}" 2>"${_stderr_dest}" 1>"${_stdout_dest}" + + if [ "$_executing" = "y" ]; then +# if command -v "runcmd"; then + if is_true "${_testing}"; then + cloyster_test_run + else + _debug "runcmd!" + runcmd "$@" 2>"${_stderr_dest}" 1>"${_stdout_dest}" + fi +# else +# _execution_error error=no_run_function +# fi + fi + ) || true fi _exec_post +} + +_cloyster_exec diff --git a/commands/freebsd/_freebsd.sh b/commands/freebsd/_freebsd.sh new file mode 100644 index 0000000..76871f7 --- /dev/null +++ b/commands/freebsd/_freebsd.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +freebsd() { + if [ ! "$(os_get "id")" = "freebsd" ]; then exit 1; fi +} + +freebsd_ensure() { + _os_type=os_get "id" + if [ ! "${_os_type}" = "freebsd" ]; then + _execution_error error=unsupported_operating_system expected_os_type="freebsd" os_type="${_os_type}" + fi +} + +freebsd_ensure_kernel_module_loaded() { + _kmod="${1}" + if [ -z "${_kmod}" ]; then _execution_error function=freebsd_ensure_kernel_module_loaded error=missing_argument arg=kmod; fi + if ! kldstat -q -m "${_kmod}" > /dev/null 2>&1; then + _execution_error error=missing_dependency dependency=kernel_module kernel_module="${_kmod}" + fi +} + diff --git a/commands/ifconfig/interfaces-names b/commands/freebsd/ifconfig/list-interfaces-names index f058404..f058404 100755 --- a/commands/ifconfig/interfaces-names +++ b/commands/freebsd/ifconfig/list-interfaces-names diff --git a/commands/jails/list b/commands/freebsd/jails/list index b7e009e..b7e009e 100755 --- a/commands/jails/list +++ b/commands/freebsd/jails/list diff --git a/commands/freebsd/netgraph/.mock.ngctl.list.l.txt b/commands/freebsd/netgraph/.mock.ngctl.list.l.txt new file mode 100644 index 0000000..90a8957 --- /dev/null +++ b/commands/freebsd/netgraph/.mock.ngctl.list.l.txt @@ -0,0 +1,380 @@ +There are 66 total nodes: + Name: pub Type: ether ID: 00000001 Num hooks: 2 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + upper pubbridge bridge 00000038 link1 + lower pubbridge bridge 00000038 link0 + + Name: igb1 Type: ether ID: 00000002 Num hooks: 0 + + Name: ng0_beautte Type: eiface ID: 00000202 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link2 + + Name: tinc Type: ether ID: 00000003 Num hooks: 2 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + upper tincbridge bridge 0000002b link1 + lower tincbridge bridge 0000002b link0 + + Name: local Type: eiface ID: 00000006 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link0 + + Name: ng1_front Type: eiface ID: 00000107 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link9 + + Name: ng0_opa Type: eiface ID: 00000407 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link23 + + Name: localbridge Type: bridge ID: 0000000a Num hooks: 25 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + link25 ng0_barrel eiface 0000047f ether + link24 ng0_portsdev eiface 00000442 ether + link23 ng0_opa eiface 00000407 ether + link22 ng0_kraui eiface 000003ce ether + link21 ngeth48 eiface 000003b1 ether + link20 ng0_kratos_admi eiface 0000037b ether + link19 ng0_enki_test2 eiface 00000347 ether + link18 ng0_enki_test1 eiface 00000315 ether + link17 ng0_grafana eiface 000002e5 ether + link16 ng0_ca eiface 000002b7 ether + link15 ng0_deterior eiface 000002a0 ether + link14 ng0_vpn_as43069 eiface 0000026a ether + link2 ng0_beautte eiface 00000202 ether + link13 ng0_mgmt eiface 000001a0 ether + link12 ng0_sade eiface 0000016c ether + link11 ng0_loki eiface 00000147 ether + link10 ng0_sso eiface 00000124 ether + link9 ng0_front eiface 000000f7 ether + link8 ng0_net eiface 000000c1 ether + link7 ng0_matrix eiface 000000a5 ether + link6 ng0_git eiface 0000008b ether + link5 ng0_vpn eiface 00000068 ether + link4 ng0_piss eiface 00000048 ether + link3 ng0_bender eiface 00000023 ether + link0 local eiface 00000006 ether + + Name: as43069 Type: eiface ID: 0000000d Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether as43069bridge bridge 00000011 link0 + + Name: as43069bridge Type: bridge ID: 00000011 Num hooks: 4 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + link4 ng2_vpn_as43069 eiface 0000028a ether + link3 ng2_beautte eiface 0000021b ether + link2 ng2_net eiface 000000d8 ether + link0 as43069 eiface 0000000d ether + + Name: ng1_beautte Type: eiface ID: 00000211 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link8 + + Name: ng2_front Type: eiface ID: 00000113 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether pubbridge bridge 00000038 link5 + + Name: ng0_enki_test1 Type: eiface ID: 00000315 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link18 + + Name: ng2_beautte Type: eiface ID: 0000021b Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether as43069bridge bridge 00000011 link3 + + Name: ng0_bender Type: eiface ID: 00000023 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link3 + + Name: ng1_opa Type: eiface ID: 00000423 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link21 + + Name: ng0_sso Type: eiface ID: 00000124 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link10 + + Name: tincbridge Type: bridge ID: 0000002b Num hooks: 24 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + link23 ng1_barrel eiface 0000049d ether + link22 ng1_portsdev eiface 0000045f ether + link21 ng1_opa eiface 00000423 ether + link20 ng1_kraui eiface 000003e9 ether + link19 ng1_kratos_admi eiface 00000395 ether + link18 ng1_enki_test2 eiface 00000360 ether + link17 ng1_enki_test1 eiface 0000032d ether + link16 ng1_grafana eiface 000002fc ether + link15 ng1_ca eiface 000002cd ether + link14 ng1_vpn_as43069 eiface 0000027f ether + link8 ng1_beautte eiface 00000211 ether + link13 ng1_mgmt eiface 000001b4 ether + link12 ng1_sade eiface 0000017f ether + link11 ng1_loki eiface 00000159 ether + link10 ng1_sso eiface 00000135 ether + link9 ng1_front eiface 00000107 ether + link7 ng1_net eiface 000000cf ether + link6 ng1_matrix eiface 000000b2 ether + link5 ng1_git eiface 00000097 ether + link4 ng1_vpn eiface 00000073 ether + link3 ng1_piss eiface 00000052 ether + link2 ng1_bender eiface 00000030 ether + link1 tinc ether 00000003 upper + link0 tinc ether 00000003 lower + + Name: ng1_enki_test1 Type: eiface ID: 0000032d Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link17 + + Name: ng1_bender Type: eiface ID: 00000030 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link2 + + Name: ng1_sso Type: eiface ID: 00000135 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link10 + + Name: pubbridge Type: bridge ID: 00000038 Num hooks: 8 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + link7 ng2_mgmt eiface 000001c2 ether + link6 ng2_sade eiface 0000018c ether + link5 ng2_front eiface 00000113 ether + link4 ng2_vpn eiface 0000007e ether + link3 ng2_piss eiface 0000005c ether + link2 ng2_bender eiface 0000003d ether + link1 pub ether 00000001 upper + link0 pub ether 00000001 lower + + Name: ng2_bender Type: eiface ID: 0000003d Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether pubbridge bridge 00000038 link2 + + Name: ng0_portsdev Type: eiface ID: 00000442 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link24 + + Name: ng0_loki Type: eiface ID: 00000147 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link11 + + Name: ng0_enki_test2 Type: eiface ID: 00000347 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link19 + + Name: ng0_piss Type: eiface ID: 00000048 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link4 + + Name: ng1_piss Type: eiface ID: 00000052 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link3 + + Name: ng1_loki Type: eiface ID: 00000159 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link11 + + Name: ng2_piss Type: eiface ID: 0000005c Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether pubbridge bridge 00000038 link3 + + Name: ng1_portsdev Type: eiface ID: 0000045f Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link22 + + Name: ng1_enki_test2 Type: eiface ID: 00000360 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link18 + + Name: ng0_vpn Type: eiface ID: 00000068 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link5 + + Name: ng0_vpn_as43069 Type: eiface ID: 0000026a Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link14 + + Name: ng0_sade Type: eiface ID: 0000016c Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link12 + + Name: ng1_vpn Type: eiface ID: 00000073 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link4 + + Name: ng0_kratos_admi Type: eiface ID: 0000037b Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link20 + + Name: ng2_vpn Type: eiface ID: 0000007e Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether pubbridge bridge 00000038 link4 + + Name: ng0_barrel Type: eiface ID: 0000047f Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link25 + + Name: ng1_sade Type: eiface ID: 0000017f Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link12 + + Name: ng1_vpn_as43069 Type: eiface ID: 0000027f Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link14 + + Name: ng2_vpn_as43069 Type: eiface ID: 0000028a Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether as43069bridge bridge 00000011 link4 + + Name: ng0_git Type: eiface ID: 0000008b Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link6 + + Name: ng2_sade Type: eiface ID: 0000018c Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether pubbridge bridge 00000038 link6 + + Name: ng1_kratos_admi Type: eiface ID: 00000395 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link19 + + Name: ng1_git Type: eiface ID: 00000097 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link5 + + Name: ng1_barrel Type: eiface ID: 0000049d Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link23 + + Name: ng0_mgmt Type: eiface ID: 000001a0 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link13 + + Name: ng0_deterior Type: eiface ID: 000002a0 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link15 + + Name: ngeth57 Type: eiface ID: 000004a3 Num hooks: 0 + + Name: ng0_matrix Type: eiface ID: 000000a5 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link7 + + Name: ngeth48 Type: eiface ID: 000003b1 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link21 + + Name: ng1_matrix Type: eiface ID: 000000b2 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link6 + + Name: ngctl39192 Type: socket ID: 000004b4 Num hooks: 0 + + Name: ng1_mgmt Type: eiface ID: 000001b4 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link13 + + Name: ng0_ca Type: eiface ID: 000002b7 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link16 + + Name: ng0_net Type: eiface ID: 000000c1 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link8 + + Name: ng2_mgmt Type: eiface ID: 000001c2 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether pubbridge bridge 00000038 link7 + + Name: ng1_ca Type: eiface ID: 000002cd Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link15 + + Name: ng0_kraui Type: eiface ID: 000003ce Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link22 + + Name: ng1_net Type: eiface ID: 000000cf Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link7 + + Name: ng2_net Type: eiface ID: 000000d8 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether as43069bridge bridge 00000011 link2 + + Name: ng0_grafana Type: eiface ID: 000002e5 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link17 + + Name: ng1_kraui Type: eiface ID: 000003e9 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link20 + + Name: ng0_front Type: eiface ID: 000000f7 Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether localbridge bridge 0000000a link9 + + Name: ng1_grafana Type: eiface ID: 000002fc Num hooks: 1 + Local hook Peer name Peer type Peer ID Peer hook + ---------- --------- --------- ------- --------- + ether tincbridge bridge 0000002b link16 diff --git a/commands/freebsd/netgraph/_netgraph.sh b/commands/freebsd/netgraph/_netgraph.sh new file mode 100644 index 0000000..d30ff76 --- /dev/null +++ b/commands/freebsd/netgraph/_netgraph.sh @@ -0,0 +1,7 @@ +include_cmd_dir "../_freebsd.sh" + +netgraph_prerun() { + freebsd_ensure + freebsd_ensure_kernel_module_loaded netgraph + ensure_command_exists ngctl +} diff --git a/commands/freebsd/netgraph/list-nodes b/commands/freebsd/netgraph/list-nodes new file mode 100755 index 0000000..4ebcd63 --- /dev/null +++ b/commands/freebsd/netgraph/list-nodes @@ -0,0 +1,111 @@ +#!./cloyster +include_cmd_dir '_netgraph.sh' + +awkk=$(cat <<AWK +BEGIN { + RS="#"; + FS="|"; +} +{ print $1; } +AWK +) + +_netgraph_list_reformat() { + _input="${1}" + echo "${_input}" | \ + tail -n +2 | \ + grep -v "Local hook" | \ + grep -v "\----------" | \ + sed -e 's:^ ::g' \ + -e 's:Name\: ::g' -e 's:Type\: ::g' -e 's:ID\: ::g' -e 's:Num hooks\: ::g' | \ + tr '\n' ':' | \ + sed -e "s/::/#/g" | \ + awk '{gsub(/ {2,}/, "|")}; $1' | \ + sed -e "s/|$//g" -e "s/|:$//g" | \ + FS='[:]' awk -F'[:]' "${awkk}" + #) || fail "awk exited: $?" +} + +_netgraph_list_json= +_netgraph_list_to_json() { + if [ -z "${_netgraph_list_json}" ]; then + _input="${1}" + _ifs=$IFS + _json=$(jo nodes="{}") + for _line in ${_input}; do + _at=0; _node=; _nodename= + IFS=: + for _row in ${_line}; do + _at=$((_at + 1)) + IFS='|' + set -- "" + _hook=; + for _field in ${_row}; do set -- "${@}" "${_field}"; done + if [ "${_at}" = "1" ]; then + _nodename="${2}" + _node=$(jo node="${_nodename}" type="${3}" id="${4}" links_count="${5}" hooks=[]) + else + _hook=$(jo hook="${2}" peer_name="${3}" peer_type="${4}" peer_id="${5}" peer_hook="${6}") + _node=$(echo "$_node" | jo -f - hooks[]="${_hook}") + fi + done + _json=$(echo "$_json" | jo -d. -f - "nodes.${_nodename}=${_node}") + done + IFS=$_ifs + _netgraph_list_json="${_json}" + else + "${_netgraph_list_json}" + fi + echo "${_netgraph_list_json}" +} + +RUN + netgraph_prerun + list="$(ngctl list -l)" + reformatted=$(_netgraph_list_reformat "${list}") + json=$(_netgraph_list_to_json "${reformatted}") + return_result "${json}" +ENDR + +_test "awk" +TEST + input="$(cat "${CLOYSTER_COMMAND_DIR}/.mock.ngctl.list.l.txt")" + assert_exited $? 0 "could not read mock data file" + assert "test -n \"${input}\"" + (echo "$input" | grep "There are 66 total nodes" >/dev/null) + assert_exited $? 0 "mock input does not contains banner" + (echo "$input" | grep "ng0_" >/dev/null) + assert_exited $? 0 "mock input does not contains ng0_" + + reformatted=$(_netgraph_list_reformat "${input}") + + (echo "$reformatted" | grep "There are 66 total nodes" >/dev/null); s=$? + assert_exited "$s" 1 "reformat output contains banner" + + (echo "$reformatted" | grep "ng0" > /dev/null); s=$? + assert_exited $s 0 "reformat output does not contains ng0" + + json=$(_netgraph_list_to_json "${reformatted}") + + (echo "${json}" | jq >/dev/null) + assert_exited $? 0 "json couldn't get parsed by jq" + + # shellcheck disable=SC2034 + count=$(echo "${json}" | jq '[.nodes[].node] | length') + assert_exited $? 0 "json query failed" + assert "echo \"\${count}\"" "67" "should contain 67 nodes" + +# assert "$(echo ${json}) | jq '[.nodes[].node] | length'" "67" "should contain 67 nodes" +ENDT + +_test "runs on freebsd" .tag:integration .if:freebsd .skip:FIXME +TEST + # ( /bin/su -l $(whoami) /bin/sh ${CLOYSTER_COMMAND_FILE} ) + exit 1 +ENDT + +_test "doesnt run on freebsd" .tag:integration .not:freebsd .refute +TEST + ( ${CLOYSTER_COMMAND_FILE} ) +ENDT + @@ -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" +} diff --git a/vendor/shellspec b/vendor/shellspec new file mode 160000 +Subproject 22a80af088c939e03599821b7e9721b51cb1bd8 |