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

jat.sh.skel 33KB

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