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
136 changes: 136 additions & 0 deletions libraries/main.bats
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,142 @@ declare -gi ARGSH_BUILTIN="${ARGSH_BUILTIN:-0}"
contains "No test files found" stderr
}

# ---------------------------------------------------------------------------
# argsh::minify — a failed template render must not truncate the output (#178)
#
# Fake minifier + envsubst executables go on PATH so argsh::minify takes its
# local path (no docker forward) with a controlled render. Executables, not
# function stubs, so the tests also hold against the minified argsh.
# ---------------------------------------------------------------------------
minify_fixture() {
local dir="${1}"
mkdir -p "${dir}/bin"
cat >"${dir}/bin/minifier" <<'EOF'
#!/usr/bin/env bash
in="" out=""
while (( ${#} )); do
case "${1}" in
-i) in="${2}"; shift 2 ;;
-o) out="${2}"; shift 2 ;;
*) shift ;;
esac
done
cat "${in}" >"${out}"
EOF
chmod +x "${dir}/bin/minifier"
echo 'echo minified' >"${dir}/in.sh"
# shellcheck disable=SC2016
printf '#!/bin/sh\n${data}\n' >"${dir}/template"
}

@test "argsh::minify: template render writes the output on success" {
local _tmp; _tmp="$(mktemp -d)"
minify_fixture "${_tmp}"
cat >"${_tmp}/bin/envsubst" <<'EOF'
#!/usr/bin/env bash
if [[ "${1:-}" == "--version" ]]; then
echo "envsubst (GNU gettext-runtime) 0.22"
exit 0
fi
content="$(cat)"
printf '%s\n' "${content//'${data}'/${data}}"
EOF
chmod +x "${_tmp}/bin/envsubst"

(
PATH="${_tmp}/bin:${PATH}" \
argsh::minify -t "${_tmp}/template" -o "${_tmp}/out" "${_tmp}/in.sh"
) >"${stdout}" 2>"${stderr}" || status=$?

assert "${status}" -eq 0
is_empty stderr
grep -q "#!/bin/sh" "${_tmp}/out"
grep -q "echo minified" "${_tmp}/out"
# The temp-file+mv path must not leak mktemp's 0600 onto the artifact —
# the pre-fix redirect produced a normally-readable file.
local _mode; _mode="$(stat -c '%a' "${_tmp}/out")"
[[ "${_mode}" != "600" ]] || { echo "out has mktemp perms 0600" >&2; return 1; }
rm -rf "${_tmp}"
}

@test "argsh::minify: failed template render leaves existing output untouched" {
local _tmp; _tmp="$(mktemp -d)"
minify_fixture "${_tmp}"
echo "previous good artifact" >"${_tmp}/out"
cat >"${_tmp}/bin/envsubst" <<'EOF'
#!/usr/bin/env bash
if [[ "${1:-}" == "--version" ]]; then
echo "envsubst (GNU gettext-runtime) 0.22"
exit 0
fi
echo "boom: render failed" >&2
exit 1
EOF
chmod +x "${_tmp}/bin/envsubst"

(
PATH="${_tmp}/bin:${PATH}" \
argsh::minify -t "${_tmp}/template" -o "${_tmp}/out" "${_tmp}/in.sh"
) >"${stdout}" 2>"${stderr}" || status=$?

assert "${status}" -ne 0
contains "left untouched" stderr
assert "$(cat "${_tmp}/out")" = "previous good artifact"
rm -rf "${_tmp}"
}

@test "argsh::minify: empty template render leaves existing output untouched" {
local _tmp; _tmp="$(mktemp -d)"
minify_fixture "${_tmp}"
echo "previous good artifact" >"${_tmp}/out"
cat >"${_tmp}/bin/envsubst" <<'EOF'
#!/usr/bin/env bash
if [[ "${1:-}" == "--version" ]]; then
echo "envsubst (GNU gettext-runtime) 0.22"
exit 0
fi
cat >/dev/null
exit 0
EOF
chmod +x "${_tmp}/bin/envsubst"

(
PATH="${_tmp}/bin:${PATH}" \
argsh::minify -t "${_tmp}/template" -o "${_tmp}/out" "${_tmp}/in.sh"
) >"${stdout}" 2>"${stderr}" || status=$?

assert "${status}" -ne 0
contains "left untouched" stderr
assert "$(cat "${_tmp}/out")" = "previous good artifact"
rm -rf "${_tmp}"
}

@test "argsh::minify: non-GNU envsubst is rejected with a clear message" {
local _tmp; _tmp="$(mktemp -d)"
minify_fixture "${_tmp}"
echo "previous good artifact" >"${_tmp}/out"
cat >"${_tmp}/bin/envsubst" <<'EOF'
#!/usr/bin/env bash
if [[ "${1:-}" == "--version" ]]; then
echo "renvsubst 0.6.1"
exit 0
fi
echo "ERROR: Unknown flag: ${1}" >&2
exit 1
EOF
chmod +x "${_tmp}/bin/envsubst"

(
PATH="${_tmp}/bin:${PATH}" \
argsh::minify -t "${_tmp}/template" -o "${_tmp}/out" "${_tmp}/in.sh"
) >"${stdout}" 2>"${stderr}" || status=$?

assert "${status}" -ne 0
contains "not GNU envsubst" stderr
assert "$(cat "${_tmp}/out")" = "previous good artifact"
rm -rf "${_tmp}"
}

# ---------------------------------------------------------------------------
# argsh::builtin::download — atomic install via temp file
#
Expand Down
35 changes: 32 additions & 3 deletions libraries/main.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1515,11 +1515,12 @@ argsh::minify() {
# is scoped to this function's invocation and does not clobber any
# caller-installed EXIT trap. :args-set vars are inherited by the subshell.
(
local _content _tout
local _content _tout _render
_content="$(mktemp)"
_tout="$(mktemp)"
_render="$(mktemp)"
# shellcheck disable=SC2064
trap "rm -f ${_content} ${_tout}" EXIT
trap "rm -f ${_content} ${_tout} ${_render}" EXIT

local _f _file
local -a _glob
Expand Down Expand Up @@ -1554,13 +1555,41 @@ argsh::minify() {
echo "argsh: envsubst is required for -t/--template (install gettext)" >&2
exit 1
}
# Drop-in replacements like renvsubst reject the GNU SHELL-FORMAT
# argument ("Unknown flag") — name the culprit instead of failing cryptic.
envsubst --version 2>/dev/null | grep -q "GNU gettext" || {
echo "argsh: $(command -v envsubst) is not GNU envsubst; -t/--template needs the gettext one (install gettext or fix PATH)" >&2
exit 1
}
# obfus ignore variable
local commit_sha="${GIT_COMMIT_SHA:-}"
# obfus ignore variable
local version="${GIT_VERSION:-}"
export data commit_sha version
# Render into a temp file first: a direct redirect into ${out} would
# truncate a previously-good artifact before envsubst even runs.
# shellcheck disable=SC2016
envsubst '$data,$commit_sha,$version' <"${template}" >"${out}"
envsubst '$data,$commit_sha,$version' <"${template}" >"${_render}" || {
echo "argsh: envsubst failed to render ${template}; ${out} left untouched" >&2
exit 1
}
[[ -s "${_render}" ]] || {
echo "argsh: envsubst rendered ${template} to an empty file; ${out} left untouched" >&2
exit 1
}
if [[ -e "${out}" && ! -f "${out}" ]]; then
# Not a regular file (e.g. the /dev/stdout default) — mv cannot replace it.
cat "${_render}" >"${out}"
else
# mv would hand ${out} mktemp's 0600 mode; the old redirect kept the
# file's existing perms (or umask for a new one). Restore that contract.
if [[ -f "${out}" ]]; then
chmod --reference="${out}" "${_render}" 2>/dev/null || chmod 644 "${_render}"
else
chmod 644 "${_render}"
fi
mv "${_render}" "${out}"
fi
)
}

Expand Down
Loading