From 6d0bcb5fbfbc0c6a5b0f28f277715f8c01b33490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Gro=C3=9Fmann?= Date: Fri, 24 Jul 2026 17:16:34 +0200 Subject: [PATCH 1/2] tests: fix silent RC 2 when a .env is present under dash shutdown-acceptance.sh sourced config with `. "${ENV_FILE:-.env}"` -- a bare ".env" with no slash. The `.` (dot) builtin PATH-searches an operand that contains no slash, so under dash (where CWD is not in PATH) ".env" is "not found"; as a special builtin that error aborts a non-interactive shell. With the message hidden by `2>/dev/null`, the script exited 2 with no output whenever a .env existed in the CWD and /bin/sh was dash -- exactly a TK5 host. bash masks this because its `.` falls back to the CWD. Force a slash on the operand so the file is read from the given path, and stop swallowing the error so a real problem is visible. ENV_FILE=/dev/null still skips sourcing. Refs #122 --- tests/shutdown-acceptance.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/shutdown-acceptance.sh b/tests/shutdown-acceptance.sh index 26f31d5..bc13b25 100755 --- a/tests/shutdown-acceptance.sh +++ b/tests/shutdown-acceptance.sh @@ -100,9 +100,19 @@ set -u # --- load .env if present (for HTTPD_HOST default only) --------------------- -if [ -f "${ENV_FILE:-.env}" ]; then +# The `.` (dot) builtin searches $PATH when its operand has NO slash — so a bare +# ".env" is looked up in $PATH, not the CWD. Under dash a not-found `.` is a +# special-builtin error that ABORTS a non-interactive shell (silent RC 2 once the +# message is hidden). Force a slash so it is read from the given path, and don't +# swallow errors. Set ENV_FILE=/dev/null to skip sourcing entirely. +env_file="${ENV_FILE:-.env}" +case "$env_file" in + */*) : ;; # already path-qualified + *) env_file="./$env_file" ;; +esac +if [ -f "$env_file" ]; then # shellcheck disable=SC1090 - . "${ENV_FILE:-.env}" 2>/dev/null || true + . "$env_file" || true fi HTTPD_HOST=${HTTPD_HOST:-${MBT_MVS_HOST:-127.0.0.1}} From e027caaaa87b711e00b161a2469e3d4dcdeb928d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20Gro=C3=9Fmann?= Date: Sat, 25 Jul 2026 16:34:12 +0200 Subject: [PATCH 2/2] tests: add run-shutdown-longpoll.sh runner + fix README unsol-key example Add a file-based runner so the longpoll acceptance run needs no fragile multi-line shell command (a stray space after a backslash silently breaks the line continuation, which manifests as "RC 2 / : command not found"). It reads API creds from HTTPD_AUTH or MBT_MVS_USER/MBT_MVS_PASS in ./.env, picks a non-matching unsol-key so workers stay parked, and supports a `check` mode that confirms the polls park without issuing P HTTPD. Also fix the README example's unsol-key: IEE136I self-matches `D T`'s own response (IEE136I), so detection fires immediately and no worker parks. Use a non-occurring key. Refs #122 --- tests/README.md | 19 ++++++++++- tests/run-shutdown-longpoll.sh | 61 ++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100755 tests/run-shutdown-longpoll.sh diff --git a/tests/README.md b/tests/README.md index 2a94a54..6ff6e33 100644 --- a/tests/README.md +++ b/tests/README.md @@ -48,6 +48,19 @@ Which shutdown path the run exercises: > A green **`abend`** run does **not** establish the `#179` fix — it tests a > different path. Use **`longpoll`** to gate `#179` (mvslovers/mvsmf#179). +The easiest way to drive this is the bundled runner +(`tests/run-shutdown-longpoll.sh`) — a file, so there is no fragile multi-line +shell command to mis-paste: + +```sh +CONSOLE_LOG=/path/to/hardcopy.log sh tests/run-shutdown-longpoll.sh check # setup check, no P HTTPD +CONSOLE_LOG=/path/to/hardcopy.log sh tests/run-shutdown-longpoll.sh # real run, prompts for P HTTPD +``` + +It reads API credentials from `HTTPD_AUTH` or from `MBT_MVS_USER`/`MBT_MVS_PASS` +in `./.env`, and uses a non-matching `unsol-key` so the workers stay parked; see +the script header for all knobs. To build the poll request by hand instead: + Example `LONGPOLL_CMD` — one synchronous unsolicited-message detection PUT that blocks up to 60 s (mvsMF console **issue-command** endpoint, `PUT /zosmf/restconsoles/consoles/{console-name}` — *not* the `/solmsgs` collect @@ -56,10 +69,14 @@ sub-resource): ```sh LONGPOLL_CMD='curl -s -o /dev/null -u USER:PASS -X PUT \ -H "Content-Type: application/json" \ - -d "{\"cmd\":\"D T\",\"unsol-key\":\"IEE136I\",\"unsol-detect-sync\":\"Y\",\"unsol-detect-timeout\":\"60\"}" \ + -d "{\"cmd\":\"D T\",\"unsol-key\":\"ZZNOMATCHZZ\",\"unsol-detect-sync\":\"Y\",\"unsol-detect-timeout\":\"60\"}" \ http://HOST:PORT/zosmf/restconsoles/consoles/defcn' ``` +> `unsol-key` must be a token that will **not** appear during the poll — do +> **not** use one the command's own response emits (`D T` emits `IEE136I`), or +> detection fires immediately and the worker never parks (→ INCONCLUSIVE). + The long poll must **still be running when `P HTTPD` is issued** — that is the whole point. The in-flight count is measured `LONGPOLL_SETTLE` s after launch, but the stop lands later. In `STOP_ADAPTER=cmd` mode the stop follows diff --git a/tests/run-shutdown-longpoll.sh b/tests/run-shutdown-longpoll.sh new file mode 100755 index 0000000..08a2cd0 --- /dev/null +++ b/tests/run-shutdown-longpoll.sh @@ -0,0 +1,61 @@ +#!/bin/sh +# Convenience runner for tests/shutdown-acceptance.sh in longpoll mode +# (mvsmf#179 / httpd#122). It parks concurrent long-polls so a worker is still +# in flight when P HTTPD lands -- the scenario a fast handler fault cannot +# reproduce, and the only one that actually exercises the #179 quiesce fix. +# +# It is a FILE on purpose: no interactive multi-line command whose line +# continuation a stray space after a backslash can silently break. +# +# Usage: +# sh tests/run-shutdown-longpoll.sh real run: prompts for P HTTPD +# sh tests/run-shutdown-longpoll.sh check non-interactive setup check +# (no P HTTPD; confirms polls park) +# +# Configure via environment (all optional except CONSOLE_LOG + credentials): +# CONSOLE_LOG REQUIRED: MVS console/hardcopy log the test asserts over. +# HTTPD_HOST HTTPD listener host (default: localhost) +# HTTPD_PORT HTTPD listener port (default: 8080) +# CONSOLE_NAME z/OSMF console name (default: defcn) +# HTTPD_AUTH API creds "user:pass"; if unset, falls back to +# MBT_MVS_USER / MBT_MVS_PASS from ./.env +# JOBNAME HTTPD STC jobname (default: HTTPD) +# LONGPOLL_N concurrent long-polls (<= MAXTASK-1) (default: 4) +# LONGPOLL_SETTLE seconds to let them park (default: 3) +set -u + +HOST="${HTTPD_HOST:-localhost}" +PORT="${HTTPD_PORT:-8080}" +CONSOLE="${CONSOLE_NAME:-defcn}" +: "${CONSOLE_LOG:?set CONSOLE_LOG to the MVS console/hardcopy log path}" + +# API auth: HTTPD_AUTH=user:pass, else the deploy creds from ./.env. +if [ -z "${HTTPD_AUTH:-}" ]; then + [ -f ./.env ] && . ./.env # './' so dash reads the CWD, not $PATH + HTTPD_AUTH="${MBT_MVS_USER:?set HTTPD_AUTH, or MBT_MVS_USER in .env}:${MBT_MVS_PASS:?set HTTPD_AUTH, or MBT_MVS_PASS in .env}" +fi + +# JSON body via a temp file -> no quoting nightmare in LONGPOLL_CMD. The unsol-key +# must NOT occur during the poll (do not use a key the command itself emits), so +# the worker blocks the full unsol-detect-timeout and is still parked at P HTTPD. +body=$(mktemp) +trap 'rm -f "$body"' EXIT INT TERM +cat > "$body" <<'JSON' +{"cmd":"D T","unsol-key":"ZZNOMATCHZZ","unsol-detect-sync":"Y","unsol-detect-timeout":"60"} +JSON + +export ENV_FILE=/dev/null # config resolved above; skip the test's own .env source +export CONSOLE_LOG +export HTTPD_HOST="$HOST" HTTPD_PORT="$PORT" +export JOBNAME="${JOBNAME:-HTTPD}" +export PROVOKE=longpoll +export LONGPOLL_N="${LONGPOLL_N:-4}" +export LONGPOLL_SETTLE="${LONGPOLL_SETTLE:-3}" +export LONGPOLL_CMD="curl -s -o /dev/null -u $HTTPD_AUTH -X PUT -H 'Content-Type: application/json' -d @$body http://$HOST:$PORT/zosmf/restconsoles/consoles/$CONSOLE" + +if [ "${1:-}" = check ]; then # non-interactive: prove the polls park, no real stop + export STOP_ADAPTER=cmd STOP_CMD=true WAIT_SECS="${WAIT_SECS:-2}" +fi + +dir=$(dirname "$0") +sh "$dir/shutdown-acceptance.sh"