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
17 changes: 16 additions & 1 deletion bash/includer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,19 @@ source "$(dirname "${BASH_SOURCE[0]}")/doc.sh"

@package includer

# @include NAME — load bash/NAME or bash/NAME.sh once per process.
#
# Resolution: try dirname(BASH_SOURCE)/NAME first; if unreadable, try the
# same path with a .sh suffix. Same order as includer::find.
#
# Deduplication: cksum(true_file) defines a global guard include_<cksum>;
# the first successful source sets it so later @include of the same path
# is a no-op. Two paths that cksum differently (e.g. symlink vs canonical)
# load twice; hardlinks to the same inode share a cksum and dedupe.
#
# Errors: missing file prints to stderr and returns 1 (does not exit),
# so callers can handle failures under set -e.

function @include {
local include_file=${1:?}
local true_file
Expand All @@ -40,7 +53,9 @@ function @include {
}

function includer::find {
@doc Find the file referred to as an include.
@doc Resolve bash/STEM or bash/STEM.sh the same way as @include: bare \
filename first, .sh suffix second. Echo the readable path or return 1 \
when missing.
@arg _1_ the name of the include
local include_file=${1:?}
local true_file
Expand Down
15 changes: 10 additions & 5 deletions bash/log.sh
Original file line number Diff line number Diff line change
Expand Up @@ -112,19 +112,24 @@ unset __log_value_trace __log_value_debug __log_value_info __log_value_warning

function log::level_increase() {
@doc Increase the LOG_LEVEL
((LOG_LEVEL += 1))
# Use $(( )) not ((var += 1)): the latter exits 1 when the result is 0, so
# set -e aborts (symmetry with log::level_decrease, SUR-2490).
LOG_LEVEL=$((LOG_LEVEL + 1))
log::level "$LOG_LEVEL"
}

function log::level_decrease() {
@doc Decrease the LOG_LEVEL by 1. The minimum effective level is 0, calling this
@doc function at LOG_LEVEL=0 logs a warning and returns without changing the level.
@doc Mirrors log::level_increase. Call log::level after adjusting to re-apply flags.
@doc Decrease LOG_LEVEL by 1 and refresh disable flags via log::level. At \
LOG_LEVEL=0, calls log::warn and returns unchanged — the message is gated \
like other warnings, so at default level 0 it may not print. Safe under \
set -e. Pair with log::level_increase for reversible verbosity toggles.
if ((LOG_LEVEL <= 0)); then
log::warn "log::level_decrease: LOG_LEVEL already at minimum (0); ignoring"
return
fi
((LOG_LEVEL -= 1))
# Use $(( )) not ((var -= 1)): the latter exits 1 when the result is 0, so
# set -e aborts after decrementing from LOG_LEVEL=1 (SUR-2490).
LOG_LEVEL=$((LOG_LEVEL - 1))
log::level "$LOG_LEVEL"
}

Expand Down
14 changes: 12 additions & 2 deletions bash/options.sh
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,12 @@ function options::standard() {
}

function options::parse_available() {
@doc parse the options using the provided argument array
@doc Parse argv against the configured option set. Does not exit when argv \
is empty. Callers that want empty argv to print syntax help and exit should \
use options::parse. Setting NO_SYNTAX_EXIT is only for use with \
options::parse: it suppresses that empty-argv syntax exit so zero-flag \
invocations are allowed. See options::parse for the OPTIND caveat when \
parsing again. Always runs mandatory-option validation after getopts.
@arg "$@" the provided argument array
OPTIONS_SEEN=()
while options::getopts opt "$@"; do
Expand Down Expand Up @@ -306,7 +311,12 @@ function options::parse_available() {
}

function options::parse() {
@doc parse the options using the provided argument array, if no args passes print syntax and exit
@doc Parse argv against the configured option set. After a successful \
parse, if argv was empty so no flags were consumed and OPTIND is still 1 \
and NO_SYNTAX_EXIT is unset, prints syntax and exits 1. Set NO_SYNTAX_EXIT \
to any non-empty value to allow zero-flag invocations such as commands \
that take only positionals. That leaks the opt-ind state from this \
parse into later code, so clear or reset OPTIND if you parse again.
@arg "$@" the provided argument array
options::parse_available "$@"
if [ -z "${NO_SYNTAX_EXIT}" ] && [ "${OPTIND}" -eq 1 ]; then
Expand Down
16 changes: 13 additions & 3 deletions bash/update-repo-tags
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,19 @@ elif [ "$CHANGES" -gt 0 ]; then
# if we can't annotate tag then we must do a prerel bump
if [ "${ANNOTATE_TAG}" = "false" ]; then
PREREL=$("$(dirs::of)/semver" get prerel "$LATEST_VERSION")
((PREREL++))
TAG=$("$(dirs::of)/semver" bump prerel "$PREREL" "$LATEST_VERSION")
TAG=${TAG/"-$PREREL"/"p$PREREL"}
# Lightweight-tag path: numeric prerel segments only. Non-numeric values
# break arithmetic and the -$PREREL substitution can corrupt the tag
# (SUR-2457). Empty prerel starts the pN sequence at 1.
if [[ -z "$PREREL" ]]; then
NEXT_PREREL=1
elif [[ "$PREREL" =~ ^[0-9]+$ ]]; then
NEXT_PREREL=$((10#$PREREL + 1))
else
log::notice "Non-numeric prerel '${PREREL}' in ${LATEST_VERSION}; resetting lightweight prerel counter to 1"
NEXT_PREREL=1
fi
TAG=$("$(dirs::of)/semver" bump prerel "$NEXT_PREREL" "$LATEST_VERSION")
TAG=${TAG/"-${NEXT_PREREL}"/"p${NEXT_PREREL}"}
else
TAG=$("$(dirs::of)/semver" bump patch "$LATEST_VERSION")
fi
Expand Down
51 changes: 48 additions & 3 deletions tests/log.bats
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/usr/bin/env bats
# SUR-1850 seed: log::level cumulative gating per documented table.

bats_require_minimum_version 1.5.0

setup() {
load 'helpers.bash'
helpers::isolate_home
Expand Down Expand Up @@ -193,9 +195,9 @@ probe_level() {
[[ "$out" == "INFO date=%DATE" ]]
}

# SUR-2475: log::level_decrease floor guard
# SUR-2490 / SUR-2476: log::level_decrease under set -e and floor guard

@test "log::level_decrease from non-zero decrements LOG_LEVEL (SUR-2475)" {
@test "log::level_decrease from non-zero decrements LOG_LEVEL (SUR-2476)" {
run bash -c "
LOG_LEVEL=2
source '$REPO_ROOT/bash/includer.sh'
Expand All @@ -207,7 +209,21 @@ probe_level() {
[ "$output" = "1" ]
}

@test "log::level_decrease at LOG_LEVEL=0 leaves level unchanged (SUR-2475)" {
@test "log::level_decrease completes under set -e when LOG_LEVEL > 0 (SUR-2490)" {
run bash -c "
set -e
LOG_LEVEL=1
LOGFILE_DISABLE=true
source '$REPO_ROOT/bash/includer.sh'
@include log
log::level_decrease
echo \"ok LOG_LEVEL=\$LOG_LEVEL\"
"
[ "$status" -eq 0 ]
[[ "$output" == *"ok LOG_LEVEL=0"* ]]
}

@test "log::level_decrease at LOG_LEVEL=0 leaves level unchanged (SUR-2476)" {
run bash -c "
LOG_LEVEL=0
source '$REPO_ROOT/bash/includer.sh'
Expand All @@ -218,3 +234,32 @@ probe_level() {
[ "$status" -eq 0 ]
[ "$output" = "0" ]
}

@test "log::level_decrease at floor emits log::warn when warnings enabled (SUR-2476)" {
local stderr
run --separate-stderr bash -c "
LOG_LEVEL=0
LOG_DISABLE_WARNING=false
source '$REPO_ROOT/bash/includer.sh'
@include log
log::level_decrease
echo \$LOG_LEVEL
"
[ "$status" -eq 0 ]
[ "$output" = "0" ]
[[ "$stderr" == *"LOG_LEVEL already at minimum"* ]]
}

@test "log::level_increase completes under set -e when result is 0 (SUR-2490 parity)" {
run bash -c "
set -e
LOG_LEVEL=-1
LOGFILE_DISABLE=true
source '$REPO_ROOT/bash/includer.sh'
@include log
log::level_increase
echo \"ok LOG_LEVEL=\$LOG_LEVEL\"
"
[ "$status" -eq 0 ]
[[ "$output" == *"ok LOG_LEVEL=0"* ]]
}
8 changes: 4 additions & 4 deletions tests/on-change.bats
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ printf '%s\n' "\$@" >>"$argv_log"
EOF
chmod +x "$stub_bin/record_argv"
(
sleep 0.4
sleep 2
echo trigger >>"$watch_dir/marker"
) &
run env PATH="$stub_bin:$PATH" timeout 8s "$ONCHANGE" -W "$watch_dir" -t 1 -v -v -- "$stub_bin/record_argv" ok
run env PATH="$stub_bin:$PATH" timeout 15s "$ONCHANGE" -W "$watch_dir" -t 1 -v -v -- "$stub_bin/record_argv" ok
[[ "$output" == *"Working directory $watch_dir"* ]]
[[ "$output" == *"Watching directory $watch_dir"* ]]
[[ "$output" == *"Polling interval 1"* ]]
Expand All @@ -66,10 +66,10 @@ printf '%s\n' "\$@" >>"$argv_log"
EOF
chmod +x "$stub_bin/record_argv"
(
sleep 0.4
sleep 2
echo trigger >>"$watch_dir/marker"
) &
run env PATH="$stub_bin:$PATH" timeout 8s "$ONCHANGE" -W "$watch_dir" -w "$watch_dir" -t 1 -- "$stub_bin/record_argv" "arg one" "arg two"
run env PATH="$stub_bin:$PATH" timeout 15s "$ONCHANGE" -W "$watch_dir" -w "$watch_dir" -t 1 -- "$stub_bin/record_argv" "arg one" "arg two"
grep -qx 'arg one' "$argv_log"
grep -qx 'arg two' "$argv_log"
rm -rf "$watch_dir" "$stub_bin"
Expand Down
36 changes: 36 additions & 0 deletions tests/pagerduty.bats
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,42 @@ setup() {
[[ "$output" == *"key-1"* ]]
}

@test "pagerduty::_incident_data omits incident_key when key is empty (SUR-2479)" {
run bash -c "
source '$REPO_ROOT/bash/includer.sh'
@include pagerduty
pagerduty::_incident_data svc incident title '' | jq -e '.incident | has(\"incident_key\") | not'
"
[ "$status" -eq 0 ]
}

@test "pagerduty::_incident_data includes incident_key when key non-empty (SUR-2479)" {
run bash -c "
source '$REPO_ROOT/bash/includer.sh'
@include pagerduty
pagerduty::_incident_data svc incident title my-key | jq -e '.incident.incident_key == \"my-key\"'
"
[ "$status" -eq 0 ]
}

@test "pagerduty::send_incident passes --fail-with-body to curl on HTTP failure path (SUR-2479)" {
run bash -c "
source '$REPO_ROOT/bash/includer.sh'
@include pagerduty
pagerduty::_curl() {
case \" \$* \" in
*' --fail-with-body '*) ;;
*) echo 'missing --fail-with-body' >&2; return 99 ;;
esac
return 22
}
pagerduty::send_incident SVC1 incident T from token ''
"
[ "$status" -eq 22 ]
[[ "$output" != *"missing --fail-with-body"* ]]
[[ "$output" == *"PagerDuty incident send failed"* ]]
}

@test "pagerduty::send_incident does not emit Token token= secret in xtrace (SUR-2339)" {
run bash -c "
trace=\$(mktemp)
Expand Down
24 changes: 24 additions & 0 deletions tests/update-repo-tags.bats
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# against a synthetic git repo. Specifically locks down the Xp1 prerel
# post-processing on lines 84-86 of update-repo-tags (replace -N with pN).

bats_require_minimum_version 1.5.0

setup() {
load 'helpers.bash'
helpers::isolate_home
Expand Down Expand Up @@ -96,6 +98,28 @@ latest_tag() {
[ "$(latest_tag)" = "v0.1.0p2" ]
}

@test "lightweight tag with non-numeric prerel resets to p1 with notice (SUR-2457)" {
local stderr
commit_msg "feat: initial"
light_tag v0.1.0-foo
commit_msg "fix: something"
run --separate-stderr "$UPDATE" -t "$REPO"
[ "$status" -eq 0 ]
[ "$(latest_tag)" = "v0.1.0p1" ]
[[ "$stderr" == *"Non-numeric prerel"* ]]
}

@test "lightweight tag with dotted prerel does not corrupt next tag (SUR-2457)" {
local stderr
commit_msg "feat: initial"
light_tag v0.1.0-rc.1
commit_msg "fix: something"
run --separate-stderr "$UPDATE" -t "$REPO"
[ "$status" -eq 0 ]
[ "$(latest_tag)" = "v0.1.0p1" ]
[[ "$stderr" == *"Non-numeric prerel"* ]]
}

@test "refuses to bump minor on breaking change without -b" {
commit_msg "feat: initial"
annotated_tag v0.1.0
Expand Down
Loading