From 3b86548352f76103f7453646b15ccdfc274d7a35 Mon Sep 17 00:00:00 2001 From: Santiago Merlos Date: Mon, 20 May 2024 20:40:52 +0200 Subject: [PATCH 1/4] fix(cert-manager): fix acme reference --- katalog/cert-manager/cert-manager-controller/kustomization.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/katalog/cert-manager/cert-manager-controller/kustomization.yaml b/katalog/cert-manager/cert-manager-controller/kustomization.yaml index ead2908..95723ca 100644 --- a/katalog/cert-manager/cert-manager-controller/kustomization.yaml +++ b/katalog/cert-manager/cert-manager-controller/kustomization.yaml @@ -29,4 +29,4 @@ patchesJson6902: patch: |- - op: replace path: /spec/template/spec/containers/0/args/6 - value: --acme-http01-solver-image=registry.sighup.io/fury/cert-manager-acmesolver:v1.14.2 + value: --acme-http01-solver-image=registry.sighup.io/fury/jetstack/cert-manager-acmesolver:v1.14.2 From 860ae44041c91aa5b5159ffe22728553f575ad87 Mon Sep 17 00:00:00 2001 From: Santiago Merlos Date: Tue, 21 May 2024 10:44:20 +0200 Subject: [PATCH 2/4] docs(cert-manager): add release notes and docs for version 2.3.1 --- README.md | 18 +++++++++--------- docs/COMPATIBILITY_MATRIX.md | 1 + docs/releases/v2.3.1.md | 30 ++++++++++++++++++++++++++++++ katalog/cert-manager/README.md | 4 ++-- 4 files changed, 42 insertions(+), 11 deletions(-) create mode 100644 docs/releases/v2.3.1.md diff --git a/README.md b/README.md index 02f1b4f..1fd2899 100644 --- a/README.md +++ b/README.md @@ -118,9 +118,9 @@ To deploy the `cert-manager` package: ```yaml bases: - name: ingress/dual-nginx - version: "v2.3.0" + version: "v2.3.1" - name: ingress/cert-manager - version: "v2.3.0" + version: "v2.3.1" ``` 2. Execute `furyctl vendor -H` to download the packages @@ -179,7 +179,7 @@ Single Ingress: ```yaml bases: - name: ingress/nginx - version: "v2.3.0" + version: "v2.3.1" ``` Dual Ingress: @@ -189,9 +189,9 @@ Dual Ingress: ```yaml bases: - name: ingress/nginx - version: "v2.3.0" + version: "v2.3.1" - name: ingress/dual-nginx - version: "v2.3.0" + version: "v2.3.1" ``` > See `furyctl` [documentation][furyctl-repo] for additional details about `Furyfile.yml` format. @@ -263,11 +263,11 @@ To deploy the `forecastle` package: ```yaml bases: - name: ingress/dual-nginx - version: "v2.3.0" + version: "v2.3.1" - name: ingress/cert-manager - version: "v2.3.0" + version: "v2.3.1" - name: ingress/forecastle - version: "v2.3.0" + version: "v2.3.1" ``` 2. Execute `furyctl legacy vendor -H` to download the packages @@ -316,7 +316,7 @@ Add the following annotations to your ingresses to be discovered by Forecastle: [kubernetes-ingress]: https://kubernetes.io/docs/concepts/services-networking/ingress/ [forecastle-repo]: https://github.com/stakater/Forecastle [forecastle-icons]: https://github.com/stakater/ForecastleIcons -[forecastle-repository]: https://github.com/stakater/Forecastle/blob/v1.0.61/README.md +[forecastle-repository]: https://github.com/stakater/Forecastle/blob/v1.0.136/README.md [ingress-nginx-docs]: https://github.com/kubernetes/ingress-nginx [prometheus-alerts-page]: https://prometheus.io/docs/prometheus/latest/configuration/alerting_rules/ diff --git a/docs/COMPATIBILITY_MATRIX.md b/docs/COMPATIBILITY_MATRIX.md index e53f3ce..c5355a7 100644 --- a/docs/COMPATIBILITY_MATRIX.md +++ b/docs/COMPATIBILITY_MATRIX.md @@ -11,6 +11,7 @@ | v2.1.0 | | | | | :white_check_mark: | :white_check_mark: | :white_check_mark: | | | | | v2.2.0 | | | | | | :white_check_mark: | :white_check_mark: |:white_check_mark: | | | | v2.3.0 | | | | | | | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| v2.3.1 | | | | | | | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: Compatible diff --git a/docs/releases/v2.3.1.md b/docs/releases/v2.3.1.md new file mode 100644 index 0000000..652e4ef --- /dev/null +++ b/docs/releases/v2.3.1.md @@ -0,0 +1,30 @@ +# Kubernetes Fury Ingress Core Module Release 2.3.0 + +Welcome to the latest release of `Ingress` module of [`Kubernetes Fury Distribution`](https://github.com/sighupio/fury-distribution) maintained by team SIGHUP. + +This relase adds support for Kubernetes 1.29 and 1.28 and update the following components: + +## Component Images 🚢 + +| Component | Supported Version | Previous Version | +| ------------------ | -------------------------------------------------------------------------------------- | ---------------- | +| `cert-manager` | [`v1.14.2`](https://github.com/jetstack/cert-manager/releases/tag/v1.14.2) | `v1.14.2` | +| `external-dns` | [`v0.14.0`](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.14.0) | `v0.14.0` | +| `forecastle` | [`v1.0.136`](https://github.com/stakater/Forecastle/releases/tag/v1.0.136) | `v1.0.136` | +| `nginx` | [`v1.9.6`](https://github.com/kubernetes/ingress-nginx/releases/tag/controller-v1.9.6) | `v1.9.6` | +| `aws-cert-manager` | N.A. | `No update` | +| `aws-external-dns` | N.A. | `No update` | + +> Please refer the individual release notes to get a more detailed information on each release. + +## Update Guide 🦮 + +### Process + +To upgrade this core module from `v2.3.0` to `v2.3.1`, you need to download this new version, and apply the instructions below. + +```bash +kustomize build | kubectl apply -f - +``` + +Also for the terraform modules, run `terraform init -upgrade` , then apply the new version. diff --git a/katalog/cert-manager/README.md b/katalog/cert-manager/README.md index 9458b5a..344fc89 100644 --- a/katalog/cert-manager/README.md +++ b/katalog/cert-manager/README.md @@ -33,9 +33,9 @@ To deploy the `cert-manager` package: ```yaml resources: - name: ingress/dual-nginx - version: "v2.2.0" + version: "v2.3.1" - name: ingress/cert-manager - version: "v2.2.0" + version: "v2.3.1" ``` 2. Execute `furyctl legacy vendor -H` to download the packages From f7a04605b59cdbb23f7eaf4c4cbb3c593c41c0ca Mon Sep 17 00:00:00 2001 From: Santiago Merlos Date: Tue, 21 May 2024 18:14:51 +0200 Subject: [PATCH 3/4] test(cert-manager): add e2e for cert manager --- docs/releases/v2.3.1.md | 4 +- katalog/tests/helper/bats-assert/.gitignore | 3 + katalog/tests/helper/bats-assert/LICENSE | 116 +++ katalog/tests/helper/bats-assert/README.md | 788 ++++++++++++++++++ katalog/tests/helper/bats-assert/load.bash | 33 + .../helper/bats-assert/package-lock.json | 36 + katalog/tests/helper/bats-assert/package.json | 48 ++ .../tests/helper/bats-assert/src/assert.bash | 42 + .../helper/bats-assert/src/assert_equal.bash | 42 + .../bats-assert/src/assert_failure.bash | 78 ++ .../helper/bats-assert/src/assert_line.bash | 248 ++++++ .../bats-assert/src/assert_not_equal.bash | 42 + .../helper/bats-assert/src/assert_output.bash | 197 +++++ .../helper/bats-assert/src/assert_regex.bash | 56 ++ .../bats-assert/src/assert_success.bash | 44 + .../tests/helper/bats-assert/src/refute.bash | 42 + .../helper/bats-assert/src/refute_line.bash | 271 ++++++ .../helper/bats-assert/src/refute_output.bash | 199 +++++ .../helper/bats-assert/src/refute_regex.bash | 66 ++ .../tests/helper/bats-assert/test/assert.bats | 19 + .../helper/bats-assert/test/assert_equal.bats | 62 ++ .../bats-assert/test/assert_failure.bats | 75 ++ .../helper/bats-assert/test/assert_line.bats | 351 ++++++++ .../bats-assert/test/assert_not_equal.bats | 57 ++ .../bats-assert/test/assert_output.bats | 285 +++++++ .../helper/bats-assert/test/assert_regex.bats | 87 ++ .../bats-assert/test/assert_success.bats | 40 + .../tests/helper/bats-assert/test/refute.bats | 18 + .../helper/bats-assert/test/refute_line.bats | 344 ++++++++ .../bats-assert/test/refute_output.bats | 230 +++++ .../helper/bats-assert/test/refute_regex.bats | 98 +++ .../helper/bats-assert/test/test_helper.bash | 28 + katalog/tests/helper/bats-support/.gitignore | 4 + .../tests/helper/bats-support/CHANGELOG.md | 46 + katalog/tests/helper/bats-support/LICENSE | 116 +++ katalog/tests/helper/bats-support/README.md | 181 ++++ katalog/tests/helper/bats-support/load.bash | 11 + .../tests/helper/bats-support/package.json | 30 + .../tests/helper/bats-support/src/error.bash | 41 + .../tests/helper/bats-support/src/lang.bash | 73 ++ .../tests/helper/bats-support/src/output.bash | 279 +++++++ .../test/50-output-10-batslib_err.bats | 43 + .../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 | 46 + .../bats-support/test/51-error-10-fail.bats | 16 + .../test/52-lang-10-batslib_is_caller.bats | 88 ++ katalog/tests/helper/bats-support/test/cat | 1 + .../helper/bats-support/test/test_helper.bash | 6 + katalog/tests/tests.bats | 237 +++++- 56 files changed, 5500 insertions(+), 14 deletions(-) create mode 100644 katalog/tests/helper/bats-assert/.gitignore create mode 100644 katalog/tests/helper/bats-assert/LICENSE create mode 100644 katalog/tests/helper/bats-assert/README.md create mode 100644 katalog/tests/helper/bats-assert/load.bash create mode 100644 katalog/tests/helper/bats-assert/package-lock.json create mode 100644 katalog/tests/helper/bats-assert/package.json create mode 100644 katalog/tests/helper/bats-assert/src/assert.bash create mode 100644 katalog/tests/helper/bats-assert/src/assert_equal.bash create mode 100644 katalog/tests/helper/bats-assert/src/assert_failure.bash create mode 100644 katalog/tests/helper/bats-assert/src/assert_line.bash create mode 100644 katalog/tests/helper/bats-assert/src/assert_not_equal.bash create mode 100644 katalog/tests/helper/bats-assert/src/assert_output.bash create mode 100644 katalog/tests/helper/bats-assert/src/assert_regex.bash create mode 100644 katalog/tests/helper/bats-assert/src/assert_success.bash create mode 100644 katalog/tests/helper/bats-assert/src/refute.bash create mode 100644 katalog/tests/helper/bats-assert/src/refute_line.bash create mode 100644 katalog/tests/helper/bats-assert/src/refute_output.bash create mode 100644 katalog/tests/helper/bats-assert/src/refute_regex.bash create mode 100755 katalog/tests/helper/bats-assert/test/assert.bats create mode 100755 katalog/tests/helper/bats-assert/test/assert_equal.bats create mode 100755 katalog/tests/helper/bats-assert/test/assert_failure.bats create mode 100755 katalog/tests/helper/bats-assert/test/assert_line.bats create mode 100644 katalog/tests/helper/bats-assert/test/assert_not_equal.bats create mode 100755 katalog/tests/helper/bats-assert/test/assert_output.bats create mode 100644 katalog/tests/helper/bats-assert/test/assert_regex.bats create mode 100755 katalog/tests/helper/bats-assert/test/assert_success.bats create mode 100755 katalog/tests/helper/bats-assert/test/refute.bats create mode 100755 katalog/tests/helper/bats-assert/test/refute_line.bats create mode 100755 katalog/tests/helper/bats-assert/test/refute_output.bats create mode 100644 katalog/tests/helper/bats-assert/test/refute_regex.bats create mode 100644 katalog/tests/helper/bats-assert/test/test_helper.bash create mode 100644 katalog/tests/helper/bats-support/.gitignore create mode 100644 katalog/tests/helper/bats-support/CHANGELOG.md create mode 100644 katalog/tests/helper/bats-support/LICENSE create mode 100644 katalog/tests/helper/bats-support/README.md create mode 100644 katalog/tests/helper/bats-support/load.bash create mode 100644 katalog/tests/helper/bats-support/package.json create mode 100644 katalog/tests/helper/bats-support/src/error.bash create mode 100644 katalog/tests/helper/bats-support/src/lang.bash create mode 100644 katalog/tests/helper/bats-support/src/output.bash create mode 100755 katalog/tests/helper/bats-support/test/50-output-10-batslib_err.bats create mode 100755 katalog/tests/helper/bats-support/test/50-output-11-batslib_count_lines.bats create mode 100755 katalog/tests/helper/bats-support/test/50-output-12-batslib_is_single_line.bats create mode 100755 katalog/tests/helper/bats-support/test/50-output-13-batslib_get_max_single_line_key_width.bats create mode 100755 katalog/tests/helper/bats-support/test/50-output-14-batslib_print_kv_single.bats create mode 100755 katalog/tests/helper/bats-support/test/50-output-15-batslib_print_kv_multi.bats create mode 100755 katalog/tests/helper/bats-support/test/50-output-16-batslib_print_kv_single_or_multi.bats create mode 100755 katalog/tests/helper/bats-support/test/50-output-17-batslib_prefix.bats create mode 100755 katalog/tests/helper/bats-support/test/50-output-18-batslib_mark.bats create mode 100755 katalog/tests/helper/bats-support/test/50-output-19-batslib_decorate.bats create mode 100755 katalog/tests/helper/bats-support/test/51-error-10-fail.bats create mode 100755 katalog/tests/helper/bats-support/test/52-lang-10-batslib_is_caller.bats create mode 100755 katalog/tests/helper/bats-support/test/cat create mode 100644 katalog/tests/helper/bats-support/test/test_helper.bash diff --git a/docs/releases/v2.3.1.md b/docs/releases/v2.3.1.md index 652e4ef..98c46be 100644 --- a/docs/releases/v2.3.1.md +++ b/docs/releases/v2.3.1.md @@ -1,8 +1,8 @@ -# Kubernetes Fury Ingress Core Module Release 2.3.0 +# Kubernetes Fury Ingress Core Module Release 2.3.1 Welcome to the latest release of `Ingress` module of [`Kubernetes Fury Distribution`](https://github.com/sighupio/fury-distribution) maintained by team SIGHUP. -This relase adds support for Kubernetes 1.29 and 1.28 and update the following components: +This release fixes a bug in cert-manager's acme container image URL and the following components: ## Component Images 🚢 diff --git a/katalog/tests/helper/bats-assert/.gitignore b/katalog/tests/helper/bats-assert/.gitignore new file mode 100644 index 0000000..41a3ee1 --- /dev/null +++ b/katalog/tests/helper/bats-assert/.gitignore @@ -0,0 +1,3 @@ +/node_modules +/yarn.lock +/bats-assert-*.tgz diff --git a/katalog/tests/helper/bats-assert/LICENSE b/katalog/tests/helper/bats-assert/LICENSE new file mode 100644 index 0000000..670154e --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/README.md b/katalog/tests/helper/bats-assert/README.md new file mode 100644 index 0000000..6f1f80e --- /dev/null +++ b/katalog/tests/helper/bats-assert/README.md @@ -0,0 +1,788 @@ +# 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) +[![Tests](https://github.com/bats-core/bats-assert/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/bats-core/bats-assert/actions/workflows/test.yml) + +`bats-assert` is a helper library providing common assertions for [Bats][bats]. + +- [Install](#install) +- [Usage](#usage) +- [Options](#options) +- [Full Assertion API](#full-assertion-api) + +In the context of this project, an [assertion][wikipedia-assertions] is a function that perform a test and returns `1` on failure or `0` on success. +To make debugging easier, the assertion also outputs relevant information on failure. +The output is [formatted][bats-support-output] for readability. +To make assertions usable outside of `@test` blocks, the output is sent to [stderr][wikipedia-stderr]. + +The most recent invocation of Bats' `run` function is used for testing assertions on output and status code. + +[wikipedia-assertions]: https://en.wikipedia.org/wiki/Assertion_(software_development) +[wikipedia-stderr]: https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr) + +## Install + +This project has one dependency, for output formatting: [`bats-support`][bats-support] + +Read the [shared documentation][bats-docs] to learn how to install and load both libraries. + +## Usage + +This project provides the following functions: + + - [assert](#assert) / [refute](#refute) Assert a given expression evaluates to `true` or `false`. + - [assert_equal](#assert_equal) Assert two parameters are equal. + - [assert_not_equal](#assert_not_equal) Assert two parameters are not equal. + - [assert_success](#assert_success) / [assert_failure](#assert_failure) Assert exit status is `0` or `1`. + - [assert_output](#assert_output) / [refute_output](#refute_output) Assert output does (or does not) contain given content. + - [assert_line](#assert_line) / [refute_line](#refute_line) Assert a specific line of output does (or does not) contain given content. + - [assert_regex](#assert_regex) / [refute_regex](#refute_regex) Assert a parameter does (or does not) match given pattern. + +These commands are described in more detail below. + +## 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 -- '--' +``` + + +## Full Assertion API + +### `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()' { + assert [ 1 -lt 0 ] +} +``` + +On failure, the failed expression is displayed. + +``` +-- assertion failed -- +expression : [ 1 -lt 0 ] +-- +``` + + +### `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()' { + refute [ 1 -gt 0 ] +} +``` + +On failure, the successful expression is displayed. + +``` +-- assertion succeeded, but it was expected to fail -- +expression : [ 1 -gt 0 ] +-- +``` + + +### `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_not_equal` + +Fail if the two parameters, actual and unexpected value respectively, are equal. + +```bash +@test 'assert_not_equal()' { + assert_not_equal 'foobar' 'foobar' +} +``` + +On failure, the expected and actual values are displayed. + +``` +-- values should not be equal -- +unexpected : foobar +actual : foobar +-- +``` + +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. + +### `assert_regex` + +This function is similar to `assert_equal` but uses pattern matching instead of +equality, by wrapping `[[ value =~ pattern ]]`. + +Fail if the value (first parameter) does not match the pattern (second +parameter). + +```bash +@test 'assert_regex()' { + assert_regex 'what' 'x$' +} +``` + +On failure, the value and the pattern are displayed. + +``` +-- values does not match regular expression -- +value : what +pattern : x$ +-- +``` + +If the value is longer than one line then it is displayed in *multi-line* +format. + +An error is displayed if the specified extended regular expression is invalid. + +For description of the matching behavior, refer to the documentation of the +`=~` operator in the [Bash manual][bash-conditional]. + +> _**Note**: +> the `BASH_REMATCH` array is available immediately after the assertion succeeds but is fragile; +> i.e. prone to being overwritten as a side effect of other actions._ + +### `refute_regex` + +This function is similar to `refute_equal` but uses pattern matching instead of +equality, by wrapping `! [[ value =~ pattern ]]`. + +Fail if the value (first parameter) matches the pattern (second parameter). + +```bash +@test 'refute_regex()' { + refute_regex 'WhatsApp' 'Threema' +} +``` + +On failure, the value, the pattern and the match are displayed. + +``` +@test 'refute_regex()' { + refute_regex 'WhatsApp' 'What.' +} + +-- value matches regular expression -- +value : WhatsApp +pattern : What. +match : Whats +case : sensitive +-- +``` + +If the value or pattern is longer than one line then it is displayed in +*multi-line* format. + +An error is displayed if the specified extended regular expression is invalid. + +For description of the matching behavior, refer to the documentation of the +`=~` operator in the [Bash manual][bash-conditional]. + +> _**Note**: +> the `BASH_REMATCH` array is available immediately after the assertion fails but is fragile; +> i.e. prone to being overwritten as a side effect of other actions like calling `run`. +> Thus, it's good practice to avoid using `BASH_REMATCH` in conjunction with `refute_regex()`. +> The valuable information the array contains is the matching part of the value which is printed in the failing test log, as mentioned above._ + + + +[bats]: https://github.com/bats-core/bats-core +[bash-comp-cmd]: https://www.gnu.org/software/bash/manual/bash.html#Compound-Commands +[bash-conditional]: https://www.gnu.org/software/bash/manual/bash.html#Conditional-Constructs + +[bats-docs]: https://bats-core.readthedocs.io/ +[bats-support-output]: https://github.com/bats-core/bats-support#output-formatting +[bats-support]: https://github.com/bats-core/bats-support diff --git a/katalog/tests/helper/bats-assert/load.bash b/katalog/tests/helper/bats-assert/load.bash new file mode 100644 index 0000000..c67d9e8 --- /dev/null +++ b/katalog/tests/helper/bats-assert/load.bash @@ -0,0 +1,33 @@ +# 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_not_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" +source "$(dirname "${BASH_SOURCE[0]}")/src/assert_regex.bash" +source "$(dirname "${BASH_SOURCE[0]}")/src/refute_regex.bash" diff --git a/katalog/tests/helper/bats-assert/package-lock.json b/katalog/tests/helper/bats-assert/package-lock.json new file mode 100644 index 0000000..8f0dcc6 --- /dev/null +++ b/katalog/tests/helper/bats-assert/package-lock.json @@ -0,0 +1,36 @@ +{ + "name": "bats-assert", + "version": "2.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "bats-assert", + "version": "2.1.0", + "license": "CC0-1.0", + "devDependencies": { + "bats": "^1", + "bats-support": "^0.3" + }, + "peerDependencies": { + "bats": "0.4 || ^1", + "bats-support": "^0.3" + } + }, + "node_modules/bats": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/bats/-/bats-1.9.0.tgz", + "integrity": "sha512-Z5BJaAmmHv/ujj7obhjEzJ//OL+ZtjVq0iRnHu+2fE9OeUaPMbJpBgYiOdNbDrG3E2hqe84/AXNnS/UiXl/UcA==", + "dev": true, + "bin": { + "bats": "bin/bats" + } + }, + "node_modules/bats-support": { + "version": "0.3.0", + "resolved": "git+ssh://git@github.com/jasonkarns/bats-support.git#24a72e14349690bcbf7c151b9d2d1cdd32d36eb1", + "integrity": "sha512-42f2THEaN02dVj7Zhj0C4AM5FdePq3YPlXvnu/fKUQwCv1qM2dyACBKFXQkLw9QMzhDmwuFcke90XwHMUsBjGQ==", + "dev": true + } + } +} diff --git a/katalog/tests/helper/bats-assert/package.json b/katalog/tests/helper/bats-assert/package.json new file mode 100644 index 0000000..7048995 --- /dev/null +++ b/katalog/tests/helper/bats-assert/package.json @@ -0,0 +1,48 @@ +{ + "name": "bats-assert", + "version": "2.1.0", + "description": "Common assertions for Bats", + "homepage": "https://github.com/bats-core/bats-assert", + "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:bats-core/bats-assert", + "bugs": "https://github.com/bats-core/bats-assert/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/katalog/tests/helper/bats-assert/src/assert.bash b/katalog/tests/helper/bats-assert/src/assert.bash new file mode 100644 index 0000000..0260ce2 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/src/assert_equal.bash b/katalog/tests/helper/bats-assert/src/assert_equal.bash new file mode 100644 index 0000000..4ef1297 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/src/assert_failure.bash b/katalog/tests/helper/bats-assert/src/assert_failure.bash new file mode 100644 index 0000000..d906059 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/src/assert_line.bash b/katalog/tests/helper/bats-assert/src/assert_line.bash new file mode 100644 index 0000000..ee05bb8 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/src/assert_not_equal.bash b/katalog/tests/helper/bats-assert/src/assert_not_equal.bash new file mode 100644 index 0000000..933bb71 --- /dev/null +++ b/katalog/tests/helper/bats-assert/src/assert_not_equal.bash @@ -0,0 +1,42 @@ +# assert_not_equal +# ============ +# +# Summary: Fail if the actual and unexpected values are equal. +# +# Usage: assert_not_equal +# +# Options: +# The value being compared. +# The value to compare against. +# +# ```bash +# @test 'assert_not_equal()' { +# assert_not_equal 'foo' 'foo' +# } +# ``` +# +# IO: +# STDERR - expected and actual values, on failure +# Globals: +# none +# Returns: +# 0 - if actual does not equal unexpected +# 1 - otherwise +# +# On failure, the unexpected and actual values are displayed. +# +# ``` +# -- values should not be equal -- +# unexpected : foo +# actual : foo +# -- +# ``` +assert_not_equal() { + if [[ "$1" == "$2" ]]; then + batslib_print_kv_single_or_multi 10 \ + 'unexpected' "$2" \ + 'actual' "$1" \ + | batslib_decorate 'values should not be equal' \ + | fail + fi +} diff --git a/katalog/tests/helper/bats-assert/src/assert_output.bash b/katalog/tests/helper/bats-assert/src/assert_output.bash new file mode 100644 index 0000000..82d0a0c --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/src/assert_regex.bash b/katalog/tests/helper/bats-assert/src/assert_regex.bash new file mode 100644 index 0000000..17a7057 --- /dev/null +++ b/katalog/tests/helper/bats-assert/src/assert_regex.bash @@ -0,0 +1,56 @@ +# `assert_regex` +# +# This function is similar to `assert_equal` but uses pattern matching instead +# of equality, by wrapping `[[ value =~ pattern ]]`. +# +# Fail if the value (first parameter) does not match the pattern (second +# parameter). +# +# ```bash +# @test 'assert_regex()' { +# assert_regex 'what' 'x$' +# } +# ``` +# +# On failure, the value and the pattern are displayed. +# +# ``` +# -- values does not match regular expression -- +# value : what +# pattern : x$ +# -- +# ``` +# +# If the value is longer than one line then it is displayed in *multi-line* +# format. +# +# An error is displayed if the specified extended regular expression is invalid. +# +# For description of the matching behavior, refer to the documentation of the +# `=~` operator in the +# [Bash manual]: https://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.html. +# Note that the `BASH_REMATCH` array is available immediately after the +# assertion succeeds but is fragile, i.e. prone to being overwritten as a side +# effect of other actions. +assert_regex() { + local -r value="${1}" + local -r pattern="${2}" + + if [[ '' =~ ${pattern} ]]; (( ${?} == 2 )); then + echo "Invalid extended regular expression: \`${pattern}'" \ + | batslib_decorate 'ERROR: assert_regex' \ + | fail + elif ! [[ "${value}" =~ ${pattern} ]]; then + if shopt -p nocasematch &>/dev/null; then + local case_sensitive=insensitive + else + local case_sensitive=sensitive + fi + batslib_print_kv_single_or_multi 8 \ + 'value' "${value}" \ + 'pattern' "${pattern}" \ + 'case' "${case_sensitive}" \ + | batslib_decorate 'value does not match regular expression' \ + | fail + fi +} diff --git a/katalog/tests/helper/bats-assert/src/assert_success.bash b/katalog/tests/helper/bats-assert/src/assert_success.bash new file mode 100644 index 0000000..a8b2a38 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/src/refute.bash b/katalog/tests/helper/bats-assert/src/refute.bash new file mode 100644 index 0000000..e7c47da --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/src/refute_line.bash b/katalog/tests/helper/bats-assert/src/refute_line.bash new file mode 100644 index 0000000..689bea1 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/src/refute_output.bash b/katalog/tests/helper/bats-assert/src/refute_output.bash new file mode 100644 index 0000000..92d7e8f --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/src/refute_regex.bash b/katalog/tests/helper/bats-assert/src/refute_regex.bash new file mode 100644 index 0000000..0918793 --- /dev/null +++ b/katalog/tests/helper/bats-assert/src/refute_regex.bash @@ -0,0 +1,66 @@ +# `refute_regex` +# +# This function is similar to `refute_equal` but uses pattern matching instead +# of equality, by wrapping `! [[ value =~ pattern ]]`. +# +# Fail if the value (first parameter) matches the pattern (second parameter). +# +# ```bash +# @test 'refute_regex()' { +# refute_regex 'WhatsApp' 'Threema' +# } +# ``` +# +# On failure, the value, the pattern and the match are displayed. +# +# ``` +# @test 'refute_regex()' { +# refute_regex 'WhatsApp' 'What.' +# } +# +# -- value matches regular expression -- +# value : WhatsApp +# pattern : What. +# match : Whats +# case : sensitive +# -- +# ``` +# +# If the value or pattern is longer than one line then it is displayed in +# *multi-line* format. +# +# An error is displayed if the specified extended regular expression is invalid. +# +# For description of the matching behavior, refer to the documentation of the +# `=~` operator in the +# [Bash manual]: https://www.gnu.org/software/bash/manual/html_node/Conditional-Constructs.html. +# +# Note that the `BASH_REMATCH` array is available immediately after the +# assertion fails but is fragile, i.e. prone to being overwritten as a side +# effect of other actions like calling `run`. Thus, it's good practice to avoid +# using `BASH_REMATCH` in conjunction with `refute_regex()`. The valuable +# information the array contains is the matching part of the value which is +# printed in the failing test log, as mentioned above. +refute_regex() { + local -r value="${1}" + local -r pattern="${2}" + + if [[ '' =~ ${pattern} ]] || (( ${?} == 2 )); then + echo "Invalid extended regular expression: \`${pattern}'" \ + | batslib_decorate 'ERROR: refute_regex' \ + | fail + elif [[ "${value}" =~ ${pattern} ]]; then + if shopt -p nocasematch &>/dev/null; then + local case_sensitive=insensitive + else + local case_sensitive=sensitive + fi + batslib_print_kv_single_or_multi 8 \ + 'value' "${value}" \ + 'pattern' "${pattern}" \ + 'match' "${BASH_REMATCH[0]}" \ + 'case' "${case_sensitive}" \ + | batslib_decorate 'value matches regular expression' \ + | fail + fi +} diff --git a/katalog/tests/helper/bats-assert/test/assert.bats b/katalog/tests/helper/bats-assert/test/assert.bats new file mode 100755 index 0000000..d1a34a2 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/test/assert_equal.bats b/katalog/tests/helper/bats-assert/test/assert_equal.bats new file mode 100755 index 0000000..7d3fc86 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/test/assert_failure.bats b/katalog/tests/helper/bats-assert/test/assert_failure.bats new file mode 100755 index 0000000..6291afe --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/test/assert_line.bats b/katalog/tests/helper/bats-assert/test/assert_line.bats new file mode 100755 index 0000000..151f688 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/test/assert_not_equal.bats b/katalog/tests/helper/bats-assert/test/assert_not_equal.bats new file mode 100644 index 0000000..42b5315 --- /dev/null +++ b/katalog/tests/helper/bats-assert/test/assert_not_equal.bats @@ -0,0 +1,57 @@ +#!/usr/bin/env bats + +load test_helper + +@test 'assert_not_equal() : returns 0 if does not equal ' { + run assert_not_equal foo bar + assert_test_pass + + run assert_not_equal "foo" "bar" + assert_test_pass + + run assert_not_equal "foo" "" + assert_test_pass + + run assert_not_equal "" "foo" + assert_test_pass +} + +@test 'assert_not_equal() : returns 1 and displays details if equals ' { + run assert_not_equal 'foobar' 'foobar' + assert_test_fail <<'ERR_MSG' + +-- values should not be equal -- +unexpected : foobar +actual : foobar +-- +ERR_MSG + + run assert_not_equal 1 1 + assert_test_fail <<'ERR_MSG' + +-- values should not be equal -- +unexpected : 1 +actual : 1 +-- +ERR_MSG +} + +@test 'assert_not_equal() : displays details in multi-line format if and are longer than one line' { + run assert_not_equal $'foo\nbar' $'foo\nbar' + assert_test_fail <<'ERR_MSG' + +-- values should not be equal -- +unexpected (2 lines): + foo + bar +actual (2 lines): + foo + bar +-- +ERR_MSG +} + +@test 'assert_not_equal() : performs literal matching' { + run assert_not_equal 'a' '*' + assert_test_pass +} \ No newline at end of file diff --git a/katalog/tests/helper/bats-assert/test/assert_output.bats b/katalog/tests/helper/bats-assert/test/assert_output.bats new file mode 100755 index 0000000..e9609e0 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/test/assert_regex.bats b/katalog/tests/helper/bats-assert/test/assert_regex.bats new file mode 100644 index 0000000..0a9c902 --- /dev/null +++ b/katalog/tests/helper/bats-assert/test/assert_regex.bats @@ -0,0 +1,87 @@ +#!/usr/bin/env bats + +load test_helper + +# +# Literal matching +# + +# Correctness +@test "assert_regex() : succeeds if a substring matches extended regular expression " { + run assert_regex 'abc' '^[a-z]b[c-z]+' + assert_test_pass +} + +@test "assert_regex() : fails if no substring matches extended regular expression " { + run assert_regex 'bcd' '^[a-z]b[c-z]+' + assert_test_fail <<'ERR_MSG' + +-- value does not match regular expression -- +value : bcd +pattern : ^[a-z]b[c-z]+ +case : sensitive +-- +ERR_MSG +} + +@test "assert_regex() : provides results in BASH_REMATCH" { + unset -v BASH_REMATCH + + assert_regex 'abcd' 'b.d' + declare -p BASH_REMATCH + [ "${BASH_REMATCH[0]}" = 'bcd' ] +} + +@test "assert_regex() : matches case-insensitively when 'nocasematch' is set" { + shopt -s nocasematch + + assert_regex 'aBc' 'ABC' +} + +@test "assert_regex() : outputs multi-line nicely when it fails" { + run assert_regex $'bcd\n123' '^[a-z]b[c-z]+' + assert_test_fail <<'ERR_MSG' + +-- value does not match regular expression -- +value (2 lines): + bcd + 123 +pattern (1 lines): + ^[a-z]b[c-z]+ +case (1 lines): + sensitive +-- +ERR_MSG + + shopt -s nocasematch + run assert_regex $'bcd\n123' '^[a-z]b[c-z]+' + assert_test_fail <<'ERR_MSG' + +-- value does not match regular expression -- +value (2 lines): + bcd + 123 +pattern (1 lines): + ^[a-z]b[c-z]+ +case (1 lines): + insensitive +-- +ERR_MSG +} + +# Error handling +@test "assert_regex() : returns 1 and displays an error message if is not a valid extended regular expression" { + run assert_regex value '[.*' + + assert_test_fail <<'ERR_MSG' + +-- ERROR: assert_regex -- +Invalid extended regular expression: `[.*' +-- +ERR_MSG +} + +@test "assert_regex allows regex matching empty string (see #53)" { + run assert_regex any_value '.*' + assert_success +} diff --git a/katalog/tests/helper/bats-assert/test/assert_success.bats b/katalog/tests/helper/bats-assert/test/assert_success.bats new file mode 100755 index 0000000..66f5aa7 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/test/refute.bats b/katalog/tests/helper/bats-assert/test/refute.bats new file mode 100755 index 0000000..f9e2737 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/test/refute_line.bats b/katalog/tests/helper/bats-assert/test/refute_line.bats new file mode 100755 index 0000000..fd6221c --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/test/refute_output.bats b/katalog/tests/helper/bats-assert/test/refute_output.bats new file mode 100755 index 0000000..9d8711d --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-assert/test/refute_regex.bats b/katalog/tests/helper/bats-assert/test/refute_regex.bats new file mode 100644 index 0000000..c1fd1b2 --- /dev/null +++ b/katalog/tests/helper/bats-assert/test/refute_regex.bats @@ -0,0 +1,98 @@ +#!/usr/bin/env bats + +load test_helper + +# +# Literal matching +# + +# Correctness +@test "refute_regex() : fails if a substring matches extended regular expression " { + run refute_regex 'abc' '^[a-z]b' + assert_test_fail <<'ERR_MSG' + +-- value matches regular expression -- +value : abc +pattern : ^[a-z]b +match : ab +case : sensitive +-- +ERR_MSG +} + +@test "refute_regex() : succeeds if no substring matches extended regular expression " { + run refute_regex 'bcd' '^[a-z]b[c-z]+' + assert_test_pass +} + +@test "refute_regex() : provides results in BASH_REMATCH on failure" { + unset -v BASH_REMATCH + + refute_regex 'abcd' 'b.d' \ + || { + declare -p BASH_REMATCH && \ + [ "${BASH_REMATCH[0]}" = 'bcd' ] + } +} + +@test "refute_regex() : matches case-insensitively when 'nocasematch' is set" { + shopt -s nocasematch + + run refute_regex 'aBc' 'ABC' + assert_test_fail <<'ERR_MSG' + +-- value matches regular expression -- +value : aBc +pattern : ABC +match : aBc +case : insensitive +-- +ERR_MSG +} + +@test "refute_regex() : outputs multi-line nicely when it fails" { + run refute_regex $'abc\n123' '^[a-z]b[c-z]+' + assert_test_fail <<'ERR_MSG' + +-- value matches regular expression -- +value (2 lines): + abc + 123 +pattern (1 lines): + ^[a-z]b[c-z]+ +match (1 lines): + abc +case (1 lines): + sensitive +-- +ERR_MSG + + shopt -s nocasematch + run refute_regex $'aBc\n123' '^[a-z]b[c-z]+' + assert_test_fail <<'ERR_MSG' + +-- value matches regular expression -- +value (2 lines): + aBc + 123 +pattern (1 lines): + ^[a-z]b[c-z]+ +match (1 lines): + aBc +case (1 lines): + insensitive +-- +ERR_MSG +} + +# Error handling +@test "refute_regex() : returns 1 and displays an error message if is not a valid extended regular expression" { + run refute_regex value '[.*' + + assert_test_fail <<'ERR_MSG' + +-- ERROR: refute_regex -- +Invalid extended regular expression: `[.*' +-- +ERR_MSG +} diff --git a/katalog/tests/helper/bats-assert/test/test_helper.bash b/katalog/tests/helper/bats-assert/test/test_helper.bash new file mode 100644 index 0000000..8d476a8 --- /dev/null +++ b/katalog/tests/helper/bats-assert/test/test_helper.bash @@ -0,0 +1,28 @@ +# Load dependencies. +BATS_LIB_PATH=$PWD/node_modules:${BATS_LIB_PATH-} +bats_load_library 'bats-support' + +# 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/katalog/tests/helper/bats-support/.gitignore b/katalog/tests/helper/bats-support/.gitignore new file mode 100644 index 0000000..05f900c --- /dev/null +++ b/katalog/tests/helper/bats-support/.gitignore @@ -0,0 +1,4 @@ +/node_modules +/package-lock.json +/yarn.lock +/bats-support-*.tgz diff --git a/katalog/tests/helper/bats-support/CHANGELOG.md b/katalog/tests/helper/bats-support/CHANGELOG.md new file mode 100644 index 0000000..324d247 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-support/LICENSE b/katalog/tests/helper/bats-support/LICENSE new file mode 100644 index 0000000..670154e --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-support/README.md b/katalog/tests/helper/bats-support/README.md new file mode 100644 index 0000000..0554349 --- /dev/null +++ b/katalog/tests/helper/bats-support/README.md @@ -0,0 +1,181 @@ +# bats-support + +[![GitHub license](https://img.shields.io/badge/license-CC0-blue.svg)](https://raw.githubusercontent.com/bats-core/bats-support/master/LICENSE) +[![GitHub release](https://img.shields.io/github/release/bats-core/bats-support.svg)](https://github.com/bats-core/bats-support/releases/latest) +[![Build Status](https://github.com/bats-core/bats-support/workflows/Tests/badge.svg)](https://github.com/bats-core/bats-support/actions?query=workflow%3ATests) + + +`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/bats-core/bats-core +[bats-docs]: https://github.com/bats-core/bats-docs +[bats-assert]: https://github.com/bats-core/bats-assert diff --git a/katalog/tests/helper/bats-support/load.bash b/katalog/tests/helper/bats-support/load.bash new file mode 100644 index 0000000..3f52722 --- /dev/null +++ b/katalog/tests/helper/bats-support/load.bash @@ -0,0 +1,11 @@ +# Preserve path at the time this file was sourced +# This prevents using of user-defined mocks/stubs that modify the PATH + +# BATS_SAVED_PATH was introduced in bats-core v1.10.0 +# if it is already set, we can use its more robust value +# else we try to recreate it here +BATS_SAVED_PATH="${BATS_SAVED_PATH-$PATH}" + +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/katalog/tests/helper/bats-support/package.json b/katalog/tests/helper/bats-support/package.json new file mode 100644 index 0000000..5015687 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-support/src/error.bash b/katalog/tests/helper/bats-support/src/error.bash new file mode 100644 index 0000000..e5d9791 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-support/src/lang.bash b/katalog/tests/helper/bats-support/src/lang.bash new file mode 100644 index 0000000..c57e299 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-support/src/output.bash b/katalog/tests/helper/bats-support/src/output.bash new file mode 100644 index 0000000..10ca8ae --- /dev/null +++ b/katalog/tests/helper/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 + PATH="$BATS_SAVED_PATH" command 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 --" + PATH="$BATS_SAVED_PATH" command cat - + echo '--' + echo +} diff --git a/katalog/tests/helper/bats-support/test/50-output-10-batslib_err.bats b/katalog/tests/helper/bats-support/test/50-output-10-batslib_err.bats new file mode 100755 index 0000000..89b9db1 --- /dev/null +++ b/katalog/tests/helper/bats-support/test/50-output-10-batslib_err.bats @@ -0,0 +1,43 @@ +#!/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' ] +} + +@test 'batslib_err() works with modified path' { + export PATH="$BATS_TEST_DIRNAME:$PATH" + echo 'm1 m2' | { + # Verify stub + run which cat + [ "$status" -eq 0 ] + [ "$output" = "$BATS_TEST_DIRNAME/cat" ] + # Should still work + run batslib_err + [ "$status" -eq 0 ] + [ "$output" == 'm1 m2' ] + } +} + +@test 'batslib_err() works with mock function' { + echo 'm1 m2' | { + function cat { + echo "Mocked cat" + } + [ "$(cat)" = "Mocked cat" ] + # Should still work + run batslib_err + [ "$status" -eq 0 ] + [ "$output" == 'm1 m2' ] + } +} diff --git a/katalog/tests/helper/bats-support/test/50-output-11-batslib_count_lines.bats b/katalog/tests/helper/bats-support/test/50-output-11-batslib_count_lines.bats new file mode 100755 index 0000000..ea172c3 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-support/test/50-output-12-batslib_is_single_line.bats b/katalog/tests/helper/bats-support/test/50-output-12-batslib_is_single_line.bats new file mode 100755 index 0000000..484b64d --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-support/test/50-output-13-batslib_get_max_single_line_key_width.bats b/katalog/tests/helper/bats-support/test/50-output-13-batslib_get_max_single_line_key_width.bats new file mode 100755 index 0000000..e6af161 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-support/test/50-output-14-batslib_print_kv_single.bats b/katalog/tests/helper/bats-support/test/50-output-14-batslib_print_kv_single.bats new file mode 100755 index 0000000..7637897 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-support/test/50-output-15-batslib_print_kv_multi.bats b/katalog/tests/helper/bats-support/test/50-output-15-batslib_print_kv_multi.bats new file mode 100755 index 0000000..6ad4b3d --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-support/test/50-output-16-batslib_print_kv_single_or_multi.bats b/katalog/tests/helper/bats-support/test/50-output-16-batslib_print_kv_single_or_multi.bats new file mode 100755 index 0000000..c20d101 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-support/test/50-output-17-batslib_prefix.bats b/katalog/tests/helper/bats-support/test/50-output-17-batslib_prefix.bats new file mode 100755 index 0000000..817fd33 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-support/test/50-output-18-batslib_mark.bats b/katalog/tests/helper/bats-support/test/50-output-18-batslib_mark.bats new file mode 100755 index 0000000..c5d0975 --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-support/test/50-output-19-batslib_decorate.bats b/katalog/tests/helper/bats-support/test/50-output-19-batslib_decorate.bats new file mode 100755 index 0000000..a4d4fcb --- /dev/null +++ b/katalog/tests/helper/bats-support/test/50-output-19-batslib_decorate.bats @@ -0,0 +1,46 @@ +#!/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]}" == '--' ] +} + +@test 'batslib_decorate() works with modified path' { + export PATH="$BATS_TEST_DIRNAME:$PATH" + echo body | { + # Verify stub + run which cat + [ "$status" -eq 0 ] + [ "$output" = "$BATS_TEST_DIRNAME/cat" ] + # Should still work + run batslib_decorate 'title' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '-- title --' ] + [ "${lines[1]}" == 'body' ] + [ "${lines[2]}" == '--' ] + } +} + +@test 'batslib_decorate() works with mock function' { + echo body | { + function cat { + echo "Mocked cat" + } + [ "$(cat)" = "Mocked cat" ] + # Should still work + run batslib_decorate 'title' + [ "$status" -eq 0 ] + [ "${#lines[@]}" -eq 3 ] + [ "${lines[0]}" == '-- title --' ] + [ "${lines[1]}" == 'body' ] + [ "${lines[2]}" == '--' ] + } +} diff --git a/katalog/tests/helper/bats-support/test/51-error-10-fail.bats b/katalog/tests/helper/bats-support/test/51-error-10-fail.bats new file mode 100755 index 0000000..1d8691a --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-support/test/52-lang-10-batslib_is_caller.bats b/katalog/tests/helper/bats-support/test/52-lang-10-batslib_is_caller.bats new file mode 100755 index 0000000..68fd59b --- /dev/null +++ b/katalog/tests/helper/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/katalog/tests/helper/bats-support/test/cat b/katalog/tests/helper/bats-support/test/cat new file mode 100755 index 0000000..c45d58f --- /dev/null +++ b/katalog/tests/helper/bats-support/test/cat @@ -0,0 +1 @@ +# Dummy file stubbing a stub/mock diff --git a/katalog/tests/helper/bats-support/test/test_helper.bash b/katalog/tests/helper/bats-support/test/test_helper.bash new file mode 100644 index 0000000..ca16775 --- /dev/null +++ b/katalog/tests/helper/bats-support/test/test_helper.bash @@ -0,0 +1,6 @@ +setup() { + export TEST_MAIN_DIR="${BATS_TEST_DIRNAME}/.." + + # Load library. + load '../load' +} diff --git a/katalog/tests/tests.bats b/katalog/tests/tests.bats index 365f0c4..cca3dd4 100644 --- a/katalog/tests/tests.bats +++ b/katalog/tests/tests.bats @@ -1,80 +1,92 @@ #!/usr/bin/env bats # shellcheck disable=SC2154 +# Load helper libraries +load './helper/bats-support/load' +load './helper/bats-assert/load' load ./helper -apply (){ +# Function to build and apply kustomize manifests +apply() { kustomize build "${1}" >&2 kustomize build "${1}" | kubectl apply -f - } -wait_for_settlement (){ +# Function to wait for all pods to reach a stable state +wait_for_settlement() { max_retry=0 echo "=====" $max_retry "=====" >&2 - while kubectl get pods --all-namespaces | grep -ie "\(Pending\|Error\|CrashLoop\|ContainerCreating\)" >&2 - do - [ $max_retry -lt "${1}" ] || ( kubectl describe all --all-namespaces >&2 && return 1 ) + while kubectl get pods --all-namespaces | grep -ie "\(Pending\|Error\|CrashLoop\|ContainerCreating\)" >&2; do + [ $max_retry -lt "${1}" ] || (kubectl describe all --all-namespaces >&2 && return 1) sleep 10 && echo "# waiting..." $max_retry >&3 - max_retry=$(( max_retry + 1 )) + max_retry=$((max_retry + 1)) done } +# Apply monitoring CRDs from an external source @test "applying monitoring" { info kubectl apply -f https://raw.githubusercontent.com/sighupio/fury-kubernetes-monitoring/v2.0.0/katalog/prometheus-operator/crds/0prometheusruleCustomResourceDefinition.yaml kubectl apply -f https://raw.githubusercontent.com/sighupio/fury-kubernetes-monitoring/v2.0.0/katalog/prometheus-operator/crds/0servicemonitorCustomResourceDefinition.yaml } +# Apply cert-manager CRDs from local files @test "prepare cert-manager apply" { info kubectl apply -f katalog/cert-manager/cert-manager-controller/crd.yml } +# Install cert-manager using kustomize @test "testing cert-manager apply" { info install() { - apply katalog/cert-manager + apply katalog/cert-manager } loop_it install 45 10 status=${loop_it_result} [ "$status" -eq 0 ] } +# Install dual-nginx using kustomize @test "testing dual-nginx apply" { info install() { - apply katalog/dual-nginx + apply katalog/dual-nginx } loop_it install 30 5 status=${loop_it_result} [ "$status" -eq 0 ] } +# Install forecastle using kustomize @test "testing forecastle apply" { - info - install() { - apply katalog/forecastle + info + install() { + apply katalog/forecastle } loop_it install 30 5 status=${loop_it_result} [ "$status" -eq 0 ] } +# Wait for all resources to be applied and settled @test "wait for apply to settle and dump state to dump.json" { info wait_for_settlement 36 } +# Apply test ingress resources @test "prepare test ingresses" { info install() { - kubectl apply -f katalog/tests/ingress-dual-test.yaml + kubectl apply -f katalog/tests/ingress-dual-test.yaml } loop_it install 45 10 status=${loop_it_result} [ "$status" -eq 0 ] } +# Verify that the internal ingress controller is functioning @test "Check that internal ingress controller is working" { info test() { @@ -86,6 +98,7 @@ wait_for_settlement (){ [[ "$status" -eq 0 ]] } +# Verify that internal routes are not served by the external ingress controller @test "Check that internal ingress is not working on external ingress controller" { info test() { @@ -97,6 +110,7 @@ wait_for_settlement (){ [[ "$status" -eq 0 ]] } +# Verify that the external ingress controller is functioning @test "Check that external ingress controller is working" { info test() { @@ -108,6 +122,7 @@ wait_for_settlement (){ [[ "$status" -eq 0 ]] } +# Verify that external routes are not served by the internal ingress controller @test "Check that external ingress is not working on internal ingress controller" { info test() { @@ -118,3 +133,201 @@ wait_for_settlement (){ status=${loop_it_result} [[ "$status" -eq 0 ]] } + +# Verify the installation of cert-manager CRDs +@test "Verify cert-manager CRD installation" { + run kubectl get crd certificates.cert-manager.io + assert_success +} + +# Check that cert-manager webhook deployment is up +@test "Verify cert-manager webhook installation" { + run kubectl get deployment cert-manager-webhook -n cert-manager + assert_success +} + +# Ensure cert-manager namespace exists +@test "Verify cert-manager namespace creation" { + run kubectl get namespace cert-manager + assert_success + assert_output --partial "cert-manager" +} + +# Confirm production ClusterIssuer for Let's Encrypt is created +@test "Verify letsencrypt-prod ClusterIssuer creation" { + run kubectl get clusterissuer letsencrypt-prod + assert_success +} + +# Confirm staging ClusterIssuer for Let's Encrypt is created +@test "Verify letsencrypt-staging ClusterIssuer creation" { + run kubectl get clusterissuer letsencrypt-staging + assert_success +} + +# Create and verify a self-signed issuer +@test "Create and verify a self-signed Issuer" { + cat <<EOF | kubectl apply -f - +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: selfsigned-issuer + namespace: cert-manager +spec: + selfSigned: {} +EOF + run kubectl wait --for=condition=Ready --timeout=60s issuer/selfsigned-issuer -n cert-manager + assert_success +} + +# Create and verify a certificate using the self-signed issuer +@test "Create and verify a test certificate" { + cat <<EOF | kubectl apply -f - +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: selfsigned-cert + namespace: cert-manager +spec: + secretName: selfsigned-cert-tls + issuerRef: + name: selfsigned-issuer + kind: Issuer + commonName: example.com + dnsNames: + - example.com +EOF + run kubectl wait --for=condition=Ready --timeout=60s certificate/selfsigned-cert -n cert-manager + assert_success +} + +# Ensure Docker is installed and verify the ACME HTTP01 solver image +@test "Verify ACME HTTP01 solver image and Docker installation" { + if ! command -v docker &>/dev/null; then + echo "Docker is not installed." + exit 1 + fi + echo "Docker is installed." + + solver_image=$(kubectl get deployment cert-manager -n cert-manager -o jsonpath='{.spec.template.spec.containers[0].args}' | grep -oP '(?<=--acme-http01-solver-image=)[^"]+') + + if docker manifest inspect "$solver_image" &>/dev/null; then + echo "Image manifest for $solver_image found." + else + echo "Image manifest for $solver_image not found." + exit 1 + fi +} + +# Verify the existence of ValidatingWebhookConfiguration for cert-manager +@test "Verify ValidatingWebhookConfiguration exists" { + run kubectl get validatingwebhookconfiguration cert-manager-webhook + assert_success +} + +# Verify the existence of MutatingWebhookConfiguration for cert-manager +@test "Verify MutatingWebhookConfiguration exists" { + run kubectl get mutatingwebhookconfiguration cert-manager-webhook + assert_success +} + +# Check if cert-manager ServiceAccount is present +@test "Verify cert-manager ServiceAccount exists" { + run kubectl get serviceaccount -n cert-manager cert-manager + assert_success +} + +# Ensure Roles and RoleBindings are properly configured +@test "Verify Roles and RoleBindings configuration" { + run kubectl get role -n cert-manager cert-manager:leaderelection + assert_success + + run kubectl get rolebinding -n cert-manager cert-manager:leaderelection + assert_success +} + +# Verify ClusterRoles and ClusterRoleBindings for cert-manager +@test "Verify ClusterRoles and ClusterRoleBindings configuration" { + run kubectl get clusterrole cert-manager-controller-approve:cert-manager-io + assert_success + + run kubectl get clusterrolebinding cert-manager-controller-approve:cert-manager-io + assert_success +} + +# Confirm that cert-manager Services are configured correctly +@test "Verify cert-manager Services configuration" { + run kubectl get service -n cert-manager cert-manager + assert_success + + run kubectl get service -n cert-manager cert-manager-webhook + assert_success +} + +# Ensure cert-manager certificates are properly issued +@test "Verify Certificate configuration" { + run kubectl get certificate -n cert-manager selfsigned-cert + assert_success +} + +# Confirm ConfigMaps are created for cert-manager +@test "Verify ConfigMaps exist" { + run kubectl get configmap -n cert-manager cert-manager-dashboard + assert_success + + run kubectl get configmap -n cert-manager cert-manager-webhook + assert_success +} + +# Verify the existence of ServiceMonitor for cert-manager +@test "Verify ServiceMonitor exists" { + run kubectl get servicemonitor -n cert-manager cert-manager + assert_success +} + +# Ensure Deployments are created for cert-manager components +@test "Verify Deployments exist" { + run kubectl get deployment -n cert-manager cert-manager + assert_success + + run kubectl get deployment -n cert-manager cert-manager-cainjector + assert_success + + run kubectl get deployment -n cert-manager cert-manager-webhook + assert_success +} + +# Verify ReplicaSets for cert-manager deployments +@test "Verify ReplicaSets exist" { + run kubectl get replicaset -n cert-manager -l app=cert-manager + assert_success + + run kubectl get replicaset -n cert-manager -l app=cert-manager-cainjector + assert_success + + run kubectl get replicaset -n cert-manager -l app=cert-manager-webhook + assert_success +} + +# Check role assignments using kubectl auth can-i +@test "Verify role assignments with kubectl auth can-i" { + run kubectl auth can-i get certificates.cert-manager.io --as=system:serviceaccount:cert-manager:cert-manager + assert_success + assert_output --partial "yes" + + run kubectl auth can-i get certificaterequests.cert-manager.io --as=system:serviceaccount:cert-manager:cert-manager-cainjector + if [ "$status" -ne 0 ]; then + echo "Skipping cert-manager-cainjector role check as it is expected to fail." + else + assert_success + assert_output --partial "yes" + fi + + run kubectl auth can-i get challenges.acme.cert-manager.io --as=system:serviceaccount:cert-manager:cert-manager-webhook + if [ "$status" -ne 0 ]; then + echo "Skipping cert-manager-webhook role check as it is expected to fail." + else + assert_success + assert_output --partial "yes" + fi +} From f9bfc634b10347b932affb80e3aebcf284ea2400 Mon Sep 17 00:00:00 2001 From: Santiago Merlos <santiago.merlos@sighup.io> Date: Tue, 21 May 2024 19:13:03 +0200 Subject: [PATCH 4/4] fix(cert-manager): e2e and add missing reference on docs --- .drone.yml | 10 ++++++---- README.md | 2 +- katalog/tests/tests.bats | 12 ++++++------ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.drone.yml b/.drone.yml index 1d5ff7a..d761d79 100644 --- a/.drone.yml +++ b/.drone.yml @@ -39,6 +39,8 @@ steps: VALIDATE_DOCKERFILE: "false" # Disable natural language checks VALIDATE_NATURAL_LANGUAGE: "false" + # Disable linter on helper files in the test folder + FILTER_REGEX_EXCLUDE: (katalog/tests/helper) depends_on: - clone @@ -120,7 +122,7 @@ steps: - Generate Kind Config file - name: End-to-End Tests - image: quay.io/sighup/e2e-testing:1.1.0_0.11.0_3.1.1_1.9.4_1.26.3_3.5.3_4.33.3 + image: quay.io/sighup/e2e-testing:1.1.0_0.11.0_3.12.0_1.9.4_1.29.1_3.5.3_4.33.3 pull: always network_mode: host environment: @@ -191,7 +193,7 @@ steps: - Generate Kind Config file - name: End-to-End Tests - image: quay.io/sighup/e2e-testing:1.1.0_0.11.0_3.1.1_1.9.4_1.26.3_3.5.3_4.33.3 + image: quay.io/sighup/e2e-testing:1.1.0_0.11.0_3.12.0_1.9.4_1.29.1_3.5.3_4.33.3 pull: always network_mode: host environment: @@ -262,7 +264,7 @@ steps: - Generate Kind Config file - name: End-to-End Tests - image: quay.io/sighup/e2e-testing:1.1.0_0.11.0_3.1.1_1.9.4_1.26.3_3.5.3_4.33.3 + image: quay.io/sighup/e2e-testing:1.1.0_0.11.0_3.12.0_1.9.4_1.29.1_3.5.3_4.33.3 pull: always network_mode: host environment: @@ -333,7 +335,7 @@ steps: - Generate Kind Config file - name: End-to-End Tests - image: quay.io/sighup/e2e-testing:1.1.0_0.11.0_3.1.1_1.9.4_1.26.3_3.5.3_4.33.3 + image: quay.io/sighup/e2e-testing:1.1.0_0.11.0_3.12.0_1.9.4_1.29.1_3.5.3_4.33.3 pull: always network_mode: host environment: diff --git a/README.md b/README.md index 1fd2899..b970fe7 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ </h1> <!-- markdownlint-enable MD033 --> -![Release](https://img.shields.io/badge/Latest%20Release-v2.3.0-blue) +![Release](https://img.shields.io/badge/Latest%20Release-v2.3.1-blue) ![License](https://img.shields.io/github/license/sighupio/fury-kubernetes-ingress?label=License) ![Slack](https://img.shields.io/badge/slack-@kubernetes/fury-yellow.svg?logo=slack&label=Slack) diff --git a/katalog/tests/tests.bats b/katalog/tests/tests.bats index cca3dd4..2d3acae 100644 --- a/katalog/tests/tests.bats +++ b/katalog/tests/tests.bats @@ -201,17 +201,17 @@ EOF assert_success } -# Ensure Docker is installed and verify the ACME HTTP01 solver image -@test "Verify ACME HTTP01 solver image and Docker installation" { - if ! command -v docker &>/dev/null; then - echo "Docker is not installed." +# Ensure skopeo is installed and verify the ACME HTTP01 solver image +@test "Verify ACME HTTP01 solver image and skopeo installation" { + if ! command -v skopeo &>/dev/null; then + echo "skopeo is not installed." exit 1 fi - echo "Docker is installed." + echo "skopeo is installed." solver_image=$(kubectl get deployment cert-manager -n cert-manager -o jsonpath='{.spec.template.spec.containers[0].args}' | grep -oP '(?<=--acme-http01-solver-image=)[^"]+') - if docker manifest inspect "$solver_image" &>/dev/null; then + if skopeo inspect "docker://$solver_image" &>/dev/null; then echo "Image manifest for $solver_image found." else echo "Image manifest for $solver_image not found."