From e404d1eebca6a6b68ae40c400db90df0b6537a72 Mon Sep 17 00:00:00 2001 From: Kevin O'Donnell Date: Mon, 1 Jun 2026 12:28:43 -0400 Subject: [PATCH 1/3] fix(SUR-3353): include standalone executables in bin package --- Makefile | 8 ++++++-- tests/package.bats | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 tests/package.bats diff --git a/Makefile b/Makefile index 967e84e..d3eeb46 100644 --- a/Makefile +++ b/Makefile @@ -37,9 +37,13 @@ dist/doc-$(VERSION).tar.gz: $(DOC_SRC) bash/bashadoc dist/bin-$(VERSION).tar.gz: $(DOC_SRC) $(BIN_SRC) bash/pack-script mkdir -p dist/bin - for s in $$(find bash -type f ! -name '*.sh' -exec grep -q "includer" {} \; -print); do \ + for s in $$(find bash -maxdepth 1 -type f ! -name '*.sh' -perm -u+x -print); do \ base=$$(basename $$s) ; \ - bash/pack-script -v -f $$s -o dist/bin/$$base ; \ + if grep -q "includer" $$s; then \ + bash/pack-script -v -f $$s -o dist/bin/$$base ; \ + else \ + cp $$s dist/bin/$$base ; \ + fi ; \ done tar -zcf dist/bin-$(VERSION).tar.gz -C dist bin rm -rf dist/bin diff --git a/tests/package.bats b/tests/package.bats new file mode 100644 index 0000000..041b281 --- /dev/null +++ b/tests/package.bats @@ -0,0 +1,21 @@ +#!/usr/bin/env bats +bats_require_minimum_version 1.5.0 + +setup() { + load 'helpers.bash' + helpers::isolate_home +} + +@test "bin package includes standalone executable commands mddoc and semver (SUR-3353)" { + run make clean package + [ "$status" -eq 0 ] + + version=$(make --no-print-directory what_version | awk -F= '/^VERSION=/{print $2}') + tarball="$REPO_ROOT/dist/bin-$version.tar.gz" + [ -f "$tarball" ] + + run tar -tzf "$tarball" + [ "$status" -eq 0 ] + [[ "$output" == *"bin/mddoc"* ]] + [[ "$output" == *"bin/semver"* ]] +} From 8b15862344dc275114656cdf6f4140e95a698de1 Mon Sep 17 00:00:00 2001 From: Kevin O'Donnell Date: Mon, 1 Jun 2026 12:31:49 -0400 Subject: [PATCH 2/3] fix(SUR-3351): preserve namespace for per-pod kubectl calls --- bash/against-each-pod | 10 ++++++++-- bash/pod-console | 6 +++++- tests/against-each-pod.bats | 5 +++++ tests/pod-console.bats | 14 ++++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/bash/against-each-pod b/bash/against-each-pod index 5c99ac5..3345979 100755 --- a/bash/against-each-pod +++ b/bash/against-each-pod @@ -35,10 +35,16 @@ LABEL=${1:?label required as first positional arg} shift COMMAND=${1:?subcommand required as second positional arg} shift + +NAMESPACE_ARGS=() +if [ -n "${NAMESPACE:-}" ]; then + NAMESPACE_ARGS=(-n "$NAMESPACE") +fi + for pod in $(k8s::pod_names_for_label "$LABEL" "${NAMESPACE:-}" | sort); do - node_name=$(k8s::ctl get pod "$pod" -o json | + node_name=$(k8s::ctl get pod "$pod" "${NAMESPACE_ARGS[@]}" -o json | $(commands::use jq) -r '.spec.nodeName') echo "=== $node_name $pod ===" - k8s::ctl "$COMMAND" "pod/$pod" "$@" + k8s::ctl "$COMMAND" "pod/$pod" "${NAMESPACE_ARGS[@]}" "$@" echo done diff --git a/bash/pod-console b/bash/pod-console index 6db08a3..ad3af94 100755 --- a/bash/pod-console +++ b/bash/pod-console @@ -32,6 +32,10 @@ options::parse "$@" shift $((OPTIND - 1)) SESSION=$LABEL +NAMESPACE_ARGS=() +if [ -n "${NAMESPACE:-}" ]; then + NAMESPACE_ARGS=(-n "$NAMESPACE") +fi function tmux::cmd { $(commands::use tmux) "$@" @@ -57,7 +61,7 @@ for pod in $(k8s::pod_names_for_label "$LABEL" "${NAMESPACE:-}" | sort); do # Build the send-keys payload via printf %q so each positional arg # round-trips through the remote shell's tokeniser intact instead of # being collapsed by IFS-joined "$*". See SUR-1869. - payload=$(printf '%q ' "$(commands::use kubectl)" "$COMMAND" "pod/$pod" "$@") + payload=$(printf '%q ' "$(commands::use kubectl)" "$COMMAND" "pod/$pod" "${NAMESPACE_ARGS[@]}" "$@") log::debug "Execute on $SESSION: $payload" tmux::cmd send-keys -t "$SESSION" "$payload" C-m log::debug "Tile window $WINDOW" diff --git a/tests/against-each-pod.bats b/tests/against-each-pod.bats index c91e364..259a302 100644 --- a/tests/against-each-pod.bats +++ b/tests/against-each-pod.bats @@ -27,6 +27,11 @@ teardown() { [[ "$argv" == *"--field-selector=status.phase=Running"* ]] [[ "$argv" == *"-o name"* ]] [[ "$argv" == *"-n my-ns"* ]] + # Every kubectl call (listing, get pod, and per-pod command) must + # preserve -n when the option is provided. + while IFS= read -r line; do + [[ "$line" == *" -n my-ns "* ]] || [[ "$line" == *" -n my-ns" ]] + done <"$KUBECTL_ARGV_LOG" } @test "against-each-pod without -n omits namespace from kubectl argv" { diff --git a/tests/pod-console.bats b/tests/pod-console.bats index 4bb5e95..0b26a8a 100644 --- a/tests/pod-console.bats +++ b/tests/pod-console.bats @@ -72,6 +72,20 @@ teardown() { [ "${!#}" = 'echo "two words"' ] } +@test "send-keys payload includes namespace when -n is provided" { + run "$REPO_ROOT/bash/pod-console" -n my-ns -l app=foo -c exec -- sh -c "true" + [ "$status" -eq 0 ] + + send_line=$(grep -a '^send-keys' "$TMUX_ARGV_LOG" | tr '\0' '\n') + [ -n "$send_line" ] + payload=$(awk 'NR==4 {print; exit}' <<<"$send_line") + [ -n "$payload" ] + + eval "set -- $payload" + joined=" $* " + [[ "$joined" == *" -n my-ns "* ]] +} + @test "no \$* expansion remains in pod-console (excluding comments)" { # Strip comment-only and trailing-comment content before matching, so # any commentary that mentions the historical bug does not trip the From c7a209882add144d807a12f24ffcdbfb991adafa Mon Sep 17 00:00:00 2001 From: Kevin O'Donnell Date: Mon, 1 Jun 2026 12:34:34 -0400 Subject: [PATCH 3/3] fix(SUR-3352): wait for background builds and avoid duplicates --- bash/daml-export | 82 ++++++++++++++++++++++++++++++++-------- tests/daml-export.bats | 86 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+), 16 deletions(-) diff --git a/bash/daml-export b/bash/daml-export index 9766c86..5a91e23 100755 --- a/bash/daml-export +++ b/bash/daml-export @@ -161,7 +161,9 @@ function build { echo "Building $dir" pushd "$dir" >/dev/null || return 1 daml build >"build.log" 2>&1 + local build_exit=$? popd >/dev/null || return 1 + return "$build_exit" } # SUR-2838 test seam: when sourced from bats, skip the main entry @@ -182,6 +184,7 @@ STOP_INT=$(hex_to_dec "$STOP_OFFSET") count=0 RUNNING_PROCS=() +RUNNING_OUTPUT_DIRS=() # TO_BUILD removed (SUR-1871): the previous queue accumulated every # OUTPUT_DIR but only drained one entry per iteration, so the post-loop # `find ... export.good` walk below already had to pick up the rest. @@ -192,15 +195,61 @@ RUNNING_PROCS=() # exit or Ctrl-C so they don't keep running after the user gives up. # `kill -0` guards against already-reaped pids, and stderr is swallowed # so the trap stays quiet on normal completion. +function refresh_running_procs { + local idx + local pid + local current_pids=() + local current_dirs=() + for idx in "${!RUNNING_PROCS[@]}"; do + pid=${RUNNING_PROCS[$idx]} + if ps -p "$pid" >/dev/null; then + current_pids+=("$pid") + current_dirs+=("${RUNNING_OUTPUT_DIRS[$idx]}") + fi + done + RUNNING_PROCS=("${current_pids[@]}") + RUNNING_OUTPUT_DIRS=("${current_dirs[@]}") +} + +function is_output_dir_running { + local dir=${1:?} + local running_dir + for running_dir in "${RUNNING_OUTPUT_DIRS[@]}"; do + if [ "$running_dir" = "$dir" ]; then + return 0 + fi + done + return 1 +} + function kill_running_procs { local pid + refresh_running_procs for pid in "${RUNNING_PROCS[@]}"; do if kill -0 "$pid" 2>/dev/null; then kill "$pid" 2>/dev/null || true fi done } -trap kill_running_procs EXIT INT TERM + +function wait_for_running_procs { + local pid + local wait_status=0 + for pid in "${RUNNING_PROCS[@]}"; do + wait "$pid" || wait_status=1 + done + RUNNING_PROCS=() + RUNNING_OUTPUT_DIRS=() + return "$wait_status" +} + +NORMAL_COMPLETION=false +function handle_exit { + if [ "$NORMAL_COMPLETION" != "true" ]; then + kill_running_procs + fi +} +trap handle_exit EXIT INT TERM # Loop guard rewrite (SUR-1871): the original # while [ "$CUR_INT" -le "$STOP_INT" ] && [ "$CUR_INT" != "$NEXT_INT" ] @@ -231,18 +280,13 @@ while [ "$CUR_INT" -le "$STOP_INT" ]; do exportRange "$CUR_HEX" "$NEXT_HEX" "$OUTPUT_DIR" "$BASE_DIR" fi - CURRENT_PROCS=() - for pid in "${RUNNING_PROCS[@]}"; do - if ps -p "$pid" >/dev/null; then - CURRENT_PROCS+=("$pid") - fi - RUNNING_PROCS=("${CURRENT_PROCS[@]}") - done + refresh_running_procs if [ "${#RUNNING_PROCS[@]}" -lt "$MAX_PARALLEL" ]; then correct_export "$OUTPUT_DIR" build "$OUTPUT_DIR" & RUNNING_PROCS+=($!) + RUNNING_OUTPUT_DIRS+=("$OUTPUT_DIR") echo "Daml builds ${RUNNING_PROCS[*]} in the background" fi @@ -256,8 +300,12 @@ done function verify_and_build() { local buildFile=${1:?} local OUTPUT_DIR - local CURRENT_PROCS OUTPUT_DIR=$(dirname "$buildFile") + refresh_running_procs + if is_output_dir_running "$OUTPUT_DIR"; then + echo "Skipping $OUTPUT_DIR since a build is already running" + return + fi verifyExport "$OUTPUT_DIR" local ret=$? if [ $ret -gt 1 ]; then @@ -272,19 +320,21 @@ function verify_and_build() { while [ "${#RUNNING_PROCS[@]}" -ge "$MAX_PARALLEL" ]; do sleep 10 - CURRENT_PROCS=() - for pid in "${RUNNING_PROCS[@]}"; do - if ps -p "$pid" >/dev/null; then - CURRENT_PROCS+=("$pid") - fi - RUNNING_PROCS=("${CURRENT_PROCS[@]}") - done + refresh_running_procs done build "$OUTPUT_DIR" & RUNNING_PROCS+=($!) + RUNNING_OUTPUT_DIRS+=("$OUTPUT_DIR") } while read -r buildFile; do verify_and_build "$buildFile" done < <(find "$TARGET_DIR" -name export.good -print | sort) + +wait_for_running_procs +WAIT_STATUS=$? +NORMAL_COMPLETION=true +if [ "$WAIT_STATUS" -ne 0 ]; then + exit "$WAIT_STATUS" +fi diff --git a/tests/daml-export.bats b/tests/daml-export.bats index 03575c8..58309b2 100644 --- a/tests/daml-export.bats +++ b/tests/daml-export.bats @@ -23,6 +23,26 @@ setup() { export DAML_EXPORT_SOURCE_ONLY=true } +function write_daml_stub() { + local stub_bin=${1:?} + cat >"$stub_bin/daml" <<'EOF' +#!/usr/bin/env bash +if [ "$1" = "build" ]; then + echo "start:$PWD" >>"${DAML_STUB_LOG:?}" + trap 'echo "killed:$PWD" >>"${DAML_STUB_LOG:?}"; exit 143' TERM INT + sleep "${DAML_BUILD_SLEEP:-1}" + if [ -n "${DAML_BUILD_FAIL_DIR:-}" ] && [[ "$PWD" == *"${DAML_BUILD_FAIL_DIR}" ]]; then + echo "fail:$PWD" >>"${DAML_STUB_LOG:?}" + exit 7 + fi + echo "done:$PWD" >>"${DAML_STUB_LOG:?}" + exit 0 +fi +exit 0 +EOF + chmod +x "$stub_bin/daml" +} + @test "DAML_EXPORT_SOURCE_ONLY exits 0 when executed (no main body) (SUR-2838)" { out=$(mktemp -d) run env DAML_EXPORT_SOURCE_ONLY=true bash "$DAML_EXPORT" -d "$out" -e ffff @@ -164,3 +184,69 @@ EOF grep -q "^archiveCmd foo$" "$tmp/Export.daml" rm -rf "$tmp" } + +@test "daml-export waits for in-flight background builds on normal exit (SUR-3352)" { + tmp=$(mktemp -d) + stub_bin=$(mktemp -d) + log_file=$(mktemp) + write_daml_stub "$stub_bin" + + dir="$tmp/0000000000000000-0000000000000001" + mkdir -p "$dir" + touch "$dir/export.good" "$dir/Export.daml" + cat >"$dir/daml.yaml" <<'EOF' +build-options: ["--target=1.14"] +EOF + + run env PATH="$stub_bin:$PATH" DAML_STUB_LOG="$log_file" DAML_BUILD_SLEEP=1 DAML_EXPORT_SOURCE_ONLY= \ + bash "$DAML_EXPORT" -d "$tmp" -b 0 -e 1 -s 1 -P 5 + [ "$status" -eq 0 ] + [[ "$(cat "$log_file")" == *"done:$dir"* ]] + run ! grep -q "^killed:" "$log_file" + rm -rf "$tmp" "$stub_bin" + rm -f "$log_file" +} + +@test "daml-export propagates background build failure to exit status (SUR-3352)" { + tmp=$(mktemp -d) + stub_bin=$(mktemp -d) + log_file=$(mktemp) + write_daml_stub "$stub_bin" + + dir="$tmp/0000000000000000-0000000000000001" + mkdir -p "$dir" + touch "$dir/export.good" "$dir/Export.daml" + cat >"$dir/daml.yaml" <<'EOF' +build-options: ["--target=1.14"] +EOF + + run env PATH="$stub_bin:$PATH" DAML_STUB_LOG="$log_file" DAML_BUILD_SLEEP=1 DAML_EXPORT_SOURCE_ONLY= \ + DAML_BUILD_FAIL_DIR="0000000000000000-0000000000000001" \ + bash "$DAML_EXPORT" -d "$tmp" -b 0 -e 1 -s 1 -P 5 + [ "$status" -ne 0 ] + [[ "$(cat "$log_file")" == *"fail:$dir"* ]] + rm -rf "$tmp" "$stub_bin" + rm -f "$log_file" +} + +@test "daml-export does not schedule duplicate builds for same output dir (SUR-3352)" { + tmp=$(mktemp -d) + stub_bin=$(mktemp -d) + log_file=$(mktemp) + write_daml_stub "$stub_bin" + + dir="$tmp/0000000000000000-0000000000000001" + mkdir -p "$dir" + touch "$dir/export.good" "$dir/Export.daml" + cat >"$dir/daml.yaml" <<'EOF' +build-options: ["--target=1.14"] +EOF + + run env PATH="$stub_bin:$PATH" DAML_STUB_LOG="$log_file" DAML_BUILD_SLEEP=1 DAML_EXPORT_SOURCE_ONLY= \ + bash "$DAML_EXPORT" -d "$tmp" -b 0 -e 1 -s 1 -P 5 + [ "$status" -eq 0 ] + starts=$(grep -c "^start:$dir" "$log_file" || true) + [ "$starts" -eq 1 ] + rm -rf "$tmp" "$stub_bin" + rm -f "$log_file" +}