shellspec
#!/bin/sh
#shellcheck disable=SC2004,SC2016
[ "$PPID" ] || { echo "Unsupported shell. (Bourne shell?)" >&2; exit 1; }
set -e -u -f
#shellcheck disable=SC3044
[ "${OIL_VERSION:-}" ] && shopt -s compat_array
if [ "${1:-}" = "-" ]; then
if [ $# -gt 1 ]; then shift; set -- "$0" "$@"; else set -- "$0"; fi
for i; do i="$i'" j=''
while [ "$i" ]; do j="$j${i%%\'*}'\''" && i=${i#*\'}; done
set -- "$@" "'${j%????}'" && shift
done
echo 'IFS= read -r shebang < "$0"'
echo 'case $shebang in \#\!*) shell=${shebang#??};; *) shell="";; esac'
echo 'exec $shell' "$@" '"$0" --pattern "*" "$@"'
echo 'exit 1'
exit 0
fi
export SHELLSPEC_VERSION='0.29.0-dev'
export SHELLSPEC_CWD="$PWD"
export SHELLSPEC_PATH=''
export SHELLSPEC_GRAMMAR_DSLS=''
export SHELLSPEC_GRAMMAR_DIRECTIVES=''
export SHELLSPEC_GRAMMAR_BLOCKS=''
export SHELLSPEC_DEFECT_SANDBOX=''
export SHELLSPEC_PATH_IS_READONLY=''
export SHELLSPEC_MSLEEP=''
export SHELLSPEC_BUILTIN_PRINTF=''
export SHELLSPEC_BUILTIN_PRINT=''
export SHELLSPEC_BUILTIN_TYPESETF=''
export SHELLSPEC_BUILTIN_READARRAY=''
export SHELLSPEC_SEEKABLE=''
export SHELLSPEC_READ_DELIM=''
export SHELLSPEC_STRING_CONCAT=''
export SHELLSPEC_TIME=''
export SHELLSPEC_LIST=''
export SHELLSPEC_COUNT_FILE=''
export SHELLSPEC_DEBUG_TRAP=''
export SHELLSPEC_INFILE=file
export SHELLSPEC_COVERAGE_SETUP=''
export SHELLSPEC_COVERAGE_SHELL_OPTIONS=''
export SHELLSPEC_KCOV_COMPATIBLE_SHELL=''
export SHELLSPEC_DEFECT_EMPTYPARAMS=''
export SHELLSPEC_DEFECT_READONLY=''
export SHELLSPEC_DEFECT_BUILTIN=''
export SHELLSPEC_DEFECT_REDEFINE=''
export SHELLSPEC_DEFECT_SHELLFLAG=''
export SHELLSPEC_DEFECT_ERREXIT=''
export SHELLSPEC_DEFECT_ZSHEXIT=''
export SHELLSPEC_DEFECT_BOSHEXIT=''
export SHELLSPEC_DEFECT_SUBSHELL=''
export SHELLSPEC_DEFECT_SETE=''
export SHELLSPEC_DEFECT_XTRACE=''
export SHELLSPEC_DEFECT_EXPORTP=''
export SHELLSPEC_DEFECT_SIGNAL=''
export SHELLSPEC_DEFECT_DEBUGXS=''
export SHELLSPEC_SHEBANG_MULTIARG=''
export SHELLSPEC_BUSYBOX_W32=''
export SHELLSPEC_SHOPT_AVAILABLE=''
export SHELLSPEC_FAILGLOB_AVAILABLE=''
export SHELLSPEC_NOMATCH_AVAILABLE=''
export SHELLSPEC_FDVAR_AVAILABLE=''
export SHELLSPEC_PATHSEP=":"
export SHELLSPEC_REPAIR=''
export SHELLSPEC_INFO=''
export SHELLSPEC_TTY=''
export SHELLSPEC_DEV_TTY="/dev/null"
export SHELLSPEC_XTRACEFD=''
export SHELLSPEC_XTRACEFD_VAR=''
export SHELLSPEC_CLONE_TYPE=''
export SHELLSPEC_PROC_VERSION='/proc/version'
export SHELLSPEC_NOEXEC_TMPDIR=''
export SHELLSPEC_ENV="env"
export SHELLSPEC_PRINTF="printf"
export SHELLSPEC_SLEEP="sleep"
export SHELLSPEC_TRAP="trap"
export SHELLSPEC_MV="mv"
export SHELLSPEC_CHMOD="chmod"
export SHELLSPEC_DATE="date"
export SHELLSPEC_RM="rm"
export SHELLSPEC_LS="ls"
export SHELLSPEC_SORT="sort"
export SHELLSPEC_FIND="find"
#shellcheck disable=SC2039,SC3028
export SHELLSPEC_HOSTNAME="${HOSTNAME:-localhost}"
export SHELLSPEC_COLOR=''
if [ ! "${NO_COLOR:-}" ] && { [ -t 1 ] || [ "${FORCE_COLOR:-}" ]; } then
SHELLSPEC_COLOR=1
fi
# Based on https://github.com/ko1nksm/readlinkf
# Changed the interpretation of symlinks to my preference (Change cd -P to cd)
readlinkf() {
[ ${1:+x} ] || return 1; p=$1; until [ _"${p%/}" = _"$p" ]; do p=${p%/}; done
[ -e "$p" ] && p=$1; [ -d "$1" ] && p=$p/; set 10 "$(pwd)" "${OLDPWD:-}"; PWD=
CDPATH="" cd "$2" && while [ "$1" -gt 0 ]; do set "$1" "$2" "$3" "${p%/*}"
[ _"$p" = _"$4" ] || { CDPATH="" cd "${4:-/}" || break; p=${p##*/}; }
[ ! -L "$p" ] && p=${PWD%/}${p:+/}$p && set "$@" "${p:-/}" && break
set $(($1-1)) "$2" "$3" "$p"; p=$(ls -dl "$p") || break; p=${p#*" $4 -> "}
done 2>/dev/null; cd "$2" && OLDPWD=$3 && [ ${5+x} ] && printf '%s\n' "$5"
}
self=$0
[ "${BASH_SOURCE:-}" ] && eval "self=\${BASH_SOURCE[0]}"
( eval "[ \"\${.sh.file:-}\" ]" ) 2>/dev/null && eval "self=\${.sh.file}"
if ! self=$(readlinkf "$self"); then
echo "Failed to detect shellspec real path." >&2
exit 1
fi
# shellspec path
export SHELLSPEC_SELF="$self"
export SHELLSPEC_ROOT="${SHELLSPEC_SELF%/*}"
export SHELLSPEC_LIB="$SHELLSPEC_ROOT/lib"
export SHELLSPEC_SUPPORT_BIN="$SHELLSPEC_LIB/support-bin.sh"
export SHELLSPEC_REPORTERLIB="$SHELLSPEC_LIB/libexec/reporter"
export SHELLSPEC_LIBEXEC="$SHELLSPEC_ROOT/libexec"
export SHELLSPEC_INSPECTION="$SHELLSPEC_LIBEXEC/shellspec-inspection.sh"
export SHELLSPEC_UNREADONLY_PATH="$SHELLSPEC_LIBEXEC/shellspec-unreadonly-path.sh"
# shellcheck source=lib/libexec/shellspec.sh
. "$SHELLSPEC_LIB/libexec/shellspec.sh"
# shellcheck source=lib/libexec/optparser/optparser.sh
. "$SHELLSPEC_LIB/libexec/optparser/optparser.sh"
export SHELLSPEC_UNIXTIME=''
unixtime SHELLSPEC_UNIXTIME
if ! check_semver "$SHELLSPEC_VERSION"; then
abort "SHELLSPEC_VERSION: Invalid version format" \
"(major.minor.patch[-pre][+build]): $SHELLSPEC_VERSION"
fi
# option parsing for change directory
{
dir='.'
while [ $# -gt 0 ]; do
case $1 in (-c?*)
opts=${1#??}
shift
case $# in
0) set -- -c -"$opts" ;;
*) set -- -c -"$opts" "$@" ;;
esac
esac
case $1 in
-C?*) dir=${1#??} ;;
-c | --chdir) dir='' ;;
--directory=*) dir=${1#*=} ;;
-C | --directory)
[ $# -lt 2 ] && abort "Requires an argument: $1"
dir=${2:-.}
shift ;;
*) break ;;
esac
shift
done
abort_chdir() { abort "Cannot change to '$1'${2:+: }${2:-}"; }
if [ ! "$dir" ]; then
[ $# -gt 0 ] || abort "There is no file or directory path"
case $1 in ([-+]?*)
abort "The file or directory path is required before the option '$1'"
esac
[ -f "$1" ] && dir=${1%/*} || dir=$1
fi
[ -e "${dir:-.}" ] || abort_chdir "$dir" 'No such file or directory'
[ -d "${dir:-.}" ] || abort_chdir "$dir" 'Not a directory'
cd "$dir" 2>/dev/null || {
error "$(cd "$dir" 2>&1)"
abort_chdir "$dir"
}
}
# project root path
export SHELLSPEC_PROJECT_ROOT="$PWD"
until [ -f "$SHELLSPEC_PROJECT_ROOT/.shellspec" ]; do
[ "$SHELLSPEC_PROJECT_ROOT" ] || break
SHELLSPEC_PROJECT_ROOT=${SHELLSPEC_PROJECT_ROOT%/*}
done
cd "${SHELLSPEC_PROJECT_ROOT:-.}"
abspath SHELLSPEC_PROJECT_ROOT "$SHELLSPEC_PROJECT_ROOT"
# option parsing
{
optparser parse_options error_message
error_message() {
error "$1${options_file:+" [$options_file]"}"
}
options_file() {
set -- "$1" "$params"
options_file=$1
read_options_file "$1" parse_options
if [ "$params" ]; then
abort "Cannot specify a specfile in the options file. [$options_file]"
fi
unset options_file
params=$2
}
enum_options_file options_file
[ $# -gt 0 ] && parse_options "$@"
if [ "$SHELLSPEC_OPTIONS" ]; then
[ -e "$SHELLSPEC_OPTIONS" ] || abort "Options file not found: $SHELLSPEC_OPTIONS"
options_file "$SHELLSPEC_OPTIONS"
fi
# Run inside docker container
if [ "${SHELLSPEC_DOCKER_IMAGE#:}" ]; then
case $SHELLSPEC_DOCKER_IMAGE in (:*)
SHELLSPEC_DOCKER_IMAGE="shellspec/runtime${SHELLSPEC_DOCKER_IMAGE}"
esac
set -- "$@" --docker :
cid=$(docker create --rm -it "$SHELLSPEC_DOCKER_IMAGE" shellspec "$@")
set -- --exclude spec --exclude .git
tar -C "$SHELLSPEC_ROOT" "$@" -c ./ | docker cp - "$cid:/bin/"
tar --exclude .git -c ./ | docker cp - "$cid:./"
exec docker start -ai "$cid"
exit
fi
eval "set -- $params"
}
case $SHELLSPEC_MODE in (init)
SHELLSPEC_PROJECT_ROOT=${SHELLSPEC_PROJECT_ROOT:-$SHELLSPEC_CWD}
helperdir=$SHELLSPEC_HELPERDIR
coveragedir=$SHELLSPEC_COVERAGEDIR
reportdir=$SHELLSPEC_REPORTDIR
esac
if [ ! "$SHELLSPEC_PROJECT_ROOT" ]; then
# DEPRECATED: Not a shellspec project directory (TODO: change from error to abort)
SHELLSPEC_PROJECT_ROOT=$SHELLSPEC_CWD
error 'Not a shellspec project directory (".shellspec" not found in any of the parent directories).'
warn 'IMPORTANT NOTES:'
warn ' In future releases, run from outside the project directory will not be allowed.'
warn ' Create a ".shellspec" or run shellspec from under the directory where the ".shellspec"'
warn ' is located. If you are executing specfile directly, the -c (--chdir) option is useful.'
warn ' Fallback to the previous behavior for compatibility.'
warn "Current project root directory: $SHELLSPEC_PROJECT_ROOT"
case $SHELLSPEC_EXECDIR in (@basedir*)
abort '--execdir @basedir is not supported (requires ".shellspec").'
esac
# abort
fi
# DEPRECATED: Remove loading the $HOME/.shellspec and remove the code here.
if [ -e "$HOME/.shellspec" ] && [ ! -e "$HOME/.shellspec-options" ]; then
if [ "$SHELLSPEC_PROJECT_ROOT" != "$HOME" ]; then
warn '"$HOME/.shellspec" has been deprecated. Use "$HOME/.shellspec-options" instead.'
fi
fi
abspath SHELLSPEC_HELPERDIR "$SHELLSPEC_HELPERDIR" "$SHELLSPEC_PROJECT_ROOT"
abspath SHELLSPEC_COVERAGEDIR "$SHELLSPEC_COVERAGEDIR" "$SHELLSPEC_PROJECT_ROOT"
abspath SHELLSPEC_REPORTDIR "$SHELLSPEC_REPORTDIR" "$SHELLSPEC_PROJECT_ROOT"
# project path
export SHELLSPEC_QUICK_FILE="$SHELLSPEC_PROJECT_ROOT/.shellspec-quick.log"
export SHELLSPEC_PROJECT_NAME="${SHELLSPEC_PROJECT_ROOT##*/}"
export SHELLSPEC_SPECDIR="$SHELLSPEC_HELPERDIR" # deprecated
export SHELLSPEC_BANNER_FILE="$SHELLSPEC_HELPERDIR/banner"
export SHELLSPEC_SUPPORT_BINDIR="$SHELLSPEC_HELPERDIR/support/bin"
export SHELLSPEC_PROFILER_REPORT="$SHELLSPEC_REPORTDIR/profiler.log"
export SHELLSPEC_KCOV_FILENAME="$SHELLSPEC_PROJECT_NAME [specfiles]"
opts=''
opts="$opts --include-path=."
opts="$opts --include-pattern=.sh"
opts="$opts --exclude-pattern=/.shellspec,/spec/,/coverage/,/report/"
opts="$opts --path-strip-level=1"
export SHELLSPEC_KCOV_COMMON_OPTS="${opts# } "
# temporary path
export SHELLSPEC_TMPDIR="${SHELLSPEC_TMPDIR%/}"
abspath SHELLSPEC_TMPDIR "$SHELLSPEC_TMPDIR" "$SHELLSPEC_CWD"
if [ ! -d "$SHELLSPEC_TMPDIR" ]; then
abort "Temporary directory '$SHELLSPEC_TMPDIR' does not exist or is not a directory."
fi
export SHELLSPEC_TMPBASE="$SHELLSPEC_TMPDIR/shellspec.$SHELLSPEC_UNIXTIME.$$"
export SHELLSPEC_TIME_LOG="$SHELLSPEC_TMPBASE/.shellspec-time.log"
export SHELLSPEC_PROFILER_LOG="$SHELLSPEC_TMPBASE/.shellspec-profiler.log"
export SHELLSPEC_DEPRECATION_LOGFILE="$SHELLSPEC_TMPBASE/.shellspec-deprecation.log"
export SHELLSPEC_PROFILER_SIGNAL="$SHELLSPEC_TMPBASE/.shellspec-profiler.signal"
export SHELLSPEC_REPORTER_PID="$SHELLSPEC_TMPBASE/.shellspec-reporter.pid"
export SHELLSPEC_KCOV_IN_FILE="$SHELLSPEC_TMPBASE/kcov/$SHELLSPEC_KCOV_FILENAME"
export SHELLSPEC_PRECHECKER_STATUS="$SHELLSPEC_TMPBASE/.shellspec-prechecker.status"
# shell detection
{
if [ "${SHELLSPEC_SHELL:-auto}" = "auto" ]; then
# shellcheck disable=SC2039,SC3047
if [ "$SHELLSPEC_KCOV" ] && ! (trap '' DEBUG) 2>/dev/null; then
for shell in sh bash ksh zsh :; do
"$shell" -c "trap '' DEBUG" 2>/dev/null && break
done
[ "$shell" = : ] && abort "Current shell is not compatible with Kcov."
warn "Current shell is not compatible with Kcov. Using '$shell' instead."
else
shell=$(current_shell "$0" "$$")
fi
if [ ! "$shell" ] && shell="sh"; then
warn "Failed to detect the current shell," \
"because the ps command does not exist or not compatible."
warn "Using 'sh' instead. You can specify the shell with --shell option."
fi
SHELLSPEC_SHELL=$shell
fi
if command_path shell "${SHELLSPEC_SHELL%% *}"; then
case $SHELLSPEC_SHELL in (*\ *) shell="$shell ${SHELLSPEC_SHELL#* }"; esac
SHELLSPEC_SHELL=$shell
elif ! $SHELLSPEC_SHELL -c '' >/dev/null 2>&1; then
abort "Not found specified shell: $SHELLSPEC_SHELL."
fi
}
# inspection
{
if ! eval "$($SHELLSPEC_SHELL "$SHELLSPEC_INSPECTION" || echo false)" &&:; then
abort "Shell inspection failed. This shell is not supported.$SHELLSPEC_LF" \
"(It is not a POSIX shell or basic functionality is defective)."
fi
if [ "$SHELLSPEC_DEFECT_BUILTIN" ]; then
warn "Unsupported shell (builtin commands can not redefine)."
fi
if [ "$SHELLSPEC_DEFECT_READONLY" ]; then
warn "Unsupported shell (readonly malfunction)."
fi
if [ "$SHELLSPEC_DEFECT_SHELLFLAG" ]; then
warn "Unsupported shell (shell flag handling broken)."
fi
if [ "$SHELLSPEC_DEFECT_ERREXIT" ]; then
warn "Unsupported shell (errexit handling broken)."
fi
if [ "$SHELLSPEC_DEFECT_SIGNAL" ]; then
SHELLSPEC_TRAP=":"
warn "Unsupported shell (signal handling broken)."
fi
[ "$SHELLSPEC_BUSYBOX_W32" ] && SHELLSPEC_PATHSEP=";"
[ "$SHELLSPEC_TTY" ] && SHELLSPEC_DEV_TTY=/dev/tty
}
setup_load_path
if [ "$SHELLSPEC_DEFECT_SANDBOX" ]; then
warn "Some features may fail due to incompatibilities with sandbox features."
fi
# shellcheck disable=SC2153
if [ "$SHELLSPEC_XTRACE" ]; then
if [ "$SHELLSPEC_XTRACE_ONLY" ]; then
SHELLSPEC_XTRACEFD=2
elif [ "$SHELLSPEC_XTRACEFD_VAR" ]; then
if [ "$SHELLSPEC_FDVAR_AVAILABLE" ]; then
SHELLSPEC_XTRACEFD=${SHELLSPEC_XTRACEFD:-"{SHELLSPEC_XTRACEFD}"}
else
# Busybox ash only?
SHELLSPEC_XTRACEFD=${SHELLSPEC_XTRACEFD:-8}
fi
else
SHELLSPEC_XTRACE_ONLY=1 SHELLSPEC_XTRACEFD=2
warn "Fall back to trace-only mode. All expectations will be skipped."
fi
if [ "$SHELLSPEC_DEFECT_XTRACE" ]; then
warn "If xtrace doesn't work, execute 'set -x' manually inside a function."
fi
fi
# resolve basic command path
{
if [ ! "$SHELLSPEC_BUILTIN_PRINTF" ]; then
command_path SHELLSPEC_PRINTF "printf" || SHELLSPEC_PRINTF="printf"
fi
command_path SHELLSPEC_ENV "env" ||:
command_path SHELLSPEC_MV "mv" ||:
command_path SHELLSPEC_CHMOD "chmod" ||:
command_path SHELLSPEC_DATE "date" ||:
command_path SHELLSPEC_RM "rm" ||:
command_path SHELLSPEC_SLEEP "sleep" ||:
command_path SHELLSPEC_LS "ls" ||:
command_path SHELLSPEC_SORT "sort" ||:
command_path SHELLSPEC_FIND "find" ||:
if command_path "time" || [ "$SHELLSPEC_BUSYBOX_W32" ]; then
SHELLSPEC_TIME="time -p"
else
SHELLSPEC_TIME="$SHELLSPEC_LIBEXEC/shellspec-time.sh"
if command_path bash; then
SHELLSPEC_TIME="bash $SHELLSPEC_TIME"
elif command_path ksh; then
SHELLSPEC_TIME="ksh $SHELLSPEC_TIME"
fi
fi
}
if ! signal 0 $$ 2>/dev/null; then
# For example posh 0.13.2 does not implement kill as builtin and
# debian 10 docker image does not have kill command installed by default.
warn "kill not found. You may encounter errors with some features."
fi
if [ "$SHELLSPEC_KCOV" ]; then
kcov_verson=$(kcov_version "$SHELLSPEC_KCOV_PATH") || abort "Kcov not found."
if [ "$(kcov_version_number "$kcov_verson")" -lt 35 ]; then
kcov_verson=${kcov_verson:-unknown (kcov v30 or below)}
abort "Kcov v35 or later required. [current: $kcov_verson]"
fi
if [ ! "$SHELLSPEC_KCOV_COMPATIBLE_SHELL" ]; then
abort "Require to use bash/zsh/ksh to run kcov (e.g: --shell bash)."
fi
export SHELLSPEC_KCOV_VERSION="$kcov_verson"
fi
if [ "$SHELLSPEC_PROFILER" ] && [ "$SHELLSPEC_WORKERS" -gt 0 ]; then
abort "Cannot be specified profiler and parallel execution at the same time."
fi
case $SHELLSPEC_MODE in (runner | list | translate | syntax-check)
[ $# -eq 0 ] && set -- "$SHELLSPEC_DEFAULT_PATH"
if includes_pathstar "$@"; then
for p in "$@"; do
includes_pathstar "$p" || continue
check_pathstar "$p" || abort "The path pattern is invalid: $p."
done
args=''
cd "$SHELLSPEC_CWD"
callback() { if [ ! "$2" ] || [ -e "${1%%:*}" ]; then pack args "$1"; fi; }
expand_pathstar callback "." "$@"
cd "$SHELLSPEC_PROJECT_ROOT"
eval "set -- $args"
[ $# -eq 0 ] && abort "Did not match the specified path(s)."
fi
for p in "$@"; do
abspath='' range=''
abspath abspath "$p" "$SHELLSPEC_CWD"
separate_abspath_and_range abspath range "$abspath"
[ -e "$abspath" ] || abort "Not found a path: ${p%%:*}."
if [ -d "$abspath" ] && [ "$range" ]; then
abort "Cannot specify range for the directory: $p"
fi
check_range "$range" || abort "Invalid range: $p"
if is_path_in_project "$abspath"; then
relpath=${abspath#"$SHELLSPEC_PROJECT_ROOT"}
[ "$relpath" ] && relpath=${relpath#/} || relpath='./'
set -- "$@" "${relpath}${range:+:}${range}"
else
# DEPRECATED: Accept only files in the project
# abort "Not a path in the project directory: $p."
if [ -e "$SHELLSPEC_PROJECT_ROOT/.shellspec" ]; then
error "Not a path in the project directory: $p"
warn 'IMPORTANT NOTES:'
warn ' You have specified specfile(s) that outside of the current project directory.'
warn ' In future releases, run specfile(s) that outside the current project directory'
warn ' will not be allowed. Fallback to the previous behavior for compatibility.'
warn "Current project root directory: $SHELLSPEC_PROJECT_ROOT"
fi
set -- "$@" "${abspath}${range:+:}${range}"
fi
shift
done
esac
case $SHELLSPEC_MODE in (init)
SHELLSPEC_PROJECT_ROOT=$SHELLSPEC_CWD
SHELLSPEC_PROJECT_NAME=${SHELLSPEC_PROJECT_ROOT##*/}
SHELLSPEC_HELPERDIR=$helperdir
SHELLSPEC_COVERAGEDIR=$coveragedir
SHELLSPEC_REPORTDIR=$reportdir
cd "$SHELLSPEC_CWD"
esac
case $SHELLSPEC_MODE in (runner | list)
# shellcheck disable=SC2153
if [ "$SHELLSPEC_RANDOM" ] && [ ! "$SHELLSPEC_SEED" ]; then
random_seed SHELLSPEC_SEED "$SHELLSPEC_UNIXTIME" "$$"
info "Randomized with seed $SHELLSPEC_SEED" >&2
fi
esac
[ "$SHELLSPEC_ENV_FROM" ] && exec="load-env" || exec=$SHELLSPEC_MODE
exec="$SHELLSPEC_LIBEXEC/shellspec-${exec}.sh"
eval exec "$SHELLSPEC_SHELL" "\"$exec\"" ${1+'"$@"'}