diff --git a/.github/workflows/self-test-qa.yaml b/.github/workflows/self-test-qa.yaml index 1fc58ab..0f27b29 100644 --- a/.github/workflows/self-test-qa.yaml +++ b/.github/workflows/self-test-qa.yaml @@ -2,6 +2,10 @@ name: QA self-test on: pull_request: +permissions: + actions: read + contents: read + jobs: lint-python: uses: ./.github/workflows/lint-python.yaml @@ -25,7 +29,7 @@ jobs: slow-test-platforms: '["ubuntu-latest"]' slow-test-python-versions: '["3.14"]' lowest-python-version: "3.8" - lowest-python-platform: '["jammy", "arm64"]' + lowest-python-platform: '["focal", "amd64"]' use-lxd: true pytest-markers: smoketest and not steamtest setup-vars: SETUP_EXTRA=1 @@ -39,6 +43,10 @@ jobs: with: # Test on many OS's to ensure that these workflows work everywhere. # Also ensure we can add a list of tags for self-hosted runners. + # Intentionally overlaps with test-python-custom to verify that + # coverage artifact names are unique across callers (the reusable + # workflow includes the caller job identifier in the artifact name + # to prevent 409 Conflict errors). fast-test-platforms: '["ubuntu-latest"]' fast-test-python-versions: '["3.14"]' slow-test-platforms: '["ubuntu-latest"]' @@ -61,3 +69,44 @@ jobs: fast-test-python-versions: "" slow-test-python-versions: "" lowest-python-version: "" + verify-coverage-artifacts: + name: Verify coverage artifacts + if: ${{ !cancelled() }} + needs: [test-python, test-python-custom, test-python-as-root] + runs-on: ubuntu-latest + steps: + - name: Verify coverage artifacts are unique and present + env: + GH_TOKEN: ${{ github.token }} + run: | + artifacts=$(gh api "repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts" \ + --paginate --jq '.artifacts[].name') + coverage=$(echo "$artifacts" | grep '^coverage-' | sort) + + echo "=== Coverage artifacts ===" + echo "$coverage" + echo "" + + # Verify no duplicate artifact names + dupes=$(echo "$coverage" | uniq -d) + if [ -n "$dupes" ]; then + echo "::error::Duplicate coverage artifact names:" + echo "$dupes" + exit 1 + fi + + exit_code=0 + + # Verify each caller job produced fast, slow, and lowest artifacts + for job in test-python test-python-custom test-python-as-root; do + for type in fast slow lowest; do + if ! echo "$coverage" | grep -q "^coverage-${job}-${type}-"; then + echo "::error::Missing coverage-${job}-${type}-* artifact" + exit_code=1 + fi + done + done + + total=$(printf %s "$coverage" | grep -c .) + echo "Total: ${total} unique coverage artifacts" + exit $exit_code diff --git a/.github/workflows/test-python.yaml b/.github/workflows/test-python.yaml index dfb9da3..5ebcd1b 100644 --- a/.github/workflows/test-python.yaml +++ b/.github/workflows/test-python.yaml @@ -152,11 +152,21 @@ jobs: echo "::endgroup::" done exit $exit_code + - name: Resolve caller job name + id: ctx + if: ${{ !cancelled() }} + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + name=$(gh api "repos/${GITHUB_REPOSITORY}/actions/jobs/${{ job.check_run_id }}" --jq '.name') + caller="${name%% / *}" + echo "caller-job=${caller}" >> "$GITHUB_OUTPUT" - name: Upload test coverage if: ${{ inputs.source-files == 'true' }} uses: actions/upload-artifact@v7 with: - name: coverage-fast-${{ join(matrix.platform, '-') }} + name: coverage-${{ steps.ctx.outputs.caller-job }}-fast-${{ join(matrix.platform, '-') }} overwrite: true path: | ./coverage.xml @@ -214,11 +224,21 @@ jobs: # Unset raw secret slots so they don't leak into make or sudo -E unset SECRET_1 SECRET_2 SECRET_3 SECRET_4 SECRET_5 SECRET_6 SECRET_7 SECRET_8 SECRET_9 SECRET_10 ${{ inputs.test-command-prefix }} make test-coverage PYTEST_ADDOPTS="--no-header -v -rN -m 'slow ${MARKERS:+and ($MARKERS)}'" + - name: Resolve caller job name + id: ctx + if: ${{ !cancelled() }} + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + name=$(gh api "repos/${GITHUB_REPOSITORY}/actions/jobs/${{ job.check_run_id }}" --jq '.name') + caller="${name%% / *}" + echo "caller-job=${caller}" >> "$GITHUB_OUTPUT" - name: Upload test coverage uses: actions/upload-artifact@v7 if: ${{ inputs.source-files == 'true' }} with: - name: coverage-slow-${{ join(matrix.platform, '-') }}-${{ matrix.python-version }} + name: coverage-${{ steps.ctx.outputs.caller-job }}-slow-${{ join(matrix.platform, '-') }}-${{ matrix.python-version }} overwrite: true path: | ./coverage.xml @@ -277,11 +297,21 @@ jobs: # Unset raw secret slots so they don't leak into make or sudo -E unset SECRET_1 SECRET_2 SECRET_3 SECRET_4 SECRET_5 SECRET_6 SECRET_7 SECRET_8 SECRET_9 SECRET_10 ${{ inputs.test-command-prefix }} make test-coverage PYTEST_ADDOPTS="-m '${MARKERS}'" + - name: Resolve caller job name + id: ctx + if: ${{ !cancelled() }} + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + name=$(gh api "repos/${GITHUB_REPOSITORY}/actions/jobs/${{ job.check_run_id }}" --jq '.name') + caller="${name%% / *}" + echo "caller-job=${caller}" >> "$GITHUB_OUTPUT" - name: Upload test coverage uses: actions/upload-artifact@v7 if: ${{ inputs.source-files == 'true' }} with: - name: coverage-lowest-py${{ inputs.lowest-python-version }} + name: coverage-${{ steps.ctx.outputs.caller-job }}-lowest-py${{ inputs.lowest-python-version }} overwrite: true path: | ./coverage.xml