name: Build and Test PR

on:
  pull_request: # Trigger for pull requests.
    types: [opened, synchronize, reopened, ready_for_review]
    branches:
      - main
      - v[0-9]*
  workflow_dispatch: # Allows for manual triggering.
    inputs:
      ref:
        description: "The ref to build and test."
        required: false

# If another instance of this workflow is started for the same PR, cancel the
# old one.  If a PR is updated and a new test run is started, the old test run
# will be cancelled automatically to conserve resources.
concurrency:
  group: ${{ github.workflow }}-${{ inputs.ref || github.ref }}
  cancel-in-progress: true

jobs:
  lint:
    name: Lint
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
        with:
          ref: ${{ inputs.ref || github.ref }}

      - name: Lint
        run: python build/check.py

  build_and_test:
    # Don't waste time doing a full matrix of test runs when there was an
    # obvious linter error.
    needs: lint
    strategy:
      matrix:
        include:
          # Run Linux browsers with xvfb, so they're in a headless X session.
          # Additionally, generate a code coverage report from Linux Chrome.
          # It should be the uncompiled build, or else we won't execute any
          # coverage instrumentation on full-stack player integration tests.
          - os: ubuntu-latest
            browser: Chrome
            extra_flags: "--use-xvfb --html-coverage-report --uncompiled"
          - os: ubuntu-latest
            browser: Firefox
            extra_flags: "--use-xvfb"
          - os: ubuntu-latest
            browser: Edge
            extra_flags: "--use-xvfb"

          - os: macos-latest
            browser: Chrome
          - os: macos-latest
            browser: Firefox
          - os: macos-latest
            browser: Edge
          - os: macos-latest
            browser: Safari
          - os: macos-latest
            browser: Safari-old

          - os: windows-latest
            browser: Chrome
          - os: windows-latest
            browser: Firefox
          - os: windows-latest
            browser: Edge

      # Disable fail-fast so that one matrix-job failing doesn't make the other
      # ones end early.
      fail-fast: false

    name: ${{ matrix.os }} ${{ matrix.browser }}
    runs-on: ${{ matrix.os }}

    steps:
      # Firefox on Ubuntu appears to not have the right things installed in
      # the environment used by GitHub actions, so make sure that ffmpeg is
      # installed. Otherwise, the browser might not support some codecs that the
      # tests assume will be supported.
      - name: Install FFmpeg
        if: matrix.os == 'ubuntu-latest' && matrix.browser == 'Firefox'
        run: sudo apt -y update && sudo apt -y install ffmpeg

      # Edge 107 fails DRM tests due to an outdated Widevine CDM.  Force Edge
      # to update to 108+.
      - name: Upgrade Edge
        if: matrix.os == 'ubuntu-latest' && matrix.browser == 'Edge'
        run: |
          # If it's Edge 107, update it.  Otherwise, don't.  This way, we don't
          # break something later when Edge 108+ is available by default in
          # GitHub Actions.
          if apt show microsoft-edge-stable | grep -q '^Version: 107'; then
            wget https://packages.microsoft.com/repos/edge/pool/main/m/microsoft-edge-stable/microsoft-edge-stable_108.0.1462.46-1_amd64.deb
            sudo dpkg -i microsoft-edge-stable_108.0.1462.46-1_amd64.deb
          fi

      - name: Checkout code
        uses: actions/checkout@v3
        with:
          ref: ${{ inputs.ref || github.ref }}

      # Older versions of Safari can be installed, but not to the root, and it
      # can't replace the standard version, at least not on GitHub's VMs.  If
      # you try to install directly to the root with sudo, it will appear to
      # succeed, but will have no effect.  If you try to script it explicitly
      # with rm -rf and cp, this will fail.  Safari may be on a read-only
      # filesystem.
      - name: Install old Safari to home directory
        if: matrix.os == 'macos-latest' && matrix.browser == 'Safari-old'
        run: |
          # Download Safari 15
          # This URL discovered through the seed files listed at
          # https://github.com/zhangyoufu/swscan.apple.com/blob/master/url.txt
          curl -Lv http://swcdn.apple.com/content/downloads/42/33/012-57329-A_41P2VU6UHN/5fw5vna27fdw4mqfak5adj3pjpxvo9hgh7/Safari15.6.1CatalinaAuto.pkg > Safari.pkg

          # Install older Safari to homedir specifically.
          installer -pkg Safari.pkg -target CurrentUserHomeDirectory

          # Install a launcher that can execute a shell script to launch this
          npm install karma-script-launcher --save-dev

      # Some CI images (self-hosted or otherwise) don't have the minimum Java
      # version necessary for the compiler (Java 11).
      - uses: actions/setup-java@v3
        with:
          distribution: zulu
          java-version: 11

      - name: Build Player
        run: python build/all.py

      - name: Test Player
        shell: bash
        run: |
          browser=${{ matrix.browser }}

          if [[ "$browser" == "Safari-old" ]]; then
            # Replace the browser name with a script that can launch this
            # browser from the command line.
            browser="$PWD/.github/workflows/safari-homedir-launcher.sh"
          fi

          python build/test.py \
            --browsers "$browser" \
            --reporters spec --spec-hide-passed \
            ${{ matrix.extra_flags }}

      - name: Find coverage reports
        id: coverage
        if: always() # Even on failure of an earlier step.
        shell: bash
        run: |
          # If the "coverage" directory exists...
          if [ -d coverage ]; then
            # Find the path to the coverage output folder.  It includes the
            # exact browser version in the path, so it will vary.
            coverage_folder="$( (ls -d coverage/* || true) | head -1 )"

            # Build a folder to stage all the coverage artifacts with
            # predictable paths.  The resulting zip file will not have any
            # internal directories.
            mkdir coverage/staging/
            cp "$coverage_folder"/coverage.json coverage/staging/
            cp "$coverage_folder"/coverage-details.json coverage/staging/
            echo "${{ github.event.number }}" > coverage/staging/pr-number.json

            echo "coverage_found=true" >> $GITHUB_OUTPUT
            echo "Coverage report staged."
          else
            echo "No coverage report generated."
          fi

      - name: Upload coverage reports
        uses: actions/upload-artifact@v3
        if: ${{ always() && steps.coverage.outputs.coverage_found }}
        with:
          name: coverage
          # This will create a download called coverage.zip containing all of
          # these files, with no internal folders.
          path: |
            coverage/staging/coverage.json
            coverage/staging/coverage-details.json
            coverage/staging/pr-number.json
          # Since we've already filtered this step for instances where there is
          # an environment variable set, the file should definitely be there.
          if-no-files-found: error

      # Upload new screenshots and diffs on failure; ignore if missing
      - name: Upload screenshots
        uses: actions/upload-artifact@v3
        if: ${{ failure() }}
        with:
          name: screenshots-${{ matrix.browser }}
          path: |
            test/test/assets/screenshots/*/*.png-new
            test/test/assets/screenshots/*/*.png-diff
          if-no-files-found: ignore
          retention-days: 5

  build_in_docker:
    # Don't waste time doing a full matrix of test runs when there was an
    # obvious linter error.
    needs: lint
    name: Docker
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
        with:
          ref: ${{ inputs.ref || github.ref }}

      - name: Docker
        run: |
          docker build -t shaka-player-build build/docker
          docker run -v $(pwd):/usr/src --user $(id -u):$(id -g) shaka-player-build
