diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 292263f0c4..19fe7d259c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -416,6 +416,9 @@ jobs: name: Check job status if: always() runs-on: ubuntu-latest + permissions: + actions: read + contents: read needs: - should-skip - detect-changes @@ -426,6 +429,122 @@ jobs: - test-windows - doc steps: + - name: Report universally-skipped tests + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + + REPO="${GITHUB_REPOSITORY}" + WORKDIR=$(mktemp -d) + trap 'rm -rf "${WORKDIR}"' EXIT + + mkdir -p "${WORKDIR}/configs" + + # Collect all jobs for this run and group matrix jobs into the five + # top-level test configurations from ci.yml. + jobs_json=$(gh api --paginate \ + "repos/${REPO}/actions/runs/${GITHUB_RUN_ID}/jobs?per_page=100" \ + | jq -s '[.[].jobs[]]') + + declare -A CONFIG_PATTERNS=( + [test-sdist-linux]='^Test sdist linux-64 / ' + [test-sdist-windows]='^Test sdist win-64 / ' + [test-linux-64]='^Test linux-64 / ' + [test-linux-aarch64]='^Test linux-aarch64 / ' + [test-windows]='^Test (win-64|windows) / ' + ) + + configs=( + test-sdist-linux + test-sdist-windows + test-linux-64 + test-linux-aarch64 + test-windows + ) + + for cfg in "${configs[@]}"; do + cfg_dir="${WORKDIR}/configs/${cfg}" + mkdir -p "${cfg_dir}/logs" + job_ids=$(echo "${jobs_json}" | jq -r \ + --arg pat "${CONFIG_PATTERNS[$cfg]}" \ + '.[] | select(.name | test($pat)) | .id') + + if [[ -z "${job_ids}" ]]; then + echo "No matrix jobs found for ${cfg}" >&2 + : > "${cfg_dir}/skipped.txt" + continue + fi + + for job_id in ${job_ids}; do + logfile="${cfg_dir}/logs/${job_id}.log" + # gh run view handles log retrieval for a job ID reliably. + gh run view "${GITHUB_RUN_ID}" --job "${job_id}" --log > "${logfile}" || true + done + + # Extract test IDs from lines matching pytest SKIPPED output. + grep -h ' SKIPPED' "${cfg_dir}/logs/"*.log 2>/dev/null \ + | grep -oE '[^[:space:]]+\.py::[^[:space:]]+ SKIPPED' \ + | sed 's/ SKIPPED$//' \ + | sort -u > "${cfg_dir}/skipped.txt" || true + + echo "${cfg}: $(wc -l < "${cfg_dir}/skipped.txt") skipped tests" + done + + { + echo "## Universally-skipped tests" + echo "" + } >> "${GITHUB_STEP_SUMMARY}" + + available_configs=0 + missing_configs=() + for cfg in "${configs[@]}"; do + if [[ -s "${WORKDIR}/configs/${cfg}/skipped.txt" || -n "$(ls -A "${WORKDIR}/configs/${cfg}/logs" 2>/dev/null || true)" ]]; then + available_configs=$((available_configs + 1)) + else + missing_configs+=("${cfg}") + fi + done + + if [[ ${available_configs} -eq 0 ]]; then + echo "_No test job logs found in this run._" >> "${GITHUB_STEP_SUMMARY}" + exit 0 + fi + + if [[ ${#missing_configs[@]} -gt 0 ]]; then + { + echo "_Warning: missing logs for configuration(s): ${missing_configs[*]}_" + echo "" + } >> "${GITHUB_STEP_SUMMARY}" + fi + + # Start intersection from first config, then narrow with each of the + # remaining four configurations. + cp "${WORKDIR}/configs/${configs[0]}/skipped.txt" "${WORKDIR}/intersection.txt" + for cfg in "${configs[@]:1}"; do + comm -12 \ + "${WORKDIR}/intersection.txt" \ + "${WORKDIR}/configs/${cfg}/skipped.txt" \ + > "${WORKDIR}/intersection_new.txt" + mv "${WORKDIR}/intersection_new.txt" "${WORKDIR}/intersection.txt" + done + + count=$(wc -l < "${WORKDIR}/intersection.txt") + { + echo "Tests skipped across all five test configurations:" + echo "" + if [[ ${count} -eq 0 ]]; then + echo "_No tests were skipped in all configurations._" + else + echo "| Test |" + echo "| --- |" + while IFS= read -r test; do + [[ -z "${test}" ]] && continue + echo "| \`${test}\` |" + done < "${WORKDIR}/intersection.txt" + fi + } >> "${GITHUB_STEP_SUMMARY}" + - name: Exit run: | # if any dependencies were cancelled or failed, that's a failure diff --git a/cuda_bindings/tests/test_cuda.py b/cuda_bindings/tests/test_cuda.py index e3eefb1fdd..0ea86a82c8 100644 --- a/cuda_bindings/tests/test_cuda.py +++ b/cuda_bindings/tests/test_cuda.py @@ -40,6 +40,11 @@ def callableBinary(name): return shutil.which(name) is not None +@pytest.mark.skipif(True, "Always skip!") +def test_always_skip(): + pass + + def test_cuda_memcpy(): # Get device