-
Notifications
You must be signed in to change notification settings - Fork 51
/
Jenkinsfile
537 lines (468 loc) · 20.1 KB
/
Jenkinsfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
#!/usr/bin/env groovy
/**
* Jenkinsfile for zanata-platform
*/
@Field
public static final String PROJ_URL = 'https://github.com/zanata/zanata-platform'
// Import pipeline library for utility methods & classes:
// ansicolor(), Notifier, PullRequests, Strings
@Field
public static final String PIPELINE_LIBRARY_BRANCH = 'v0.3.1'
// GROOVY-3278:
// Using referenced String constant as value of Annotation causes compile error
@Library('[email protected]')
import org.zanata.jenkins.Notifier
import org.zanata.jenkins.PullRequests
import org.zanata.jenkins.ScmGit
import static org.zanata.jenkins.Reporting.codecov
import static org.zanata.jenkins.StackTraces.getStackTrace
import groovy.transform.Field
// The first milestone step starts tracking concurrent build order
milestone()
PullRequests.ensureJobDescription(env, manager, steps)
@Field
boolean allFuncTestsDefault
//noinspection GroovyPointlessBoolean
allFuncTestsDefault = manager.build.project.description?.contains('allFuncTests') || false
// initialiser must be run separately (bindings not available during compilation phase)
// we can't set these values yet, because we need a node to look at the environment
@Field
def defaultNodeLabel
// Define project properties: general properties for the Pipeline-defined jobs.
// 1. discard old artifacts and build logs
// 2. configure build parameters (eg requested labels for build nodes)
//
// Normally the project properties wouldn't go inside a node, but
// we need a node to access env.DEFAULT_NODE.
node {
echo "running on node ${env.NODE_NAME}"
defaultNodeLabel = env.DEFAULT_NODE ?: 'master || !master'
// eg github-zanata-org/zanata-platform/update-Jenkinsfile
def projectProperties = [
[
$class: 'BuildDiscarderProperty',
strategy: [$class: 'LogRotator',
daysToKeepStr: '30', // keep records no more than X days
numToKeepStr: '20', // keep records for at most X builds
artifactDaysToKeepStr: '', // keep artifacts no more than X days
artifactNumToKeepStr: '4'] // keep artifacts for at most X builds
],
[
$class: 'GithubProjectProperty',
projectUrlStr: PROJ_URL
],
[
$class: 'ParametersDefinitionProperty',
parameterDefinitions: [
[
$class: 'LabelParameterDefinition',
// Specify the default node in Jenkins env var DEFAULT_NODE
// (eg kvm), or leave blank to build on any node.
defaultValue: defaultNodeLabel,
description: 'Jenkins node label to use for build',
name: 'LABEL'
],
[
$class: 'BooleanParameterDefinition',
defaultValue: allFuncTestsDefault,
description: 'Run all functional tests?',
name: 'allFuncTests'
],
]
],
]
properties(projectProperties)
}
String getLabel() {
def labelParam = null
try {
labelParam = params.LABEL
} catch (e1) {
// workaround for https://issues.jenkins-ci.org/browse/JENKINS-38813
echo '[WARNING] unable to access `params`'
echo getStackTrace(e1)
try {
labelParam = LABEL
} catch (e2) {
echo '[WARNING] unable to access `LABEL`'
echo getStackTrace(e2)
}
}
if (labelParam == null) {
echo "LABEL param is null; using default value."
}
def result = labelParam ?: defaultNodeLabel
echo "Using build node label: $result"
return result
}
// NB: don't call this getAllFuncTests() or isAllFuncTests(), because it would
// shadow the global parameter allFuncTests.
boolean resolveAllFuncTests() {
def paramVal = null
try {
paramVal = params.allFuncTests
} catch (e1) {
// workaround for https://issues.jenkins-ci.org/browse/JENKINS-38813
echo '[WARNING] unable to access `params`'
echo getStackTrace(e1)
try {
paramVal = allFuncTests
} catch (e2) {
echo '[WARNING] unable to access `allFuncTests`'
echo getStackTrace(e2)
}
}
if (paramVal == null) {
echo "allFuncTests param is null; using default value."
}
// paramVal may be a String, so compare as Strings
def result = (paramVal ?: allFuncTestsDefault).toString().equals("true")
echo "allFuncTests: $result"
return result
}
@Field
def mainlineBranches = ['master', 'release', 'legacy']
// Seems it does not require to be in node{} to get env
// But need to be run after node
@Field
def gwtOpts = '-Dchromefirefox'
if (env.BRANCH_NAME in mainlineBranches) {
gwtOpts = ''
}
String getLockName() {
if (env.BRANCH_NAME in mainlineBranches) {
// Each mainline branch pipeline will have its own lock.
return env.JOB_NAME
} else if (env.BRANCH_NAME.startsWith('PR-')) {
// Pull requests 1, 101, 201, xx01 will all share the same lock name.
// Barring collisions, this means each PR gets its own lock, with a
// maximum of 100 lock names. This is a workaround for
// https://issues.jenkins-ci.org/browse/JENKINS-38906
String digits = '00' + env.BRANCH_NAME.substring('PR-'.length())
String last2Digits = digits.substring(digits.length() - 2)
return 'zanata-platform-PR-x' + last2Digits
} else {
// All miscellaneous branches (across all repos) will share the same lock.
return 'GitHubMiscBranch'
}
}
/* Build/Unit Tests should fail fast:
* We want to stop the build, but first create JUnit/surefire reports
*/
// use timestamps for Jenkins logs
timestamps {
// Init the notifier
def pipelineLibraryScmGit
def mainScmGit
def notify
// allocate a node for build+unit tests
node(getLabel()) {
echo "running on node ${env.NODE_NAME}"
currentBuild.displayName = currentBuild.displayName + " {${env.NODE_NAME}}"
// Init the notifier
pipelineLibraryScmGit = new ScmGit(env, steps, 'https://github.com/zanata/zanata-pipeline-library')
pipelineLibraryScmGit.init(PIPELINE_LIBRARY_BRANCH)
mainScmGit = new ScmGit(env, steps, PROJ_URL)
mainScmGit.init(env.BRANCH_NAME)
notify = new Notifier(env, steps, currentBuild,
pipelineLibraryScmGit, mainScmGit, (env.GITHUB_COMMIT_CONTEXT) ?: 'Jenkinsfile',
)
// generate logs in colour
ansicolor {
try {
stage('Checkout') {
// notify methods send instant messages about the build progress
notify.started()
// Shallow Clone does not work with RHEL7, which uses git-1.8.3
// https://issues.jenkins-ci.org/browse/JENKINS-37229
checkout scm
// Clean the workspace
sh "git clean -dfqx"
}
// Build and Unit Tests
// The built files are stashed for integration tests in other nodes.
stage('Build') {
// Now SCM commit info is available (after checkout scm)
notify.startBuilding()
// validate translations
sh """./run-clean.sh ./mvnw -e -V \
--batch-mode -Dstyle.color=never \
com.googlecode.l10n-maven-plugin:l10n-maven-plugin:1.8:validate \
-pl :zanata-war -am -DexcludeFrontend \
"""
def jarFiles = 'target/*.jar'
def warFiles = 'target/*.war'
// run-clean.sh: ensure that child processes will be cleaned up
// (eg surefirebooter)
// -T 1: run build without multi-threading (workaround for
// suspected concurrency problems)
// -Dmaven.test.failure.ignore: Continue building other modules
// even after test failures.
withEnv(['NODE_OPTIONS=--max_old_space_size=4096']) {
sh """./run-clean.sh ./mvnw -e -V --builder singlethreaded \
-Dbuildtime.output.csv -Dbuildtime.output.csv.file=buildtime.csv \
clean install jxr:aggregate \
--batch-mode -Dstyle.color=never \
--update-snapshots \
-DstaticAnalysisCI \
-Doptimise \
$gwtOpts \
-Dkotlin.compiler.incremental=false \
-DskipFuncTests \
-DskipArqTests \
-Dmaven.compiler.failOnWarning \
-Dmaven.test.failure.ignore \
-Ddepcheck \
"""
}
def surefireTestReports = 'target/surefire-reports/TEST-*.xml'
setJUnitPrefix('UNIT', surefireTestReports)
// gather surefire results; mark build as unstable in case of failures
junit(testResults: "**/${surefireTestReports}")
// TODO try https://github.com/jenkinsci/github-pr-coverage-status-plugin
// send test coverage data to codecov.io
codecov(env, steps, mainScmGit)
// notify if compile+unit test successful
// TODO update notify (in pipeline library) to support Rocket.Chat webhook integration
notify.testResults('UNIT', currentBuild.result)
// TODO publish coverage for jest (cobertura format)
// https://issues.jenkins-ci.org/browse/JENKINS-30700 https://github.com/jenkinsci/cobertura-plugin/pull/62
// TODO publish reports:
// appserver/browser/flaky warnings
// TODO more static analysis:
// task scanner (for TODOs), DRY, PMD
// victims, OWASP, ossindex: scan dependencies for vulnerabilities
// https://wiki.jenkins-ci.org/display/JENKINS/Static+Code+Analysis+Plug-ins
// https://philphilphil.wordpress.com/2016/12/28/using-static-code-analysis-tools-with-jenkins-pipeline-jobs/
// archive build artifacts (and cross-referenced source code)
archive "**/${jarFiles},**/${warFiles},**/target/site/xref/**,target/buildtime.csv,**/target/dependencies/**,**/target/test-output/**,**/reports/scan_node_modules.xml"
// parse Jacoco test coverage
step([$class: 'JacocoPublisher'])
if (env.BRANCH_NAME == 'master') {
step([$class: 'MasterCoverageAction'])
} else if (env.BRANCH_NAME.startsWith('PR-')) {
step([$class: 'CompareCoverageAction'])
}
// ref: https://philphilphil.wordpress.com/2016/12/28/using-static-code-analysis-tools-with-jenkins-pipeline-jobs/
step([$class: 'CheckStylePublisher',
pattern: '**/target/checkstyle-result.xml',
unstableTotalAll: '0'])
// TODO set up maven-pmd-plugin for PMD/CPD
//step([$class: 'PmdPublisher', pattern: '**/target/pmd.xml', unstableTotalAll:'0'])
//step([$class: 'DryPublisher', canComputeNew: false, defaultEncoding: '', healthy: '', pattern: '**/cpd/cpdCheck.xml', unHealthy: ''])
step([$class: 'FindBugsPublisher',
pattern: '**/findbugsXml.xml',
unstableTotalAll: '0'])
step([$class: 'WarningsPublisher',
consoleParsers: [
// we use maven.compiler.failOnWarning instead
// [parserName: 'Java Compiler (javac)'],
// we use arg -Werror instead
// [parserName: 'kotlin'],
[parserName: 'JavaDoc'],
[parserName: 'Maven'], // ~279 warnings, but too variable
// TODO check integration test warnings (EAP and WildFly)
[parserName: 'appserver log messages'], // 119 warnings
[parserName: 'browser warnings'], // 0 warnings
],
// unstableTotalAll: '0',
// unstableTotalHigh: '0',
])
// TODO check integration test warnings (EAP and WildFly)
// step([$class: 'WarningsPublisher',
// parserConfigurations: [
// [parserName: 'Flaky Tests',
// pattern: '**/target/*-reports/TEST-*.xml']
// ],
// // there is exactly one deliberately flaky test
// unstableTotalAll: '1',
// ])
// this should appear after all other static analysis steps
step([$class: 'AnalysisPublisher'])
}
// gather built files to use in following pipeline stages (on
// other build nodes)
stage('Stash') {
stash name: 'generated-files',
includes: '**/target/**, **/src/main/resources/**,**/.zanata-cache/**'
}
// Reduce workspace size
sh "git clean -dfqx"
} catch (e) {
echo("Caught exception: " + e)
notify.error(e.toString())
currentBuild.result = 'FAILURE'
// abort the rest of the pipeline
throw e
}
}
}
// if the build is still green:
if (currentBuild.result == null || currentBuild.result == 'SUCCESS') {
// Only one build per lock name is allowed to run integration tests at a
// time (unless we define multiple identical locks).
// When there are more potential builds than locks available,
// *newer* builds are pulled off the queue first. When a build reaches the
// milestone at the beginning of the lock, all jobs (from the same pipeline)
// started prior to the current build that are still waiting for the lock
// will be aborted when they reach the milestone.
// Note that the milestone is at the beginning of the lock, because we
// want to abort concurrent tests before they are executed.
// Ref: https://jenkins.io/blog/2016/10/16/stage-lock-milestone/
// You can probably allow increased concurrency by defining more locks
// in Jenkins system config. q.v. JENKINS-42339
lock(resource: getLockName(), inversePrecedence: true, quantity: 1) {
milestone()
// NB all the parallel tasks must be in one stage, because you can't use `stage` inside `parallel`.
// See https://issues.jenkins-ci.org/browse/JENKINS-34696 and https://issues.jenkins-ci.org/browse/JENKINS-33185
stage('Integration tests') {
// define tasks which will run in parallel
def tasks = [
"WILDFLY" : { integrationTests('wildfly8', notify) },
"JBOSSEAP": { integrationTests('jbosseap6', notify) },
// abort other tasks (for faster feedback) as soon as one fails
// disabled; not currently handled by pipeline-unit
// failFast: true
]
// run integration test tasks in parallel
parallel tasks
node() {
// when build is *still* green after running integration tests
// GitHubCommitStatusSetter need to be inside a node
notify.finish()
currentBuild.result = (currentBuild.result) ?: 'SUCCESS'
}
// TODO in case of failure, notify culprits via IRC, email and/or Rocket.Chat
// https://wiki.jenkins-ci.org/display/JENKINS/Email-ext+plugin#Email-extplugin-PipelineExamples
// http://stackoverflow.com/a/39535424/14379
// IRC: https://issues.jenkins-ci.org/browse/JENKINS-33922
}
}
}
}
void integrationTests(String appserver, def notify) {
def failsafeTestReports='target/failsafe-reports/TEST-*.xml'
node(getLabel()) {
echo "running on node ${env.NODE_NAME}"
currentBuild.displayName = currentBuild.displayName + " {${env.NODE_NAME}}"
echo "WORKSPACE=${env.WORKSPACE}"
// we want parallel builds to use different directories so we can distinguish the archived files
dir(appserver) {
checkout scm
// Clean the workspace
sh "git clean -dfqx"
unstash 'generated-files'
/* touch all target */
//sh "find `pwd -P` -path '*/target/*' -print -exec touch '{}' \\;"
xvfb {
withPorts {
echo "Running maven build for integration tests"
// Run the maven build
echo "env.DISPLAY=${env.DISPLAY}"
echo "env.JBOSS_HTTP_PORT=${env.JBOSS_HTTP_PORT}"
echo "env.JBOSS_HTTPS_PORT=${env.JBOSS_HTTPS_PORT}"
// Build up Maven options for running functional tests:
// disable debugging:
def ftOpts = '-Dcargo.debug.jvm.args= '
// run *all* functional tests in these branches only:
if (env.BRANCH_NAME in mainlineBranches || resolveAllFuncTests()) {
ftOpts += '-DallFuncTests '
}
// skip recompilation, unit tests, static analysis
// (done in Build stage):
ftOpts += """\
-Dgwt.compiler.skip \
-Dmaven.main.skip \
-Dkotlin.compiler.incremental=false \
-Dskip.npminstall \
-DskipUnitTests \
-Danimal.sniffer.skip \
-Dcheckstyle.skip \
-Dfindbugs.skip \
-DstaticAnalysis=false \
$gwtOpts \
"""
def mvnResult = sh returnStatus: true, script: """\
./run-clean.sh ./mvnw -e -V --builder singlethreaded \
-Dbuildtime.output.csv -Dbuildtime.output.csv.file=buildtime.csv \
install \
--batch-mode -Dstyle.color=never \
--update-snapshots \
-Dappserver=$appserver \
-Dwebdriver.display=${env.DISPLAY} \
-Dwebdriver.type=chrome \
-Dwebdriver.chrome.driver=/opt/chromedriver \
${ftOpts}
"""
/* TODO
-Dassembly.skipAssembly \
-DskipAppassembler \
-DskipShade \
*/
// retain traceability report and build time info
// work from parent directory so that $appserver will appear at the beginning of the archive paths
dir('..') {
archive('*/server/functional-test/target/**/traceability.json,*/target/buildtime.csv')
}
if (mvnResult != 0) {
notify.testResults(appserver, 'UNSTABLE',
'Failed maven build for integration tests')
currentBuild.result = 'UNSTABLE'
// gather db/app/gc logs, heap dumps and screenshots to help debugging
// work from parent directory so that $appserver will appear at the beginning of the archive paths
dir('..') {
archive(
includes: '*/server/functional-test/target/**/*.log,*/server/functional-test/target/screenshots/**,*/server/*/target/**/gc.log*,*/server/*/target/**/*.hprof',
excludes: '**/BACKUP-*.log')
}
} else {
notify.testResults(appserver, 'SUCCESS')
}
echo "Capturing JUnit results"
if (setJUnitPrefix(appserver, failsafeTestReports)) {
junit(testResults: "**/${failsafeTestReports}"
// NB: if this is enabled, make sure (a) max history in Jenkins
// Configuration is small (eg 3) or
// (b) https://issues.jenkins-ci.org/browse/JENKINS-33168 is fixed.
// Update: even with max=3 it can take many hours. Disabling for now.
// ,testDataPublishers: [[$class: 'StabilityTestDataPublisher']]
)
// Reduce workspace size
sh "git clean -dfqx"
} else {
notify.error("No integration test result for $appserver")
currentBuild.result = 'FAILURE'
error "no integration test results for $appserver"
}
}
}
}
}
}
void xvfb(Closure wrapped) {
wrap([$class: 'Xvfb', debug: true, timeout: 30, displayName: (10 + env.EXECUTOR_NUMBER.toInteger())]) {
wrapped.call()
}
}
// Run enclosed steps after allocating ports and adding them to the environment
void withPorts(Closure wrapped) {
def ports = sh(script: 'server/etc/scripts/allocate-jboss-ports', returnStdout: true)
withEnv(ports.trim().readLines()) {
wrapped.call()
}
}
// Modify classnames of tests, to avoid collision between EAP and WildFly test runs.
// We also use this to distinguish unit tests from integration tests.
// from https://issues.jenkins-ci.org/browse/JENKINS-27395?focusedCommentId=256459&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-256459
boolean setJUnitPrefix(prefix, files) {
// add prefix to qualified classname
def fileGlob = "**/${files}"
def reportFiles = findFiles glob: fileGlob
if (reportFiles.size() > 0) {
sh "set +x; echo 'Using sed to edit classnames in ${fileGlob}'; sed -i \"s/\\(<testcase .*classname=['\\\"]\\)\\([a-z]\\)/\\1${prefix.toUpperCase()}.\\2/g\" ${reportFiles.join(" ")}"
return true
} else {
echo "[WARNING] Failed to find JUnit report files **/${files}"
return false
}
}