#!/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}" /* To stop e2e tests using port 8545 */ STATUS_RUNTIME_HTTP_API = 'False' STATUS_RUNTIME_WS_API = 'False' /* 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 = 18 /* 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: '2560x1440x24', 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 += "${key}: ${val}
\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 }