# Tests a broad set of Quarto functionality that users are likely to encounter.
# A failure indicates some significant portion of functionality is likely to be broken.
name: Smoke Tests
on:
  workflow_call:
    inputs:
      buckets:
        description: "JSON string for buckets of tests to run in loop. Array of grouped tests."
        required: true
        type: string
      time-test:
        description: "Should we run tests to produce test file"
        required: false
        type: boolean
        default: false
      extra-r-packages:
        description: "extra R package to install for the runs (like a dev version of one of the deps) - comma separated, passed to renv::install"
        required: false
        type: string
        default: ""
  workflow_dispatch:
    inputs:
      buckets:
        description: "JSON string for buckets of tests to run in loop. Array of grouped tests."
        required: false
        type: string
        default: ""
      time-test:
        description: "Should we run tests to produce test file"
        required: false
        type: boolean
        default: false
      extra-r-packages:
        description: "extra R package to install for the runs (like a dev version of one of the deps) - comma separated, passed to renv::install"
        required: false
        type: string
        default: ""
  schedule:
    - cron: "0 6 * * *"

# this is required to cleanup julia cache
permissions:
  actions: write
  contents: read

jobs:
  run-smokes:
    name: Run smoke (${{ matrix.os }})${{ matrix.time-test && ' with timed file' || ''}}${{ inputs.extra-r-packages && ' - with some extra R packages'|| ''}}
    if: github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository == 'quarto-dev/quarto-cli')
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest]
        time-test:
          - ${{ inputs.time-test }}
        exclude:
          # only run timed tests on Linux
          - os: windows-latest
            time-test: true
    runs-on: ${{ matrix.os }}
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v6

      - name: Fix temp dir to use runner one (windows)
        if: runner.os == 'Windows'
        run: |
          echo "TMPDIR=${{ runner.temp }}" >> $GITHUB_ENV
          echo "TMP=${{ runner.temp }}" >> $GITHUB_ENV
          echo "TEMP=${{ runner.temp }}" >> $GITHUB_ENV
        shell: bash

      - name: Set up R
        uses: r-lib/actions/setup-r@v2
        with:
          r-version: "4.5.1"
          use-public-rspm: true
          # required to avoid rtools bin in path
          windows-path-include-rtools: false

      - name: Install node (for Playwright, MECA)
        uses: actions/setup-node@v6
        with:
          node-version: 22

      - name: Cache multiplex server node_modules
        if: ${{ runner.os != 'Windows' || github.event_name == 'schedule' }}
        uses: actions/cache@v5
        with:
          path: tests/integration/playwright/multiplex-server/node_modules
          key: ${{ runner.os }}-multiplex-server-${{ hashFiles('tests/integration/playwright/multiplex-server/package.json') }}
          restore-keys: |
            ${{ runner.os }}-multiplex-server-

      - name: Install node dependencies
        if: ${{ runner.os != 'Windows' || github.event_name == 'schedule' }}
        run: npm install --loglevel=error --no-audit
        working-directory: ./tests/integration/playwright
        shell: bash

      - name: Install Playwright Browsers
        if: ${{ runner.os != 'Windows' || github.event_name == 'schedule' }}
        run: npx playwright install --with-deps
        working-directory: ./tests/integration/playwright

      - name: Install MECA validator
        if: ${{ runner.os != 'Windows' }}
        run: npm install -g meca --loglevel=error --no-audit

      - name: Set RENV_PATHS_ROOT
        shell: bash
        run: |
          echo "RENV_PATHS_ROOT=${{ runner.temp }}/renv" >> $GITHUB_ENV

      - name: Get R and OS version
        id: get-version
        run: |
          cat("os-version=", sessionInfo()$running, "\n", file = Sys.getenv("GITHUB_OUTPUT"), sep = "", append = TRUE)
          cat("r-version=", R.Version()$version.string, "\n", file = Sys.getenv("GITHUB_OUTPUT"), sep = "", append = TRUE)
        shell: Rscript {0}

      - name: Restore Renv package cache
        id: cache-renv-packages-restore
        uses: actions/cache/restore@v5
        with:
          path: |
            ${{ env.RENV_PATHS_ROOT }}
            renv/library
          key: ${{ steps.get-version.outputs.os-version }}-${{ steps.get-version.outputs.r-version }}-renv-2-${{ hashFiles('tests/renv.lock') }}
          restore-keys: |
            ${{ steps.get-version.outputs.os-version }}-${{ steps.get-version.outputs.r-version }}-renv-2-

      - name: Install missing system deps
        if: runner.os == 'Linux'
        run: |
          sudo apt-get update -y
          sudo apt-get install -y libcurl4-openssl-dev
          sudo apt-get install -y libxml2-utils
          sudo apt-get install -y libharfbuzz-dev libfribidi-dev
          sudo apt-get install -y poppler-utils

      - name: Install rsvg-convert for SVG conversion tests
        # Only do it on linux runner, and only if we are running the relevant tests
        if: runner.os == 'Linux' && (format('{0}', inputs.buckets) == '' || contains(inputs.buckets, 'render-pdf-svg-conversion') || contains(inputs.buckets, 'pdf-standard'))
        run: |
          sudo apt-get update -y
          sudo apt-get install -y librsvg2-bin

      - name: Restore R packages
        working-directory: tests
        run: |
          cat("::group::Installing renv if needed\n")
          if (!requireNamespace('renv', quietly = TRUE)) install.packages('renv')
          cat("::endgroup::\n")
          cat("::group::Restoring R packages from renv.lock\n")
          renv::restore()
          cat("::endgroup::\n")
          cat("::group::Installing dev versions of knitr and rmarkdown\n")
          # Install dev versions for our testing
          # Use r-universe to avoid github api calls
          try(install.packages('rmarkdown', repos = c('https://rstudio.r-universe.dev', getOption('repos'))))
          try(install.packages('knitr', repos = c('https://yihui.r-universe.dev', getOption('repos'))))
          cat("::endgroup::\n")
          if ('${{ inputs.extra-r-packages }}' != '') {
            cat(sprintf("::notice::Running with the following extra R packages for renv: %s\n", "${{ inputs.extra-r-packages }}"))
            cat("::group::Installing extra R packages\n")
            renv::install(strsplit("${{ inputs.extra-r-packages }}", split = ",")[[1]])
            cat("::endgroup::\n")
          }
        shell: Rscript {0}
        env:
          GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}

      - name: Show R session information
        working-directory: tests
        run: |
          # Session info
          cat("::group::Session info\n")
          renv::install("sessioninfo")
          sessioninfo::session_info(pkgs = "installed", include_base = TRUE)
          cat("::endgroup::\n")
        shell: Rscript {0}

      - name: Install uv for Python
        uses: astral-sh/setup-uv@v7
        with:
          version: "0.9.5"
          enable-cache: true
          cache-dependency-glob: "tests/uv.lock"

      - name: Install Python and Dependencies
        working-directory: tests
        run: |
          uv sync --frozen

      - uses: ./.github/workflows/actions/quarto-dev

      - name: Install Tinytex
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          quarto install tinytex

      - name: Install veraPDF for PDF standard validation
        if: runner.os == 'Linux' && (format('{0}', inputs.buckets) == '' || contains(inputs.buckets, 'pdf-standard'))
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          quarto install verapdf

      - name: Cache Typst packages
        id: cache-typst
        uses: ./.github/actions/cache-typst

      - name: Install Chrome Headless Shell
        run: |
          quarto install chrome-headless-shell --no-prompt

      - name: Setup Julia
        uses: julia-actions/setup-julia@a8c65a2a580b6a5cf30070e825e62f9fc0fee1d7
        id: setup-julia
        with:
          version: "1.11.7"

      - name: Load Julia packages from cache
        uses: julia-actions/cache@v3
        with:
          cache-name: julia-cache;workflow=${{ github.workflow }};job=${{ github.job }};julia=${{ steps.setup-julia.outputs.julia-version }}

      # - name: Setup tmate session
      #   uses: mxschmitt/action-tmate@v3

      - name: Restore Julia Packages
        working-directory: tests
        shell: bash
        run: |
          # Setup IJulia with the jupyter from the Python environment
          # https://julialang.github.io/IJulia.jl/stable/manual/installation/
          export JUPYTER=$(find $(dirname $(uv run --frozen which jupyter))/ -type f -name "jupyter.exe" -o -name "jupyter")
          uv run --frozen julia --color=yes --project=. -e "import Pkg; Pkg.instantiate(); Pkg.build(\"IJulia\"); Pkg.precompile()"
          echo "Julia Jupyter:"
          uv run julia --project=. -e "import IJulia;println(IJulia.JUPYTER);println(IJulia.find_jupyter_subcommand(\"notebook\"))"

      - uses: ./.github/actions/merge-extension-tests

      - name: Setup timing file for timed test
        if: ${{ matrix.time-test == true }}
        run: |
          echo "QUARTO_TEST_TIMING=timing-for-ci.txt" >> "$GITHUB_ENV"

      # - name: Setup tmate session
      #   uses: mxschmitt/action-tmate@v3

      - name: Run all Smoke Tests Windows
        if: ${{ runner.os == 'Windows' && format('{0}', inputs.buckets) == '' && matrix.time-test == false }}
        env:
          # Useful as TinyTeX latest release is checked in run-test.sh
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: ./run-tests.ps1
        working-directory: tests
        shell: pwsh

      - name: Run all Smoke Tests Linux
        if: ${{ runner.os != 'Windows' && format('{0}', inputs.buckets) == '' }}
        env:
          # Useful as TinyTeX latest release is checked in run-test.sh
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: ./run-tests.sh
        working-directory: tests
        shell: bash

      - name: Run Smoke Tests as a bucket on Linux
        if: ${{ runner.os != 'Windows' && format('{0}', inputs.buckets) != '' }}
        env:
          # Useful as TinyTeX latest release is checked in run-test.sh
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          haserror=0
          failed_tests=()
          readarray -t my_array < <(echo '${{ inputs.buckets }}' | jq -rc '.[]')
          for file in "${my_array[@]}"; do
            echo "::group::Testing ${file}"
            echo ">>> ./run-tests.sh ${file}"
            # Run tests without -e so we don't exit on first failure
            set +e
            shopt -s globstar && ./run-tests.sh "$file"
            status=$?
            set -e
            echo "::endgroup::"
            if [ $status -ne 0 ]; then
              echo "::error title=Test Bucket Failed::Test bucket ${file} failed with exit code ${status}"
              echo ">>> Error found in test file: ${file}"
              haserror=1
              failed_tests+=("$file")
            fi
          done
          if [ $haserror -eq 1 ]; then
            echo "---- FAILING TESTS SUMMARY ----"
            echo "    The following test buckets failed:"
            for failed in "${failed_tests[@]}"; do
              echo "    - $failed"
            done
            exit 1
          else
            echo ">>> All tests passed"
          fi
        working-directory: tests
        shell: bash

      - name: Run Smoke Tests as a bucket on Windows
        if: ${{ runner.os == 'Windows' && format('{0}', inputs.buckets) != '' }}
        env:
          # Useful as TinyTeX latest release is checked in run-test.sh
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          $haserror=$false
          $failed_tests=@()
          foreach ($file in ('${{ inputs.buckets }}' | ConvertFrom-Json)) {
            Write-Host "::group::Testing ${file}"
            Write-Host ">>> ./run-tests.ps1 ${file}"
            ./run-tests.ps1 $file
            $status=$LASTEXITCODE
            Write-Host "::endgroup::"
            if ($status -ne 0) {
              Write-Host "::error title=Test Bucket Failed::Test bucket ${file} failed with exit code ${status}"
              Write-Host ">>> Error found in test file: ${file}"
              $haserror=$true
              $failed_tests+=$file
            }
          }
          if ($haserror) {
            Write-Host "---- FAILING TESTS SUMMARY ----"
            Write-Host "    The following test buckets failed:"
            foreach ($failed in $failed_tests) {
              Write-Host "    - $failed"
            }
            Exit 1
          } else {
            Write-Host ">>> All tests have passed"
          }
        working-directory: tests
        shell: pwsh

      - name: Upload test timing file
        uses: actions/upload-artifact@v7
        if: matrix.time-test
        with:
          name: timed test file
          path: tests/timing-for-ci.txt

      - name: Save Typst cache
        if: always() && steps.cache-typst.outputs.cache-hit != 'true'
        uses: actions/cache/save@v5
        with:
          key: ${{ steps.cache-typst.outputs.cache-primary-key }}
          path: ${{ steps.cache-typst.outputs.cache-path }}

      - name: Save Renv package cache
        # don't save cache if we have extra R packages
        if: ${{ always() && steps.cache-renv-packages-restore.outputs.cache-hit != 'true' && inputs.extra-r-packages == '' }}
        uses: actions/cache/save@v5
        with:
          path: |
            ${{ env.RENV_PATHS_ROOT }}
            renv/library
          key: ${{ steps.cache-renv-packages-restore.outputs.cache-primary-key }}

      - uses: actions/upload-artifact@v7
        # Upload pLaywright test report if they exists (playwright is only running on Linux for now)
        if: ${{ !cancelled() && runner.os != 'Windows' && hashFiles('tests/integration/playwright/playwright-report/**/*') != '' }}
        with:
          name: playwright-report
          path: ./tests/integration/playwright/playwright-report/
          retention-days: 30
