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  }