#!/usr/bin/env groovy

/*
 * Copyright (C) 2022 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

library "canvas-builds-library@${env.CANVAS_BUILDS_REFSPEC}"
loadLocalLibrary('local-lib', 'build/new-jenkins/library')

commitMessageFlag.setDefaultValues(commitMessageFlagDefaults() + commitMessageFlagPrivateDefaults())

@groovy.transform.Field
def rspecqNodeTotal = 23
@groovy.transform.Field
def rspecNodeTotal = 27

def setupNode() {
  sh 'rm -vrf ./tmp'

  checkout scm

  distribution.stashBuildScripts()

  sh(script: 'build/new-jenkins/docker-compose-pull.sh', label: 'Pull Images')

  sh(script: 'build/new-jenkins/docker-compose-build-up.sh', label: 'Start Containers')
}

def getPatchsetTag() {
  (env.GERRIT_REFSPEC.contains('master')) ? "${configuration.buildRegistryPath()}:${env.GERRIT_BRANCH}" : imageTag.patchset()
}

def redisUrl() {
  return "redis://${TEST_QUEUE_HOST}:6379"
}

def generateSkippedSpecsReport() {
  try{
    copyArtifacts(
      filter: 'tmp/*/rspec_results.tgz',
      optional: false,
      projectName: env.JOB_NAME,
      selector: specific(env.BUILD_NUMBER),
    )

    sh "ls tmp/*/rspec_results.tgz | xargs -n1 tar xvf"

    withEnv(['COMPOSE_FILE=docker-compose.new-jenkins.yml']) {
      withCredentials([usernamePassword(credentialsId: 'INSENG_CANVAS_CI_AWS_ACCESS', usernameVariable: 'INSENG_AWS_ACCESS_KEY_ID', passwordVariable: 'INSENG_AWS_SECRET_ACCESS_KEY')]) {
        def awsCreds = "AWS_DEFAULT_REGION=us-west-2 AWS_ACCESS_KEY_ID=${INSENG_AWS_ACCESS_KEY_ID} AWS_SECRET_ACCESS_KEY=${INSENG_AWS_SECRET_ACCESS_KEY}"
        sh "$awsCreds aws s3 cp s3://instructure-canvas-ci/skipped_specs_ruby.json skipped_specs.json"
        sh """
          docker compose run -v \$(pwd)/\$LOCAL_WORKDIR/tmp/:/tmp \
          -v \$(pwd)/\$LOCAL_WORKDIR/skipped_specs.json/:/usr/src/app/skipped_specs.json \
          --name skipped-spec-collator canvas bash -c \
          "mkdir -p /usr/src/app/out; bundle install; ruby build/new-jenkins/skipped_specs_manager.rb ruby"
        """
        sh 'docker cp skipped-spec-collator:/usr/src/app/out/skipped_specs.json skipped_specs.json'
        sh "$awsCreds aws s3 cp skipped_specs.json s3://instructure-canvas-ci/skipped_specs_ruby.json"
      }
      sendSkippedSpecsSlackReport()
      archiveArtifacts allowEmptyArchive: true, artifacts: 'skipped_specs.json'
    }
  } catch (org.jenkinsci.plugins.workflow.steps.FlowInterruptedException e) {
    slackSend channel: '#canvas-test-stats', color: 'danger', message: "<$env.BUILD_URL|coverage-ruby failed to generate skipped specs report!>"
  }
}

def sendSkippedSpecsSlackReport() {
  def rubySkippedSpecs = sh(script: "grep -o 'file_indicator' skipped_specs.json | wc -l", returnStdout: true).trim() ?: '0'
  def color = 'danger'
  if(rubySkippedSpecs.toInteger() < 100) {
    color = 'good'
  } else if (rubySkippedSpecs.toInteger() < 300) {
    color = 'warning'
  }
  def jobInfo = "<$env.BUILD_URL|Ruby>"
  slackSend channel: '#canvas-test-stats', color: color, message: "$rubySkippedSpecs skipped specs in $jobInfo! "
}

def collectCoverageReport() {
  stage('Collect Coverage Report') {
    copyArtifacts(
      filter: 'tmp/*/coverage/**',
      optional: false,
      projectName: env.JOB_NAME,
      selector: specific(env.BUILD_NUMBER),
    )

    sh "docker compose -f ${env.COMPOSE_FILE} cp tmp canvas:/usr/src/app/coverage-data"
    sh "docker compose -f ${env.COMPOSE_FILE} exec -T canvas bash -c \"bundle exec rake coverage:report['/usr/src/app/coverage-data/*/coverage/**']\""
    pipelineHelpers.copyFromContainer('canvas', '/usr/src/app/coverage', './coverage')

    archiveArtifacts allowEmptyArchive: true, artifacts: 'coverage/**'

    publishHTML target: [
      allowMissing: false,
      alwaysLinkToLastBuild: false,
      keepAll: true,
      reportDir: './coverage',
      reportFiles: 'index.html',
      reportName: 'Ruby Coverage Report'
    ]

    uploadCoverage([
      uploadSource: '/coverage',
      uploadDest: env.COVERAGE_LOCATION
    ])
  }
}

env.COMPOSE_FILE = 'docker-compose.new-jenkins.yml:docker-compose.new-jenkins-selenium.yml'
env.COMPOSE_PROJECT_NAME = 'coverage'
env.FORCE_FAILURE = commitMessageFlag("force-failure-rspec").asBooleanInteger()
env.RERUNS_RETRY = commitMessageFlag('rspecq-max-requeues').asType(Integer)
env.RSPECQ_FILE_SPLIT_THRESHOLD = commitMessageFlag('rspecq-file-split-threshold').asType(Integer)
env.RSPECQ_MAX_REQUEUES = commitMessageFlag('rspecq-max-requeues').asType(Integer)
env.SELENIUM_TEST_PATTERN = '^./(spec|gems/plugins/.*/spec_canvas)/selenium'
env.TEST_PATTERN = '^./(spec|gems/plugins/.*/spec_canvas)/'
env.ENABLE_AXE_SELENIUM = "${env.ENABLE_AXE_SELENIUM}"
env.POSTGRES_PASSWORD = 'sekret'
env.RSPECQ_REDIS_URL = redisUrl()
env.PATCHSET_TAG = getPatchsetTag()
env.DYNAMODB_PREFIX = configuration.buildRegistryPath('dynamodb-migrations')
env.POSTGRES_PREFIX = configuration.buildRegistryPath('postgres-migrations')
env.IMAGE_CACHE_MERGE_SCOPE = configuration.gerritBranchSanitized()
env.RSPEC_PROCESSES = commitMessageFlag('rspecq-processes').asType(Integer)
env.DYNAMODB_IMAGE_TAG = "$env.DYNAMODB_PREFIX:$env.IMAGE_CACHE_MERGE_SCOPE-$env.RSPEC_PROCESSES"
env.POSTGRES_IMAGE_TAG = "$env.POSTGRES_PREFIX:$env.IMAGE_CACHE_MERGE_SCOPE-$env.RSPEC_PROCESSES"
env.COVERAGE_LOCATION = "${env.COVERAGE_TYPE == 'ruby-selenium' ? 'canvas__master__selenium--coverage/coverage' : (env.COVERAGE_TYPE == 'ruby-nonselenium' ? 'canvas__master__rspec--coverage/coverage' : 'canvas-lms-rspec/coverage')}"
env.LOCAL_WORKDIR = pipelineHelpers.getLocalWorkDir()

node(nodeLabel()) {
  timeout(time: 60, unit: 'MINUTES') {
    ansiColor('xterm') {
      timestamps {
        try {
          stage('Clean Workspace') {
            pipelineHelpers.cleanupWorkspace()
          }

          stage('Setup') {
            setupNode()
          }

          stage('Run Tests') {
            def rspecqStages = [:]

            // Reporters run on coordinator node
            if (env.COVERAGE_TYPE != 'ruby-selenium') {
              rspecqStages['RSpecQ Reporter for Rspec'] = {
                try {
                  sh(script: "docker run -e SENTRY_DSN -e RSPECQ_REDIS_URL -t $env.PATCHSET_TAG bundle exec rspecq \
                    --build=${env.JOB_NAME}_build${env.BUILD_NUMBER}_rspec \
                    --queue-wait-timeout 300 \
                    --redis-url $env.RSPECQ_REDIS_URL \
                    --report", label: 'Reporter')
                } catch (org.jenkinsci.plugins.workflow.steps.FlowInterruptedException e) {
                  if (e.causes[0] instanceof org.jenkinsci.plugins.workflow.steps.TimeoutStepExecution.ExceededTimeout) {
                    sh '''#!/bin/bash
                      ids=($(docker ps -aq --filter "name=canvas-"))
                      for i in "${ids[@]}"
                        do
                          docker exec $i bash -c "cat /usr/src/app/log/cmd_output/*.log"
                      done
                    '''
                  }
                  throw e
                }
              }
            }

            if (env.COVERAGE_TYPE != 'ruby-nonselenium') {
              rspecqStages['RSpecQ Reporter for Selenium'] = {
                try {
                  sh(script: "docker run -e SENTRY_DSN -e RSPECQ_REDIS_URL -t $env.PATCHSET_TAG bundle exec rspecq \
                    --build=${env.JOB_NAME}_build${env.BUILD_NUMBER}_selenium \
                    --queue-wait-timeout 300 \
                    --redis-url $env.RSPECQ_REDIS_URL \
                    --report", label: 'Reporter')
                } catch (org.jenkinsci.plugins.workflow.steps.FlowInterruptedException e) {
                  if (e.causes[0] instanceof org.jenkinsci.plugins.workflow.steps.TimeoutStepExecution.ExceededTimeout) {
                    sh '''#!/bin/bash
                      ids=($(docker ps -aq --filter "name=canvas-"))
                      for i in "${ids[@]}"
                        do
                          docker exec $i bash -c "cat /usr/src/app/log/cmd_output/*.log"
                      done
                    '''
                  }
                  throw e
                }
              }
            }

            // Selenium worker nodes grab their own nodes
            if (env.COVERAGE_TYPE != 'ruby-nonselenium') {
              for (int i = 0; i < rspecqNodeTotal; i++) {
                def index = i
                def indexStr = (index).toString().padLeft(2, '0')
                rspecqStages["RSpecQ Selenium Set ${indexStr}"] = {
                  rspecStage.runRspecQWorkerNode("Selenium_${indexStr}", [
                    "BUILD_NAME=${env.JOB_NAME}_build${env.BUILD_NUMBER}_selenium",
                    "TEST_PATTERN=${env.SELENIUM_TEST_PATTERN}"
                  ])
                }
              }
            }

            // Non-Selenium worker nodes grab their own nodes
            if (env.COVERAGE_TYPE != 'ruby-selenium') {
              for (int i = 0; i < rspecNodeTotal; i++) {
                def index = i
                def indexStr = (index + 1).toString().padLeft(2, '0')
                rspecqStages["RSpecQ Rspec Set ${indexStr}"] = {
                  rspecStage.runRspecQWorkerNode("Rspec_${indexStr}", [
                    "BUILD_NAME=${env.JOB_NAME}_build${env.BUILD_NUMBER}_rspec",
                    "TEST_PATTERN=${env.TEST_PATTERN}",
                    "EXCLUDE_TESTS=.*/selenium"
                  ])
                }
              }
            }

            parallel(rspecqStages)
          }
        } finally {
          collectCoverageReport()

          if (env.GERRIT_EVENT_TYPE != 'comment-added' && env.COVERAGE_TYPE == 'ruby-total') {
            generateSkippedSpecsReport()
          }

          pipelineHelpers.cleanupDocker()
        }
      }
    }
  }
}
