From fc6eaf37d2769b145333d652e6bd287e4d37ea15 Mon Sep 17 00:00:00 2001 From: Jan W Date: Fri, 17 Jul 2026 15:38:23 +0200 Subject: [PATCH 01/15] test(write-program-buffer): add program fixtures Three prebuilt SBF binaries for the integration tests: small (544 B), big (4,680 B, delta 4,136 for the SIMD-0431 clamp scenario), and huge (10,482,400 B unpacked, gzipped to 10 KB) for the near-max scenario. Built once with cargo-build-sbf --arch v3 and deploy-verified against a local all-features validator; README documents regeneration. --- .../tests/fixtures/program-big.so | Bin 0 -> 4680 bytes .../tests/fixtures/program-huge.so.gz | Bin 0 -> 10431 bytes .../tests/fixtures/program-small.so | Bin 0 -> 544 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100755 write-program-buffer/tests/fixtures/program-big.so create mode 100644 write-program-buffer/tests/fixtures/program-huge.so.gz create mode 100755 write-program-buffer/tests/fixtures/program-small.so diff --git a/write-program-buffer/tests/fixtures/program-big.so b/write-program-buffer/tests/fixtures/program-big.so new file mode 100755 index 0000000000000000000000000000000000000000..ce6700a5df5b01f69095dc25fdcd87fd0d394c99 GIT binary patch literal 4680 zcmeI0Ee^s!5QV2dfl6?|2~gFQ7zlz(lt6+bX;+XSI0Xmb6iBXvtV5Q4-5qF(tRrud z-PxJ9vrRwwF3Zg-3iX_Vx%w=r*`JK0<)Ji%$+wag$6ao6mHTGcEBJKSc}RT~^-ol8{{woM#TuaS cfm;0)^;1;q&JV2sZ>Jk`@B2(U0^V-N8~Z*el>h($ literal 0 HcmV?d00001 diff --git a/write-program-buffer/tests/fixtures/program-huge.so.gz b/write-program-buffer/tests/fixtures/program-huge.so.gz new file mode 100644 index 0000000000000000000000000000000000000000..a3e7fb6ec5ef73a419db4a2f0e661e8645f1928a GIT binary patch literal 10431 zcmb2|=HQsE7nQ}tTu_vsUX+-tn^Bscs#l!P@aBvm-ys7Y)&O>^FCMbHFRUz2daHZt zqJ(5(lIp~yPOqMx_nzm<=bX5HdeuYG?u^4XQ&XAJ)>W*7ydAut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF z0;3@?8Un*H1mwRxZ~*)3#Q&{=A)%vSGz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0q zLtr!nMnhn@hrq45)iO*?8Vm=TS=REk-?&v8=20TFY{HHP?iGbxs>cJ@39k{=o{^z) z>b-Yd+_S$=c!%SN$f%!2Ltr!nMnhmU1V%$(_=bS}YjdX;ZPULRhJfjWMqH=W`^$!Fdm3@fJ!Jp1)0HO77WZl85ST35@rG77f^NR6bDoQod)Rx z;SWd}pjLpz1)$5i6((=KLA9M B5n2EM literal 0 HcmV?d00001 From f1263b673155a818dfdeb1fd6005f079d1c51cda Mon Sep 17 00:00:00 2001 From: Jan W Date: Fri, 17 Jul 2026 16:07:53 +0200 Subject: [PATCH 02/15] test(write-program-buffer): add validator harness and fresh-program scenario The integration workflow boots a local test validator per matrix cell (solana 3.1.14 and 4.1.2 via setup-all) and runs the action against it. The fresh path asserts the buffer contents match the artifact, the authority stays with the deployer, and no resize or transfer happens. --- .../workflows/test-write-program-buffer.yaml | 52 +++++++++++++++++++ .../tests/integration/assert-fresh.sh | 30 +++++++++++ .../tests/integration/prepare-fresh.sh | 31 +++++++++++ .../tests/integration/start-validator.sh | 21 ++++++++ 4 files changed, 134 insertions(+) create mode 100644 .github/workflows/test-write-program-buffer.yaml create mode 100755 write-program-buffer/tests/integration/assert-fresh.sh create mode 100755 write-program-buffer/tests/integration/prepare-fresh.sh create mode 100755 write-program-buffer/tests/integration/start-validator.sh diff --git a/.github/workflows/test-write-program-buffer.yaml b/.github/workflows/test-write-program-buffer.yaml new file mode 100644 index 0000000..d4e7f83 --- /dev/null +++ b/.github/workflows/test-write-program-buffer.yaml @@ -0,0 +1,52 @@ +name: Test write-program-buffer + +on: + pull_request: + workflow_dispatch: + +jobs: + integration: + name: Integration (solana ${{ matrix.solana-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + solana-version: ["3.1.14", "4.1.2"] + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - uses: ./setup-all + with: + solana_version: ${{ matrix.solana-version }} + + - name: Start local validator + shell: bash + run: ./write-program-buffer/tests/integration/start-validator.sh + + - name: Prepare fresh-program scenario + id: prepare-fresh + shell: bash + run: ./write-program-buffer/tests/integration/prepare-fresh.sh + + - name: Run action for fresh program + id: run-fresh + uses: ./write-program-buffer + with: + program-id: ${{ steps.prepare-fresh.outputs.program-id }} + program: fixture-fresh + rpc-url: http://127.0.0.1:8899 + keypair: ${{ steps.prepare-fresh.outputs.keypair }} + buffer-authority-address: ${{ steps.prepare-fresh.outputs.deployer }} + + - name: Assert fresh-program scenario + shell: bash + env: + BUFFER: ${{ steps.run-fresh.outputs.buffer }} + PROGRAM_ID: ${{ steps.prepare-fresh.outputs.program-id }} + DEPLOYER: ${{ steps.prepare-fresh.outputs.deployer }} + run: ./write-program-buffer/tests/integration/assert-fresh.sh + + - name: Dump validator log on failure + if: failure() + shell: bash + run: tail -100 "$RUNNER_TEMP/test-ledger/validator.log" || true diff --git a/write-program-buffer/tests/integration/assert-fresh.sh b/write-program-buffer/tests/integration/assert-fresh.sh new file mode 100755 index 0000000..14c1c91 --- /dev/null +++ b/write-program-buffer/tests/integration/assert-fresh.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -euo pipefail + +RPC_URL="http://127.0.0.1:8899" +ARTIFACT="target/deploy/fixture-fresh.so" + +fail() { + echo "ASSERT FAIL: $1" >&2 + exit 1 +} + +[ -n "${BUFFER:-}" ] || fail "action did not output a buffer address" +[ -n "${PROGRAM_ID:-}" ] || fail "PROGRAM_ID env not set" +[ -n "${DEPLOYER:-}" ] || fail "DEPLOYER env not set" + +DUMP="$(mktemp)" +solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER" +cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact" +rm -f "$DUMP" + +BUFFER_INFO=$(solana program show "$BUFFER" -u "$RPC_URL") +echo "$BUFFER_INFO" +AUTHORITY=$(echo "$BUFFER_INFO" | grep "Authority:" | awk '{print $2}') +[ "$AUTHORITY" = "$DEPLOYER" ] || fail "buffer authority is $AUTHORITY, expected deployer $DEPLOYER" + +if solana program show "$PROGRAM_ID" -u "$RPC_URL" 2>&1 | grep -q "Data Length:"; then + fail "program $PROGRAM_ID unexpectedly exists" +fi + +echo "Fresh-program scenario assertions passed" diff --git a/write-program-buffer/tests/integration/prepare-fresh.sh b/write-program-buffer/tests/integration/prepare-fresh.sh new file mode 100755 index 0000000..7ddc75a --- /dev/null +++ b/write-program-buffer/tests/integration/prepare-fresh.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail + +RPC_URL="http://127.0.0.1:8899" +FIXTURES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../fixtures" && pwd)" +SCENARIO_DIR="${RUNNER_TEMP:-/tmp}/scenario-fresh" + +mkdir -p "$SCENARIO_DIR" target/deploy + +solana-keygen new -s --no-bip39-passphrase --force -o "$SCENARIO_DIR/deployer.json" >/dev/null +solana-keygen new -s --no-bip39-passphrase --force -o "$SCENARIO_DIR/program-id.json" >/dev/null +DEPLOYER=$(solana-keygen pubkey "$SCENARIO_DIR/deployer.json") +PROGRAM_ID=$(solana-keygen pubkey "$SCENARIO_DIR/program-id.json") + +for i in 1 2 3; do + solana airdrop 100 "$DEPLOYER" -u "$RPC_URL" && break + if [ "$i" -eq 3 ]; then + echo "Airdrop failed after 3 attempts" >&2 + exit 1 + fi + sleep 2 +done + +cp "$FIXTURES_DIR/program-small.so" target/deploy/fixture-fresh.so + +echo "Prepared fresh scenario: deployer=$DEPLOYER program-id=$PROGRAM_ID" +{ + echo "keypair=$(cat "$SCENARIO_DIR/deployer.json")" + echo "deployer=$DEPLOYER" + echo "program-id=$PROGRAM_ID" +} >> "$GITHUB_OUTPUT" diff --git a/write-program-buffer/tests/integration/start-validator.sh b/write-program-buffer/tests/integration/start-validator.sh new file mode 100755 index 0000000..f524777 --- /dev/null +++ b/write-program-buffer/tests/integration/start-validator.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +RPC_URL="http://127.0.0.1:8899" +LEDGER_DIR="${RUNNER_TEMP:-/tmp}/test-ledger" + +rm -rf "$LEDGER_DIR" +solana-test-validator --reset --quiet --ledger "$LEDGER_DIR" > "${RUNNER_TEMP:-/tmp}/validator-stdout.log" 2>&1 & +echo "Validator started with PID $!" + +for i in $(seq 1 60); do + if solana cluster-version -u "$RPC_URL" >/dev/null 2>&1; then + echo "Validator healthy after ${i}s" + exit 0 + fi + sleep 1 +done + +echo "Validator did not become healthy within 60s" >&2 +tail -50 "$LEDGER_DIR/validator.log" 2>/dev/null || true +exit 1 From 2ac9ec46c6fcc413e31ce859f48278dfe12c1bb4 Mon Sep 17 00:00:00 2001 From: Jan W Date: Fri, 17 Jul 2026 16:10:41 +0200 Subject: [PATCH 03/15] feat(start-test-validator): extract validator startup into reusable action Consumer repos and this repo's own integration tests share the same need: boot a local test validator and wait for health. The action exposes rpc-url and ledger-dir outputs; the test workflow now consumes it instead of a scenario-local script. --- .../workflows/test-write-program-buffer.yaml | 6 +-- start-test-validator/action.yaml | 53 +++++++++++++++++++ .../tests/integration/start-validator.sh | 21 -------- 3 files changed, 56 insertions(+), 24 deletions(-) create mode 100644 start-test-validator/action.yaml delete mode 100755 write-program-buffer/tests/integration/start-validator.sh diff --git a/.github/workflows/test-write-program-buffer.yaml b/.github/workflows/test-write-program-buffer.yaml index d4e7f83..6cc419f 100644 --- a/.github/workflows/test-write-program-buffer.yaml +++ b/.github/workflows/test-write-program-buffer.yaml @@ -20,8 +20,8 @@ jobs: solana_version: ${{ matrix.solana-version }} - name: Start local validator - shell: bash - run: ./write-program-buffer/tests/integration/start-validator.sh + id: validator + uses: ./start-test-validator - name: Prepare fresh-program scenario id: prepare-fresh @@ -49,4 +49,4 @@ jobs: - name: Dump validator log on failure if: failure() shell: bash - run: tail -100 "$RUNNER_TEMP/test-ledger/validator.log" || true + run: tail -100 "${{ steps.validator.outputs.ledger-dir }}/validator.log" || true diff --git a/start-test-validator/action.yaml b/start-test-validator/action.yaml new file mode 100644 index 0000000..9283a6c --- /dev/null +++ b/start-test-validator/action.yaml @@ -0,0 +1,53 @@ +name: "Start Test Validator" +description: "Starts a local solana-test-validator in the background and waits until it is healthy" + +inputs: + ledger-dir: + description: "Ledger directory (defaults to /test-ledger)" + required: false + default: "" + max-attempts: + description: "Maximum health check attempts, roughly one per second" + required: false + default: "60" + +outputs: + rpc-url: + description: "RPC URL of the started validator" + value: ${{ steps.start.outputs.rpc-url }} + ledger-dir: + description: "Ledger directory of the started validator" + value: ${{ steps.start.outputs.ledger-dir }} + +runs: + using: "composite" + steps: + - name: Start validator + id: start + shell: bash + run: | + RPC_URL="http://127.0.0.1:8899" + LEDGER_DIR="${{ inputs.ledger-dir }}" + if [ -z "$LEDGER_DIR" ]; then + LEDGER_DIR="${RUNNER_TEMP:-/tmp}/test-ledger" + fi + + rm -rf "$LEDGER_DIR" + solana-test-validator --reset --quiet --ledger "$LEDGER_DIR" > "${RUNNER_TEMP:-/tmp}/validator-stdout.log" 2>&1 & + echo "Validator started with PID $!" + + echo "rpc-url=$RPC_URL" >> $GITHUB_OUTPUT + echo "ledger-dir=$LEDGER_DIR" >> $GITHUB_OUTPUT + + - name: Wait for validator health + uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0 + with: + timeout_seconds: 5 + max_attempts: ${{ inputs.max-attempts }} + retry_wait_seconds: 1 + command: solana cluster-version -u ${{ steps.start.outputs.rpc-url }} + + - name: Dump validator log if unhealthy + if: failure() + shell: bash + run: tail -50 "${{ steps.start.outputs.ledger-dir }}/validator.log" 2>/dev/null || true diff --git a/write-program-buffer/tests/integration/start-validator.sh b/write-program-buffer/tests/integration/start-validator.sh deleted file mode 100755 index f524777..0000000 --- a/write-program-buffer/tests/integration/start-validator.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -RPC_URL="http://127.0.0.1:8899" -LEDGER_DIR="${RUNNER_TEMP:-/tmp}/test-ledger" - -rm -rf "$LEDGER_DIR" -solana-test-validator --reset --quiet --ledger "$LEDGER_DIR" > "${RUNNER_TEMP:-/tmp}/validator-stdout.log" 2>&1 & -echo "Validator started with PID $!" - -for i in $(seq 1 60); do - if solana cluster-version -u "$RPC_URL" >/dev/null 2>&1; then - echo "Validator healthy after ${i}s" - exit 0 - fi - sleep 1 -done - -echo "Validator did not become healthy within 60s" >&2 -tail -50 "$LEDGER_DIR/validator.log" 2>/dev/null || true -exit 1 From 66621fcc7303770729a8f0ed0fe09c83f0d794c7 Mon Sep 17 00:00:00 2001 From: Jan W Date: Fri, 17 Jul 2026 16:41:08 +0200 Subject: [PATCH 04/15] refactor(test): run scenarios as a matrix dimension Each scenario becomes its own matrix cell alongside the solana version, giving per-scenario check reporting and full isolation. A shared output contract on the prepare scripts (keypair, deployer, program-id, buffer-authority, pre-len) keeps the action invocation scenario-agnostic. --- .../workflows/test-write-program-buffer.yaml | 35 ++++++++++--------- .../tests/integration/prepare-fresh.sh | 1 + 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test-write-program-buffer.yaml b/.github/workflows/test-write-program-buffer.yaml index 6cc419f..7f7df83 100644 --- a/.github/workflows/test-write-program-buffer.yaml +++ b/.github/workflows/test-write-program-buffer.yaml @@ -6,12 +6,13 @@ on: jobs: integration: - name: Integration (solana ${{ matrix.solana-version }}) + name: ${{ matrix.scenario }} (solana ${{ matrix.solana-version }}) runs-on: ubuntu-latest strategy: fail-fast: false matrix: solana-version: ["3.1.14", "4.1.2"] + scenario: [fresh] steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 @@ -23,28 +24,30 @@ jobs: id: validator uses: ./start-test-validator - - name: Prepare fresh-program scenario - id: prepare-fresh + - name: Prepare scenario + id: prepare shell: bash - run: ./write-program-buffer/tests/integration/prepare-fresh.sh + run: ./write-program-buffer/tests/integration/prepare-${{ matrix.scenario }}.sh - - name: Run action for fresh program - id: run-fresh + - name: Run write-program-buffer action + id: run uses: ./write-program-buffer with: - program-id: ${{ steps.prepare-fresh.outputs.program-id }} - program: fixture-fresh - rpc-url: http://127.0.0.1:8899 - keypair: ${{ steps.prepare-fresh.outputs.keypair }} - buffer-authority-address: ${{ steps.prepare-fresh.outputs.deployer }} + program-id: ${{ steps.prepare.outputs.program-id }} + program: fixture-${{ matrix.scenario }} + rpc-url: ${{ steps.validator.outputs.rpc-url }} + keypair: ${{ steps.prepare.outputs.keypair }} + buffer-authority-address: ${{ steps.prepare.outputs.buffer-authority }} - - name: Assert fresh-program scenario + - name: Assert scenario shell: bash env: - BUFFER: ${{ steps.run-fresh.outputs.buffer }} - PROGRAM_ID: ${{ steps.prepare-fresh.outputs.program-id }} - DEPLOYER: ${{ steps.prepare-fresh.outputs.deployer }} - run: ./write-program-buffer/tests/integration/assert-fresh.sh + BUFFER: ${{ steps.run.outputs.buffer }} + PROGRAM_ID: ${{ steps.prepare.outputs.program-id }} + DEPLOYER: ${{ steps.prepare.outputs.deployer }} + BUFFER_AUTHORITY: ${{ steps.prepare.outputs.buffer-authority }} + PRE_LEN: ${{ steps.prepare.outputs.pre-len }} + run: ./write-program-buffer/tests/integration/assert-${{ matrix.scenario }}.sh - name: Dump validator log on failure if: failure() diff --git a/write-program-buffer/tests/integration/prepare-fresh.sh b/write-program-buffer/tests/integration/prepare-fresh.sh index 7ddc75a..3037ee9 100755 --- a/write-program-buffer/tests/integration/prepare-fresh.sh +++ b/write-program-buffer/tests/integration/prepare-fresh.sh @@ -28,4 +28,5 @@ echo "Prepared fresh scenario: deployer=$DEPLOYER program-id=$PROGRAM_ID" echo "keypair=$(cat "$SCENARIO_DIR/deployer.json")" echo "deployer=$DEPLOYER" echo "program-id=$PROGRAM_ID" + echo "buffer-authority=$DEPLOYER" } >> "$GITHUB_OUTPUT" From a292bf098619b02142085feb98853eaa10ee51c9 Mon Sep 17 00:00:00 2001 From: Jan W Date: Fri, 17 Jul 2026 16:41:37 +0200 Subject: [PATCH 05/15] test(write-program-buffer): add SIMD-0431 clamp regression scenario Deploys the small fixture, grows the artifact by 4,136 bytes, and asserts the programdata account grew by exactly 10,240 bytes - the minimum extend size the clamp must produce. Also verifies the buffer contents match the grown artifact. --- .../workflows/test-write-program-buffer.yaml | 2 +- .../tests/integration/assert-clamp.sh | 26 ++++++++++ .../tests/integration/prepare-clamp.sh | 52 +++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100755 write-program-buffer/tests/integration/assert-clamp.sh create mode 100755 write-program-buffer/tests/integration/prepare-clamp.sh diff --git a/.github/workflows/test-write-program-buffer.yaml b/.github/workflows/test-write-program-buffer.yaml index 7f7df83..6ede821 100644 --- a/.github/workflows/test-write-program-buffer.yaml +++ b/.github/workflows/test-write-program-buffer.yaml @@ -12,7 +12,7 @@ jobs: fail-fast: false matrix: solana-version: ["3.1.14", "4.1.2"] - scenario: [fresh] + scenario: [fresh, clamp] steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 diff --git a/write-program-buffer/tests/integration/assert-clamp.sh b/write-program-buffer/tests/integration/assert-clamp.sh new file mode 100755 index 0000000..fd1ab8e --- /dev/null +++ b/write-program-buffer/tests/integration/assert-clamp.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +RPC_URL="http://127.0.0.1:8899" +ARTIFACT="target/deploy/fixture-clamp.so" +MIN_EXTEND_SIZE=10240 + +fail() { + echo "ASSERT FAIL: $1" >&2 + exit 1 +} + +[ -n "${BUFFER:-}" ] || fail "action did not output a buffer address" +[ -n "${PROGRAM_ID:-}" ] || fail "PROGRAM_ID env not set" +[ -n "${PRE_LEN:-}" ] || fail "PRE_LEN env not set" + +POST_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | awk '{print $3}') +GROWTH=$((POST_LEN - PRE_LEN)) +[ "$GROWTH" -eq "$MIN_EXTEND_SIZE" ] || fail "program grew by $GROWTH bytes, expected exactly $MIN_EXTEND_SIZE" + +DUMP="$(mktemp)" +solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER" +cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact" +rm -f "$DUMP" + +echo "Clamp regression assertions passed: program grew by exactly $MIN_EXTEND_SIZE bytes" diff --git a/write-program-buffer/tests/integration/prepare-clamp.sh b/write-program-buffer/tests/integration/prepare-clamp.sh new file mode 100755 index 0000000..81ff027 --- /dev/null +++ b/write-program-buffer/tests/integration/prepare-clamp.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -euo pipefail + +RPC_URL="http://127.0.0.1:8899" +FIXTURES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../fixtures" && pwd)" +SCENARIO_DIR="${RUNNER_TEMP:-/tmp}/scenario-clamp" + +SMALL_SIZE=$(wc -c < "$FIXTURES_DIR/program-small.so" | tr -d ' ') +BIG_SIZE=$(wc -c < "$FIXTURES_DIR/program-big.so" | tr -d ' ') +DELTA=$((BIG_SIZE - SMALL_SIZE)) +if [ "$DELTA" -le 0 ] || [ "$DELTA" -ge 10240 ]; then + echo "Fixture drift: big-small delta of $DELTA bytes is outside (0, 10240)" >&2 + exit 1 +fi + +mkdir -p "$SCENARIO_DIR" target/deploy + +solana-keygen new -s --no-bip39-passphrase --force -o "$SCENARIO_DIR/deployer.json" >/dev/null +solana-keygen new -s --no-bip39-passphrase --force -o "$SCENARIO_DIR/program-id.json" >/dev/null +DEPLOYER=$(solana-keygen pubkey "$SCENARIO_DIR/deployer.json") +PROGRAM_ID=$(solana-keygen pubkey "$SCENARIO_DIR/program-id.json") + +for i in 1 2 3; do + solana airdrop 100 "$DEPLOYER" -u "$RPC_URL" && break + if [ "$i" -eq 3 ]; then + echo "Airdrop failed after 3 attempts" >&2 + exit 1 + fi + sleep 2 +done + +solana program deploy "$FIXTURES_DIR/program-small.so" \ + --program-id "$SCENARIO_DIR/program-id.json" \ + -u "$RPC_URL" -k "$SCENARIO_DIR/deployer.json" \ + --commitment confirmed + +PRE_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | awk '{print $3}') +if [ -z "$PRE_LEN" ]; then + echo "Could not read deployed program size" >&2 + exit 1 +fi + +cp "$FIXTURES_DIR/program-big.so" target/deploy/fixture-clamp.so + +echo "Prepared clamp scenario: deployer=$DEPLOYER program-id=$PROGRAM_ID pre-len=$PRE_LEN" +{ + echo "keypair=$(cat "$SCENARIO_DIR/deployer.json")" + echo "deployer=$DEPLOYER" + echo "program-id=$PROGRAM_ID" + echo "buffer-authority=$DEPLOYER" + echo "pre-len=$PRE_LEN" +} >> "$GITHUB_OUTPUT" From dc5eea71f3be0fea0ddaaf2a18db98a607ce4787 Mon Sep 17 00:00:00 2001 From: Jan W Date: Fri, 17 Jul 2026 17:06:24 +0200 Subject: [PATCH 06/15] test(write-program-buffer): add buffer authority transfer scenario Deploys the small fixture and hands the action a third keypair as buffer-authority-address. Asserts the buffer's on-chain authority equals that address and no longer equals the deployer - the handoff the Squads flow depends on. --- .../workflows/test-write-program-buffer.yaml | 2 +- .../tests/integration/assert-authority.sh | 27 +++++++++++++ .../tests/integration/prepare-authority.sh | 39 +++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100755 write-program-buffer/tests/integration/assert-authority.sh create mode 100755 write-program-buffer/tests/integration/prepare-authority.sh diff --git a/.github/workflows/test-write-program-buffer.yaml b/.github/workflows/test-write-program-buffer.yaml index 6ede821..14bee18 100644 --- a/.github/workflows/test-write-program-buffer.yaml +++ b/.github/workflows/test-write-program-buffer.yaml @@ -12,7 +12,7 @@ jobs: fail-fast: false matrix: solana-version: ["3.1.14", "4.1.2"] - scenario: [fresh, clamp] + scenario: [fresh, clamp, authority] steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 diff --git a/write-program-buffer/tests/integration/assert-authority.sh b/write-program-buffer/tests/integration/assert-authority.sh new file mode 100755 index 0000000..1933b0e --- /dev/null +++ b/write-program-buffer/tests/integration/assert-authority.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +RPC_URL="http://127.0.0.1:8899" +ARTIFACT="target/deploy/fixture-authority.so" + +fail() { + echo "ASSERT FAIL: $1" >&2 + exit 1 +} + +[ -n "${BUFFER:-}" ] || fail "action did not output a buffer address" +[ -n "${BUFFER_AUTHORITY:-}" ] || fail "BUFFER_AUTHORITY env not set" +[ -n "${DEPLOYER:-}" ] || fail "DEPLOYER env not set" + +BUFFER_INFO=$(solana program show "$BUFFER" -u "$RPC_URL") +echo "$BUFFER_INFO" +AUTHORITY=$(echo "$BUFFER_INFO" | grep "Authority:" | awk '{print $2}') +[ "$AUTHORITY" = "$BUFFER_AUTHORITY" ] || fail "buffer authority is $AUTHORITY, expected $BUFFER_AUTHORITY" +[ "$AUTHORITY" != "$DEPLOYER" ] || fail "buffer authority still equals the deployer" + +DUMP="$(mktemp)" +solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER" +cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact" +rm -f "$DUMP" + +echo "Authority transfer assertions passed" diff --git a/write-program-buffer/tests/integration/prepare-authority.sh b/write-program-buffer/tests/integration/prepare-authority.sh new file mode 100755 index 0000000..4b6bdf3 --- /dev/null +++ b/write-program-buffer/tests/integration/prepare-authority.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -euo pipefail + +RPC_URL="http://127.0.0.1:8899" +FIXTURES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../fixtures" && pwd)" +SCENARIO_DIR="${RUNNER_TEMP:-/tmp}/scenario-authority" + +mkdir -p "$SCENARIO_DIR" target/deploy + +solana-keygen new -s --no-bip39-passphrase --force -o "$SCENARIO_DIR/deployer.json" >/dev/null +solana-keygen new -s --no-bip39-passphrase --force -o "$SCENARIO_DIR/program-id.json" >/dev/null +solana-keygen new -s --no-bip39-passphrase --force -o "$SCENARIO_DIR/authority-target.json" >/dev/null +DEPLOYER=$(solana-keygen pubkey "$SCENARIO_DIR/deployer.json") +PROGRAM_ID=$(solana-keygen pubkey "$SCENARIO_DIR/program-id.json") +AUTHORITY_TARGET=$(solana-keygen pubkey "$SCENARIO_DIR/authority-target.json") + +for i in 1 2 3; do + solana airdrop 100 "$DEPLOYER" -u "$RPC_URL" && break + if [ "$i" -eq 3 ]; then + echo "Airdrop failed after 3 attempts" >&2 + exit 1 + fi + sleep 2 +done + +solana program deploy "$FIXTURES_DIR/program-small.so" \ + --program-id "$SCENARIO_DIR/program-id.json" \ + -u "$RPC_URL" -k "$SCENARIO_DIR/deployer.json" \ + --commitment confirmed + +cp "$FIXTURES_DIR/program-small.so" target/deploy/fixture-authority.so + +echo "Prepared authority scenario: deployer=$DEPLOYER program-id=$PROGRAM_ID authority-target=$AUTHORITY_TARGET" +{ + echo "keypair=$(cat "$SCENARIO_DIR/deployer.json")" + echo "deployer=$DEPLOYER" + echo "program-id=$PROGRAM_ID" + echo "buffer-authority=$AUTHORITY_TARGET" +} >> "$GITHUB_OUTPUT" From 2e948be086e3b8f4a32a9e150462875744b044cc Mon Sep 17 00:00:00 2001 From: Jan W Date: Fri, 17 Jul 2026 17:07:00 +0200 Subject: [PATCH 07/15] test(write-program-buffer): add near-max extend scenario Extends a deployed program to within 5,315 bytes of the 10 MiB cap, then runs the action with the padded 10,482,400-byte artifact. The action must extend by exactly the remaining headroom - the SIMD-0431 carve-out where the 10 KiB minimum does not apply - landing the account at precisely 10,485,760 bytes. The setup extend retries across block boundaries since the loader rejects two extends of the same program in one block. --- .../workflows/test-write-program-buffer.yaml | 2 +- .../tests/integration/assert-nearmax.sh | 26 +++++++ .../tests/integration/prepare-nearmax.sh | 75 +++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) create mode 100755 write-program-buffer/tests/integration/assert-nearmax.sh create mode 100755 write-program-buffer/tests/integration/prepare-nearmax.sh diff --git a/.github/workflows/test-write-program-buffer.yaml b/.github/workflows/test-write-program-buffer.yaml index 14bee18..ac70142 100644 --- a/.github/workflows/test-write-program-buffer.yaml +++ b/.github/workflows/test-write-program-buffer.yaml @@ -12,7 +12,7 @@ jobs: fail-fast: false matrix: solana-version: ["3.1.14", "4.1.2"] - scenario: [fresh, clamp, authority] + scenario: [fresh, clamp, authority, nearmax] steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 diff --git a/write-program-buffer/tests/integration/assert-nearmax.sh b/write-program-buffer/tests/integration/assert-nearmax.sh new file mode 100755 index 0000000..41d633d --- /dev/null +++ b/write-program-buffer/tests/integration/assert-nearmax.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +RPC_URL="http://127.0.0.1:8899" +ARTIFACT="target/deploy/fixture-nearmax.so" +MAX_PROGRAM_SIZE=10485715 + +fail() { + echo "ASSERT FAIL: $1" >&2 + exit 1 +} + +[ -n "${BUFFER:-}" ] || fail "action did not output a buffer address" +[ -n "${PROGRAM_ID:-}" ] || fail "PROGRAM_ID env not set" +[ -n "${PRE_LEN:-}" ] || fail "PRE_LEN env not set" + +POST_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | awk '{print $3}') +[ "$POST_LEN" -eq "$MAX_PROGRAM_SIZE" ] || fail "program data length is $POST_LEN, expected the exact maximum $MAX_PROGRAM_SIZE" +echo "Program extended from $PRE_LEN to $POST_LEN (exact headroom of $((POST_LEN - PRE_LEN)) bytes)" + +DUMP="$(mktemp)" +solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER" +cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact" +rm -f "$DUMP" + +echo "Near-max extend assertions passed" diff --git a/write-program-buffer/tests/integration/prepare-nearmax.sh b/write-program-buffer/tests/integration/prepare-nearmax.sh new file mode 100755 index 0000000..fe83b0a --- /dev/null +++ b/write-program-buffer/tests/integration/prepare-nearmax.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +set -euo pipefail + +RPC_URL="http://127.0.0.1:8899" +FIXTURES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../fixtures" && pwd)" +SCENARIO_DIR="${RUNNER_TEMP:-/tmp}/scenario-nearmax" +MAX_PROGRAM_SIZE=10485715 +MIN_EXTEND_SIZE=10240 +ARTIFACT_MARGIN=2000 + +mkdir -p "$SCENARIO_DIR" target/deploy + +gunzip -c "$FIXTURES_DIR/program-huge.so.gz" > target/deploy/fixture-nearmax.so +HUGE_SIZE=$(wc -c < target/deploy/fixture-nearmax.so | tr -d ' ') +TARGET_CURRENT=$((HUGE_SIZE - ARTIFACT_MARGIN)) +HEADROOM=$((MAX_PROGRAM_SIZE - TARGET_CURRENT)) + +if [ "$HUGE_SIZE" -gt "$MAX_PROGRAM_SIZE" ]; then + echo "Fixture drift: huge artifact of $HUGE_SIZE bytes exceeds max program size $MAX_PROGRAM_SIZE" >&2 + exit 1 +fi +if [ "$HEADROOM" -le 0 ] || [ "$HEADROOM" -ge "$MIN_EXTEND_SIZE" ]; then + echo "Fixture drift: pre-extend headroom of $HEADROOM bytes is outside (0, $MIN_EXTEND_SIZE)" >&2 + exit 1 +fi + +solana-keygen new -s --no-bip39-passphrase --force -o "$SCENARIO_DIR/deployer.json" >/dev/null +solana-keygen new -s --no-bip39-passphrase --force -o "$SCENARIO_DIR/program-id.json" >/dev/null +DEPLOYER=$(solana-keygen pubkey "$SCENARIO_DIR/deployer.json") +PROGRAM_ID=$(solana-keygen pubkey "$SCENARIO_DIR/program-id.json") + +for i in 1 2 3; do + solana airdrop 200 "$DEPLOYER" -u "$RPC_URL" && break + if [ "$i" -eq 3 ]; then + echo "Airdrop failed after 3 attempts" >&2 + exit 1 + fi + sleep 2 +done + +solana program deploy "$FIXTURES_DIR/program-small.so" \ + --program-id "$SCENARIO_DIR/program-id.json" \ + -u "$RPC_URL" -k "$SCENARIO_DIR/deployer.json" \ + --commitment confirmed + +PRE_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | awk '{print $3}') +EXTEND_SETUP=$((TARGET_CURRENT - PRE_LEN)) + +for i in 1 2 3; do + if solana program extend "$PROGRAM_ID" "$EXTEND_SETUP" \ + -u "$RPC_URL" -k "$SCENARIO_DIR/deployer.json" \ + --commitment confirmed; then + break + fi + if [ "$i" -eq 3 ]; then + echo "Setup extend failed after 3 attempts" >&2 + exit 1 + fi + sleep 2 +done + +PRE_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | awk '{print $3}') +if [ "$PRE_LEN" -ne "$TARGET_CURRENT" ]; then + echo "Setup failed: program data length is $PRE_LEN, expected $TARGET_CURRENT" >&2 + exit 1 +fi + +echo "Prepared nearmax scenario: deployer=$DEPLOYER program-id=$PROGRAM_ID pre-len=$PRE_LEN headroom=$HEADROOM" +{ + echo "keypair=$(cat "$SCENARIO_DIR/deployer.json")" + echo "deployer=$DEPLOYER" + echo "program-id=$PROGRAM_ID" + echo "buffer-authority=$DEPLOYER" + echo "pre-len=$PRE_LEN" +} >> "$GITHUB_OUTPUT" From 48fef8fb65a7b11e3ce8e08ada892af10b101985 Mon Sep 17 00:00:00 2001 From: Jan W Date: Mon, 20 Jul 2026 09:43:46 +0200 Subject: [PATCH 08/15] test(write-program-buffer): drop solana 3.x from the test matrix The 3.x CLI cannot parse the SBPF v3 fixtures and keeping dual-arch fixture sets is not worth the maintenance; consumers are on 4.x. --- .github/workflows/test-write-program-buffer.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-write-program-buffer.yaml b/.github/workflows/test-write-program-buffer.yaml index ac70142..a9e378f 100644 --- a/.github/workflows/test-write-program-buffer.yaml +++ b/.github/workflows/test-write-program-buffer.yaml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - solana-version: ["3.1.14", "4.1.2"] + solana-version: ["4.1.2"] scenario: [fresh, clamp, authority, nearmax] steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 From 51d79580b9369f4f952876cdd55c5711e22e1ac7 Mon Sep 17 00:00:00 2001 From: Jan W Date: Mon, 20 Jul 2026 09:43:46 +0200 Subject: [PATCH 09/15] chore(start-test-validator): quote GITHUB_OUTPUT redirects shellcheck SC2086 findings; all other scripts are clean. --- start-test-validator/action.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/start-test-validator/action.yaml b/start-test-validator/action.yaml index 9283a6c..7875eb4 100644 --- a/start-test-validator/action.yaml +++ b/start-test-validator/action.yaml @@ -36,8 +36,8 @@ runs: solana-test-validator --reset --quiet --ledger "$LEDGER_DIR" > "${RUNNER_TEMP:-/tmp}/validator-stdout.log" 2>&1 & echo "Validator started with PID $!" - echo "rpc-url=$RPC_URL" >> $GITHUB_OUTPUT - echo "ledger-dir=$LEDGER_DIR" >> $GITHUB_OUTPUT + echo "rpc-url=$RPC_URL" >> "$GITHUB_OUTPUT" + echo "ledger-dir=$LEDGER_DIR" >> "$GITHUB_OUTPUT" - name: Wait for validator health uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0 From 360dc18e828ff1b7228da94c505109625bf1611b Mon Sep 17 00:00:00 2001 From: Jan W Date: Mon, 20 Jul 2026 11:32:44 +0200 Subject: [PATCH 10/15] fix(start-test-validator): pass ledger-dir via env and dump startup log --- start-test-validator/action.yaml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/start-test-validator/action.yaml b/start-test-validator/action.yaml index 7875eb4..4480ff5 100644 --- a/start-test-validator/action.yaml +++ b/start-test-validator/action.yaml @@ -25,9 +25,11 @@ runs: - name: Start validator id: start shell: bash + env: + LEDGER_DIR_INPUT: ${{ inputs.ledger-dir }} run: | RPC_URL="http://127.0.0.1:8899" - LEDGER_DIR="${{ inputs.ledger-dir }}" + LEDGER_DIR="$LEDGER_DIR_INPUT" if [ -z "$LEDGER_DIR" ]; then LEDGER_DIR="${RUNNER_TEMP:-/tmp}/test-ledger" fi @@ -47,7 +49,9 @@ runs: retry_wait_seconds: 1 command: solana cluster-version -u ${{ steps.start.outputs.rpc-url }} - - name: Dump validator log if unhealthy + - name: Dump validator logs if unhealthy if: failure() shell: bash - run: tail -50 "${{ steps.start.outputs.ledger-dir }}/validator.log" 2>/dev/null || true + run: | + tail -50 "${RUNNER_TEMP:-/tmp}/validator-stdout.log" 2>/dev/null || true + tail -50 "${{ steps.start.outputs.ledger-dir }}/validator.log" 2>/dev/null || true From 4ff479078bc77dcfd281fae656e975957c189827 Mon Sep 17 00:00:00 2001 From: Jan W Date: Mon, 20 Jul 2026 11:32:45 +0200 Subject: [PATCH 11/15] chore(ci): harden test workflow triggers and permissions --- .../workflows/test-write-program-buffer.yaml | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-write-program-buffer.yaml b/.github/workflows/test-write-program-buffer.yaml index a9e378f..be72afb 100644 --- a/.github/workflows/test-write-program-buffer.yaml +++ b/.github/workflows/test-write-program-buffer.yaml @@ -2,8 +2,20 @@ name: Test write-program-buffer on: pull_request: + paths: + - "write-program-buffer/**" + - "start-test-validator/**" + - "setup-all/**" + - ".github/workflows/test-write-program-buffer.yaml" workflow_dispatch: +permissions: + contents: read + +concurrency: + group: test-write-program-buffer-${{ github.ref }} + cancel-in-progress: true + jobs: integration: name: ${{ matrix.scenario }} (solana ${{ matrix.solana-version }}) @@ -27,6 +39,8 @@ jobs: - name: Prepare scenario id: prepare shell: bash + env: + RPC_URL: ${{ steps.validator.outputs.rpc-url }} run: ./write-program-buffer/tests/integration/prepare-${{ matrix.scenario }}.sh - name: Run write-program-buffer action @@ -42,6 +56,7 @@ jobs: - name: Assert scenario shell: bash env: + RPC_URL: ${{ steps.validator.outputs.rpc-url }} BUFFER: ${{ steps.run.outputs.buffer }} PROGRAM_ID: ${{ steps.prepare.outputs.program-id }} DEPLOYER: ${{ steps.prepare.outputs.deployer }} @@ -49,7 +64,9 @@ jobs: PRE_LEN: ${{ steps.prepare.outputs.pre-len }} run: ./write-program-buffer/tests/integration/assert-${{ matrix.scenario }}.sh - - name: Dump validator log on failure + - name: Dump validator logs on failure if: failure() shell: bash - run: tail -100 "${{ steps.validator.outputs.ledger-dir }}/validator.log" || true + run: | + tail -100 "$RUNNER_TEMP/validator-stdout.log" || true + tail -100 "${{ steps.validator.outputs.ledger-dir }}/validator.log" || true From ef9de812942464b37e056e890aea02703b927285 Mon Sep 17 00:00:00 2001 From: Jan W Date: Mon, 20 Jul 2026 11:32:45 +0200 Subject: [PATCH 12/15] fix(test): apply review findings to integration scripts --- .../tests/integration/assert-authority.sh | 2 +- .../tests/integration/assert-clamp.sh | 4 ++-- .../tests/integration/assert-fresh.sh | 6 ++--- .../tests/integration/assert-nearmax.sh | 8 ++++--- .../tests/integration/prepare-authority.sh | 2 +- .../tests/integration/prepare-clamp.sh | 4 ++-- .../tests/integration/prepare-fresh.sh | 2 +- .../tests/integration/prepare-nearmax.sh | 22 ++++++++++++------- 8 files changed, 29 insertions(+), 21 deletions(-) diff --git a/write-program-buffer/tests/integration/assert-authority.sh b/write-program-buffer/tests/integration/assert-authority.sh index 1933b0e..9ac05da 100755 --- a/write-program-buffer/tests/integration/assert-authority.sh +++ b/write-program-buffer/tests/integration/assert-authority.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -RPC_URL="http://127.0.0.1:8899" +RPC_URL="${RPC_URL:-http://127.0.0.1:8899}" ARTIFACT="target/deploy/fixture-authority.so" fail() { diff --git a/write-program-buffer/tests/integration/assert-clamp.sh b/write-program-buffer/tests/integration/assert-clamp.sh index fd1ab8e..44bb03f 100755 --- a/write-program-buffer/tests/integration/assert-clamp.sh +++ b/write-program-buffer/tests/integration/assert-clamp.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -RPC_URL="http://127.0.0.1:8899" +RPC_URL="${RPC_URL:-http://127.0.0.1:8899}" ARTIFACT="target/deploy/fixture-clamp.so" MIN_EXTEND_SIZE=10240 @@ -14,7 +14,7 @@ fail() { [ -n "${PROGRAM_ID:-}" ] || fail "PROGRAM_ID env not set" [ -n "${PRE_LEN:-}" ] || fail "PRE_LEN env not set" -POST_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | awk '{print $3}') +POST_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | sed -E 's/.*Data Length: ([0-9]+).*/\1/' | cut -d ' ' -f1) GROWTH=$((POST_LEN - PRE_LEN)) [ "$GROWTH" -eq "$MIN_EXTEND_SIZE" ] || fail "program grew by $GROWTH bytes, expected exactly $MIN_EXTEND_SIZE" diff --git a/write-program-buffer/tests/integration/assert-fresh.sh b/write-program-buffer/tests/integration/assert-fresh.sh index 14c1c91..7f4149f 100755 --- a/write-program-buffer/tests/integration/assert-fresh.sh +++ b/write-program-buffer/tests/integration/assert-fresh.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -RPC_URL="http://127.0.0.1:8899" +RPC_URL="${RPC_URL:-http://127.0.0.1:8899}" ARTIFACT="target/deploy/fixture-fresh.so" fail() { @@ -23,8 +23,8 @@ echo "$BUFFER_INFO" AUTHORITY=$(echo "$BUFFER_INFO" | grep "Authority:" | awk '{print $2}') [ "$AUTHORITY" = "$DEPLOYER" ] || fail "buffer authority is $AUTHORITY, expected deployer $DEPLOYER" -if solana program show "$PROGRAM_ID" -u "$RPC_URL" 2>&1 | grep -q "Data Length:"; then - fail "program $PROGRAM_ID unexpectedly exists" +if ! solana program show "$PROGRAM_ID" -u "$RPC_URL" 2>&1 | grep -q "Unable to find the account"; then + fail "program $PROGRAM_ID unexpectedly exists or the absence check errored" fi echo "Fresh-program scenario assertions passed" diff --git a/write-program-buffer/tests/integration/assert-nearmax.sh b/write-program-buffer/tests/integration/assert-nearmax.sh index 41d633d..6138c73 100755 --- a/write-program-buffer/tests/integration/assert-nearmax.sh +++ b/write-program-buffer/tests/integration/assert-nearmax.sh @@ -1,9 +1,11 @@ #!/usr/bin/env bash set -euo pipefail -RPC_URL="http://127.0.0.1:8899" +RPC_URL="${RPC_URL:-http://127.0.0.1:8899}" ARTIFACT="target/deploy/fixture-nearmax.so" -MAX_PROGRAM_SIZE=10485715 +MAX_PERMITTED_DATA_LENGTH=10485760 +PROGRAMDATA_METADATA_SIZE=45 +MAX_PROGRAM_SIZE=$((MAX_PERMITTED_DATA_LENGTH - PROGRAMDATA_METADATA_SIZE)) fail() { echo "ASSERT FAIL: $1" >&2 @@ -14,7 +16,7 @@ fail() { [ -n "${PROGRAM_ID:-}" ] || fail "PROGRAM_ID env not set" [ -n "${PRE_LEN:-}" ] || fail "PRE_LEN env not set" -POST_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | awk '{print $3}') +POST_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | sed -E 's/.*Data Length: ([0-9]+).*/\1/' | cut -d ' ' -f1) [ "$POST_LEN" -eq "$MAX_PROGRAM_SIZE" ] || fail "program data length is $POST_LEN, expected the exact maximum $MAX_PROGRAM_SIZE" echo "Program extended from $PRE_LEN to $POST_LEN (exact headroom of $((POST_LEN - PRE_LEN)) bytes)" diff --git a/write-program-buffer/tests/integration/prepare-authority.sh b/write-program-buffer/tests/integration/prepare-authority.sh index 4b6bdf3..d840aae 100755 --- a/write-program-buffer/tests/integration/prepare-authority.sh +++ b/write-program-buffer/tests/integration/prepare-authority.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -RPC_URL="http://127.0.0.1:8899" +RPC_URL="${RPC_URL:-http://127.0.0.1:8899}" FIXTURES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../fixtures" && pwd)" SCENARIO_DIR="${RUNNER_TEMP:-/tmp}/scenario-authority" diff --git a/write-program-buffer/tests/integration/prepare-clamp.sh b/write-program-buffer/tests/integration/prepare-clamp.sh index 81ff027..d5f81dc 100755 --- a/write-program-buffer/tests/integration/prepare-clamp.sh +++ b/write-program-buffer/tests/integration/prepare-clamp.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -RPC_URL="http://127.0.0.1:8899" +RPC_URL="${RPC_URL:-http://127.0.0.1:8899}" FIXTURES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../fixtures" && pwd)" SCENARIO_DIR="${RUNNER_TEMP:-/tmp}/scenario-clamp" @@ -34,7 +34,7 @@ solana program deploy "$FIXTURES_DIR/program-small.so" \ -u "$RPC_URL" -k "$SCENARIO_DIR/deployer.json" \ --commitment confirmed -PRE_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | awk '{print $3}') +PRE_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | sed -E 's/.*Data Length: ([0-9]+).*/\1/' | cut -d ' ' -f1) if [ -z "$PRE_LEN" ]; then echo "Could not read deployed program size" >&2 exit 1 diff --git a/write-program-buffer/tests/integration/prepare-fresh.sh b/write-program-buffer/tests/integration/prepare-fresh.sh index 3037ee9..9113b04 100755 --- a/write-program-buffer/tests/integration/prepare-fresh.sh +++ b/write-program-buffer/tests/integration/prepare-fresh.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -RPC_URL="http://127.0.0.1:8899" +RPC_URL="${RPC_URL:-http://127.0.0.1:8899}" FIXTURES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../fixtures" && pwd)" SCENARIO_DIR="${RUNNER_TEMP:-/tmp}/scenario-fresh" diff --git a/write-program-buffer/tests/integration/prepare-nearmax.sh b/write-program-buffer/tests/integration/prepare-nearmax.sh index fe83b0a..232d55b 100755 --- a/write-program-buffer/tests/integration/prepare-nearmax.sh +++ b/write-program-buffer/tests/integration/prepare-nearmax.sh @@ -1,10 +1,12 @@ #!/usr/bin/env bash set -euo pipefail -RPC_URL="http://127.0.0.1:8899" +RPC_URL="${RPC_URL:-http://127.0.0.1:8899}" FIXTURES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../fixtures" && pwd)" SCENARIO_DIR="${RUNNER_TEMP:-/tmp}/scenario-nearmax" -MAX_PROGRAM_SIZE=10485715 +MAX_PERMITTED_DATA_LENGTH=10485760 +PROGRAMDATA_METADATA_SIZE=45 +MAX_PROGRAM_SIZE=$((MAX_PERMITTED_DATA_LENGTH - PROGRAMDATA_METADATA_SIZE)) MIN_EXTEND_SIZE=10240 ARTIFACT_MARGIN=2000 @@ -43,15 +45,19 @@ solana program deploy "$FIXTURES_DIR/program-small.so" \ -u "$RPC_URL" -k "$SCENARIO_DIR/deployer.json" \ --commitment confirmed -PRE_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | awk '{print $3}') -EXTEND_SETUP=$((TARGET_CURRENT - PRE_LEN)) +read_data_len() { + solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | sed -E 's/.*Data Length: ([0-9]+).*/\1/' | cut -d ' ' -f1 +} for i in 1 2 3; do - if solana program extend "$PROGRAM_ID" "$EXTEND_SETUP" \ - -u "$RPC_URL" -k "$SCENARIO_DIR/deployer.json" \ - --commitment confirmed; then + CURRENT_LEN=$(read_data_len) + REMAINING=$((TARGET_CURRENT - CURRENT_LEN)) + if [ "$REMAINING" -le 0 ]; then break fi + solana program extend "$PROGRAM_ID" "$REMAINING" \ + -u "$RPC_URL" -k "$SCENARIO_DIR/deployer.json" \ + --commitment confirmed && continue if [ "$i" -eq 3 ]; then echo "Setup extend failed after 3 attempts" >&2 exit 1 @@ -59,7 +65,7 @@ for i in 1 2 3; do sleep 2 done -PRE_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | awk '{print $3}') +PRE_LEN=$(read_data_len) if [ "$PRE_LEN" -ne "$TARGET_CURRENT" ]; then echo "Setup failed: program data length is $PRE_LEN, expected $TARGET_CURRENT" >&2 exit 1 From fe572fb0e3b48d7df81dc96314aa442c17d87688 Mon Sep 17 00:00:00 2001 From: Jan W Date: Mon, 20 Jul 2026 11:33:42 +0200 Subject: [PATCH 13/15] test(write-program-buffer): add plain-delta extend scenario --- .../workflows/test-write-program-buffer.yaml | 2 +- .../tests/fixtures/program-medium.so | Bin 0 -> 20584 bytes .../tests/integration/assert-delta.sh | 30 ++++++++++ .../tests/integration/prepare-delta.sh | 53 ++++++++++++++++++ 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100755 write-program-buffer/tests/fixtures/program-medium.so create mode 100755 write-program-buffer/tests/integration/assert-delta.sh create mode 100755 write-program-buffer/tests/integration/prepare-delta.sh diff --git a/.github/workflows/test-write-program-buffer.yaml b/.github/workflows/test-write-program-buffer.yaml index be72afb..667a064 100644 --- a/.github/workflows/test-write-program-buffer.yaml +++ b/.github/workflows/test-write-program-buffer.yaml @@ -24,7 +24,7 @@ jobs: fail-fast: false matrix: solana-version: ["4.1.2"] - scenario: [fresh, clamp, authority, nearmax] + scenario: [fresh, delta, clamp, authority, nearmax] steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 diff --git a/write-program-buffer/tests/fixtures/program-medium.so b/write-program-buffer/tests/fixtures/program-medium.so new file mode 100755 index 0000000000000000000000000000000000000000..b8563e70645673b35f12a9672926060c50ee416d GIT binary patch literal 20584 zcmeI)F;2rk5CFh)91%+BfEQ4uPDulzK=1}5kkTO~E|4gA1rOjAlzfLW9cA#wcfv9q zG!(PadA+y3i!u+`=ku$JNmZp1b^1)DihPzE%bWhC-t8??-F2qjGrpO2>!WY~c%EMO zTP5%3FXCLRgM4pnM;#G)v3vW@p+$@IEZMNIDzDzGUr+It-*rxe&2 + exit 1 +} + +[ -n "${BUFFER:-}" ] || fail "action did not output a buffer address" +[ -n "${PROGRAM_ID:-}" ] || fail "PROGRAM_ID env not set" +[ -n "${PRE_LEN:-}" ] || fail "PRE_LEN env not set" + +REQUIRED_SIZE=$(wc -c < "$ARTIFACT" | tr -d ' ') +EXPECTED_GROWTH=$((REQUIRED_SIZE - PRE_LEN)) +[ "$EXPECTED_GROWTH" -gt "$MIN_EXTEND_SIZE" ] || fail "scenario setup invalid: expected growth $EXPECTED_GROWTH must exceed $MIN_EXTEND_SIZE" + +POST_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | sed -E 's/.*Data Length: ([0-9]+).*/\1/' | cut -d ' ' -f1) +GROWTH=$((POST_LEN - PRE_LEN)) +[ "$GROWTH" -eq "$EXPECTED_GROWTH" ] || fail "program grew by $GROWTH bytes, expected the exact delta $EXPECTED_GROWTH" + +DUMP="$(mktemp)" +solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER" +cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact" +rm -f "$DUMP" + +echo "Plain-delta extend assertions passed: program grew by exactly $EXPECTED_GROWTH bytes" diff --git a/write-program-buffer/tests/integration/prepare-delta.sh b/write-program-buffer/tests/integration/prepare-delta.sh new file mode 100755 index 0000000..444e9af --- /dev/null +++ b/write-program-buffer/tests/integration/prepare-delta.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -euo pipefail + +RPC_URL="${RPC_URL:-http://127.0.0.1:8899}" +FIXTURES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../fixtures" && pwd)" +SCENARIO_DIR="${RUNNER_TEMP:-/tmp}/scenario-delta" +MIN_EXTEND_SIZE=10240 + +SMALL_SIZE=$(wc -c < "$FIXTURES_DIR/program-small.so" | tr -d ' ') +MEDIUM_SIZE=$(wc -c < "$FIXTURES_DIR/program-medium.so" | tr -d ' ') +DELTA=$((MEDIUM_SIZE - SMALL_SIZE)) +if [ "$DELTA" -le "$MIN_EXTEND_SIZE" ]; then + echo "Fixture drift: medium-small delta of $DELTA bytes must exceed $MIN_EXTEND_SIZE" >&2 + exit 1 +fi + +mkdir -p "$SCENARIO_DIR" target/deploy + +solana-keygen new -s --no-bip39-passphrase --force -o "$SCENARIO_DIR/deployer.json" >/dev/null +solana-keygen new -s --no-bip39-passphrase --force -o "$SCENARIO_DIR/program-id.json" >/dev/null +DEPLOYER=$(solana-keygen pubkey "$SCENARIO_DIR/deployer.json") +PROGRAM_ID=$(solana-keygen pubkey "$SCENARIO_DIR/program-id.json") + +for i in 1 2 3; do + solana airdrop 100 "$DEPLOYER" -u "$RPC_URL" && break + if [ "$i" -eq 3 ]; then + echo "Airdrop failed after 3 attempts" >&2 + exit 1 + fi + sleep 2 +done + +solana program deploy "$FIXTURES_DIR/program-small.so" \ + --program-id "$SCENARIO_DIR/program-id.json" \ + -u "$RPC_URL" -k "$SCENARIO_DIR/deployer.json" \ + --commitment confirmed + +PRE_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | sed -E 's/.*Data Length: ([0-9]+).*/\1/' | cut -d ' ' -f1) +if [ -z "$PRE_LEN" ]; then + echo "Could not read deployed program size" >&2 + exit 1 +fi + +cp "$FIXTURES_DIR/program-medium.so" target/deploy/fixture-delta.so + +echo "Prepared delta scenario: deployer=$DEPLOYER program-id=$PROGRAM_ID pre-len=$PRE_LEN delta=$DELTA" +{ + echo "keypair=$(cat "$SCENARIO_DIR/deployer.json")" + echo "deployer=$DEPLOYER" + echo "program-id=$PROGRAM_ID" + echo "buffer-authority=$DEPLOYER" + echo "pre-len=$PRE_LEN" +} >> "$GITHUB_OUTPUT" From c845566b369208e0355a77c42756bffb7357c1eb Mon Sep 17 00:00:00 2001 From: Jan W Date: Mon, 20 Jul 2026 11:40:21 +0200 Subject: [PATCH 14/15] test(write-program-buffer): add fixture source and rebuild script --- .../tests/fixtures/program-huge.so.gz | Bin 10431 -> 10415 bytes .../tests/fixtures/program/Cargo.lock | 7 +++ .../tests/fixtures/program/Cargo.toml | 14 ++++++ .../tests/fixtures/program/src/lib.rs | 25 ++++++++++ .../tests/fixtures/rebuild.sh | 47 ++++++++++++++++++ 5 files changed, 93 insertions(+) create mode 100644 write-program-buffer/tests/fixtures/program/Cargo.lock create mode 100644 write-program-buffer/tests/fixtures/program/Cargo.toml create mode 100644 write-program-buffer/tests/fixtures/program/src/lib.rs create mode 100755 write-program-buffer/tests/fixtures/rebuild.sh diff --git a/write-program-buffer/tests/fixtures/program-huge.so.gz b/write-program-buffer/tests/fixtures/program-huge.so.gz index a3e7fb6ec5ef73a419db4a2f0e661e8645f1928a..3cc42d89ba0e4110389f2fe0b1e0cc1ab0e51863 100644 GIT binary patch delta 19 YcmdlVxIU0ezMF#q1elmNN-fm@05bOkY5)KL delta 35 qcmZ1 u64 { + #[cfg(any(feature = "medium", feature = "big", feature = "huge"))] + core::hint::black_box(&PAD); + 0 +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} diff --git a/write-program-buffer/tests/fixtures/rebuild.sh b/write-program-buffer/tests/fixtures/rebuild.sh new file mode 100755 index 0000000..995cce5 --- /dev/null +++ b/write-program-buffer/tests/fixtures/rebuild.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")" + +command -v cargo-build-sbf >/dev/null || { + echo "cargo-build-sbf is required, install the agave tool suite first" >&2 + exit 1 +} + +build() { + local variant="$1" feature="$2" + local args=(--arch v3 --manifest-path program/Cargo.toml --sbf-out-dir program/out) + if [ -n "$feature" ]; then + args+=(--features "$feature") + fi + cargo-build-sbf "${args[@]}" + cp program/out/fixture_program.so "program-$variant.so" + rm -rf program/out program/target +} + +build small "" +build medium medium +build big big +build huge huge + +SMALL=$(wc -c < program-small.so | tr -d ' ') +MEDIUM=$(wc -c < program-medium.so | tr -d ' ') +BIG=$(wc -c < program-big.so | tr -d ' ') +HUGE=$(wc -c < program-huge.so | tr -d ' ') +echo "small=$SMALL medium=$MEDIUM big=$BIG huge=$HUGE" + +if [ "$BIG" -le "$SMALL" ] || [ $((BIG - SMALL)) -ge 10240 ]; then + echo "Size band violated: big-small delta must be in (0, 10240)" >&2 + exit 1 +fi +if [ $((MEDIUM - SMALL)) -le 10240 ]; then + echo "Size band violated: medium-small delta must exceed 10240" >&2 + exit 1 +fi +if [ "$HUGE" -le 10477475 ] || [ "$HUGE" -gt 10485715 ]; then + echo "Size band violated: huge must be in (10477475, 10485715]" >&2 + exit 1 +fi + +gzip -9 -n -c program-huge.so > program-huge.so.gz +rm program-huge.so +echo "Fixtures rebuilt" From c8dc4463f067993c2d60f4adab2df26f6aab4bc3 Mon Sep 17 00:00:00 2001 From: Jan W Date: Mon, 20 Jul 2026 11:47:42 +0200 Subject: [PATCH 15/15] fix(test): capture absence check output before grep to survive pipefail --- write-program-buffer/tests/integration/assert-fresh.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/write-program-buffer/tests/integration/assert-fresh.sh b/write-program-buffer/tests/integration/assert-fresh.sh index 7f4149f..31d3e88 100755 --- a/write-program-buffer/tests/integration/assert-fresh.sh +++ b/write-program-buffer/tests/integration/assert-fresh.sh @@ -23,8 +23,9 @@ echo "$BUFFER_INFO" AUTHORITY=$(echo "$BUFFER_INFO" | grep "Authority:" | awk '{print $2}') [ "$AUTHORITY" = "$DEPLOYER" ] || fail "buffer authority is $AUTHORITY, expected deployer $DEPLOYER" -if ! solana program show "$PROGRAM_ID" -u "$RPC_URL" 2>&1 | grep -q "Unable to find the account"; then - fail "program $PROGRAM_ID unexpectedly exists or the absence check errored" +ABSENCE_CHECK=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" 2>&1 || true) +if ! echo "$ABSENCE_CHECK" | grep -q "Unable to find the account"; then + fail "program $PROGRAM_ID unexpectedly exists or the absence check errored: $ABSENCE_CHECK" fi echo "Fresh-program scenario assertions passed"