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
2 changes: 1 addition & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ compiles XMRig and mines. Each layer covers what the one below it has to stub.
| **Coverage gate** | [`coverage.sh`](coverage.sh) | kcov in **Docker**. **In CI.** | Line coverage of `rigforge.sh` + `util/proposed-grub.sh` by running `run.sh` under kcov; enforces the committed floor ([`coverage-floor.txt`](coverage-floor.txt)) plus a patch-coverage gate (diff-cover) on changed lines. | `make coverage` |
| **Release smoke (quick)** | [`smoke.sh`](smoke.sh) | Real Linux rig, **manual**. Not in CI. | The compiled binary actually starts and hashes (`xmrig --bench`, fully offline). Fast pre-tag confidence that the worker we ship runs. | `make smoke` |
| **Release e2e (full)** | [`e2e-real.sh`](e2e-real.sh) | Real Linux rig, **manual, root**. Not in CI. | The real thing end to end: build + tune + kernel tuning + service + a real hash, the writable control path (#236) against real systemd (`control` phase), the remote-upgrade chain with real git as the root oneshot (`upgrade` phase, #322), then a clean uninstall. **The release gate.** | `make e2e-real` (see [`RELEASING.md`](../RELEASING.md)) |
| **Release e2e (worker↔stack)** | [`e2e-pithead.sh`](e2e-pithead.sh) ([`control` phase](e2e-pithead-control.sh), [`access-tokens` phase](e2e-pithead-tokens.sh)) | Real rig + a **live Pithead stack**, **manual, root**. Not in CI (`connect`/`control`/`access-tokens` also run as a `rigforge`/`tier4-e2e` bench-ci job). | The integration contract: mining round-trip against the stack, the `:8080` API posture (open/token/`restricted`), the writable control path's DONATION apply round trip (#344/#509), a named `ACCESS_TOKENS` entry authenticating `:8081`/`:8082` while staying refused on `:8080` and after revocation (#516), stratum auth accept/reject + rotation, dashboard visibility/drop-off, dev-fee independence, and the sister-API **hashrate-impact guard** (#99 must not shave H/s under polling load). | `PITHEAD_URL=host:3333 sudo -E make e2e-pithead` (see [`RELEASING.md`](../RELEASING.md)) |
| **Release e2e (worker↔stack)** | [`e2e-pithead.sh`](e2e-pithead.sh) ([`control` phase](e2e-pithead-control.sh), [`access-tokens` phase](e2e-pithead-tokens.sh)) | Real rig + a **live Pithead stack**, **manual, root**. Not in CI (`connect`/`control`/`access-tokens` also run as a `rigforge`/`tier4-e2e` bench-ci job). | The integration contract: the `network` phase records fixed boolean summary diagnostics (`network-summary`) before enforcing metadata and the XMRig key superset, without logging response bodies or key names; `network-sequence` reproduces the nightly `connect` → `worker-api` → `api-impact` → `network` prefix in one process/snapshot and stops there (separate phase submissions restore between phases); mining round-trip against the stack, the `:8080` API posture (open/token/`restricted`), the writable control path's DONATION apply round trip (#344/#509), a named `ACCESS_TOKENS` entry authenticating `:8081`/`:8082` while staying refused on `:8080` and after revocation (#516), stratum auth accept/reject + rotation, dashboard visibility/drop-off, dev-fee independence, and the sister-API **hashrate-impact guard** (#99 must not shave H/s under polling load). | `PITHEAD_URL=host:3333 sudo -E make e2e-pithead` (see [`RELEASING.md`](../RELEASING.md)) |

The unit + black-box suite, the Linux container e2e, and the coverage gate run automatically on every
push/PR (see [`.github/workflows/ci.yml`](../.github/workflows/ci.yml)). The native macOS e2e is
Expand Down
10 changes: 5 additions & 5 deletions tests/e2e-pithead.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ RIGFORGE="$HERE/rigforge.sh"
CFG="$HERE/config.json"
source "$(dirname "${BASH_SOURCE[0]}")/e2e-pithead-control.sh" && source "$(dirname "${BASH_SOURCE[0]}")/e2e-pithead-tokens.sh" # phase_control (#509), phase_access_tokens (#516)

source "$(dirname "${BASH_SOURCE[0]}")/e2e-control-replay.sh"
source "$(dirname "${BASH_SOURCE[0]}")/e2e-control-replay.sh" && source "$HERE/tests/e2e-summary-diagnostics.sh"
# bench-ci-fixture: pithead-control control-replay-thermal control-replay-pools
PASS=0 FAIL=0 E2E_EXIT_RC=0
ok() {
Expand Down Expand Up @@ -370,16 +370,15 @@ phase_network() {
else
bad "miner has unexpected listeners: $xl"
fi
# Require the documented metadata, then compare the remaining keys exactly with XMRig's API.
k1=$(api8080 http://127.0.0.1:8080/2/summary | jq -cS 'keys' 2>/dev/null || true)
k2=$(api8081 http://127.0.0.1:8081/2/summary)
printf '%s\n' "${k1:-null}" "${k2:-null}" | summary_contract_diagnostics
if [ -n "$k1" ] && printf '%s' "$k2" | jq -e '(.rigforge | type) == "object" and (.generated_at | type) == "string" and (.generated_at as $g | [try ($g | fromdateiso8601 | strftime("%Y-%m-%dT%H:%M:%SZ")) catch empty] == [$g])' >/dev/null 2>&1 &&
[ "$k1" = "$(printf '%s' "$k2" | jq -cS 'del(.rigforge, .generated_at) | keys' 2>/dev/null || true)" ]; then
ok "wire superset: sister /2/summary = xmrig's keys + rigforge + generated_at"
else
bad "sister summary lacks valid metadata or its XMRig key superset differs"
fi
# No response on either port may expose the configured token or pool password.
set_cfg '.ACCESS_TOKEN = "tok-net1" | .pools[0].pass = "pass-net1"'
sleep 3
sweep=$(
Expand Down Expand Up @@ -534,7 +533,7 @@ summary() {
require_preflight "$@"
# Validate the phase against its own `phase_<name>` function: a typo dies with the rig untouched.
# #183: the lock follows, before snapshot_config (whose _cleanup runs `apply`) and the first API touch.
[ "${1:-all}" = all ] || declare -F "phase_${1//-/_}" >/dev/null || die "unknown phase '$1' (connect|worker-api|api-impact|network|stratum-auth|dashboard|dev-fee|control|control-replay-thermal|control-replay-pools|access-tokens|all)"
[ "${1:-all}" = all ] || declare -F "phase_${1//-/_}" >/dev/null || die "unknown phase '$1' (connect|worker-api|api-impact|network|network-sequence|stratum-auth|dashboard|dev-fee|control|control-replay-thermal|control-replay-pools|access-tokens|all)"
replay_lock "${1:-all}"

snapshot_config
Expand All @@ -543,6 +542,7 @@ connect) phase_connect ;;
worker-api) phase_worker_api ;;
api-impact) phase_api_impact ;;
network) phase_network ;;
network-sequence) phase_network_sequence ;;
stratum-auth) phase_stratum_auth ;;
dashboard) phase_dashboard ;;
dev-fee) phase_dev_fee ;;
Expand All @@ -555,7 +555,7 @@ all)
[ "$FAIL" -eq 0 ] || break
done
;;
*) die "unknown phase '$1' (connect|worker-api|api-impact|network|stratum-auth|dashboard|dev-fee|control|control-replay-thermal|control-replay-pools|access-tokens|all)" ;;
*) die "unknown phase '$1' (connect|worker-api|api-impact|network|network-sequence|stratum-auth|dashboard|dev-fee|control|control-replay-thermal|control-replay-pools|access-tokens|all)" ;;
esac
_cleanup || bad "pre-test config/runtime restoration failed; snapshot retained at $SAVED_CFG"
trap - EXIT
Expand Down
28 changes: 28 additions & 0 deletions tests/e2e-summary-diagnostics.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# shellcheck shell=bash
# Fixed-schema observations only: never echo bodies, key names, timestamps or parser errors.
summary_contract_diagnostics() {
local result
result=$(jq -cs '
if length != 2 then error("record count") else . end |
.[0] as $keys | .[1] as $body |
(if ($body | type) == "object" then $body else {} end) as $s |
{json_valid: true,
worker_keys: (($keys | type) == "array"),
sister_object: (($body | type) == "object"),
rigforge_object: (($s.rigforge | type) == "object"),
generated_at_valid: (try ($s.generated_at as $g |
($g | type) == "string" and
[($g | fromdateiso8601 | strftime("%Y-%m-%dT%H:%M:%SZ"))] == [$g]) catch false),
xmrig_unreachable: (try ($s.rigforge.xmrig_api == "unreachable") catch false),
keys_match: ($keys == ($s | del(.rigforge, .generated_at) | keys))}
' 2>/dev/null) || result='{"json_valid":false}'
printf 'network-summary: %s\n' "$result"
}
# Preserve the nightly prefix in one snapshot, without running unrelated later phases.
phase_network_sequence() {
local run_phase
for run_phase in phase_connect phase_worker_api phase_api_impact phase_network; do
"$run_phase"
[ "$FAIL" -eq 0 ] || return 1
done
}
4 changes: 3 additions & 1 deletion tests/test-e2e-gate-fail-closed.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# shellcheck disable=SC2016,SC2034,SC2329
# Hardware-free regression for the release-gate failure plumbing (#491).
echo "== unit: release e2e gates fail closed (#491) =="
source "$ROOT/tests/test-summary-diagnostics.sh"
source "$ROOT/tests/e2e-refresh-window.sh"
T491="$(mktemp -d "$SANDBOX/gate491.XXXXXX")"

Expand Down Expand Up @@ -64,7 +65,7 @@ pit_dispatch_case() { # <phase arg> -> what-ran:rc
out="$(
eval "$PIT_DIE"
FAIL=0
for p in connect worker_api api_impact network stratum_auth dashboard dev_fee control access_tokens; do
for p in connect worker_api api_impact network network_sequence stratum_auth dashboard dev_fee control access_tokens; do
eval "phase_$p() { printf '%s ' $p; }"
done
set -- "$1"
Expand All @@ -73,6 +74,7 @@ pit_dispatch_case() { # <phase arg> -> what-ran:rc
rc=$?
printf '%s:%s\n' "${out:-nothing}" "$rc"
}
assert_eq "dispatch selects the network sequence" "$(pit_dispatch_case network-sequence)" "network_sequence :0"
assert_eq "dispatch runs the named phase" "$(pit_dispatch_case worker-api)" "worker_api :0"
assert_eq "dispatch runs every phase for 'all'" "$(pit_dispatch_case all)" "connect worker_api api_impact network stratum_auth dashboard dev_fee control access_tokens :0"
assert_eq "an underscored typo dies instead of silently passing with nothing run" "$(pit_dispatch_case worker_api)" "nothing:2"
Expand Down
90 changes: 90 additions & 0 deletions tests/test-summary-diagnostics.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# shellcheck shell=bash
# shellcheck disable=SC2034
# Hardware-free diagnosis of #563; raw response fields must never reach the job log.
source "$ROOT/tests/e2e-summary-diagnostics.sh"
summary_diag_case() { printf '%s\n' "$1" "$2" | summary_contract_diagnostics; }
DIAG_GOOD='{"hashrate":{},"rigforge":{},"generated_at":"2026-10-02T06:00:00Z"}'
diag=$(summary_diag_case '["hashrate"]' "$DIAG_GOOD")
assert_eq "summary diagnostics: valid superset classified" "$diag" 'network-summary: {"json_valid":true,"worker_keys":true,"sister_object":true,"rigforge_object":true,"generated_at_valid":true,"xmrig_unreachable":false,"keys_match":true}'
assert_eq "summary diagnostics: absent record sanitized" "$(summary_diag_case '["hashrate"]' '')" 'network-summary: {"json_valid":false}'
for payload in 'null' '[]' '"secret-marker"' 'true' '42'; do
diag=$(summary_diag_case '["hashrate"]' "$payload")
assert_contains "summary diagnostics: missing/non-object sister classified" "$diag" '"sister_object":false'
done
for stamp in '' 'secret-marker' '2026-10-02T06:00:00+00:00'; do
diag=$(summary_diag_case '["hashrate"]' "$(printf '%s' "$DIAG_GOOD" | jq --arg s "$stamp" '.generated_at=$s')")
assert_contains "summary diagnostics: invalid timestamp classified" "$diag" '"generated_at_valid":false'
assert_absent "summary diagnostics: timestamp value omitted" "$diag" 'secret-marker'
done
diag=$(summary_diag_case '["hashrate"]' '{"rigforge":{"xmrig_api":"unreachable","secret":"secret-marker"},"generated_at":"2026-10-02T06:00:00Z"}')
assert_contains "summary diagnostics: unreachable snapshot distinguished" "$diag" '"xmrig_unreachable":true'
assert_contains "summary diagnostics: missing XMRig keys distinguished" "$diag" '"keys_match":false'
assert_absent "summary diagnostics: nested secrets omitted" "$diag" 'secret-marker'
diag=$(summary_diag_case '["secret-marker"]' "$DIAG_GOOD")
assert_absent "summary diagnostics: key names omitted" "$diag" 'secret-marker'
assert_eq "summary diagnostics: malformed JSON sanitized" "$(summary_diag_case '["hashrate"]' '{secret-marker')" 'network-summary: {"json_valid":false}'
assert_eq "summary diagnostics: multiple records refused" "$(summary_diag_case '[]' '{} {}')" 'network-summary: {"json_valid":false}'
# Exercise phase wiring and keep the original failing assertion fatal.
(
eval "$(sed -n '/^phase_network()/,/^}/p' "$ROOT/tests/e2e-pithead.sh")"
HERE="$ROOT" PITHEAD_URL=pool.invalid:3333
phase() { :; }
set_cfg() { :; }
sleep() { :; }
ss() {
case "$1" in
-Htnp) printf 'ESTAB 0 0 local pool.invalid:3333 xmrig\n' ;;
-Htln) printf 'LISTEN 0 0 *:8081 peer\n' ;;
-Htlnp) printf 'LISTEN 0 0 *:8080 peer xmrig\n' ;;
esac
}
api8080() { printf '{"hashrate":{}}'; }
api8081() { printf '{"rigforge":{"xmrig_api":"unreachable"}}'; }
ok() { :; }
bad() {
printf 'FAIL:%s\n' "$1"
return 1
}
set -e
phase_network
) >"$SANDBOX/summary-diag-phase" 2>&1
rc=$?
assert_rc "summary diagnostics: wire failure remains fatal" "$rc" 1
assert_contains "summary diagnostics: phase emits observations before failure" "$(cat "$SANDBOX/summary-diag-phase")" 'network-summary:'
assert_contains "summary diagnostics: original failing row retained" "$(cat "$SANDBOX/summary-diag-phase")" 'FAIL:sister summary lacks valid metadata or its XMRig key superset differs'
# No inter-phase cleanup: the same shell state must reach the network check.
summary_sequence_case() {
(
set -e
FAIL=0 sequence_state=initial
phase_connect() {
sequence_state=connected
printf 'connect '
}
phase_worker_api() {
[ "$sequence_state" = connected ]
sequence_state=worker
printf 'worker-api '
}
fault=$1
phase_api_impact() {
[ "$sequence_state" = worker ]
sequence_state=loaded
printf 'api-impact '
case "$fault" in fatal) return 7 ;; row) FAIL=1 ;; esac
return 0
}
phase_network() {
[ "$sequence_state" = loaded ]
printf network
}
phase_network_sequence
)
}
assert_eq "network sequence: ordered nightly prefix shares shell state" "$(summary_sequence_case success)" 'connect worker-api api-impact network'
sequence_out=$(summary_sequence_case fatal)
assert_rc "network sequence: fatal phase status propagates" "$?" 7
assert_eq "network sequence: fatal predecessor stops before network" "$sequence_out" 'connect worker-api api-impact '
sequence_out=$(summary_sequence_case row)
assert_rc "network sequence: failed row stops the prefix" "$?" 1
assert_eq "network sequence: failed predecessor row stops before network" "$sequence_out" 'connect worker-api api-impact '