JAT - Just A Testing library https://pagure.io/shellfu-bash-jat

jat.sh.skel 35KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233
  1. #!/bin/bash
  2. shellfu import pretty
  3. shellfu import isa
  4. shellfu import termcolors
  5. #
  6. # Just A[nother] Test assert and logging library
  7. #
  8. #
  9. # COMMON ARGUMENTS
  10. # ================
  11. #
  12. # Assert functions share following parameters:
  13. #
  14. # * `-b BEID` Behavior Evidence ID; is a Simple Name (see STRINGS)
  15. # associated with a particular behavior. If provided, BEID is merely
  16. # included within the log, see BEHAVIOR EVIDENCE ID section for more
  17. # details.
  18. #
  19. # * `-h HINT` Human-readable description used to describe meaning of
  20. # the assertion to reader. Note that HINT should be worded in such
  21. # way that it makes claim about the reality that matches expectation.
  22. # That is, to reader of the log, a passing assert tells true, but
  23. # a failing assert "lies".
  24. #
  25. # Phase start functions share following parameters:
  26. #
  27. # * `-C DIR` Change to directory DIR for duration of the phase.
  28. #
  29. #
  30. # STRINGS
  31. # =======
  32. #
  33. # Many strings, when passed to jat functions, are internally used
  34. # as paths, prefixes or identifiers. For this reason, they may be
  35. # restricted to contain only "safe" chars. Following classes are
  36. # defined:
  37. #
  38. # * *simple word* - must begin with letter or underscore; second
  39. # and further characters may be letter, digit or underscore.
  40. #
  41. #
  42. # Path to result directory
  43. #
  44. JAT__DIR=${JAT__DIR:-/var/tmp/jat}
  45. #
  46. # Path to result log
  47. #
  48. # If unset, another identical log is *still* created under a unique
  49. # subdirectory of $JAT__DIR. Set this if you want to have log at
  50. # a predictable path. This can be useful eg. if you want to send
  51. # log into a named pipe.
  52. #
  53. JAT__YLOG=${JAT__YLOG:-}
  54. #
  55. # Behavior Evidence ID namespace
  56. #
  57. # All BEIDs will be prefixed by this namespace qualifier,
  58. # and meaning of them must be defined within that namespace.
  59. #
  60. JAT__BEID_NS=${JAT__BEID_NS:-_jat_anon_beid_ns_}
  61. #
  62. # Test name
  63. #
  64. JAT__TESTID=${JAT__TESTID:-_jat_anon_test_}
  65. #
  66. # Path to main logfile
  67. #
  68. __JAT__SDIR=
  69. #
  70. # Make result log deterministic?
  71. #
  72. # Determines whether result should be made deterministic. Currently
  73. # this means that instead of wall clock time, timestamps will be simply
  74. # ordinal stamps like `time-1`, `time-2`...
  75. #
  76. # This makes test log deterministic, enabling comparison of multiple
  77. # runs against each other or against fixed oracle. The disadvantage
  78. # is that you can't assess performance data from the log.
  79. #
  80. __JAT__DETERMINISTIC=true
  81. #
  82. # Log format version
  83. #
  84. __JAT__LOG_FMT='jat/0.0'
  85. #
  86. # Self version (in variable to enable overriding in unit tests)
  87. #
  88. __JAT__SELF_VERSION=__MKIT_PROJ_VERSION__
  89. jat__cmd() {
  90. #
  91. # Assert that command $@ returns zero (or other)
  92. #
  93. # Usage:
  94. #
  95. # jat__cmd [-o R_OUT] [-e R_ERR] [-s R_ESF] [-S ES_EXPR] [--] CMD [ARG]..
  96. #
  97. # Run CMD with all ARGs and if exit status is zero, announce assertion
  98. # success (PASS), otherwise announce assertion failure (FAIL).
  99. #
  100. # Exit status expectation can be changed from zero to ES_EXPR, which
  101. # must be in form of comma-separated list of integer values or ranges
  102. # thereof. E, g. expression '0,10-20' would be a valid expression,
  103. # matching 0, 11, 20, but not 2 or 21.
  104. #
  105. # If R_ESF is passed, it will be treated as path to file where exit status
  106. # of CODE is written. This is recommended over consulting $?, since the
  107. # latter is not reliable; see below.
  108. #
  109. # If path R_OUT or R_ERR are provided, standard output and standard
  110. # error are saved to respective files.
  111. #
  112. # See COMMON ARGUMENTS section for common assert options.
  113. #
  114. # Exit status of the this function normally will, but IS NOT GUARRANTEED TO
  115. # correspond to the exit status of the CODE. For example, if the function
  116. # call is incomplete, the exit status will be 2.
  117. #
  118. local __jat__cmd=()
  119. local __jat__r_es
  120. local __jat__o_es=0
  121. local __jat__hint
  122. local __jat__beids=()
  123. local __jat__etype=TEST_ERROR
  124. local __jat__r_out
  125. local __jat__r_err
  126. local __jat__r_esf
  127. #
  128. # NOTE: names need to be qualified because they might interfere
  129. # with the actual execution of the assert command.
  130. #
  131. while true; do case $1 in
  132. -b) __jat__beids+=("$2"); shift 2 || { __jat__usage "missing BEID"; return 2; } ;;
  133. -h) __jat__hint=$2; shift 2 || { __jat__usage "missing HINT"; return 2; } ;;
  134. -e) __jat__r_err=$2; shift 2 || { __jat__usage "missing R_ERR"; return 2; } ;;
  135. -o) __jat__r_out=$2; shift 2 || { __jat__usage "missing R_OUT"; return 2; } ;;
  136. -s) __jat__r_esf=$2; shift 2 || { __jat__usage "missing R_ESF"; return 2; } ;;
  137. -S) __jat__o_es=$2; shift 2 || { __jat__usage "missing ES_EXPR"; return 2; } ;;
  138. --) shift; break ;;
  139. *) break ;;
  140. esac done
  141. __jat__cmd=("$@")
  142. test -n "${__jat__cmd[*]}" || { __jat__usage "no CMD?"; return 2; }
  143. test -n "$__jat__hint" || __jat__hint="${__jat__cmd[*]}"
  144. debug -v __jat__cmd __jat__o_es __jat__hint __jat__beids
  145. jat__log_info "CMD: ${__jat__cmd[*]}"
  146. case ${#__jat__r_out}:${#__jat__r_err} in
  147. 0:0) "${__jat__cmd[@]}" ;;
  148. 0:*) "${__jat__cmd[@]}" 2>"$__jat__r_err" ;;
  149. *:0) "${__jat__cmd[@]}" >"$__jat__r_out" ;;
  150. *:*) "${__jat__cmd[@]}" >"$__jat__r_out" 2>"$__jat__r_err" ;;
  151. esac; __jat__r_es=$?
  152. debug -v __jat__r_es __jat__r_esf
  153. if test -n "$__jat__r_esf"; then
  154. echo $__jat__r_es > "$__jat__r_esf" \
  155. || jat__log_error "error writing R_ESF file: $__jat__r_esf"
  156. fi
  157. if __jat__es_match "$__jat__o_es" "$__jat__r_es"; then
  158. __jat__etype=PASS
  159. else
  160. __jat__etype=FAIL
  161. fi
  162. __jat__assert $__jat__etype "$__jat__hint" "${__jat__beids[@]}" \
  163. -- "t.cmd=${__jat__cmd[*]}" "o.es_expr=$__jat__o_es" "r.es=$__jat__r_es"
  164. return "$__jat__r_es"
  165. }
  166. jat__cmp() {
  167. #
  168. # Assert that value $@ returns zero (or other)
  169. #
  170. # Usage:
  171. #
  172. # jat__cmp [assert-options] RVAL OP OVAL
  173. #
  174. # Compare RVAL (result value) to OVAL (oracle value) and announce
  175. # assertion success (PASS) or assertion failure (FAIL).
  176. #
  177. # OP can be any of `eq`, `ne`, `lt`, `gt`, `le`, `ge` for numeric values,
  178. # `==` and `re` for string values. In case of `re`, OVAL is expected to
  179. # be basic regular expression and is matched against whole string (ie.
  180. # anchors `^` and `$` are included automatically.
  181. #
  182. # Note that in most cases using jat__cmd() with `test` or `grep` commands
  183. # is all you need.
  184. #
  185. # See COMMON ARGUMENTS section for common assert options.
  186. #
  187. local RVal # "result" value
  188. local Op # operator
  189. local OVal # "oracle" value
  190. local hint # log hint
  191. local beids=() # Behavior Evidence IDs
  192. local cmpes # comparison exit status
  193. while true; do case $1 in
  194. -b) beids+=("$2"); shift 2 || { __jat__usage "missing BEID"; return 2; } ;;
  195. -h) hint=$2; shift 2 || { __jat__usage "missing HINT"; return 2; } ;;
  196. *) break ;;
  197. esac done
  198. RVal=$1; Op=$2; OVal=$3
  199. test -n "$RVal" || { __jat__usage "no RVAL?"; return 2; }
  200. test -n "$Op" || { __jat__usage "no OP?"; return 2; }
  201. test -n "$OVal" || { __jat__usage "no OVAL?"; return 2; }
  202. test -n "$hint" || hint="$RVal $Op $OVal"
  203. debug -v RVal Op OVal hint beids
  204. __jat__cmp_match; cmpes=$?
  205. case $cmpes in
  206. 0)
  207. __jat__assert PASS "$hint" "${beids[@]}" \
  208. -- "r.val=$RVal" "t.op=$Op" "o.val=$OVal"
  209. ;;
  210. 1)
  211. __jat__assert FAIL "$hint" "${beids[@]}" \
  212. -- "r.val=$RVal" "t.op=$Op" "o.val=$OVal"
  213. ;;
  214. *)
  215. __jat__usage "bad syntax: $RVal $Op $OVal"
  216. return 2
  217. ;;
  218. esac
  219. }
  220. jat__eval() {
  221. #
  222. # Assert that command in code $1 returns zero (or other)
  223. #
  224. # Usage:
  225. #
  226. # jat__eval [-s R_ESF] [-S ES_EXPR] [--] CODE
  227. #
  228. # Run CODE using eval builtin and if exit status is zero, announce assertion
  229. # success (PASS), otherwise announce assertion failure (FAIL).
  230. #
  231. # Exit status expectation can be changed from zero to ES_EXPR, which
  232. # must be in form of comma-separated list of integer values or ranges
  233. # thereof. E, g. expression '0,10-20' would be a valid expression,
  234. # matching 0, 11, 20, but not 2 or 21.
  235. #
  236. # If R_ESF is passed, it will be treated as path to file where exit status
  237. # of CODE is written. This is recommended over consulting $?, since the
  238. # latter is not reliable; see below.
  239. #
  240. # This is similar to jat__cmd(), except that code is checked against syntax
  241. # errors and user is given full control over redirections.
  242. #
  243. # See COMMON ARGUMENTS section for common assert options.
  244. #
  245. # Exit status of the this function normally will, but IS NOT GUARRANTEED TO
  246. # correspond to the exit status of the CODE. For example, if the function
  247. # call is incomplete, the exit status will be 2. If there is syntax error
  248. # in CODE, the exit status will be 3.
  249. #
  250. local __jat__code=""
  251. local __jat__r_es
  252. local __jat__r_esf
  253. local __jat__o_es=0
  254. local __jat__hint
  255. local __jat__beids=()
  256. local __jat__etype=TEST_ERROR
  257. #
  258. # NOTE: names need to be qualified because they might interfere
  259. # with the actual execution of the assert command.
  260. #
  261. while true; do case $1 in
  262. -b) __jat__beids+=("$2"); shift 2 || { __jat__usage "missing BEID"; return 2; } ;;
  263. -h) __jat__hint=$2; shift 2 || { __jat__usage "missing HINT"; return 2; } ;;
  264. -s) __jat__r_esf=$2; shift 2 || { __jat__usage "missing R_ESF"; return 2; } ;;
  265. -S) __jat__o_es=$2; shift 2 || { __jat__usage "missing ES_EXPR"; return 2; } ;;
  266. --) shift; break ;;
  267. *) break ;;
  268. esac done
  269. __jat__code=$1
  270. test -n "$__jat__code" || { __jat__usage "no CODE?"; return 2; }
  271. test -n "$2" && { __jat__usage "extra args!: $*"; return 2; }
  272. test -n "$__jat__hint" || __jat__hint="$__jat__code"
  273. debug -v __jat__code __jat__o_es __jat__hint __jat__beids
  274. bash -n <<<"$__jat__code" || {
  275. jat__log_error "syntax error in test code: $__jat__code"
  276. return 3
  277. }
  278. jat__log_info "CODE: $__jat__code"
  279. eval "$__jat__code"; __jat__r_es=$?
  280. if test -n "$__jat__r_esf"; then
  281. echo $__jat__r_es > "$__jat__r_esf" \
  282. || jat__log_error "error writing R_ESF file: $__jat__r_esf"
  283. fi
  284. debug -v __jat__r_es
  285. if __jat__es_match "$__jat__o_es" "$__jat__r_es"; then
  286. __jat__etype=PASS
  287. else
  288. __jat__etype=FAIL
  289. fi
  290. __jat__assert $__jat__etype "$__jat__hint" "${__jat__beids[@]}" \
  291. -- "t.code=$__jat__code" "o.es_expr=$__jat__o_es" "r.es=$__jat__r_es"
  292. return "$__jat__r_es"
  293. }
  294. jat__fail() {
  295. #
  296. # Assert test failure
  297. #
  298. # Usage:
  299. #
  300. # jat__fail [assert-options]
  301. #
  302. # Simply log event that an assert has failed. Use other assert functions
  303. # if you can, since they probably provide more useful information to log
  304. # reader.
  305. #
  306. # See COMMON ARGUMENTS section for common assert options.
  307. #
  308. local hint # log hint
  309. local beids=() # Behavior Evidence IDs
  310. while true; do case $1 in
  311. -b) beids+=("$2"); shift 2 || { __jat__usage "missing BEID"; return 2; } ;;
  312. -h) hint=$2; shift 2 || { __jat__usage "missing HINT"; return 2; } ;;
  313. *) break ;;
  314. esac done
  315. test -n "$1" && { __jat__usage "extra arguments: $*"; return 2; }
  316. debug -v hint beids
  317. __jat__assert FAIL "$hint" "${beids[@]}"
  318. }
  319. jat__filebackup() {
  320. #
  321. # Back up file $1
  322. #
  323. # Usage:
  324. #
  325. # jat__filebackup [-n NS] [-c] [--] PATH
  326. #
  327. # Back up PATH aside to be able to restore it later using
  328. # jat__filerestore().
  329. #
  330. # Provide `-c` switch to ensure that path is removed before restoring.
  331. # If NS is given, it must be "simple word" (see STRINGS section).
  332. #
  333. local path
  334. local abspath
  335. local ns=_jat_anon_backupns_
  336. local store
  337. local dest
  338. local clean=false
  339. local pdig
  340. local nshint
  341. while true; do case $1 in
  342. --) shift; break ;;
  343. -c|--clean)
  344. clean=true
  345. shift ;;
  346. -n|--namespace)
  347. ns=$2; nshint="(NS=$ns) "
  348. shift 2 || { __jat__usage "no NS?"; return 2; }
  349. ;;
  350. -*) __jat__usage "unknown argument: $1"; return 2 ;;
  351. *) break ;;
  352. esac done
  353. path=$1
  354. test -n "$path" || { __jat__usage "no PATH?"; return 2; }
  355. isa__name "$ns" || { __jat__usage "NS must be simple word"; return 2; }
  356. abspath=$(readlink -e "$path") || {
  357. jat__log_error "no such file: $1"
  358. return 3
  359. }
  360. pdig=$(md5sum <<<"$abspath" | cut -d' ' -f1)
  361. __jat__sd_keya "backup/$ns/rainbow" "$pdig $abspath"
  362. $clean && __jat__sd_keya "backup/$ns/clean" "$abspath"
  363. store=$(__jat__sd_path "backup/$ns/store")
  364. mkdir -p "$store"
  365. dest="$store/$pdig"
  366. test -e "$store/$pdig" && {
  367. jat__log_error "already backed up, giving up: $nshint$path"
  368. return 2
  369. }
  370. cp -ar "$abspath" "$store/$pdig" || {
  371. jat__log_error "failed to create backup: $abspath"
  372. return 3
  373. }
  374. jat__log_info "backed up: $nshint$path"
  375. debug -c tree "$(__jat__sd_path backup)"
  376. }
  377. jat__filerestore() {
  378. #
  379. # Restore paths stored by jat__filebackup()
  380. #
  381. # Usage:
  382. #
  383. # jat__filerestore [-n NS]
  384. #
  385. # Restore all paths backed up using jat__filebackup(). Paths
  386. # are restored in opposite order than backed up.
  387. #
  388. local abspath
  389. local ns=_jat_anon_backupns_
  390. local store
  391. local pdig
  392. local nshint
  393. local rainbow
  394. local cleanlog
  395. while true; do case $1 in
  396. -n|--namespace)
  397. ns=$2; nshint="(NS=$ns) "
  398. shift 2 || { __jat__usage "no NS?"; return 2; }
  399. ;;
  400. "") break ;;
  401. *) __jat__usage "unknown argument: $1"; return 2 ;;
  402. esac done
  403. isa__name "$ns" || { __jat__usage "NS must be simple word"; return 2; }
  404. test -d "$__JAT__SDIR/internal/backup/$ns" || {
  405. jat__log_error "unknown backup namespace: $ns"
  406. return 3
  407. }
  408. rainbow=$(__jat__sd_path "backup/$ns/rainbow")
  409. cleanlog=$(__jat__sd_path "backup/$ns/clean")
  410. store=$(__jat__sd_path "backup/$ns/store")
  411. while read -r pdig abspath; do
  412. debug -v pdig abspath
  413. grep -qxF "$abspath" "$cleanlog" 2>/dev/null && {
  414. rm -rf "$abspath" || {
  415. jat__log_error "failed to pre-clean original path: $abspath"
  416. return 3
  417. }
  418. }
  419. debug -c ls -l "$store"
  420. cp -Tar "$store/$pdig" "$abspath" || {
  421. jat__log_error "failed to restore backup: $nshint$abspath"
  422. return 3
  423. }
  424. jat__log_info "restored: $nshint$abspath"
  425. :
  426. done <"$rainbow"
  427. }
  428. jat__sfinish() {
  429. #
  430. # Finalize session
  431. #
  432. # Log event to announce that log is finalized and remove test
  433. # session.
  434. #
  435. # This is necessary because in order to support persistent multi
  436. # PID tests (ie, re-booting within test), jat__sinit() will reload
  437. # leftover session and whole log will be merged.
  438. #
  439. local fileas # session id for filing in 'finished' folder
  440. test -d "$JAT__DIR/session" || {
  441. __jat__show_error "no active session: no $JAT__DIR/session"
  442. return 2
  443. }
  444. __jat__show_sfinish
  445. __jat__log_event SINFO "finishing session"
  446. __jat__writelog <<<"finalized: true"
  447. __jat__writelog <<<"end: $(__jat__newstamp)"
  448. fileas=$(__jat__sd_keyr fileas)
  449. mkdir -p "$JAT__DIR/finished"
  450. mv "$JAT__DIR/session" "$JAT__DIR/finished/$fileas"
  451. rm -f "$JAT__DIR/last"
  452. ln -s "finished/$fileas" "$JAT__DIR/last"
  453. }
  454. jat__log_error() {
  455. #
  456. # Log internal error unrelated to SUT
  457. #
  458. local msg=$1
  459. echo "error" >> "$(__jat__sd_path "llog")"
  460. echo "error" >> "$(__jat__sd_path "P.llog")"
  461. __jat__log_event TEST_ERROR "$msg"
  462. __jat__show_terror "$msg"
  463. }
  464. jat__log_info() {
  465. #
  466. # Log internal info unrelated to SUT
  467. #
  468. local msg=$1
  469. echo "info" >> "$(__jat__sd_path "llog")"
  470. echo "info" >> "$(__jat__sd_path "P.llog")"
  471. __jat__log_event SINFO "$msg"
  472. __jat__show_tinfo "$msg"
  473. }
  474. jat__log_warning() {
  475. #
  476. # Log internal error unrelated to SUT
  477. #
  478. local msg=$1
  479. echo "warning" >> "$(__jat__sd_path "llog")"
  480. echo "warning" >> "$(__jat__sd_path "P.llog")"
  481. __jat__log_event TEST_WARNING "$msg"
  482. __jat__show_twarning "$msg"
  483. }
  484. jat__pass() {
  485. #
  486. # Assert test pass
  487. #
  488. # Usage:
  489. #
  490. # jat__pass [assert-options]
  491. #
  492. # Simply log event that an assert has passed. Use other assert functions
  493. # if you can, since they probably provide more useful information to log
  494. # reader.
  495. #
  496. # See COMMON ARGUMENTS section for common assert options.
  497. #
  498. local hint # log hint
  499. local beids=() # Behavior Evidence IDs
  500. while true; do case $1 in
  501. -b) beids+=("$2"); shift 2 || { __jat__usage "missing BEID"; return 2; } ;;
  502. -h) hint=$2; shift 2 || { __jat__usage "missing HINT"; return 2; } ;;
  503. *) break ;;
  504. esac done
  505. test -n "$1" && { __jat__usage "extra arguments: $*"; return 2; }
  506. debug -v hint beids
  507. __jat__assert PASS "$hint" "${beids[@]}"
  508. }
  509. jat__promise_asserts() {
  510. #
  511. # Promise number of asserts will be $1
  512. #
  513. local num=$1
  514. test -n "$num" || {
  515. __jat__usage "no NUM?"
  516. return 2
  517. }
  518. __jat__log_event PROMISE "assert number: $num" \
  519. -- "num=$num"
  520. }
  521. jat__sinit() {
  522. #
  523. # Initialize session
  524. #
  525. # Load active session if found; otherwise initialize new one.
  526. #
  527. local reload=false
  528. mkdir -p "$JAT__DIR" \
  529. || die "could not initialize JAT__DIR: $JAT__DIR"
  530. __JAT__SDIR=$JAT__DIR/session
  531. test -d "$__JAT__SDIR" && reload=true
  532. mkdir -p "$__JAT__SDIR"
  533. __jat__sd_keyw fileas "session-$(date +%s-%N)"
  534. export __JAT__SDIR
  535. export JAT__YLOG
  536. debug -v reload __JAT__SDIR
  537. if $reload; then
  538. __jat__log_event SINFO "reloaded session" \
  539. -- \
  540. "JAT__LOG_FMT=$__JAT__LOG_FMT" \
  541. "JAT__VERSION=$__JAT__SELF_VERSION"
  542. __jat__show_sinitr
  543. else
  544. debug -v __JAT__SDIR JAT__YLOG
  545. {
  546. echo "---"
  547. echo "format: $__JAT__LOG_FMT"
  548. echo "jat_version: $__JAT__SELF_VERSION"
  549. echo "test:"
  550. echo " id: $JAT__TESTID"
  551. echo "start: $(__jat__newstamp)"
  552. echo "events:"
  553. } | __jat__writelog
  554. __jat__pdummy
  555. __jat__log_event SINFO "started new session"
  556. __jat__show_sinitn
  557. fi
  558. }
  559. jat__stat() {
  560. #
  561. # Print session statistic $1
  562. #
  563. # Usage:
  564. # jat__stat sasrtc # session assert count
  565. # jat__stat spassc # session pass count
  566. # jat__stat sfailc # session fail count
  567. # jat__stat pasrtc # phase assert count
  568. # jat__stat ppassc # phase pass count
  569. # jat__stat pfailc # phase fail count
  570. # jat__stat swarc # session warning count
  571. # jat__stat pwarc # phase warning count
  572. # jat__stat serrc # session error count
  573. # jat__stat perrc # phase error count
  574. #
  575. # Print statistics about session state.
  576. #
  577. local which=$1
  578. case $which in
  579. swarc) grep -cxF warning "$(__jat__sd_path "llog")" ;;
  580. pwarc) grep -cxF warning "$(__jat__sd_path "P.llog")" ;;
  581. serrc) grep -cxF error "$(__jat__sd_path "llog")" ;;
  582. perrc) grep -cxF error "$(__jat__sd_path "P.llog")" ;;
  583. sfailc) grep -cxF FAIL "$(__jat__sd_path "vlog")" ;;
  584. pfailc) grep -cxF FAIL "$(__jat__sd_path "P.vlog")" ;;
  585. ppassc) grep -cxF PASS "$(__jat__sd_path "P.vlog")" ;;
  586. spassc) grep -cxF PASS "$(__jat__sd_path "vlog")" ;;
  587. pasrtc) grep -cx 'PASS\|FAIL' "$(__jat__sd_path "P.vlog")" ;;
  588. sasrtc) grep -cx 'PASS\|FAIL' "$(__jat__sd_path "vlog")" ;;
  589. *) __jat__usage "invalid statistic field: $which"
  590. return 2 ;;
  591. esac
  592. }
  593. jat__pend() {
  594. #
  595. # End active phase
  596. #
  597. local oldpdir
  598. oldpdir=$(__jat__sd_keyR P.pdir)
  599. test -n "$oldpdir" && {
  600. jat__log_info "changing back from phase dir: $oldpdir"
  601. popd >/dev/null
  602. }
  603. __jat__show_pend
  604. __jat__pdummy
  605. __jat__log_event SINFO
  606. }
  607. jat__pstartd() {
  608. #
  609. # Start diagnostic phase named $1
  610. #
  611. # See COMMON ARGUMENTS section of this manual.
  612. #
  613. __jat__pstart -t diag "$@"
  614. }
  615. jat__pstartc() {
  616. #
  617. # Start cleanup phase named $1
  618. #
  619. # See COMMON ARGUMENTS section of this manual.
  620. #
  621. __jat__pstart -t cleanup "$@"
  622. }
  623. jat__pstarts() {
  624. #
  625. # Start setup phase named $1
  626. #
  627. # See COMMON ARGUMENTS section of this manual.
  628. #
  629. __jat__pstart -t setup "$@"
  630. }
  631. jat__pstartt() {
  632. #
  633. # Start test phase named $1
  634. #
  635. # See COMMON ARGUMENTS section of this manual.
  636. #
  637. __jat__pstart -t test "$@"
  638. }
  639. jat__submit() {
  640. #
  641. # Submit file $1 as part of test result
  642. #
  643. # Usage:
  644. #
  645. # jat__submit PATH [ALTNAME]
  646. #
  647. # Last element of PATH is used as result name, unless alternative
  648. # name ALTNAME is given, in which case it's used instead.
  649. #
  650. local path=$1
  651. local altname=$2
  652. local dest=$__JAT__SDIR/results
  653. local nhint
  654. test -n "$path" || {
  655. __jat__usage "no SRC?"
  656. return 2
  657. }
  658. test -n "$altname" && {
  659. dest="$dest/$altname"
  660. nhint=" as $altname"
  661. }
  662. mkdir -p "$(dirname "$dest")" || {
  663. jat__log_error "cannot write to result storage: $dest"
  664. return 3
  665. }
  666. jat__log_info "submitting result: $path$nhint"
  667. cp -ar "$path" "$dest"
  668. }
  669. __jat__assert() {
  670. #
  671. # Make assert event
  672. #
  673. # Use this function to create own asserts
  674. #
  675. case $1 in
  676. PASS) __jat__show_pass "$2" ;;
  677. FAIL) __jat__show_fail "$2" ;;
  678. esac
  679. __jat__log_event "$@"
  680. }
  681. __jat__bumpid() {
  682. #
  683. # Create and/or bump ID named $1
  684. #
  685. # Print next ID from series named $1. Series starts
  686. # with 1, so on firts call, value of ID is 1, then 2.
  687. # etc.
  688. #
  689. # Isession is not initialized, ID value will be 0 and
  690. # return status will be 3.
  691. #
  692. local name=$1 # name of id
  693. local cache # ordinal cache
  694. local ord # ordinal
  695. if test -n "$__JAT__SDIR"; then
  696. cache=$(__jat__sd_path "series/$name")
  697. else
  698. echo 0; return 3
  699. fi
  700. if test -f "$cache"; then
  701. ord=$(<"$cache")
  702. ((ord++))
  703. else
  704. ord=1
  705. fi
  706. echo $ord >"$cache"
  707. echo "$name-$ord"
  708. }
  709. __jat__cmp_match() {
  710. #
  711. # True if $RVal matches $OVal by $Op
  712. #
  713. case $Op in
  714. eq|ne|lt|gt|le|ge) test "$RVal" -"$Op" "$OVal" ;;
  715. ==) test "$RVal" == "$OVal" ;;
  716. re) grep -qx "$OVal" <<< "$RVal" ;;
  717. *) return 2 ;;
  718. esac
  719. }
  720. __jat__es_match() {
  721. #
  722. # True if exit status $1 matches expression $EsExpr
  723. #
  724. local expr=$1
  725. local es=$2
  726. local part
  727. for part in ${expr//,/ }; do
  728. test -n "$part" || continue
  729. #FIXME: a rather funny implementation (works, though...)
  730. eval "echo {${part/-/..}}" | grep -qwF "$es" && return 0
  731. done
  732. return 1
  733. }
  734. __jat__log_event() {
  735. #
  736. # Pass YAML log event to __jat__writelog()
  737. #
  738. # Usage:
  739. #
  740. # __jat__log_event ETYPE HINT [BEID].. -- [KEY=VALUE]..
  741. #
  742. # ETYPE can be FAIL, PASS, TEST_ERROR or SINFO (the latter two
  743. # are for events unrelated to SUT, like internal errors or
  744. # phase start/end events).
  745. #
  746. # HINT and BEIDs are explained in COMMON ARGUMENTS section of this
  747. # manual.
  748. #
  749. # You can provide any amount of KEY=VALUE pairs, meaning of which
  750. # is specific to every assert function. (The assert function name
  751. # is auto-detected and logged so that you can infer KEY meanings
  752. # later.) Each KEY must be simple word, except that prefixes of
  753. # `t.`, `r.`, and `o.` are allowed to signify that the value logged
  754. # is relevant to test method, result or oracle, respectively.
  755. local etype=$1; shift
  756. local hint=$1; shift
  757. local arg
  758. local beids=()
  759. local pairs=()
  760. local real_beids=()
  761. local reading=beids
  762. local pair
  763. local origin=${FUNCNAME[1]}
  764. case $etype in
  765. PASS|FAIL|SINFO|TEST_ERROR|TEST_WARNING|PROMISE) : ;;
  766. *) __jat__show_error "bad ETYPE, changing to TEST_ERROR: $etype"
  767. etype=TEST_ERROR ;;
  768. esac
  769. case $etype:$origin in
  770. PASS:__jat__assert) : ;;
  771. FAIL:__jat__assert) : ;;
  772. TEST_ERROR:__jat__usage) : ;;
  773. SINFO:jat__sinit) : ;;
  774. SINFO:jat__sfinish) : ;;
  775. SINFO:__jat__pstart) : ;;
  776. SINFO:jat__pend) : ;;
  777. SINFO:jat__log_info) : ;;
  778. TEST_ERROR:jat__log_error) : ;;
  779. TEST_WARNING:jat__log_warning) : ;;
  780. *) __jat__show_error "illegal call of __jat__log_event: $etype from $origin"
  781. etype=TEST_ERROR ;;
  782. esac
  783. for arg in "$@"; do
  784. case $reading:$arg in
  785. *:)
  786. shift
  787. ;;
  788. beids:--)
  789. shift
  790. reading=pairs
  791. ;;
  792. beids:*)
  793. __jat__valid_beid "$arg" || {
  794. beids=(); pairs=(); etype=API_BUG
  795. hint="bad BEID syntax (must be simple id): '$arg'"
  796. __jat__show_error "$hint"
  797. break
  798. }
  799. beids+=("$arg")
  800. shift
  801. ;;
  802. pairs:*)
  803. __jat__valid_pair "$arg" || {
  804. beids=(); pairs=(); etype=API_BUG
  805. hint="bad K=V syntax: '$arg'"
  806. __jat__show_error "$hint"
  807. break
  808. }
  809. pairs+=("$arg")
  810. shift
  811. ;;
  812. esac
  813. done
  814. for beid in "${beids[@]}"; do
  815. case $beid in
  816. "") : ;;
  817. *.*) real_beids+=("$beid") ;;
  818. *) real_beids+=("$JAT__BEID_NS.$beid") ;;
  819. esac
  820. done
  821. echo "$etype" >> "$(__jat__sd_path "vlog")"
  822. echo "$etype" >> "$(__jat__sd_path "P.vlog")"
  823. {
  824. # NOTE: some scalars are printed directly for performance
  825. # reasons (these will never be null nor will they
  826. # contain YAML special chars)
  827. #
  828. echo "-"
  829. echo " origin: $origin"
  830. echo " etype: $etype"
  831. echo " stamp: $(__jat__newstamp)"
  832. __jat__yamls hint "$hint"
  833. __jat__yamla beids "${real_beids[@]}"
  834. __jat__yamld data "${pairs[@]}"
  835. echo " phase:"
  836. echo " id: $(__jat__sd_keyr phase)"
  837. echo " name: $(__jat__sd_keyr P.name)"
  838. echo " type: $(__jat__sd_keyr P.type)"
  839. } | sed 's/^/ /' | __jat__writelog
  840. }
  841. __jat__newstamp() {
  842. #
  843. # Create new timestamp
  844. #
  845. case $__JAT__DETERMINISTIC in
  846. true) __jat__bumpid time ;;
  847. false) date +%s ;;
  848. *) die "bad value of __JAT__DETERMINISTIC: $__JAT__DETERMINISTIC" ;;
  849. esac
  850. }
  851. __jat__pdummy() {
  852. #
  853. # Create dummy phase
  854. #
  855. __jat__sd_keyw phase "dummy"
  856. __jat__sd_keyw P.name "_jat_dummy_phase_"
  857. __jat__sd_keyw P.type "none"
  858. }
  859. __jat__pstart() {
  860. #
  861. # Start phase of type $1 and name $2
  862. #
  863. local type # phase name
  864. local name # ^^ name
  865. local pdir # ^^ directory
  866. local oldphase # current phase id
  867. while true; do case $1 in
  868. -t) type="$2"; shift 2 || return 2 ;;
  869. -C) pdir="$2"; shift 2 || return 2 ;;
  870. *) break ;;
  871. esac done
  872. name="${1:-_jat_anon_phase_}"
  873. oldphase=$(__jat__sd_keyr phase)
  874. test "$oldphase" == dummy || {
  875. jat__log_error "old phase not ended; ending automatically: $oldphase"
  876. jat__pend
  877. }
  878. debug -v type name pdir
  879. __jat__sd_keyw phase "$(__jat__bumpid phasid)"
  880. __jat__sd_keyw P.type "$type"
  881. __jat__sd_keyw P.name "$name"
  882. __jat__show_pstart "$name"
  883. __jat__log_event SINFO
  884. test -n "$pdir" && {
  885. __jat__sd_keyw P.pdir "$pdir"
  886. __jat__sd_keyw P.PWD "$PWD"
  887. jat__log_info "changing directory for phase: $pdir"
  888. pushd "$pdir" >/dev/null || {
  889. jat__log_error "failed to change to phase directory: $pdir"
  890. return 3
  891. }
  892. }
  893. }
  894. __jat__sd_keya() {
  895. #
  896. # Append line $2 to session key $1
  897. #
  898. local key=$1
  899. local value=$2
  900. local path
  901. path=$(__jat__sd_path "$key")
  902. echo "$value" >> "$path" || {
  903. __jat__show_error "error appending session data: key '$key' to '$path'"
  904. return 3
  905. }
  906. }
  907. __jat__sd_keyR() {
  908. #
  909. # Read session key $1 if it exists, return 1 otherwise
  910. #
  911. local key=$1
  912. local path
  913. path=$(__jat__sd_path "$key") || return 1
  914. cat "$path" || {
  915. __jat__show_error "error reading session data: key '$key' from '$path'"
  916. return 3
  917. }
  918. }
  919. __jat__sd_keyr() {
  920. #
  921. # Read session key $1
  922. #
  923. local key=$1
  924. local path
  925. path=$(__jat__sd_path "$key")
  926. cat "$path" || {
  927. __jat__show_error "error reading session data: key '$key' from '$path'"
  928. return 3
  929. }
  930. }
  931. __jat__sd_keyw() {
  932. #
  933. # Write $2 to session key $1
  934. #
  935. local key=$1
  936. local value=$2
  937. local path
  938. path=$(__jat__sd_path "$key")
  939. echo "$value" > "$path" || {
  940. __jat__show_error "error writing session data: key '$key' to '$path'"
  941. return 3
  942. }
  943. }
  944. __jat__sd_path() {
  945. #
  946. # Dereference path to session key $1; true if exists
  947. #
  948. # Key starting with 'P.' will be phase-specific.
  949. #
  950. local key=$1
  951. local path="$__JAT__SDIR/internal/"
  952. case $key in
  953. "") __jat__show_error "no KEY?"; return 2 ;;
  954. P.*) path+="phase-$(__jat__sd_keyr phase)/$key" ;;
  955. *) path+="$key" ;;
  956. esac
  957. mkdir -p "${path%/*}" 2>/dev/null || {
  958. __jat__show_error "could not create key: $path"
  959. return 2
  960. }
  961. echo "$path"
  962. test -e "$path"
  963. }
  964. __jat__show() {
  965. #
  966. # Show to user and also keep in ansi log
  967. #
  968. local msg
  969. for msg in "$@"; do
  970. echo -e "$msg" >> "$__JAT__SDIR/log.ansi"
  971. echo -e "$msg" >&2
  972. done
  973. }
  974. __jat__show_fail() {
  975. #
  976. # Show assert fail message $1 to stderr
  977. #
  978. __jat__show \
  979. " ${TERMCOLORS_YELLOW}sut${TERMCOLORS_NONE}.${TERMCOLORS_RED}FAIL${TERMCOLORS_NONE}: $1"
  980. }
  981. __jat__show_pass() {
  982. #
  983. # Show assert fail message $1 to stderr
  984. #
  985. __jat__show \
  986. " ${TERMCOLORS_YELLOW}sut${TERMCOLORS_NONE}.${TERMCOLORS_GREEN}PASS${TERMCOLORS_NONE}: $1"
  987. }
  988. __jat__show_pend() {
  989. #
  990. # Show phase end
  991. #
  992. local pverd # phase verdict text
  993. local pname # ^^ name
  994. local pnhint # ^^ ^^ display
  995. pname=$(__jat__sd_keyr P.name)
  996. test "$pname" == "_jat_anon_phase_" || pnhint=" '$pname'"
  997. case "$(jat__stat pfailc)" in
  998. 0) pverd="${TERMCOLORS_GREEN}PASS${TERMCOLORS_NONE}" ;;
  999. *) pverd="${TERMCOLORS_RED}FAIL${TERMCOLORS_NONE}" ;;
  1000. esac
  1001. __jat__show \
  1002. "${TERMCOLORS_YELLOW}phase${TERMCOLORS_NONE}.${TERMCOLORS_LBLACK}END${TERMCOLORS_NONE}.$pverd$pnhint" \
  1003. ""
  1004. }
  1005. __jat__show_pstart() {
  1006. #
  1007. # Show phase start
  1008. #
  1009. local pname=$1 # phase name
  1010. local pnhint # ^^ ^^ display
  1011. test "$pname" == "_jat_anon_phase_" || pnhint=" '$pname'"
  1012. __jat__show \
  1013. "${TERMCOLORS_YELLOW}phase${TERMCOLORS_NONE}.${TERMCOLORS_LBLACK}START${TERMCOLORS_NONE}$pnhint"
  1014. }
  1015. __jat__show_sfinish() {
  1016. #
  1017. # Show message about session $1 finalization to stderr
  1018. #
  1019. local sverd # session verdict text
  1020. case "$(jat__stat sfailc)" in
  1021. 0) sverd="${TERMCOLORS_GREEN}PASS${TERMCOLORS_NONE}" ;;
  1022. *) sverd="${TERMCOLORS_RED}FAIL${TERMCOLORS_NONE}" ;;
  1023. esac
  1024. __jat__show \
  1025. "${TERMCOLORS_YELLOW}session${TERMCOLORS_NONE}.${TERMCOLORS_LBLACK}FINALIZE${TERMCOLORS_NONE}.$sverd"
  1026. }
  1027. __jat__show_sinitn() {
  1028. #
  1029. # Show message about session initialization to stderr
  1030. #
  1031. __jat__show \
  1032. "${TERMCOLORS_YELLOW}session${TERMCOLORS_NONE}.${TERMCOLORS_LBLACK}START${TERMCOLORS_NONE}" \
  1033. ""
  1034. }
  1035. __jat__show_sinitr() {
  1036. #
  1037. # Show message about session reload to stderr
  1038. #
  1039. __jat__show \
  1040. "${TERMCOLORS_YELLOW}session${TERMCOLORS_NONE}.${TERMCOLORS_LBLUE}RELOAD${TERMCOLORS_NONE}" \
  1041. ""
  1042. }
  1043. __jat__show_tinfo() {
  1044. #
  1045. # Show test info
  1046. #
  1047. local sp=""
  1048. test "$(__jat__sd_keyr phase)" == dummy || sp=" "
  1049. __jat__show \
  1050. "$sp${TERMCOLORS_YELLOW}test${TERMCOLORS_NONE}.${TERMCOLORS_LBLACK}INFO${TERMCOLORS_NONE}: $1"
  1051. }
  1052. __jat__show_terror() {
  1053. #
  1054. # Show test error
  1055. #
  1056. local sp=""
  1057. test "$(__jat__sd_keyr phase)" == dummy || sp=" "
  1058. __jat__show \
  1059. "$sp${TERMCOLORS_YELLOW}test${TERMCOLORS_NONE}.${TERMCOLORS_RED}ERROR${TERMCOLORS_NONE}: $1"
  1060. }
  1061. __jat__show_twarning() {
  1062. #
  1063. # Show test warning
  1064. #
  1065. local sp=""
  1066. test "$(__jat__sd_keyr phase)" == dummy || sp=" "
  1067. __jat__show \
  1068. "$sp${TERMCOLORS_YELLOW}test${TERMCOLORS_NONE}.${TERMCOLORS_RED}WARNING${TERMCOLORS_NONE}: $1"
  1069. }
  1070. __jat__show_error() {
  1071. #
  1072. # Emit general jat warning (not related to SUT)
  1073. #
  1074. warn "jat.ERROR" "$1"
  1075. }
  1076. __jat__usage() {
  1077. #
  1078. # Print usage error $1 and hint according to FUNCNAME
  1079. #
  1080. local msg=$1
  1081. local parent=${FUNCNAME[1]}
  1082. local patt
  1083. local patts=()
  1084. __jat__show_error "bad usage: $msg"
  1085. case $parent in
  1086. jat__cmd) patts=("[assert-options] [-e ES_EXPR] [--] CMD [ARG]..") ;;
  1087. jat__cmp) patts=("[assert-options] RVAL OP OVAL") ;;
  1088. jat__fail) patts=("[assert-options]") ;;
  1089. jat__pass) patts=("[assert-options]") ;;
  1090. jat__submit) patts=("SRC [ALTNAME]") ;;
  1091. jat__stat) patts=(spassc sfailc ppassc pfailc) ;;
  1092. esac
  1093. for patt in "${patts[@]}"; do
  1094. __jat__show_error "usage: $parent $patt"
  1095. done
  1096. __jat__log_event TEST_ERROR "bad usage: $parent()" \
  1097. -- "PATTERNS=${patts[*]}"
  1098. }
  1099. __jat__writelog() {
  1100. #
  1101. # Write logged output (copy if needed)
  1102. #
  1103. case $JAT__YLOG in
  1104. "") cat >> "$__JAT__SDIR/log.yaml" ;;
  1105. -) tee -a "$__JAT__SDIR/log.yaml" ;;
  1106. *) tee -a "$__JAT__SDIR/log.yaml" >> "$JAT__YLOG" ;;
  1107. esac
  1108. }
  1109. __jat__valid_beid() {
  1110. #
  1111. # True if $1 is a valid BEID
  1112. #
  1113. local tainted=$1
  1114. grep -qx '[[:alpha:]_][[:alnum:]_.]*' <<<"$tainted"
  1115. }
  1116. __jat__valid_pair() {
  1117. #
  1118. # True if $1 is a valid BEID
  1119. #
  1120. local tainted=$1
  1121. grep -qx '\([tor][.]\)\?[[:alpha:]_][[:alnum:]_]*=.*' <<<"$tainted"
  1122. }
  1123. __jat__yamls() {
  1124. #
  1125. # Print a scalar field named $1 with value $2
  1126. #
  1127. local name=$1
  1128. local value=$2
  1129. case $value in
  1130. "") echo " $name: ~" ;;
  1131. *) echo " $name: |-"
  1132. echo " $value" ;;
  1133. esac
  1134. }
  1135. __jat__yamla() {
  1136. #
  1137. # Print an array field named $1 with array of values $2..
  1138. #
  1139. local name=$1; shift
  1140. local value
  1141. test $# -eq 0 && echo " $name: []" && return 0
  1142. echo " $name:"
  1143. for value in "$@"; do
  1144. case $value in
  1145. "") echo " - ~" ;;
  1146. *) echo " - |"
  1147. echo " $value" ;;
  1148. esac
  1149. done
  1150. }
  1151. __jat__yamld() {
  1152. #
  1153. # Print a dict field named $1 and composed of KEY=VALUE pairs $2..
  1154. #
  1155. local name=$1; shift
  1156. local pair
  1157. local key
  1158. local value
  1159. test $# -eq 0 && echo " $name: {}" && return 0
  1160. echo " $name:"
  1161. for pair in "$@"; do
  1162. key=${pair%%=*}; value=${pair#$key=}
  1163. case $value in
  1164. "") echo " $key: ~" ;;
  1165. *) echo " $key: |"
  1166. echo " $value" ;;
  1167. esac
  1168. done
  1169. }
  1170. #shellfu module-version=__MKIT_PROJ_VERSION__