2
0

batslib.bash 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. #
  2. # batslib.bash
  3. # ------------
  4. #
  5. # The Standard Library is a collection of test helpers intended to
  6. # simplify testing. It contains the following types of test helpers.
  7. #
  8. # - Assertions are functions that perform a test and output relevant
  9. # information on failure to help debugging. They return 1 on failure
  10. # and 0 otherwise.
  11. #
  12. # All output is formatted for readability using the functions of
  13. # `output.bash' and sent to the standard error.
  14. #
  15. source "${BATS_LIB}/batslib/output.bash"
  16. ########################################################################
  17. # ASSERTIONS
  18. ########################################################################
  19. # Fail and display a message. When no parameters are specified, the
  20. # message is read from the standard input. Other functions use this to
  21. # report failure.
  22. #
  23. # Globals:
  24. # none
  25. # Arguments:
  26. # $@ - [=STDIN] message
  27. # Returns:
  28. # 1 - always
  29. # Inputs:
  30. # STDIN - [=$@] message
  31. # Outputs:
  32. # STDERR - message
  33. fail() {
  34. (( $# == 0 )) && batslib_err || batslib_err "$@"
  35. return 1
  36. }
  37. # Fail and display details if the expression evaluates to false. Details
  38. # include the expression, `$status' and `$output'.
  39. #
  40. # NOTE: The expression must be a simple command. Compound commands, such
  41. # as `[[', can be used only when executed with `bash -c'.
  42. #
  43. # Globals:
  44. # status
  45. # output
  46. # Arguments:
  47. # $1 - expression
  48. # Returns:
  49. # 0 - expression evaluates to TRUE
  50. # 1 - otherwise
  51. # Outputs:
  52. # STDERR - details, on failure
  53. assert() {
  54. if ! "$@"; then
  55. { local -ar single=(
  56. 'expression' "$*"
  57. 'status' "$status"
  58. )
  59. local -ar may_be_multi=(
  60. 'output' "$output"
  61. )
  62. local -ir width="$( batslib_get_max_single_line_key_width \
  63. "${single[@]}" "${may_be_multi[@]}" )"
  64. batslib_print_kv_single "$width" "${single[@]}"
  65. batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
  66. } | batslib_decorate 'assertion failed' \
  67. | fail
  68. fi
  69. }
  70. # Fail and display details if the expected and actual values do not
  71. # equal. Details include both values.
  72. #
  73. # Globals:
  74. # none
  75. # Arguments:
  76. # $1 - actual value
  77. # $2 - expected value
  78. # Returns:
  79. # 0 - values equal
  80. # 1 - otherwise
  81. # Outputs:
  82. # STDERR - details, on failure
  83. assert_equal() {
  84. if [[ $1 != "$2" ]]; then
  85. batslib_print_kv_single_or_multi 8 \
  86. 'expected' "$2" \
  87. 'actual' "$1" \
  88. | batslib_decorate 'values do not equal' \
  89. | fail
  90. fi
  91. }
  92. # Fail and display details if `$status' is not 0. Details include
  93. # `$status' and `$output'.
  94. #
  95. # Globals:
  96. # status
  97. # output
  98. # Arguments:
  99. # none
  100. # Returns:
  101. # 0 - `$status' is 0
  102. # 1 - otherwise
  103. # Outputs:
  104. # STDERR - details, on failure
  105. assert_success() {
  106. if (( status != 0 )); then
  107. { local -ir width=6
  108. batslib_print_kv_single "$width" 'status' "$status"
  109. batslib_print_kv_single_or_multi "$width" 'output' "$output"
  110. } | batslib_decorate 'command failed' \
  111. | fail
  112. fi
  113. }
  114. # Fail and display details if `$status' is 0. Details include `$output'.
  115. #
  116. # Optionally, when the expected status is specified, fail when it does
  117. # not equal `$status'. In this case, details include the expected and
  118. # actual status, and `$output'.
  119. #
  120. # Globals:
  121. # status
  122. # output
  123. # Arguments:
  124. # $1 - [opt] expected status
  125. # Returns:
  126. # 0 - `$status' is not 0, or
  127. # `$status' equals the expected status
  128. # 1 - otherwise
  129. # Outputs:
  130. # STDERR - details, on failure
  131. assert_failure() {
  132. (( $# > 0 )) && local -r expected="$1"
  133. if (( status == 0 )); then
  134. batslib_print_kv_single_or_multi 6 'output' "$output" \
  135. | batslib_decorate 'command succeeded, but it was expected to fail' \
  136. | fail
  137. elif (( $# > 0 )) && (( status != expected )); then
  138. { local -ir width=8
  139. batslib_print_kv_single "$width" \
  140. 'expected' "$expected" \
  141. 'actual' "$status"
  142. batslib_print_kv_single_or_multi "$width" \
  143. 'output' "$output"
  144. } | batslib_decorate 'command failed as expected, but status differs' \
  145. | fail
  146. fi
  147. }
  148. # Fail and display details if the expected does not match the actual
  149. # output or a fragment of it.
  150. #
  151. # By default, the entire output is matched. The assertion fails if the
  152. # expected output does not equal `$output'. Details include both values.
  153. #
  154. # When `-l <index>' is used, only the <index>-th line is matched. The
  155. # assertion fails if the expected line does not equal
  156. # `${lines[<index>}'. Details include the compared lines and <index>.
  157. #
  158. # When `-l' is used without the <index> argument, the output is searched
  159. # for the expected line. The expected line is matched against each line
  160. # in `${lines[@]}'. If no match is found the assertion fails. Details
  161. # include the expected line and `$output'.
  162. #
  163. # By default, literal matching is performed. Options `-p' and `-r'
  164. # enable partial (i.e. substring) and extended regular expression
  165. # matching, respectively. Specifying an invalid extended regular
  166. # expression with `-r' displays an error.
  167. #
  168. # Options `-p' and `-r' are mutually exclusive. When used
  169. # simultaneously, an error is displayed.
  170. #
  171. # Globals:
  172. # output
  173. # lines
  174. # Options:
  175. # -l <index> - match against the <index>-th element of `${lines[@]}'
  176. # -l - search `${lines[@]}' for the expected line
  177. # -p - partial matching
  178. # -r - extended regular expression matching
  179. # Arguments:
  180. # $1 - expected output
  181. # Returns:
  182. # 0 - expected matches the actual output
  183. # 1 - otherwise
  184. # Outputs:
  185. # STDERR - details, on failure
  186. # error message, on error
  187. assert_output() {
  188. local -i is_match_line=0
  189. local -i is_match_contained=0
  190. local -i is_mode_partial=0
  191. local -i is_mode_regex=0
  192. # Handle options.
  193. while (( $# > 0 )); do
  194. case "$1" in
  195. -l)
  196. if (( $# > 2 )) && [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
  197. is_match_line=1
  198. local -ri idx="$2"
  199. shift
  200. else
  201. is_match_contained=1;
  202. fi
  203. shift
  204. ;;
  205. -p) is_mode_partial=1; shift ;;
  206. -r) is_mode_regex=1; shift ;;
  207. --) break ;;
  208. *) break ;;
  209. esac
  210. done
  211. if (( is_match_line )) && (( is_match_contained )); then
  212. echo "\`-l' and \`-l <index>' are mutually exclusive" \
  213. | batslib_decorate 'ERROR: assert_output' \
  214. | fail
  215. return $?
  216. fi
  217. if (( is_mode_partial )) && (( is_mode_regex )); then
  218. echo "\`-p' and \`-r' are mutually exclusive" \
  219. | batslib_decorate 'ERROR: assert_output' \
  220. | fail
  221. return $?
  222. fi
  223. # Arguments.
  224. local -r expected="$1"
  225. if (( is_mode_regex == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then
  226. echo "Invalid extended regular expression: \`$expected'" \
  227. | batslib_decorate 'ERROR: assert_output' \
  228. | fail
  229. return $?
  230. fi
  231. # Matching.
  232. if (( is_match_contained )); then
  233. # Line contained in output.
  234. if (( is_mode_regex )); then
  235. local -i idx
  236. for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
  237. [[ ${lines[$idx]} =~ $expected ]] && return 0
  238. done
  239. { local -ar single=(
  240. 'regex' "$expected"
  241. )
  242. local -ar may_be_multi=(
  243. 'output' "$output"
  244. )
  245. local -ir width="$( batslib_get_max_single_line_key_width \
  246. "${single[@]}" "${may_be_multi[@]}" )"
  247. batslib_print_kv_single "$width" "${single[@]}"
  248. batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
  249. } | batslib_decorate 'no output line matches regular expression' \
  250. | fail
  251. elif (( is_mode_partial )); then
  252. local -i idx
  253. for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
  254. [[ ${lines[$idx]} == *"$expected"* ]] && return 0
  255. done
  256. { local -ar single=(
  257. 'substring' "$expected"
  258. )
  259. local -ar may_be_multi=(
  260. 'output' "$output"
  261. )
  262. local -ir width="$( batslib_get_max_single_line_key_width \
  263. "${single[@]}" "${may_be_multi[@]}" )"
  264. batslib_print_kv_single "$width" "${single[@]}"
  265. batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
  266. } | batslib_decorate 'no output line contains substring' \
  267. | fail
  268. else
  269. local -i idx
  270. for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
  271. [[ ${lines[$idx]} == "$expected" ]] && return 0
  272. done
  273. { local -ar single=(
  274. 'line' "$expected"
  275. )
  276. local -ar may_be_multi=(
  277. 'output' "$output"
  278. )
  279. local -ir width="$( batslib_get_max_single_line_key_width \
  280. "${single[@]}" "${may_be_multi[@]}" )"
  281. batslib_print_kv_single "$width" "${single[@]}"
  282. batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}"
  283. } | batslib_decorate 'output does not contain line' \
  284. | fail
  285. fi
  286. elif (( is_match_line )); then
  287. # Specific line.
  288. if (( is_mode_regex )); then
  289. if ! [[ ${lines[$idx]} =~ $expected ]]; then
  290. batslib_print_kv_single 5 \
  291. 'index' "$idx" \
  292. 'regex' "$expected" \
  293. 'line' "${lines[$idx]}" \
  294. | batslib_decorate 'regular expression does not match line' \
  295. | fail
  296. fi
  297. elif (( is_mode_partial )); then
  298. if [[ ${lines[$idx]} != *"$expected"* ]]; then
  299. batslib_print_kv_single 9 \
  300. 'index' "$idx" \
  301. 'substring' "$expected" \
  302. 'line' "${lines[$idx]}" \
  303. | batslib_decorate 'line does not contain substring' \
  304. | fail
  305. fi
  306. else
  307. if [[ ${lines[$idx]} != "$expected" ]]; then
  308. batslib_print_kv_single 8 \
  309. 'index' "$idx" \
  310. 'expected' "$expected" \
  311. 'actual' "${lines[$idx]}" \
  312. | batslib_decorate 'line differs' \
  313. | fail
  314. fi
  315. fi
  316. else
  317. # Entire output.
  318. if (( is_mode_regex )); then
  319. if ! [[ $output =~ $expected ]]; then
  320. batslib_print_kv_single_or_multi 6 \
  321. 'regex' "$expected" \
  322. 'output' "$output" \
  323. | batslib_decorate 'regular expression does not match output' \
  324. | fail
  325. fi
  326. elif (( is_mode_partial )); then
  327. if [[ $output != *"$expected"* ]]; then
  328. batslib_print_kv_single_or_multi 9 \
  329. 'substring' "$expected" \
  330. 'output' "$output" \
  331. | batslib_decorate 'output does not contain substring' \
  332. | fail
  333. fi
  334. else
  335. if [[ $output != "$expected" ]]; then
  336. batslib_print_kv_single_or_multi 8 \
  337. 'expected' "$expected" \
  338. 'actual' "$output" \
  339. | batslib_decorate 'output differs' \
  340. | fail
  341. fi
  342. fi
  343. fi
  344. }
  345. # Fail and display details if the unexpected matches the actual output
  346. # or a fragment of it.
  347. #
  348. # By default, the entire output is matched. The assertion fails if the
  349. # unexpected output equals `$output'. Details include `$output'.
  350. #
  351. # When `-l <index>' is used, only the <index>-th line is matched. The
  352. # assertion fails if the unexpected line equals `${lines[<index>}'.
  353. # Details include the compared line and <index>.
  354. #
  355. # When `-l' is used without the <index> argument, the output is searched
  356. # for the unexpected line. The unexpected line is matched against each
  357. # line in `${lines[<index>]}'. If a match is found the assertion fails.
  358. # Details include the unexpected line, the index where it was found and
  359. # `$output' (with the unexpected line highlighted in it if `$output` is
  360. # longer than one line).
  361. #
  362. # By default, literal matching is performed. Options `-p' and `-r'
  363. # enable partial (i.e. substring) and extended regular expression
  364. # matching, respectively. On failure, the substring or the regular
  365. # expression is added to the details (if not already displayed).
  366. # Specifying an invalid extended regular expression with `-r' displays
  367. # an error.
  368. #
  369. # Options `-p' and `-r' are mutually exclusive. When used
  370. # simultaneously, an error is displayed.
  371. #
  372. # Globals:
  373. # output
  374. # lines
  375. # Options:
  376. # -l <index> - match against the <index>-th element of `${lines[@]}'
  377. # -l - search `${lines[@]}' for the unexpected line
  378. # -p - partial matching
  379. # -r - extended regular expression matching
  380. # Arguments:
  381. # $1 - unexpected output
  382. # Returns:
  383. # 0 - unexpected matches the actual output
  384. # 1 - otherwise
  385. # Outputs:
  386. # STDERR - details, on failure
  387. # error message, on error
  388. refute_output() {
  389. local -i is_match_line=0
  390. local -i is_match_contained=0
  391. local -i is_mode_partial=0
  392. local -i is_mode_regex=0
  393. # Handle options.
  394. while (( $# > 0 )); do
  395. case "$1" in
  396. -l)
  397. if (( $# > 2 )) && [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then
  398. is_match_line=1
  399. local -ri idx="$2"
  400. shift
  401. else
  402. is_match_contained=1;
  403. fi
  404. shift
  405. ;;
  406. -L) is_match_contained=1; shift ;;
  407. -p) is_mode_partial=1; shift ;;
  408. -r) is_mode_regex=1; shift ;;
  409. --) break ;;
  410. *) break ;;
  411. esac
  412. done
  413. if (( is_match_line )) && (( is_match_contained )); then
  414. echo "\`-l' and \`-l <index>' are mutually exclusive" \
  415. | batslib_decorate 'ERROR: refute_output' \
  416. | fail
  417. return $?
  418. fi
  419. if (( is_mode_partial )) && (( is_mode_regex )); then
  420. echo "\`-p' and \`-r' are mutually exclusive" \
  421. | batslib_decorate 'ERROR: refute_output' \
  422. | fail
  423. return $?
  424. fi
  425. # Arguments.
  426. local -r unexpected="$1"
  427. if (( is_mode_regex == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then
  428. echo "Invalid extended regular expression: \`$unexpected'" \
  429. | batslib_decorate 'ERROR: refute_output' \
  430. | fail
  431. return $?
  432. fi
  433. # Matching.
  434. if (( is_match_contained )); then
  435. # Line contained in output.
  436. if (( is_mode_regex )); then
  437. local -i idx
  438. for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
  439. if [[ ${lines[$idx]} =~ $unexpected ]]; then
  440. { local -ar single=(
  441. 'regex' "$unexpected"
  442. 'index' "$idx"
  443. )
  444. local -a may_be_multi=(
  445. 'output' "$output"
  446. )
  447. local -ir width="$( batslib_get_max_single_line_key_width \
  448. "${single[@]}" "${may_be_multi[@]}" )"
  449. batslib_print_kv_single "$width" "${single[@]}"
  450. if batslib_is_single_line "${may_be_multi[1]}"; then
  451. batslib_print_kv_single "$width" "${may_be_multi[@]}"
  452. else
  453. may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" \
  454. | batslib_prefix \
  455. | batslib_mark '>' "$idx" )"
  456. batslib_print_kv_multi "${may_be_multi[@]}"
  457. fi
  458. } | batslib_decorate 'no line should match the regular expression' \
  459. | fail
  460. return $?
  461. fi
  462. done
  463. elif (( is_mode_partial )); then
  464. local -i idx
  465. for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
  466. if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
  467. { local -ar single=(
  468. 'substring' "$unexpected"
  469. 'index' "$idx"
  470. )
  471. local -a may_be_multi=(
  472. 'output' "$output"
  473. )
  474. local -ir width="$( batslib_get_max_single_line_key_width \
  475. "${single[@]}" "${may_be_multi[@]}" )"
  476. batslib_print_kv_single "$width" "${single[@]}"
  477. if batslib_is_single_line "${may_be_multi[1]}"; then
  478. batslib_print_kv_single "$width" "${may_be_multi[@]}"
  479. else
  480. may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" \
  481. | batslib_prefix \
  482. | batslib_mark '>' "$idx" )"
  483. batslib_print_kv_multi "${may_be_multi[@]}"
  484. fi
  485. } | batslib_decorate 'no line should contain substring' \
  486. | fail
  487. return $?
  488. fi
  489. done
  490. else
  491. local -i idx
  492. for (( idx = 0; idx < ${#lines[@]}; ++idx )); do
  493. if [[ ${lines[$idx]} == "$unexpected" ]]; then
  494. { local -ar single=(
  495. 'line' "$unexpected"
  496. 'index' "$idx"
  497. )
  498. local -a may_be_multi=(
  499. 'output' "$output"
  500. )
  501. local -ir width="$( batslib_get_max_single_line_key_width \
  502. "${single[@]}" "${may_be_multi[@]}" )"
  503. batslib_print_kv_single "$width" "${single[@]}"
  504. if batslib_is_single_line "${may_be_multi[1]}"; then
  505. batslib_print_kv_single "$width" "${may_be_multi[@]}"
  506. else
  507. may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" \
  508. | batslib_prefix \
  509. | batslib_mark '>' "$idx" )"
  510. batslib_print_kv_multi "${may_be_multi[@]}"
  511. fi
  512. } | batslib_decorate 'line should not be in output' \
  513. | fail
  514. return $?
  515. fi
  516. done
  517. fi
  518. elif (( is_match_line )); then
  519. # Specific line.
  520. if (( is_mode_regex )); then
  521. if [[ ${lines[$idx]} =~ $unexpected ]] || (( $? == 0 )); then
  522. batslib_print_kv_single 5 \
  523. 'index' "$idx" \
  524. 'regex' "$unexpected" \
  525. 'line' "${lines[$idx]}" \
  526. | batslib_decorate 'regular expression should not match line' \
  527. | fail
  528. fi
  529. elif (( is_mode_partial )); then
  530. if [[ ${lines[$idx]} == *"$unexpected"* ]]; then
  531. batslib_print_kv_single 9 \
  532. 'index' "$idx" \
  533. 'substring' "$unexpected" \
  534. 'line' "${lines[$idx]}" \
  535. | batslib_decorate 'line should not contain substring' \
  536. | fail
  537. fi
  538. else
  539. if [[ ${lines[$idx]} == "$unexpected" ]]; then
  540. batslib_print_kv_single 5 \
  541. 'index' "$idx" \
  542. 'line' "${lines[$idx]}" \
  543. | batslib_decorate 'line should differ' \
  544. | fail
  545. fi
  546. fi
  547. else
  548. # Entire output.
  549. if (( is_mode_regex )); then
  550. if [[ $output =~ $unexpected ]] || (( $? == 0 )); then
  551. batslib_print_kv_single_or_multi 6 \
  552. 'regex' "$unexpected" \
  553. 'output' "$output" \
  554. | batslib_decorate 'regular expression should not match output' \
  555. | fail
  556. fi
  557. elif (( is_mode_partial )); then
  558. if [[ $output == *"$unexpected"* ]]; then
  559. batslib_print_kv_single_or_multi 9 \
  560. 'substring' "$unexpected" \
  561. 'output' "$output" \
  562. | batslib_decorate 'output should not contain substring' \
  563. | fail
  564. fi
  565. else
  566. if [[ $output == "$unexpected" ]]; then
  567. batslib_print_kv_single_or_multi 6 \
  568. 'output' "$output" \
  569. | batslib_decorate 'output equals, but it was expected to differ' \
  570. | fail
  571. fi
  572. fi
  573. fi
  574. }