github_actions_diff.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. #!/usr/bin/env python
  2. # @file: toolset/github_actions/github_actions_diff.py
  3. # @author: Nate Brady
  4. #
  5. # @description: This script is only for use within Github Actions. It is meant
  6. # to 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
  8. # branch against the target branch.
  9. #
  10. # Any changes found in the toolset/* directory other than continuous/*,
  11. # github_actions/* and scaffolding/* will cause all tests to be run.
  12. #
  13. # The following commands can be put in commit messages to affect which tests
  14. # will run:
  15. #
  16. # [ci skip] - Provided by Travis. Travis won't trigger any builds.
  17. # [ci run-all] - This will force all tests to run.
  18. # [ci fw-only Java/gemini JavaScript/nodejs] - Ensures that only Java/gemini and
  19. # JavaScript/nodejs tests are run despite the detected changes.
  20. # [ci fw Java/gemini] - Forces Java/gemini to run in addition to detected changes.
  21. # [ci lang-only Java C++] - Ensures that only Java and C++ run despite detected changes.
  22. # [ci lang Java C++] - Forces Java and C++ tests to run in addition to detected changes.
  23. #
  24. # If only a single test within a language group is forced to run, none of the
  25. # other tests in that language group will run.
  26. #
  27. # The master branch will run the full suite of tests.
  28. #
  29. # IMPORTANT: the [ci *] commands must be added to every commit message. We do
  30. # not look at previous commit messages. Make sure to keep your PR branch
  31. # up-to-date with the target branch to avoid running unwanted tests.
  32. import subprocess
  33. import os
  34. import re
  35. def fw_found_in_changes(test, changes_output):
  36. return re.search(
  37. r"frameworks/" + re.escape(test) + "/",
  38. changes_output, re.M)
  39. # Cleans up diffing and grep output and into an array of strings
  40. def clean_output(output):
  41. return os.linesep.join([s for s in output.splitlines() if s])
  42. def quit_diffing():
  43. if len(run_tests):
  44. print("github-actions-run-tests {!s}".format(" ".join(set(run_tests))))
  45. else:
  46. print("No tests to run.")
  47. exit(0)
  48. curr_branch = ""
  49. is_PR = (os.getenv("PR_NUMBER") != "")
  50. # BRANCH_NAME is the the name of the branch
  51. is_master = os.getenv("BRANCH_NAME") == "master"
  52. previous_commit = os.getenv("PREVIOUS_COMMIT")
  53. if is_PR:
  54. curr_branch = "HEAD"
  55. # Also fetch master to compare against
  56. subprocess.check_output(['bash', '-c', 'git fetch origin master:master'])
  57. elif not is_master:
  58. curr_branch = os.getenv("GITHUB_SHA")
  59. diff_target = "master" if is_PR else previous_commit
  60. # https://stackoverflow.com/questions/25071579/list-all-files-changed-in-a-pull-request-in-git-github
  61. changes = clean_output(
  62. subprocess.check_output([
  63. 'bash', '-c',
  64. 'git --no-pager diff --name-only {0} $(git merge-base {0} {1})'
  65. .format(curr_branch, diff_target)
  66. ]))
  67. print("Determining what to run based on the following file changes: \n{!s}"
  68. .format('\n'.join(changes.split('\n')[0:10])))
  69. if len(changes.split('\n')) > 10:
  70. print("Too many files to show.")
  71. # COMMIT MESSAGES:
  72. # Before any complicated diffing, check for forced runs from the commit message
  73. # Use -2 because travis now inserts a merge commit as the last commit
  74. last_commit_msg = os.getenv("COMMIT_MESSAGE")
  75. test_dirs = []
  76. run_tests = []
  77. # Break the test env variable down into test directories
  78. if os.getenv("TESTLANG"):
  79. dir = "frameworks/" + os.getenv("TESTLANG") + "/"
  80. test_dirs = map(lambda x: os.getenv("TESTLANG") + "/" + x,
  81. filter(lambda x: os.path.isdir(dir + x), os.listdir(dir)))
  82. elif os.getenv("TESTDIR"):
  83. test_dirs = os.getenv("TESTDIR").split(' ')
  84. else:
  85. def get_frameworks(test_lang):
  86. dir = "frameworks/" + test_lang + "/"
  87. return map(lambda x: test_lang + "/" + x,
  88. filter(lambda x: os.path.isdir(dir + x),
  89. os.listdir(dir)))
  90. test_dirs = []
  91. for frameworks in map(get_frameworks, os.listdir("frameworks")):
  92. for framework in frameworks:
  93. test_dirs.append(framework)
  94. # Forced full run
  95. if re.search(r'\[ci run-all\]', last_commit_msg, re.M):
  96. print("All tests have been forced to run from the commit message.")
  97. run_tests = test_dirs
  98. quit_diffing()
  99. # Forced *fw-only* specific tests
  100. if re.search(r'\[ci fw-only .+\]', last_commit_msg, re.M):
  101. tests = re.findall(r'\[ci fw-only (.+)\]', last_commit_msg, re.M)[0].strip().split(' ')
  102. for test in tests:
  103. if test in test_dirs:
  104. print("{!s} has been forced to run from the commit message.".format(test))
  105. run_tests.append(test)
  106. # quit here because we're using "only"
  107. quit_diffing()
  108. # Forced *lang-only* specific tests
  109. if re.search(r'\[ci lang-only .+\]', last_commit_msg, re.M):
  110. langs = re.findall(r'\[ci lang-only (.+)\]', last_commit_msg, re.M)[0].strip().split(' ')
  111. for test in test_dirs:
  112. for lang in langs:
  113. if test.startswith(lang + "/"):
  114. print("{!s} has been forced to run from the commit message.".format(test))
  115. run_tests.append(test)
  116. # quit here because we're using "only"
  117. quit_diffing()
  118. # Forced framework run in addition to other tests
  119. if re.search(r'\[ci fw .+\]', last_commit_msg, re.M):
  120. tests = re.findall(r'\[ci fw (.+)\]', last_commit_msg, re.M)[0].strip().split(' ')
  121. for test in tests:
  122. if test in test_dirs:
  123. print("{!s} has been forced to run from the commit message.".format(test))
  124. run_tests.append(test)
  125. # Forced lang run in addition to other running tests
  126. if re.search(r'\[ci lang .+\]', last_commit_msg, re.M):
  127. langs = re.findall(r'\[ci lang (.+)\]', last_commit_msg, re.M)[0].strip().split(' ')
  128. for test in test_dirs:
  129. for lang in langs:
  130. if test.startswith(lang + "/"):
  131. print("{!s} has been forced to run from the commit message.".format(test))
  132. run_tests.append(test)
  133. # Ignore travis, github_actions, continuous and scaffolding changes
  134. if re.search(r'^toolset\/(?!(travis\/|github_actions\/|continuous\/|scaffolding\/))', changes, re.M) is not None:
  135. print("Found changes to core toolset. Running all tests.")
  136. run_tests = test_dirs
  137. quit_diffing()
  138. for test in test_dirs:
  139. if fw_found_in_changes(test, changes):
  140. print("Found changes that affect {!s}".format(test))
  141. run_tests.append(test)
  142. quit_diffing()