name: 'Run SYCL CTS tests'

inputs:
  extra_cmake_args:
    required: false
  cts_testing_mode:
    required: true
  sycl_cts_artifact:
    require: false
  target_devices:
    required: true
  retention-days:
    required: false

runs:
  using: "composite"
  steps:
  - name: Checkout SYCL CTS tests
    if: inputs.cts_testing_mode != 'run-only'
    uses: ./devops/actions/cached_checkout
    with:
      path: khronos_sycl_cts
      repository: 'KhronosGroup/SYCL-CTS'
      ref: 'main'
      default_branch: 'main'
      cache_path: "/__w/repo_cache/"
  - name: SYCL CTS GIT submodules init
    if: inputs.cts_testing_mode != 'run-only'
    shell: bash
    run: |
      git -C khronos_sycl_cts submodule update --init
  - name: Build SYCL CTS tests
    if: inputs.sycl_cts_artifact == ''
    shell: bash
    env:
      CMAKE_EXTRA_ARGS: ${{ inputs.extra_cmake_args }}
    run: |
      cts_exclude_filter=""
      # If CTS_TESTS_TO_BUILD is null - use filter
      if [ -z "$CTS_TESTS_TO_BUILD" ]; then
        if [ "${{ contains(inputs.cts_testing_mode, 'build-only')  }}" = "true" ]; then
          cts_exclude_filter=$PWD/devops/cts_exclude_filter_compfails
        elif [ "${{ contains(inputs.target_devices, 'opencl:cpu')  }}" = "true" ]; then
          cts_exclude_filter=$PWD/devops/cts_exclude_filter_OCL_CPU
        elif [ "${{ contains(inputs.target_devices, 'level_zero:gpu')  }}" = "true" ]; then
          cts_exclude_filter=$PWD/devops/cts_exclude_filter_L0_GPU
        fi

        # List excluded SYCL CTS categories:
        # SYCL_CTS_EXCLUDE_TEST_CATEGORIES - Optional file specifying a list
        # of test categories to be excluded from the build.
        echo "::group::Excluded test categories"
        cat $cts_exclude_filter
        echo "::endgroup::"
      fi

      cmake -GNinja -B./build-cts -S./khronos_sycl_cts -DCMAKE_CXX_COMPILER=$(which clang++) \
      -DSYCL_IMPLEMENTATION=DPCPP \
      -DSYCL_CTS_EXCLUDE_TEST_CATEGORIES="$cts_exclude_filter" \
      -DSYCL_CTS_ENABLE_OPENCL_INTEROP_TESTS=OFF \
      -DDPCPP_INSTALL_DIR="$(dirname $(which clang++))/.." \
      $CMAKE_EXTRA_ARGS
      # Ignore errors so that if one category build fails others still have a
      # chance to finish and be executed at the run stage. Note that
      # "test_conformance" target skips building "test_all" executable.
      ninja -C build-cts -k0 $( [ -n "$CTS_TESTS_TO_BUILD" ] && echo "$CTS_TESTS_TO_BUILD" || echo "test_conformance")

  - name: Pack SYCL-CTS binaries
    if: always() && !cancelled() && inputs.cts_testing_mode == 'build-only'
    shell: bash
    run: tar -I 'zstd -9' -cf sycl_cts_bin.tar.zst -C ./build-cts/bin .

  - name: Upload SYCL-CTS binaries
    if: always() && !cancelled() && inputs.cts_testing_mode == 'build-only'
    uses: actions/upload-artifact@v4
    with:
      name: sycl_cts_bin
      path: sycl_cts_bin.tar.zst
      retention-days: ${{ inputs.retention-days }}

  - name: Download SYCL-CTS binaries
    if: inputs.sycl_cts_artifact != ''
    uses: actions/download-artifact@v4
    with:
      name: ${{ inputs.sycl_cts_artifact }}

  - name: Extract SYCL-CTS binaries
    if: inputs.sycl_cts_artifact != ''
    shell: bash
    run: |
      mkdir -p build-cts/bin
      tar -I 'zstd' -xf sycl_cts_bin.tar.zst -C build-cts/bin

  - name: SYCL CTS List devices
    # Proceed with execution even if the 'build' step did not succeed.
    if: (always() && !cancelled()) && inputs.cts_testing_mode != 'build-only'
    shell: bash
    env:
      ONEAPI_DEVICE_SELECTOR: ${{ inputs.target_devices }}
    run: |
      ./build-cts/bin/* --list-devices

  # If the suite was built on another machine then the build contains the full
  # set of tests. We have special files to filter out some test categories,
  # see "devops/cts_exclude_filter_*". Each configuration has its own file, e.g.
  # there is "cts_exclude_filter_OCL_CPU" for opencl:cpu device. Therefore,
  # these files may differ from each other, so when there is a pre-built set of
  # tests, we need to filter it according to the filter-file.
  - name: Filter SYCL CTS test categories
    if: inputs.sycl_cts_artifact != ''
    shell: bash
    run: |
      cts_exclude_filter=""
      if [ "${{ contains(inputs.target_devices, 'opencl:cpu')  }}" = "true" ]; then
        cts_exclude_filter=$PWD/devops/cts_exclude_filter_OCL_CPU
      elif [ "${{ contains(inputs.target_devices, 'level_zero:gpu')  }}" = "true" ]; then
        cts_exclude_filter=$PWD/devops/cts_exclude_filter_L0_GPU
      fi

      while IFS= read -r line; do
        if [[ $line != \#* ]]; then
          rm "./build-cts/bin/test_$line"
        fi
      done < "$cts_exclude_filter"

  - name: Run SYCL CTS tests
    # Proceed with execution even if the previous two steps did not succeed.
    if: (always() && !cancelled()) && inputs.cts_testing_mode != 'build-only'
    env:
      ONEAPI_DEVICE_SELECTOR: ${{ inputs.target_devices }}
    # By-default GitHub actions execute the "run" shell script with -e option,
    # so the execution terminates if any command returns a non-zero status.
    # Since we're using a loop to run all test-binaries separately, some test
    # may fail and terminate the execution. Setting "shell" value to override
    # the default behavior.
    # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#custom-shell
    shell: bash {0}
    run: |
      # Run each test category separately so that
      #  - crash on one would not affect others
      #  - multiple tests could be run in parallel
      mkdir logs
      find build-cts/bin/ -type f -print | \
        xargs -t -I % -P 8 sh -c 'log=logs/$(basename %).log ; echo % >$log ; date >>$log ; timeout 60m % >>$log 2>&1 ; ret=$? ; echo "exit code: $ret" >>$log ; date >>$log  ; exit $ret'
      ret=$?

      for f in logs/* ; do
        echo "::group::$f"
        cat $f
        echo "::endgroup::"
      done

      echo "::group::Fails:"
      grep 'exit code: [^0]' -r logs
      echo "::endgroup::"

      grep 'exit code: [^0]' -r logs >> $GITHUB_STEP_SUMMARY

      exit $ret
