name: "Bot: Pytest" # Reusable workflow called by diffusers_bot.yml for `/diffusers-bot pytest `. # Runs pytest on a GPU runner and reports the result back on the PR. on: workflow_call: inputs: pytest_args: required: true type: string jobs: gate: name: Acknowledge runs-on: ubuntu-22.04 permissions: pull-requests: write outputs: comment_id: ${{ steps.comment.outputs.comment_id }} steps: - name: Acknowledge with 👀 env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: ${{ github.repository }} COMMENT_ID: ${{ github.event.comment.id }} run: | gh api -X POST "repos/${REPO}/issues/comments/${COMMENT_ID}/reactions" -f content="eyes" >/dev/null - name: Post "running" comment id: comment env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: ${{ github.repository }} PR: ${{ github.event.issue.number }} COMMENTER: ${{ github.event.comment.user.login }} PYTEST_ARGS: ${{ inputs.pytest_args }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | BODY="⏳ Running \`pytest ${PYTEST_ARGS}\` on a GPU runner — [view logs](${RUN_URL}). Triggered by @${COMMENTER}." CID=$(gh api -X POST "repos/${REPO}/issues/${PR}/comments" -f body="$BODY" --jq '.id') echo "comment_id=${CID}" >> "$GITHUB_OUTPUT" gpu: name: Run pytest on GPU needs: gate # A newer command on the same PR supersedes an in-flight one. Scoped to this job # only so the superseded run's `report` still updates its comment. concurrency: group: diffusers-bot-pytest-${{ github.event.issue.number }} cancel-in-progress: true runs-on: group: aws-g4dn-2xlarge container: image: diffusers/diffusers-pytorch-cuda options: --gpus all --shm-size "16gb" --ipc host # Least privilege: this job checks out and runs untrusted fork code, so it gets no # write token. Comment writes happen only in `gate`/`report`. permissions: contents: read env: DIFFUSERS_IS_CI: yes OMP_NUM_THREADS: 8 MKL_NUM_THREADS: 8 HF_XET_HIGH_PERFORMANCE: 1 PYTEST_TIMEOUT: 600 # Force version overrides across every `uv pip install`: pin the # torch/torchvision/torchaudio set baked into the image so `-U` installs can't bump # torch and break torchvision's C++ ABI. Re-written into the file in the install step. UV_OVERRIDE: /tmp/uv-overrides.txt defaults: run: shell: bash steps: - name: Checkout PR head uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 with: # Works for forks too — no fork credentials needed. ref: refs/pull/${{ github.event.issue.number }}/head fetch-depth: 2 - name: NVIDIA-SMI run: nvidia-smi - name: Install dependencies run: | printf 'torch==2.10.0\ntorchvision==0.25.0\ntorchaudio==2.10.0\n' > "$UV_OVERRIDE" uv pip install -e ".[quality,training,test]" uv pip install peft@git+https://github.com/huggingface/peft.git uv pip uninstall accelerate && uv pip install -U accelerate@git+https://github.com/huggingface/accelerate.git uv pip uninstall transformers huggingface_hub && UV_PRERELEASE=allow uv pip install -U transformers@git+https://github.com/huggingface/transformers.git - name: Environment run: diffusers-cli env - name: Run pytest env: # No secrets here: this step runs untrusted fork code (pytest imports the PR's # conftest.py/plugins), so exposing a token would let a malicious PR exfiltrate # it. Public Hub models download without auth; gated-repo tests are unsupported. # https://pytorch.org/docs/stable/notes/randomness.html#avoiding-nondeterministic-algorithms CUBLAS_WORKSPACE_CONFIG: :16:8 # Forwarded via env (not interpolated into the script) to avoid breakage on # quotes/special characters in a legitimate command. PYTEST_ARGS: ${{ inputs.pytest_args }} run: | eval "pytest --make-reports=tests_bot_gpu $PYTEST_ARGS" - name: Failure short reports if: ${{ failure() }} run: | cat reports/tests_bot_gpu_stats.txt || true cat reports/tests_bot_gpu_failures_short.txt || true - name: Test suite reports artifacts if: ${{ always() }} uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: bot_gpu_test_reports path: reports report: name: Report status needs: [gate, gpu] # Always run so the comment is updated on success, failure, or cancellation — # but only if `gate` actually posted a comment to update. if: ${{ always() && needs.gate.outputs.comment_id != '' }} runs-on: ubuntu-22.04 permissions: pull-requests: write steps: - name: Update comment with final status env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: ${{ github.repository }} CID: ${{ needs.gate.outputs.comment_id }} RESULT: ${{ needs.gpu.result }} PYTEST_ARGS: ${{ inputs.pytest_args }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} run: | case "$RESULT" in success) EMOJI="✅"; MSG="passed";; failure) EMOJI="❌"; MSG="failed";; cancelled) EMOJI="⚠️"; MSG="was cancelled";; *) EMOJI="⚠️"; MSG="did not run (${RESULT})";; esac BODY="${EMOJI} \`pytest ${PYTEST_ARGS}\` ${MSG} on GPU — [view logs](${RUN_URL})." gh api -X PATCH "repos/${REPO}/issues/comments/${CID}" -f body="$BODY"