Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

### Fixed

- `init` writes the bootstrap path and a custom tests directory to `.env` quoted, so a directory with a space runs afterwards instead of breaking every next run (#1411)
- Failure source context stays inside a one-line test instead of listing the next function's assertions (#1420)
- Parallel runs count tests under relative hidden paths and keep their results separate from matching visible paths (#1393)

Expand Down
4 changes: 2 additions & 2 deletions docs/command-line.md
Original file line number Diff line number Diff line change
Expand Up @@ -1603,9 +1603,9 @@ Creates:
- `tests/bootstrap.sh` - Setup file for test configuration
- `tests/example_test.sh` - Sample test file to get started
- `.github/workflows/tests.yml` - CI workflow using the official action
- `.env` with `BASHUNIT_BOOTSTRAP=tests/bootstrap.sh`
- `.env` with `BASHUNIT_BOOTSTRAP="tests/bootstrap.sh"`, plus `BASHUNIT_DEFAULT_PATH="<dir>"` for a custom directory so a bare `bashunit` runs it

An existing `BASHUNIT_BOOTSTRAP=` line in `.env` is commented out first, so the new value
An existing `BASHUNIT_BOOTSTRAP=` or `BASHUNIT_DEFAULT_PATH=` line in `.env` is commented out first, so the new value
takes effect.

## upgrade
Expand Down
64 changes: 41 additions & 23 deletions src/cli/init.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
#!/usr/bin/env bash

# Appends `line` to .env, leaves the file alone when it already holds it, and
# comments an older, different `KEY=` out first. Commenting the line out and
# appending an identical one made every re-init add a dead line, so a third
# run left three (#1175); -Fx so a dot in the path is compared literally.
function bashunit::init::write_env_line() {
local env_file=".env"
local line=$1
local key=${line%%=*}
if [ ! -f "$env_file" ]; then
printf '%s\n' "$line" >"$env_file"
echo "> Created $env_file"
return
fi
if grep -Fxq "$line" "$env_file"; then
echo "> $env_file already sets $key"
return
fi
if grep -q "^$key=" "$env_file"; then
if bashunit::check_os::is_macos; then
sed -i '' -e "s/^$key=/#&/" "$env_file"
else
sed -i -e "s/^$key=/#&/" "$env_file"
fi
fi
printf '%s\n' "$line" >>"$env_file"
echo "> Updated $env_file"
}

function bashunit::init::project() {
local tests_dir="${1:-$BASHUNIT_DEFAULT_PATH}"
mkdir -p "$tests_dir"
Expand Down Expand Up @@ -47,29 +75,19 @@ SH
echo "> Created $workflow_file"
fi

local env_file=".env"
local env_line="BASHUNIT_BOOTSTRAP=$bootstrap_file"
if [ -f "$env_file" ]; then
# Already pointing where this run would point it: leave the file alone.
# Commenting the line out and appending an identical one made every re-init
# add a dead line, so a third run left three (#1175). -Fx so a dot in the
# path is compared literally.
if grep -Fxq "$env_line" "$env_file"; then
echo "> $env_file already sets BASHUNIT_BOOTSTRAP"
else
if grep -q "^BASHUNIT_BOOTSTRAP=" "$env_file"; then
if bashunit::check_os::is_macos; then
sed -i '' -e "s/^BASHUNIT_BOOTSTRAP=/#&/" "$env_file"
else
sed -i -e "s/^BASHUNIT_BOOTSTRAP=/#&/" "$env_file"
fi
fi
echo "$env_line" >>"$env_file"
echo "> Updated $env_file"
fi
else
echo "$env_line" >"$env_file"
echo "> Created $env_file"
# .env is sourced as shell, so values are written double-quoted and the
# characters that are special inside double quotes are escaped: a directory
# with a space stays one assignment instead of turning into a command.
local quoted="$tests_dir"
quoted=${quoted//\\/\\\\}
quoted=${quoted//\"/\\\"}
quoted=${quoted//\$/\\\$}
quoted=${quoted//\`/\\\`}
bashunit::init::write_env_line "BASHUNIT_BOOTSTRAP=\"$quoted/bootstrap.sh\""
# A custom directory is otherwise invisible to the next bare `bashunit`,
# which searches BASHUNIT_DEFAULT_PATH ("tests").
if [ "$tests_dir" != "$BASHUNIT_DEFAULT_PATH" ]; then
bashunit::init::write_env_line "BASHUNIT_DEFAULT_PATH=\"$quoted\""
fi

echo "> bashunit initialized in $tests_dir"
Expand Down
18 changes: 17 additions & 1 deletion tests/acceptance/bashunit_init_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,26 @@ function test_bashunit_init_updates_env() {
assert_file_exists "custom/example_test.sh"
assert_file_exists "custom/bootstrap.sh"
assert_file_contains .env "#BASHUNIT_BOOTSTRAP=old/bootstrap.sh"
assert_file_contains .env "BASHUNIT_BOOTSTRAP=custom/bootstrap.sh"
assert_file_contains .env 'BASHUNIT_BOOTSTRAP="custom/bootstrap.sh"'
popd >/dev/null
}

# A directory with a space is a valid project directory. `.env` is sourced as
# shell, so an unquoted BASHUNIT_BOOTSTRAP turns the second word into a command
# and the next run dies with "No such file or directory" naming a path the user
# never typed.
function test_bashunit_init_with_a_space_in_the_directory_runs_afterwards() {
pushd "$TMP_DIR" >/dev/null
"$BASHUNIT_PATH" init "my tests" >/dev/null

local output
output="$($BASHUNIT_PATH --no-color 2>&1)"
popd >/dev/null

assert_not_contains "No such file or directory" "$output"
assert_contains "1 passed" "$output"
}

# `bashunit init` is run more than once in practice -- re-scaffolding, or a
# setup script that is not guarded. The files it writes are idempotent; .env
# was not: every run commented out the previous BASHUNIT_BOOTSTRAP and appended
Expand Down
Loading