name: test

# This reusable workflow structure was chosen for the sole purpose of working around
# the 256 job per matrix limit.  The initial total test job count was 290.  This
# approach shifts the 256 limit to be per OS rather than overall.  A simpler single
# regular workflow with matrixing against the OS would be preferred.

on:
  workflow_call:
    inputs:
      emoji:
        required: true
        type: string
      matrix:
        required: true
        type: string
      name:
        required: true
        type: string
      file_name:
        required: true
        type: string
      concurrency-name:
        required: true
        type: string
      configuration:
        required: true
        type: string
      matrix_mode:
        required: true
        type: string
      runs-on:
        required: true
        type: string
      arch:
        required: true
        type: string
      collect-junit:
        required: false
        type: boolean
        default: true

concurrency:
  # SHA is added to the end if on `main` to let all main workflows run
  group: ${{ github.ref }}-${{ github.workflow }}-${{ inputs.concurrency-name }}-${{ github.event_name == 'workflow_dispatch' && github.run_id || '' }}-${{ (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') || startsWith(github.ref, 'refs/heads/long_lived/')) && github.sha || '' }}
  cancel-in-progress: true

defaults:
  run:
    shell: bash

jobs:
  test:
    name: ${{ matrix.os.emoji }} ${{ matrix.configuration.name }} - ${{ matrix.python.name }}
    runs-on: ${{ matrix.os.runs-on }}
    timeout-minutes: ${{ matrix.configuration.job_timeout }}
    strategy:
      fail-fast: false
      matrix:
        configuration: ${{ fromJson(inputs.configuration) }}
        os:
          - emoji: ${{ inputs.emoji }}
            matrix: ${{ inputs.matrix }}
            name: ${{ inputs.name }}
            file_name: ${{ inputs.file_name }}
            runs-on: ${{ inputs.runs-on }}
        arch:
          - matrix: ${{ inputs.arch }}
        python:
          - name: "3.8"
            file_name: "3.8"
            action: "3.8"
            apt: "3.8"
            install_sh: "3.8"
            matrix: "3.8"
            exclude_from:
              limited: True
              main: True
          - name: "3.9"
            file_name: "3.9"
            action: "3.9"
            apt: "3.9"
            install_sh: "3.9"
            matrix: "3.9"
            exclude_from:
              limited: True
              main: True
          - name: "3.10"
            file_name: "3.10"
            action: "3.10"
            apt: "3.10"
            install_sh: "3.10"
            matrix: "3.10"
          - name: "3.11"
            file_name: "3.11"
            action: "3.11"
            apt: "3.11"
            install_sh: "3.11"
            matrix: "3.11"
            exclude_from:
              limited: True
              main: True
          - name: "3.12"
            file_name: "3.12"
            action: "3.12"
            apt: "3.12"
            install_sh: "3.12"
            matrix: "3.12"
            exclude_from:
              limited: True
              main: True
        exclude:
          - os:
              matrix: macos
            python:
              matrix: "3.8"
          - arch:
              matrix: arm
            configuration:
              install_timelord: true
          - os:
              matrix: windows
            configuration:
              install_timelord: true
          - os:
              matrix: windows
            configuration:
              name: core.full_node
          - python:
              exclude_from:
                ${{ inputs.matrix_mode }}: True

    env:
      CHIA_ROOT: ${{ github.workspace }}/.chia/mainnet
      CHIA_SIMULATOR_ROOT: ${{ github.workspace }}/.chia/simulator
      JOB_FILE_NAME: tests_${{ matrix.os.file_name }}_python-${{ matrix.python.file_name }}_${{ matrix.configuration.module_import_path }}${{ matrix.configuration.file_name_index }}
      BLOCKS_AND_PLOTS_VERSION: 0.38.0

    steps:
      - name: Configure git
        run: |
          git config --global core.autocrlf false

      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set Env
        uses: Chia-Network/actions/setjobenv@main
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Setup Python environment
        uses: Chia-Network/actions/setup-python@main
        with:
          python-version: ${{ matrix.python.action }}

      - name: Create keychain for CI use (macOS)
        if: matrix.os.matrix == 'macos'
        run: |
          security create-keychain -p foo chiachain
          security default-keychain -s chiachain
          security unlock-keychain -p foo chiachain
          security set-keychain-settings -t 7200 -u chiachain

      - name: Cache npm (Ubuntu)
        if: matrix.os.matrix == 'ubuntu'
        uses: actions/cache@v4
        env:
          SEGMENT_DOWNLOAD_TIMEOUT_MIN: 1
        with:
          path: ~/.npm
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      - uses: chia-network/actions/cache-pip@main

      - name: Cache test blocks and plots
        if: matrix.configuration.checkout_blocks_and_plots
        uses: actions/cache@v4
        env:
          SEGMENT_DOWNLOAD_TIMEOUT_MIN: 1
        id: test-blocks-plots
        with:
          path: |
            ${{ github.workspace }}/.chia/blocks
            ${{ github.workspace }}/.chia/test-plots
          key: ${{ env.BLOCKS_AND_PLOTS_VERSION }}

      - name: Checkout test blocks and plots
        if: matrix.configuration.checkout_blocks_and_plots && steps.test-blocks-plots.outputs.cache-hit != 'true'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release download -R Chia-Network/test-cache ${{ env.BLOCKS_AND_PLOTS_VERSION }} --archive=tar.gz -O - | tar xzf -
          mkdir "${GITHUB_WORKSPACE}/.chia"
          mv "${GITHUB_WORKSPACE}/test-cache-${{ env.BLOCKS_AND_PLOTS_VERSION }}/"* "${GITHUB_WORKSPACE}/.chia"

      - uses: ./.github/actions/install
        with:
          python-version: ${{ matrix.python.install_sh }}
          development: true
          editable: false
          legacy_keyring: ${{ matrix.configuration.legacy_keyring_required }}

      - uses: chia-network/actions/activate-venv@main

      - name: Install timelord
        if: matrix.configuration.install_timelord
        run: |
          sh install-timelord.sh -n
          ./vdf_bench square_asm 400000

      - name: Verify source and installed tests collect and match
        run: |
          echo '==== collecting source tests ===='
          if pytest --collect-only -qq --import-mode=prepend chia/_tests/ > source_tests_raw
          then
            cat source_tests_raw | sed -n 's;^\(chia/_tests/.*\);\1;p' | sort > source_tests
          else
            echo '    ==== source test collection failure'
            cat source_tests_raw
            exit 1
          fi

          echo '==== collecting installed tests ===='
          if pytest --collect-only -qq --import-mode=append --pyargs chia._tests > installed_tests_raw
          then
            cat installed_tests_raw | sed -n 's;^\.venv/.*/\(chia/_tests/.*\);\1;p' | sort > installed_tests
          else
            echo '    ==== installed test collection failure'
            cat installed_tests_raw
            exit 1
          fi

          echo '==== collected tests ===='
          for mode in source installed
          do
            echo "::group::collected ${mode} tests"
            cat ${mode}_tests
            echo '::endgroup::'
          done

          echo '==== diffing collected tests ===='
          echo "::group::collected tests diff"
          diff --unified source_tests installed_tests; DIFF_EXIT_CODE=$?
          echo '::endgroup::'

      - name: Move chia/ so we test the installed code
        run: |
          mv chia/ notchia/

      - name: Test blockchain code with pytest
        env:
          ENABLE_PYTEST_MONITOR: ${{ matrix.os.matrix == 'ubuntu' && matrix.configuration.enable_pytest_monitor || '' }}
        run: |
          pytest --cov=chia --cov-config=.coveragerc --cov-report= -o 'junit_suite_name=${{ env.JOB_FILE_NAME }}' --junitxml='junit-data/junit.${{ env.JOB_FILE_NAME }}.xml' --durations=10 ${{ matrix.configuration.pytest_parallel_args[matrix.os.matrix] }} -m "not benchmark" ${{ env.ENABLE_PYTEST_MONITOR }} ${{ matrix.configuration.test_files }}

      - name: Move back to chia/ for coverage
        run: |
          mv notchia/ chia/

      - name: Publish JUnit results
        if: inputs.collect-junit
        uses: actions/upload-artifact@v4
        with:
          name: junit-data-${{ env.JOB_FILE_NAME }}
          path: junit-data/*
          if-no-files-found: error

      - name: Process coverage data
        run: |
          coverage xml --rcfile=.coveragerc -o coverage.xml
          mkdir coverage-data
          cp .coverage "coverage-data/.coverage.${{ env.JOB_FILE_NAME }}"
          cp coverage.xml "coverage-data/coverage.${{ env.JOB_FILE_NAME }}.xml"
          coverage report --rcfile=.coveragerc --show-missing

      - name: Publish coverage data
        uses: actions/upload-artifact@v4
        with:
          name: coverage-data-${{ env.JOB_FILE_NAME }}
          path: coverage-data/*
          if-no-files-found: error

      - name: Check resource usage
        if: matrix.os.matrix == 'ubuntu' && matrix.configuration.check_resource_usage
        run: |
          sqlite3 -readonly -separator " " .pymon "select item,cpu_usage,total_time,mem_usage from TEST_METRICS order by mem_usage desc;" >metrics.out
          ./chia/_tests/check_pytest_monitor_output.py <metrics.out
