diff --git a/.github/workflows/test-lint-deploy.yml b/.github/workflows/test-lint-deploy.yml index 2b3e95c..cc308d5 100644 --- a/.github/workflows/test-lint-deploy.yml +++ b/.github/workflows/test-lint-deploy.yml @@ -36,12 +36,13 @@ jobs: steps: - name: Install system dependencies - env: + env: DEBIAN_FRONTEND: noninteractive run: | sudo apt-get update -qq sudo apt-get install -qqy --no-install-recommends \ - ca-certificates coreutils curl git jq kcov make shellcheck + build-essential ca-certificates coreutils curl gawk gcc git jq \ + kcov make shellcheck - name: Install bats run: | @@ -65,18 +66,46 @@ jobs: chmod +x shfmt sudo mv shfmt /usr/local/bin/shfmt + - name: Install prettier + run: | + curl -sSfL -o install_nvm.sh https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh + echo "${NVMINSTALL_SHA256} install_nvm.sh" | sha256sum --check + bash install_nvm.sh + source "${HOME}/.nvm/nvm.sh" + nvm install "${NODE_VERSION}" + dirname "$(which npm)" >> "${GITHUB_PATH}" + npm install -g prettier + env: + NODE_VERSION: "20" + NVM_VERSION: v0.39.5 + NVMINSTALL_SHA256: 69da4f89f430cd5d6e591c2ccfa2e9e3ad55564ba60f651f00da85e04010c640 + + - name: Install mdslw + run: | + curl -sSfL -o mdslw https://github.com/razziel89/mdslw/releases/download/${MDSLW_VERSION}/mdslw_x86_64-unknown-linux-musl + echo "${MDSLW_SHA256} mdslw" | sha256sum --check + chmod +x mdslw + sudo mv mdslw /usr/local/bin + env: + MDSLW_VERSION: 0.5.5 + MDSLW_SHA256: 04d944b8e9596b82b7511c29bbf5ff10ab3ca707772d6a3164acb087a7e3f02e + - uses: actions/checkout@v3 with: fetch-depth: 0 + - name: Build Package + run: make build + - name: Test Package run: make test - name: Lint Package run: make lint - - name: Build Package - run: make build + - name: Test Supported Bash Versions + if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') }} + run: make test-bash-versions - name: Publish package on GH (only tags) if: ${{ startsWith(github.ref, 'refs/tags/') }} diff --git a/.gitignore b/.gitignore index 38d4107..39412b0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ # Deployable. /shellmock.bash /.coverage +# Version test artefacts. +/.failed +/.bash-*_test.log diff --git a/3rd-party-licenses.md b/3rd-party-licenses.md index f9ecdb4..dc519d0 100644 --- a/3rd-party-licenses.md +++ b/3rd-party-licenses.md @@ -2,13 +2,13 @@ Copyright (c) 2022 - for information on the respective copyright owner see the NOTICE file or the repository https://github.com/boschresearch/shellmock - + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d23d8e2..6147faa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,13 +2,13 @@ Copyright (c) 2022 - for information on the respective copyright owner see the NOTICE file or the repository https://github.com/boschresearch/shellmock - + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -27,10 +27,10 @@ Shellmock team early in the process. Coordinating up front helps to avoid frustration later on. Please make sure to: - - add tests for all new or updated code - - make sure existing tests work by running `make test` - - make sure the code follows this repository's guidelines by running the - commands `make format` and `make lint` +- add tests for all new or updated code +- make sure existing tests work by running `make test` +- make sure the code follows this repository's guidelines by running the + commands `make format` and `make lint` Your contribution must be licensed under the Apache-2.0 license, the license used by this project. @@ -90,8 +90,9 @@ By making a contribution to this project, I certify that: With the sign-off in a commit message you certify that you authored the patch or otherwise have the right to submit it under an open source license. -The procedure is simple: To certify above Developer's Certificate of Origin 1.1 -for your contribution just append a line +The procedure is simple: +To certify above Developer's Certificate of Origin 1.1 for your contribution +just append a line ```text Signed-off-by: Random J Developer @@ -112,9 +113,9 @@ which is stored in an external repository. ### Individual vs. Corporate Contributors -Often employers or academic institution have ownership over code that is -written in certain circumstances, so please do due diligence to ensure that -you have the rights to submit the code. +Often employers or academic institution have ownership over code that is written +in certain circumstances, so please do due diligence to ensure that you have the +rights to submit the code. If you are a developer who is authorized to contribute to Shellmock on behalf of your employer, then please use your corporate email address in the Signed-off-by @@ -146,13 +147,13 @@ holder(s) to the [NOTICE](./NOTICE) file as part of your contribution. Copyright (c) 2022 - for information on the respective copyright owner see the NOTICE file or the repository https://github.com/boschresearch/shellmock - + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/Makefile b/Makefile index ce4306e..2f46b4b 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,13 @@ # Copyright (c) 2022 - for information on the respective copyright owner # see the NOTICE file or the repository # https://github.com/boschresearch/shellmock -# +# # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -40,6 +40,45 @@ lint: test: build bats --print-output-on-failure ./tests/*.bats +DOWNLOAD_URL_PREFIX := https://mirror.kumi.systems/gnu/bash/ + +.PHONY: build-bash-version +build-bash-version: + # Ensure that the arguments have been set. + [[ -n "$(BASH_VERSION)" && -n "$(BASH_PATH)" ]] + cd "$(BASH_PATH)" && \ + curl -sSfL -o bash.tar.gz "$(DOWNLOAD_URL_PREFIX)/bash-$(BASH_VERSION).tar.gz" && \ + tar -xvzf bash.tar.gz && \ + cd "bash-$(BASH_VERSION)" && \ + ./configure && \ + make && \ + mv bash "$(BASH_PATH)" + +.PHONY: test-bash-version +test-bash-version: + # Ensure that the temp dir will be removed afterwards. + tmp=$$(mktemp -d) && trap "rm -rf '$${tmp}'" EXIT && \ + $(MAKE) build-bash-version BASH_PATH="$${tmp}" && \ + export PATH="$${tmp}:$${PATH}" && \ + echo "INFO: using $$(which bash) @ $$(bash -c 'echo $${BASH_VERSION}')" && \ + bats --print-output-on-failure ./tests/*.bats + +SUPPORTED_VERSIONS := 5.2 5.1 5.0 4.4 + +.PHONY: test-bash-versions +test-bash-versions: build + rm -f .failed .bash-*_test.log + for version in $(SUPPORTED_VERSIONS); do \ + $(MAKE) test-bash-version BASH_VERSION="$${version}" 2>&1 \ + | tee ".bash-$${version}_test.log" \ + || echo "$${version}" >> .failed & \ + done; \ + wait + if [[ -s .failed ]]; then \ + echo "Failed versions: $$(sort .failed | tr -s '[:space:]' ' ')"; \ + fi + [[ ! -s .failed ]] + COVERAGE_FAILED_MESSAGE := \ Cannot generate coverage reports as root user because kcov is not \ compatible with current versions of bash when run as root, also see \ @@ -54,7 +93,7 @@ coverage: test bats --print-output-on-failure ./tests/*.bats # Analyse output of coverage reports and fail if not all files have been # covered of if coverage is not high enough. - awk \ + gawk \ -v min_cov="92" \ -v tot_num_files="1" \ 'BEGIN{num_files=0; cov=0;} \ @@ -77,10 +116,12 @@ coverage: test format: shfmt -w -bn -i 2 -sr -ln bash ./bin/* ./lib/* shfmt -w -bn -i 2 -sr -ln bats ./tests/* + mdslw --mode=format --upstream="prettier --parser=markdown" . check-format: shfmt -d -bn -i 2 -sr -ln bash ./bin/* ./lib/* shfmt -d -bn -i 2 -sr -ln bats ./tests/* + mdslw --mode=check --upstream="prettier --parser=markdown" . build: ./generate_deployable.sh diff --git a/README.md b/README.md index 3eb80b8..267a77d 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ Copyright (c) 2022 - for information on the respective copyright owner see the NOTICE file or the repository https://github.com/boschresearch/shellmock - + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -18,9 +18,9 @@ # shellmock -- [Quickstart Guide](#quickstart-guide) +- [Quickstart Guide](#quickstart-guide) - [Dependencies](#dependencies) -- [Further examples](#further-examples) +- [Further examples](#further-examples) - [Feedback](#feedback) - [About](#about) - [Maintainers](#maintainers) @@ -37,13 +37,13 @@ and `golang/mock`. ## Quickstart Guide If you simply want to get this show in the road, just head over to the -[releases], download the [latest release][latest-release]. +[releases], download the [latest release][latest-release]. Then, you can write your [bats-core]-based tests. Just make sure to use `load shellmock` in your `setup` function. -You can also have a look at [this example](./docs/example.md) or -[shellmock's own tests][shellmock-tests]. +You can also have a look at [this example](./docs/example.md) or +[shellmock's own tests][shellmock-tests]. -The [technical documentation](./docs/README.md) will go more in depth on how to +The [technical documentation](./docs/README.md) will go more in depth on how to use `shellmock` to mock any commands but here is a simple example of the command and what each parameter does. @@ -66,19 +66,21 @@ The `shellmock` command: - `shellmock new curl` Create a mock executable called `curl` in a directory `shellmock` controls. - Then, `shellmock` will modify the `PATH` environment variable to make - sure that the mock it controls is used preferentially to the actual - `curl` executable on your system. + Then, `shellmock` will modify the `PATH` environment variable to make sure + that the mock it controls is used preferentially to the actual `curl` + executable on your system. - `shellmock config curl 0 2:http://www.google.com` Configure the mock. - Here, you specify the arguments you expect your command - to be called with, as well as the mock's exit status code. - - - `0`: the exit status code of the mock (`0` means "success") - - `2:http://www.google.com`: State that the second argument of the command - is expected to be the literal string `http://www.google.com`. + Here, you specify the arguments you expect your command to be called with, as + well as the mock's exit status code. + + - `0`: + the exit status code of the mock (`0` means "success") + - `2:http://www.google.com`: + State that the second argument of the command is expected to be the literal + string `http://www.google.com`. Note that counting arguments starts at 1. Any other argument could have any value and the mock would accept it. @@ -93,7 +95,7 @@ The `shellmock` command: via a previous call to `shellmock config` (i.e. an expected call is missing). -Please have a look at the [full command reference](./docs/usage.md) for all +Please have a look at the [full command reference](./docs/usage.md) for all details. [shellmock-tests]: ./tests/main.bats "shellmock tests" @@ -104,13 +106,13 @@ details. The following tools are needed to use `shellmock`: -- `awk` - `base32` - `base64` -- `bash` +- `bash` (at least version 4.4) - `cat` - `env` - `find` +- `gawk` - `grep` - `sed` - `sort` @@ -125,8 +127,8 @@ sudo apt install -yqq coreutils findutils gawk grep sed ## Further examples -As mentioned before, you can check out more examples in [shellmock's own -tests][shellmock-tests]. +As mentioned before, you can check out more examples in +[shellmock's own tests][shellmock-tests]. A non-exhaustive list of examples follows: - Mock an executable @@ -148,7 +150,7 @@ But we are eager to hear your feedback on how we could improve! ### Maintainers -['Torsten Long'](https://github.com/razziel89) +['Torsten Long'](https://github.com/razziel89) ### License @@ -158,13 +160,13 @@ See the [LICENSE](./LICENSE) file for details. > Copyright (c) 2022 - for information on the respective copyright owner > see the NOTICE file or the repository > https://github.com/boschresearch/shellmock -> +> > Licensed under the Apache License, Version 2.0 (the "License"); you may not > use this file except in compliance with the License. You may obtain a copy of > the License at -> -> http://www.apache.org/licenses/LICENSE-2.0 -> +> +> http://www.apache.org/licenses/LICENSE-2.0 +> > Unless required by applicable law or agreed to in writing, software > distributed under the License is distributed on an "AS IS" BASIS, WITHOUT > WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -175,13 +177,13 @@ See the [LICENSE](./LICENSE) file for details. Copyright (c) 2022 - for information on the respective copyright owner see the NOTICE file or the repository https://github.com/boschresearch/shellmock - + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the diff --git a/bin/mock_exe.sh b/bin/mock_exe.sh index 0d1cfe0..6f9dabf 100755 --- a/bin/mock_exe.sh +++ b/bin/mock_exe.sh @@ -112,7 +112,7 @@ _match_spec() { while read -r spec; do local id val - id="$(awk -F: '{print $1}' <<< "${spec}")" + id="$(gawk -F: '{print $1}' <<< "${spec}")" val="${spec##"${id}":}" if [[ ${spec} =~ ^any: ]]; then @@ -136,7 +136,7 @@ _match_spec() { errecho "Internal error, incorrect spec ${spec}" return 1 fi - done < <(base64 --decode <<< "${full_spec}") + done < <(base64 --decode <<< "${full_spec}") && wait $! } _kill_parent() { @@ -169,7 +169,7 @@ find_matching_argspec() { done < <( env | sed 's/=.*$//' \ | grep -x "MOCK_ARGSPEC_BASE64_${cmd_b32}_[0-9][0-9]*" | sort -u - ) + ) && wait $! errecho "SHELLMOCK: unexpected call to '$0 $*'" _kill_parent "${PPID}" diff --git a/docs/README.md b/docs/README.md index b3a3d31..4878ce2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,13 +2,13 @@ Copyright (c) 2022 - for information on the respective copyright owner see the NOTICE file or the repository https://github.com/boschresearch/shellmock - + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -21,8 +21,8 @@ This is the in-depth technical documentation of `shellmock`. You can find more information about: -- [Building the deployable](./build.md) -- [Usage and command reference](./usage.md) -- [Detailed Example](./example.md) +- [Building the deployable](./build.md) +- [Usage and command reference](./usage.md) +- [Detailed Example](./example.md) -You can also go back to [the main readme](../README.md). +You can also go back to [the main readme](../README.md). diff --git a/docs/build.md b/docs/build.md index 2c521b2..b39aa26 100644 --- a/docs/build.md +++ b/docs/build.md @@ -2,13 +2,13 @@ Copyright (c) 2022 - for information on the respective copyright owner see the NOTICE file or the repository https://github.com/boschresearch/shellmock - + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -20,4 +20,4 @@ Simply run the script `./generate_deployable.sh` at the top level of this repository to generate `shellmoch.bash`. -It uses only the standard Unix tools `bash`, `cat`, and `awk` to generate it. +It uses only the standard Unix tools `bash`, `cat`, and `gawk` to generate it. diff --git a/docs/example.md b/docs/example.md index a741191..4d762b1 100644 --- a/docs/example.md +++ b/docs/example.md @@ -2,13 +2,13 @@ Copyright (c) 2022 - for information on the respective copyright owner see the NOTICE file or the repository https://github.com/boschresearch/shellmock - + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -31,7 +31,7 @@ That script could look like this: # Read argument to script. branch_name="$1" # Ensure the argument is non-empty. -if [[ -z "${branch_name}" ]]; then +if [[ -z ${branch_name} ]]; then echo "Empty argument received." >&2 # This command always exits with an error. It's the last one executed and, # thus, its exit code will be the one of this script. It is important not to @@ -61,7 +61,7 @@ For example, you could test: The below examples assume some familiarity with [bats-core]. If you want to get started with [bats-core]-based testing, we can recommend this -[bats testing guide][bats-guide]. +[bats testing guide][bats-guide]. Although, instead of installing [bats-core] as a `git` sub-module, we recommend a user-space installation via `npm` via `npm install -g bats`. @@ -148,7 +148,7 @@ To do so, you need to use shell functions throughout and source the script in your tests. Doing so will allow you to test individual functions. You can also mock functions called by your own functions. -Please have a look at [shellmock's own tests][shellmock-tests] for what is +Please have a look at [shellmock's own tests][shellmock-tests] for what is possible. [bats-core]: https://bats-core.readthedocs.io/ "bats core website" diff --git a/docs/usage.md b/docs/usage.md index fc6082f..f773267 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -45,12 +45,18 @@ sure to replace `${PATH_TO_SHELLMOCK_LIBRARY}` appropriately. You can access all functionality of Shellmock via the `shellmock` command. It is implemented as a shell function with the following sub-commands: -- `new`: Create a new mock for an executable. -- `config`: Configure a previously-created mock by defining expectations. -- `assert`: Assert based on previously-configured expectations. -- `global-config`: Configure global behaviour of Shellmock itself. -- `calls`: Log past calls to mocks and suggest mock configs to reproduce. -- `help`: Provide a help text. +- `new`: + Create a new mock for an executable. +- `config`: + Configure a previously-created mock by defining expectations. +- `assert`: + Assert based on previously-configured expectations. +- `global-config`: + Configure global behaviour of Shellmock itself. +- `calls`: + Log past calls to mocks and suggest mock configs to reproduce. +- `help`: + Provide a help text. @@ -60,7 +66,8 @@ The more complex sub-commands will be described below in detail. -Syntax: `shellmock new ` +Syntax: +`shellmock new ` The `new` command creates a new mock executable called `name`. It is created in a directory in your `PATH` that is controlled by Shellmock. @@ -68,8 +75,8 @@ You need to create a mock before you can configure it or make assertions on it. -The `new` command takes exactly one argument: the name of the executable to be -mocked. +The `new` command takes exactly one argument: +the name of the executable to be mocked. For example: ```bash @@ -84,7 +91,8 @@ from that point forward, assuming no code changes `PATH`. -Syntax: `shellmock config [1: [...]]` +Syntax: +`shellmock config [1: [...]]` The `config` command defines expectations for calls to your mocked executable. You need to define expectations before you can make assertions on your mock. @@ -108,7 +116,8 @@ Everything read from standard input will be echoed by the mock to its standard output verbatim. There is no way to have the mock write something to standard error. -** Example**: A call to `git branch` that +** Example**: +A call to `git branch` that - returns with exit code `0`, indicating success, - expects to be called with `branch` as first argument, and @@ -146,7 +155,8 @@ Argspec sets as defined via `config` are matched in order of definition. The first one found that matches the given arguments will be used by the mock executable. -** Example**: Catch-all mock configuration +** Example**: +Catch-all mock configuration ```bash shellmock new git @@ -164,18 +174,20 @@ fi #### argspec Definitions -There are three _types_ of argspecs: two position-dependent ones (numeric and -incremental) and one position-independent (flexible) one. +There are three _types_ of argspecs: +two position-dependent ones (numeric and incremental) and one +position-independent (flexible) one. Position-dependent types should be preferred whenever possible. -There are also two _kinds_ of argspecs: exact string matches and regex-based -string matches. +There are also two _kinds_ of argspecs: +exact string matches and regex-based string matches. Exact string matches should be preferred whenever possible. -The _types_ and _kinds_ of argspecs can be combined to create, for example, -a regex-based position-independent argspec. +The _types_ and _kinds_ of argspecs can be combined to create, for example, a +regex-based position-independent argspec. -In general, an argspec looks like this: `:`. +In general, an argspec looks like this: +`:`. Normal shell-quoting rules apply to argspecs, especially to the `value` part. That is, to specify an argument with spaces, you need to quote the argspec. We recommend quoting only the value because it is easier to read. @@ -191,7 +203,8 @@ This argspec matches if the argument at position `n` has exactly the value Argument counting starts at 1. Arguments at undefined positions can be anything. -** Example**: Only specified argspecs matter +** Example**: +Only specified argspecs matter ```bash shellmock new git @@ -209,7 +222,8 @@ git diff develop main While the order of numeric argspecs has no influence, we recommend to define numeric argspecs in ascending order. -** Example**: Numeric argspec order +** Example**: +Numeric argspec order ```bash # these mocks are equivalent @@ -219,14 +233,15 @@ shellmock config git 0 1:checkout 3:master 2:develop ##### Incremental Position-Dependent argspec -You can also replace the numeric value indicating the expected -position of an argument by the letter `i`. +You can also replace the numeric value indicating the expected position of an +argument by the letter `i`. That letter will automatically be replaced by the value used for the previous argspec increased by 1. If the first argspec uses the `i` placeholder, it will be replaced by `1`. Numeric and incremental position indicators can be mixed. -** Example**: Incremental argspec +** Example**: +Incremental argspec ```bash shellmock new git @@ -245,9 +260,11 @@ git rebase my-branch develop A flexible position-independent argspec replaces the position indicator by the literal word `any`. Thus, if we did not care at which position the `branch` keyword were in the -first example, we could use: `any:branch`. +first example, we could use: +`any:branch`. -** Example**: Position-independent argspec +** Example**: +Position-independent argspec ```bash shellmock new git @@ -261,7 +278,8 @@ git diff develop main You can combine position-independent and position-dependent argspecs. Note that the position indicator `i` cannot directly follow `any`. -** Example**: Combining position-independent and dependent argspecs +** Example**: +Combining position-independent and dependent argspecs ```bash shellmock new git @@ -275,7 +293,8 @@ Note that the flexible position independent argspec matches any position. That is, even if it precedes a numeric argspec, it can still match later arguments. -** Example**: Flexible argspecs match anywhere +** Example**: +Flexible argspecs match anywhere ```bash shellmock new git @@ -296,7 +315,8 @@ You _cannot_ combine it with the flexible position indicator `i`, though. With such an argspec, `value` will be re-interpreted as a _bash regular expression_ matched via the comparison `[[ ${argument} =~ ${value} ]]`. -** Example**: Regex-based argspecs +** Example**: +Regex-based argspecs ```bash shellmock new git @@ -318,7 +338,8 @@ It is very easy to input a character that is interpreted as a special one without realizing that. You can, of course, combine string and regex based argspecs. -** Example**: Combining string-based and regex-based argspecs +** Example**: +Combining string-based and regex-based argspecs ```bash shellmock new git @@ -332,7 +353,8 @@ git checkout -b feature/barbaz master -Syntax: `shellmock assert ` +Syntax: +`shellmock assert ` The `assert` command can be used to check whether expectations previously defined via the `config` command have been fulfilled for a mock or not. @@ -342,7 +364,8 @@ We recommend to always use `expectations` as assertion type. -** Example**: Asserting expectations +** Example**: +Asserting expectations ```bash shellmock assert expectations git @@ -448,7 +471,8 @@ Use `shellmock global-config getval killparent` to retrieve the current setting. -Syntax: `shellmock calls [--plain|--json]` +Syntax: +`shellmock calls [--plain|--json]` The `calls` command retrieves information about past calls to your mock. The `calls` command is useful when developing mocks. diff --git a/generate_deployable.sh b/generate_deployable.sh index 5077b07..d6f8aa5 100755 --- a/generate_deployable.sh +++ b/generate_deployable.sh @@ -46,7 +46,7 @@ __shellmock__help() { This is shellmock, a tool to mock executables called within shell scripts. ENDOFFILE - awk \ + gawk \ -v start='' \ -v end='' \ 'BEGIN{act=0} {if($0==end){act=0}; if(act==1){print}; if($0==start){act=1;};}' \ diff --git a/lib/mock_management.bash b/lib/mock_management.bash index 71780ee..2d0c5b8 100644 --- a/lib/mock_management.bash +++ b/lib/mock_management.bash @@ -52,8 +52,10 @@ __shellmock_assert_no_duplicate_argspecs() { fi done if [[ ${#duplicate_arg_indices[@]} -gt 0 ]]; then - echo >&2 "Multiple arguments specified for the following indices," \ - "cannot continue: ${!duplicate_arg_indices[*]}" + local dups + dups=$(printf '%s\n' "${!duplicate_arg_indices[@]}" | sort -n | tr '\n' ' ') + echo >&2 "Multiple arguments specified for the following indices, cannot" \ + "continue: ${dups}" return 1 fi } @@ -183,7 +185,7 @@ __shellmock__assert() { fi done < <( find "${__SHELLMOCK_OUTPUT}/${cmd_b32}" -mindepth 2 -type f -name stderr - ) + ) && wait $! if [[ ${has_err} -ne 0 ]]; then echo >&2 "SHELLMOCK: got at least one unexpected call for mock ${cmd}." return 1 @@ -196,17 +198,18 @@ __shellmock__assert() { call-correspondence) declare -a actual_argspecs mapfile -t actual_argspecs < <( - [[ -d "${__SHELLMOCK_OUTPUT}/${cmd_b32}" ]] \ - && find "${__SHELLMOCK_OUTPUT}/${cmd_b32}" -mindepth 2 -type f \ + if [[ -d "${__SHELLMOCK_OUTPUT}/${cmd_b32}" ]]; then + find "${__SHELLMOCK_OUTPUT}/${cmd_b32}" -mindepth 2 -type f \ -name argspec -print0 | xargs -0 cat | sort -u - ) + fi + ) && wait $! declare -a expected_argspecs mapfile -t expected_argspecs < <( env | sed 's/=.*$//' \ | grep -x "MOCK_ARGSPEC_BASE64_${cmd_b32}_[0-9][0-9]*" \ | sort -u - ) + ) && wait $! local has_err=0 for argspec in "${expected_argspecs[@]}"; do @@ -285,7 +288,7 @@ __shellmock__calls() { readarray -d $'\n' -t call_ids < <( find "${__SHELLMOCK_OUTPUT}/${cmd_b32}" -mindepth 1 -maxdepth 1 -type d \ | sort -n - ) + ) && wait $! for call_idx in "${!call_ids[@]}"; do local call_id="${call_ids[${call_idx}]}" diff --git a/lib/shellmock.bash b/lib/shellmock.bash index d71a997..d77076a 100644 --- a/lib/shellmock.bash +++ b/lib/shellmock.bash @@ -23,6 +23,10 @@ # installed on the system. It also sets some global, internal configurations to # their default values. __shellmock_internal_init() { + # Check minimum required bash version. + if ! __shellmock_internal_bash_version_check; then + return 1 + fi local has_bats=1 if [[ -z ${BATS_RUN_TMPDIR} ]]; then has_bats=0 @@ -55,6 +59,23 @@ __shellmock_internal_init() { declare -gx __SHELLMOCK__KILLPARENT=1 } +__shellmock_internal_bash_version_check() { + if [[ -z ${BASH_VERSION} ]]; then + echo >&2 "Shellmock requires bash but different shell detected." + return 1 + fi + local major minor + major="${BASH_VERSION%%.*}" + minor="${BASH_VERSION#*.}" + minor="${minor%%.*}" + + # Error out if the version is too low. + if [[ ${major} -lt 4 ]] || [[ ${major} -eq 4 && ${minor} -lt 4 ]]; then + echo >&2 "Shellmock requires bash >= 4.4 but ${BASH_VERSION} detected." + return 1 + fi +} + # Check whether PATH changed since shellmock has been initialised. If it has # changed, the shellmock's mocks might no longer be used preferentially. __shellmock_internal_pathcheck() { diff --git a/tests/main.bats b/tests/main.bats index 3b2ac93..e18d317 100644 --- a/tests/main.bats +++ b/tests/main.bats @@ -471,6 +471,29 @@ EOF run ! shellmock config my_exe 0 2:two 1:one i:three regex-1:another-one local expected="Multiple arguments specified for the following \ -indices, cannot continue: 2 1" +indices, cannot continue: 1 2 " [[ ${output} == "${expected}" ]] } + +@test "refusing to work with shells other than bash" { + if output=$( + unset BASH_VERSION + load ../shellmock 2>&1 + ); then + echo >&2 "Expected failure to load shellmock." + exit 1 + else + expected="Shellmock requires bash but different shell detected." + grep -x "${expected}" <<< "${output}" + fi +} + +@test "refusing to work with old bash versions" { + if output=$(BASH_VERSION=1.2 load ../shellmock 2>&1); then + echo >&2 "Expected failure to load shellmock." + exit 1 + else + expected="Shellmock requires bash >= 4.4 but 1.2 detected." + grep -x "${expected}" <<< "${output}" + fi +}