diff --git a/Jenkinsfile b/Jenkinsfile index 63791b616..4b961fada 100755 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -19,6 +19,7 @@ // Then a second PR submitted to comment out the @Library line, and when it // is landed, both PR branches can be deleted. //@Library(value='pipeline-lib@my_branch_name') _ +@Library(value='pipeline-lib@hendersp/DAOS-19247-fix') _ /* groovylint-disable-next-line CompileStatic */ job_status_internal = [:] diff --git a/vars/getFunctionalTestStage.groovy b/vars/getFunctionalTestStage.groovy index 406cc0bbe..632fa8c03 100644 --- a/vars/getFunctionalTestStage.groovy +++ b/vars/getFunctionalTestStage.groovy @@ -1,4 +1,4 @@ -/* groovylint-disable VariableName */ +/* groovylint-disable NestedBlockDepth, VariableName, DuplicateStringLiteral */ // vars/getFunctionalTestStage.groovy import org.jenkinsci.plugins.pipeline.modeldefinition.Utils @@ -33,8 +33,9 @@ import org.jenkinsci.plugins.pipeline.modeldefinition.Utils * job_status Map of status for each stage in the job/build * @return a scripted stage to run in a pipeline */ +/* groovylint-disable-next-line MethodSize */ Map call(Map kwargs = [:]) { - String name = kwargs.get('name', 'Unknown Functional Test Stage') + String name = kwargs.get('name', '') String pragma_suffix = kwargs.get('pragma_suffix') String label = kwargs.get('label') String next_version = kwargs.get('next_version', null) @@ -57,37 +58,38 @@ Map call(Map kwargs = [:]) { // to be skipped by the existing skipFunctionalTestStage logic, while new stages can use // runStage directly. Once all stages have been converted to use runStage, this should be // changed to a Boolean. - String runStage = kwargs.get('runStage', 'undefined').toString() + String runStage = kwargs.get('runStage', 'undefined') Map job_status = kwargs.get('job_status', [:]) + if (!name) { + error("getFunctionalTestStage() requires a stage 'name' argument") + } + return { stage("${name}") { + if (runStage == 'false') { + println("[${name}] Stage skipped by runStage=false") + Utils.markStageSkippedForConditional("${name}") + return + } + // Get the tags for the stage. Use either the build parameter, commit pragma, or // default tags. All tags are combined with the stage tags to ensure only tests that // 'fit' the cluster will be run. String tags = getFunctionalTags( pragma_suffix: pragma_suffix, stage_tags: stage_tags, default_tags: default_tags) - - if (runStage == 'false') { - println("[${name}] Stage skipped by runStage=false") - Utils.markStageSkippedForConditional("${name}") - return - } else if (runStage == 'undefined') { - // To be removed once all stages have been converted to use runStage. - Map skip_kwargs = [ - 'tags': tags, - 'pragma_suffix': pragma_suffix, - 'distro': distro, - 'run_if_pr': run_if_pr, - 'run_if_landing': run_if_landing] - if (skipFunctionalTestStage(skip_kwargs)) { - println("[${name}] Stage skipped by skipFunctionalTestStage()") - Utils.markStageSkippedForConditional("${name}") - return - } - } else if (!testsInStage(tags)) { - println("[${name}] Stage skipped by no tests matching the '${tags}' tags") + Map skipKwargs = ['tags': tags, 'basicCheck': false] + if (runStage == 'true') { + skipKwargs['basicCheck'] = true + } else { + skipKwargs['pragma_suffix'] = pragma_suffix + skipKwargs['distro'] = distro + skipKwargs['run_if_pr'] = run_if_pr + skipKwargs['run_if_landing'] = run_if_landing + } + if (skipFunctionalTestStage(skipKwargs)) { + println("[${name}] Stage skipped by skipFunctionalTestStage()") Utils.markStageSkippedForConditional("${name}") return } @@ -105,6 +107,7 @@ Map call(Map kwargs = [:]) { checkoutScm(pruneStaleBranch: true) } + Throwable tryError = null try { println("[${name}] Running functionalTest() on ${label} with tags=${tags}") Map ftestConfig = [ @@ -129,13 +132,29 @@ Map call(Map kwargs = [:]) { job_status, name, functionalTest(ftestConfig)) + /* groovylint-disable-next-line CatchException */ + } catch (Exception e) { + tryError = e + println("[${name}] Caught exception in try: ${tryError}") + jobStatusUpdate(job_status, name, 'FAILURE') + throw tryError } finally { - println("[${name}] Running functionalTestPostV2()") - functionalTestPostV2() - jobStatusUpdate(job_status, name) + try { + println("[${name}] Running functionalTestPostV2()") + functionalTestPostV2() + jobStatusUpdate(job_status, name) + /* groovylint-disable-next-line CatchException */ + } catch (Exception finallyError) { + println("[${name}] Caught exception in finally: ${finallyError}") + jobStatusUpdate(job_status, name, 'FAILURE') + if (tryError == null) { + /* groovylint-disable-next-line ThrowExceptionFromFinallyBlock */ + throw finallyError + } + } } } + println("[${name}] Finished with ${job_status}") } - println("[${name}] Finished with ${job_status}") } } diff --git a/vars/scriptedTestRpmStage.groovy b/vars/scriptedTestRpmStage.groovy new file mode 100644 index 000000000..35ad55bfe --- /dev/null +++ b/vars/scriptedTestRpmStage.groovy @@ -0,0 +1,113 @@ +/* groovylint-disable NestedBlockDepth */ +// vars/scriptedTestRpmStage.groovy + +import org.jenkinsci.plugins.pipeline.modeldefinition.Utils + +/** + * scriptedTestRpmStage.groovy + * + * Get a test stage in scripted syntax. + * + * @param kwargs Map containing the following optional arguments (empty strings yield defaults): + * name test stage name + * runStage whether or not to run the test stage; defaults to true + * label test stage default cluster label + * testBranch if specified, checkout sources from this branch before running tests; + * defaults to '' + * jobStatus Map of status for each stage in the job/build + * testRpmArgs Map of arguments to pass to testRpm() for the stage + * alwaysScript script to always run after the test stage, e.g. + * 'ci/rpm/test_daos_post.sh'; defaults to '' + * archiveArtifactsArgs Map of arguments to pass to archiveArtifacts() for the stage + * distro the distro to pass to daosRepos() if testRpmArgs does not specify a + * inst_repos, e.g. 'el9'; defaults to null + * next_version next daos package version to pass to daosPackagesVersion() if + * testRpmArgs does not specify a daos_pkg_version; defaults to null + * @return a scripted stage to run in a pipeline + */ +Map call(Map kwargs = [:]) { + // General parameters + String name = kwargs.get('name', '') + Boolean runStage = kwargs.get('runStage', true) as Boolean + String label = kwargs.get('label') + String testBranch = kwargs.get('testBranch', '') + Map jobStatus = kwargs.get('jobStatus', null) ?: [:] + Map testRpmArgs = kwargs.get('testRpmArgs', null) ?: [:] + String alwaysScript = kwargs.get('alwaysScript', '') + Map archiveArtifactsArgs = kwargs.get('archiveArtifactsArgs', null) ?: [:] + + if (!name) { + error("scriptedTestRpmStage() requires a stage 'name' argument") + } + + return { + stage("${name}") { + if (!runStage) { + println("[${name}] Stage skipped by runStage=false") + Utils.markStageSkippedForConditional("${name}") + return + } + + // Add defaults for any missing testRpm() arguments + if (!testRpmArgs.containsKey('inst_repos')) { + /* groovylint-disable-next-line DuplicateStringLiteral */ + testRpmArgs['inst_repos'] = daosRepos(kwargs.get('distro', null)) + } + if (!testRpmArgs.containsKey('daos_pkg_version')) { + /* groovylint-disable-next-line DuplicateStringLiteral */ + testRpmArgs['daos_pkg_version'] = daosPackagesVersion( + kwargs.get('next_version', null)) + } + + node(label) { + // Ensure access to any branch provisioning scripts exist + if (testBranch) { + println("[${name}] Check out '${testBranch}' from version control") + checkoutScm( + url: 'https://github.com/daos-stack/daos.git', + branch: testBranch, + withSubmodules: false, + pruneStaleBranch: true) + } else { + println("[${name}] Check out branch from version control") + checkoutScm(pruneStaleBranch: true) + } + + Throwable tryError = null + try { + println("[${name}] Running testRpm() on ${label}") + jobStatusUpdate(jobStatus, name, testRpm(testRpmArgs)) + /* groovylint-disable-next-line CatchException */ + } catch (Exception e) { + tryError = e + println("[${name}] Caught exception in try: ${tryError}") + jobStatusUpdate(jobStatus, name, 'FAILURE') + throw tryError + } finally { + try { + if (alwaysScript) { + sh(script: alwaysScript, + label: "Running alwaysScript: ${alwaysScript}", + returnStatus: true) + } + if (archiveArtifactsArgs) { + println("[${name}] Running archiveArtifacts()") + archiveArtifacts(archiveArtifactsArgs) + } + jobStatusUpdate(jobStatus, name) + /* groovylint-disable-next-line CatchException */ + } catch (Exception finallyError) { + println("[${name}] Caught exception in finally: ${finallyError}") + /* groovylint-disable-next-line DuplicateStringLiteral */ + jobStatusUpdate(jobStatus, name, 'FAILURE') + if (tryError == null) { + /* groovylint-disable-next-line ThrowExceptionFromFinallyBlock */ + throw finallyError + } + } + } + } + println("[${name}] Finished with ${jobStatus}") + } + } +} diff --git a/vars/scriptedUnitTestStage.groovy b/vars/scriptedUnitTestStage.groovy new file mode 100644 index 000000000..a332cf457 --- /dev/null +++ b/vars/scriptedUnitTestStage.groovy @@ -0,0 +1,112 @@ +/* groovylint-disable NestedBlockDepth */ +// vars/scriptedUnitTestStage.groovy + +import org.jenkinsci.plugins.pipeline.modeldefinition.Utils + +/** + * scriptedUnitTestStage.groovy + * + * Get a unit test stage in scripted syntax. + * + * @param kwargs Map containing the following optional arguments (empty strings yield defaults): + * name test stage name + * runStage whether or not to run the test stage + * label test stage default cluster label + * testBranch if specified, checkout sources from this branch before running tests + * jobStatus Map of status for each stage in the job/build + * distro the distro to use for daosRepos() and unitPackages() when providing + * default arguments in unitTestArgs, e.g. 'el9'; defaults to '' + * unitTestArgs Map of arguments to pass to unitTest; defaults to an empty Map. + * unitTestPostArgs Map of arguments to pass to unitTestPost() for the stage; defaults to + * an empty Map. + * archiveArtifactsArgs Map of arguments to pass to archiveArtifacts() for the stage; defaults + * to an empty Map. + * @return a scripted stage to run in a pipeline + */ +Map call(Map kwargs = [:]) { + // General parameters + String name = kwargs.get('name', '') + Boolean runStage = kwargs.get('runStage', true) as Boolean + String label = kwargs.get('label') + String testBranch = kwargs.get('testBranch', '') + Map jobStatus = kwargs.get('jobStatus', null) ?: [:] + String distro = kwargs.get('distro', '') + + // Unit Test stage parameters + Map unitTestArgs = kwargs.get('unitTestArgs', null) ?: [:] + Map unitTestPostArgs = kwargs.get('unitTestPostArgs', null) ?: [:] + Map archiveArtifactsArgs = kwargs.get('archiveArtifactsArgs', null) ?: [:] + + if (!name) { + error("scriptedUnitTestStage() requires a stage 'name' argument") + } + + return { + stage("${name}") { + if (!runStage) { + println("[${name}] Stage skipped by runStage=false") + Utils.markStageSkippedForConditional("${name}") + return + } + + // Add defaults for any missing unitTest() arguments + if (!unitTestArgs.containsKey('inst_repos')) { + /* groovylint-disable-next-line DuplicateStringLiteral */ + unitTestArgs['inst_repos'] = daosRepos(distro) + } + if (!unitTestArgs.containsKey('inst_rpms')) { + /* groovylint-disable-next-line DuplicateStringLiteral */ + unitTestArgs['inst_rpms'] = unitPackages(target: distro) + ' daos-client-tests' + } + + node(label) { + // Ensure access to any branch provisioning scripts exist + if (testBranch) { + println("[${name}] Check out '${testBranch}' from version control") + checkoutScm( + url: 'https://github.com/daos-stack/daos.git', + branch: testBranch, + withSubmodules: false, + pruneStaleBranch: true) + } else { + println("[${name}] Check out branch from version control") + checkoutScm(pruneStaleBranch: true) + } + + Throwable tryError = null + try { + println("[${name}] Running unitTest() on ${label}") + jobStatusUpdate(jobStatus, name, unitTest(unitTestArgs)) + /* groovylint-disable-next-line CatchException */ + } catch (Exception e) { + tryError = e + println("[${name}] Caught exception in try: ${tryError}") + jobStatusUpdate(jobStatus, name, 'FAILURE') + throw tryError + } finally { + try { + if (unitTestPostArgs) { + println("[${name}] Running unitTestPost()") + unitTestPost(unitTestPostArgs) + } + if (archiveArtifactsArgs) { + println("[${name}] Running archiveArtifacts()") + archiveArtifacts(archiveArtifactsArgs) + } + jobStatusUpdate(jobStatus, name) + /* groovylint-disable-next-line CatchException */ + } catch (Exception finallyError) { + println("[${name}] Caught exception in finally: ${finallyError}") + /* groovylint-disable-next-line DuplicateStringLiteral */ + jobStatusUpdate(jobStatus, name, 'FAILURE') + if (tryError == null) { + /* groovylint-disable-next-line ThrowExceptionFromFinallyBlock */ + throw finallyError + } + } + } + } + println("[${name}] Finished with ${jobStatus}") + } + } +} diff --git a/vars/skipFunctionalTestStage.groovy b/vars/skipFunctionalTestStage.groovy index d99de55d3..384901081 100644 --- a/vars/skipFunctionalTestStage.groovy +++ b/vars/skipFunctionalTestStage.groovy @@ -12,6 +12,7 @@ * distro functional test stage distro (required for VM) * run_if_pr whether or not the stage should run for PR builds * run_if_landing whether or not the stage should run for landing builds + * basicCheck only verify if the stage has altready passed and has tests matching the tags * @return a String reason why the stage should be skipped; empty if the stage should run */ /* groovylint-disable-next-line MethodSize */ @@ -30,14 +31,19 @@ Map call(Map kwargs = [:]) { String build_param_value = paramsValue(build_param, '').toString() Boolean run_if_landing = kwargs['run_if_landing'] ?: false Boolean run_if_pr = kwargs['run_if_pr'] ?: false + Boolean basicCheck = kwargs.get('basicCheck', false) String target_branch = env.CHANGE_TARGET ? env.CHANGE_TARGET : env.BRANCH_NAME - println("[${env.STAGE_NAME}] Running skipFunctionalTestStage: " + - "tags=${tags}, pragma_suffix=${pragma_suffix}, size=${size}, distro=${distro}, " + - "build_param=${build_param}, build_param_value=${build_param_value}, " + - "run_if_landing=${run_if_landing}, run_if_pr=${run_if_pr}, " + - "startedByUser()=${startedByUser()}, startedByTimer()=${startedByTimer()}, " + - "startedByUpstream()=${startedByUpstream()}, target_branch=${target_branch}") + if (basicCheck) { + println("[${env.STAGE_NAME}] Running skipFunctionalTestStage: tags=${tags}") + } else { + println("[${env.STAGE_NAME}] Running skipFunctionalTestStage: " + + "tags=${tags}, pragma_suffix=${pragma_suffix}, size=${size}, distro=${distro}, " + + "build_param=${build_param}, build_param_value=${build_param_value}, " + + "run_if_landing=${run_if_landing}, run_if_pr=${run_if_pr}, " + + "startedByUser()=${startedByUser()}, startedByTimer()=${startedByTimer()}, " + + "startedByUpstream()=${startedByUpstream()}, target_branch=${target_branch}") + } // Regardless of how the stage has been started always skip a stage that has either already // passed or does not contain any tests match the tags. @@ -50,6 +56,12 @@ Map call(Map kwargs = [:]) { return true } + // With basicCheck set other stage skip criteria has been already confirmed externally + if (basicCheck) { + println("[${env.STAGE_NAME}] Stage needs to run and has tests matching the tags.") + return false + } + // If the stage has been started by the user, e.g. Build with Parameters, or a timer, or an // upstream build then use the stage's build parameter (check box) to determine if the stage // should be run or skipped. diff --git a/vars/testsInStage.groovy b/vars/testsInStage.groovy index 96eca3951..690f1d0e5 100644 --- a/vars/testsInStage.groovy +++ b/vars/testsInStage.groovy @@ -10,9 +10,7 @@ * @return boolean true if there are tests that match the tags */ boolean call(String tags) { - if (env.UNIT_TEST && env.UNIT_TEST == 'true') { - return true - } + println("[${env.STAGE_NAME}] Determining if tests w/ '${tags}' tags exist for this stage") /* groovylint-disable-next-line UnnecessaryGetter */ String verbose = isPr() ? '--verbose ' : '' return sh(label: 'Get test list',