Context
While setting up a basic unit testing system, I ran into an odd issue. My goal was to make sure all individual test scripts:
- were run with
set -e
to detect errors, without needing to explicitly set this in each file; - knew right away about the functions to be tested (stored in another file) without needing to explicitly source those in each test file.
Observations
Let this be a dummy test file called to-be-sourced.sh
. We want to be able to know if a command in it fails:
# Failing command!
false
# Last command is OK:
true
And here is a dummy test runner, which must run the test file:
#! /usr/bin/env bash
if (
set -e
. to-be-sourced.sh
)
then
echo 'Via set: =0'
else
echo 'Via set: ≠0'
fi
This yields Via set: =0
, meaning that the runner is happy. But it should not!
My hypothesis was:
set -e
is not propagated within.
sourcing, and as explained in the help for.
andsource
, the exit status is the one of the last command.
But then I came up with a workaround that works, but also relies on .
:
if bash -ec '. "$0"' to-be-sourced.sh
then
echo 'Via bash: =0'
else
echo 'Via bash: ≠0'
fi
This yields ≠0
whenever a command in the test file fails, regardless of whether that command was the last one of the test file. As a bonus, I can toss any number of . a/library/file.sh
within the -c
command, so each test file can use all of my functions out of the box. I should therefore be happy, but:
Why does this work, considering that the -c
command also relies on .
to load the test file (and I thought bash
’s -e
was equivalent to set
’s -e
)?
I also thought about using bash
’s --init-file
, but it appeared to be skipped when a script is passed as a parameter. And anyway my question is not so much about what I was trying to achieve, but rather about the observed difference of behavior.
Edit
Sounds like if
is tempering with the way set -e
is handled.
This halts execution, indicating failure:
. to-be-sourced.sh
… while this goes into the then
(not the else
), indicating success:
if . to-be-sourced.sh
then
echo =0
else
echo ≠0
fi
CodePudding user response:
(This may not be precisely correct, but I think it captures what happens.)
In your first example, set -e
sets the option in a command that is lexically in the scope of an if
statement, and so even though it is set, it is ignored. (You can confirm it is set by running echo $-
inside to-be-sourced.sh
. Note, too, that .
itself has a 0 exit status, which you can confirm by replacing true
with an echo
statement; it's not that it fails but the failure is ignored.)
In your second example, -e
sets the errexit
option in a new process, which knows nothing about the if
statement and therefore it is not ignored.