Skip to content

Commit 60ed5d7

Browse files
committed
More changes towards minor release 0.9.0
1 parent 28816fd commit 60ed5d7

14 files changed

+259
-119
lines changed

.github/workflows/test-lint-deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ jobs:
4141
run: |
4242
sudo apt-get update -qq
4343
sudo apt-get install -qqy --no-install-recommends \
44-
build-essential ca-certificates coreutils curl gawk gcc git jq \
44+
build-essential ca-certificates coreutils curl gcc git jq \
4545
kcov make parallel shellcheck
4646
4747
- name: Install bats

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,14 @@ The following tools are needed to use `shellmock`:
5050
- `bash` (at least version 4.4)
5151
- `cat`
5252
- `chmod`
53-
- `flock`
5453
- `mkdir`
5554
- `mktemp`
5655
- `rm`
5756

5857
On Debian-based systems, they can be installed via:
5958

6059
```bash
61-
sudo apt install -yqq bash coreutils util-linux
60+
sudo apt install -yqq bash coreutils
6261
```
6362

6463
You also need the [bats-core] testing framework that
@@ -69,6 +68,8 @@ the version installable via `npm` is up to date.
6968

7069
To run the [`commands` command](./docs/usage.md#commands), you also need a
7170
[Golang][golang] toolchain.
71+
For optimal performance, install `flock`, which is contained within the
72+
`util-linux` package on Debian-based systems.
7273

7374
[bats-npm-install]: https://bats-core.readthedocs.io/en/stable/installation.html#any-os-npm
7475
[golang]: https://go.dev/doc/install

bin/mock_exe.sh

Lines changed: 100 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -28,57 +28,112 @@
2828

2929
# Check whether required environment variables are set.
3030
env_var_check() {
31-
local var
32-
for var in __SHELLMOCK_MOCKBIN __SHELLMOCK_FUNCSTORE __SHELLMOCK_OUTPUT; do
33-
if ! [[ -d ${!var-} ]]; then
34-
echo >&2 "Vairable ${var} not defined or no directory."
35-
exit 1
31+
local var dir
32+
local vars=(
33+
__SHELLMOCK_ORGPATH
34+
)
35+
local dirs=(
36+
__SHELLMOCK_MOCKBIN
37+
__SHELLMOCK_FUNCSTORE
38+
__SHELLMOCK_OUTPUT
39+
)
40+
for dir in "${dirs[@]}"; do
41+
if ! [[ -d ${!dir-} ]]; then
42+
echo >&2 "Variable ${dir} not defined or no directory."
43+
_kill_parent
3644
fi
3745
done
46+
for var in "${vars[@]}"; do
47+
if [[ -z ${!var-} ]]; then
48+
echo >&2 "Variable ${var} not defined."
49+
_kill_parent
50+
fi
51+
done
52+
}
53+
54+
# Remove shellmock's mockbin directory from PATH. We do so by using the env var
55+
# set by the main shellmock code.
56+
rm_mock_path() {
57+
export PATH="${__SHELLMOCK_ORGPATH}"
58+
}
59+
60+
# Make sure that we can find all the executables we need.
61+
binary_deps_check() {
62+
local has_err=0
63+
local cmd
64+
for cmd in base32 cat mkdir; do
65+
if ! command -v "${cmd}" &> /dev/null; then
66+
echo >&2 "Required executable ${cmd} not found."
67+
has_err=1
68+
fi
69+
done
70+
if ! command -v flock &> /dev/null; then
71+
echo >&2 "SHELLMOCK: Optional executable flock not found." \
72+
"Please install for best performance."
73+
fi
74+
if [[ ${has_err} -ne 0 ]]; then
75+
_kill_parent
76+
return 1
77+
fi
78+
return 0
3879
}
3980

4081
get_and_ensure_outdir() {
4182
local cmd_b32="$1"
4283

43-
local max_num_calls=${SHELLMOCK_MAX_CALLS_PER_MOCK:-1000}
84+
local max_num_calls=${SHELLMOCK_MAX_CALLS_PER_MOCK:-100}
4485
if ! [[ ${max_num_calls} =~ ^[0-9][0-9]*$ ]]; then
4586
echo >&2 "SHELLMOCK_MAX_CALLS_PER_MOCK must be a number."
46-
_kill_parent "${PPID}"
87+
_kill_parent
4788
return 1
4889
fi
4990
local tmp
5091
tmp=$((max_num_calls - 1))
5192
local max_digits=${#tmp}
5293

94+
# shellmock: uses-command=flock
95+
local _flock=flock
96+
if
97+
! command -v flock &> /dev/null \
98+
|| [[ ${__SHELLMOCK_TESTING_WO_FLOCK-0} -eq 1 ]]
99+
then
100+
_flock=true
101+
fi
102+
53103
# Ensure no two calls overwrite each other in a thread-safe way.
54-
local count
55-
count=$(printf "%0${max_digits}d" "0")
56-
local outdir="${__SHELLMOCK_OUTPUT}/${cmd_b32}/${count}"
104+
local padded count=0
105+
padded=$(printf "%0${max_digits}d" "${count}")
106+
mkdir -p "${__SHELLMOCK_OUTPUT}/${cmd_b32}"
107+
local outdir="${__SHELLMOCK_OUTPUT}/${cmd_b32}/${padded}"
57108
while ! (
58109
# Increment the counter until we find one that has not been used before.
59-
flock -n 9 || exit 1
60-
[[ -d ${outdir} ]] && exit 1
61-
mkdir -p "${outdir}"
110+
"${_flock}" -n 9 || exit 1
111+
mkdir "${outdir}" &> /dev/null
62112
) 9> "${__SHELLMOCK_OUTPUT}/lockfile_${cmd_b32}_${count}"; do
63-
count=$(printf "%0${max_digits}d" "$((count + 1))")
64-
outdir="${__SHELLMOCK_OUTPUT}/${cmd_b32}/${count}"
113+
count=$((count + 1))
114+
padded=$(printf "%0${max_digits}d" "${count}")
115+
outdir="${__SHELLMOCK_OUTPUT}/${cmd_b32}/${padded}"
116+
117+
if [[ ${count} -ge ${max_num_calls} ]]; then
118+
echo >&2 "The maximum number of calls per mock is ${max_num_calls}." \
119+
"Consider increasing SHELLMOCK_MAX_CALLS_PER_MOCK, which is currently" \
120+
"set to '${max_num_calls}'."
121+
_kill_parent
122+
return 1
123+
fi
65124
done
66125

67-
if [[ ${count} -ge ${max_num_calls} ]]; then
68-
echo >&2 "The maximum number of calls per mock is ${max_num_calls}." \
69-
"Consider increasing SHELLMOCK_MAX_CALLS_PER_MOCK, which is currently" \
70-
"set to '${max_num_calls}'."
71-
_kill_parent "${PPID}"
72-
return 1
73-
fi
74-
75126
echo "${outdir}"
76127
}
77128

78129
# When called, this script will write its own errors to a file so that they can
79130
# be retrieved later when asserting expectations.
80131
errecho() {
81-
echo >> "${STDERR}" "$@"
132+
if [[ -n ${STDERR} ]]; then
133+
echo >> "${STDERR}" "$@"
134+
else
135+
echo >&2 "$@"
136+
fi
82137
}
83138

84139
output_args_and_stdin() {
@@ -103,6 +158,7 @@ _find_arg() {
103158
shift
104159
local args=("$@")
105160

161+
local check
106162
for check in "${args[@]}"; do
107163
if [[ ${arg} == "${check}" ]]; then
108164
return 0
@@ -117,6 +173,7 @@ _find_regex_arg() {
117173
shift
118174
local args=("$@")
119175

176+
local check
120177
for check in "${args[@]}"; do
121178
if [[ ${check} =~ ${regex} ]]; then
122179
return 0
@@ -135,8 +192,8 @@ _match_spec() {
135192
local spec
136193
while read -r spec; do
137194
local id val
138-
read -r -d ':' id <<< "${spec}"
139-
val="${spec##"${id}":}"
195+
id="${spec%%:*}"
196+
val="${spec#*:}"
140197

141198
if [[ ${spec} =~ ^any: ]]; then
142199
if ! _find_arg "${val}" "$@"; then
@@ -183,7 +240,7 @@ _is_bats_process() {
183240
}
184241

185242
_kill_parent() {
186-
local parent="$1"
243+
local parent="${PPID}"
187244

188245
# Do not kill the parent process if it is a bats process. If we did, bats
189246
# would no longer be able to track the test.
@@ -224,7 +281,7 @@ find_matching_argspec() {
224281
) && wait $! || return 1
225282

226283
errecho "SHELLMOCK: unexpected call '${cmd} $*'"
227-
_kill_parent "${PPID}"
284+
_kill_parent
228285
return 1
229286
}
230287

@@ -254,7 +311,7 @@ run_hook() {
254311
# output. Anything output via errecho will end up in a file that is only
255312
# looked at when asserting expectations.
256313
echo >&2 "SHELLMOCK: error calling hook '${!hook_env_var}'"
257-
_kill_parent "${PPID}"
314+
_kill_parent
258315
return 1
259316
fi
260317
fi
@@ -291,21 +348,25 @@ forward() {
291348
shift
292349
local args=("$@")
293350

294-
while read -r -d: path; do
295-
if
296-
[[ ${path} != "${__SHELLMOCK_MOCKBIN}" ]] \
297-
&& PATH="${path}" command -v "${cmd}" &> /dev/null
298-
then
299-
local exe="${path}/${cmd}"
300-
echo >&2 "SHELLMOCK: forwarding call: ${exe} $*"
301-
exec "${exe}" "${args[@]}"
302-
fi
303-
done <<< "${__SHELLMOCK_FUNCSTORE}:${PATH}"
351+
local exe
352+
local path="${__SHELLMOCK_FUNCSTORE}:${PATH}"
353+
# Extend PATH by shellmock's funcstore because we may want to forward to a
354+
# function instead of a binary.
355+
if
356+
! exe=$(PATH="${path}" command -v "${cmd}")
357+
then
358+
echo >&2 "SHELLMOCK: failed to find executable to forward to: ${cmd}"
359+
_kill_parent
360+
fi
361+
echo >&2 "SHELLMOCK: forwarding call: ${exe@Q} ${*@Q}"
362+
exec "${exe}" "${args[@]}"
304363
}
305364

306365
main() {
307366
# Make sure that shell aliases never interfere with this mock.
308367
unalias -a
368+
rm_mock_path
369+
binary_deps_check
309370
env_var_check
310371
# Determine our name. This assumes that the first value in argv is the name of
311372
# the command. This is almost always so.

docs/build.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@
2020

2121
Simply run the script `./generate_deployable.sh` at the top level of this
2222
repository to generate `shellmock.bash`.
23-
It uses only the standard Unix tools `bash` and `cat` to generate it.
23+
It uses only `bash` to generate it.

generate_deployable.sh

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@
2222

2323
__SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/" &> /dev/null && pwd)"
2424

25+
_cat() {
26+
(
27+
local line
28+
IFS=
29+
while read -d $'\n' -r line; do
30+
printf -- '%s\n' "${line}"
31+
done
32+
)
33+
}
34+
2535
deployable() {
2636
# Output header including the licence file.
2737
echo '#!/bin/bash'
@@ -31,7 +41,7 @@ deployable() {
3141
echo "# ${line}"
3242
done < LICENSE
3343
)
34-
cat << 'EOF'
44+
_cat << 'EOF'
3545
3646
# This file is auto-generated. It is the main deployable of shellmock. To make
3747
# contributions to shellmock, please visit the repository under:
@@ -41,17 +51,20 @@ EOF
4151
# Output all bats helper files containing function definitions.
4252
for bats_file in lib/*.bash; do
4353
printf -- "\n# FILE: %s\n" "${bats_file}"
44-
cat "${bats_file}"
54+
_cat < "${bats_file}"
4555
done
4656

4757
# Create a function providing the help text.
48-
cat << 'ENDOFFILE'
58+
_cat << 'ENDOFFILE'
4959
__shellmock__help() {
5060
"${PAGER-cat}" << 'EOF'
5161
This is shellmock, a tool to mock executables called within shell scripts.
5262
ENDOFFILE
5363

64+
# Add helptexts from the usage docs, but only a reduced version. The docs are
65+
# enclosed by the HTML comments given below.
5466
(
67+
local line
5568
IFS=
5669
do_print=0
5770
while read -r line; do
@@ -67,22 +80,22 @@ ENDOFFILE
6780
done < ./docs/usage.md
6881
)
6982

70-
cat << 'ENDOFFILE'
83+
_cat << 'ENDOFFILE'
7184
EOF
7285
}
7386
ENDOFFILE
7487

7588
# Create a function that outputs the mock executable to its stdout.
76-
cat << 'EOF'
89+
_cat << 'EOF'
7790
7891
# Mock executable writer.
7992
__shellmock_write_mock_exe() {
8093
EOF
8194

82-
echo "cat << 'ENDOFFILE'"
83-
cat ./bin/mock_exe.sh
95+
echo "PATH=\"\${__SHELLMOCK_ORGPATH}\" cat << 'ENDOFFILE'"
96+
_cat < ./bin/mock_exe.sh
8497

85-
cat << 'EOF'
98+
_cat << 'EOF'
8699
ENDOFFILE
87100
}
88101
@@ -91,17 +104,17 @@ __shellmock_internal_init_command_search() {
91104
local path=$1
92105
EOF
93106

94-
echo "cat > \"\${path}/go.mod\" << 'ENDOFFILE'"
95-
cat ./go/go.mod
107+
echo "PATH=\"\${__SHELLMOCK_ORGPATH}\" cat > \"\${path}/go.mod\" << 'ENDOFFILE'"
108+
_cat < ./go/go.mod
96109

97-
cat << 'EOF'
110+
_cat << 'EOF'
98111
ENDOFFILE
99112
EOF
100113

101-
echo "cat > \"\${path}/main.go\" << 'ENDOFFILE'"
102-
cat ./go/main.go
114+
echo "PATH=\"\${__SHELLMOCK_ORGPATH}\" cat > \"\${path}/main.go\" << 'ENDOFFILE'"
115+
_cat < ./go/main.go
103116

104-
cat << 'EOF'
117+
_cat << 'EOF'
105118
ENDOFFILE
106119
}
107120

lib/command_commands.bash

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ __shellmock__commands() {
4141
shift
4242
done
4343

44-
if ! command -v go &> /dev/null; then
44+
if ! PATH="${__SHELLMOCK_ORGPATH}" command -v go &> /dev/null; then
4545
echo >&2 "The 'commands' command requires a Go toolchain." \
4646
"Get it from here: https://go.dev/doc/install"
4747
return 1
@@ -52,13 +52,15 @@ __shellmock__commands() {
5252
return 1
5353
fi
5454
local code
55-
code="$(cat -)"
55+
code="$(PATH="${__SHELLMOCK_ORGPATH}" cat -)"
5656

5757
# Build the binary used to analyse the shell code.
5858
local bin="${__SHELLMOCK_GO_MOD}/main"
5959
if ! [[ -x ${bin} ]]; then
6060
__shellmock_internal_init_command_search "${__SHELLMOCK_GO_MOD}"
61-
(cd "${__SHELLMOCK_GO_MOD}" && go get && go build) 1>&2
61+
(cd "${__SHELLMOCK_GO_MOD}" \
62+
&& PATH="${__SHELLMOCK_ORGPATH}" go get \
63+
&& PATH="${__SHELLMOCK_ORGPATH}" go build) 1>&2
6264
fi
6365

6466
declare -A builtins

0 commit comments

Comments
 (0)