Skip to content
Open
Show file tree
Hide file tree
Changes from 14 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
169 changes: 90 additions & 79 deletions .github/workflows/check-bittensor-e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ on:
env:
CARGO_TERM_COLOR: always
VERBOSE: ${{ github.event.inputs.verbose }}
# The in-repo SDK e2e harness reads LOCALNET_IMAGE from the environment.
# The Rust SDK e2e harness reads LOCALNET_IMAGE from the environment.
LOCALNET_IMAGE: ghcr.io/raofoundation/subtensor-localnet:ci
# Per-commit tag on GHCR: built once, pulled by every test job. Registry
# pulls beat the old artifact-tarball + docker-load path (~1-2 min/job)
Expand Down Expand Up @@ -119,59 +119,85 @@ jobs:
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.10"

- name: Install uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: "true"
cache-dependency-glob: "sdk/python/uv.lock"

# One matrix entry per test FILE, not per test: every job pays ~2-3
# minutes of fixed cost (checkout, uv sync, downloading + loading the
# localnet image, booting a chain), so per-test fan-out spent ~20x more
# time on setup than on tests. The e2e conftest's fixtures are already
# session-scoped — one localnet shared by all tests in a pytest run —
# so a per-file job is the granularity the suite was written for.
- name: Collect in-repo SDK e2e tests
- name: Verify the Rust e2e migration manifest
id: get-tests
working-directory: sdk/python
shell: bash
run: |
set -euo pipefail
uv sync --locked --all-extras --dev
test_matrix=$(
uv run pytest -q --collect-only -m e2e \
| sed -n '/^tests\/e2e\//p' \
| jq -R -s -c '
split("\n")
| map(select(. != ""))
| map(split("::")[0])
| unique
| map({nodeid: ., label: (sub("^tests/e2e/"; "") | sub("\\.py$"; ""))})
'
)
echo "Found test files: $test_matrix"
if [ "$test_matrix" = "[]" ] || [ -z "$test_matrix" ]; then
echo "No e2e tests found — failing."
exit 1
fi
manifest=sdk/bittensor-core/tests/e2e-manifest.json
jq -e 'length == 113' "$manifest" >/dev/null
jq -e 'map(.test) | length == (unique | length)' "$manifest" >/dev/null
jq -e 'all(.[]; (.test | startswith("intent_")) or (.test | startswith("test_")))' "$manifest" >/dev/null
test_matrix=$(jq -c . "$manifest")
echo "Found $(jq length "$manifest") migrated Rust e2e tests"
echo "test-files=$test_matrix" >> "$GITHUB_OUTPUT"

build-rust-e2e-test-binary:
needs: [check-label, find-e2e-tests]
if: needs.check-label.outputs.skip-bittensor-e2e-tests == 'false'
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}

- name: Install Rust + build dependencies
run: |
chmod +x ./scripts/install_build_env.sh
./scripts/install_build_env.sh

- name: Compile once and prove manifest completeness
shell: bash
run: |
set -euo pipefail
source "$HOME/.cargo/env"
mkdir -p build/rust-e2e
# The workspace/localnet build also compiles the Python binding. Keep
# CoreError/API compatibility in this faster job so failures surface
# before the expensive node builds.
cargo check -p bittensor-core-py
cargo test -p bittensor-core --test e2e --no-run --message-format=json \
> build/rust-e2e/cargo-messages.json
executable=$(
jq -r '
select(.reason == "compiler-artifact")
| select(.target.name == "e2e")
| select(.target.kind | index("test"))
| .executable // empty
' build/rust-e2e/cargo-messages.json | tail -n 1
)
test -n "$executable"
cp "$executable" build/rust-e2e/bittensor-core-e2e
chmod +x build/rust-e2e/bittensor-core-e2e

jq -r '.[].test' sdk/bittensor-core/tests/e2e-manifest.json \
| sort > build/rust-e2e/expected-tests.txt
build/rust-e2e/bittensor-core-e2e --list --format terse \
| sed -n 's/: test$//p' \
| sort > build/rust-e2e/compiled-tests.txt
diff -u build/rust-e2e/expected-tests.txt build/rust-e2e/compiled-tests.txt

- name: Upload compiled Rust e2e binary
uses: actions/upload-artifact@v4
with:
name: bittensor-core-e2e
path: build/rust-e2e/bittensor-core-e2e
if-no-files-found: error

artifacts:
name: Node • ${{ matrix.runtime }} • ${{ matrix.platform.arch }}
name: Node • fast-runtime • ${{ matrix.platform.arch }}
needs: [check-label, trusted-pr]
if: needs.check-label.outputs.skip-bittensor-e2e-tests == 'false'
strategy:
fail-fast: false
matrix:
platform:
- runner: [self-hosted, fireactions-heavy]
triple: x86_64-unknown-linux-gnu
arch: amd64
runtime: ["fast-runtime", "non-fast-runtime"]

runs-on: ${{ matrix.platform.runner }}

Expand Down Expand Up @@ -202,15 +228,13 @@ jobs:
export PATH="$HOME/.cargo/bin:$PATH"
export CARGO_BUILD_TARGET="${{ matrix.platform.triple }}"

if [ "${{ matrix.runtime }}" = "fast-runtime" ]; then
./scripts/localnet.sh --build-only
else
./scripts/localnet.sh False --build-only
fi
# The SDK e2e suite validates the fast-runtime localnet image used
# by developer and CI localnet runs.
./scripts/localnet.sh --build-only

- name: Prepare artifacts for upload
run: |
RUNTIME="${{ matrix.runtime }}"
RUNTIME="fast-runtime"
TRIPLE="${{ matrix.platform.triple }}"

BINARY_PATH="target/${RUNTIME}/${TRIPLE}/release/node-subtensor"
Expand All @@ -237,7 +261,7 @@ jobs:
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: binaries-${{ matrix.platform.triple }}-${{ matrix.runtime }}
name: binaries-${{ matrix.platform.triple }}-fast-runtime
path: build/
if-no-files-found: error

Expand Down Expand Up @@ -270,7 +294,7 @@ jobs:
docker info | grep "Docker Root Dir"

- name: Build Docker Image
run: docker build -f Dockerfile-localnet --build-arg BUILT_IN_CI="Boom shakalaka" -t "$LOCALNET_IMAGE_CI" .
run: docker build -f sdk/bittensor-core/tests/Dockerfile.localnet-fast -t "$LOCALNET_IMAGE_CI" .

- name: Login to GHCR
uses: docker/login-action@v3
Expand All @@ -282,10 +306,11 @@ jobs:
- name: Push Docker Image to GHCR
run: docker push "$LOCALNET_IMAGE_CI"

run-sdk-e2e-tests:
run-rust-e2e-tests:
needs:
- check-label
- find-e2e-tests
- build-rust-e2e-test-binary
- build-image-with-current-branch
if: needs.check-label.outputs.skip-bittensor-e2e-tests == 'false'
runs-on: ubuntu-latest
Expand All @@ -296,24 +321,13 @@ jobs:
include: ${{ fromJson(needs.find-e2e-tests.outputs.test-files) }}
Comment thread
UnArbosSix marked this conversation as resolved.

timeout-minutes: 60
name: "sdk: ${{ matrix.label }}"
name: "rust-e2e: ${{ matrix.test }}"
steps:
- name: Check-out repository
uses: actions/checkout@v4
with:
repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
ref: ${{ github.event.pull_request.head.ref || github.ref_name }}

- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Download compiled Rust e2e binary
uses: actions/download-artifact@v4
with:
enable-cache: "true"
cache-dependency-glob: "sdk/python/uv.lock"

- name: Install in-repo SDK
working-directory: sdk/python
run: |
uv sync --locked --all-extras --dev
name: bittensor-core-e2e
path: rust-e2e

- name: Login to GHCR
uses: docker/login-action@v3
Expand All @@ -329,31 +343,28 @@ jobs:
run: docker tag "$LOCALNET_IMAGE_CI" "$LOCALNET_IMAGE"

- name: Run with retry
working-directory: sdk/python
env:
SKIP_PULL: 1
RUST_BACKTRACE: 1
TEST_NAME: ${{ matrix.test }}
run: |
set +e
chmod +x rust-e2e/bittensor-core-e2e
for i in 1 2; do
echo "Attempt $i: Running tests"
# -m e2e overrides the default addopts (-m 'not e2e') which would
# otherwise deselect the collected tests and fail with "no tests
# ran". The retry reruns only what failed (--lf reads pytest's
# cache from attempt 1) against a fresh localnet — the session
# fixture tears the container down and boots a new one per run.
if [ "$i" = "1" ]; then
uv run pytest "${{ matrix.nodeid }}" -s -m e2e
else
uv run pytest "${{ matrix.nodeid }}" -s -m e2e --lf
fi
echo "Attempt $i: Running $TEST_NAME"
rust-e2e/bittensor-core-e2e \
"$TEST_NAME" \
--exact \
--nocapture \
--test-threads=1
status=$?
if [ $status -eq 0 ]; then
echo "Tests passed on attempt $i"
echo "Test passed on attempt $i"
break
else
echo "Tests failed on attempt $i"
echo "Test failed on attempt $i"
if [ $i -eq 2 ]; then
echo "Tests failed after 2 attempts"
echo "Test failed after 2 attempts"
exit 1
fi
echo "Retrying..."
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/mainnet-clone-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ jobs:
'',
`- [Open in polkadot.js apps](${appsUrl})`,
'- Sudo key: `//Alice` (the clone is a throwaway — do not use real keys)',
'- SDK e2e against it: `E2E_ENDPOINT=' + wss + ' uv run pytest -m e2e`',
'- SDK drift gate against it: `cd sdk/python && uv run python -m codegen.check --drift ' + wss + '`',
'',
`Stays up while the \`mainnet-clone\` label is on this PR (max ${hours}h). ` +
'Pushing a new commit replaces it with a fresh clone of the new code; ' +
Expand Down
31 changes: 17 additions & 14 deletions .github/workflows/runtime-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ jobs:
outputs:
runtime: ${{ steps.filter.outputs.runtime }}
docs: ${{ steps.filter.outputs.docs }}
python_sdk: ${{ steps.filter.outputs.python_sdk }}
sdk_drift: ${{ steps.filter.outputs.sdk_drift }}
steps:
# Plain gh-api file listing instead of a marketplace action: the org's
# Actions allowlist rejects unlisted third-party actions (startup_failure).
Expand All @@ -56,15 +58,23 @@ jobs:
run: |
set -euo pipefail
files=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files" --paginate --jq '.[].filename')
runtime_pattern='^(common|node|pallets|precompiles|primitives|runtime|support|chain-extensions|src|vendor|clones|sdk)/|^(Cargo\.(toml|lock)|build\.rs|rust-toolchain\.toml)$|^website/apps/bittensor-website/scripts/|^\.github/(workflows/runtime-checks\.yml|actions/rust-setup/)'
# SDK-only changes are covered by sdk-checks and the Rust SDK e2e
# workflow; they should not force clone-upgrade or SDK drift.
runtime_pattern='^(common|node|pallets|precompiles|primitives|runtime|support|chain-extensions|src|vendor|clones)/|^(Cargo\.toml|build\.rs|rust-toolchain\.toml)$|^website/apps/bittensor-website/scripts/|^\.github/(workflows/runtime-checks\.yml|actions/rust-setup/)'
docs_pattern='^website/|^sdk/python/|^\.github/workflows/runtime-checks\.yml$'
runtime=false; docs=false
python_sdk_pattern='^sdk/(python|bittensor-core|bittensor-core-py|bittensor-core-wasm)/|^Cargo.lock$|^\.github/workflows/runtime-checks\.yml$'
sdk_drift_pattern='^(common|node|pallets|precompiles|primitives|runtime|support|chain-extensions|src|vendor)/|^(Cargo\.toml|build\.rs|rust-toolchain\.toml)$'
runtime=false; docs=false; python_sdk=false; sdk_drift=false
grep -qE "$runtime_pattern" <<< "$files" && runtime=true
grep -qE "$docs_pattern" <<< "$files" && docs=true
echo "runtime=$runtime, docs=$docs"
grep -qE "$python_sdk_pattern" <<< "$files" && python_sdk=true
grep -qE "$sdk_drift_pattern" <<< "$files" && sdk_drift=true
echo "runtime=$runtime, docs=$docs, python_sdk=$python_sdk, sdk_drift=$sdk_drift"
{
echo "runtime=$runtime"
echo "docs=$docs"
echo "python_sdk=$python_sdk"
echo "sdk_drift=$sdk_drift"
} >> "$GITHUB_OUTPUT"

# The single compile of this workflow. Two artifacts:
Expand Down Expand Up @@ -275,7 +285,7 @@ jobs:
sdk-checks:
name: SDK offline checks
needs: [trusted-pr, changes]
if: github.event_name != 'pull_request' || needs.changes.outputs.runtime == 'true'
if: github.event_name != 'pull_request' || needs.changes.outputs.runtime == 'true' || needs.changes.outputs.python_sdk == 'true'
runs-on: [self-hosted, fireactions-light]
timeout-minutes: 30
steps:
Expand All @@ -298,8 +308,8 @@ jobs:
uv run python -m codegen.check --names

# For every PR: sudo-upgrade a local clone of mainnet with the proposed
# runtime, then run the clone regression suite, the SDK metadata drift gate,
# and the SDK e2e suite against the upgraded chain. Consumes the
# runtime, then run the clone regression suite and the SDK metadata drift
# gate against the upgraded chain. Consumes the
# node-release artifact instead of building, and restores the nightly
# mainnet-snapshot artifact instead of re-scraping mainnet state (falls back
# to a live scrape when no snapshot exists or the `fresh-mainnet-clone`
Expand Down Expand Up @@ -453,17 +463,10 @@ jobs:
run: uv sync --locked --all-extras --dev

- name: Metadata drift gate (committed _generated vs upgraded clone)
if: ${{ matrix.shard.sdk }}
if: ${{ matrix.shard.sdk && (github.event_name != 'pull_request' || needs.changes.outputs.sdk_drift == 'true') }}
working-directory: sdk/python
run: uv run python -m codegen.check --drift ${{ env.WS_ENDPOINT }}

- name: SDK e2e tests against upgraded clone
if: ${{ matrix.shard.sdk }}
working-directory: sdk/python
env:
E2E_ENDPOINT: ws://127.0.0.1:9944
run: uv run pytest -m e2e

- name: Dump clone node and harness logs
if: failure()
run: |
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/typescript-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ jobs:
run: |
set -euo pipefail
files=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files" --paginate --jq '.[].filename')
pattern='^(ts-tests|common|node|pallets|precompiles|primitives|runtime|support|chain-extensions|src|vendor)/|^(Cargo\.(toml|lock)|build\.rs|rust-toolchain\.toml)$|^\.github/workflows/typescript-e2e\.yml$'
# SDK-only lockfile movement is covered by SDK checks; do not run
Comment thread
UnArbosSix marked this conversation as resolved.
# zombienet unless chain/runtime or ts-tests inputs changed.
pattern='^(ts-tests|common|node|pallets|precompiles|primitives|runtime|support|chain-extensions|src|vendor)/|^(Cargo\.toml|build\.rs|rust-toolchain\.toml)$'
if grep -qE "$pattern" <<< "$files"; then
echo "e2e=true" >> "$GITHUB_OUTPUT"
else
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ just sync # uv environment; builds bittensor-core from ../bittensor-core-py (
just check # lint, typecheck, unit tests, codegen gates (same as CI)
```

End-to-end tests against a running localnet: `just e2e`. See
Chain-facing SDK coverage lives in the Rust core e2e suite:
`cargo test -p bittensor-core --test e2e -- --nocapture`. See
[SDK tests](https://bittensor.com/docs/internals/sdk-tests).

## Chain architecture (high level)
Expand Down
2 changes: 1 addition & 1 deletion clones/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

TypeScript regression tests (run via `tsx`) against a **local clone of mainnet state**, sudo-upgraded
to the runtime built from this monorepo. CI runs the smoke test plus
`test:clone-regressions` in `check-clone-upgrade.yml` after every PR runtime
`test:clone-regressions` in `runtime-checks.yml` after every PR runtime
upgrade.

## What happens
Expand Down
Loading
Loading