From 6565a1f7453da061898c4d8e2b740487e62203b2 Mon Sep 17 00:00:00 2001 From: Chad Metcalf Date: Sat, 8 Aug 2020 12:46:22 -0700 Subject: [PATCH] Vendoring bats libraries. Signed-off-by: Chad Metcalf --- lib/bats-assert/.gitignore | 4 + lib/bats-assert/.travis.yml | 3 + lib/bats-assert/LICENSE | 116 +++ lib/bats-assert/README.md | 661 ++++++++++++++++++ lib/bats-assert/load.bash | 30 + lib/bats-assert/package.json | 48 ++ lib/bats-assert/src/assert.bash | 42 ++ lib/bats-assert/src/assert_equal.bash | 42 ++ lib/bats-assert/src/assert_failure.bash | 78 +++ lib/bats-assert/src/assert_line.bash | 248 +++++++ lib/bats-assert/src/assert_output.bash | 197 ++++++ lib/bats-assert/src/assert_success.bash | 44 ++ lib/bats-assert/src/refute.bash | 42 ++ lib/bats-assert/src/refute_line.bash | 271 +++++++ lib/bats-assert/src/refute_output.bash | 199 ++++++ lib/bats-assert/test/assert.bats | 19 + lib/bats-assert/test/assert_equal.bats | 62 ++ lib/bats-assert/test/assert_failure.bats | 75 ++ lib/bats-assert/test/assert_line.bats | 351 ++++++++++ lib/bats-assert/test/assert_output.bats | 285 ++++++++ lib/bats-assert/test/assert_success.bats | 40 ++ lib/bats-assert/test/refute.bats | 18 + lib/bats-assert/test/refute_line.bats | 344 +++++++++ lib/bats-assert/test/refute_output.bats | 230 ++++++ lib/bats-assert/test/test_helper.bash | 27 + lib/bats-support/.gitignore | 4 + lib/bats-support/.travis.yml | 3 + lib/bats-support/CHANGELOG.md | 46 ++ lib/bats-support/LICENSE | 116 +++ lib/bats-support/README.md | 189 +++++ lib/bats-support/load.bash | 3 + lib/bats-support/package.json | 30 + lib/bats-support/src/error.bash | 41 ++ lib/bats-support/src/lang.bash | 73 ++ lib/bats-support/src/output.bash | 279 ++++++++ .../test/50-output-10-batslib_err.bats | 16 + .../50-output-11-batslib_count_lines.bats | 21 + .../50-output-12-batslib_is_single_line.bats | 13 + ...batslib_get_max_single_line_key_width.bats | 21 + .../50-output-14-batslib_print_kv_single.bats | 27 + .../50-output-15-batslib_print_kv_multi.bats | 19 + ...t-16-batslib_print_kv_single_or_multi.bats | 31 + .../test/50-output-17-batslib_prefix.bats | 43 ++ .../test/50-output-18-batslib_mark.bats | 72 ++ .../test/50-output-19-batslib_decorate.bats | 13 + lib/bats-support/test/51-error-10-fail.bats | 16 + .../test/52-lang-10-batslib_is_caller.bats | 88 +++ lib/bats-support/test/test_helper.bash | 6 + 48 files changed, 4646 insertions(+) create mode 100644 lib/bats-assert/.gitignore create mode 100644 lib/bats-assert/.travis.yml create mode 100644 lib/bats-assert/LICENSE create mode 100644 lib/bats-assert/README.md create mode 100644 lib/bats-assert/load.bash create mode 100644 lib/bats-assert/package.json create mode 100644 lib/bats-assert/src/assert.bash create mode 100644 lib/bats-assert/src/assert_equal.bash create mode 100644 lib/bats-assert/src/assert_failure.bash create mode 100644 lib/bats-assert/src/assert_line.bash create mode 100644 lib/bats-assert/src/assert_output.bash create mode 100644 lib/bats-assert/src/assert_success.bash create mode 100644 lib/bats-assert/src/refute.bash create mode 100644 lib/bats-assert/src/refute_line.bash create mode 100644 lib/bats-assert/src/refute_output.bash create mode 100755 lib/bats-assert/test/assert.bats create mode 100755 lib/bats-assert/test/assert_equal.bats create mode 100755 lib/bats-assert/test/assert_failure.bats create mode 100755 lib/bats-assert/test/assert_line.bats create mode 100755 lib/bats-assert/test/assert_output.bats create mode 100755 lib/bats-assert/test/assert_success.bats create mode 100755 lib/bats-assert/test/refute.bats create mode 100755 lib/bats-assert/test/refute_line.bats create mode 100755 lib/bats-assert/test/refute_output.bats create mode 100644 lib/bats-assert/test/test_helper.bash create mode 100644 lib/bats-support/.gitignore create mode 100644 lib/bats-support/.travis.yml create mode 100644 lib/bats-support/CHANGELOG.md create mode 100644 lib/bats-support/LICENSE create mode 100644 lib/bats-support/README.md create mode 100644 lib/bats-support/load.bash create mode 100644 lib/bats-support/package.json create mode 100644 lib/bats-support/src/error.bash create mode 100644 lib/bats-support/src/lang.bash create mode 100644 lib/bats-support/src/output.bash create mode 100755 lib/bats-support/test/50-output-10-batslib_err.bats create mode 100755 lib/bats-support/test/50-output-11-batslib_count_lines.bats create mode 100755 lib/bats-support/test/50-output-12-batslib_is_single_line.bats create mode 100755 lib/bats-support/test/50-output-13-batslib_get_max_single_line_key_width.bats create mode 100755 lib/bats-support/test/50-output-14-batslib_print_kv_single.bats create mode 100755 lib/bats-support/test/50-output-15-batslib_print_kv_multi.bats create mode 100755 lib/bats-support/test/50-output-16-batslib_print_kv_single_or_multi.bats create mode 100755 lib/bats-support/test/50-output-17-batslib_prefix.bats create mode 100755 lib/bats-support/test/50-output-18-batslib_mark.bats create mode 100755 lib/bats-support/test/50-output-19-batslib_decorate.bats create mode 100755 lib/bats-support/test/51-error-10-fail.bats create mode 100755 lib/bats-support/test/52-lang-10-batslib_is_caller.bats create mode 100644 lib/bats-support/test/test_helper.bash diff --git a/lib/bats-assert/.gitignore b/lib/bats-assert/.gitignore new file mode 100644 index 0000000..418531e --- /dev/null +++ b/lib/bats-assert/.gitignore @@ -0,0 +1,4 @@ +/node_modules +/package-lock.json +/yarn.lock +/bats-assert-*.tgz diff --git a/lib/bats-assert/.travis.yml b/lib/bats-assert/.travis.yml new file mode 100644 index 0000000..965fe2d --- /dev/null +++ b/lib/bats-assert/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: node +cache: npm diff --git a/lib/bats-assert/LICENSE b/lib/bats-assert/LICENSE new file mode 100644 index 0000000..670154e --- /dev/null +++ b/lib/bats-assert/LICENSE @@ -0,0 +1,116 @@ +CC0 1.0 Universal + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later +claims of infringement build upon, modify, incorporate in other works, reuse +and redistribute as freely as possible in any form whatsoever and for any +purposes, including without limitation commercial purposes. These owners may +contribute to the Commons to promote the ideal of a free culture and the +further production of creative, cultural and scientific works, or to gain +reputation or greater distribution for their Work in part through the use and +efforts of others. + +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with a +Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or her +Copyright and Related Rights in the Work and the meaning and intended legal +effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not limited +to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + + iii. publicity and privacy rights pertaining to a person's image or likeness + depicted in a Work; + + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + + v. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + + vii. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time +extensions), (iii) in any current or future medium and for any number of +copies, and (iv) for any purpose whatsoever, including without limitation +commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes +the Waiver for the benefit of each member of the public at large and to the +detriment of Affirmer's heirs and successors, fully intending that such Waiver +shall not be subject to revocation, rescission, cancellation, termination, or +any other legal or equitable action to disrupt the quiet enjoyment of the Work +by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account +Affirmer's express Statement of Purpose. In addition, to the extent the Waiver +is so judged Affirmer hereby grants to each affected person a royalty-free, +non transferable, non sublicensable, non exclusive, irrevocable and +unconditional license to exercise Affirmer's Copyright and Related Rights in +the Work (i) in all territories worldwide, (ii) for the maximum duration +provided by applicable law or treaty (including future time extensions), (iii) +in any current or future medium and for any number of copies, and (iv) for any +purpose whatsoever, including without limitation commercial, advertising or +promotional purposes (the "License"). The License shall be deemed effective as +of the date CC0 was applied by Affirmer to the Work. Should any part of the +License for any reason be judged legally invalid or ineffective under +applicable law, such partial invalidity or ineffectiveness shall not +invalidate the remainder of the License, and in such case Affirmer hereby +affirms that he or she will not (i) exercise any of his or her remaining +Copyright and Related Rights in the Work or (ii) assert any associated claims +and causes of action with respect to the Work, in either case contrary to +Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + + b. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or otherwise, + including without limitation warranties of title, merchantability, fitness + for a particular purpose, non infringement, or the absence of latent or + other defects, accuracy, or the present or absence of errors, whether or not + discoverable, all to the greatest extent permissible under applicable law. + + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without limitation + any person's Copyright and Related Rights in the Work. Further, Affirmer + disclaims responsibility for obtaining any necessary consents, permissions + or other rights required for any use of the Work. + + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +For more information, please see + diff --git a/lib/bats-assert/README.md b/lib/bats-assert/README.md new file mode 100644 index 0000000..f980537 --- /dev/null +++ b/lib/bats-assert/README.md @@ -0,0 +1,661 @@ +# bats-assert + +[![License](https://img.shields.io/npm/l/bats-assert.svg)](https://github.com/bats-core/bats-assert/blob/master/LICENSE) +[![GitHub release](https://img.shields.io/github/release/bats-core/bats-assert.svg)](https://github.com/bats-core/bats-assert/releases/latest) +[![npm release](https://img.shields.io/npm/v/bats-assert.svg)](https://www.npmjs.com/package/bats-assert) +[![Build Status](https://travis-ci.org/bats-core/bats-assert.svg?branch=master)](https://travis-ci.org/bats-core/bats-assert) + +`bats-assert` is a helper library providing common assertions for [Bats][bats]. + +Assertions are functions that perform a test and output relevant information on failure to help debugging. +They return 1 on failure and 0 otherwise. +Output, [formatted][bats-support-output] for readability, is sent to the standard error to make assertions usable outside of `@test` blocks too. + +Assertions testing exit code and output operate on the results of the most recent invocation of `run`. + +Dependencies: +- [`bats-support`][bats-support] (formerly `bats-core`) - output formatting + +See the [shared documentation][bats-docs] to learn how to install and load this library. + + +## Usage + +### `assert` + +Fail if the given expression evaluates to false. + +***Note:*** +*The expression must be a simple command. +[Compound commands][bash-comp-cmd], such as `[[`, can be used only when executed with `bash -c`.* + +```bash +@test 'assert()' { + touch '/var/log/test.log' + assert [ -e '/var/log/test.log' ] +} +``` + +On failure, the failed expression is displayed. + +``` +-- assertion failed -- +expression : [ -e /var/log/test.log ] +-- +``` + + +### `refute` + +Fail if the given expression evaluates to true. + +***Note:*** +*The expression must be a simple command. +[Compound commands][bash-comp-cmd], such as `[[`, can be used only when executed with `bash -c`.* + +```bash +@test 'refute()' { + rm -f '/var/log/test.log' + refute [ -e '/var/log/test.log' ] +} +``` + +On failure, the successful expression is displayed. + +``` +-- assertion succeeded, but it was expected to fail -- +expression : [ -e /var/log/test.log ] +-- +``` + + +### `assert_equal` + +Fail if the two parameters, actual and expected value respectively, do not equal. + +```bash +@test 'assert_equal()' { + assert_equal 'have' 'want' +} +``` + +On failure, the expected and actual values are displayed. + +``` +-- values do not equal -- +expected : want +actual : have +-- +``` + +If either value is longer than one line both are displayed in *multi-line* format. + + +### `assert_success` + +Fail if `$status` is not 0. + +```bash +@test 'assert_success() status only' { + run bash -c "echo 'Error!'; exit 1" + assert_success +} +``` + +On failure, `$status` and `$output` are displayed. + +``` +-- command failed -- +status : 1 +output : Error! +-- +``` + +If `$output` is longer than one line, it is displayed in *multi-line* format. + + +### `assert_failure` + +Fail if `$status` is 0. + +```bash +@test 'assert_failure() status only' { + run echo 'Success!' + assert_failure +} +``` + +On failure, `$output` is displayed. + +``` +-- command succeeded, but it was expected to fail -- +output : Success! +-- +``` + +If `$output` is longer than one line, it is displayed in *multi-line* format. + +#### Expected status + +When one parameter is specified, fail if `$status` does not equal the expected status specified by the parameter. + +```bash +@test 'assert_failure() with expected status' { + run bash -c "echo 'Error!'; exit 1" + assert_failure 2 +} +``` + +On failure, the expected and actual status, and `$output` are displayed. + +``` +-- command failed as expected, but status differs -- +expected : 2 +actual : 1 +output : Error! +-- +``` + +If `$output` is longer than one line, it is displayed in *multi-line* format. + + +### `assert_output` + +This function helps to verify that a command or function produces the correct output by checking that the specified expected output matches the actual output. +Matching can be literal (default), partial or regular expression. +This function is the logical complement of `refute_output`. + +#### Literal matching + +By default, literal matching is performed. +The assertion fails if `$output` does not equal the expected output. + +```bash +@test 'assert_output()' { + run echo 'have' + assert_output 'want' +} +``` + +On failure, the expected and actual output are displayed. + +``` +-- output differs -- +expected : want +actual : have +-- +``` + +If either value is longer than one line both are displayed in *multi-line* format. + +#### Existence + +To assert that any (non-empty) output exists at all, simply omit the matching argument. + +```bash +@test 'assert_output()' { + run echo 'have' + assert_output +} +``` + +On failure, an error message is displayed. + +``` +-- no output -- +expected non-empty output, but output was empty +-- +``` + +#### Partial matching + +Partial matching can be enabled with the `--partial` option (`-p` for short). +When used, the assertion fails if the expected *substring* is not found in `$output`. + +```bash +@test 'assert_output() partial matching' { + run echo 'ERROR: no such file or directory' + assert_output --partial 'SUCCESS' +} +``` + +On failure, the substring and the output are displayed. + +``` +-- output does not contain substring -- +substring : SUCCESS +output : ERROR: no such file or directory +-- +``` + +This option and regular expression matching (`--regexp` or `-e`) are mutually exclusive. +An error is displayed when used simultaneously. + +#### Regular expression matching + +Regular expression matching can be enabled with the `--regexp` option (`-e` for short). +When used, the assertion fails if the *extended regular expression* does not match `$output`. + +*Note: The anchors `^` and `$` bind to the beginning and the end of the entire output (not individual lines), respectively.* + +```bash +@test 'assert_output() regular expression matching' { + run echo 'Foobar 0.1.0' + assert_output --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$' +} +``` + +On failure, the regular expression and the output are displayed. + +``` +-- regular expression does not match output -- +regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$ +output : Foobar 0.1.0 +-- +``` + +An error is displayed if the specified extended regular expression is invalid. + +This option and partial matching (`--partial` or `-p`) are mutually exclusive. +An error is displayed when used simultaneously. + +#### Standard Input, HereDocs and HereStrings + +The expected output can be specified via standard input (also heredoc/herestring) with the `-`/`--stdin` option. + +```bash +@test 'assert_output() with pipe' { + run echo 'hello' + echo 'hello' | assert_output - +} + +@test 'assert_output() with herestring' { + run echo 'hello' + assert_output - <<< hello +} +``` + + +### `refute_output` + +This function helps to verify that a command or function produces the correct output by checking that the specified unexpected output does not match the actual output. +Matching can be literal (default), partial or regular expression. +This function is the logical complement of `assert_output`. + +#### Literal matching + +By default, literal matching is performed. +The assertion fails if `$output` equals the unexpected output. + +```bash +@test 'refute_output()' { + run echo 'want' + refute_output 'want' +} +``` + +On failure, the output is displayed. + +``` +-- output equals, but it was expected to differ -- +output : want +-- +``` + +If output is longer than one line it is displayed in *multi-line* format. + +#### Existence + +To assert that there is no output at all, simply omit the matching argument. + +```bash +@test 'refute_output()' { + run foo --silent + refute_output +} +``` + +On failure, an error message is displayed. + +``` +-- unexpected output -- +expected no output, but output was non-empty +-- +``` + +#### Partial matching + +Partial matching can be enabled with the `--partial` option (`-p` for short). +When used, the assertion fails if the unexpected *substring* is found in `$output`. + +```bash +@test 'refute_output() partial matching' { + run echo 'ERROR: no such file or directory' + refute_output --partial 'ERROR' +} +``` + +On failure, the substring and the output are displayed. + +``` +-- output should not contain substring -- +substring : ERROR +output : ERROR: no such file or directory +-- +``` + +This option and regular expression matching (`--regexp` or `-e`) are mutually exclusive. +An error is displayed when used simultaneously. + +#### Regular expression matching + +Regular expression matching can be enabled with the `--regexp` option (`-e` for short). +When used, the assertion fails if the *extended regular expression* matches `$output`. + +*Note: The anchors `^` and `$` bind to the beginning and the end of the entire output (not individual lines), respectively.* + +```bash +@test 'refute_output() regular expression matching' { + run echo 'Foobar v0.1.0' + refute_output --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$' +} +``` + +On failure, the regular expression and the output are displayed. + +``` +-- regular expression should not match output -- +regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$ +output : Foobar v0.1.0 +-- +``` + +An error is displayed if the specified extended regular expression is invalid. + +This option and partial matching (`--partial` or `-p`) are mutually exclusive. +An error is displayed when used simultaneously. + +#### Standard Input, HereDocs and HereStrings + +The unexpected output can be specified via standard input (also heredoc/herestring) with the `-`/`--stdin` option. + +```bash +@test 'refute_output() with pipe' { + run echo 'hello' + echo 'world' | refute_output - +} + +@test 'refute_output() with herestring' { + run echo 'hello' + refute_output - <<< world +} +``` + + +### `assert_line` + +Similarly to `assert_output`, this function helps to verify that a command or function produces the correct output. +It checks that the expected line appears in the output (default) or in a specific line of it. +Matching can be literal (default), partial or regular expression. +This function is the logical complement of `refute_line`. + +***Warning:*** +*Due to a [bug in Bats][bats-93], empty lines are discarded from `${lines[@]}`, causing line indices to change and preventing testing for empty lines.* + +[bats-93]: https://github.com/sstephenson/bats/pull/93 + +#### Looking for a line in the output + +By default, the entire output is searched for the expected line. +The assertion fails if the expected line is not found in `${lines[@]}`. + +```bash +@test 'assert_line() looking for line' { + run echo $'have-0\nhave-1\nhave-2' + assert_line 'want' +} +``` + +On failure, the expected line and the output are displayed. + +***Warning:*** +*The output displayed does not contain empty lines. +See the Warning above for more.* + +``` +-- output does not contain line -- +line : want +output (3 lines): + have-0 + have-1 + have-2 +-- +``` + +If output is not longer than one line, it is displayed in *two-column* format. + +#### Matching a specific line + +When the `--index ` option is used (`-n ` for short), the expected line is matched only against the line identified by the given index. +The assertion fails if the expected line does not equal `${lines[]}`. + +```bash +@test 'assert_line() specific line' { + run echo $'have-0\nhave-1\nhave-2' + assert_line --index 1 'want-1' +} +``` + +On failure, the index and the compared lines are displayed. + +``` +-- line differs -- +index : 1 +expected : want-1 +actual : have-1 +-- +``` + +#### Partial matching + +Partial matching can be enabled with the `--partial` option (`-p` for short). +When used, a match fails if the expected *substring* is not found in the matched line. + +```bash +@test 'assert_line() partial matching' { + run echo $'have 1\nhave 2\nhave 3' + assert_line --partial 'want' +} +``` + +On failure, the same details are displayed as for literal matching, except that the substring replaces the expected line. + +``` +-- no output line contains substring -- +substring : want +output (3 lines): + have 1 + have 2 + have 3 +-- +``` + +This option and regular expression matching (`--regexp` or `-e`) are mutually exclusive. +An error is displayed when used simultaneously. + +#### Regular expression matching + +Regular expression matching can be enabled with the `--regexp` option (`-e` for short). +When used, a match fails if the *extended regular expression* does not match the line being tested. + +*Note: As expected, the anchors `^` and `$` bind to the beginning and the end of the matched line, respectively.* + +```bash +@test 'assert_line() regular expression matching' { + run echo $'have-0\nhave-1\nhave-2' + assert_line --index 1 --regexp '^want-[0-9]$' +} +``` + +On failure, the same details are displayed as for literal matching, except that the regular expression replaces the expected line. + +``` +-- regular expression does not match line -- +index : 1 +regexp : ^want-[0-9]$ +line : have-1 +-- +``` + +An error is displayed if the specified extended regular expression is invalid. + +This option and partial matching (`--partial` or `-p`) are mutually exclusive. +An error is displayed when used simultaneously. + + +### `refute_line` + +Similarly to `refute_output`, this function helps to verify that a command or function produces the correct output. +It checks that the unexpected line does not appear in the output (default) or in a specific line of it. +Matching can be literal (default), partial or regular expression. +This function is the logical complement of `assert_line`. + +***Warning:*** +*Due to a [bug in Bats][bats-93], empty lines are discarded from `${lines[@]}`, causing line indices to change and preventing testing for empty lines.* + +[bats-93]: https://github.com/sstephenson/bats/pull/93 + +#### Looking for a line in the output + +By default, the entire output is searched for the unexpected line. +The assertion fails if the unexpected line is found in `${lines[@]}`. + +```bash +@test 'refute_line() looking for line' { + run echo $'have-0\nwant\nhave-2' + refute_line 'want' +} +``` + +On failure, the unexpected line, the index of its first match and the output with the matching line highlighted are displayed. + +***Warning:*** +*The output displayed does not contain empty lines. +See the Warning above for more.* + +``` +-- line should not be in output -- +line : want +index : 1 +output (3 lines): + have-0 +> want + have-2 +-- +``` + +If output is not longer than one line, it is displayed in *two-column* format. + +#### Matching a specific line + +When the `--index ` option is used (`-n ` for short), the unexpected line is matched only against the line identified by the given index. +The assertion fails if the unexpected line equals `${lines[]}`. + +```bash +@test 'refute_line() specific line' { + run echo $'have-0\nwant-1\nhave-2' + refute_line --index 1 'want-1' +} +``` + +On failure, the index and the unexpected line are displayed. + +``` +-- line should differ -- +index : 1 +line : want-1 +-- +``` + +#### Partial matching + +Partial matching can be enabled with the `--partial` option (`-p` for short). +When used, a match fails if the unexpected *substring* is found in the matched line. + +```bash +@test 'refute_line() partial matching' { + run echo $'have 1\nwant 2\nhave 3' + refute_line --partial 'want' +} +``` + +On failure, in addition to the details of literal matching, the substring is also displayed. +When used with `--index ` the substring replaces the unexpected line. + +``` +-- no line should contain substring -- +substring : want +index : 1 +output (3 lines): + have 1 +> want 2 + have 3 +-- +``` + +This option and regular expression matching (`--regexp` or `-e`) are mutually exclusive. +An error is displayed when used simultaneously. + +#### Regular expression matching + +Regular expression matching can be enabled with the `--regexp` option (`-e` for short). +When used, a match fails if the *extended regular expression* matches the line being tested. + +*Note: As expected, the anchors `^` and `$` bind to the beginning and the end of the matched line, respectively.* + +```bash +@test 'refute_line() regular expression matching' { + run echo $'Foobar v0.1.0\nRelease date: 2015-11-29' + refute_line --index 0 --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$' +} +``` + +On failure, in addition to the details of literal matching, the regular expression is also displayed. +When used with `--index ` the regular expression replaces the unexpected line. + +``` +-- regular expression should not match line -- +index : 0 +regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$ +line : Foobar v0.1.0 +-- +``` + +An error is displayed if the specified extended regular expression is invalid. + +This option and partial matching (`--partial` or `-p`) are mutually exclusive. +An error is displayed when used simultaneously. + + +## Options + +For functions that have options, `--` disables option parsing for the remaining arguments to allow using arguments identical to one of the allowed options. + +```bash +assert_output -- '-p' +``` + +Specifying `--` as an argument is similarly simple. + +```bash +refute_line -- '--' +``` + + + + +[bats]: https://github.com/sstephenson/bats +[bats-support-output]: https://github.com/ztombol/bats-support#output-formatting +[bats-support]: https://github.com/ztombol/bats-support +[bats-docs]: https://github.com/ztombol/bats-docs +[bash-comp-cmd]: https://www.gnu.org/software/bash/manual/bash.html#Compound-Commands diff --git a/lib/bats-assert/load.bash b/lib/bats-assert/load.bash new file mode 100644 index 0000000..38bffe0 --- /dev/null +++ b/lib/bats-assert/load.bash @@ -0,0 +1,30 @@ +# bats-assert - Common assertions for Bats +# +# Written in 2016 by Zoltan Tombol +# +# To the extent possible under law, the author(s) have dedicated all +# copyright and related and neighboring rights to this software to the +# public domain worldwide. This software is distributed without any +# warranty. +# +# You should have received a copy of the CC0 Public Domain Dedication +# along with this software. If not, see +# . +# +# Assertions are functions that perform a test and output relevant +# information on failure to help debugging. They return 1 on failure +# and 0 otherwise. +# +# All output is formatted for readability using the functions of +# `output.bash' and sent to the standard error. + +# shellcheck disable=1090 +source "$(dirname "${BASH_SOURCE[0]}")/src/assert.bash" +source "$(dirname "${BASH_SOURCE[0]}")/src/refute.bash" +source "$(dirname "${BASH_SOURCE[0]}")/src/assert_equal.bash" +source "$(dirname "${BASH_SOURCE[0]}")/src/assert_success.bash" +source "$(dirname "${BASH_SOURCE[0]}")/src/assert_failure.bash" +source "$(dirname "${BASH_SOURCE[0]}")/src/assert_output.bash" +source "$(dirname "${BASH_SOURCE[0]}")/src/refute_output.bash" +source "$(dirname "${BASH_SOURCE[0]}")/src/assert_line.bash" +source "$(dirname "${BASH_SOURCE[0]}")/src/refute_line.bash" diff --git a/lib/bats-assert/package.json b/lib/bats-assert/package.json new file mode 100644 index 0000000..3c9bdda --- /dev/null +++ b/lib/bats-assert/package.json @@ -0,0 +1,48 @@ +{ + "name": "bats-assert", + "version": "2.0.0", + "description": "Common assertions for Bats", + "homepage": "https://github.com/jasonkarns/bats-assert-1", + "license": "CC0-1.0", + "author": "Zoltán Tömböl (https://github.com/ztombol)", + "contributors": [ + "Sam Stephenson (http://sstephenson.us/)", + "Jason Karns (http://jason.karns.name)", + "Mislav Marohnić (http://mislav.net/)", + "Tim Pope (https://github.com/tpope)" + ], + "repository": "github:jasonkarns/bats-assert-1", + "bugs": "https://github.com/jasonkarns/bats-assert-1/issues", + "directories": { + "lib": "src", + "test": "test" + }, + "files": [ + "load.bash", + "src" + ], + "scripts": { + "test": "bats ${CI+-t} test", + "postversion": "npm publish", + "prepublishOnly": "npm run publish:github", + "publish:github": "git push --follow-tags" + }, + "devDependencies": { + "bats": "^1", + "bats-support": "^0.3" + }, + "peerDependencies": { + "bats": "0.4 || ^1", + "bats-support": "^0.3" + }, + "keywords": [ + "bats", + "bash", + "shell", + "test", + "unit", + "assert", + "assertion", + "helper" + ] +} diff --git a/lib/bats-assert/src/assert.bash b/lib/bats-assert/src/assert.bash new file mode 100644 index 0000000..0260ce2 --- /dev/null +++ b/lib/bats-assert/src/assert.bash @@ -0,0 +1,42 @@ +# assert +# ====== +# +# Summary: Fail if the given expression evaluates to false. +# +# Usage: assert + +# Options: +# The expression to evaluate for truthiness. +# *__Note:__ The expression must be a simple command. +# [Compound commands](https://www.gnu.org/software/bash/manual/bash.html#Compound-Commands), +# such as `[[`, can be used only when executed with `bash -c`.* +# +# IO: +# STDERR - the failed expression, on failure +# Globals: +# none +# Returns: +# 0 - if expression evaluates to true +# 1 - otherwise +# +# ```bash +# @test 'assert()' { +# touch '/var/log/test.log' +# assert [ -e '/var/log/test.log' ] +# } +# ``` +# +# On failure, the failed expression is displayed. +# +# ``` +# -- assertion failed -- +# expression : [ -e /var/log/test.log ] +# -- +# ``` +assert() { + if ! "$@"; then + batslib_print_kv_single 10 'expression' "$*" \ + | batslib_decorate 'assertion failed' \ + | fail + fi +} diff --git a/lib/bats-assert/src/assert_equal.bash b/lib/bats-assert/src/assert_equal.bash new file mode 100644 index 0000000..4ef1297 --- /dev/null +++ b/lib/bats-assert/src/assert_equal.bash @@ -0,0 +1,42 @@ +# assert_equal +# ============ +# +# Summary: Fail if the actual and expected values are not equal. +# +# Usage: assert_equal +# +# Options: +# The value being compared. +# The value to compare against. +# +# ```bash +# @test 'assert_equal()' { +# assert_equal 'have' 'want' +# } +# ``` +# +# IO: +# STDERR - expected and actual values, on failure +# Globals: +# none +# Returns: +# 0 - if values equal +# 1 - otherwise +# +# On failure, the expected and actual values are displayed. +# +# ``` +# -- values do not equal -- +# expected : want +# actual : have +# -- +# ``` +assert_equal() { + if [[ $1 != "$2" ]]; then + batslib_print_kv_single_or_multi 8 \ + 'expected' "$2" \ + 'actual' "$1" \ + | batslib_decorate 'values do not equal' \ + | fail + fi +} diff --git a/lib/bats-assert/src/assert_failure.bash b/lib/bats-assert/src/assert_failure.bash new file mode 100644 index 0000000..d906059 --- /dev/null +++ b/lib/bats-assert/src/assert_failure.bash @@ -0,0 +1,78 @@ +# assert_failure +# ============== +# +# Summary: Fail if `$status` is 0; or is not equal to the optionally provided status. +# +# Usage: assert_failure [] +# +# Options: +# The specific status code to check against. +# If not provided, simply asserts status is != 0. +# +# IO: +# STDERR - `$output`, on failure; +# - also, `$status` and `expected_status`, if provided +# Globals: +# status +# output +# Returns: +# 0 - if `$status' is 0, +# or if expected_status is provided but does not equal `$status' +# 1 - otherwise +# +# ```bash +# @test 'assert_failure() status only' { +# run echo 'Success!' +# assert_failure +# } +# ``` +# +# On failure, `$output` is displayed. +# +# ``` +# -- command succeeded, but it was expected to fail -- +# output : Success! +# -- +# ``` +# +# ## Expected status +# +# When `expected_status` is provided, fail if `$status` does not equal the `expected_status`. +# +# ```bash +# @test 'assert_failure() with expected status' { +# run bash -c "echo 'Error!'; exit 1" +# assert_failure 2 +# } +# ``` +# +# On failure, both the expected and actual statuses, and `$output` are displayed. +# +# ``` +# -- command failed as expected, but status differs -- +# expected : 2 +# actual : 1 +# output : Error! +# -- +# ``` +assert_failure() { + : "${output?}" + : "${status?}" + + (( $# > 0 )) && local -r expected="$1" + if (( status == 0 )); then + batslib_print_kv_single_or_multi 6 'output' "$output" \ + | batslib_decorate 'command succeeded, but it was expected to fail' \ + | fail + elif (( $# > 0 )) && (( status != expected )); then + { local -ir width=8 + batslib_print_kv_single "$width" \ + 'expected' "$expected" \ + 'actual' "$status" + batslib_print_kv_single_or_multi "$width" \ + 'output' "$output" + } \ + | batslib_decorate 'command failed as expected, but status differs' \ + | fail + fi +} diff --git a/lib/bats-assert/src/assert_line.bash b/lib/bats-assert/src/assert_line.bash new file mode 100644 index 0000000..c0f68de --- /dev/null +++ b/lib/bats-assert/src/assert_line.bash @@ -0,0 +1,248 @@ +# assert_line +# =========== +# +# Summary: Fail if the expected line is not found in the output (default) or at a specific line number. +# +# Usage: assert_line [-n index] [-p | -e] [--] +# +# Options: +# -n, --index Match the th line +# -p, --partial Match if `expected` is a substring of `$output` or line +# -e, --regexp Treat `expected` as an extended regular expression +# The expected line string, substring, or regular expression +# +# IO: +# STDERR - details, on failure +# error message, on error +# Globals: +# output +# lines +# Returns: +# 0 - if matching line found +# 1 - otherwise +# +# Similarly to `assert_output`, this function verifies that a command or function produces the expected output. +# (It is the logical complement of `refute_line`.) +# It checks that the expected line appears in the output (default) or at a specific line number. +# Matching can be literal (default), partial or regular expression. +# +# *__Warning:__ +# Due to a [bug in Bats][bats-93], empty lines are discarded from `${lines[@]}`, +# causing line indices to change and preventing testing for empty lines.* +# +# [bats-93]: https://github.com/sstephenson/bats/pull/93 +# +# ## Looking for a line in the output +# +# By default, the entire output is searched for the expected line. +# The assertion fails if the expected line is not found in `${lines[@]}`. +# +# ```bash +# @test 'assert_line() looking for line' { +# run echo $'have-0\nhave-1\nhave-2' +# assert_line 'want' +# } +# ``` +# +# On failure, the expected line and the output are displayed. +# +# ``` +# -- output does not contain line -- +# line : want +# output (3 lines): +# have-0 +# have-1 +# have-2 +# -- +# ``` +# +# ## Matching a specific line +# +# When the `--index ` option is used (`-n ` for short), the expected line is matched only against the line identified by the given index. +# The assertion fails if the expected line does not equal `${lines[]}`. +# +# ```bash +# @test 'assert_line() specific line' { +# run echo $'have-0\nhave-1\nhave-2' +# assert_line --index 1 'want-1' +# } +# ``` +# +# On failure, the index and the compared lines are displayed. +# +# ``` +# -- line differs -- +# index : 1 +# expected : want-1 +# actual : have-1 +# -- +# ``` +# +# ## Partial matching +# +# Partial matching can be enabled with the `--partial` option (`-p` for short). +# When used, a match fails if the expected *substring* is not found in the matched line. +# +# ```bash +# @test 'assert_line() partial matching' { +# run echo $'have 1\nhave 2\nhave 3' +# assert_line --partial 'want' +# } +# ``` +# +# On failure, the same details are displayed as for literal matching, except that the substring replaces the expected line. +# +# ``` +# -- no output line contains substring -- +# substring : want +# output (3 lines): +# have 1 +# have 2 +# have 3 +# -- +# ``` +# +# ## Regular expression matching +# +# Regular expression matching can be enabled with the `--regexp` option (`-e` for short). +# When used, a match fails if the *extended regular expression* does not match the line being tested. +# +# *__Note__: +# As expected, the anchors `^` and `$` bind to the beginning and the end (respectively) of the matched line.* +# +# ```bash +# @test 'assert_line() regular expression matching' { +# run echo $'have-0\nhave-1\nhave-2' +# assert_line --index 1 --regexp '^want-[0-9]$' +# } +# ``` +# +# On failure, the same details are displayed as for literal matching, except that the regular expression replaces the expected line. +# +# ``` +# -- regular expression does not match line -- +# index : 1 +# regexp : ^want-[0-9]$ +# line : have-1 +# -- +# ``` +# FIXME(ztombol): Display `${lines[@]}' instead of `$output'! +assert_line() { + local -i is_match_line=0 + local -i is_mode_partial=0 + local -i is_mode_regexp=0 + : "${lines?}" + + # Handle options. + while (( $# > 0 )); do + case "$1" in + -n|--index) + if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then + echo "\`--index' requires an integer argument: \`$2'" \ + | batslib_decorate 'ERROR: assert_line' \ + | fail + return $? + fi + is_match_line=1 + local -ri idx="$2" + shift 2 + ;; + -p|--partial) is_mode_partial=1; shift ;; + -e|--regexp) is_mode_regexp=1; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + if (( is_mode_partial )) && (( is_mode_regexp )); then + echo "\`--partial' and \`--regexp' are mutually exclusive" \ + | batslib_decorate 'ERROR: assert_line' \ + | fail + return $? + fi + + # Arguments. + local -r expected="$1" + + if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then + echo "Invalid extended regular expression: \`$expected'" \ + | batslib_decorate 'ERROR: assert_line' \ + | fail + return $? + fi + + # Matching. + if (( is_match_line )); then + # Specific line. + if (( is_mode_regexp )); then + if ! [[ ${lines[$idx]} =~ $expected ]]; then + batslib_print_kv_single 6 \ + 'index' "$idx" \ + 'regexp' "$expected" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'regular expression does not match line' \ + | fail + fi + elif (( is_mode_partial )); then + if [[ ${lines[$idx]} != *"$expected"* ]]; then + batslib_print_kv_single 9 \ + 'index' "$idx" \ + 'substring' "$expected" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'line does not contain substring' \ + | fail + fi + else + if [[ ${lines[$idx]} != "$expected" ]]; then + batslib_print_kv_single 8 \ + 'index' "$idx" \ + 'expected' "$expected" \ + 'actual' "${lines[$idx]}" \ + | batslib_decorate 'line differs' \ + | fail + fi + fi + else + # Contained in output. + if (( is_mode_regexp )); then + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + [[ ${lines[$idx]} =~ $expected ]] && return 0 + done + { local -ar single=( 'regexp' "$expected" ) + local -ar may_be_multi=( 'output' "$output" ) + local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" + } \ + | batslib_decorate 'no output line matches regular expression' \ + | fail + elif (( is_mode_partial )); then + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + [[ ${lines[$idx]} == *"$expected"* ]] && return 0 + done + { local -ar single=( 'substring' "$expected" ) + local -ar may_be_multi=( 'output' "$output" ) + local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" + } \ + | batslib_decorate 'no output line contains substring' \ + | fail + else + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + [[ ${lines[$idx]} == "$expected" ]] && return 0 + done + { local -ar single=( 'line' "$expected" ) + local -ar may_be_multi=( 'output' "$output" ) + local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" + } \ + | batslib_decorate 'output does not contain line' \ + | fail + fi + fi +} diff --git a/lib/bats-assert/src/assert_output.bash b/lib/bats-assert/src/assert_output.bash new file mode 100644 index 0000000..82d0a0c --- /dev/null +++ b/lib/bats-assert/src/assert_output.bash @@ -0,0 +1,197 @@ +# assert_output +# ============= +# +# Summary: Fail if `$output' does not match the expected output. +# +# Usage: assert_output [-p | -e] [- | [--] ] +# +# Options: +# -p, --partial Match if `expected` is a substring of `$output` +# -e, --regexp Treat `expected` as an extended regular expression +# -, --stdin Read `expected` value from STDIN +# The expected value, substring or regular expression +# +# IO: +# STDIN - [=$1] expected output +# STDERR - details, on failure +# error message, on error +# Globals: +# output +# Returns: +# 0 - if output matches the expected value/partial/regexp +# 1 - otherwise +# +# This function verifies that a command or function produces the expected output. +# (It is the logical complement of `refute_output`.) +# Output matching can be literal (the default), partial or by regular expression. +# The expected output can be specified either by positional argument or read from STDIN by passing the `-`/`--stdin` flag. +# +# ## Literal matching +# +# By default, literal matching is performed. +# The assertion fails if `$output` does not equal the expected output. +# +# ```bash +# @test 'assert_output()' { +# run echo 'have' +# assert_output 'want' +# } +# +# @test 'assert_output() with pipe' { +# run echo 'hello' +# echo 'hello' | assert_output - +# } +# +# @test 'assert_output() with herestring' { +# run echo 'hello' +# assert_output - <<< hello +# } +# ``` +# +# On failure, the expected and actual output are displayed. +# +# ``` +# -- output differs -- +# expected : want +# actual : have +# -- +# ``` +# +# ## Existence +# +# To assert that any output exists at all, omit the `expected` argument. +# +# ```bash +# @test 'assert_output()' { +# run echo 'have' +# assert_output +# } +# ``` +# +# On failure, an error message is displayed. +# +# ``` +# -- no output -- +# expected non-empty output, but output was empty +# -- +# ``` +# +# ## Partial matching +# +# Partial matching can be enabled with the `--partial` option (`-p` for short). +# When used, the assertion fails if the expected _substring_ is not found in `$output`. +# +# ```bash +# @test 'assert_output() partial matching' { +# run echo 'ERROR: no such file or directory' +# assert_output --partial 'SUCCESS' +# } +# ``` +# +# On failure, the substring and the output are displayed. +# +# ``` +# -- output does not contain substring -- +# substring : SUCCESS +# output : ERROR: no such file or directory +# -- +# ``` +# +# ## Regular expression matching +# +# Regular expression matching can be enabled with the `--regexp` option (`-e` for short). +# When used, the assertion fails if the *extended regular expression* does not match `$output`. +# +# *__Note__: +# The anchors `^` and `$` bind to the beginning and the end (respectively) of the entire output; +# not individual lines.* +# +# ```bash +# @test 'assert_output() regular expression matching' { +# run echo 'Foobar 0.1.0' +# assert_output --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$' +# } +# ``` +# +# On failure, the regular expression and the output are displayed. +# +# ``` +# -- regular expression does not match output -- +# regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$ +# output : Foobar 0.1.0 +# -- +# ``` +assert_output() { + local -i is_mode_partial=0 + local -i is_mode_regexp=0 + local -i is_mode_nonempty=0 + local -i use_stdin=0 + : "${output?}" + + # Handle options. + if (( $# == 0 )); then + is_mode_nonempty=1 + fi + + while (( $# > 0 )); do + case "$1" in + -p|--partial) is_mode_partial=1; shift ;; + -e|--regexp) is_mode_regexp=1; shift ;; + -|--stdin) use_stdin=1; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + if (( is_mode_partial )) && (( is_mode_regexp )); then + echo "\`--partial' and \`--regexp' are mutually exclusive" \ + | batslib_decorate 'ERROR: assert_output' \ + | fail + return $? + fi + + # Arguments. + local expected + if (( use_stdin )); then + expected="$(cat -)" + else + expected="${1-}" + fi + + # Matching. + if (( is_mode_nonempty )); then + if [ -z "$output" ]; then + echo 'expected non-empty output, but output was empty' \ + | batslib_decorate 'no output' \ + | fail + fi + elif (( is_mode_regexp )); then + if [[ '' =~ $expected ]] || (( $? == 2 )); then + echo "Invalid extended regular expression: \`$expected'" \ + | batslib_decorate 'ERROR: assert_output' \ + | fail + elif ! [[ $output =~ $expected ]]; then + batslib_print_kv_single_or_multi 6 \ + 'regexp' "$expected" \ + 'output' "$output" \ + | batslib_decorate 'regular expression does not match output' \ + | fail + fi + elif (( is_mode_partial )); then + if [[ $output != *"$expected"* ]]; then + batslib_print_kv_single_or_multi 9 \ + 'substring' "$expected" \ + 'output' "$output" \ + | batslib_decorate 'output does not contain substring' \ + | fail + fi + else + if [[ $output != "$expected" ]]; then + batslib_print_kv_single_or_multi 8 \ + 'expected' "$expected" \ + 'actual' "$output" \ + | batslib_decorate 'output differs' \ + | fail + fi + fi +} diff --git a/lib/bats-assert/src/assert_success.bash b/lib/bats-assert/src/assert_success.bash new file mode 100644 index 0000000..a8b2a38 --- /dev/null +++ b/lib/bats-assert/src/assert_success.bash @@ -0,0 +1,44 @@ +# assert_success +# ============== +# +# Summary: Fail if `$status` is not 0. +# +# Usage: assert_success +# +# IO: +# STDERR - `$status` and `$output`, on failure +# Globals: +# status +# output +# Returns: +# 0 - if `$status' is 0 +# 1 - otherwise +# +# ```bash +# @test 'assert_success() status only' { +# run bash -c "echo 'Error!'; exit 1" +# assert_success +# } +# ``` +# +# On failure, `$status` and `$output` are displayed. +# +# ``` +# -- command failed -- +# status : 1 +# output : Error! +# -- +# ``` +assert_success() { + : "${output?}" + : "${status?}" + + if (( status != 0 )); then + { local -ir width=6 + batslib_print_kv_single "$width" 'status' "$status" + batslib_print_kv_single_or_multi "$width" 'output' "$output" + } \ + | batslib_decorate 'command failed' \ + | fail + fi +} diff --git a/lib/bats-assert/src/refute.bash b/lib/bats-assert/src/refute.bash new file mode 100644 index 0000000..e7c47da --- /dev/null +++ b/lib/bats-assert/src/refute.bash @@ -0,0 +1,42 @@ +# refute +# ====== +# +# Summary: Fail if the given expression evaluates to true. +# +# Usage: refute +# +# Options: +# The expression to evaluate for falsiness. +# *__Note:__ The expression must be a simple command. +# [Compound commands](https://www.gnu.org/software/bash/manual/bash.html#Compound-Commands), +# such as `[[`, can be used only when executed with `bash -c`.* +# +# IO: +# STDERR - the successful expression, on failure +# Globals: +# none +# Returns: +# 0 - if expression evaluates to false +# 1 - otherwise +# +# ```bash +# @test 'refute()' { +# rm -f '/var/log/test.log' +# refute [ -e '/var/log/test.log' ] +# } +# ``` +# +# On failure, the successful expression is displayed. +# +# ``` +# -- assertion succeeded, but it was expected to fail -- +# expression : [ -e /var/log/test.log ] +# -- +# ``` +refute() { + if "$@"; then + batslib_print_kv_single 10 'expression' "$*" \ + | batslib_decorate 'assertion succeeded, but it was expected to fail' \ + | fail + fi +} diff --git a/lib/bats-assert/src/refute_line.bash b/lib/bats-assert/src/refute_line.bash new file mode 100644 index 0000000..c9aa47f --- /dev/null +++ b/lib/bats-assert/src/refute_line.bash @@ -0,0 +1,271 @@ +# refute_line +# =========== +# +# Summary: Fail if the unexpected line is found in the output (default) or at a specific line number. +# +# Usage: refute_line [-n index] [-p | -e] [--] +# +# Options: +# -n, --index Match the th line +# -p, --partial Match if `unexpected` is a substring of `$output` or line +# -e, --regexp Treat `unexpected` as an extended regular expression +# The unexpected line string, substring, or regular expression. +# +# IO: +# STDERR - details, on failure +# error message, on error +# Globals: +# output +# lines +# Returns: +# 0 - if match not found +# 1 - otherwise +# +# Similarly to `refute_output`, this function verifies that a command or function does not produce the unexpected output. +# (It is the logical complement of `assert_line`.) +# It checks that the unexpected line does not appear in the output (default) or at a specific line number. +# Matching can be literal (default), partial or regular expression. +# +# *__Warning:__ +# Due to a [bug in Bats][bats-93], empty lines are discarded from `${lines[@]}`, +# causing line indices to change and preventing testing for empty lines.* +# +# [bats-93]: https://github.com/sstephenson/bats/pull/93 +# +# ## Looking for a line in the output +# +# By default, the entire output is searched for the unexpected line. +# The assertion fails if the unexpected line is found in `${lines[@]}`. +# +# ```bash +# @test 'refute_line() looking for line' { +# run echo $'have-0\nwant\nhave-2' +# refute_line 'want' +# } +# ``` +# +# On failure, the unexpected line, the index of its first match and the output with the matching line highlighted are displayed. +# +# ``` +# -- line should not be in output -- +# line : want +# index : 1 +# output (3 lines): +# have-0 +# > want +# have-2 +# -- +# ``` +# +# ## Matching a specific line +# +# When the `--index ` option is used (`-n ` for short), the unexpected line is matched only against the line identified by the given index. +# The assertion fails if the unexpected line equals `${lines[]}`. +# +# ```bash +# @test 'refute_line() specific line' { +# run echo $'have-0\nwant-1\nhave-2' +# refute_line --index 1 'want-1' +# } +# ``` +# +# On failure, the index and the unexpected line are displayed. +# +# ``` +# -- line should differ -- +# index : 1 +# line : want-1 +# -- +# ``` +# +# ## Partial matching +# +# Partial matching can be enabled with the `--partial` option (`-p` for short). +# When used, a match fails if the unexpected *substring* is found in the matched line. +# +# ```bash +# @test 'refute_line() partial matching' { +# run echo $'have 1\nwant 2\nhave 3' +# refute_line --partial 'want' +# } +# ``` +# +# On failure, in addition to the details of literal matching, the substring is also displayed. +# When used with `--index ` the substring replaces the unexpected line. +# +# ``` +# -- no line should contain substring -- +# substring : want +# index : 1 +# output (3 lines): +# have 1 +# > want 2 +# have 3 +# -- +# ``` +# +# ## Regular expression matching +# +# Regular expression matching can be enabled with the `--regexp` option (`-e` for short). +# When used, a match fails if the *extended regular expression* matches the line being tested. +# +# *__Note__: +# As expected, the anchors `^` and `$` bind to the beginning and the end (respectively) of the matched line.* +# +# ```bash +# @test 'refute_line() regular expression matching' { +# run echo $'Foobar v0.1.0\nRelease date: 2015-11-29' +# refute_line --index 0 --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$' +# } +# ``` +# +# On failure, in addition to the details of literal matching, the regular expression is also displayed. +# When used with `--index ` the regular expression replaces the unexpected line. +# +# ``` +# -- regular expression should not match line -- +# index : 0 +# regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$ +# line : Foobar v0.1.0 +# -- +# ``` +# FIXME(ztombol): Display `${lines[@]}' instead of `$output'! +refute_line() { + local -i is_match_line=0 + local -i is_mode_partial=0 + local -i is_mode_regexp=0 + : "${lines?}" + + # Handle options. + while (( $# > 0 )); do + case "$1" in + -n|--index) + if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then + echo "\`--index' requires an integer argument: \`$2'" \ + | batslib_decorate 'ERROR: refute_line' \ + | fail + return $? + fi + is_match_line=1 + local -ri idx="$2" + shift 2 + ;; + -p|--partial) is_mode_partial=1; shift ;; + -e|--regexp) is_mode_regexp=1; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + if (( is_mode_partial )) && (( is_mode_regexp )); then + echo "\`--partial' and \`--regexp' are mutually exclusive" \ + | batslib_decorate 'ERROR: refute_line' \ + | fail + return $? + fi + + # Arguments. + local -r unexpected="$1" + + if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then + echo "Invalid extended regular expression: \`$unexpected'" \ + | batslib_decorate 'ERROR: refute_line' \ + | fail + return $? + fi + + # Matching. + if (( is_match_line )); then + # Specific line. + if (( is_mode_regexp )); then + if [[ ${lines[$idx]} =~ $unexpected ]]; then + batslib_print_kv_single 6 \ + 'index' "$idx" \ + 'regexp' "$unexpected" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'regular expression should not match line' \ + | fail + fi + elif (( is_mode_partial )); then + if [[ ${lines[$idx]} == *"$unexpected"* ]]; then + batslib_print_kv_single 9 \ + 'index' "$idx" \ + 'substring' "$unexpected" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'line should not contain substring' \ + | fail + fi + else + if [[ ${lines[$idx]} == "$unexpected" ]]; then + batslib_print_kv_single 5 \ + 'index' "$idx" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'line should differ' \ + | fail + fi + fi + else + # Line contained in output. + if (( is_mode_regexp )); then + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + if [[ ${lines[$idx]} =~ $unexpected ]]; then + { local -ar single=( 'regexp' "$unexpected" 'index' "$idx" ) + local -a may_be_multi=( 'output' "$output" ) + local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + if batslib_is_single_line "${may_be_multi[1]}"; then + batslib_print_kv_single "$width" "${may_be_multi[@]}" + else + may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )" + batslib_print_kv_multi "${may_be_multi[@]}" + fi + } \ + | batslib_decorate 'no line should match the regular expression' \ + | fail + return $? + fi + done + elif (( is_mode_partial )); then + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + if [[ ${lines[$idx]} == *"$unexpected"* ]]; then + { local -ar single=( 'substring' "$unexpected" 'index' "$idx" ) + local -a may_be_multi=( 'output' "$output" ) + local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + if batslib_is_single_line "${may_be_multi[1]}"; then + batslib_print_kv_single "$width" "${may_be_multi[@]}" + else + may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )" + batslib_print_kv_multi "${may_be_multi[@]}" + fi + } \ + | batslib_decorate 'no line should contain substring' \ + | fail + return $? + fi + done + else + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + if [[ ${lines[$idx]} == "$unexpected" ]]; then + { local -ar single=( 'line' "$unexpected" 'index' "$idx" ) + local -a may_be_multi=( 'output' "$output" ) + local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + if batslib_is_single_line "${may_be_multi[1]}"; then + batslib_print_kv_single "$width" "${may_be_multi[@]}" + else + may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )" + batslib_print_kv_multi "${may_be_multi[@]}" + fi + } \ + | batslib_decorate 'line should not be in output' \ + | fail + return $? + fi + done + fi + fi +} diff --git a/lib/bats-assert/src/refute_output.bash b/lib/bats-assert/src/refute_output.bash new file mode 100644 index 0000000..92d7e8f --- /dev/null +++ b/lib/bats-assert/src/refute_output.bash @@ -0,0 +1,199 @@ +# refute_output +# ============= +# +# Summary: Fail if `$output' matches the unexpected output. +# +# Usage: refute_output [-p | -e] [- | [--] ] +# +# Options: +# -p, --partial Match if `unexpected` is a substring of `$output` +# -e, --regexp Treat `unexpected` as an extended regular expression +# -, --stdin Read `unexpected` value from STDIN +# The unexpected value, substring, or regular expression +# +# IO: +# STDIN - [=$1] unexpected output +# STDERR - details, on failure +# error message, on error +# Globals: +# output +# Returns: +# 0 - if output matches the unexpected value/partial/regexp +# 1 - otherwise +# +# This function verifies that a command or function does not produce the unexpected output. +# (It is the logical complement of `assert_output`.) +# Output matching can be literal (the default), partial or by regular expression. +# The unexpected output can be specified either by positional argument or read from STDIN by passing the `-`/`--stdin` flag. +# +# ## Literal matching +# +# By default, literal matching is performed. +# The assertion fails if `$output` equals the unexpected output. +# +# ```bash +# @test 'refute_output()' { +# run echo 'want' +# refute_output 'want' +# } +# +# @test 'refute_output() with pipe' { +# run echo 'hello' +# echo 'world' | refute_output - +# } +# +# @test 'refute_output() with herestring' { +# run echo 'hello' +# refute_output - <<< world +# } +# ``` +# +# On failure, the output is displayed. +# +# ``` +# -- output equals, but it was expected to differ -- +# output : want +# -- +# ``` +# +# ## Existence +# +# To assert that there is no output at all, omit the matching argument. +# +# ```bash +# @test 'refute_output()' { +# run foo --silent +# refute_output +# } +# ``` +# +# On failure, an error message is displayed. +# +# ``` +# -- unexpected output -- +# expected no output, but output was non-empty +# -- +# ``` +# +# ## Partial matching +# +# Partial matching can be enabled with the `--partial` option (`-p` for short). +# When used, the assertion fails if the unexpected _substring_ is found in `$output`. +# +# ```bash +# @test 'refute_output() partial matching' { +# run echo 'ERROR: no such file or directory' +# refute_output --partial 'ERROR' +# } +# ``` +# +# On failure, the substring and the output are displayed. +# +# ``` +# -- output should not contain substring -- +# substring : ERROR +# output : ERROR: no such file or directory +# -- +# ``` +# +# ## Regular expression matching +# +# Regular expression matching can be enabled with the `--regexp` option (`-e` for short). +# When used, the assertion fails if the *extended regular expression* matches `$output`. +# +# *__Note__: +# The anchors `^` and `$` bind to the beginning and the end (respectively) of the entire output; +# not individual lines.* +# +# ```bash +# @test 'refute_output() regular expression matching' { +# run echo 'Foobar v0.1.0' +# refute_output --regexp '^Foobar v[0-9]+\.[0-9]+\.[0-9]$' +# } +# ``` +# +# On failure, the regular expression and the output are displayed. +# +# ``` +# -- regular expression should not match output -- +# regexp : ^Foobar v[0-9]+\.[0-9]+\.[0-9]$ +# output : Foobar v0.1.0 +# -- +# ``` +refute_output() { + local -i is_mode_partial=0 + local -i is_mode_regexp=0 + local -i is_mode_empty=0 + local -i use_stdin=0 + : "${output?}" + + # Handle options. + if (( $# == 0 )); then + is_mode_empty=1 + fi + + while (( $# > 0 )); do + case "$1" in + -p|--partial) is_mode_partial=1; shift ;; + -e|--regexp) is_mode_regexp=1; shift ;; + -|--stdin) use_stdin=1; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + if (( is_mode_partial )) && (( is_mode_regexp )); then + echo "\`--partial' and \`--regexp' are mutually exclusive" \ + | batslib_decorate 'ERROR: refute_output' \ + | fail + return $? + fi + + # Arguments. + local unexpected + if (( use_stdin )); then + unexpected="$(cat -)" + else + unexpected="${1-}" + fi + + if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then + echo "Invalid extended regular expression: \`$unexpected'" \ + | batslib_decorate 'ERROR: refute_output' \ + | fail + return $? + fi + + # Matching. + if (( is_mode_empty )); then + if [ -n "$output" ]; then + batslib_print_kv_single_or_multi 6 \ + 'output' "$output" \ + | batslib_decorate 'output non-empty, but expected no output' \ + | fail + fi + elif (( is_mode_regexp )); then + if [[ $output =~ $unexpected ]]; then + batslib_print_kv_single_or_multi 6 \ + 'regexp' "$unexpected" \ + 'output' "$output" \ + | batslib_decorate 'regular expression should not match output' \ + | fail + fi + elif (( is_mode_partial )); then + if [[ $output == *"$unexpected"* ]]; then + batslib_print_kv_single_or_multi 9 \ + 'substring' "$unexpected" \ + 'output' "$output" \ + | batslib_decorate 'output should not contain substring' \ + | fail + fi + else + if [[ $output == "$unexpected" ]]; then + batslib_print_kv_single_or_multi 6 \ + 'output' "$output" \ + | batslib_decorate 'output equals, but it was expected to differ' \ + | fail + fi + fi +} diff --git a/lib/bats-assert/test/assert.bats b/lib/bats-assert/test/assert.bats new file mode 100755 index 0000000..d1a34a2 --- /dev/null +++ b/lib/bats-assert/test/assert.bats @@ -0,0 +1,19 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'assert() : returns 0 if evaluates to TRUE' { + run assert true + assert_test_pass +} + +@test 'assert() : returns 1 and displays if it evaluates to FALSE' { + run assert false + + assert_test_fail <<'ERR_MSG' + +-- assertion failed -- +expression : false +-- +ERR_MSG +} diff --git a/lib/bats-assert/test/assert_equal.bats b/lib/bats-assert/test/assert_equal.bats new file mode 100755 index 0000000..7d3fc86 --- /dev/null +++ b/lib/bats-assert/test/assert_equal.bats @@ -0,0 +1,62 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'assert_equal() : returns 0 if equals ' { + run assert_equal 'a' 'a' + assert_test_pass +} + +@test 'assert_equal() : returns 1 and displays details if does not equal ' { + run assert_equal 'a' 'b' + + assert_test_fail <<'ERR_MSG' + +-- values do not equal -- +expected : b +actual : a +-- +ERR_MSG +} + +@test 'assert_equal() : displays details in multi-line format if is longer than one line' { + run assert_equal $'a 0\na 1' 'b' + + assert_test_fail <<'ERR_MSG' + +-- values do not equal -- +expected (1 lines): + b +actual (2 lines): + a 0 + a 1 +-- +ERR_MSG +} + +@test 'assert_equal() : displays details in multi-line format if is longer than one line' { + run assert_equal 'a' $'b 0\nb 1' + + assert_test_fail <<'ERR_MSG' + +-- values do not equal -- +expected (2 lines): + b 0 + b 1 +actual (1 lines): + a +-- +ERR_MSG +} + +@test 'assert_equal() : performs literal matching' { + run assert_equal 'a' '*' + + assert_test_fail <<'ERR_MSG' + +-- values do not equal -- +expected : * +actual : a +-- +ERR_MSG +} diff --git a/lib/bats-assert/test/assert_failure.bats b/lib/bats-assert/test/assert_failure.bats new file mode 100755 index 0000000..6291afe --- /dev/null +++ b/lib/bats-assert/test/assert_failure.bats @@ -0,0 +1,75 @@ +#!/usr/bin/env bats + +load test_helper + +@test "assert_failure(): returns 0 if \`\$status' is not 0" { + run false + run assert_failure + assert_test_pass +} + +@test "assert_failure(): returns 1 and displays details if \`\$status' is 0" { + run bash -c 'echo "a" + exit 0' + run assert_failure + + assert_test_fail <<'ERR_MSG' + +-- command succeeded, but it was expected to fail -- +output : a +-- +ERR_MSG +} + +@test "assert_failure(): displays \`\$output' in multi-line format if it is longer then one line" { + run bash -c 'printf "a 0\na 1" + exit 0' + run assert_failure + + assert_test_fail <<'ERR_MSG' + +-- command succeeded, but it was expected to fail -- +output (2 lines): + a 0 + a 1 +-- +ERR_MSG +} + +@test "assert_failure() : returns 0 if \`\$status' equals " { + run bash -c 'exit 1' + run assert_failure 1 + assert_test_pass +} + +@test "assert_failure() : returns 1 and displays details if \`\$status' does not equal " { + run bash -c 'echo "a" + exit 1' + run assert_failure 2 + + assert_test_fail <<'ERR_MSG' + +-- command failed as expected, but status differs -- +expected : 2 +actual : 1 +output : a +-- +ERR_MSG +} + +@test "assert_failure() : displays \`\$output' in multi-line format if it is longer then one line" { + run bash -c 'printf "a 0\na 1" + exit 1' + run assert_failure 2 + + assert_test_fail <<'ERR_MSG' + +-- command failed as expected, but status differs -- +expected : 2 +actual : 1 +output (2 lines): + a 0 + a 1 +-- +ERR_MSG +} diff --git a/lib/bats-assert/test/assert_line.bats b/lib/bats-assert/test/assert_line.bats new file mode 100755 index 0000000..151f688 --- /dev/null +++ b/lib/bats-assert/test/assert_line.bats @@ -0,0 +1,351 @@ +#!/usr/bin/env bats + +load test_helper + + +############################################################################### +# Containing a line +############################################################################### + +# +# Literal matching +# + +# Correctness +@test "assert_line() : returns 0 if is a line in \`\${lines[@]}'" { + run printf 'a\nb\nc' + run assert_line 'b' + assert_test_pass +} + +@test "assert_line() : returns 1 and displays details if is not a line in \`\${lines[@]}'" { + run echo 'b' + run assert_line 'a' + + assert_test_fail <<'ERR_MSG' + +-- output does not contain line -- +line : a +output : b +-- +ERR_MSG +} + +# Output formatting +@test "assert_line() : displays \`\$output' in multi-line format if it is longer than one line" { + run printf 'b 0\nb 1' + run assert_line 'a' + + assert_test_fail <<'ERR_MSG' + +-- output does not contain line -- +line : a +output (2 lines): + b 0 + b 1 +-- +ERR_MSG +} + +# Options +@test 'assert_line() : performs literal matching by default' { + run echo 'a' + run assert_line '*' + + assert_test_fail <<'ERR_MSG' + +-- output does not contain line -- +line : * +output : a +-- +ERR_MSG +} + + +# +# Partial matching: `-p' and `--partial' +# + +# Options +@test 'assert_line() -p : enables partial matching' { + run printf 'a\n_b_\nc' + run assert_line -p 'b' + assert_test_pass +} + +@test 'assert_line() --partial : enables partial matching' { + run printf 'a\n_b_\nc' + run assert_line --partial 'b' + assert_test_pass +} + +# Correctness +@test "assert_line() --partial : returns 0 if is a substring in any line in \`\${lines[@]}'" { + run printf 'a\n_b_\nc' + run assert_line --partial 'b' + assert_test_pass +} + +@test "assert_line() --partial : returns 1 and displays details if is not a substring in any lines in \`\${lines[@]}'" { + run echo 'b' + run assert_line --partial 'a' + + assert_test_fail <<'ERR_MSG' + +-- no output line contains substring -- +substring : a +output : b +-- +ERR_MSG +} + +# Output formatting +@test "assert_line() --partial : displays \`\$output' in multi-line format if it is longer than one line" { + run printf 'b 0\nb 1' + run assert_line --partial 'a' + + assert_test_fail <<'ERR_MSG' + +-- no output line contains substring -- +substring : a +output (2 lines): + b 0 + b 1 +-- +ERR_MSG +} + + +# +# Regular expression matching: `-e' and `--regexp' +# + +# Options +@test 'assert_line() -e : enables regular expression matching' { + run printf 'a\n_b_\nc' + run assert_line -e '^.b' + assert_test_pass +} + +@test 'assert_line() --regexp : enables regular expression matching' { + run printf 'a\n_b_\nc' + run assert_line --regexp '^.b' + assert_test_pass +} + +# Correctness +@test "assert_line() --regexp : returns 0 if matches any line in \`\${lines[@]}'" { + run printf 'a\n_b_\nc' + run assert_line --regexp '^.b' + assert_test_pass +} + +@test "assert_line() --regexp : returns 1 and displays details if does not match any lines in \`\${lines[@]}'" { + run echo 'b' + run assert_line --regexp '^.a' + + assert_test_fail <<'ERR_MSG' + +-- no output line matches regular expression -- +regexp : ^.a +output : b +-- +ERR_MSG +} + +# Output formatting +@test "assert_line() --regexp : displays \`\$output' in multi-line format if longer than one line" { + run printf 'b 0\nb 1' + run assert_line --regexp '^.a' + + assert_test_fail <<'ERR_MSG' + +-- no output line matches regular expression -- +regexp : ^.a +output (2 lines): + b 0 + b 1 +-- +ERR_MSG +} + + +############################################################################### +# Matching single line: `-n' and `--index' +############################################################################### + +# Options +@test 'assert_line() -n : matches against the -th line only' { + run printf 'a\nb\nc' + run assert_line -n 1 'b' + assert_test_pass +} + +@test 'assert_line() --index : matches against the -th line only' { + run printf 'a\nb\nc' + run assert_line --index 1 'b' + assert_test_pass +} + +@test 'assert_line() --index : returns 1 and displays an error message if is not an integer' { + run assert_line --index 1a + + assert_test_fail <<'ERR_MSG' + +-- ERROR: assert_line -- +`--index' requires an integer argument: `1a' +-- +ERR_MSG +} + + +# +# Literal matching +# + +# Correctness +@test "assert_line() --index : returns 0 if equals \`\${lines[]}'" { + run printf 'a\nb\nc' + run assert_line --index 1 'b' + assert_test_pass +} + +@test "assert_line() --index : returns 1 and displays details if does not equal \`\${lines[]}'" { + run printf 'a\nb\nc' + run assert_line --index 1 'a' + + assert_test_fail <<'ERR_MSG' + +-- line differs -- +index : 1 +expected : a +actual : b +-- +ERR_MSG +} + +# Options +@test 'assert_line() --index : performs literal matching by default' { + run printf 'a\nb\nc' + run assert_line --index 1 '*' + + assert_test_fail <<'ERR_MSG' + +-- line differs -- +index : 1 +expected : * +actual : b +-- +ERR_MSG +} + + +# +# Partial matching: `-p' and `--partial' +# + +# Options +@test 'assert_line() --index -p : enables partial matching' { + run printf 'a\n_b_\nc' + run assert_line --index 1 -p 'b' + assert_test_pass +} + +@test 'assert_line() --index --partial : enables partial matching' { + run printf 'a\n_b_\nc' + run assert_line --index 1 --partial 'b' + assert_test_pass +} + +# Correctness +@test "assert_line() --index --partial : returns 0 if is a substring in \`\${lines[]}'" { + run printf 'a\n_b_\nc' + run assert_line --index 1 --partial 'b' + assert_test_pass +} + +@test "assert_line() --index --partial : returns 1 and displays details if is not a substring in \`\${lines[]}'" { + run printf 'b 0\nb 1' + run assert_line --index 1 --partial 'a' + + assert_test_fail <<'ERR_MSG' + +-- line does not contain substring -- +index : 1 +substring : a +line : b 1 +-- +ERR_MSG +} + + +# +# Regular expression matching: `-e' and `--regexp' +# + +# Options +@test 'assert_line() --index -e : enables regular expression matching' { + run printf 'a\n_b_\nc' + run assert_line --index 1 -e '^.b' + assert_test_pass +} + +@test 'assert_line() --index --regexp : enables regular expression matching' { + run printf 'a\n_b_\nc' + run assert_line --index 1 --regexp '^.b' + assert_test_pass +} + +# Correctness +@test "assert_line() --index --regexp : returns 0 if matches \`\${lines[]}'" { + run printf 'a\n_b_\nc' + run assert_line --index 1 --regexp '^.b' + assert_test_pass +} + +@test "assert_line() --index --regexp : returns 1 and displays details if does not match \`\${lines[]}'" { + run printf 'a\nb\nc' + run assert_line --index 1 --regexp '^.a' + + assert_test_fail <<'ERR_MSG' + +-- regular expression does not match line -- +index : 1 +regexp : ^.a +line : b +-- +ERR_MSG +} + + +############################################################################### +# Common +############################################################################### + +@test "assert_line(): \`--partial' and \`--regexp' are mutually exclusive" { + run assert_line --partial --regexp + + assert_test_fail <<'ERR_MSG' + +-- ERROR: assert_line -- +`--partial' and `--regexp' are mutually exclusive +-- +ERR_MSG +} + +@test 'assert_line() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { + run assert_line --regexp '[.*' + + assert_test_fail <<'ERR_MSG' + +-- ERROR: assert_line -- +Invalid extended regular expression: `[.*' +-- +ERR_MSG +} + +@test "assert_line(): \`--' stops parsing options" { + run printf 'a\n-p\nc' + run assert_line -- '-p' + assert_test_pass +} diff --git a/lib/bats-assert/test/assert_output.bats b/lib/bats-assert/test/assert_output.bats new file mode 100755 index 0000000..e9609e0 --- /dev/null +++ b/lib/bats-assert/test/assert_output.bats @@ -0,0 +1,285 @@ +#!/usr/bin/env bats + +load test_helper + +# +# Literal matching +# + +# Correctness +@test "assert_output() : returns 0 if equals \`\$output'" { + run echo 'a' + run assert_output 'a' + assert_test_pass +} + +@test "assert_output() : returns 1 and displays details if does not equal \`\$output'" { + run echo 'b' + run assert_output 'a' + + assert_test_fail <<'ERR_MSG' + +-- output differs -- +expected : a +actual : b +-- +ERR_MSG +} + +@test 'assert_output(): succeeds if output is non-empty' { + run echo 'a' + run assert_output + + assert_test_pass +} + +@test 'assert_output(): fails if output is empty' { + run echo '' + run assert_output + + assert_test_fail <<'ERR_MSG' + +-- no output -- +expected non-empty output, but output was empty +-- +ERR_MSG +} + +@test 'assert_output() - : reads from STDIN' { + run echo 'a' + run assert_output - < from STDIN' { + run echo 'a' + run assert_output --stdin <: displays details in multi-line format if \`\$output' is longer than one line" { + run printf 'b 0\nb 1' + run assert_output 'a' + + assert_test_fail <<'ERR_MSG' + +-- output differs -- +expected (1 lines): + a +actual (2 lines): + b 0 + b 1 +-- +ERR_MSG +} + +@test 'assert_output() : displays details in multi-line format if is longer than one line' { + run echo 'b' + run assert_output $'a 0\na 1' + + assert_test_fail <<'ERR_MSG' + +-- output differs -- +expected (2 lines): + a 0 + a 1 +actual (1 lines): + b +-- +ERR_MSG +} + +# Options +@test 'assert_output() : performs literal matching by default' { + run echo 'a' + run assert_output '*' + + assert_test_fail <<'ERR_MSG' + +-- output differs -- +expected : * +actual : a +-- +ERR_MSG +} + + +# +# Partial matching: `-p' and `--partial' +# + +@test 'assert_output() -p : enables partial matching' { + run echo 'abc' + run assert_output -p 'b' + assert_test_pass +} + +@test 'assert_output() --partial : enables partial matching' { + run echo 'abc' + run assert_output --partial 'b' + assert_test_pass +} + +# Correctness +@test "assert_output() --partial : returns 0 if is a substring in \`\$output'" { + run printf 'a\nb\nc' + run assert_output --partial 'b' + assert_test_pass +} + +@test "assert_output() --partial : returns 1 and displays details if is not a substring in \`\$output'" { + run echo 'b' + run assert_output --partial 'a' + + assert_test_fail <<'ERR_MSG' + +-- output does not contain substring -- +substring : a +output : b +-- +ERR_MSG +} + +# Output formatting +@test "assert_output() --partial : displays details in multi-line format if \`\$output' is longer than one line" { + run printf 'b 0\nb 1' + run assert_output --partial 'a' + + assert_test_fail <<'ERR_MSG' + +-- output does not contain substring -- +substring (1 lines): + a +output (2 lines): + b 0 + b 1 +-- +ERR_MSG +} + +@test 'assert_output() --partial : displays details in multi-line format if is longer than one line' { + run echo 'b' + run assert_output --partial $'a 0\na 1' + + assert_test_fail <<'ERR_MSG' + +-- output does not contain substring -- +substring (2 lines): + a 0 + a 1 +output (1 lines): + b +-- +ERR_MSG +} + + +# +# Regular expression matching: `-e' and `--regexp' +# + +@test 'assert_output() -e : enables regular expression matching' { + run echo 'abc' + run assert_output -e '^a' + assert_test_pass +} + +@test 'assert_output() --regexp : enables regular expression matching' { + run echo 'abc' + run assert_output --regexp '^a' + assert_test_pass +} + +# Correctness +@test "assert_output() --regexp : returns 0 if matches \`\$output'" { + run printf 'a\nb\nc' + run assert_output --regexp '.*b.*' + assert_test_pass +} + +@test "assert_output() --regexp : returns 1 and displays details if does not match \`\$output'" { + run echo 'b' + run assert_output --regexp '.*a.*' + + assert_test_fail <<'ERR_MSG' + +-- regular expression does not match output -- +regexp : .*a.* +output : b +-- +ERR_MSG +} + +# Output formatting +@test "assert_output() --regexp : displays details in multi-line format if \`\$output' is longer than one line" { + run printf 'b 0\nb 1' + run assert_output --regexp '.*a.*' + + assert_test_fail <<'ERR_MSG' + +-- regular expression does not match output -- +regexp (1 lines): + .*a.* +output (2 lines): + b 0 + b 1 +-- +ERR_MSG +} + +@test 'assert_output() --regexp : displays details in multi-line format if is longer than one line' { + run echo 'b' + run assert_output --regexp $'.*a\nb.*' + + assert_test_fail <<'ERR_MSG' + +-- regular expression does not match output -- +regexp (2 lines): + .*a + b.* +output (1 lines): + b +-- +ERR_MSG +} + +# Error handling +@test 'assert_output() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { + run assert_output --regexp '[.*' + + assert_test_fail <<'ERR_MSG' + +-- ERROR: assert_output -- +Invalid extended regular expression: `[.*' +-- +ERR_MSG +} + + +# +# Common +# + +@test "assert_output(): \`--partial' and \`--regexp' are mutually exclusive" { + run assert_output --partial --regexp + + assert_test_fail <<'ERR_MSG' + +-- ERROR: assert_output -- +`--partial' and `--regexp' are mutually exclusive +-- +ERR_MSG +} + +@test "assert_output(): \`--' stops parsing options" { + run echo '-p' + run assert_output -- '-p' + assert_test_pass +} diff --git a/lib/bats-assert/test/assert_success.bats b/lib/bats-assert/test/assert_success.bats new file mode 100755 index 0000000..66f5aa7 --- /dev/null +++ b/lib/bats-assert/test/assert_success.bats @@ -0,0 +1,40 @@ +#!/usr/bin/env bats + +load test_helper + +@test "assert_success(): returns 0 if \`\$status' is 0" { + run true + run assert_success + + assert_test_pass +} + +@test "assert_success(): returns 1 and displays details if \`\$status' is not 0" { + run bash -c 'echo "a" + exit 1' + run assert_success + + assert_test_fail <<'ERR_MSG' + +-- command failed -- +status : 1 +output : a +-- +ERR_MSG +} + +@test "assert_success(): displays \`\$output' in multi-line format if it is longer than one line" { + run bash -c 'printf "a 0\na 1" + exit 1' + run assert_success + + assert_test_fail <<'ERR_MSG' + +-- command failed -- +status : 1 +output (2 lines): + a 0 + a 1 +-- +ERR_MSG +} diff --git a/lib/bats-assert/test/refute.bats b/lib/bats-assert/test/refute.bats new file mode 100755 index 0000000..f9e2737 --- /dev/null +++ b/lib/bats-assert/test/refute.bats @@ -0,0 +1,18 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'refute() : returns 0 if evaluates to FALSE' { + run refute false + assert_test_pass +} + +@test 'refute() : returns 1 and displays if it evaluates to TRUE' { + run refute true + assert_test_fail <<'ERR_MSG' + +-- assertion succeeded, but it was expected to fail -- +expression : true +-- +ERR_MSG +} diff --git a/lib/bats-assert/test/refute_line.bats b/lib/bats-assert/test/refute_line.bats new file mode 100755 index 0000000..fd6221c --- /dev/null +++ b/lib/bats-assert/test/refute_line.bats @@ -0,0 +1,344 @@ +#!/usr/bin/env bats + +load test_helper + + +############################################################################### +# Containing a line +############################################################################### + +# +# Literal matching +# + +# Correctness +@test "refute_line() : returns 0 if is not a line in \`\${lines[@]}'" { + run printf 'a\nb\nc' + run refute_line 'd' + assert_test_pass +} + +@test "refute_line() : returns 1 and displays details if is not a line in \`\${lines[@]}'" { + run echo 'a' + run refute_line 'a' + + assert_test_fail <<'ERR_MSG' + +-- line should not be in output -- +line : a +index : 0 +output : a +-- +ERR_MSG +} + +# Output formatting +@test "refute_line() : displays \`\$output' in multi-line format if it is longer than one line" { + run printf 'a 0\na 1\na 2' + run refute_line 'a 1' + + assert_test_fail <<'ERR_MSG' + +-- line should not be in output -- +line : a 1 +index : 1 +output (3 lines): + a 0 +> a 1 + a 2 +-- +ERR_MSG +} + +# Options +@test 'refute_line() : performs literal matching by default' { + run echo 'a' + run refute_line '*' + assert_test_pass +} + + +# +# Partial matching: `-p' and `--partial' +# + +# Options +@test 'refute_line() -p : enables partial matching' { + run printf 'a\nb\nc' + run refute_line -p 'd' + assert_test_pass +} + +@test 'refute_line() --partial : enables partial matching' { + run printf 'a\nb\nc' + run refute_line --partial 'd' + assert_test_pass +} + +# Correctness +@test "refute_line() --partial : returns 0 if is not a substring in any line in \`\${lines[@]}'" { + run printf 'a\nb\nc' + run refute_line --partial 'd' + assert_test_pass +} + +@test "refute_line() --partial : returns 1 and displays details if is a substring in any line in \`\${lines[@]}'" { + run echo 'a' + run refute_line --partial 'a' + + assert_test_fail <<'ERR_MSG' + +-- no line should contain substring -- +substring : a +index : 0 +output : a +-- +ERR_MSG +} + +# Output formatting +@test "refute_line() --partial : displays \`\$output' in multi-line format if it is longer than one line" { + run printf 'a\nabc\nc' + run refute_line --partial 'b' + + assert_test_fail <<'ERR_MSG' + +-- no line should contain substring -- +substring : b +index : 1 +output (3 lines): + a +> abc + c +-- +ERR_MSG +} + + +# +# Regular expression matching: `-e' and `--regexp' +# + +# Options +@test 'refute_line() -e : enables regular expression matching' { + run printf 'a\nb\nc' + run refute_line -e '^.d' + assert_test_pass +} + +@test 'refute_line() --regexp : enables regular expression matching' { + run printf 'a\nb\nc' + run refute_line --regexp '^.d' + assert_test_pass +} + +# Correctness +@test "refute_line() --regexp : returns 0 if does not match any line in \`\${lines[@]}'" { + run printf 'a\nb\nc' + run refute_line --regexp '.*d.*' + assert_test_pass +} + +@test "refute_line() --regexp : returns 1 and displays details if matches any lines in \`\${lines[@]}'" { + run echo 'a' + run refute_line --regexp '.*a.*' + + assert_test_fail <<'ERR_MSG' + +-- no line should match the regular expression -- +regexp : .*a.* +index : 0 +output : a +-- +ERR_MSG +} + +# Output formatting +@test "refute_line() --regexp : displays \`\$output' in multi-line format if longer than one line" { + run printf 'a\nabc\nc' + run refute_line --regexp '.*b.*' + + assert_test_fail <<'ERR_MSG' + +-- no line should match the regular expression -- +regexp : .*b.* +index : 1 +output (3 lines): + a +> abc + c +-- +ERR_MSG +} + + +############################################################################### +# Matching single line: `-n' and `--index' +############################################################################### + +# Options +@test 'refute_line() -n : matches against the -th line only' { + run printf 'a\nb\nc' + run refute_line -n 1 'd' + assert_test_pass +} + +@test 'refute_line() --index : matches against the -th line only' { + run printf 'a\nb\nc' + run refute_line --index 1 'd' + assert_test_pass +} + +@test 'refute_line() --index : returns 1 and displays an error message if is not an integer' { + run refute_line --index 1a + + assert_test_fail <<'ERR_MSG' + +-- ERROR: refute_line -- +`--index' requires an integer argument: `1a' +-- +ERR_MSG +} + + +# +# Literal matching +# + +# Correctness +@test "refute_line() --index : returns 0 if does not equal \`\${lines[]}'" { + run printf 'a\nb\nc' + run refute_line --index 1 'd' + assert_test_pass +} + +@test "refute_line() --index : returns 1 and displays details if equals \`\${lines[]}'" { + run printf 'a\nb\nc' + run refute_line --index 1 'b' + + assert_test_fail <<'ERR_MSG' + +-- line should differ -- +index : 1 +line : b +-- +ERR_MSG +} + +# Options +@test 'refute_line() --index : performs literal matching by default' { + run printf 'a\nb\nc' + run refute_line --index 1 '*' + assert_test_pass +} + + +# +# Partial matching: `-p' and `--partial' +# + +# Options +@test 'refute_line() --index -p : enables partial matching' { + run printf 'a\nb\nc' + run refute_line --index 1 -p 'd' + assert_test_pass +} + +@test 'refute_line() --index --partial : enables partial matching' { + run printf 'a\nb\nc' + run refute_line --index 1 --partial 'd' + assert_test_pass +} + +# Correctness +@test "refute_line() --index --partial : returns 0 if is not a substring in \`\${lines[]}'" { + run printf 'a\nabc\nc' + run refute_line --index 1 --partial 'd' + assert_test_pass +} + +@test "refute_line() --index --partial : returns 1 and displays details if is a substring in \`\${lines[]}'" { + run printf 'a\nabc\nc' + run refute_line --index 1 --partial 'b' + + assert_test_fail <<'ERR_MSG' + +-- line should not contain substring -- +index : 1 +substring : b +line : abc +-- +ERR_MSG +} + + +# +# Regular expression matching: `-e' and `--regexp' +# + +# Options +@test 'refute_line() --index -e : enables regular expression matching' { + run printf 'a\nb\nc' + run refute_line --index 1 -e '^.b' + assert_test_pass +} + +@test 'refute_line() --index --regexp : enables regular expression matching' { + run printf 'a\nb\nc' + run refute_line --index 1 --regexp '^.b' + assert_test_pass +} + +# Correctness +@test "refute_line() --index --regexp : returns 0 if does not match \`\${lines[]}'" { + run printf 'a\nabc\nc' + run refute_line --index 1 --regexp '.*d.*' + assert_test_pass +} + +@test "refute_line() --index --regexp : returns 1 and displays details if matches \`\${lines[]}'" { + run printf 'a\nabc\nc' + run refute_line --index 1 --regexp '.*b.*' + + assert_test_fail <<'ERR_MSG' + +-- regular expression should not match line -- +index : 1 +regexp : .*b.* +line : abc +-- +ERR_MSG +} + + +############################################################################### +# Common +############################################################################### + +@test "refute_line(): \`--partial' and \`--regexp' are mutually exclusive" { + run refute_line --partial --regexp + + assert_test_fail <<'ERR_MSG' + +-- ERROR: refute_line -- +`--partial' and `--regexp' are mutually exclusive +-- +ERR_MSG +} + +@test 'refute_line() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { + run refute_line --regexp '[.*' + + assert_test_fail <<'ERR_MSG' + +-- ERROR: refute_line -- +Invalid extended regular expression: `[.*' +-- +ERR_MSG +} + +@test "refute_line(): \`--' stops parsing options" { + run printf 'a\n--\nc' + run refute_line -- '-p' + assert_test_pass +} diff --git a/lib/bats-assert/test/refute_output.bats b/lib/bats-assert/test/refute_output.bats new file mode 100755 index 0000000..9d8711d --- /dev/null +++ b/lib/bats-assert/test/refute_output.bats @@ -0,0 +1,230 @@ +#!/usr/bin/env bats + +load test_helper + + +# +# Literal matching +# + +# Correctness +@test "refute_output() : returns 0 if does not equal \`\$output'" { + run echo 'b' + run refute_output 'a' + assert_test_pass +} + +@test "refute_output() : returns 1 and displays details if equals \`\$output'" { + run echo 'a' + run refute_output 'a' + + assert_test_fail <<'ERR_MSG' + +-- output equals, but it was expected to differ -- +output : a +-- +ERR_MSG +} + +@test 'refute_output(): succeeds if output is empty' { + run echo '' + run refute_output + + assert_test_pass +} + +@test 'refute_output(): fails if output is non-empty' { + run echo 'a' + run refute_output + + assert_test_fail <<'ERR_MSG' + +-- output non-empty, but expected no output -- +output : a +-- +ERR_MSG +} + +@test 'refute_output() - : reads from STDIN' { + run echo '-' + run refute_output - < from STDIN' { + run echo '--stdin' + run refute_output --stdin <: displays details in multi-line format if necessary' { + run printf 'a 0\na 1' + run refute_output $'a 0\na 1' + + assert_test_fail <<'ERR_MSG' + +-- output equals, but it was expected to differ -- +output (2 lines): + a 0 + a 1 +-- +ERR_MSG +} + +# Options +@test 'refute_output() : performs literal matching by default' { + run echo 'a' + run refute_output '*' + assert_test_pass +} + + +# +# Partial matching: `-p' and `--partial' +# + +# Options +@test 'refute_output() -p : enables partial matching' { + run echo 'abc' + run refute_output -p 'd' + assert_test_pass +} + +@test 'refute_output() --partial : enables partial matching' { + run echo 'abc' + run refute_output --partial 'd' + assert_test_pass +} + +# Correctness +@test "refute_output() --partial : returns 0 if is not a substring in \`\$output'" { + run printf 'a\nb\nc' + run refute_output --partial 'd' + assert_test_pass +} + +@test "refute_output() --partial : returns 1 and displays details if is a substring in \`\$output'" { + run echo 'a' + run refute_output --partial 'a' + + assert_test_fail <<'ERR_MSG' + +-- output should not contain substring -- +substring : a +output : a +-- +ERR_MSG +} + +# Output formatting +@test 'refute_output() --partial : displays details in multi-line format if necessary' { + run printf 'a 0\na 1' + run refute_output --partial 'a' + + assert_test_fail <<'ERR_MSG' + +-- output should not contain substring -- +substring (1 lines): + a +output (2 lines): + a 0 + a 1 +-- +ERR_MSG +} + + +# +# Regular expression matching: `-e' and `--regexp' +# + +# Options +@test 'refute_output() -e : enables regular expression matching' { + run echo 'abc' + run refute_output -e '^d' + assert_test_pass +} + +@test 'refute_output() --regexp : enables regular expression matching' { + run echo 'abc' + run refute_output --regexp '^d' + assert_test_pass +} + +# Correctness +@test "refute_output() --regexp : returns 0 if does not match \`\$output'" { + run printf 'a\nb\nc' + run refute_output --regexp '.*d.*' + assert_test_pass +} + +@test "refute_output() --regexp : returns 1 and displays details if matches \`\$output'" { + run echo 'a' + run refute_output --regexp '.*a.*' + + assert_test_fail <<'ERR_MSG' + +-- regular expression should not match output -- +regexp : .*a.* +output : a +-- +ERR_MSG +} + +# Output formatting +@test 'refute_output() --regexp : displays details in multi-line format if necessary' { + run printf 'a 0\na 1' + run refute_output --regexp '.*a.*' + + assert_test_fail <<'ERR_MSG' + +-- regular expression should not match output -- +regexp (1 lines): + .*a.* +output (2 lines): + a 0 + a 1 +-- +ERR_MSG +} + +# Error handling +@test 'refute_output() --regexp : returns 1 and displays an error message if is not a valid extended regular expression' { + run refute_output --regexp '[.*' + + assert_test_fail <<'ERR_MSG' + +-- ERROR: refute_output -- +Invalid extended regular expression: `[.*' +-- +ERR_MSG +} + + +# +# Common +# + +@test "refute_output(): \`--partial' and \`--regexp' are mutually exclusive" { + run refute_output --partial --regexp + + assert_test_fail <<'ERR_MSG' + +-- ERROR: refute_output -- +`--partial' and `--regexp' are mutually exclusive +-- +ERR_MSG +} + +@test "refute_output(): \`--' stops parsing options" { + run echo '--' + run refute_output -- '-p' + assert_test_pass +} diff --git a/lib/bats-assert/test/test_helper.bash b/lib/bats-assert/test/test_helper.bash new file mode 100644 index 0000000..65d366f --- /dev/null +++ b/lib/bats-assert/test/test_helper.bash @@ -0,0 +1,27 @@ +# Load dependencies. +load "${BATS_TEST_DIRNAME}/../node_modules/bats-support/load.bash" + +# Load library. +load '../load' + +# validate that bats-assert is safe to use under -u +set -u + +: "${status:=}" +: "${lines:=}" +: "${output:=}" + +assert_test_pass() { + test "$status" -eq 0 + test "${#lines[@]}" -eq 0 +} + +assert_test_fail() { + local err_msg="${1-$(cat -)}" + local num_lines + num_lines="$(printf '%s' "$err_msg" | wc -l)" + + test "$status" -eq 1 + test "${#lines[@]}" -eq "$num_lines" + test "$output" == "$err_msg" +} diff --git a/lib/bats-support/.gitignore b/lib/bats-support/.gitignore new file mode 100644 index 0000000..05f900c --- /dev/null +++ b/lib/bats-support/.gitignore @@ -0,0 +1,4 @@ +/node_modules +/package-lock.json +/yarn.lock +/bats-support-*.tgz diff --git a/lib/bats-support/.travis.yml b/lib/bats-support/.travis.yml new file mode 100644 index 0000000..965fe2d --- /dev/null +++ b/lib/bats-support/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: node +cache: npm diff --git a/lib/bats-support/CHANGELOG.md b/lib/bats-support/CHANGELOG.md new file mode 100644 index 0000000..324d247 --- /dev/null +++ b/lib/bats-support/CHANGELOG.md @@ -0,0 +1,46 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + + +## [0.3.0] - 2016-11-29 + +### Added + +- Restricting invocation to specific locations with + `batslib_is_caller()` + + +## [0.2.0] - 2016-03-22 + +### Added + +- `npm` support +- Reporting arbitrary failures with `fail()` (moved from `bats-assert`) + +### Changed + +- Library renamed to `bats-support` + + +## 0.1.0 - 2016-02-16 + +### Added + +- Two-column key-value formatting with `batslib_print_kv_single()` +- Multi-line key-value formatting with `batslib_print_kv_multi()` +- Mixed formatting with `batslib_print_kv_single_or_multi()` +- Header and footer decoration with `batslib_decorate()` +- Prefixing lines with `batslib_prefix()` +- Marking lines with `batslib_mark()` +- Common output function `batslib_err()` +- Line counting with `batslib_count_lines()` +- Checking whether a text is one line long with + `batslib_is_single_line()` +- Determining key width for two-column and mixed formatting with + `batslib_get_max_single_line_key_width()` + + +[0.3.0]: https://github.com/ztombol/bats-support/compare/v0.2.0...v0.3.0 +[0.2.0]: https://github.com/ztombol/bats-support/compare/v0.1.0...v0.2.0 diff --git a/lib/bats-support/LICENSE b/lib/bats-support/LICENSE new file mode 100644 index 0000000..670154e --- /dev/null +++ b/lib/bats-support/LICENSE @@ -0,0 +1,116 @@ +CC0 1.0 Universal + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator and +subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for the +purpose of contributing to a commons of creative, cultural and scientific +works ("Commons") that the public can reliably and without fear of later +claims of infringement build upon, modify, incorporate in other works, reuse +and redistribute as freely as possible in any form whatsoever and for any +purposes, including without limitation commercial purposes. These owners may +contribute to the Commons to promote the ideal of a free culture and the +further production of creative, cultural and scientific works, or to gain +reputation or greater distribution for their Work in part through the use and +efforts of others. + +For these and/or other purposes and motivations, and without any expectation +of additional consideration or compensation, the person associating CC0 with a +Work (the "Affirmer"), to the extent that he or she is an owner of Copyright +and Related Rights in the Work, voluntarily elects to apply CC0 to the Work +and publicly distribute the Work under its terms, with knowledge of his or her +Copyright and Related Rights in the Work and the meaning and intended legal +effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not limited +to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, communicate, + and translate a Work; + + ii. moral rights retained by the original author(s) and/or performer(s); + + iii. publicity and privacy rights pertaining to a person's image or likeness + depicted in a Work; + + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + + v. rights protecting the extraction, dissemination, use and reuse of data in + a Work; + + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation thereof, + including any amended or successor version of such directive); and + + vii. other similar, equivalent or corresponding rights throughout the world + based on applicable law or treaty, and any national implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention of, +applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and +unconditionally waives, abandons, and surrenders all of Affirmer's Copyright +and Related Rights and associated claims and causes of action, whether now +known or unknown (including existing as well as future claims and causes of +action), in the Work (i) in all territories worldwide, (ii) for the maximum +duration provided by applicable law or treaty (including future time +extensions), (iii) in any current or future medium and for any number of +copies, and (iv) for any purpose whatsoever, including without limitation +commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes +the Waiver for the benefit of each member of the public at large and to the +detriment of Affirmer's heirs and successors, fully intending that such Waiver +shall not be subject to revocation, rescission, cancellation, termination, or +any other legal or equitable action to disrupt the quiet enjoyment of the Work +by the public as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason be +judged legally invalid or ineffective under applicable law, then the Waiver +shall be preserved to the maximum extent permitted taking into account +Affirmer's express Statement of Purpose. In addition, to the extent the Waiver +is so judged Affirmer hereby grants to each affected person a royalty-free, +non transferable, non sublicensable, non exclusive, irrevocable and +unconditional license to exercise Affirmer's Copyright and Related Rights in +the Work (i) in all territories worldwide, (ii) for the maximum duration +provided by applicable law or treaty (including future time extensions), (iii) +in any current or future medium and for any number of copies, and (iv) for any +purpose whatsoever, including without limitation commercial, advertising or +promotional purposes (the "License"). The License shall be deemed effective as +of the date CC0 was applied by Affirmer to the Work. Should any part of the +License for any reason be judged legally invalid or ineffective under +applicable law, such partial invalidity or ineffectiveness shall not +invalidate the remainder of the License, and in such case Affirmer hereby +affirms that he or she will not (i) exercise any of his or her remaining +Copyright and Related Rights in the Work or (ii) assert any associated claims +and causes of action with respect to the Work, in either case contrary to +Affirmer's express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + + b. Affirmer offers the Work as-is and makes no representations or warranties + of any kind concerning the Work, express, implied, statutory or otherwise, + including without limitation warranties of title, merchantability, fitness + for a particular purpose, non infringement, or the absence of latent or + other defects, accuracy, or the present or absence of errors, whether or not + discoverable, all to the greatest extent permissible under applicable law. + + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without limitation + any person's Copyright and Related Rights in the Work. Further, Affirmer + disclaims responsibility for obtaining any necessary consents, permissions + or other rights required for any use of the Work. + + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to this + CC0 or use of the Work. + +For more information, please see + diff --git a/lib/bats-support/README.md b/lib/bats-support/README.md new file mode 100644 index 0000000..71c02ba --- /dev/null +++ b/lib/bats-support/README.md @@ -0,0 +1,189 @@ +*__Important:__ `bats-core` has been renamed to `bats-support`. GitHub +automatically redirects all references, e.g. submodules and clones will +continue to work, but you are encouraged to [update][github-rename] +them. Version numbering continues where `bats-core` left off.* + +[github-rename]: https://help.github.com/articles/renaming-a-repository/ + +- - - - - + +# bats-support + +[![GitHub license](https://img.shields.io/badge/license-CC0-blue.svg)](https://raw.githubusercontent.com/ztombol/bats-support/master/LICENSE) +[![GitHub release](https://img.shields.io/github/release/ztombol/bats-support.svg)](https://github.com/ztombol/bats-support/releases/latest) +[![Build Status](https://travis-ci.org/ztombol/bats-support.svg?branch=master)](https://travis-ci.org/ztombol/bats-support) + +`bats-support` is a supporting library providing common functions to +test helper libraries written for [Bats][bats]. + +Features: +- [error reporting](#error-reporting) +- [output formatting](#output-formatting) +- [language tools](#language-and-execution) + +See the [shared documentation][bats-docs] to learn how to install and +load this library. + +If you want to use this library in your own helpers or just want to +learn about its internals see the developer documentation in the [source +files](src). + + +## Error reporting + +### `fail` + +Display an error message and fail. This function provides a convenient +way to report failure in arbitrary situations. You can use it to +implement your own helpers when the ones available do not meet your +needs. Other functions use it internally as well. + +```bash +@test 'fail()' { + fail 'this test always fails' +} +``` + +The message can also be specified on the standard input. + +```bash +@test 'fail() with pipe' { + echo 'this test always fails' | fail +} +``` + +This function always fails and simply outputs the given message. + +``` +this test always fails +``` + + +## Output formatting + +Many test helpers need to produce human readable output. This library +provides a simple way to format simple messages and key value pairs, and +display them on the standard error. + + +### Simple message + +Simple messages without structure, e.g. one-line error messages, are +simply wrapped in a header and a footer to help them stand out. + +``` +-- ERROR: assert_output -- +`--partial' and `--regexp' are mutually exclusive +-- +``` + + +### Key-Value pairs + +Some helpers, e.g. [assertions][bats-assert], structure output as +key-value pairs. This library provides two ways to format them. + +When the value is one line long, a pair can be displayed in a columnar +fashion called ***two-column*** format. + +``` +-- output differs -- +expected : want +actual : have +-- +``` + +When the value is longer than one line, the key and value must be +displayed on separate lines. First, the key is displayed along with the +number of lines in the value. Then, the value, indented by two spaces +for added readability, starting on the next line. This is called +***multi-line*** format. + +``` +-- command failed -- +status : 1 +output (2 lines): + Error! Something went terribly wrong! + Our engineers are panicing... \`>`;/ +-- +``` + +Sometimes, for clarity, it is a good idea to display related values also +in this format, even if they are just one line long. + +``` +-- output differs -- +expected (1 lines): + want +actual (3 lines): + have 1 + have 2 + have 3 +-- +``` + +## Language and Execution + +### Restricting invocation to specific locations + +Sometimes a helper may work properly only when called from a certain +location. Because it depends on variables to be set or some other side +effect. + +A good example is cleaning up temporary files only if the test has +succeeded. The outcome of a test is only available in `teardown`. Thus, +to avoid programming mistakes, it makes sense to restrict such a +clean-up helper to that function. + +`batslib_is_caller` checks the call stack and returns `0` if the caller +was invoked from a given function, and `1` otherwise. This function +becomes really useful with the `--indirect` option, which allows calls +through intermediate functions, e.g. the calling function may be called +from a function that was called from the given function. + +Staying with the example above, the following code snippet implements a +helper that is restricted to `teardown` or any function called +indirectly from it. + +```shell +clean_up() { + # Check caller. + if batslib_is_caller --indirect 'teardown'; then + echo "Must be called from \`teardown'" \ + | batslib_decorate 'ERROR: clean_up' \ + | fail + return $? + fi + + # Body goes here... +} +``` + +In some cases a helper may be called from multiple locations. For +example, a logging function that uses the test name, description or +number, information only available in `setup`, `@test` or `teardown`, to +distinguish entries. The following snippet implements this restriction. + +```shell +log_test() { + # Check caller. + if ! ( batslib_is_caller --indirect 'setup' \ + || batslib_is_caller --indirect "$BATS_TEST_NAME" \ + || batslib_is_caller --indirect 'teardown' ) + then + echo "Must be called from \`setup', \`@test' or \`teardown'" \ + | batslib_decorate 'ERROR: log_test' \ + | fail + return $? + fi + + # Body goes here... +} +``` + + + + +[bats]: https://github.com/sstephenson/bats +[bats-docs]: https://github.com/ztombol/bats-docs +[bats-assert]: https://github.com/ztombol/bats-assert diff --git a/lib/bats-support/load.bash b/lib/bats-support/load.bash new file mode 100644 index 0000000..0727aeb --- /dev/null +++ b/lib/bats-support/load.bash @@ -0,0 +1,3 @@ +source "$(dirname "${BASH_SOURCE[0]}")/src/output.bash" +source "$(dirname "${BASH_SOURCE[0]}")/src/error.bash" +source "$(dirname "${BASH_SOURCE[0]}")/src/lang.bash" diff --git a/lib/bats-support/package.json b/lib/bats-support/package.json new file mode 100644 index 0000000..5015687 --- /dev/null +++ b/lib/bats-support/package.json @@ -0,0 +1,30 @@ +{ + "name": "bats-support", + "version": "0.3.0", + "description": "Supporting library for Bats test helpers", + "homepage": "https://github.com/jasonkarns/bats-support", + "license": "CC0-1.0", + "author": "Zoltán Tömböl (https://github.com/ztombol)", + "contributors": [ + "Jason Karns (http://jason.karns.name)" + ], + "repository": "github:jasonkarns/bats-support", + "bugs": "https://github.com/jasonkarns/bats-support/issues", + "directories": { + "lib": "src", + "test": "test" + }, + "files": [ + "load.bash", + "src" + ], + "scripts": { + "test": "bats ${CI+-t} test" + }, + "devDependencies": { + "bats": "^1" + }, + "peerDependencies": { + "bats": "0.4 || ^1" + } +} diff --git a/lib/bats-support/src/error.bash b/lib/bats-support/src/error.bash new file mode 100644 index 0000000..e5d9791 --- /dev/null +++ b/lib/bats-support/src/error.bash @@ -0,0 +1,41 @@ +# +# bats-support - Supporting library for Bats test helpers +# +# Written in 2016 by Zoltan Tombol +# +# To the extent possible under law, the author(s) have dedicated all +# copyright and related and neighboring rights to this software to the +# public domain worldwide. This software is distributed without any +# warranty. +# +# You should have received a copy of the CC0 Public Domain Dedication +# along with this software. If not, see +# . +# + +# +# error.bash +# ---------- +# +# Functions implementing error reporting. Used by public helper +# functions or test suits directly. +# + +# Fail and display a message. When no parameters are specified, the +# message is read from the standard input. Other functions use this to +# report failure. +# +# Globals: +# none +# Arguments: +# $@ - [=STDIN] message +# Returns: +# 1 - always +# Inputs: +# STDIN - [=$@] message +# Outputs: +# STDERR - message +fail() { + (( $# == 0 )) && batslib_err || batslib_err "$@" + return 1 +} diff --git a/lib/bats-support/src/lang.bash b/lib/bats-support/src/lang.bash new file mode 100644 index 0000000..c57e299 --- /dev/null +++ b/lib/bats-support/src/lang.bash @@ -0,0 +1,73 @@ +# +# bats-util - Various auxiliary functions for Bats +# +# Written in 2016 by Zoltan Tombol +# +# To the extent possible under law, the author(s) have dedicated all +# copyright and related and neighboring rights to this software to the +# public domain worldwide. This software is distributed without any +# warranty. +# +# You should have received a copy of the CC0 Public Domain Dedication +# along with this software. If not, see +# . +# + +# +# lang.bash +# --------- +# +# Bash language and execution related functions. Used by public helper +# functions. +# + +# Check whether the calling function was called from a given function. +# +# By default, direct invocation is checked. The function succeeds if the +# calling function was called directly from the given function. In other +# words, if the given function is the next element on the call stack. +# +# When `--indirect' is specified, indirect invocation is checked. The +# function succeeds if the calling function was called from the given +# function with any number of intermediate calls. In other words, if the +# given function can be found somewhere on the call stack. +# +# Direct invocation is a form of indirect invocation with zero +# intermediate calls. +# +# Globals: +# FUNCNAME +# Options: +# -i, --indirect - check indirect invocation +# Arguments: +# $1 - calling function's name +# Returns: +# 0 - current function was called from the given function +# 1 - otherwise +batslib_is_caller() { + local -i is_mode_direct=1 + + # Handle options. + while (( $# > 0 )); do + case "$1" in + -i|--indirect) is_mode_direct=0; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + # Arguments. + local -r func="$1" + + # Check call stack. + if (( is_mode_direct )); then + [[ $func == "${FUNCNAME[2]}" ]] && return 0 + else + local -i depth + for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do + [[ $func == "${FUNCNAME[$depth]}" ]] && return 0 + done + fi + + return 1 +} diff --git a/lib/bats-support/src/output.bash b/lib/bats-support/src/output.bash new file mode 100644 index 0000000..c6cf6a6 --- /dev/null +++ b/lib/bats-support/src/output.bash @@ -0,0 +1,279 @@ +# +# bats-support - Supporting library for Bats test helpers +# +# Written in 2016 by Zoltan Tombol +# +# To the extent possible under law, the author(s) have dedicated all +# copyright and related and neighboring rights to this software to the +# public domain worldwide. This software is distributed without any +# warranty. +# +# You should have received a copy of the CC0 Public Domain Dedication +# along with this software. If not, see +# . +# + +# +# output.bash +# ----------- +# +# Private functions implementing output formatting. Used by public +# helper functions. +# + +# Print a message to the standard error. When no parameters are +# specified, the message is read from the standard input. +# +# Globals: +# none +# Arguments: +# $@ - [=STDIN] message +# Returns: +# none +# Inputs: +# STDIN - [=$@] message +# Outputs: +# STDERR - message +batslib_err() { + { if (( $# > 0 )); then + echo "$@" + else + cat - + fi + } >&2 +} + +# Count the number of lines in the given string. +# +# TODO(ztombol): Fix tests and remove this note after #93 is resolved! +# NOTE: Due to a bug in Bats, `batslib_count_lines "$output"' does not +# give the same result as `${#lines[@]}' when the output contains +# empty lines. +# See PR #93 (https://github.com/sstephenson/bats/pull/93). +# +# Globals: +# none +# Arguments: +# $1 - string +# Returns: +# none +# Outputs: +# STDOUT - number of lines +batslib_count_lines() { + local -i n_lines=0 + local line + while IFS='' read -r line || [[ -n $line ]]; do + (( ++n_lines )) + done < <(printf '%s' "$1") + echo "$n_lines" +} + +# Determine whether all strings are single-line. +# +# Globals: +# none +# Arguments: +# $@ - strings +# Returns: +# 0 - all strings are single-line +# 1 - otherwise +batslib_is_single_line() { + for string in "$@"; do + (( $(batslib_count_lines "$string") > 1 )) && return 1 + done + return 0 +} + +# Determine the length of the longest key that has a single-line value. +# +# This function is useful in determining the correct width of the key +# column in two-column format when some keys may have multi-line values +# and thus should be excluded. +# +# Globals: +# none +# Arguments: +# $odd - key +# $even - value of the previous key +# Returns: +# none +# Outputs: +# STDOUT - length of longest key +batslib_get_max_single_line_key_width() { + local -i max_len=-1 + while (( $# != 0 )); do + local -i key_len="${#1}" + batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len" + shift 2 + done + echo "$max_len" +} + +# Print key-value pairs in two-column format. +# +# Keys are displayed in the first column, and their corresponding values +# in the second. To evenly line up values, the key column is fixed-width +# and its width is specified with the first parameter (possibly computed +# using `batslib_get_max_single_line_key_width'). +# +# Globals: +# none +# Arguments: +# $1 - width of key column +# $even - key +# $odd - value of the previous key +# Returns: +# none +# Outputs: +# STDOUT - formatted key-value pairs +batslib_print_kv_single() { + local -ir col_width="$1"; shift + while (( $# != 0 )); do + printf '%-*s : %s\n' "$col_width" "$1" "$2" + shift 2 + done +} + +# Print key-value pairs in multi-line format. +# +# The key is displayed first with the number of lines of its +# corresponding value in parenthesis. Next, starting on the next line, +# the value is displayed. For better readability, it is recommended to +# indent values using `batslib_prefix'. +# +# Globals: +# none +# Arguments: +# $odd - key +# $even - value of the previous key +# Returns: +# none +# Outputs: +# STDOUT - formatted key-value pairs +batslib_print_kv_multi() { + while (( $# != 0 )); do + printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )" + printf '%s\n' "$2" + shift 2 + done +} + +# Print all key-value pairs in either two-column or multi-line format +# depending on whether all values are single-line. +# +# If all values are single-line, print all pairs in two-column format +# with the specified key column width (identical to using +# `batslib_print_kv_single'). +# +# Otherwise, print all pairs in multi-line format after indenting values +# with two spaces for readability (identical to using `batslib_prefix' +# and `batslib_print_kv_multi') +# +# Globals: +# none +# Arguments: +# $1 - width of key column (for two-column format) +# $even - key +# $odd - value of the previous key +# Returns: +# none +# Outputs: +# STDOUT - formatted key-value pairs +batslib_print_kv_single_or_multi() { + local -ir width="$1"; shift + local -a pairs=( "$@" ) + + local -a values=() + local -i i + for (( i=1; i < ${#pairs[@]}; i+=2 )); do + values+=( "${pairs[$i]}" ) + done + + if batslib_is_single_line "${values[@]}"; then + batslib_print_kv_single "$width" "${pairs[@]}" + else + local -i i + for (( i=1; i < ${#pairs[@]}; i+=2 )); do + pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )" + done + batslib_print_kv_multi "${pairs[@]}" + fi +} + +# Prefix each line read from the standard input with the given string. +# +# Globals: +# none +# Arguments: +# $1 - [= ] prefix string +# Returns: +# none +# Inputs: +# STDIN - lines +# Outputs: +# STDOUT - prefixed lines +batslib_prefix() { + local -r prefix="${1:- }" + local line + while IFS='' read -r line || [[ -n $line ]]; do + printf '%s%s\n' "$prefix" "$line" + done +} + +# Mark select lines of the text read from the standard input by +# overwriting their beginning with the given string. +# +# Usually the input is indented by a few spaces using `batslib_prefix' +# first. +# +# Globals: +# none +# Arguments: +# $1 - marking string +# $@ - indices (zero-based) of lines to mark +# Returns: +# none +# Inputs: +# STDIN - lines +# Outputs: +# STDOUT - lines after marking +batslib_mark() { + local -r symbol="$1"; shift + # Sort line numbers. + set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" ) + + local line + local -i idx=0 + while IFS='' read -r line || [[ -n $line ]]; do + if (( ${1:--1} == idx )); then + printf '%s\n' "${symbol}${line:${#symbol}}" + shift + else + printf '%s\n' "$line" + fi + (( ++idx )) + done +} + +# Enclose the input text in header and footer lines. +# +# The header contains the given string as title. The output is preceded +# and followed by an additional newline to make it stand out more. +# +# Globals: +# none +# Arguments: +# $1 - title +# Returns: +# none +# Inputs: +# STDIN - text +# Outputs: +# STDOUT - decorated text +batslib_decorate() { + echo + echo "-- $1 --" + cat - + echo '--' + echo +} diff --git a/lib/bats-support/test/50-output-10-batslib_err.bats b/lib/bats-support/test/50-output-10-batslib_err.bats new file mode 100755 index 0000000..8c27fd1 --- /dev/null +++ b/lib/bats-support/test/50-output-10-batslib_err.bats @@ -0,0 +1,16 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_err() : displays ' { + run batslib_err 'm1' 'm2' + [ "$status" -eq 0 ] + [ "$output" == 'm1 m2' ] +} + +@test 'batslib_err(): reads from STDIN' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + echo 'm1' 'm2' | batslib_err" + [ "$status" -eq 0 ] + [ "$output" == 'm1 m2' ] +} diff --git a/lib/bats-support/test/50-output-11-batslib_count_lines.bats b/lib/bats-support/test/50-output-11-batslib_count_lines.bats new file mode 100755 index 0000000..ea172c3 --- /dev/null +++ b/lib/bats-support/test/50-output-11-batslib_count_lines.bats @@ -0,0 +1,21 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_count_lines() : displays the number of lines in ' { + run batslib_count_lines $'a\nb\nc\n' + [ "$status" -eq 0 ] + [ "$output" == '3' ] +} + +@test 'batslib_count_lines() : counts the last line when it is not terminated by a newline' { + run batslib_count_lines $'a\nb\nc' + [ "$status" -eq 0 ] + [ "$output" == '3' ] +} + +@test 'batslib_count_lines() : counts empty lines' { + run batslib_count_lines $'\n\n\n' + [ "$status" -eq 0 ] + [ "$output" == '3' ] +} diff --git a/lib/bats-support/test/50-output-12-batslib_is_single_line.bats b/lib/bats-support/test/50-output-12-batslib_is_single_line.bats new file mode 100755 index 0000000..484b64d --- /dev/null +++ b/lib/bats-support/test/50-output-12-batslib_is_single_line.bats @@ -0,0 +1,13 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_is_single_line() : returns 0 if all are single-line' { + run batslib_is_single_line 'a' $'b\n' 'c' + [ "$status" -eq 0 ] +} + +@test 'batslib_is_single_line() : returns 1 if at least one of is longer than one line' { + run batslib_is_single_line 'a' $'b\nb' 'c' + [ "$status" -eq 1 ] +} diff --git a/lib/bats-support/test/50-output-13-batslib_get_max_single_line_key_width.bats b/lib/bats-support/test/50-output-13-batslib_get_max_single_line_key_width.bats new file mode 100755 index 0000000..e6af161 --- /dev/null +++ b/lib/bats-support/test/50-output-13-batslib_get_max_single_line_key_width.bats @@ -0,0 +1,21 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_get_max_single_line_key_width() : displays the length of the longest key' { + local -ar pairs=( 'k _1' 'v 1' + 'k 2' 'v 2' + 'k __3' 'v 3' ) + run batslib_get_max_single_line_key_width "${pairs[@]}" + [ "$status" -eq 0 ] + [ "$output" == '5' ] +} + +@test 'batslib_get_max_single_line_key_width() : only considers keys with single-line values' { + local -ar pairs=( 'k _1' 'v 1' + 'k 2' 'v 2' + 'k __3' $'v\n3' ) + run batslib_get_max_single_line_key_width "${pairs[@]}" + [ "$status" -eq 0 ] + [ "$output" == '4' ] +} diff --git a/lib/bats-support/test/50-output-14-batslib_print_kv_single.bats b/lib/bats-support/test/50-output-14-batslib_print_kv_single.bats new file mode 100755 index 0000000..7637897 --- /dev/null +++ b/lib/bats-support/test/50-output-14-batslib_print_kv_single.bats @@ -0,0 +1,27 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_print_kv_single() : displays in two-column format with wide key column' { + local -ar pairs=( 'k _1' 'v 1' + 'k 2 ' 'v 2' + 'k __3' 'v 3' ) + run batslib_print_kv_single 5 "${pairs[@]}" + [ "$status" -eq 0 ] + [ "${#lines[@]}" == '3' ] + [ "${lines[0]}" == 'k _1 : v 1' ] + [ "${lines[1]}" == 'k 2 : v 2' ] + [ "${lines[2]}" == 'k __3 : v 3' ] +} + +@test 'batslib_print_kv_single() : does not truncate keys when the column is too narrow' { + local -ar pairs=( 'k _1' 'v 1' + 'k 2' 'v 2' + 'k __3' 'v 3' ) + run batslib_print_kv_single 0 "${pairs[@]}" + [ "$status" -eq 0 ] + [ "${#lines[@]}" == '3' ] + [ "${lines[0]}" == 'k _1 : v 1' ] + [ "${lines[1]}" == 'k 2 : v 2' ] + [ "${lines[2]}" == 'k __3 : v 3' ] +} diff --git a/lib/bats-support/test/50-output-15-batslib_print_kv_multi.bats b/lib/bats-support/test/50-output-15-batslib_print_kv_multi.bats new file mode 100755 index 0000000..6ad4b3d --- /dev/null +++ b/lib/bats-support/test/50-output-15-batslib_print_kv_multi.bats @@ -0,0 +1,19 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_print_kv_multi() : displays in multi-line format' { + local -ar pairs=( 'k _1' 'v 1' + 'k 2' $'v 2-1\nv 2-2' + 'k __3' 'v 3' ) + run batslib_print_kv_multi "${pairs[@]}" + [ "$status" -eq 0 ] + [ "${#lines[@]}" == '7' ] + [ "${lines[0]}" == 'k _1 (1 lines):' ] + [ "${lines[1]}" == 'v 1' ] + [ "${lines[2]}" == 'k 2 (2 lines):' ] + [ "${lines[3]}" == 'v 2-1' ] + [ "${lines[4]}" == 'v 2-2' ] + [ "${lines[5]}" == 'k __3 (1 lines):' ] + [ "${lines[6]}" == 'v 3' ] +} diff --git a/lib/bats-support/test/50-output-16-batslib_print_kv_single_or_multi.bats b/lib/bats-support/test/50-output-16-batslib_print_kv_single_or_multi.bats new file mode 100755 index 0000000..c20d101 --- /dev/null +++ b/lib/bats-support/test/50-output-16-batslib_print_kv_single_or_multi.bats @@ -0,0 +1,31 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_print_kv_single_or_multi() : displays in two-column format if all values are one line long' { + local -ar pairs=( 'k _1' 'v 1' + 'k 2 ' 'v 2' + 'k __3' 'v 3' ) + run batslib_print_kv_single_or_multi 5 "${pairs[@]}" + [ "$status" -eq 0 ] + [ "${#lines[@]}" == '3' ] + [ "${lines[0]}" == 'k _1 : v 1' ] + [ "${lines[1]}" == 'k 2 : v 2' ] + [ "${lines[2]}" == 'k __3 : v 3' ] +} + +@test 'batslib_print_kv_single_or_multi() : displays in multi-line format if at least one value is longer than one line' { + local -ar pairs=( 'k _1' 'v 1' + 'k 2' $'v 2-1\nv 2-2' + 'k __3' 'v 3' ) + run batslib_print_kv_single_or_multi 5 "${pairs[@]}" + [ "$status" -eq 0 ] + [ "${#lines[@]}" == '7' ] + [ "${lines[0]}" == 'k _1 (1 lines):' ] + [ "${lines[1]}" == ' v 1' ] + [ "${lines[2]}" == 'k 2 (2 lines):' ] + [ "${lines[3]}" == ' v 2-1' ] + [ "${lines[4]}" == ' v 2-2' ] + [ "${lines[5]}" == 'k __3 (1 lines):' ] + [ "${lines[6]}" == ' v 3' ] +} diff --git a/lib/bats-support/test/50-output-17-batslib_prefix.bats b/lib/bats-support/test/50-output-17-batslib_prefix.bats new file mode 100755 index 0000000..817fd33 --- /dev/null +++ b/lib/bats-support/test/50-output-17-batslib_prefix.bats @@ -0,0 +1,43 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_prefix() : prefixes each line of the input with ' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf 'a\nb\nc\n' | batslib_prefix '_'" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '_a' ] + [ "${lines[1]}" == '_b' ] + [ "${lines[2]}" == '_c' ] +} + +@test 'batslib_prefix() : prefixes the last line when it is not terminated by a newline' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf 'a\nb\nc' | batslib_prefix '_'" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '_a' ] + [ "${lines[1]}" == '_b' ] + [ "${lines[2]}" == '_c' ] +} + +@test 'batslib_prefix() : prefixes empty lines' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf '\n\n\n' | batslib_prefix '_'" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '_' ] + [ "${lines[1]}" == '_' ] + [ "${lines[2]}" == '_' ] +} + +@test 'batslib_prefix(): default to two spaces' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf 'a\nb\nc\n' | batslib_prefix" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == ' a' ] + [ "${lines[1]}" == ' b' ] + [ "${lines[2]}" == ' c' ] +} diff --git a/lib/bats-support/test/50-output-18-batslib_mark.bats b/lib/bats-support/test/50-output-18-batslib_mark.bats new file mode 100755 index 0000000..c5d0975 --- /dev/null +++ b/lib/bats-support/test/50-output-18-batslib_mark.bats @@ -0,0 +1,72 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_mark() : marks the -th line of the input with ' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf ' a\n b\n c\n' | batslib_mark '>' 0" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '>a' ] + [ "${lines[1]}" == ' b' ] + [ "${lines[2]}" == ' c' ] +} + +@test 'batslib_mark() : marks multiple lines when is in ascending order' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf ' a\n b\n c\n' | batslib_mark '>' 1 2" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == ' a' ] + [ "${lines[1]}" == '>b' ] + [ "${lines[2]}" == '>c' ] +} + +@test 'batslib_mark() : marks multiple lines when is in random order' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf ' a\n b\n c\n d\n' | batslib_mark '>' 2 1 3" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 4 ] + [ "${lines[0]}" == ' a' ] + [ "${lines[1]}" == '>b' ] + [ "${lines[2]}" == '>c' ] + [ "${lines[3]}" == '>d' ] +} + +@test 'batslib_mark() : ignores duplicate indices' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf ' a\n b\n c\n' | batslib_mark '>' 1 2 1" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == ' a' ] + [ "${lines[1]}" == '>b' ] + [ "${lines[2]}" == '>c' ] +} + +@test 'batslib_mark() : outputs the input untouched if is the empty string' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf ' a\n b\n c\n' | batslib_mark '' 1" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == ' a' ] + [ "${lines[1]}" == ' b' ] + [ "${lines[2]}" == ' c' ] +} + +@test 'batslib_mark() : marks the last line when it is not terminated by a newline' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf ' a\n b\n c' | batslib_mark '>' 2" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == ' a' ] + [ "${lines[1]}" == ' b' ] + [ "${lines[2]}" == '>c' ] +} + +@test 'batslib_mark() : does not truncate if it is longer than the marked line' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + printf '\n' | batslib_mark '>' 0" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 1 ] + [ "${lines[0]}" == '>' ] +} diff --git a/lib/bats-support/test/50-output-19-batslib_decorate.bats b/lib/bats-support/test/50-output-19-batslib_decorate.bats new file mode 100755 index 0000000..02d55ad --- /dev/null +++ b/lib/bats-support/test/50-output-19-batslib_decorate.bats @@ -0,0 +1,13 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'batslib_decorate() : encloses the input in a footer line and a header line containing <title>' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + echo 'body' | batslib_decorate 'title'" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '-- title --' ] + [ "${lines[1]}" == 'body' ] + [ "${lines[2]}" == '--' ] +} diff --git a/lib/bats-support/test/51-error-10-fail.bats b/lib/bats-support/test/51-error-10-fail.bats new file mode 100755 index 0000000..1d8691a --- /dev/null +++ b/lib/bats-support/test/51-error-10-fail.bats @@ -0,0 +1,16 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'fail() <message>: returns 1 and displays <message>' { + run fail 'message' + [ "$status" -eq 1 ] + [ "$output" == 'message' ] +} + +@test 'fail(): reads <message> from STDIN' { + run bash -c "source '${TEST_MAIN_DIR}/load.bash' + echo 'message' | fail" + [ "$status" -eq 1 ] + [ "$output" == 'message' ] +} diff --git a/lib/bats-support/test/52-lang-10-batslib_is_caller.bats b/lib/bats-support/test/52-lang-10-batslib_is_caller.bats new file mode 100755 index 0000000..68fd59b --- /dev/null +++ b/lib/bats-support/test/52-lang-10-batslib_is_caller.bats @@ -0,0 +1,88 @@ +#!/usr/bin/env bats + +load 'test_helper' + + +# Test functions +test_func_lvl_2() { + test_func_lvl_1 "$@" +} + +test_func_lvl_1() { + test_func_lvl_0 "$@" +} + +test_func_lvl_0() { + batslib_is_caller "$@" +} + + +# +# Direct invocation +# + +# Interface +@test 'batslib_is_caller() <function>: returns 0 if the current function was called directly from <function>' { + run test_func_lvl_1 test_func_lvl_1 + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'batslib_is_caller() <function>: returns 1 if the current function was not called directly from <function>' { + run test_func_lvl_0 test_func_lvl_1 + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 0 ] +} + +# Correctness +@test 'batslib_is_caller() <function>: the current function does not appear on the call stack' { + run test_func_lvl_0 test_func_lvl_0 + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 0 ] +} + + +# +# Indirect invocation +# + +# Options +test_i_indirect() { + run test_func_lvl_2 "$@" + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'batslib_is_caller() -i <function>: enables indirect checking' { + test_i_indirect -i test_func_lvl_2 +} + +@test 'batslib_is_caller() --indirect <function>: enables indirect checking' { + test_i_indirect --indirect test_func_lvl_2 +} + +# Interface +@test 'batslib_is_caller() --indirect <function>: returns 0 if the current function was called indirectly from <function>' { + run test_func_lvl_2 --indirect test_func_lvl_2 + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'batslib_is_caller() --indirect <function>: returns 1 if the current function was not called indirectly from <function>' { + run test_func_lvl_1 --indirect test_func_lvl_2 + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 0 ] +} + +# Correctness +@test 'batslib_is_caller() --indirect <function>: direct invocation is a special case of indirect invocation with zero intermediate calls' { + run test_func_lvl_1 --indirect test_func_lvl_1 + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 0 ] +} + +@test 'batslib_is_caller() --indirect <function>: the current function does not appear on the call stack' { + run test_func_lvl_0 --indirect test_func_lvl_0 + [ "$status" -eq 1 ] + [ "${#lines[@]}" -eq 0 ] +} diff --git a/lib/bats-support/test/test_helper.bash b/lib/bats-support/test/test_helper.bash new file mode 100644 index 0000000..ca16775 --- /dev/null +++ b/lib/bats-support/test/test_helper.bash @@ -0,0 +1,6 @@ +setup() { + export TEST_MAIN_DIR="${BATS_TEST_DIRNAME}/.." + + # Load library. + load '../load' +}