#!/usr/bin/env groovy
library 'status-jenkins-lib@v1.8.6'

pipeline {

  agent {
    label "${params.AGENT} && x86_64 && qt-5.15.2"
  }

  parameters {
    gitParameter(
      name: 'GIT_REF',
      description: 'Git branch to checkout.',
      branchFilter: 'origin/(.*)',
      branch: '',
      defaultValue: 'master',
      quickFilterEnabled: false,
      selectedValue: 'DEFAULT',
      sortMode: 'ASCENDING_SMART',
      tagFilter: '*',
      type: 'PT_BRANCH'
    )
    string(
      name: 'BUILD_SOURCE',
      description: 'URL to tar.gz file OR path to Jenkins build.',
      defaultValue: getDefaultBuildSource()
    )
    string(
      name: 'TEST_NAME',
      description: 'Paste test name/part of test name to run specific test.',
      defaultValue: ''
    )
    string(
      name: 'TEST_SCOPE_FLAG',
      description: 'Paste a known mark to run tests labeled with this mark',
      defaultValue: getDefaultTestScopeFlag()
    )
    string(
      name: 'TESTRAIL_RUN_NAME',
      description: 'Test run name in Test Rail.',
      defaultValue: ''
    )
    choice(
      name: 'LOG_LEVEL',
      description: 'Log level for pytest.',
      choices: ['INFO', 'DEBUG', 'TRACE', 'WARNING', 'CRITICAL']
    )
    /* FIXME: This is temporary and should be removed. */
    choice(
      name: 'AGENT',
      description: 'Agent name to run tests on it.',
      choices: ['linux', 'linux-01', 'linux-02', 'linux-03', 'linux-04', 'linux-05']
    )
  }

  options {
    timestamps()
    /* Prevent Jenkins jobs from running forever */
    timeout(time: 120, unit: 'MINUTES')
    /* manage how many builds we keep */
    buildDiscarder(logRotator(
      daysToKeepStr: '60',
      numToKeepStr: '50',
      artifactNumToKeepStr: '50',
    ))
  }

  environment {
    SQUISH_DIR = '/opt/squish-runner-7.2.1'
    PYTHONPATH = "${SQUISH_DIR}/lib:${SQUISH_DIR}/lib/python:${PYTHONPATH}"
    LD_LIBRARY_PATH = "${SQUISH_DIR}/lib:${SQUISH_DIR}/python3/lib:${LD_LIBRARY_PATH}"

    /* Avoid race conditions with other builds using virtualenv. */
    VIRTUAL_ENV = "${WORKSPACE_TMP}/venv"
    PATH = "${VIRTUAL_ENV}/bin:${PATH}"

    TESTRAIL_URL = 'https://ethstatus.testrail.net'
    TESTRAIL_PROJECT_ID = 17
    /* Override QT xcb plugin with linux to avoid errors like:
     * "Could not load the Qt platform plugin "xcb" in "" even though it was found." */
    QT_QPA_PLATFORM = "linuxfb"

    /* Runtime flag to make testing of the app easier.  Switched off: unpredictable app behavior under new tests */
    /* STATUS_RUNTIME_TEST_MODE = 'True' */

    /* Logging rules let you enable or disable logging for categories */
    QT_LOGGING_RULES = '*.warning=true'
    
    /* Set to a non-zero value to make Qt print out diagnostic information about the each (C++) plugin it tries to load. */
    QT_DEBUG_PLUGINS = 0
  }

  stages {
    stage('Prep') {
      steps { script {
        setNewBuildName()
        updateGitHubStatus()
      } }
    }

    stage('Deps') {
      steps { script { dir('test/e2e') {
        sh "python3 -m venv ${VIRTUAL_ENV}"
        sh 'pip3 install -r requirements.txt'
      } } }
    }

    stage('Download') {
      when { expression { params.BUILD_SOURCE.startsWith('http') } }
      steps { timeout(5) { script { dir('test/e2e') {
        sh 'mkdir -p ./pkg/'
        setBuildDescFromFile(params.BUILD_SOURCE)
        fileOperations([
          fileDownloadOperation(
            url:            params.BUILD_SOURCE,
            targetFileName: 'StatusIm-Desktop.tar.gz',
            targetLocation: './pkg/',
            userName:       '',
            password:       '',
          )
        ])
      } } } }
    }

    stage('Copy') {
      when { expression { ! params.BUILD_SOURCE.startsWith('http') } }
      steps { timeout(5) { script { dir('test/e2e') {
        copyArtifacts(
          projectName: params.BUILD_SOURCE,
          filter:      'pkg/*-x86_64.tar.gz',
          selector:    lastWithArtifacts(),
          target:      './'
        )
        setBuildDescFromFile(utils.findFile('pkg/*tar.gz'))
      } } } }
    }

    stage('Unpack') {
      steps { timeout(5) { script { dir('test/e2e') {
        sh 'mkdir aut'
        sh "tar -zxvf '${utils.findFile('pkg/*tar.gz')}' -C './aut'"
        env.AUT_PATH = utils.findFile('aut/*.AppImage')
      } } } }
    }

    stage('Test') {
      steps { timeout(getTestStageTimeout()) { script { dir('test/e2e') {
        def flags = []
        if (params.TEST_NAME)  { flags.add("-k=${params.TEST_NAME}") }
        if (params.TEST_SCOPE_FLAG) { flags.add(params.TEST_SCOPE_FLAG) }
        if (params.LOG_LEVEL)  { flags.addAll(["--log-level=${params.LOG_LEVEL}", "--log-cli-level=${params.LOG_LEVEL}"]) }
        dir ('configs') { sh 'ln -s _local.ci.py _local.py' }
        wrap([
          $class:            'Xvfb',
          autoDisplayName:   true,
          parallelBuild:     true,
          screen:            '1920x1080x24',
          additionalOptions: '-dpi 1'
        ]) {
          sh 'fluxbox &'
          withCredentials([
            usernamePassword(
              credentialsId: 'test-rail-api-devops',
              usernameVariable: 'TESTRAIL_USR',
              passwordVariable: 'TESTRAIL_PSW'
            )
          ]) {
            /* Keep the --reruns flag first, or it won't work */
            sh """
              python3 -m pytest --reruns=1 --timeout=180 ${flags.join(" ")} \
                --disable-warnings \
                --alluredir=./allure-results \
                -o timeout_func_only=true
            """
          }
        }
      } } } }
    }
  }

  post {
    always { script { dir('test/e2e') {
      archiveArtifacts('aut/*.log')

      /* Needed to categorize types of errors and add environment section in allure report. */
      sh 'cp ext/allure_files/categories.json allure-results'
      sh 'cp ext/allure_files/environment.properties allure-results'

      allure([
        results: [[path: 'allure-results']],
        reportBuildPolicy: 'ALWAYS',
        properties: [],
        jdk: '',
      ])
      updateGitHubStatus()
    } } }
    failure { script {
      discord.send(
        header: '**Desktop E2E test failure!**',
        cred: 'discord-status-desktop-e2e-webhook',
      )
    } }
    cleanup { cleanWs() }
  }
}

def setNewBuildName() {
  if (currentBuild.upstreamBuilds) {
    def parent = utils.parentOrCurrentBuild()
    currentBuild.displayName = parent.getFullDisplayName().minus('status-desktop ยป ')
  }
}

def setBuildDescFromFile(fileNameOrPath) {
  def tokens = utils.parseFilename(utils.baseName(fileNameOrPath))
  if (tokens == null) { /* Fallback for regex fail. */
    currentBuild.description = utils.baseName(fileNameOrPath)
    return
  }
  if (tokens.build && tokens.build.startsWith('pr')) {
    currentBuild.displayName = tokens.build.replace(/^pr/, 'PR-')
  }
  currentBuild.description = formatMap([
    Node: NODE_NAME,
    Build: tokens.build,
    Commit: tokens.commit,
    Version: (tokens.tstamp ?: tokens.version),
  ])
}

def updateGitHubStatus() {
  /* For PR builds update check status. */
  if (params.BUILD_SOURCE ==~ /.*\/PR-[0-9]+\/?$/) {
    github.statusUpdate(
      context: 'jenkins/prs/tests/e2e-new',
      commit: jenkins.getJobCommitByPath(params.BUILD_SOURCE),
      repo_url: 'https://github.com/status-im/status-desktop'
    )
  }
}

def formatMap(Map data=[:]) {
  def text = ''
  data.each { key, val -> text += "<b>${key}</b>: ${val}</a><br>\n" }
  return text
}

def getDefaultBuildSource() {
  if (JOB_NAME ==~ 'status-desktop/qa-automation/prs/.*') {
    return 'status-desktop/nightly'
  }
  return ''
}

def getDefaultTestScopeFlag() {
  if (JOB_NAME == "status-desktop/systems/linux/x86_64/tests-e2e") {
    return ''
  } else {
    return '-m=critical'
  }
}

def getTestStageTimeout() { TEST_SCOPE_FLAG == '-m=critical' ? 30 : 120 }