-
Notifications
You must be signed in to change notification settings - Fork 0
/
build.gradle
287 lines (255 loc) · 11.7 KB
/
build.gradle
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
plugins {
id 'groovy'
id 'codenarc'
id 'jacoco'
}
group = 'org.example'
version = '1.0-SNAPSHOT'
layout.buildDirectory.set(layout.projectDirectory.dir("out"))
def classPathDumpsDir = "${layout.buildDirectory.get()}/jacoco/classpathdumps"
def parsedClassPathDumpsDir = "${layout.buildDirectory.get()}/jacoco/parsedclasspathdumps"
sourceSets {
main {
groovy {
srcDirs = ['src','vars']
}
resources {
srcDirs = ['resources']
}
}
test {
groovy {
srcDirs = ['test']
}
}
}
repositories {
mavenCentral()
maven {
url "https://repo.jenkins-ci.org/public/"
}
}
groovydoc {
docTitle = "jcx_lib"
windowTitle = "jenkins continousX shared library"
}
codenarc {
toolVersion = "3.3.0"
}
codenarcMain {
configFile = rootProject.file("config/codenarc/codenarc.groovy")
compilationClasspath = sourceSets.main.compileClasspath + sourceSets.main.output
}
codenarcTest {
configFile = rootProject.file("config/codenarc/codenarc.groovy")
compilationClasspath = codenarcMain.compilationClasspath + sourceSets.test.compileClasspath + sourceSets.test.output
}
dependencies {
implementation 'org.apache.groovy:groovy:4.0.14'
testImplementation platform('org.junit:junit-bom:5.9.1')
testImplementation 'org.junit.jupiter:junit-jupiter'
implementation 'org.apache.ivy:ivy:2.4.0'
implementation('org.jenkins-ci.main:jenkins-core:2.190.2') {
exclude group: 'org.slf4j', module: 'log4j-over-slf4j'
}
def staplerGAV = 'org.kohsuke.stapler:stapler:1.255'
implementation staplerGAV
annotationProcessor staplerGAV
implementation 'org.jenkins-ci.plugins:pipeline-utility-steps:2.2.0@jar'
testImplementation 'org.spockframework:spock-core:2.4-M1-groovy-4.0'
testImplementation 'com.homeaway.devtools.jenkins:jenkins-spock:2.1.5'
testImplementation group: 'org.jenkins-ci.plugins.workflow', name: 'workflow-cps', version: '2.77', ext: 'jar'
testImplementation( group: 'org.jenkins-ci.plugins.workflow', name: 'workflow-step-api', version: '2.21', ext: 'jar' )
testImplementation 'org.jenkins-ci:symbol-annotation:1.20'
testImplementation 'javax.servlet:javax.servlet-api:4.0.1'
//testImplementation 'org.slf4j:slf4j-log4j12:1.7.21'
testImplementation 'commons-beanutils:commons-beanutils:1.9.3'
testImplementation 'net.bytebuddy:byte-buddy:1.9.12'
testImplementation 'org.objenesis:objenesis:3.0.1'
testImplementation 'junit:junit:4.12'
testImplementation 'org.junit.vintage:junit-vintage-engine:5.4.1'
testImplementation 'com.lesfurets:jenkins-pipeline-unit:1.9'
}
test {
useJUnitPlatform()
testLogging {
events 'passed', 'skipped', 'failed'
}
jacoco {
classDumpDir = file("${layout.buildDirectory.get()}/jacoco/classpathdumps")
}
finalizedBy jacocoTestReport
}
jacocoTestReport {
afterEvaluate {
afterEvaluate {
//classDirectories = fileTree("${System.getProperty('coverageClassesDir', "${project.layout.buildDirectory}/classes/${sourceSets.main.groovy.name}/${sourceSets.main.groovy.name}")}")
/*
* There are 2 location where the ".class" files exist:
* 1) "$buildDir/jacoco/classpathdumps": This is where jacoco puts the class files which have been triggered by
* the tests.
* 2) "$buildDir/classes/groovy/main": This is where gradle places all the class files during compilation
*
* It should be enough to just include the file tree from (1) above. However, this will result in not
* considering any of the files which has not been tested.
* In order to consider those as well, we add the class files from (2) as well. However, if we try to add the
* same class multiple times, jacoco will complain. Therefore, we need to add all the files from (1), and the
* files from (2) which are not in (1) already.
*/
String jacocoClassesPath = "$buildDir/jacoco/classpathdumps"
String gradleClassesPath = "$buildDir/classes/groovy/main"
// The single "*" here indicates the "default" package which in our case is the "vars" dir
List includesList = ["*", "com/company/**"]
List excludesList = ["**/*Test*", "**/*Mock*"]
List allTrees = [
fileTree(dir: jacocoClassesPath, includes: includesList, excludes: excludesList),
fileTree(dir: gradleClassesPath, includes: includesList, excludes: excludesList).filter { f ->
/*
* In this filter, we want to remove all the files which were already covered by jacoco in the first
* `fileTree` above. This filter is only executed after the tests have completed while jacoco is
* generating the reports. Therefore, we cannot find the files list earlier. Although here were are
* finding the files list multiple times (for each file `f`), this is still acceptable because number of
* files are limited anyway.
*/
// This list will collect all the ".class" files from jacoco classes path folder
@SuppressWarnings("JavaIoPackageAccess")
List fs = new File(jacocoClassesPath).listFiles().findAll { it.name.endsWith(".class") }
/*
* Next we need to keep only the file name (without path). Additionally, jacoco adds a session ID to the
* file name (e.g. something.class would look something like something.93153c79e479a993.class). What we
* are interested in here is only the "something.class" without the session ID.
* The `toString()` is also required otherwise the `.contains()` we do next will be comparing a G-String
* to a String and not match.
*/
fs = fs.collect { "${it.name.split('\\.')[0]}.class".toString() }
// We only want to consider the current file `f` if its name does not already exist in `fs`.
boolean keepFile = !fs.contains(f.name)
/*
* XXX - it's enough to just return the value of `keepFile` without deletion but we do so to simplify
* the call to the jacoco plugin from Jenkins. If we don't do so, we'll need to do the filtering again
* from Jenkinsfile as well.
*/
if (!keepFile) {
f.delete()
}
return keepFile
},
]
classDirectories.from = allTrees
}
}
reports {
xml.required = true
csv.required = false
html.required = true
html.destination file("${layout.buildDirectory.get()}/reports/jacoco")
}
}
task jacocoParseClassDumps {
doFirst {
cleanClassPathDumps(classPathDumpsDir)
parseClassPathDumps(classPathDumpsDir, parsedClassPathDumpsDir)
}
}
task jacocoTestReports {
def classesDirs = []
classesDirs << "${project.layout.buildDirectory}/classes/${sourceSets.main.groovy.name}/${sourceSets.main.groovy.name}"
if (file(parsedClassPathDumpsDir).exists()) {
file(parsedClassPathDumpsDir).eachFile {
classesDirs << it.path
}
}
dependsOn classesDirs.collect { "runJacocoWith${file(it).name}" }
classesDirs.each {
def targetDir = file(it)
def targetDirName = targetDir.name
def targetDirPath = targetDir.path
def targetFileName = targetDirName + '.xml'
task "runJacocoWith${targetDirName}"(type: GradleBuild) {
buildFile = 'build.gradle'
tasks = ['jacocoTestReport']
startParameter.systemPropertiesArgs += [coverageFileName: targetFileName, coverageClassesDir: targetDirPath]
}
}
}
// --------------------------------------------------
// Helper functions
/**
* Collects all the source file paths from all the source folders specified in the project configuration.
*
* @return A list of all the source file paths relative to the root package and excluding the
* extensions (i.e. [foo, mypackage/bar])
*/
def getAllSourceFilePaths() {
def sourceFilePaths = []
def baseDirs = sourceSets.main.groovy.srcDirs
baseDirs.each { File baseDir ->
fileTree(dir: baseDir).each { File file ->
sourceFilePaths << file.path.replace(baseDir.path + '/', '').replace('.groovy', '')
}
}
return sourceFilePaths
}
/**
* Cleans the class path dump directory by deleting class files that were not compiled from the source files (i.e. class
* files from external libraries, etc).
*
* @param classPathDumpsDir Path to the class dumps directory seen by the Jacoco agent.
*/
def cleanClassPathDumps(String classPathDumpsDir) {
def srcFilePaths = getAllSourceFilePaths()
fileTree(dir: classPathDumpsDir).each { File file ->
def filePath = file.path.replace(classPathDumpsDir + '/', '')
def cleanedFilePath = filePath.contains('$') ? filePath.tokenize('$')[0] : filePath.tokenize('.')[0]
if (!srcFilePaths.contains(cleanedFilePath) || file.path.contains('$')) {
file.delete()
}
}
}
/**
* Check whether the class is already duplicated in the specified directory.
*
* @param baseDir Path to the directory containing the classes to check.
* @param relativeFilePath Relative path of the class file to check for duplication.
*
* @return True if there is already a class file with the same class as the one we are checking, or False otherwise.
*/
def isClassDuplicatedInDir(String baseDir, String relativeFilePath) {
for (File file in fileTree(dir: baseDir)) {
def cleanRelativeFilePath = relativeFilePath.contains('$') ? relativeFilePath.tokenize('$')[0] : relativeFilePath.tokenize('.')[0]
if (file.path.contains("${baseDir}/${cleanRelativeFilePath}")) {
return true
}
}
return false
}
/**
* Parses the class dump directory seen by the Jacoco agent by copying duplicated file names to individual folders. If
* this is not done, then at the time of analysis it will throw 'Can't add different class with same name' error since
* the Jenkins unit testing framework dynamically creates different class files for the same class at runtime.
*
* @param classPathDumpsDir Path to the class files dump directory from the Jacoco agent.
* @param parsedClassPathDumpsDir Path to the base directory that will contain the sub-directories containing the parsed
* class files.
*/
def parseClassPathDumps(String classPathDumpsDir, String parsedClassPathDumpsDir) {
def classesBaseTree = fileTree(dir: classPathDumpsDir)
for (File srcFile in classesBaseTree) {
for (int i = 1 ; i <= classesBaseTree.size() ; i++) {
def targetDir = "${parsedClassPathDumpsDir}/${i}"
if (Files.notExists(Paths.get(targetDir))) {
Files.createDirectories(Paths.get(targetDir))
}
def relativeTargetFilePath = srcFile.path.replace(classPathDumpsDir + '/', '')
def targetFile = file("${targetDir}/${relativeTargetFilePath}")
if (!isClassDuplicatedInDir(targetDir, relativeTargetFilePath)) {
def targetParentPath = Paths.get(targetFile.parent)
if (Files.notExists(targetParentPath)) {
Files.createDirectories(targetParentPath)
}
Files.copy(Paths.get(srcFile.path), Paths.get(targetFile.path))
break
}
}
}
}