This is part nine of a series on how to approach bash programming in a way that’s safer and more structured than your basic script.

See part 1 if you want to catch the series from the start.

Last time, we factored our support function into a library and sourced it. We forgot something, however…there’s no test for the sourced function! Let’s fix that now.

shpec/support_shpec.bash:

export TMPDIR=${TMPDIR:-$HOME/tmp}
mkdir -p "$TMPDIR"

support_lib=$(dirname "$(readlink -f "$BASH_SOURCE")")/../lib/support.bash

describe sourced
  it "returns true when in a file being sourced"
    file=$(mktemp) || return
    printf 'source "%s"\nsourced' "$support_lib"  >"$file"
    (source "$file")
    assert equal 0 $?
    rm "$file"
  end

  it "returns false when that file is run"
    file=$(mktemp) || return
    printf 'source "%s"\nsourced' "$support_lib" >"$file"
    chmod 775 "$file"
    "$file"
    assert unequal 0 $?
    rm "$file"
  end
end

Shpec output:

> shpec shpec/support_shpec.bash
sourced
  returns true when in a file being sourced
  returns false when that file is run
2 examples, 0 failures
0m0.000s 0m0.000s
0m0.000s 0m0.000s

This one is quite a bit trickier than our earlier tests. The function we want to test, sourced, has to be in another file, not this test file.

Since we’ll be creating that file, we’ll take advantage of unix’s mktemp command, which will create a file in a temporary location. When we’re done with it, we’ll use rm to remove the file.

One bit of preparation at the top of the file is to pin down the location of the support.bash file explicitly, since the temporary file won’t have the benefit of a fixed location relative to it.

Let’s look at the first test, piece by piece:

file=$(mktemp) || return

Here we make the a test file. If it happens to fail, we bail here, returning the error code.

printf 'source "%s"\nsourced' "$support_lib" >"$file"

This line creates the content of the file which will be sourced, importing and calling the sourced function.

(source "$file")

Here we source the file. On principle, we’re using a subshell which is what the surrounding parentheses are. The subshell creates a new context, kind of like a sandbox.

Anything that would normally come into this shell’s namespace from sourcing a file will now go into the subshell’s. When the subshell ends, those changes will go away and not pollute the current shell’s namespace.

The return code from the source will be the return code of the subshell, so the following line will still test the correct value:

assert equal 0 $?

Since all the file does is return the value returned by sourced, it should be 0, which is bash’s ok return value. $? refers to the return value of the last command.

rm "$file"

Now we clean up the file.

The second test works the same as the first, but sets the file executable and runs it instead. This time we expect false from sourced, which is why the assert tests for $? to be unequal to zero.

Also, since running a file automatically creates its own shell instance, we don’t need to manually create a subshell context for it.

One last thing that I’ll note about the sourced function is that it can only be called in the top-level body of a script, not from inside a function. If it appears inside a function, that function’s name will be the name it tests against “source”, and it will return the wrong result.

Continue with part 10 - test independence