`env: 'bash': No such file or directory`
Here's a commit that contains the code for the following output. For reproducibility, include the current bach.sh (3db30af).
$ ./er317_clone_test.sh
1..1
not ok 1 - clone
env: 'bash': No such file or directory
' EXIT
export unique_dir=/tmp/tmp.iR7GSRCV41/unique
export trash_dir=/tmp/tmp.iR7GSRCV41/trash
Assert Failed:
Expected: 0
But got: 1
# -----
# All tests: 1, failed: 1, skipped: 0
Sharing this in case it helps improve the robustness of the framework.
Hi @rogard
Thank you for your suggestion!
I think puttting source bach.sh in every test case file is OK. It's very common in other programming languages, like in Java, every test file has import org.junit.* at the begining. So, source bach.sh is like that.
I agree that having a test runner, like bach.sh your-script.test.sh can be useful. But for now, we can run the test by bash your-script.test.sh. I might implement this feature in the future.
I write some test cases for your script, and I want to explain a few things.
- About checking if files/directories exist
In your script, you use
[[ -f path/to/file ]]to check if a file exists. In Bach, it's better to use[ -f path/to/file ]ortest -f path/to/file. The reason is that[[ ... ]]is a shell keyword and cannot be mocked. But[ ... ]andtestare commands can be mocked in tests. - About simulation user input
In your script, you use
readcommand to get user input. In the test, we can use@stdout y yto simulate user inputs. - About env vars and command mocking
The script uses the env var
HOME, we must set it manually. There are also threesedcomands that need to run for real. We can allow them with@allow-realAPI. I also mocked all the necessary commands that are called in the script in the@setup-test. This helps making write tests easier. In each test case, we only need to change the mock for a command if we want a different result.
Patch:
--- a/er317_clone.sh 2025-07-23 11:40:35.648572148 +0800
+++ b/er317_clone.sh 2025-07-23 11:06:36.608360435 +0800
@@ -120,10 +120,10 @@
fi
-[[ -f "${source_file}" ]] || {
+test -f "${source_file}" || {
msg_fun "$?" "$source_file is not a file"
exit 1
}
-if [[ ! -d "$unique_dir" ]]; then
+if test ! -d "$unique_dir"; then
msg='Create unique directory %s? [y/n] '
read -p "$(printf "$msg" "$unique_dir")" answer
@@ -144,5 +144,5 @@
fi
-if [[ ! -f "$unique_log" ]]; then
+if test ! -f "$unique_log"; then
msg='Create unique log %s? [y/n] '
read -p "$(printf "$msg" "$unique_log")" answer
Test er317_clone.test.sh:
#!/usr/bin/env bash
script_dir=$(realpath $(dirname "${BASH_SOURCE[0]}"))
source "$script_dir"/bach.sh
@setup-test {
HOME=/home/bach
@allow-real sed 's/^fallback_msg_fun/msg_fun/'
@allow-real sed 's/^fallback_recurse_fun/recurse_fun/'
@allow-real sed 's/^fallback_unique_fun/unique_fun/'
@allow-real basename trash.me
@mocktrue test -f trash.me
@mocktrue test ! -d /home/bach/unique
@mocktrue mkdir -p /home/bach/unique
@mocktrue test ! -f /home/bach/.local/share/unique.log
@mocktrue touch /home/bach/.local/share/unique.log
@mock mktemp -u === @stdout /tmp/tmp.aabbcc
@mock exec er317_clone.sh --dry-run=false --extension=me trash.me
@mock echo "/home/bach/unique/aabbcc.me"
}
test-clone() {
@stdout y y | @run er317_clone.sh trash.me
}
test-clone-assert() {
cp trash.me /home/bach/unique/aabbcc.me
mv trash.me /home/bach/.local/share/Trash/files/trash.me
@dryrun echo "/home/bach/unique/aabbcc.me"
}
test-clone-if-cannot-create-unique-dir() {
@mockfalse mkdir -p /home/bach/unique
@stdout y | @run er317_clone.sh trash.me
}
test-clone-if-cannot-create-unique-dir-assert() {
@false
}
test-clone-if-cannot-create-unique-log-file() {
@mockfalse touch /home/bach/.local/share/unique.log
@stdout y y | @run er317_clone.sh trash.me
}
test-clone-if-cannot-create-unique-log-file-assert() {
@false
}
test-clone-if-do-not-create-unique-log-file() {
@mockfalse touch /home/bach/.local/share/unique.log
@stdout y N | @run er317_clone.sh trash.me
}
test-clone-if-do-not-create-unique-log-file-assert() {
@false
}