# Google Cloud unit tests that run when Rust code or dependencies are modified,
# but only on PRs from the ZcashFoundation/zebra repository. (External PRs are tested by mergify.)
#
# This workflow is designed for running various unit tests within Docker containers.
# Jobs:
# 1. Builds a Docker image for tests, adaptable to the specified network (Mainnet or Testnet).
# 2. 'test-all': Executes all Zebra tests, including normally ignored ones, in a Docker environment.
# 3. 'test-fake-activation-heights': Runs state tests with fake activation heights, isolating its build products.
# 4. 'test-empty-sync': Tests Zebra's ability to sync and checkpoint from an empty state.
# 5. 'test-lightwalletd-integration': Validates integration with 'lightwalletd' starting from an empty state.
# 6. 'test-configuration-file': Assesses the default Docker configuration for Zebra.
# 7. 'test-configuration-file-testnet': Checks the Docker image reconfiguration for the Testnet.
# 8. 'test-zebra-conf-path': Tests Zebra using a custom Docker configuration.
name: Docker Unit Tests

# Ensures that only one workflow task will run at a time. Previous builds, if
# already in process, will get cancelled. Only the latest commit will be allowed
# to run, cancelling any workflows in between
concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

on:
  workflow_dispatch:
    inputs:
      network:
        default: 'Mainnet'
        description: 'Network to deploy: Mainnet or Testnet'
        required: true
      no_cache:
        description: 'Disable the Docker cache for this build'
        required: false
        type: boolean
        default: false

  pull_request:
    # Skip PRs where Rust code and dependencies aren't modified.
    paths:
      # code and tests
      - '**/*.rs'
      # hard-coded checkpoints and proptest regressions
      - '**/*.txt'
      # test data snapshots
      - '**/*.snap'
      # dependencies
      - '**/Cargo.toml'
      - '**/Cargo.lock'
      # configuration files
      - '.cargo/config.toml'
      - '**/clippy.toml'
      # workflow definitions
      - 'docker/**'
      - '.github/workflows/ci-unit-tests-docker.yml'
      - '.github/workflows/sub-deploy-integration-tests-gcp.yml'
      - '.github/workflows/sub-build-docker-image.yml'
      - '.github/workflows/sub-find-cached-disks.yml'
      - '.github/workflows/sub-test-zebra-config.yml'

  push:
    branches:
      - main
    # Skip main branch updates where Rust code and dependencies aren't modified.
    paths:
      # code and tests
      - '**/*.rs'
      # hard-coded checkpoints and proptest regressions
      - '**/*.txt'
      # test data snapshots
      - '**/*.snap'
      # dependencies
      - '**/Cargo.toml'
      - '**/Cargo.lock'
      # configuration files
      - '.cargo/config.toml'
      - '**/clippy.toml'
      # workflow definitions
      - 'docker/**'
      - '.dockerignore'
      - '.github/workflows/ci-unit-tests-docker.yml'
      - '.github/workflows/sub-deploy-integration-tests-gcp.yml'
      - '.github/workflows/sub-find-cached-disks.yml'
      - '.github/workflows/sub-build-docker-image.yml'

env:
  RUST_LOG: ${{ vars.RUST_LOG }}
  RUST_BACKTRACE: ${{ vars.RUST_BACKTRACE }}
  RUST_LIB_BACKTRACE: ${{ vars.RUST_LIB_BACKTRACE }}
  COLORBT_SHOW_HIDDEN: ${{ vars.COLORBT_SHOW_HIDDEN }}
  CARGO_INCREMENTAL: ${{ vars.CARGO_INCREMENTAL }}

# IMPORTANT
#
# The job names in `ci-unit-tests-docker.yml`, `ci-unit-tests-docker.patch.yml` and
# `ci-unit-tests-docker.patch-external.yml` must be kept in sync.
jobs:
  # Build the docker image used by the tests.
  #
  # The default network in the Zebra config in the image is mainnet, unless a manually triggered
  # workflow or repository variable is configured differently. Testnet jobs change that config to
  # testnet when running the image.
  build:
    name: Build CI Docker
    # Skip PRs from external repositories, let them pass, and then Mergify will check them
    if: ${{ !startsWith(github.event_name, 'pull') || !github.event.pull_request.head.repo.fork }}
    uses: ./.github/workflows/sub-build-docker-image.yml
    with:
      dockerfile_path: ./docker/Dockerfile
      dockerfile_target: tests
      image_name: ${{ vars.CI_IMAGE_NAME }}
      no_cache: ${{ inputs.no_cache || false }}
      rust_backtrace: full
      rust_lib_backtrace: full
      rust_log: info

  # Run all the zebra tests, including tests that are ignored by default.
  #
  # - We activate the gRPC feature to avoid recompiling `zebrad`, but we don't actually run any gRPC tests.
  test-all:
    name: Test all
    timeout-minutes: 180
    runs-on: ubuntu-latest-xl
    needs: build
    steps:
      - uses: r7kamura/rust-problem-matchers@v1.4.0

      - name: Inject slug/short variables
        uses: rlespinasse/github-slug-action@v4
        with:
          short-length: 7

      # Run unit, basic acceptance tests, and ignored tests, only showing command output if the test fails.
      #
      # If some tests hang, add "-- --nocapture" for just that test, or for all the tests.
      #
      - name: Run zebrad tests
        env:
          NETWORK: ${{ inputs.network || vars.ZCASH_NETWORK }}
        run: |
          docker pull ${{ vars.GAR_BASE }}/${{ vars.CI_IMAGE_NAME }}@${{ needs.build.outputs.image_digest }}
          docker run --tty -e NETWORK -e RUN_ALL_TESTS=1 ${{ vars.GAR_BASE }}/${{ vars.CI_IMAGE_NAME }}@${{ needs.build.outputs.image_digest }}

      # Run unit, basic acceptance tests, and ignored tests with experimental features.
      #
      - name: Run zebrad tests with experimental features
        env:
          NETWORK: ${{ inputs.network || vars.ZCASH_NETWORK }}
        run: |
          docker pull ${{ vars.GAR_BASE }}/${{ vars.CI_IMAGE_NAME }}@${{ needs.build.outputs.image_digest }}
          docker run --tty -e NETWORK -e RUN_ALL_EXPERIMENTAL_TESTS=1 ${{ vars.GAR_BASE }}/${{ vars.CI_IMAGE_NAME }}@${{ needs.build.outputs.image_digest }}

  # Run state tests with fake activation heights.
  #
  # This test changes zebra-chain's activation heights,
  # which can recompile all the Zebra crates,
  # so we want its build products to be cached separately.
  #
  # Also, we don't want to accidentally use the fake heights in other tests.
  #
  # (We activate the test features to avoid recompiling dependencies, but we don't actually run any gRPC tests.)
  test-fake-activation-heights:
    name: Test with fake activation heights
    timeout-minutes: 60
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: r7kamura/rust-problem-matchers@v1.4.0

      - name: Inject slug/short variables
        uses: rlespinasse/github-slug-action@v4
        with:
          short-length: 7

      - name: Run tests with fake activation heights
        env:
          NETWORK: ${{ inputs.network || vars.ZCASH_NETWORK }}
        run: |
          docker pull ${{ vars.GAR_BASE }}/${{ vars.CI_IMAGE_NAME }}@${{ needs.build.outputs.image_digest }}
          docker run --tty -e NETWORK -e TEST_FAKE_ACTIVATION_HEIGHTS=1 ${{ vars.GAR_BASE }}/${{ vars.CI_IMAGE_NAME }}@${{ needs.build.outputs.image_digest }}

  # Test that Zebra syncs and checkpoints a few thousand blocks from an empty state.
  test-empty-sync:
    name: Test checkpoint sync from empty state
    timeout-minutes: 60
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: r7kamura/rust-problem-matchers@v1.4.0

      - name: Inject slug/short variables
        uses: rlespinasse/github-slug-action@v4
        with:
          short-length: 7

      - name: Run zebrad large sync tests
        env:
          NETWORK: ${{ inputs.network || vars.ZCASH_NETWORK }}
        run: |
          docker pull ${{ vars.GAR_BASE }}/${{ vars.CI_IMAGE_NAME }}@${{ needs.build.outputs.image_digest }}
          docker run --tty -e NETWORK -e TEST_ZEBRA_EMPTY_SYNC=1 ${{ vars.GAR_BASE }}/${{ vars.CI_IMAGE_NAME }}@${{ needs.build.outputs.image_digest }}

  # Test launching lightwalletd with an empty lightwalletd and Zebra state.
  test-lightwalletd-integration:
    name: Test integration with lightwalletd
    timeout-minutes: 60
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: r7kamura/rust-problem-matchers@v1.4.0

      - name: Inject slug/short variables
        uses: rlespinasse/github-slug-action@v4
        with:
          short-length: 7

      - name: Run tests with empty lightwalletd launch
        env:
          NETWORK: ${{ inputs.network || vars.ZCASH_NETWORK }}
        run: |
          docker pull ${{ vars.GAR_BASE }}/${{ vars.CI_IMAGE_NAME }}@${{ needs.build.outputs.image_digest }}
          docker run --tty -e NETWORK -e ZEBRA_TEST_LIGHTWALLETD=1 ${{ vars.GAR_BASE }}/${{ vars.CI_IMAGE_NAME }}@${{ needs.build.outputs.image_digest }}

  # Test that Zebra works using the default config with the latest Zebra version.
  test-configuration-file:
    name: Test CI default Docker config file
    needs: build
    uses: ./.github/workflows/sub-test-zebra-config.yml
    with:
      test_id: 'default-conf'
      docker_image: ${{ vars.GAR_BASE }}/${{ vars.CI_IMAGE_NAME }}@${{ needs.build.outputs.image_digest }}
      grep_patterns: '-e "net.*=.*Main.*estimated progress to chain tip.*BeforeOverwinter"'
      test_variables: '-e NETWORK'
      network: 'Mainnet'

  # Test reconfiguring the the docker image for tesnet.
  test-configuration-file-testnet:
    name: Test CI testnet Docker config file
    needs: build
    # Make sure Zebra can sync the genesis block on testnet
    uses: ./.github/workflows/sub-test-zebra-config.yml
    with:
      test_id: 'testnet-conf'
      docker_image: ${{ vars.GAR_BASE }}/${{ vars.CI_IMAGE_NAME }}@${{ needs.build.outputs.image_digest }}
      grep_patterns: '-e "net.*=.*Test.*estimated progress to chain tip.*Genesis" -e "net.*=.*Test.*estimated progress to chain tip.*BeforeOverwinter"'
      # TODO: improve the entrypoint to avoid using `ENTRYPOINT_FEATURES=""`
      test_variables: '-e NETWORK -e ZEBRA_CONF_PATH="/etc/zebrad/zebrad.toml" -e ENTRYPOINT_FEATURES=""'
      network: 'Testnet'

  # Test that Zebra works using $ZEBRA_CONF_PATH config
  test-zebra-conf-path:
    name: Test CI custom Docker config file
    needs: build
    uses: ./.github/workflows/sub-test-zebra-config.yml
    with:
      test_id: 'custom-conf'
      docker_image: ${{ vars.GAR_BASE }}/${{ vars.CI_IMAGE_NAME }}@${{ needs.build.outputs.image_digest }}
      grep_patterns: '-e "loaded zebrad config.*config_path.*=.*v1.0.0-rc.2.toml"'
      test_variables: '-e NETWORK -e ZEBRA_CONF_PATH="zebrad/tests/common/configs/v1.0.0-rc.2.toml"'
      network: ${{ inputs.network || vars.ZCASH_NETWORK }}

  failure-issue:
    name: Open or update issues for main branch failures
    # When a new test is added to this workflow, add it to this list.
    #
    # This list is for reliable tests that are run on the `main` branch.
    # Testnet jobs are not in this list, because we expect testnet to fail occasionally.
    needs: [ test-all, test-fake-activation-heights, test-empty-sync, test-lightwalletd-integration, test-configuration-file, test-zebra-conf-path ]
    # Only open tickets for failed scheduled jobs, manual workflow runs, or `main` branch merges.
    # (PR statuses are already reported in the PR jobs list, and checked by Mergify.)
    # TODO: if a job times out, we want to create a ticket. Does failure() do that? Or do we need cancelled()?
    if: failure() && github.event.pull_request == null
    runs-on: ubuntu-latest
    steps:
      - uses: jayqi/failed-build-issue-action@v1
        with:
          title-template: "{{refname}} branch CI failed: {{eventName}} in {{workflow}}"
          # New failures open an issue with this label.
          # TODO: do we want a different label for each workflow, or each kind of workflow?
          label-name: S-ci-fail-auto-issue
          # If there is already an open issue with this label, any failures become comments on that issue.
          always-create-new-issue: false
          github-token: ${{ secrets.GITHUB_TOKEN }}
