Skip to content
Open
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
26 changes: 26 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Tests

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

jobs:
mqtt-shell:
name: MQTT shell behavior
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Check shell syntax
run: |
dash -n docker/mqtt.sh
dash -n docker/mqtt-summary.sh
dash -n tests/mqtt-summary-test.sh

- name: Run MQTT summary tests
run: dash tests/mqtt-summary-test.sh
1 change: 1 addition & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ RUN apk add --no-cache mosquitto-clients curl jq

# Add scripts
COPY mqtt.sh /app/mqtt.sh
COPY mqtt-summary.sh /app/mqtt-summary.sh
COPY entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/mqtt.sh /app/entrypoint.sh

Expand Down
20 changes: 20 additions & 0 deletions docker/mqtt-summary.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/sh

# Write an Orb summary to stdout while keeping the per-invocation startup
# banner out of normal logs. File descriptor 3 carries the real stdout out of
# the command substitution so only stderr is captured in ORB_ERRS. Tests may
# pass a stub Orb executable as the first argument.
emit_orb_summary() {
(
ORB_ERRS=$("${1:-/app/orb}" summary 2>&1 >&3)
ORB_RC=$?

if [ "$ORB_RC" -ne 0 ]; then
echo "orb summary failed (exit $ORB_RC):" >&2
[ -n "$ORB_ERRS" ] && printf '%s\n' "$ORB_ERRS" >&2
echo '{}'
elif [ "$DEBUG_MODE" = "true" ] && [ -n "$ORB_ERRS" ]; then
printf '%s\n' "$ORB_ERRS" >&2
fi
) 3>&1
}
21 changes: 4 additions & 17 deletions docker/mqtt.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/bin/sh

. /app/mqtt-summary.sh

AUTH_HEADER="Authorization: Bearer ${SUPERVISOR_TOKEN}"

CONFIG_PATH=/data/options.json
Expand Down Expand Up @@ -116,26 +118,11 @@ while true; do
# sleep first to avoid publishing zeroes on first iteration
sleep "$MQTT_PAUSE"

# output a single line to mosquitto_pub. orb summary prints a startup
# banner to stderr on every invocation, which would flood the journal at
# MQTT_PAUSE cadence -- so capture stderr in a variable (fd 3 carries the
# real stdout out of the command substitution) and only replay it when
# orb fails (it's the only diagnostic we have then) or mqtt_debug is on.
{
ORB_ERRS=$(/app/orb summary 2>&1 >&3)
ORB_RC=$?
if [ "$ORB_RC" -ne 0 ]; then
echo "orb summary failed (exit $ORB_RC):" >&2
[ -n "$ORB_ERRS" ] && printf '%s\n' "$ORB_ERRS" >&2
echo '{}'
elif [ "$DEBUG_MODE" = "true" ] && [ -n "$ORB_ERRS" ]; then
printf '%s\n' "$ORB_ERRS" >&2
fi
} 3>&1 | jq -c .
# output a single line to mosquitto_pub
emit_orb_summary | jq -c .

done | mosquitto_pub -h "$MQTT_HOST" -p "$MQTT_PORT" -u "$MQTT_USER" -P "$MQTT_PASS" \
-t "$STATE_TOPIC" -l -r -k $((MQTT_PAUSE * 2))

echo "mosquitto_pub disconnected, reconnecting..."
done

98 changes: 98 additions & 0 deletions tests/mqtt-summary-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/bin/sh

set -eu

TEST_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
. "$TEST_DIR/../docker/mqtt-summary.sh"

TEST_TMP=$(mktemp -d "${TMPDIR:-/tmp}/mqtt-summary-test.XXXXXX")
trap 'rm -rf "$TEST_TMP"' EXIT HUP INT TERM

TEST_NUMBER=0

orb_stub() {
if [ "$#" -ne 1 ] || [ "$1" != "summary" ]; then
printf 'unexpected orb arguments\n' >&2
return 99
fi

[ -n "$STUB_STDOUT" ] && printf '%s\n' "$STUB_STDOUT"
[ -n "$STUB_STDERR" ] && printf '%s\n' "$STUB_STDERR" >&2
return "$STUB_STATUS"
}

fail() {
printf 'not ok %s - %s\n' "$TEST_NUMBER" "$1"
exit 1
}

assert_file() {
ASSERT_NAME=$1
ASSERT_FILE=$2
ASSERT_EXPECTED=$3
EXPECTED_FILE="$TEST_TMP/expected"

printf '%b' "$ASSERT_EXPECTED" >"$EXPECTED_FILE"
if ! cmp -s "$EXPECTED_FILE" "$ASSERT_FILE"; then
printf '%s mismatch:\n' "$ASSERT_NAME" >&2
diff -u "$EXPECTED_FILE" "$ASSERT_FILE" >&2 || true
return 1
fi
}

run_case() {
CASE_NAME=$1
DEBUG_MODE=$2
STUB_STATUS=$3
STUB_STDOUT=$4
STUB_STDERR=$5
EXPECTED_STDOUT=$6
EXPECTED_STDERR=$7
TEST_NUMBER=$((TEST_NUMBER + 1))

if ! emit_orb_summary orb_stub >"$TEST_TMP/stdout" 2>"$TEST_TMP/stderr"; then
fail "$CASE_NAME returned a non-zero status"
fi

assert_file stdout "$TEST_TMP/stdout" "$EXPECTED_STDOUT" || fail "$CASE_NAME stdout"
assert_file stderr "$TEST_TMP/stderr" "$EXPECTED_STDERR" || fail "$CASE_NAME stderr"
printf 'ok %s - %s\n' "$TEST_NUMBER" "$CASE_NAME"
}

printf '1..4\n'

run_case \
'success suppresses stderr when debug is off' \
false 0 \
'{"orb_score":42}' \
'Starting Orb
Config directory: /data' \
'{"orb_score":42}\n' \
''

run_case \
'success replays stderr when debug is on' \
true 0 \
'{"orb_score":42}' \
'Starting Orb
Config directory: /data' \
'{"orb_score":42}\n' \
'Starting Orb\nConfig directory: /data\n'

run_case \
'failure logs diagnostics when debug is off' \
false 7 \
'' \
'summary unavailable
connection refused' \
'{}\n' \
'orb summary failed (exit 7):\nsummary unavailable\nconnection refused\n'

run_case \
'failure logs diagnostics when debug is on' \
true 7 \
'' \
'summary unavailable
connection refused' \
'{}\n' \
'orb summary failed (exit 7):\nsummary unavailable\nconnection refused\n'