Test Extensions #50
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: Test Extensions | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| debug: | |
| description: "If 'true', test a small sample (up to 5 per extension type)" | |
| default: "false" | |
| batch_size: | |
| description: "Number of extensions per batch" | |
| default: "25" | |
| schedule: | |
| - cron: "0 14 15 * *" | |
| concurrency: | |
| group: test-extensions | |
| cancel-in-progress: false | |
| permissions: | |
| contents: read | |
| jobs: | |
| build-matrix: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| matrix: ${{ steps.matrix.outputs.matrix }} | |
| skipped: ${{ steps.matrix.outputs.skipped }} | |
| steps: | |
| - name: Checkout scripts | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| with: | |
| sparse-checkout: .github/scripts/test-extensions | |
| sparse-checkout-cone-mode: false | |
| path: _scripts | |
| - name: Checkout quarto-wizard branch | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| with: | |
| ref: quarto-wizard | |
| path: _data | |
| - name: Create GitHub App token | |
| uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 | |
| id: app-token | |
| with: | |
| app-id: ${{ vars.APP_ID }} | |
| private-key: ${{ secrets.APP_KEY }} | |
| permission-contents: read | |
| permission-metadata: read | |
| - name: Build test matrix | |
| id: matrix | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| DEBUG: ${{ inputs.debug || 'false' }} | |
| BATCH_SIZE: ${{ inputs.batch_size || '25' }} | |
| REPO_OWNER: ${{ github.repository_owner }} | |
| shell: bash | |
| working-directory: _data | |
| run: bash "${GITHUB_WORKSPACE}/_scripts/.github/scripts/test-extensions/build-matrix.sh" | |
| test-extensions: | |
| name: test-extensions (batch ${{ matrix.batch_index }}, ${{ matrix.quarto_channel }}) | |
| needs: build-matrix | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| max-parallel: 10 | |
| matrix: ${{ fromJSON(needs.build-matrix.outputs.matrix) }} | |
| timeout-minutes: 90 | |
| defaults: | |
| run: | |
| shell: bash | |
| env: | |
| IMAGE: ${{ matrix.image_ref }} | |
| IMAGE_READY: ${{ matrix.image_ready }} | |
| QUARTO_CHANNEL: ${{ matrix.quarto_channel }} | |
| steps: | |
| - name: Checkout scripts | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| with: | |
| sparse-checkout: .github/scripts/test-extensions | |
| sparse-checkout-cone-mode: false | |
| - name: Write extensions batch to file | |
| env: | |
| EXTENSIONS_JSON: ${{ toJSON(matrix.extensions) }} | |
| run: printf '%s\n' "${EXTENSIONS_JSON}" > extensions-batch.json | |
| - name: Inspect batch | |
| id: inspect-batch | |
| run: | | |
| set -euo pipefail | |
| ext_count=$(jq 'length' extensions-batch.json) | |
| echo "count=${ext_count}" >> "${GITHUB_OUTPUT}" | |
| if [[ "${ext_count}" -eq 0 ]]; then | |
| echo "::notice::No extensions in this batch." | |
| echo '[]' > results.json | |
| mkdir -p logs | |
| fi | |
| - name: Prepare render image | |
| if: steps.inspect-batch.outputs.count != '0' | |
| run: | | |
| set -euo pipefail | |
| if [[ ! "${IMAGE}" =~ @sha256:[0-9a-f]{64}$ ]]; then | |
| echo "::error::Image reference '${IMAGE}' is not a valid digest-pinned reference." | |
| exit 1 | |
| fi | |
| if [[ "${IMAGE_READY}" == "true" ]]; then | |
| docker pull "${IMAGE}" | |
| docker tag "${IMAGE}" render-image | |
| else | |
| echo "::notice::IMAGE_READY=false for ${QUARTO_CHANNEL}. Rebuilding from inline Dockerfile (~15 min overhead)." | |
| docker build \ | |
| --build-arg BASE_IMAGE="${IMAGE}" \ | |
| --build-arg HOST_UID="$(id -u)" \ | |
| --build-arg HOST_GID="$(id -g)" \ | |
| -t render-image - <<'DOCKERFILE' | |
| ARG BASE_IMAGE | |
| ARG HOST_UID=1000 | |
| ARG HOST_GID=1000 | |
| FROM ${BASE_IMAGE} | |
| USER root | |
| RUN apt-get update -qq && apt-get install -y -qq --no-install-recommends \ | |
| jq libcurl4-openssl-dev libssl-dev libxml2-dev libfontconfig1-dev \ | |
| libharfbuzz-dev libfribidi-dev libfreetype6-dev libpng-dev \ | |
| libtiff5-dev libjpeg-dev libgit2-dev zlib1g-dev cmake \ | |
| ghostscript \ | |
| && rm -rf /var/lib/apt/lists/* | |
| RUN Rscript -e 'install.packages(c("rmarkdown", "knitr", "reticulate", "ggplot2", "dplyr", "tidyverse", "kableExtra", "palmerpenguins", "gt", "tibble", "ragg"), repos = "https://cloud.r-project.org", lib = .Library)' | |
| ARG HOST_UID | |
| ARG HOST_GID | |
| RUN if [ -d /opt/tinytex ]; then chown -R ${HOST_UID}:${HOST_GID} /opt/tinytex; fi | |
| USER vscode | |
| ENV UV_LINK_MODE=copy | |
| RUN uv venv /home/vscode/.venv \ | |
| && . /home/vscode/.venv/bin/activate \ | |
| && uv pip install jupyter papermill matplotlib shinylive | |
| ENV VIRTUAL_ENV=/home/vscode/.venv | |
| ENV PATH="/home/vscode/.venv/bin:${PATH}" | |
| DOCKERFILE | |
| fi | |
| - name: Clone extensions | |
| if: steps.inspect-batch.outputs.count != '0' | |
| run: bash .github/scripts/test-extensions/clone-extensions.sh | |
| - name: Render extensions | |
| if: steps.inspect-batch.outputs.count != '0' | |
| run: bash .github/scripts/test-extensions/render-extensions.sh | |
| - name: Upload results | |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 | |
| if: always() | |
| with: | |
| name: results-batch-${{ matrix.batch_index }}-${{ matrix.quarto_channel }} | |
| path: results.json | |
| retention-days: 7 | |
| - name: Enforce log limits | |
| id: enforce-log-limits | |
| if: always() | |
| env: | |
| MAX_LOG_FILE_BYTES: "10485760" | |
| MAX_LOG_FILES: "300" | |
| MAX_LOG_TOTAL_BYTES: "104857600" | |
| run: bash .github/scripts/test-extensions/enforce-log-limits.sh | |
| - name: Upload logs | |
| uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 | |
| if: always() && steps.enforce-log-limits.outcome == 'success' | |
| with: | |
| name: logs-batch-${{ matrix.batch_index }}-${{ matrix.quarto_channel }} | |
| path: logs/ | |
| retention-days: 7 | |
| summarise-and-publish: | |
| needs: [build-matrix, test-extensions] | |
| if: always() && needs.build-matrix.result == 'success' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Create GitHub App token | |
| uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3 | |
| id: app-token | |
| with: | |
| app-id: ${{ vars.APP_ID }} | |
| private-key: ${{ secrets.APP_KEY }} | |
| permission-contents: write | |
| permission-metadata: read | |
| - name: Setup git and check branch | |
| id: setup | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| APP_SLUG: ${{ steps.app-token.outputs.app-slug }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| if [[ ! "${APP_SLUG}" =~ ^[a-z0-9-]+$ ]]; then | |
| echo "::error::Invalid app slug '${APP_SLUG}'." | |
| exit 1 | |
| fi | |
| user_id=$(gh api "/users/${APP_SLUG}[bot]" --jq .id) | |
| git config --global user.name "${APP_SLUG}[bot]" | |
| git config --global user.email "${user_id}+${APP_SLUG}[bot]@users.noreply.github.com" | |
| if gh api "repos/${GITHUB_REPOSITORY}/branches/quarto-tests" >/dev/null 2>&1; then | |
| echo "branch-exists=true" >> "${GITHUB_OUTPUT}" | |
| else | |
| echo "branch-exists=false" >> "${GITHUB_OUTPUT}" | |
| fi | |
| - name: Checkout quarto-tests branch | |
| if: steps.setup.outputs.branch-exists == 'true' | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| with: | |
| ref: quarto-tests | |
| token: ${{ steps.app-token.outputs.token }} | |
| - name: Initialise if branch does not exist | |
| if: steps.setup.outputs.branch-exists == 'false' | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| gh auth setup-git | |
| git init | |
| git remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" | |
| git checkout --orphan quarto-tests | |
| echo '{}' > test-results.json | |
| git add test-results.json | |
| git commit -m "ci: initialise quarto-tests branch" | |
| if ! git push -u origin quarto-tests; then | |
| echo "::warning::Branch initialisation raced with another run. Fetching existing branch." | |
| git fetch origin quarto-tests | |
| git checkout -B quarto-tests origin/quarto-tests | |
| fi | |
| - name: Download all result artefacts | |
| uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 | |
| with: | |
| path: artefacts | |
| pattern: results-batch-* | |
| - name: Download all log artefacts | |
| uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 | |
| with: | |
| path: logs | |
| pattern: logs-batch-* | |
| merge-multiple: true | |
| - name: Checkout scripts | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 | |
| with: | |
| sparse-checkout: .github/scripts/test-extensions | |
| sparse-checkout-cone-mode: false | |
| path: _scripts | |
| - name: Publish results | |
| env: | |
| SKIPPED: ${{ needs.build-matrix.outputs.skipped }} | |
| shell: bash | |
| run: bash "${GITHUB_WORKSPACE}/_scripts/.github/scripts/test-extensions/publish-results.sh" |