123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293 |
- #!/bin/bash
-
- shellfu import pretty
- shellfu import isa
- shellfu import termcolors
-
- #
- # Just A[nother] Test assert and logging library
- #
- #
- # COMMON ARGUMENTS
- # ================
- #
- # Assert functions share following parameters:
- #
- # * `-b BEID` Behavior Evidence ID; is a Simple Name (see STRINGS)
- # associated with a particular behavior. If provided, BEID is merely
- # included within the log, see BEHAVIOR EVIDENCE ID section for more
- # details.
- #
- # * `-c CASEID` Case ID; together with $JAT__TEST_ID can be used to
- # uniquely identify a particular test case, with granularity up to
- # single assert. CASEID set here is joined with any CASEIDs set
- # by active phase.
- #
- # * `-h HINT` Human-readable description used to describe meaning of
- # the assertion to reader. Note that HINT should be worded in such
- # way that it makes claim about the reality that matches expectation.
- # That is, to reader of the log, a passing assert tells true, but
- # a failing assert "lies".
- #
- # Phase start functions share following parameters:
- #
- # * `-C DIR` Change to directory DIR for duration of the phase.
- #
- # * `-c CASEID` Inherit CASEID as Case ID to all asserts in this
- # phase. Individual asserts can extend the final case id for
- # extra granularity and profit.
- #
- #
- # STRINGS
- # =======
- #
- # Many strings, when passed to jat functions, are internally used
- # as paths, prefixes or identifiers. For this reason, they may be
- # restricted to contain only "safe" chars. Following classes are
- # defined:
- #
- # * *simple word* - must begin with letter or underscore; second
- # and further characters may be letter, digit or underscore.
- #
-
- #
- # Path to result directory
- #
- JAT__DIR=${JAT__DIR:-/var/tmp/jat}
-
- #
- # Path to result log
- #
- # If unset, another identical log is *still* created under a unique
- # subdirectory of $JAT__DIR. Set this if you want to have log at
- # a predictable path. This can be useful eg. if you want to send
- # log into a named pipe.
- #
- JAT__YLOG=${JAT__YLOG:-}
-
- #
- # Behavior Evidence ID namespace
- #
- # All BEIDs will be prefixed by this namespace qualifier,
- # and meaning of them must be defined within that namespace.
- #
- JAT__BEID_NS=${JAT__BEID_NS:-_jat_anon_beid_ns_}
-
- #
- # Test name
- #
- JAT__TEST_ID=${JAT__TEST_ID:-_jat_anon_test_}
-
- #
- # Test version
- #
- JAT__TEST_VERSION=${JAT__TEST_VERSION:-_jat_no_test_version_}
-
- #
- # Path to main logfile
- #
- __JAT__SDIR=
-
- #
- # Colored words for `jat__show_*` functions
- #
- __JAT__SWORD_END=${TERMCOLORS_LBLACK}END${TERMCOLORS_NONE}
- __JAT__SWORD_FAIL=${TERMCOLORS_RED}FAIL${TERMCOLORS_NONE}
- __JAT__SWORD_FINALIZE=${TERMCOLORS_LBLACK}FINALIZE${TERMCOLORS_NONE}
- __JAT__SWORD_PASS=${TERMCOLORS_GREEN}PASS${TERMCOLORS_NONE}
- __JAT__SWORD_PHASE=${TERMCOLORS_YELLOW}phase${TERMCOLORS_NONE}
- __JAT__SWORD_RELOAD=${TERMCOLORS_LBLUE}RELOAD${TERMCOLORS_NONE}
- __JAT__SWORD_SESSION=${TERMCOLORS_YELLOW}session${TERMCOLORS_NONE}
- __JAT__SWORD_START=${TERMCOLORS_LBLACK}START${TERMCOLORS_NONE}
- __JAT__SWORD_SUT=${TERMCOLORS_YELLOW}sut${TERMCOLORS_NONE}
- __JAT__SWORD_TERROR=${TERMCOLORS_RED}ERROR${TERMCOLORS_NONE}
- __JAT__SWORD_TEST=${TERMCOLORS_YELLOW}test${TERMCOLORS_NONE}
- __JAT__SWORD_TINFO=${TERMCOLORS_LBLACK}INFO${TERMCOLORS_NONE}
- __JAT__SWORD_TWARNING=${TERMCOLORS_RED}WARNING${TERMCOLORS_NONE}
-
-
- #
- # Make result log deterministic?
- #
- # Determines whether result should be made deterministic. Currently
- # this means that instead of wall clock time, timestamps will be simply
- # ordinal stamps like `time-1`, `time-2`...
- #
- # This makes test log deterministic, enabling comparison of multiple
- # runs against each other or against fixed oracle. The disadvantage
- # is that you can't assess performance data from the log.
- #
- __JAT__DETERMINISTIC=true
-
- #
- # Log format version
- #
- __JAT__LOG_FMT='jat/0.0'
-
- #
- # Self version (in variable to enable overriding in unit tests)
- #
- __JAT__SELF_VERSION=__MKIT_PROJ_VERSION__
-
- jat__cmd() {
- #
- # Assert that command $@ returns zero (or other)
- #
- # Usage:
- #
- # jat__cmd [-o R_OUT] [-e R_ERR] [-s R_ESF] [-S ES_EXPR] [--] CMD [ARG]..
- #
- # Run CMD with all ARGs and if exit status is zero, announce assertion
- # success (PASS), otherwise announce assertion failure (FAIL).
- #
- # Exit status expectation can be changed from zero to ES_EXPR, which
- # must be in form of comma-separated list of integer values or ranges
- # thereof. E, g. expression '0,10-20' would be a valid expression,
- # matching 0, 11, 20, but not 2 or 21.
- #
- # If R_ESF is passed, it will be treated as path to file where exit status
- # of CODE is written. This is recommended over consulting $?, since the
- # latter is not reliable; see below.
- #
- # If path R_OUT or R_ERR are provided, standard output and standard
- # error are saved to respective files.
- #
- # See COMMON ARGUMENTS section for common assert options.
- #
- # Exit status of the this function normally will, but IS NOT GUARRANTEED TO
- # correspond to the exit status of the CODE. For example, if the function
- # call is incomplete, the exit status will be 2.
- #
- local __jat__cmd=()
- local __jat__r_es
- local __jat__o_es=0
- local __jat__hint
- local __jat__beids=()
- local __jat__caseid
- local __jat__etype=TEST_ERROR
- local __jat__r_out
- local __jat__r_err
- local __jat__r_esf
- #
- # NOTE: names need to be qualified because they might interfere
- # with the actual execution of the assert command.
- #
- while true; do case $1 in
- -b) __jat__beids+=("$2"); shift 2 || { __jat__usage "missing BEID"; return 2; } ;;
- -c) __jat__caseid="$2"; shift 2 || { __jat__usage "missing CASEID"; return 2; } ;;
- -h) __jat__hint=$2; shift 2 || { __jat__usage "missing HINT"; return 2; } ;;
- -e) __jat__r_err=$2; shift 2 || { __jat__usage "missing R_ERR"; return 2; } ;;
- -o) __jat__r_out=$2; shift 2 || { __jat__usage "missing R_OUT"; return 2; } ;;
- -s) __jat__r_esf=$2; shift 2 || { __jat__usage "missing R_ESF"; return 2; } ;;
- -S) __jat__o_es=$2; shift 2 || { __jat__usage "missing ES_EXPR"; return 2; } ;;
- --) shift; break ;;
- *) break ;;
- esac done
- __jat__cmd=("$@")
- test -n "${__jat__cmd[*]}" || { __jat__usage "no CMD?"; return 2; }
- test -n "$__jat__hint" || __jat__hint="${__jat__cmd[*]}"
- debug -v __jat__cmd __jat__o_es __jat__hint __jat__beids
- jat__log_info "CMD: ${__jat__cmd[*]}"
- case ${#__jat__r_out}:${#__jat__r_err} in
- 0:0) "${__jat__cmd[@]}" ;;
- 0:*) "${__jat__cmd[@]}" 2>"$__jat__r_err" ;;
- *:0) "${__jat__cmd[@]}" >"$__jat__r_out" ;;
- *:*) "${__jat__cmd[@]}" >"$__jat__r_out" 2>"$__jat__r_err" ;;
- esac; __jat__r_es=$?
- debug -v __jat__r_es __jat__r_esf
- if test -n "$__jat__r_esf"; then
- echo $__jat__r_es > "$__jat__r_esf" \
- || jat__log_error "error writing R_ESF file: $__jat__r_esf"
- fi
- if __jat__es_match "$__jat__o_es" "$__jat__r_es"; then
- __jat__etype=PASS
- else
- __jat__etype=FAIL
- fi
- __jat__assert $__jat__etype "$__jat__hint" "$__jat__caseid" "${__jat__beids[@]}" \
- -- "t.cmd=${__jat__cmd[*]}" "o.es_expr=$__jat__o_es" "r.es=$__jat__r_es"
- return "$__jat__r_es"
- }
-
- jat__cmp() {
- #
- # Assert that value $@ returns zero (or other)
- #
- # Usage:
- #
- # jat__cmp [assert-options] RVAL OP OVAL
- #
- # Compare RVAL (result value) to OVAL (oracle value) and announce
- # assertion success (PASS) or assertion failure (FAIL).
- #
- # OP can be any of `eq`, `ne`, `lt`, `gt`, `le`, `ge` for numeric values,
- # `==` and `re` for string values. In case of `re`, OVAL is expected to
- # be basic regular expression and is matched against whole string (ie.
- # anchors `^` and `$` are included automatically.
- #
- # Note that in most cases using jat__cmd() with `test` or `grep` commands
- # is all you need.
- #
- # See COMMON ARGUMENTS section for common assert options.
- #
- local RVal # "result" value
- local Op # operator
- local OVal # "oracle" value
- local hint # log hint
- local caseid # case id
- local beids=() # Behavior Evidence IDs
- local cmpes # comparison exit status
- while true; do case $1 in
- -b) beids+=("$2"); shift 2 || { __jat__usage "missing BEID"; return 2; } ;;
- -c) caseid="$2"; shift 2 || { __jat__usage "missing CASEID"; return 2; } ;;
- -h) hint=$2; shift 2 || { __jat__usage "missing HINT"; return 2; } ;;
- *) break ;;
- esac done
- RVal=$1; Op=$2; OVal=$3
- test -n "$RVal" || { __jat__usage "no RVAL?"; return 2; }
- test -n "$Op" || { __jat__usage "no OP?"; return 2; }
- test -n "$OVal" || { __jat__usage "no OVAL?"; return 2; }
- test -n "$hint" || hint="$RVal $Op $OVal"
- debug -v RVal Op OVal hint beids
- __jat__cmp_match; cmpes=$?
- case $cmpes in
- 0)
- __jat__assert PASS "$hint" "$caseid" "${beids[@]}" \
- -- "r.val=$RVal" "t.op=$Op" "o.val=$OVal"
- ;;
- 1)
- __jat__assert FAIL "$hint" "$caseid" "${beids[@]}" \
- -- "r.val=$RVal" "t.op=$Op" "o.val=$OVal"
- ;;
- *)
- __jat__usage "bad syntax: $RVal $Op $OVal"
- return 2
- ;;
- esac
- }
-
- jat__eval() {
- #
- # Assert that command in code $1 returns zero (or other)
- #
- # Usage:
- #
- # jat__eval [-s R_ESF] [-S ES_EXPR] [--] CODE
- #
- # Run CODE using eval builtin and if exit status is zero, announce assertion
- # success (PASS), otherwise announce assertion failure (FAIL).
- #
- # Exit status expectation can be changed from zero to ES_EXPR, which
- # must be in form of comma-separated list of integer values or ranges
- # thereof. E, g. expression '0,10-20' would be a valid expression,
- # matching 0, 11, 20, but not 2 or 21.
- #
- # If R_ESF is passed, it will be treated as path to file where exit status
- # of CODE is written. This is recommended over consulting $?, since the
- # latter is not reliable; see below.
- #
- # This is similar to jat__cmd(), except that code is checked against syntax
- # errors and user is given full control over redirections.
- #
- # See COMMON ARGUMENTS section for common assert options.
- #
- # Exit status of the this function normally will, but IS NOT GUARRANTEED TO
- # correspond to the exit status of the CODE. For example, if the function
- # call is incomplete, the exit status will be 2. If there is syntax error
- # in CODE, the exit status will be 3.
- #
- local __jat__caseid
- local __jat__code=""
- local __jat__r_es
- local __jat__r_esf
- local __jat__o_es=0
- local __jat__hint
- local __jat__beids=()
- local __jat__etype=TEST_ERROR
- #
- # NOTE: names need to be qualified because they might interfere
- # with the actual execution of the assert command.
- #
- while true; do case $1 in
- -b) __jat__beids+=("$2"); shift 2 || { __jat__usage "missing BEID"; return 2; } ;;
- -c) __jat__caseid="$2"; shift 2 || { __jat__usage "missing CASEID"; return 2; } ;;
- -h) __jat__hint=$2; shift 2 || { __jat__usage "missing HINT"; return 2; } ;;
- -s) __jat__r_esf=$2; shift 2 || { __jat__usage "missing R_ESF"; return 2; } ;;
- -S) __jat__o_es=$2; shift 2 || { __jat__usage "missing ES_EXPR"; return 2; } ;;
- --) shift; break ;;
- *) break ;;
- esac done
- __jat__code=$1
- test -n "$__jat__code" || { __jat__usage "no CODE?"; return 2; }
- test -n "$2" && { __jat__usage "extra args!: $*"; return 2; }
- test -n "$__jat__hint" || __jat__hint="$__jat__code"
- debug -v __jat__code __jat__o_es __jat__hint __jat__beids
- bash -n <<<"$__jat__code" || {
- jat__log_error "syntax error in test code: $__jat__code"
- return 3
- }
- jat__log_info "CODE: $__jat__code"
- eval "$__jat__code"; __jat__r_es=$?
- if test -n "$__jat__r_esf"; then
- echo $__jat__r_es > "$__jat__r_esf" \
- || jat__log_error "error writing R_ESF file: $__jat__r_esf"
- fi
- debug -v __jat__r_es
- if __jat__es_match "$__jat__o_es" "$__jat__r_es"; then
- __jat__etype=PASS
- else
- __jat__etype=FAIL
- fi
- __jat__assert $__jat__etype "$__jat__hint" "$__jat__caseid" "${__jat__beids[@]}" \
- -- "t.code=$__jat__code" "o.es_expr=$__jat__o_es" "r.es=$__jat__r_es"
- return "$__jat__r_es"
- }
-
- jat__fail() {
- #
- # Assert test failure
- #
- # Usage:
- #
- # jat__fail [assert-options]
- #
- # Simply log event that an assert has failed. Use other assert functions
- # if you can, since they probably provide more useful information to log
- # reader.
- #
- # See COMMON ARGUMENTS section for common assert options.
- #
- local hint # log hint
- local caseid # case ID
- local beids=() # Behavior Evidence IDs
- while true; do case $1 in
- -b) beids+=("$2"); shift 2 || { __jat__usage "missing BEID"; return 2; } ;;
- -c) caseid="$2"; shift 2 || { __jat__usage "missing CASEID"; return 2; } ;;
- -h) hint=$2; shift 2 || { __jat__usage "missing HINT"; return 2; } ;;
- *) break ;;
- esac done
- test -n "$1" && { __jat__usage "extra arguments: $*"; return 2; }
- debug -v hint beids
- __jat__assert FAIL "$hint" "$caseid" "${beids[@]}"
- }
-
- jat__filebackup() {
- #
- # Back up file $1
- #
- # Usage:
- #
- # jat__filebackup [-n NS] [-c] [--] PATH
- #
- # Back up PATH aside to be able to restore it later using
- # jat__filerestore().
- #
- # Provide `-c` switch to ensure that path is removed before restoring.
- # If NS is given, it must be "simple word" (see STRINGS section).
- #
- local path
- local abspath
- local ns=_jat_anon_backupns_
- local store
- local dest
- local clean=false
- local pdig
- local nshint
- while true; do case $1 in
- --) shift; break ;;
- -c|--clean)
- clean=true
- shift ;;
- -n|--namespace)
- ns=$2; nshint="(NS=$ns) "
- shift 2 || { __jat__usage "no NS?"; return 2; }
- ;;
- -*) __jat__usage "unknown argument: $1"; return 2 ;;
- *) break ;;
- esac done
- path=$1
- test -n "$path" || { __jat__usage "no PATH?"; return 2; }
- isa__name "$ns" || { __jat__usage "NS must be simple word"; return 2; }
- abspath=$(readlink -e "$path") || {
- jat__log_error "no such file: $1"
- return 3
- }
- pdig=$(md5sum <<<"$abspath" | cut -d' ' -f1)
- __jat__sd_keya "backup/$ns/rainbow" "$pdig $abspath"
- $clean && __jat__sd_keya "backup/$ns/clean" "$abspath"
- store=$(__jat__sd_path "backup/$ns/store")
- mkdir -p "$store"
- dest="$store/$pdig"
- test -e "$store/$pdig" && {
- jat__log_error "already backed up, giving up: $nshint$path"
- return 2
- }
- cp -ar "$abspath" "$store/$pdig" || {
- jat__log_error "failed to create backup: $abspath"
- return 3
- }
- jat__log_info "backed up: $nshint$path"
- debug -c tree "$(__jat__sd_path backup)"
- }
-
- jat__filerestore() {
- #
- # Restore paths stored by jat__filebackup()
- #
- # Usage:
- #
- # jat__filerestore [-n NS]
- #
- # Restore all paths backed up using jat__filebackup(). Paths
- # are restored in opposite order than backed up.
- #
- local abspath
- local ns=_jat_anon_backupns_
- local store
- local pdig
- local nshint
- local rainbow
- local cleanlog
- while true; do case $1 in
- -n|--namespace)
- ns=$2; nshint="(NS=$ns) "
- shift 2 || { __jat__usage "no NS?"; return 2; }
- ;;
- "") break ;;
- *) __jat__usage "unknown argument: $1"; return 2 ;;
- esac done
- isa__name "$ns" || { __jat__usage "NS must be simple word"; return 2; }
- test -d "$__JAT__SDIR/internal/backup/$ns" || {
- jat__log_error "unknown backup namespace: $ns"
- return 3
- }
- rainbow=$(__jat__sd_path "backup/$ns/rainbow")
- cleanlog=$(__jat__sd_path "backup/$ns/clean")
- store=$(__jat__sd_path "backup/$ns/store")
- while read -r pdig abspath; do
- debug -v pdig abspath
- grep -qxF "$abspath" "$cleanlog" 2>/dev/null && {
- rm -rf "$abspath" || {
- jat__log_error "failed to pre-clean original path: $abspath"
- return 3
- }
- }
- debug -c ls -l "$store"
- cp -Tar "$store/$pdig" "$abspath" || {
- jat__log_error "failed to restore backup: $nshint$abspath"
- return 3
- }
- jat__log_info "restored: $nshint$abspath"
- :
- done <"$rainbow"
- }
-
- jat__sfinish() {
- #
- # Finalize session
- #
- # Log event to announce that log is finalized and remove test
- # session.
- #
- # This is necessary because in order to support persistent multi
- # PID tests (ie, re-booting within test), jat__sinit() will reload
- # leftover session and whole log will be merged.
- #
- local fileas # session id for filing in 'finished' folder
- test -d "$JAT__DIR/session" || {
- __jat__show_error "no active session: no $JAT__DIR/session"
- return 2
- }
- __jat__show_sfinish
- __jat__log_event SINFO "finishing session"
- __jat__writelog <<<"finalized: true"
- __jat__writelog <<<"end: $(__jat__newstamp)"
- fileas=$(__jat__sd_keyr fileas)
- mkdir -p "$JAT__DIR/finished"
- mv "$JAT__DIR/session" "$JAT__DIR/finished/$fileas"
- rm -f "$JAT__DIR/last"
- ln -s "finished/$fileas" "$JAT__DIR/last"
- }
-
- jat__log_error() {
- #
- # Log internal error unrelated to SUT
- #
- local msg=$1
- echo "error" >> "$(__jat__sd_path "llog")"
- echo "error" >> "$(__jat__sd_path "P.llog")"
- __jat__log_event TEST_ERROR "$msg"
- __jat__show_terror "$msg"
- }
-
- jat__log_info() {
- #
- # Log internal info unrelated to SUT
- #
- local msg=$1
- echo "info" >> "$(__jat__sd_path "llog")"
- echo "info" >> "$(__jat__sd_path "P.llog")"
- __jat__log_event SINFO "$msg"
- __jat__show_tinfo "$msg"
- }
-
- jat__log_warning() {
- #
- # Log internal error unrelated to SUT
- #
- local msg=$1
- echo "warning" >> "$(__jat__sd_path "llog")"
- echo "warning" >> "$(__jat__sd_path "P.llog")"
- __jat__log_event TEST_WARNING "$msg"
- __jat__show_twarning "$msg"
- }
-
- jat__pass() {
- #
- # Assert test pass
- #
- # Usage:
- #
- # jat__pass [assert-options]
- #
- # Simply log event that an assert has passed. Use other assert functions
- # if you can, since they probably provide more useful information to log
- # reader.
- #
- # See COMMON ARGUMENTS section for common assert options.
- #
- local hint # log hint
- local beids=() # Behavior Evidence IDs
- local caseid # case ID
- while true; do case $1 in
- -b) beids+=("$2"); shift 2 || { __jat__usage "missing BEID"; return 2; } ;;
- -c) caseid="$2"; shift 2 || { __jat__usage "missing CASEID"; return 2; } ;;
- -h) hint=$2; shift 2 || { __jat__usage "missing HINT"; return 2; } ;;
- *) break ;;
- esac done
- test -n "$1" && { __jat__usage "extra arguments: $*"; return 2; }
- debug -v hint beids
- __jat__assert PASS "$hint" "$caseid" "${beids[@]}"
- }
-
- jat__promise_asserts() {
- #
- # Promise number of asserts will be $1
- #
- local num=$1
- test -n "$num" || {
- __jat__usage "no NUM?"
- return 2
- }
- __jat__log_event PROMISE "assert number: $num" "" \
- -- "num=$num"
- }
-
- jat__sinit() {
- #
- # Initialize session
- #
- # Load active session if found; otherwise initialize new one.
- #
- local reload=false
- mkdir -p "$JAT__DIR" \
- || die "could not initialize JAT__DIR: $JAT__DIR"
- __JAT__SDIR=$JAT__DIR/session
- test -d "$__JAT__SDIR" && reload=true
- mkdir -p "$__JAT__SDIR"
- __jat__sd_keyw fileas "session-$(date +%s-%N)"
- export __JAT__SDIR
- export JAT__YLOG
- debug -v reload __JAT__SDIR
- if $reload; then
- __jat__log_event SINFO "reloaded session" "" \
- -- \
- "JAT__LOG_FMT=$__JAT__LOG_FMT" \
- "JAT__VERSION=$__JAT__SELF_VERSION"
- __jat__show_sinitr
- else
- debug -v __JAT__SDIR JAT__YLOG
- {
- echo "---"
- echo "format: $__JAT__LOG_FMT"
- echo "jat_version: $__JAT__SELF_VERSION"
- echo "test:"
- echo " id: $JAT__TEST_ID"
- echo " version: $JAT__TEST_VERSION"
- echo "start: $(__jat__newstamp)"
- echo "events:"
- } | __jat__writelog
- __jat__pdummy
- __jat__log_event SINFO "started new session"
- __jat__show_sinitn
- fi
- }
-
- jat__stat() {
- #
- # Print session statistic $1
- #
- # Usage:
- # jat__stat sasrtc # session assert count
- # jat__stat spassc # session pass count
- # jat__stat sfailc # session fail count
- # jat__stat pasrtc # phase assert count
- # jat__stat ppassc # phase pass count
- # jat__stat pfailc # phase fail count
- # jat__stat swarc # session warning count
- # jat__stat pwarc # phase warning count
- # jat__stat serrc # session error count
- # jat__stat perrc # phase error count
- #
- # Print statistics about session state.
- #
- local which=$1
- case $which in
- swarc) grep -cxF warning "$(__jat__sd_path "llog")" ;;
- pwarc) grep -cxF warning "$(__jat__sd_path "P.llog")" ;;
- serrc) grep -cxF error "$(__jat__sd_path "llog")" ;;
- perrc) grep -cxF error "$(__jat__sd_path "P.llog")" ;;
- sfailc) grep -cxF FAIL "$(__jat__sd_path "vlog")" ;;
- pfailc) grep -cxF FAIL "$(__jat__sd_path "P.vlog")" ;;
- ppassc) grep -cxF PASS "$(__jat__sd_path "P.vlog")" ;;
- spassc) grep -cxF PASS "$(__jat__sd_path "vlog")" ;;
- pasrtc) grep -cx 'PASS\|FAIL' "$(__jat__sd_path "P.vlog")" ;;
- sasrtc) grep -cx 'PASS\|FAIL' "$(__jat__sd_path "vlog")" ;;
- *) __jat__usage "invalid statistic field: $which"
- return 2 ;;
- esac
- }
-
- jat__pend() {
- #
- # End active phase
- #
- local oldpdir
- oldpdir=$(__jat__sd_keyR P.pdir)
- test -n "$oldpdir" && {
- jat__log_info "changing back from phase dir: $oldpdir"
- popd >/dev/null
- }
- __jat__show_pend
- __jat__pdummy
- __jat__log_event SINFO
- }
-
- jat__pstartd() {
- #
- # Start diagnostic phase named $1
- #
- # See COMMON ARGUMENTS section of this manual.
- #
- __jat__pstart -t diag "$@"
- }
-
- jat__pstartc() {
- #
- # Start cleanup phase named $1
- #
- # See COMMON ARGUMENTS section of this manual.
- #
- __jat__pstart -t cleanup "$@"
- }
-
- jat__pstarts() {
- #
- # Start setup phase named $1
- #
- # See COMMON ARGUMENTS section of this manual.
- #
- __jat__pstart -t setup "$@"
- }
-
- jat__pstartt() {
- #
- # Start test phase named $1
- #
- # See COMMON ARGUMENTS section of this manual.
- #
- __jat__pstart -t test "$@"
- }
-
- jat__submit() {
- #
- # Submit file $1 as part of test result
- #
- # Usage:
- #
- # jat__submit PATH [ALTNAME]
- #
- # Last element of PATH is used as result name, unless alternative
- # name ALTNAME is given, in which case it's used instead.
- #
- local path=$1
- local altname=$2
- local dest=$__JAT__SDIR/results
- local nhint
- test -n "$path" || {
- __jat__usage "no SRC?"
- return 2
- }
- test -n "$altname" && {
- dest="$dest/$altname"
- nhint=" as $altname"
- }
- mkdir -p "$(dirname "$dest")" || {
- jat__log_error "cannot write to result storage: $dest"
- return 3
- }
- jat__log_info "submitting result: $path$nhint"
- cp -ar "$path" "$dest"
- }
-
- __jat__assert() {
- #
- # Make assert event
- #
- # Use this function to create own asserts
- #
- case $1 in
- PASS) __jat__show_pass "$2" ;;
- FAIL) __jat__show_fail "$2" ;;
- esac
- __jat__log_event "$@"
- }
-
- __jat__bumpid() {
- #
- # Create and/or bump ID named $1
- #
- # Print next ID from series named $1. Series starts
- # with 1, so on firts call, value of ID is 1, then 2.
- # etc.
- #
- # Isession is not initialized, ID value will be 0 and
- # return status will be 3.
- #
- local name=$1 # name of id
- local cache # ordinal cache
- local ord # ordinal
- if test -n "$__JAT__SDIR"; then
- cache=$(__jat__sd_path "series/$name")
- else
- echo 0; return 3
- fi
- if test -f "$cache"; then
- ord=$(<"$cache")
- ((ord++))
- else
- ord=1
- fi
- echo $ord >"$cache"
- echo "$name-$ord"
- }
-
- __jat__cmp_match() {
- #
- # True if $RVal matches $OVal by $Op
- #
- case $Op in
- eq|ne|lt|gt|le|ge) test "$RVal" -"$Op" "$OVal" ;;
- ==) test "$RVal" == "$OVal" ;;
- re) grep -qx "$OVal" <<< "$RVal" ;;
- *) return 2 ;;
- esac
- }
-
- __jat__es_match() {
- #
- # True if exit status $1 matches expression $EsExpr
- #
- local expr=$1
- local es=$2
- local part
- for part in ${expr//,/ }; do
- test -n "$part" || continue
- #FIXME: a rather funny implementation (works, though...)
- eval "echo {${part/-/..}}" | grep -qwF "$es" && return 0
- done
- return 1
- }
-
- __jat__log_event() {
- #
- # Pass YAML log event to __jat__writelog()
- #
- # Usage:
- #
- # __jat__log_event ETYPE HINT CASEID [BEID].. -- [KEY=VALUE]..
- #
- # ETYPE can be FAIL, PASS, TEST_ERROR or SINFO (the latter two
- # are for events unrelated to SUT, like internal errors or
- # phase start/end events).
- #
- # HINT and BEIDs are explained in COMMON ARGUMENTS section of this
- # manual.
- #
- # You can provide any amount of KEY=VALUE pairs, meaning of which
- # is specific to every assert function. (The assert function name
- # is auto-detected and logged so that you can infer KEY meanings
- # later.) Each KEY must be simple word, except that prefixes of
- # `t.`, `r.`, and `o.` are allowed to signify that the value logged
- # is relevant to test method, result or oracle, respectively.
- local etype=$1; shift
- local hint=$1; shift
- local acaseid=$1; shift
- local arg
- local beids=()
- local pairs=()
- local real_beids=()
- local caseid="$JAT__TEST_ID"
- local pcaseid
- local reading=beids
- local pair
- local origin=${FUNCNAME[1]}
- pcaseid=$(__jat__sd_keyR P.caseid)
- test -n "$pcaseid" && caseid+=":$pcaseid"
- test -n "$acaseid" && caseid+=":$acaseid"
- case $etype in
- PASS|FAIL|SINFO|TEST_ERROR|TEST_WARNING|PROMISE) : ;;
- *) __jat__show_error "bad ETYPE, changing to TEST_ERROR: $etype"
- etype=TEST_ERROR ;;
- esac
- case $etype:$origin in
- PASS:__jat__assert) : ;;
- FAIL:__jat__assert) : ;;
- TEST_ERROR:__jat__usage) : ;;
- SINFO:jat__sinit) : ;;
- SINFO:jat__sfinish) : ;;
- SINFO:__jat__pstart) : ;;
- SINFO:jat__pend) : ;;
- SINFO:jat__log_info) : ;;
- TEST_ERROR:jat__log_error) : ;;
- TEST_WARNING:jat__log_warning) : ;;
- *) __jat__show_error "illegal call of __jat__log_event: $etype from $origin"
- etype=TEST_ERROR ;;
- esac
- for arg in "$@"; do
- case $reading:$arg in
- *:)
- shift
- ;;
- beids:--)
- shift
- reading=pairs
- ;;
- beids:*)
- __jat__valid_beid "$arg" || {
- beids=(); pairs=(); etype=API_BUG
- hint="bad BEID syntax (must be simple id): '$arg'"
- __jat__show_error "$hint"
- break
- }
- beids+=("$arg")
- shift
- ;;
- pairs:*)
- __jat__valid_pair "$arg" || {
- beids=(); pairs=(); etype=API_BUG
- hint="bad K=V syntax: '$arg'"
- __jat__show_error "$hint"
- break
- }
- pairs+=("$arg")
- shift
- ;;
- esac
- done
- for beid in "${beids[@]}"; do
- case $beid in
- "") : ;;
- *.*) real_beids+=("$beid") ;;
- *) real_beids+=("$JAT__BEID_NS.$beid") ;;
- esac
- done
- echo "$etype" >> "$(__jat__sd_path "vlog")"
- echo "$etype" >> "$(__jat__sd_path "P.vlog")"
- {
- # NOTE: some scalars are printed directly for performance
- # reasons (these will never be null nor will they
- # contain YAML special chars)
- #
- echo "-"
- echo " origin: $origin"
- echo " etype: $etype"
- echo " stamp: $(__jat__newstamp)"
- echo " caseid: $caseid"
- __jat__yamls hint "$hint"
- __jat__yamla beids "${real_beids[@]}"
- __jat__yamld data "${pairs[@]}"
- echo " phase:"
- echo " id: $(__jat__sd_keyr phase)"
- echo " name: $(__jat__sd_keyr P.name)"
- echo " type: $(__jat__sd_keyr P.type)"
- } | sed 's/^/ /' | __jat__writelog
- }
-
- __jat__newstamp() {
- #
- # Create new timestamp
- #
- case $__JAT__DETERMINISTIC in
- true) __jat__bumpid time ;;
- false) date +%s ;;
- *) die "bad value of __JAT__DETERMINISTIC: $__JAT__DETERMINISTIC" ;;
- esac
- }
-
- __jat__pdummy() {
- #
- # Create dummy phase
- #
- __jat__sd_keyw phase "dummy"
- __jat__sd_keyw P.name "_jat_dummy_phase_"
- __jat__sd_keyw P.type "none"
- }
-
- __jat__pstart() {
- #
- # Start phase of type $1 and name $2
- #
- local type # phase name
- local name # ^^ name
- local pdir # ^^ directory
- local pcaseid # ^^ case id, if assigned
- local oldphase # current phase id
- while true; do case $1 in
- -t) type="$2"; shift 2 || return 2 ;;
- -c) pcaseid="$2"; shift 2 || return 2 ;;
- -C) pdir="$2"; shift 2 || return 2 ;;
- *) break ;;
- esac done
- name="${1:-_jat_anon_phase_}"
- oldphase=$(__jat__sd_keyr phase)
- test "$oldphase" == dummy || {
- jat__log_error "old phase not ended; ending automatically: $oldphase"
- jat__pend
- }
- debug -v type name pdir
- __jat__sd_keyw phase "$(__jat__bumpid phasid)"
- __jat__sd_keyw P.type "$type"
- __jat__sd_keyw P.name "$name"
- __jat__sd_keyw P.caseid "$pcaseid"
- __jat__show_pstart "$name"
- __jat__log_event SINFO
- test -n "$pdir" && {
- __jat__sd_keyw P.pdir "$pdir"
- __jat__sd_keyw P.PWD "$PWD"
- jat__log_info "changing directory for phase: $pdir"
- pushd "$pdir" >/dev/null || {
- jat__log_error "failed to change to phase directory: $pdir"
- return 3
- }
- }
- }
-
- __jat__sd_keya() {
- #
- # Append line $2 to session key $1
- #
- local key=$1
- local value=$2
- local path
- path=$(__jat__sd_path "$key")
- echo "$value" >> "$path" || {
- __jat__show_error "error appending session data: key '$key' to '$path'"
- return 3
- }
- }
-
- __jat__sd_keyR() {
- #
- # Read session key $1 if it exists, return 1 otherwise
- #
- local key=$1
- local path
- path=$(__jat__sd_path "$key") || return 1
- cat "$path" || {
- __jat__show_error "error reading session data: key '$key' from '$path'"
- return 3
- }
- }
-
- __jat__sd_keyr() {
- #
- # Read session key $1
- #
- local key=$1
- local path
- path=$(__jat__sd_path "$key")
- cat "$path" || {
- __jat__show_error "error reading session data: key '$key' from '$path'"
- return 3
- }
- }
-
- __jat__sd_keyw() {
- #
- # Write $2 to session key $1
- #
- local key=$1
- local value=$2
- local path
- path=$(__jat__sd_path "$key")
- echo "$value" > "$path" || {
- __jat__show_error "error writing session data: key '$key' to '$path'"
- return 3
- }
- }
-
- __jat__sd_path() {
- #
- # Dereference path to session key $1; true if exists
- #
- # Key starting with 'P.' will be phase-specific.
- #
- local key=$1
- local path="$__JAT__SDIR/internal/"
- case $key in
- "") __jat__show_error "no KEY?"; return 2 ;;
- P.*) path+="phase-$(__jat__sd_keyr phase)/$key" ;;
- *) path+="$key" ;;
- esac
- mkdir -p "${path%/*}" 2>/dev/null || {
- __jat__show_error "could not create key: $path"
- return 2
- }
- echo "$path"
- test -e "$path"
- }
-
- __jat__show() {
- #
- # Show to user and also keep in ansi log
- #
- local msg
- for msg in "$@"; do
- echo -e "$msg" >> "$__JAT__SDIR/log.ansi"
- echo -e "$msg" >&2
- done
- }
-
- __jat__show_fail() {
- #
- # Show assert fail message $1 to stderr
- #
- __jat__show \
- " $__JAT__SWORD_SUT.$__JAT__SWORD_FAIL: $1"
- }
-
- __jat__show_pass() {
- #
- # Show assert fail message $1 to stderr
- #
- __jat__show \
- " $__JAT__SWORD_SUT.$__JAT__SWORD_PASS: $1"
- }
-
- __jat__show_pend() {
- #
- # Show phase end
- #
- local pverd # phase verdict text
- local pname # ^^ name
- local pnhint # ^^ ^^ display
- pname=$(__jat__sd_keyr P.name)
- test "$pname" == "_jat_anon_phase_" || pnhint=" '$pname'"
- case "$(jat__stat pfailc)" in
- 0) pverd="$__JAT__SWORD_PASS" ;;
- *) pverd="$__JAT__SWORD_FAIL" ;;
- esac
- __jat__show \
- "$__JAT__SWORD_PHASE.$__JAT__SWORD_END.$pverd$pnhint" \
- ""
- }
-
- __jat__show_pstart() {
- #
- # Show phase start
- #
- local pname=$1 # phase name
- local pnhint # ^^ ^^ display
- test "$pname" == "_jat_anon_phase_" || pnhint=" '$pname'"
- __jat__show \
- "$__JAT__SWORD_PHASE.$__JAT__SWORD_START$pnhint"
- }
-
- __jat__show_sfinish() {
- #
- # Show message about session $1 finalization to stderr
- #
- local sverd # session verdict text
- case "$(jat__stat sfailc)" in
- 0) sverd="$__JAT__SWORD_PASS" ;;
- *) sverd="$__JAT__SWORD_FAIL" ;;
- esac
- __jat__show \
- "$__JAT__SWORD_SESSION.$__JAT__SWORD_FINALIZE.$sverd"
- }
-
- __jat__show_sinitn() {
- #
- # Show message about session initialization to stderr
- #
- __jat__show \
- "$__JAT__SWORD_TEST.$__JAT__SWORD_TINFO: id: $JAT__TEST_ID" \
- "$__JAT__SWORD_TEST.$__JAT__SWORD_TINFO: version: $JAT__TEST_VERSION" \
- "" \
- "$__JAT__SWORD_SESSION.$__JAT__SWORD_START" \
- ""
- }
-
- __jat__show_sinitr() {
- #
- # Show message about session reload to stderr
- #
- __jat__show \
- "$__JAT__SWORD_TEST.$__JAT__SWORD_TINFO: id: $JAT__TEST_ID" \
- "$__JAT__SWORD_TEST.$__JAT__SWORD_TINFO: version: $JAT__TEST_VERSION" \
- "" \
- "$__JAT__SWORD_SESSION.$__JAT__SWORD_RELOAD" \
- ""
- }
-
- __jat__show_tinfo() {
- #
- # Show test info
- #
- local sp=""
- test "$(__jat__sd_keyr phase)" == dummy || sp=" "
- __jat__show \
- "$sp$__JAT__SWORD_TEST.$__JAT__SWORD_TINFO: $1"
- }
-
- __jat__show_terror() {
- #
- # Show test error
- #
- local sp=""
- test "$(__jat__sd_keyr phase)" == dummy || sp=" "
- __jat__show \
- "$sp$__JAT__SWORD_TEST.$__JAT__SWORD_TERROR: $1"
- }
-
- __jat__show_twarning() {
- #
- # Show test warning
- #
- local sp=""
- test "$(__jat__sd_keyr phase)" == dummy || sp=" "
- __jat__show \
- "$sp$__JAT__SWORD_TEST.$__JAT__SWORD_TWARNING: $1"
- }
-
- __jat__show_error() {
- #
- # Emit general jat warning (not related to SUT)
- #
- warn "jat.ERROR" "$1"
- }
-
- __jat__usage() {
- #
- # Print usage error $1 and hint according to FUNCNAME
- #
- local msg=$1
- local parent=${FUNCNAME[1]}
- local patt
- local patts=()
- __jat__show_error "bad usage: $msg"
- case $parent in
- jat__cmd) patts=("[assert-options] [-o R_OUT] [-e R_ERR] [-s R_ESF] [-S ES_EXPR] [--] CMD [ARG]..") ;;
- jat__eval) patts=("[assert-options] [-s R_ESF] [-S ES_EXPR] [--] CODE") ;;
- jat__cmp) patts=("[assert-options] RVAL OP OVAL") ;;
- jat__fail) patts=("[assert-options]") ;;
- jat__pass) patts=("[assert-options]") ;;
- jat__submit) patts=("SRC [ALTNAME]") ;;
- jat__stat) patts=(spassc sfailc ppassc pfailc) ;;
- esac
- for patt in "${patts[@]}"; do
- __jat__show_error "usage: $parent $patt"
- done
- __jat__log_event TEST_ERROR "bad usage: $parent()" "" \
- -- "PATTERNS=${patts[*]}"
- }
-
- __jat__writelog() {
- #
- # Write logged output (copy if needed)
- #
- case $JAT__YLOG in
- "") cat >> "$__JAT__SDIR/log.yaml" ;;
- -) tee -a "$__JAT__SDIR/log.yaml" ;;
- *) tee -a "$__JAT__SDIR/log.yaml" >> "$JAT__YLOG" ;;
- esac
- }
-
- __jat__valid_beid() {
- #
- # True if $1 is a valid BEID
- #
- local tainted=$1
- grep -qx '[[:alpha:]_][[:alnum:]_.]*' <<<"$tainted"
- }
-
- __jat__valid_pair() {
- #
- # True if $1 is a valid BEID
- #
- local tainted=$1
- grep -qx '\([tor][.]\)\?[[:alpha:]_][[:alnum:]_]*=.*' <<<"$tainted"
- }
-
- __jat__yamls() {
- #
- # Print a scalar field named $1 with value $2
- #
- local name=$1
- local value=$2
- case $value in
- "") echo " $name: ~" ;;
- *) echo " $name: |-"
- echo " $value" ;;
- esac
- }
-
- __jat__yamla() {
- #
- # Print an array field named $1 with array of values $2..
- #
- local name=$1; shift
- local value
- test $# -eq 0 && echo " $name: []" && return 0
- echo " $name:"
- for value in "$@"; do
- case $value in
- "") echo " - ~" ;;
- *) echo " - |"
- echo " $value" ;;
- esac
- done
- }
-
- __jat__yamld() {
- #
- # Print a dict field named $1 and composed of KEY=VALUE pairs $2..
- #
- local name=$1; shift
- local pair
- local key
- local value
- test $# -eq 0 && echo " $name: {}" && return 0
- echo " $name:"
- for pair in "$@"; do
- key=${pair%%=*}; value=${pair#$key=}
- case $value in
- "") echo " $key: ~" ;;
- *) echo " $key: |"
- echo " $value" ;;
- esac
- done
- }
-
- #shellfu module-version=__MKIT_PROJ_VERSION__
|