This repository was archived by the owner on Jul 10, 2026. It is now read-only.
Fix websocket poison connection and leaks on failed requests #725
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Bittensor SDK Test | |
| permissions: | |
| pull-requests: write | |
| contents: read | |
| concurrency: | |
| group: e2e-sdk-${{ github.ref }} | |
| cancel-in-progress: true | |
| on: | |
| pull_request: | |
| branches: | |
| - staging | |
| - master | |
| types: [ opened, synchronize, reopened, labeled, unlabeled ] | |
| workflow_dispatch: | |
| inputs: | |
| bittensor_branch: | |
| description: "Optional: Bittensor branch to use instead of staging" | |
| required: false | |
| default: "staging" | |
| env: | |
| CARGO_TERM_COLOR: always | |
| VERBOSE: ${{ github.event.inputs.verbose }} | |
| BITTENSOR_BRANCH: ${{ github.event.inputs.bittensor_branch || 'staging' }} | |
| jobs: | |
| apply-label-to-new-pr: | |
| runs-on: ubuntu-latest | |
| if: ${{ github.event.pull_request.draft == false }} | |
| outputs: | |
| should_continue_sdk: ${{ steps.check.outputs.should_continue_sdk }} | |
| steps: | |
| - name: Check | |
| id: check | |
| run: | | |
| ACTION="${{ github.event.action }}" | |
| if [[ "$ACTION" == "opened" || "$ACTION" == "reopened" ]]; then | |
| echo "should_continue_sdk=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "should_continue_sdk=false" >> $GITHUB_OUTPUT | |
| fi | |
| shell: bash | |
| - name: Add label | |
| if: steps.check.outputs.should_continue_sdk == 'true' | |
| uses: actions-ecosystem/action-add-labels@v1 | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| labels: run-bittensor-sdk-tests | |
| check-labels: | |
| needs: apply-label-to-new-pr | |
| runs-on: ubuntu-latest | |
| if: ${{ !cancelled() }} | |
| outputs: | |
| run-sdk-tests: ${{ steps.check-manual.outputs.run-sdk-tests || steps.get-labels-pr.outputs.run-sdk-tests }} | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v6 | |
| - name: Skip label check for manual runs | |
| id: check-manual | |
| if: ${{ github.event_name == 'workflow_dispatch' }} | |
| run: | | |
| echo "Manual workflow dispatch detected, skipping PR label check." | |
| echo "run-sdk-tests=true" >> $GITHUB_OUTPUT | |
| - name: Get labels from PR | |
| id: get-labels-pr | |
| if: ${{ github.event_name == 'pull_request' }} | |
| run: | | |
| sleep 5 | |
| LABELS=$(gh pr view ${{ github.event.pull_request.number }} --json labels --jq '.labels[].name') | |
| echo "Current labels: $LABELS" | |
| if echo "$LABELS" | grep -q "run-bittensor-sdk-tests"; then | |
| echo "run-sdk-tests=true" >> $GITHUB_ENV | |
| echo "run-sdk-tests=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "run-sdk-tests=false" >> $GITHUB_ENV | |
| echo "run-sdk-tests=false" >> $GITHUB_OUTPUT | |
| fi | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| find-e2e-tests: | |
| needs: check-labels | |
| if: ${{ !cancelled() && needs.check-labels.outputs.run-sdk-tests == 'true' }} | |
| runs-on: ubuntu-latest | |
| outputs: | |
| test-cases: ${{ steps.get-tests.outputs.test-cases }} | |
| steps: | |
| - name: Research preparation | |
| working-directory: ${{ github.workspace }} | |
| run: git clone https://github.com/latent-to/bittensor.git | |
| - name: Verify and checkout Bittensor branch | |
| working-directory: ${{ github.workspace }}/bittensor | |
| run: | | |
| if ! git fetch origin $BITTENSOR_BRANCH; then | |
| echo "❌ Error: Branch '$BITTENSOR_BRANCH' does not exist in latent-to/bittensor." | |
| exit 1 | |
| fi | |
| git checkout FETCH_HEAD | |
| echo "✅ Using Bittensor branch: $BITTENSOR_BRANCH" | |
| - name: Install dependencies | |
| run: sudo apt-get install -y jq | |
| - name: Find e2e test cases | |
| id: get-tests | |
| run: | | |
| # Emit one matrix entry per individual test function (as a `path::test_name` | |
| # node id) rather than per file, so each test gets its own concurrent job. | |
| # The e2e suite has no test classes, so grepping top-level `def test_*` is | |
| # sufficient and keeps this job dependency-free (no pytest collection needed). | |
| # Node ids are relative to the bittensor repo root so they resolve inside the | |
| # prebuilt image (where bittensor lives at /opt/bittensor), not the workspace. | |
| cd "${{ github.workspace }}/bittensor" | |
| test_cases=$( | |
| while IFS= read -r f; do | |
| grep -oE '^(async[[:space:]]+)?def[[:space:]]+test_[A-Za-z0-9_]+' "$f" \ | |
| | grep -oE 'test_[A-Za-z0-9_]+' \ | |
| | while IFS= read -r name; do printf '%s::%s\n' "$f" "$name"; done | |
| done < <(find tests/e2e_tests -name "test*.py") \ | |
| | jq -R -s -c 'split("\n") | map(select(. != ""))' | |
| ) | |
| echo "Collected $(echo "$test_cases" | jq 'length') test cases" | |
| echo "test-cases=$test_cases" >> $GITHUB_OUTPUT | |
| shell: bash | |
| pull-docker-image: | |
| needs: | |
| - check-labels | |
| - find-e2e-tests | |
| runs-on: ubuntu-latest | |
| if: ${{ !cancelled() && needs.check-labels.outputs.run-sdk-tests == 'true' }} | |
| steps: | |
| - name: Log in to GitHub Container Registry | |
| run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin | |
| - name: Pull Docker Image | |
| run: docker pull ghcr.io/opentensor/subtensor-localnet:devnet-ready | |
| - name: Save Docker Image to Cache | |
| run: docker save -o subtensor-localnet.tar ghcr.io/opentensor/subtensor-localnet:devnet-ready | |
| - name: Upload Docker Image as Artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: subtensor-localnet | |
| path: subtensor-localnet.tar | |
| build-e2e-image: | |
| # Build the test environment (system deps + bittensor[dev] + this PR's | |
| # async-substrate-interface) into a single image ONCE per run, so the e2e | |
| # matrix jobs below just pull it and run pytest — no per-test setup. | |
| needs: | |
| - check-labels | |
| if: ${{ !cancelled() && needs.check-labels.outputs.run-sdk-tests == 'true' }} | |
| runs-on: ubuntu-latest | |
| outputs: | |
| image: ${{ steps.meta.outputs.image }} | |
| steps: | |
| - name: Clone Bittensor SDK repo | |
| run: git clone https://github.com/latent-to/bittensor.git | |
| - name: Checkout Bittensor branch | |
| working-directory: ${{ github.workspace }}/bittensor | |
| run: | | |
| if ! git fetch origin $BITTENSOR_BRANCH; then | |
| echo "❌ Error: Branch '$BITTENSOR_BRANCH' does not exist in latent-to/bittensor." | |
| exit 1 | |
| fi | |
| git checkout FETCH_HEAD | |
| echo "✅ Using Bittensor branch: $BITTENSOR_BRANCH" | |
| - name: Clone async-substrate-interface repo | |
| run: git clone https://github.com/latent-to/async-substrate-interface.git | |
| - name: Checkout PR branch in async-substrate-interface | |
| working-directory: ${{ github.workspace }}/async-substrate-interface | |
| run: | | |
| BRANCH="${{ github.event.pull_request.head.ref || github.ref_name }}" | |
| REPO_URL="${{ github.event.pull_request.head.repo.clone_url || 'https://github.com/latent-to/async-substrate-interface.git' }}" | |
| git fetch $REPO_URL $BRANCH | |
| git checkout FETCH_HEAD | |
| echo "Current SHA: $(git rev-parse HEAD)" | |
| - name: Write Dockerfile | |
| run: | | |
| # Exclude the (large) .git dirs from the build context. | |
| printf '**/.git\n' > .dockerignore | |
| cat > Dockerfile <<'DOCKERFILE' | |
| FROM python:3.13-slim | |
| # System deps: build toolchain for any source wheels, curl for the docker CLI | |
| # download below, and git for any VCS-based installs. | |
| RUN apt-get update && apt-get install -y --no-install-recommends \ | |
| clang curl libssl-dev llvm libudev-dev protobuf-compiler git \ | |
| && rm -rf /var/lib/apt/lists/* | |
| # Static Docker CLI (client only). The e2e fixtures shell out to `docker` to | |
| # launch the localnet via the mounted host socket; installing the static binary | |
| # to /usr/local/bin guarantees it is on PATH regardless of the base distro's | |
| # packaging (the `docker.io` apt package did not reliably provide it). The final | |
| # `docker --version` fails the build early if the CLI is ever missing. | |
| ARG DOCKER_CLI_VERSION=27.5.1 | |
| RUN curl -fsSL "https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_CLI_VERSION}.tgz" \ | |
| | tar -xz -C /usr/local/bin --strip-components=1 docker/docker \ | |
| && docker --version | |
| RUN pip install --no-cache-dir uv | |
| COPY bittensor /opt/bittensor | |
| COPY async-substrate-interface /opt/async-substrate-interface | |
| # Install the SDK with dev extras, then overlay this PR's build of | |
| # async-substrate-interface so the tests exercise the PR code. | |
| RUN uv pip install --system '/opt/bittensor[dev]' \ | |
| && (uv pip uninstall --system async-substrate-interface || true) \ | |
| && uv pip install --system /opt/async-substrate-interface | |
| # pytest is invoked from here so the relative `tests/e2e_tests/...` node ids resolve. | |
| WORKDIR /opt/bittensor | |
| DOCKERFILE | |
| - name: Set image ref | |
| id: meta | |
| # A plain local tag (no registry): the image is shipped to the matrix jobs as a | |
| # `docker save` tarball artifact, not via a registry push. | |
| run: echo "image=async-substrate-interface-e2e:${{ github.run_id }}-${{ github.run_attempt }}" >> $GITHUB_OUTPUT | |
| - name: Build image | |
| run: docker build -t "${{ steps.meta.outputs.image }}" . | |
| - name: Save image to tarball | |
| # Distribute via a GitHub artifact (fast internal transport, same as the localnet | |
| # image) instead of GHCR, which avoids slow registry pulls across all matrix jobs, | |
| # registry rate-limits, and the fork-PR push-permission problem. | |
| run: docker save -o e2e-image.tar "${{ steps.meta.outputs.image }}" | |
| - name: Upload image as artifact | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: e2e-image | |
| path: e2e-image.tar | |
| run-e2e-tests: | |
| needs: | |
| - check-labels | |
| - find-e2e-tests | |
| - pull-docker-image | |
| - build-e2e-image | |
| # Only fan out the matrix if every input-producing dependency succeeded — otherwise | |
| # all 139 jobs would spawn only to fail (missing image/localnet artifact, or no | |
| # test list). !cancelled() alone would let them run on upstream failure. | |
| if: >- | |
| ${{ !cancelled() | |
| && needs.check-labels.outputs.run-sdk-tests == 'true' | |
| && needs.find-e2e-tests.result == 'success' | |
| && needs.pull-docker-image.result == 'success' | |
| && needs.build-e2e-image.result == 'success' }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| max-parallel: 64 | |
| matrix: | |
| test-case: ${{ fromJson(needs.find-e2e-tests.outputs.test-cases) }} | |
| timeout-minutes: 60 | |
| name: "e2e tests: ${{ matrix.test-case }}" | |
| steps: | |
| - name: Download prebuilt test image | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: e2e-image | |
| - name: Load prebuilt test image | |
| run: docker load -i e2e-image.tar | |
| - name: Download Cached Docker Image | |
| uses: actions/download-artifact@v8 | |
| with: | |
| name: subtensor-localnet | |
| - name: Load Docker Image | |
| run: docker load -i subtensor-localnet.tar | |
| # The job stays on the bare runner (no `container:`), so we can launch the test | |
| # container ourselves with `--network host` — GitHub injects its own network into | |
| # job containers, which conflicts with host mode. Host networking + the mounted | |
| # docker socket let the e2e fixtures start the localnet (published on the host's | |
| # :9944) and reach it at ws://localhost:9944. All deps are baked into the image, | |
| # so there is no per-test apt/clone/install. | |
| - name: Run e2e tests | |
| # --reruns retries a flaky test (e.g. when the localnet is listening but its RPC | |
| # isn't ready yet, surfacing as a websocket InvalidMessage during connect). The | |
| # whole test reruns, re-spinning the localnet fixture, so by the retry the node | |
| # is ready. pytest-rerunfailures ships with bittensor[dev]. | |
| run: | | |
| docker run --rm \ | |
| --network host \ | |
| -v /var/run/docker.sock:/var/run/docker.sock \ | |
| -w /opt/bittensor \ | |
| -e SKIP_PULL=1 \ | |
| "${{ needs.build-e2e-image.outputs.image }}" \ | |
| pytest "${{ matrix.test-case }}" -s --reruns 2 --reruns-delay 5 | |
| run-integration-and-unit-test: | |
| needs: | |
| - check-labels | |
| if: ${{ !cancelled() && needs.check-labels.outputs.run-sdk-tests == 'true' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check-out repository | |
| uses: actions/checkout@v6 | |
| - name: Install system dependencies | |
| run: | | |
| sudo apt-get update && | |
| sudo apt-get install -y clang curl libssl-dev llvm libudev-dev protobuf-compiler | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: '3.13' | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v8.0.0 | |
| - name: Clone Bittensor SDK repo | |
| run: git clone https://github.com/latent-to/bittensor.git | |
| - name: Checkout Bittensor branch | |
| working-directory: ${{ github.workspace }}/bittensor | |
| run: | | |
| if ! git fetch origin $BITTENSOR_BRANCH; then | |
| echo "❌ Error: Branch '$BITTENSOR_BRANCH' does not exist in latent-to/bittensor." | |
| exit 1 | |
| fi | |
| git checkout FETCH_HEAD | |
| echo "✅ Using Bittensor branch: $BITTENSOR_BRANCH" | |
| - name: Install Bittensor SDK dependencies | |
| working-directory: ${{ github.workspace }}/bittensor | |
| run: uv pip install --system '.[dev]' torch | |
| - name: Clone async-substrate-interface repo | |
| run: git clone https://github.com/latent-to/async-substrate-interface.git | |
| - name: Checkout PR branch in async-substrate-interface | |
| working-directory: ${{ github.workspace }}/async-substrate-interface | |
| run: | | |
| git fetch ${{ github.event.pull_request.head.repo.clone_url }} ${{ github.event.pull_request.head.ref }} | |
| git checkout FETCH_HEAD | |
| echo "Current SHA: $(git rev-parse HEAD)" | |
| - name: Install async-substrate-interface with dev dependencies | |
| working-directory: ${{ github.workspace }}/async-substrate-interface | |
| run: | | |
| uv pip uninstall --system async-substrate-interface || true | |
| uv pip install --system '.[dev]' | |
| - name: Run SDK integration tests | |
| run: pytest ${{ github.workspace }}/bittensor/tests/integration_tests | |
| - name: Run Bittensor SDK unit tests | |
| run: pytest ${{ github.workspace }}/bittensor/tests/unit_tests |