github.com/xgoffin/jenkins-library@v1.154.0/consumer-test/consumerTestController.groovy (about) 1 import groovy.io.FileType 2 3 import static groovy.json.JsonOutput.toJson 4 5 COMMIT_HASH = null 6 RUNNING_LOCALLY = false 7 AUXILIARY_SLEEP_MS = 10000 8 START_TIME_MS = System.currentTimeMillis() 9 WORKSPACES_ROOT = 'workspaces' 10 TEST_CASES_DIR = 'testCases' 11 LIBRARY_VERSION_UNDER_TEST = "git log --format=%H -n 1".execute().text.trim() 12 REPOSITORY_UNDER_TEST = System.getenv('REPOSITORY_UNDER_TEST') ?: System.getenv('TRAVIS_REPO_SLUG') ?: 'SAP/jenkins-library' 13 BRANCH_NAME = System.getenv('TRAVIS_BRANCH') ?: System.getenv('BRANCH_NAME') 14 15 EXCLUDED_FROM_CONSUMER_TESTING_REGEXES = [ 16 /^documentation\/.*$/, 17 /^.travis.yml$/, 18 /^test\/.*$/ 19 ] 20 21 println "Running tests for repository: ${REPOSITORY_UNDER_TEST}, branch: ${BRANCH_NAME}, commit: ${LIBRARY_VERSION_UNDER_TEST}" 22 23 newEmptyDir(WORKSPACES_ROOT) 24 TestRunnerThread.workspacesRootDir = WORKSPACES_ROOT 25 TestRunnerThread.libraryVersionUnderTest = LIBRARY_VERSION_UNDER_TEST 26 TestRunnerThread.repositoryUnderTest = REPOSITORY_UNDER_TEST 27 28 def testCaseThreads 29 def cli = new CliBuilder( 30 usage: 'groovy consumerTestController.groovy [<options>]', 31 header: 'Options:', 32 footer: 'If no options are set, all tests are run centrally, i.e. on travisCI.') 33 34 cli.with { 35 h longOpt: 'help', 'Print this help text and exit.' 36 l longOpt: 'run-locally', 'Run consumer tests locally in Docker, i.e. skip reporting of GitHub status.' 37 s longOpt: 'single-test', args: 1, argName: 'filePath', 'Run single test.' 38 } 39 40 def options = cli.parse(args) 41 42 if (options.h) { 43 cli.usage() 44 return 45 } 46 47 if (options.l) { 48 RUNNING_LOCALLY = true 49 } 50 51 if (!RUNNING_LOCALLY) { 52 /* 53 In case the build is performed for a pull request TRAVIS_COMMIT is a merge 54 commit between the base branch and the PR branch HEAD. That commit is actually built. 55 But for notifying about a build status we need the commit which is currently 56 the HEAD of the PR branch. 57 58 In case the build is performed for a simple branch (not associated with a PR) 59 In this case there is no merge commit between any base branch and HEAD of a PR branch. 60 The commit which we need for notifying about a build status is in this case simply 61 TRAVIS_COMMIT itself. 62 */ 63 COMMIT_HASH = System.getenv('TRAVIS_PULL_REQUEST_SHA') ?: System.getenv('TRAVIS_COMMIT') ?: LIBRARY_VERSION_UNDER_TEST 64 65 if (changeDoesNotNeedConsumerTesting()) { 66 println 'No consumer tests necessary.' 67 notifyGithub("success", "No consumer tests necessary.") 68 return 69 } else { 70 notifyGithub("pending", "Consumer tests are in progress.") 71 } 72 } 73 74 if (options.s) { 75 def file = new File(options.s) 76 if (!file.exists()) { 77 exitPrematurely("Test case configuration file '${file}' does not exist. " + 78 "Please provide path to a configuration file of structure '/rootDir/areaDir/testCase.yml'.") 79 } 80 testCaseThreads = [new TestRunnerThread(file)] 81 } else { 82 testCaseThreads = listTestCaseThreads() 83 } 84 85 testCaseThreads.each { it -> 86 it.start() 87 } 88 89 //The thread below will print to console while the test cases are running. 90 //Otherwise the job would be canceled after 10 minutes without output. 91 def done = false 92 Thread.start { 93 def outputWasPrintedPrematurely = false 94 def singleTestCase = (testCaseThreads.size() == 1) 95 if (singleTestCase) { 96 AUXILIARY_SLEEP_MS = 1000 //for a single test case we print the running output every second 97 } 98 for (; ;) { 99 if (singleTestCase) { 100 testCaseThreads[0].printRunningStdOut() 101 } else { 102 println "[INFO] Consumer tests are still running." 103 } 104 105 // Build is killed at 50 min, print log to console at minute 45 106 int MINUTES_SINCE_START = (System.currentTimeMillis() - START_TIME_MS) / (1000 * 60) 107 if (!singleTestCase && MINUTES_SINCE_START > 44 && !outputWasPrintedPrematurely) { 108 testCaseThreads.each { thread -> 109 thread.printOutput() 110 } 111 outputWasPrintedPrematurely = true 112 } 113 114 sleep(AUXILIARY_SLEEP_MS) 115 if (done) { 116 break 117 } 118 } 119 } 120 121 testCaseThreads.each { it -> 122 it.join() 123 } 124 done = true 125 126 def failedThreads = testCaseThreads.findAll { thread -> 127 thread.returnCode != 0 128 } 129 130 def status 131 def statusMessage 132 if (failedThreads.size() == 0) { 133 status = "success" 134 statusMessage = "All consumer tests finished successfully. Congratulations!" 135 } else { 136 failedThreads.each { failedThread -> 137 println "[ERROR] ${failedThread.uniqueName}: Process execution of command: '${failedThread.lastCommand}' failed. " + 138 "Return code: ${failedThread.returnCode}." 139 failedThread.printOutput() 140 } 141 status = "failure" 142 statusMessage = "The following consumer test(s) failed: ${failedThreads}" 143 } 144 145 if (!RUNNING_LOCALLY) { 146 notifyGithub(status, statusMessage) 147 } 148 149 println statusMessage 150 151 if (status == "failure") { 152 System.exit(1) 153 } 154 155 156 def listTestCaseThreads() { 157 //Each dir that includes a yml file is a test case 158 def threads = [] 159 new File(TEST_CASES_DIR).traverse(type: FileType.FILES, nameFilter: ~/^.+\.yml\u0024/) { file -> 160 threads << new TestRunnerThread(file) 161 } 162 return threads 163 } 164 165 def notifyGithub(state, description) { 166 println "[INFO] Notifying about state '${state}' for commit '${COMMIT_HASH}'." 167 168 URL url = new URL("https://api.github.com/repos/SAP/jenkins-library/statuses/${COMMIT_HASH}") 169 HttpURLConnection con = (HttpURLConnection) url.openConnection() 170 con.setRequestMethod('POST') 171 con.setRequestProperty("Content-Type", "application/json; utf-8"); 172 con.setRequestProperty('User-Agent', 'groovy-script') 173 con.setRequestProperty('Authorization', "token ${System.getenv('INTEGRATION_TEST_VOTING_TOKEN')}") 174 175 def postBody = [ 176 state : state, 177 target_url : System.getenv('TRAVIS_BUILD_WEB_URL') ?: System.getenv('BUILD_WEB_URL'), 178 description: description, 179 context : "integration-tests" 180 ] 181 182 con.setDoOutput(true) 183 con.getOutputStream().withStream { os -> 184 os.write(toJson(postBody).getBytes("UTF-8")) 185 } 186 187 int responseCode = con.getResponseCode() 188 if (responseCode != HttpURLConnection.HTTP_CREATED) { 189 exitPrematurely("[ERROR] Posting status to github failed. Expected response code " + 190 "'${HttpURLConnection.HTTP_CREATED}', but got '${responseCode}'. " + 191 "Response message: '${con.getResponseMessage()}'", 192 34) // Error code taken from curl: CURLE_HTTP_POST_ERROR 193 } 194 } 195 196 def changeDoesNotNeedConsumerTesting() { 197 if (BRANCH_NAME == 'master') { 198 return false 199 } 200 201 def excludesRegex = '(' + EXCLUDED_FROM_CONSUMER_TESTING_REGEXES.join('|') + ')' 202 203 "git remote add sap https://github.com/SAP/jenkins-library.git".execute().waitFor() 204 "git fetch sap".execute().waitFor() 205 def diff = "git diff --name-only sap/master ${LIBRARY_VERSION_UNDER_TEST}".execute().text.trim() 206 207 for (def line : diff.readLines()) { 208 if (!(line ==~ excludesRegex)) { 209 return false 210 } 211 } 212 213 return true 214 } 215 216 static def newEmptyDir(String dirName) { 217 def dir = new File(dirName) 218 if (dir.exists()) { 219 if (!dir.deleteDir()) { 220 exitPrematurely("Deletion of dir '${dirName}' failed.") 221 } 222 } 223 if (!dir.mkdirs()) { 224 exitPrematurely("Creation of dir '${dirName}' failed.") 225 } 226 } 227 228 static def exitPrematurely(String message, int returnCode = 1) { 229 println message 230 System.exit(returnCode) 231 }