Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
1451589
feat(ci): add E2E test suite CI job with JS and Rust code coverage
mudigal Jun 3, 2026
4c5e128
feat(e2e): add E2E test suite workflows and updated api.js
mudigal Jun 3, 2026
c307999
Merge branch 'dev' into feat/e2e-coverage
bkontur Jun 3, 2026
fcdd4d9
fix(e2e): fix 6 bugs across E2E workflows and extract CI composite ac…
mudigal Jun 3, 2026
f7f81a6
merge: resolve conflicts in integration-tests.yml
mudigal Jun 3, 2026
b1073b1
chore(e2e): remove unused imports and variables (CodeQL)
mudigal Jun 3, 2026
829a570
perf(e2e): switch tx submission from finalization to best-block for ~…
mudigal Jun 3, 2026
b0a1f3c
fix(e2e): use finalized-block for submitTx so state queries see changes
mudigal Jun 3, 2026
15be944
feat(ci): run E2E tests with 2s dev blocks instead of zombienet
mudigal Jun 3, 2026
12f02be
fix(ci): add --unsafe-force-node-key-generation for ephemeral E2E chain
mudigal Jun 3, 2026
b313b20
fix(e2e): fix test failures in workflows 07, 08, and 10
mudigal Jun 3, 2026
afcb299
fix(e2e): fix workflow 02 reject balance assertion and Burn encoding
mudigal Jun 4, 2026
a346f9f
ci(e2e): consolidate to single runtime and add JS coverage step
mudigal Jun 4, 2026
2cbb1ac
fix(ci): fix Rust coverage profraw generation and add diagnostics
mudigal Jun 4, 2026
54d8fc1
fix(e2e): fix Burn test Token::BelowMinimum and remove unused vars
mudigal Jun 4, 2026
0ecdab4
fix(ci): improve Rust coverage profraw generation reliability
mudigal Jun 4, 2026
3b8cf24
perf(ci): build common artifacts once, run the two test suites in par…
bkontur Jun 4, 2026
464fae4
Merge branch 'bko-ci-build-once-shard' into feat/e2e-coverage
bkontur Jun 4, 2026
a91e231
ci(e2e): single-source runtime + chain-spec from runtimes-matrix.json
bkontur Jun 4, 2026
5ee9cb6
fix(ci): restore artifact paths and shrink build artifact
bkontur Jun 4, 2026
b216b7c
Merge remote-tracking branch 'origin/bko-ci-build-once-shard' into fe…
bkontur Jun 4, 2026
3fa90a9
ci(e2e): drop unused build-artifact dependency from e2e job
bkontur Jun 4, 2026
445f9a0
docs(ci): correct build-once artifact comments
bkontur Jun 4, 2026
d3de2d9
use in-block for tests
bkontur Jun 4, 2026
4f414c1
Merge remote-tracking branch 'origin/bko-ci-build-once-shard' into fe…
bkontur Jun 4, 2026
391f8bf
fix(papi): read demo state at best block, centralised via READ_OPTS
bkontur Jun 4, 2026
456a4b4
Merge remote-tracking branch 'origin/bko-ci-build-once-shard' into fe…
bkontur Jun 4, 2026
de28f36
fix(e2e): read state at best block (READ_OPTS) in e2e workflows
bkontur Jun 4, 2026
18e1416
fix(papi): count ChallengeDefended from tx events, not finalized watch
bkontur Jun 4, 2026
2bd19cf
fix(papi): finalize challenge-creating txs so respond can't hit Chall…
bkontur Jun 4, 2026
42d64c7
demo(papi): shorten full-flow agreement duration 50 -> 15 blocks
bkontur Jun 4, 2026
83328e3
Merge remote-tracking branch 'origin/bko-ci-build-once-shard' into fe…
bkontur Jun 4, 2026
923f158
fix(ci): fix Rust coverage (use continuous mode) and add pallet coverage
mudigal Jun 4, 2026
0c258fe
fix(ci): correct pallet package name for coverage (pallet-storage-pro…
mudigal Jun 4, 2026
c5fa7be
refactor(pallet): split tests.rs into tests/ directory and add CI cov…
mudigal Jun 4, 2026
e507d07
fix(ci): fix pallet binary path (debug not release) and use SIGTERM f…
mudigal Jun 4, 2026
7a37421
ci(ui-e2e): make the required check always report via an aggregate gate
bkontur Jun 4, 2026
189b3ac
docs: trim the explanatory comments added in this PR
bkontur Jun 4, 2026
e288a37
Merge remote-tracking branch 'origin/bko-ci-build-once-shard' into fe…
bkontur Jun 4, 2026
57814d9
fix(ci): add safe.directory for container git ops, fix pallet binary …
mudigal Jun 5, 2026
3bc91fc
Merge remote-tracking branch 'origin/feat/e2e-coverage' into feat/e2e…
mudigal Jun 5, 2026
23fa0dc
Merge remote-tracking branch 'origin/dev' into feat/e2e-coverage
mudigal Jun 5, 2026
0801b5a
refactor(ci): consolidate Rust coverage into check.yml, keep JS-only …
mudigal Jun 5, 2026
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
222 changes: 208 additions & 14 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -183,24 +183,15 @@ jobs:
sleep 2
done

- name: Run L0 demo (inmemory provider)
run: just demo "http://127.0.0.1:3333" "//Alice" "//Bob"

- name: Run L0 demo (disk provider)
run: just drain-tx-pool-then demo "http://127.0.0.1:3334" "//Charlie" "//Dave"
run: just demo "http://127.0.0.1:3334" "//Charlie" "//Dave"

- name: Run file system integration test
run: just drain-tx-pool-then fs-demo-ci

- name: Run S3 integration test
run: just drain-tx-pool-then s3-demo-ci

- name: Run PAPI s3-lifecycle demo
run: just drain-tx-pool-then papi-s3-lifecycle "http://127.0.0.1:3333" "//Alice" "//Bob"

- name: Run PAPI drive-lifecycle demo
run: just drain-tx-pool-then papi-drive-lifecycle "http://127.0.0.1:3333" "//Alice" "//Bob" "//Ferdie"

- name: Stop services
if: always()
run: |
Expand All @@ -222,10 +213,213 @@ jobs:
/tmp/zombie-*/*-plain.json
/tmp/zombie-*/*-raw.json

# E2E test suite — runs all 10 PAPI E2E workflows against a single
# inmemory provider. Supersedes the standalone PAPI demo scripts.
e2e-integration-tests:
name: E2E Integration Tests (${{ matrix.runtime.name }})
runs-on: parity-large
timeout-minutes: 60
needs: [set-image, runtime-matrix]
container:
image: ${{ needs.set-image.outputs.CI_IMAGE }}
strategy:
fail-fast: false
matrix:
runtime: ${{ fromJSON(needs.runtime-matrix.outputs.runtime) }}
steps:
- name: Checkout sources
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Load common environment variables via env file
run: cat .github/env >> $GITHUB_ENV

- name: Rust cache
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
shared-key: "e2e-integration-tests-${{ matrix.runtime.name }}"
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' }}

- name: Install just
run: cargo install just --locked || true

- name: Cache Polkadot SDK binaries
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
id: sdk-cache
with:
path: |
.bin/polkadot
.bin/polkadot-execute-worker
.bin/polkadot-prepare-worker
.bin/polkadot-omni-node
.bin/chain-spec-builder
key: polkadot-sdk-${{ env.POLKADOT_SDK_VERSION }}

- name: Download Polkadot SDK binaries
if: steps.sdk-cache.outputs.cache-hit != 'true'
run: just polkadot_version=${{ env.POLKADOT_SDK_VERSION }} download-polkadot-sdk-binaries

- name: Cache zombienet
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
id: zombienet-cache
with:
path: .bin/zombienet
key: zombienet-${{ env.ZOMBIENET_VERSION }}

- name: Download zombienet
if: steps.zombienet-cache.outputs.cache-hit != 'true'
run: just zombienet_version=${{ env.ZOMBIENET_VERSION }} download-zombienet

- name: Log binary versions
run: |
.bin/zombienet --version
.bin/polkadot --version
.bin/polkadot-omni-node --version

- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: ${{ env.NODE_VERSION }}

- name: Build runtime
run: just ${{ matrix.runtime.build_command }}

- name: Build provider with coverage instrumentation
run: cargo build --release -p storage-provider-node
env:
RUSTFLAGS: "-C instrument-coverage"
CARGO_TARGET_DIR: target-cov
CARGO_INCREMENTAL: '0'

- name: Start chain and wait for blocks
run: |
nohup env PROJECT_ROOT=$(pwd) .bin/zombienet spawn -p native ${{ matrix.runtime.zombienet_config }} > /tmp/zombienet.log 2>&1 &
ZOMBIE_PID=$!
echo "Waiting for zombienet to set up nodes..."
sleep 15
if ! kill -0 $ZOMBIE_PID 2>/dev/null; then
echo "ERROR: zombienet exited early"
cat /tmp/zombienet.log || true
exit 1
fi
echo "Waiting for parachain to produce finalized blocks..."
for i in $(seq 1 180); do
RESPONSE=$(curl -s -H "Content-Type: application/json" \
-d '{"id":1,"jsonrpc":"2.0","method":"chain_getFinalizedHead","params":[]}' \
http://127.0.0.1:2222 2>/dev/null) || true
FINALIZED_HASH=$(echo "$RESPONSE" | jq -r '.result // empty' 2>/dev/null)
if [ -n "$FINALIZED_HASH" ]; then
HEADER=$(curl -s -H "Content-Type: application/json" \
-d "{\"id\":1,\"jsonrpc\":\"2.0\",\"method\":\"chain_getHeader\",\"params\":[\"$FINALIZED_HASH\"]}" \
http://127.0.0.1:2222 2>/dev/null) || true
BLOCK_HEX=$(echo "$HEADER" | jq -r '.result.number // empty' 2>/dev/null)
if [ -n "$BLOCK_HEX" ]; then
BLOCK_NUM=$((BLOCK_HEX))
if [ "$BLOCK_NUM" -ge 1 ]; then
echo "Parachain finalized block: #$BLOCK_NUM (attempt $i)"
break
fi
fi
fi
if [ "$i" -eq 180 ]; then
echo "TIMEOUT: parachain did not finalize blocks"
exit 1
fi
sleep 5
done

- name: Start provider (inmemory) and wait for health
Comment thread
mudigal marked this conversation as resolved.
Outdated
run: |
echo "//Alice" > /tmp/alice-key && chmod 600 /tmp/alice-key
mkdir -p /tmp/coverage
nohup ./target-cov/release/storage-provider-node \
--keyfile /tmp/alice-key --storage-mode inmemory \
--bind-addr 0.0.0.0:3333 --chain-rpc ws://127.0.0.1:2222 \
--enable-agreement-coordinator --enable-checkpoint-coordinator \
> /tmp/provider.log 2>&1 &
echo "Waiting for provider HTTP server..."
for i in $(seq 1 60); do
if curl -s http://127.0.0.1:3333/health | jq -e '.status' > /dev/null 2>&1; then
echo "Provider is healthy (attempt $i)"
break
fi
if [ "$i" -eq 60 ]; then
echo "Timeout: provider did not become healthy"
cat /tmp/provider.log || true
exit 1
fi
sleep 2
done
env:
LLVM_PROFILE_FILE: /tmp/coverage/provider-%p-%m.profraw

- name: Run E2E test suite
run: just e2e "http://127.0.0.1:3333"

- name: Stop services and generate Rust coverage
if: always()
run: |
# Graceful shutdown — SIGTERM lets provider flush profraw
pkill -TERM -f "storage-provider-node" 2>/dev/null || true
sleep 5
pkill -f "polkadot-omni-node" 2>/dev/null || true
pkill -f "polkadot" 2>/dev/null || true

# Generate Rust coverage report
if ls /tmp/coverage/*.profraw 1>/dev/null 2>&1; then
rustup component add llvm-tools 2>/dev/null || true
LLVM_PROFDATA=$(find $(rustc --print sysroot) -name llvm-profdata -type f | head -1)
LLVM_COV=$(find $(rustc --print sysroot) -name llvm-cov -type f | head -1)
if [ -n "$LLVM_PROFDATA" ] && [ -n "$LLVM_COV" ]; then
mkdir -p /tmp/coverage/report
"$LLVM_PROFDATA" merge -sparse /tmp/coverage/*.profraw -o /tmp/coverage/provider.profdata
echo ""
echo "═══════════════════════════════════════════════════════════"
echo " Rust Provider Coverage (provider-node/src/)"
echo "═══════════════════════════════════════════════════════════"
"$LLVM_COV" report \
./target-cov/release/storage-provider-node \
--instr-profile=/tmp/coverage/provider.profdata \
--ignore-filename-regex='(\.cargo|rustc|target-cov)' \
--sources provider-node/src/
echo ""
"$LLVM_COV" export \
./target-cov/release/storage-provider-node \
--instr-profile=/tmp/coverage/provider.profdata \
--ignore-filename-regex='(\.cargo|rustc|target-cov)' \
--sources provider-node/src/ \
--format=lcov > /tmp/coverage/report/rust-lcov.info 2>/dev/null || true
fi
fi

- name: Upload JS coverage
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: js-coverage-${{ matrix.runtime.name }}
path: examples/papi/coverage/

- name: Upload Rust coverage
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: rust-coverage-${{ matrix.runtime.name }}
path: /tmp/coverage/report/

- name: Upload logs (on failure)
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: e2e-integration-test-logs-${{ matrix.runtime.name }}
path: |
/tmp/zombienet.log
/tmp/provider.log
/tmp/zombie-*/*.log
/tmp/zombie-*/**/*.log
/tmp/zombie-*/*-plain.json
/tmp/zombie-*/*-raw.json

# Smart-contract demos run in a parallel job with their own chain +
# provider — the sequential time of L0 demos + fs/s3 lifecycles + sc demos
# was bumping against the 60-minute job timeout. Parallel splits the
# wall-clock roughly in half; SC tests only need //Alice as a provider.
# provider. SC tests only need //Alice as a provider.
sc-integration-tests:
name: SC Integration Tests (${{ matrix.runtime.name }})
runs-on: parity-large
Expand Down Expand Up @@ -407,7 +601,7 @@ jobs:

integration-tests-complete:
name: Integration Tests
needs: [integration-tests, sc-integration-tests]
needs: [integration-tests, e2e-integration-tests, sc-integration-tests]
if: always()
runs-on: ubuntu-latest
steps:
Expand Down
6 changes: 6 additions & 0 deletions examples/papi/.c8rc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"all": true,
"src": ["."],
"include": ["api.js", "common.js"],
"exclude": ["e2e/**", "node_modules/**", "sc-*.js", ".papi/**"]
}
122 changes: 122 additions & 0 deletions examples/papi/api.js
Comment thread
mudigal marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,128 @@ export async function endAgreement(api, client, provider, bucketId, action = "Pa
);
}

export async function addStake(api, provider, amount) {
return submitTx(
api.tx.StorageProvider.add_stake({ amount }),
provider.signer,
"add_stake"
);
}

export async function deregisterProvider(api, provider) {
return submitTx(
api.tx.StorageProvider.deregister_provider(),
provider.signer,
"deregister_provider"
);
}

export async function completeDeregister(api, provider) {
return submitTx(
api.tx.StorageProvider.complete_deregister(),
provider.signer,
"complete_deregister"
);
}

export async function cancelDeregister(api, provider) {
return submitTx(
api.tx.StorageProvider.cancel_deregister(),
provider.signer,
"cancel_deregister"
);
}

export async function updateProviderMultiaddr(api, provider, multiaddr) {
const bytes = typeof multiaddr === "string"
? new TextEncoder().encode(multiaddr)
: multiaddr;
return submitTx(
api.tx.StorageProvider.update_provider_multiaddr({
multiaddr: Binary.fromBytes(bytes),
}),
provider.signer,
"update_provider_multiaddr"
);
}

export async function setMinProviders(api, admin, bucketId, minProviders) {
return submitTx(
api.tx.StorageProvider.set_min_providers({
bucket_id: bucketId,
min_providers: minProviders,
}),
admin.signer,
"set_min_providers"
);
}

export async function rejectAgreement(api, provider, bucketId) {
return submitTx(
api.tx.StorageProvider.reject_agreement({ bucket_id: bucketId }),
provider.signer,
"reject_agreement"
);
}

export async function withdrawAgreementRequest(api, client, bucketId, provider) {
return submitTx(
api.tx.StorageProvider.withdraw_agreement_request({
bucket_id: bucketId,
provider: provider.address,
}),
client.signer,
"withdraw_agreement_request"
);
}

export async function claimExpiredAgreement(api, caller, bucketId, provider) {
return submitTx(
api.tx.StorageProvider.claim_expired_agreement({
bucket_id: bucketId,
provider: provider.address,
}),
caller.signer,
"claim_expired_agreement"
);
}

export async function extendAgreement(api, client, bucketId, provider, params) {
return submitTx(
api.tx.StorageProvider.extend_agreement({
bucket_id: bucketId,
provider: provider.address,
...params,
}),
client.signer,
"extend_agreement"
);
}

export async function topUpAgreement(api, client, bucketId, provider, params) {
return submitTx(
api.tx.StorageProvider.top_up_agreement({
bucket_id: bucketId,
provider: provider.address,
...params,
}),
client.signer,
"top_up_agreement"
);
}

export async function setExtensionsBlocked(api, client, bucketId, provider, blocked) {
return submitTx(
api.tx.StorageProvider.set_extensions_blocked({
bucket_id: bucketId,
provider: provider.address,
blocked,
}),
client.signer,
"set_extensions_blocked"
);
}

export async function freezeBucket(api, client, bucketId) {
const result = await submitTx(
api.tx.StorageProvider.freeze_bucket({ bucket_id: bucketId }),
Expand Down
Loading
Loading