travis_diff.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. #!/usr/bin/env python
  2. # @file: toolset/travis/travis_diff.py
  3. # @author: Nate Brady
  4. #
  5. # @description: This script is only for use within Travis-CI. It is meant to
  6. # look through the commit history and determine whether or not the current
  7. # framework test directory needs to be run. It compares the state of the PR branch
  8. # against the target branch.
  9. #
  10. # Any changes found in the toolset/* directory other than continuous/*, travis/* and
  11. # scaffolding/* will cause all tests to be run.
  12. #
  13. # The following commands can be put in commit messages to affect which tests will run:
  14. #
  15. # [ci skip] - Provided by Travis. Travis won't trigger any builds.
  16. # [ci run-all] - This will force all tests to run.
  17. # [ci fw-only Java/gemini JavaScript/nodejs] - Ensures that only Java/gemini and
  18. # JavaScript/nodejs tests are run despite the detected changes.
  19. # [ci fw Java/gemini] - Forces Java/gemini to run in addition to detected changes.
  20. # [ci lang-only Java C++] - Ensures that only Java and C++ run despite detected changes.
  21. # [ci lang Java C++] - Forces Java and C++ tests to run in addition to detected changes.
  22. #
  23. # If only a single test within a language group is forced to run, none of the other tests
  24. # in that language group will run.
  25. #
  26. # IMPORTANT: the [ci *] commands must be added to every commit message. We do not look at
  27. # previous commit messages. Make sure to keep your PR branch up-to-date with the target
  28. # branch to avoid running unwanted tests.
  29. import subprocess
  30. import os
  31. import re
  32. def fw_found_in_changes(test, changes_output):
  33. return re.search(
  34. r"frameworks/" + re.escape(test) + "/",
  35. changes_output, re.M)
  36. # Cleans up diffing and grep output and into an array of strings
  37. def clean_output(output):
  38. return os.linesep.join([s for s in output.splitlines() if s])
  39. def quit_diffing():
  40. if len(run_tests):
  41. print("travis-run-tests {!s}".format(" ".join(set(run_tests))))
  42. else:
  43. print("No tests to run.")
  44. exit(0)
  45. print("TRAVIS_COMMIT_RANGE: {!s}".format(os.getenv("TRAVIS_COMMIT_RANGE")))
  46. print("TRAVIS_COMMIT : {!s}".format(os.getenv("TRAVIS_COMMIT")))
  47. is_PR = (os.getenv("TRAVIS_PULL_REQUEST") != "false")
  48. commit_range = ""
  49. first_commit = ""
  50. last_commit = ""
  51. if is_PR:
  52. print('I am testing a pull request')
  53. first_commit = os.getenv("TRAVIS_COMMIT_RANGE").split('...')[0]
  54. last_commit = subprocess.check_output(
  55. "git rev-list -n 1 FETCH_HEAD^2", shell=True).rstrip('\n')
  56. print("Guessing that first commit in PR is : {!s}".format(first_commit))
  57. print("Guessing that final commit in PR is : {!s}".format(last_commit))
  58. if first_commit == "":
  59. print("No first commit, using Github's automerge commit")
  60. commit_range = "--first-parent -1 -m FETCH_HEAD"
  61. elif first_commit == last_commit:
  62. print("Only one commit in range, examining {!s}".format(last_commit))
  63. commit_range = "-m --first-parent -1 {!s}".format(last_commit)
  64. else:
  65. commit_range = "--first-parent {!s}...{!s}".format(
  66. first_commit, last_commit)
  67. if not is_PR:
  68. print('I am not testing a pull request')
  69. commit_range = "--first-parent -m {!s}".format(
  70. os.getenv("TRAVIS_COMMIT_RANGE"))
  71. # Handle 1
  72. if commit_range == "":
  73. commit_range = "--first-parent -m -1 {!s}".format(
  74. os.getenv("TRAVIS_COMMIT"))
  75. print("Using commit range `{!s}`".format(commit_range))
  76. print("Running `git log --name-only --pretty=\"format:\" {!s}`".format(
  77. commit_range))
  78. changes = clean_output(
  79. subprocess.check_output([
  80. 'bash', '-c',
  81. 'git log --name-only --pretty="format:" {!s}'.format(commit_range)
  82. ]))
  83. print("Determining what to run based on the following file changes: \n{!s}"
  84. .format(changes))
  85. # COMMIT MESSAGES:
  86. # Before any complicated diffing, check for forced runs from the commit message
  87. # Use -2 because travis now inserts a merge commit as the last commit
  88. last_commit_msg = subprocess.check_output(
  89. ["bash", "-c", "git log --format=%B -n 1 {!s}".format(last_commit)])
  90. print("Parsing commit message for travis commands: {!s}"
  91. .format(last_commit_msg))
  92. test_dirs = []
  93. run_tests = []
  94. # Break the test env variable down into test directories
  95. if os.getenv("TESTLANG"):
  96. dir = "frameworks/" + os.getenv("TESTLANG") + "/"
  97. test_dirs = map(lambda x: os.getenv("TESTLANG") + "/" + x,
  98. filter(lambda x: os.path.isdir(dir + x), os.listdir(dir)))
  99. elif os.getenv("TESTDIR"):
  100. test_dirs = os.getenv("TESTDIR").split(' ')
  101. # Forced full run
  102. if re.search(r'\[ci run-all\]', last_commit_msg, re.M):
  103. print("All tests have been forced to run from the commit message.")
  104. run_tests = test_dirs
  105. quit_diffing()
  106. # Forced *fw-only* specific tests
  107. if re.search(r'\[ci fw-only .+\]', last_commit_msg, re.M):
  108. tests = re.findall(r'\[ci fw-only (.+)\]', last_commit_msg, re.M)[0].strip().split(' ')
  109. for test in tests:
  110. if test in test_dirs:
  111. print("{!s} has been forced to run from the commit message.".format(test))
  112. run_tests.append(test)
  113. # quit here because we're using "only"
  114. quit_diffing()
  115. # Forced *lang-only* specific tests
  116. if re.search(r'\[ci lang-only .+\]', last_commit_msg, re.M):
  117. langs = re.findall(r'\[ci lang-only (.+)\]', last_commit_msg, re.M)[0].strip().split(' ')
  118. for test in test_dirs:
  119. for lang in langs:
  120. if test.startswith(lang + "/"):
  121. print("{!s} has been forced to run from the commit message.".format(test))
  122. run_tests.append(test)
  123. # quit here because we're using "only"
  124. quit_diffing()
  125. # Forced framework run in addition to other tests
  126. if re.search(r'\[ci fw .+\]', last_commit_msg, re.M):
  127. tests = re.findall(r'\[ci fw (.+)\]', last_commit_msg, re.M)[0].strip().split(' ')
  128. for test in tests:
  129. if test in test_dirs:
  130. print("{!s} has been forced to run from the commit message.".format(test))
  131. run_tests.append(test)
  132. # Forced lang run in addition to other running tests
  133. if re.search(r'\[ci lang .+\]', last_commit_msg, re.M):
  134. langs = re.findall(r'\[ci lang (.+)\]', last_commit_msg, re.M)[0].strip().split(' ')
  135. for test in test_dirs:
  136. for lang in langs:
  137. if test.startswith(lang + "/"):
  138. print("{!s} has been forced to run from the commit message.".format(test))
  139. run_tests.append(test)
  140. # Ignore travis and docker directory changes
  141. # Also for now, ignore the old linux setup folders, as we don't want to
  142. # trigger a full run as we remove old fw_depends scripts. [ci run-all] will
  143. # still work if it's needed.
  144. if re.search(r'^toolset\/(?!(travis\/|continuous\/|scaffolding\/))|^tfb', changes, re.M) is not None:
  145. print("Found changes to core toolset. Running all tests.")
  146. run_tests = test_dirs
  147. quit_diffing()
  148. for test in test_dirs:
  149. if fw_found_in_changes(test, changes):
  150. print("Found changes that affect {!s}".format(test))
  151. run_tests.append(test)
  152. quit_diffing()