determine_tests_to_run.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. # Script used for checking changes for incremental testing cases
  2. from __future__ import absolute_import, division, print_function
  3. import argparse
  4. import json
  5. import os
  6. import re
  7. import subprocess
  8. import sys
  9. from pprint import pformat
  10. import traceback
  11. # NOTE(simon): do not add type hint here because it's ran using python2 in CI.
  12. def list_changed_files(commit_range):
  13. """Returns a list of names of files changed in the given commit range.
  14. The function works by opening a subprocess and running git. If an error
  15. occurs while running git, the script will abort.
  16. Args:
  17. commit_range: The commit range to diff, consisting of the two
  18. commit IDs separated by \"..\"
  19. Returns:
  20. list: List of changed files within the commit range
  21. """
  22. base_branch = os.environ.get("BUILDKITE_PULL_REQUEST_BASE_BRANCH")
  23. if base_branch:
  24. pull_command = ["git", "fetch", "origin", base_branch]
  25. subprocess.check_call(pull_command)
  26. command = ["git", "diff", "--name-only", commit_range, "--"]
  27. out = subprocess.check_output(command)
  28. return [s.strip() for s in out.decode().splitlines() if s is not None]
  29. def is_pull_request():
  30. event_type = None
  31. for key in ["GITHUB_EVENT_NAME", "TRAVIS_EVENT_TYPE"]:
  32. event_type = os.getenv(key, event_type)
  33. if (
  34. os.environ.get("BUILDKITE")
  35. and os.environ.get("BUILDKITE_PULL_REQUEST") != "false"
  36. ):
  37. event_type = "pull_request"
  38. return event_type == "pull_request"
  39. def get_commit_range():
  40. commit_range = None
  41. if os.environ.get("TRAVIS"):
  42. commit_range = os.environ["TRAVIS_COMMIT_RANGE"]
  43. elif os.environ.get("GITHUB_EVENT_PATH"):
  44. with open(os.environ["GITHUB_EVENT_PATH"], "rb") as f:
  45. event = json.loads(f.read())
  46. base = event["pull_request"]["base"]["sha"]
  47. commit_range = "{}...{}".format(base, event.get("after", ""))
  48. elif os.environ.get("BUILDKITE"):
  49. commit_range = "origin/{}...{}".format(
  50. os.environ["BUILDKITE_PULL_REQUEST_BASE_BRANCH"],
  51. os.environ["BUILDKITE_COMMIT"],
  52. )
  53. assert commit_range is not None
  54. return commit_range
  55. if __name__ == "__main__":
  56. parser = argparse.ArgumentParser()
  57. parser.add_argument("--output", type=str, help="json or envvars", default="envvars")
  58. args = parser.parse_args()
  59. RAY_CI_BRANCH_BUILD = int(
  60. os.environ.get("BUILDKITE_PULL_REQUEST", "false") == "false"
  61. )
  62. RAY_CI_ML_AFFECTED = 0
  63. RAY_CI_TUNE_AFFECTED = 0
  64. RAY_CI_TRAIN_AFFECTED = 0
  65. # Whether only the most important (high-level) RLlib tests should be run.
  66. # Set to 1 for any changes to Ray Tune or python source files that are
  67. # NOT related to Serve, Dashboard or Train.
  68. RAY_CI_RLLIB_AFFECTED = 0
  69. # Whether all RLlib tests should be run.
  70. # Set to 1 only when a source file in `ray/rllib` has been changed.
  71. RAY_CI_RLLIB_DIRECTLY_AFFECTED = 0
  72. RAY_CI_SERVE_AFFECTED = 0
  73. RAY_CI_CORE_CPP_AFFECTED = 0
  74. RAY_CI_CPP_AFFECTED = 0
  75. RAY_CI_JAVA_AFFECTED = 0
  76. RAY_CI_PYTHON_AFFECTED = 0
  77. RAY_CI_LINUX_WHEELS_AFFECTED = 0
  78. RAY_CI_MACOS_WHEELS_AFFECTED = 0
  79. RAY_CI_DASHBOARD_AFFECTED = 0
  80. RAY_CI_DOCKER_AFFECTED = 0
  81. RAY_CI_DOC_AFFECTED = 0
  82. RAY_CI_PYTHON_DEPENDENCIES_AFFECTED = 0
  83. RAY_CI_TOOLS_AFFECTED = 0
  84. RAY_CI_DATA_AFFECTED = 0
  85. RAY_CI_WORKFLOW_AFFECTED = 0
  86. RAY_CI_RELEASE_TESTS_AFFECTED = 0
  87. RAY_CI_COMPILED_PYTHON_AFFECTED = 0
  88. if is_pull_request():
  89. commit_range = get_commit_range()
  90. files = list_changed_files(commit_range)
  91. print(pformat(commit_range), file=sys.stderr)
  92. print(pformat(files), file=sys.stderr)
  93. # Dry run py_dep_analysis.py to see which tests we would have run.
  94. try:
  95. import py_dep_analysis as pda
  96. graph = pda.build_dep_graph()
  97. rllib_tests = pda.list_rllib_tests()
  98. print("Total # of RLlib tests: ", len(rllib_tests), file=sys.stderr)
  99. impacted = {}
  100. for test in rllib_tests:
  101. for file in files:
  102. if pda.test_depends_on_file(graph, test, file):
  103. impacted[test[0]] = True
  104. print("RLlib tests impacted: ", len(impacted), file=sys.stderr)
  105. for test in impacted.keys():
  106. print(" ", test, file=sys.stderr)
  107. except Exception:
  108. print("Failed to dry run py_dep_analysis.py", file=sys.stderr)
  109. traceback.print_exc(file=sys.stderr)
  110. # End of dry run.
  111. skip_prefix_list = [
  112. ".buildkite/",
  113. "doc/",
  114. "examples/",
  115. "dev/",
  116. "kubernetes/",
  117. "site/",
  118. ]
  119. for changed_file in files:
  120. if changed_file.startswith("python/ray/air"):
  121. RAY_CI_ML_AFFECTED = 1
  122. RAY_CI_TRAIN_AFFECTED = 1
  123. RAY_CI_TUNE_AFFECTED = 1
  124. RAY_CI_RLLIB_AFFECTED = 1
  125. RAY_CI_LINUX_WHEELS_AFFECTED = 1
  126. RAY_CI_MACOS_WHEELS_AFFECTED = 1
  127. elif changed_file.startswith("python/ray/data"):
  128. RAY_CI_DATA_AFFECTED = 1
  129. RAY_CI_ML_AFFECTED = 1
  130. RAY_CI_TRAIN_AFFECTED = 1
  131. RAY_CI_LINUX_WHEELS_AFFECTED = 1
  132. RAY_CI_MACOS_WHEELS_AFFECTED = 1
  133. elif changed_file.startswith("python/ray/workflow"):
  134. RAY_CI_WORKFLOW_AFFECTED = 1
  135. RAY_CI_LINUX_WHEELS_AFFECTED = 1
  136. RAY_CI_MACOS_WHEELS_AFFECTED = 1
  137. elif changed_file.startswith("python/ray/tune"):
  138. RAY_CI_ML_AFFECTED = 1
  139. RAY_CI_DOC_AFFECTED = 1
  140. RAY_CI_TUNE_AFFECTED = 1
  141. RAY_CI_RLLIB_AFFECTED = 1
  142. RAY_CI_LINUX_WHEELS_AFFECTED = 1
  143. RAY_CI_MACOS_WHEELS_AFFECTED = 1
  144. elif changed_file.startswith("python/ray/train"):
  145. RAY_CI_ML_AFFECTED = 1
  146. RAY_CI_TRAIN_AFFECTED = 1
  147. RAY_CI_LINUX_WHEELS_AFFECTED = 1
  148. RAY_CI_MACOS_WHEELS_AFFECTED = 1
  149. elif re.match("^(python/ray/)?rllib/", changed_file):
  150. RAY_CI_RLLIB_AFFECTED = 1
  151. RAY_CI_RLLIB_DIRECTLY_AFFECTED = 1
  152. RAY_CI_LINUX_WHEELS_AFFECTED = 1
  153. RAY_CI_MACOS_WHEELS_AFFECTED = 1
  154. elif changed_file.startswith("python/ray/serve"):
  155. RAY_CI_DOC_AFFECTED = 1
  156. RAY_CI_SERVE_AFFECTED = 1
  157. RAY_CI_LINUX_WHEELS_AFFECTED = 1
  158. RAY_CI_MACOS_WHEELS_AFFECTED = 1
  159. elif changed_file.startswith("python/ray/dashboard"):
  160. RAY_CI_DASHBOARD_AFFECTED = 1
  161. # https://github.com/ray-project/ray/pull/15981
  162. RAY_CI_LINUX_WHEELS_AFFECTED = 1
  163. RAY_CI_MACOS_WHEELS_AFFECTED = 1
  164. elif changed_file.startswith("dashboard"):
  165. RAY_CI_DASHBOARD_AFFECTED = 1
  166. # https://github.com/ray-project/ray/pull/15981
  167. RAY_CI_LINUX_WHEELS_AFFECTED = 1
  168. RAY_CI_MACOS_WHEELS_AFFECTED = 1
  169. elif changed_file.startswith("python/"):
  170. RAY_CI_ML_AFFECTED = 1
  171. RAY_CI_TUNE_AFFECTED = 1
  172. RAY_CI_TRAIN_AFFECTED = 1
  173. RAY_CI_RLLIB_AFFECTED = 1
  174. RAY_CI_SERVE_AFFECTED = 1
  175. RAY_CI_WORKFLOW_AFFECTED = 1
  176. RAY_CI_DATA_AFFECTED = 1
  177. RAY_CI_PYTHON_AFFECTED = 1
  178. RAY_CI_DASHBOARD_AFFECTED = 1
  179. RAY_CI_LINUX_WHEELS_AFFECTED = 1
  180. RAY_CI_MACOS_WHEELS_AFFECTED = 1
  181. RAY_CI_DOC_AFFECTED = 1
  182. # Python changes might impact cross language stack in Java.
  183. # Java also depends on Python CLI to manage processes.
  184. RAY_CI_JAVA_AFFECTED = 1
  185. if changed_file.startswith("python/setup.py") or re.match(
  186. ".*requirements.*\.txt", changed_file
  187. ):
  188. RAY_CI_PYTHON_DEPENDENCIES_AFFECTED = 1
  189. for compiled_extension in (".pxd", ".pyi", ".pyx", ".so"):
  190. if changed_file.endswith(compiled_extension):
  191. RAY_CI_COMPILED_PYTHON_AFFECTED = 1
  192. break
  193. elif changed_file.startswith("java/"):
  194. RAY_CI_JAVA_AFFECTED = 1
  195. elif changed_file.startswith("cpp/"):
  196. RAY_CI_CPP_AFFECTED = 1
  197. elif changed_file.startswith("docker/"):
  198. RAY_CI_DOCKER_AFFECTED = 1
  199. RAY_CI_LINUX_WHEELS_AFFECTED = 1
  200. elif changed_file.startswith("doc/"):
  201. if (
  202. changed_file.endswith(".py")
  203. or changed_file.endswith(".ipynb")
  204. or changed_file.endswith("BUILD")
  205. ):
  206. RAY_CI_DOC_AFFECTED = 1
  207. # Else, this affects only a rst file or so. In that case,
  208. # we pass, as the flag RAY_CI_DOC_AFFECTED is only
  209. # used to indicate that tests/examples should be run
  210. # (documentation will be built always)
  211. elif changed_file.startswith("release/"):
  212. if changed_file.startswith("release/ray_release"):
  213. # Release test unit tests are ALWAYS RUN, so pass
  214. pass
  215. elif not changed_file.endswith(".yaml") and not changed_file.endswith(
  216. ".md"
  217. ):
  218. # Do not run on config changes
  219. RAY_CI_RELEASE_TESTS_AFFECTED = 1
  220. elif any(changed_file.startswith(prefix) for prefix in skip_prefix_list):
  221. # nothing is run but linting in these cases
  222. pass
  223. elif changed_file.startswith("ci/lint"):
  224. # Linter will always be run
  225. RAY_CI_TOOLS_AFFECTED = 1
  226. elif changed_file.startswith("ci/pipeline"):
  227. # These scripts are always run as part of the build process
  228. RAY_CI_TOOLS_AFFECTED = 1
  229. elif changed_file.endswith("build-docker-images.py"):
  230. RAY_CI_DOCKER_AFFECTED = 1
  231. RAY_CI_LINUX_WHEELS_AFFECTED = 1
  232. RAY_CI_TOOLS_AFFECTED = 1
  233. elif changed_file.startswith("ci/run"):
  234. RAY_CI_TOOLS_AFFECTED = 1
  235. elif changed_file.startswith("src/"):
  236. RAY_CI_ML_AFFECTED = 1
  237. RAY_CI_TUNE_AFFECTED = 1
  238. RAY_CI_TRAIN_AFFECTED = 1
  239. RAY_CI_RLLIB_AFFECTED = 1
  240. RAY_CI_SERVE_AFFECTED = 1
  241. RAY_CI_CORE_CPP_AFFECTED = 1
  242. RAY_CI_CPP_AFFECTED = 1
  243. RAY_CI_JAVA_AFFECTED = 1
  244. RAY_CI_PYTHON_AFFECTED = 1
  245. RAY_CI_LINUX_WHEELS_AFFECTED = 1
  246. RAY_CI_MACOS_WHEELS_AFFECTED = 1
  247. RAY_CI_DASHBOARD_AFFECTED = 1
  248. RAY_CI_DOC_AFFECTED = 1
  249. RAY_CI_RELEASE_TESTS_AFFECTED = 1
  250. else:
  251. print(
  252. "Unhandled source code change: {changed_file}".format(
  253. changed_file=changed_file
  254. ),
  255. file=sys.stderr,
  256. )
  257. RAY_CI_ML_AFFECTED = 1
  258. RAY_CI_TUNE_AFFECTED = 1
  259. RAY_CI_TRAIN_AFFECTED = 1
  260. RAY_CI_RLLIB_AFFECTED = 1
  261. RAY_CI_SERVE_AFFECTED = 1
  262. RAY_CI_CORE_CPP_AFFECTED = 1
  263. RAY_CI_CPP_AFFECTED = 1
  264. RAY_CI_JAVA_AFFECTED = 1
  265. RAY_CI_PYTHON_AFFECTED = 1
  266. RAY_CI_DOC_AFFECTED = 1
  267. RAY_CI_LINUX_WHEELS_AFFECTED = 1
  268. RAY_CI_MACOS_WHEELS_AFFECTED = 1
  269. RAY_CI_DASHBOARD_AFFECTED = 1
  270. RAY_CI_TOOLS_AFFECTED = 1
  271. RAY_CI_RELEASE_TESTS_AFFECTED = 1
  272. RAY_CI_COMPILED_PYTHON_AFFECTED = 1
  273. else:
  274. RAY_CI_ML_AFFECTED = 1
  275. RAY_CI_TUNE_AFFECTED = 1
  276. RAY_CI_TRAIN_AFFECTED = 1
  277. RAY_CI_RLLIB_AFFECTED = 1
  278. RAY_CI_RLLIB_DIRECTLY_AFFECTED = 1
  279. RAY_CI_SERVE_AFFECTED = 1
  280. RAY_CI_CPP_AFFECTED = 1
  281. RAY_CI_CORE_CPP_AFFECTED = 1
  282. RAY_CI_JAVA_AFFECTED = 1
  283. RAY_CI_PYTHON_AFFECTED = 1
  284. RAY_CI_DOC_AFFECTED = 1
  285. RAY_CI_LINUX_WHEELS_AFFECTED = 1
  286. RAY_CI_MACOS_WHEELS_AFFECTED = 1
  287. RAY_CI_DASHBOARD_AFFECTED = 1
  288. RAY_CI_TOOLS_AFFECTED = 1
  289. RAY_CI_WORKFLOW_AFFECTED = 1
  290. RAY_CI_DATA_AFFECTED = 1
  291. RAY_CI_RELEASE_TESTS_AFFECTED = 1
  292. RAY_CI_COMPILED_PYTHON_AFFECTED = 1
  293. # Log the modified environment variables visible in console.
  294. output_string = " ".join(
  295. [
  296. "RAY_CI_BRANCH_BUILD={}".format(RAY_CI_BRANCH_BUILD),
  297. "RAY_CI_ML_AFFECTED={}".format(RAY_CI_ML_AFFECTED),
  298. "RAY_CI_TUNE_AFFECTED={}".format(RAY_CI_TUNE_AFFECTED),
  299. "RAY_CI_TRAIN_AFFECTED={}".format(RAY_CI_TRAIN_AFFECTED),
  300. "RAY_CI_RLLIB_AFFECTED={}".format(RAY_CI_RLLIB_AFFECTED),
  301. "RAY_CI_RLLIB_DIRECTLY_AFFECTED={}".format(RAY_CI_RLLIB_DIRECTLY_AFFECTED),
  302. "RAY_CI_SERVE_AFFECTED={}".format(RAY_CI_SERVE_AFFECTED),
  303. "RAY_CI_DASHBOARD_AFFECTED={}".format(RAY_CI_DASHBOARD_AFFECTED),
  304. "RAY_CI_DOC_AFFECTED={}".format(RAY_CI_DOC_AFFECTED),
  305. "RAY_CI_CORE_CPP_AFFECTED={}".format(RAY_CI_CORE_CPP_AFFECTED),
  306. "RAY_CI_CPP_AFFECTED={}".format(RAY_CI_CPP_AFFECTED),
  307. "RAY_CI_JAVA_AFFECTED={}".format(RAY_CI_JAVA_AFFECTED),
  308. "RAY_CI_PYTHON_AFFECTED={}".format(RAY_CI_PYTHON_AFFECTED),
  309. "RAY_CI_LINUX_WHEELS_AFFECTED={}".format(RAY_CI_LINUX_WHEELS_AFFECTED),
  310. "RAY_CI_MACOS_WHEELS_AFFECTED={}".format(RAY_CI_MACOS_WHEELS_AFFECTED),
  311. "RAY_CI_DOCKER_AFFECTED={}".format(RAY_CI_DOCKER_AFFECTED),
  312. "RAY_CI_PYTHON_DEPENDENCIES_AFFECTED={}".format(
  313. RAY_CI_PYTHON_DEPENDENCIES_AFFECTED
  314. ),
  315. "RAY_CI_TOOLS_AFFECTED={}".format(RAY_CI_TOOLS_AFFECTED),
  316. "RAY_CI_WORKFLOW_AFFECTED={}".format(RAY_CI_WORKFLOW_AFFECTED),
  317. "RAY_CI_DATA_AFFECTED={}".format(RAY_CI_DATA_AFFECTED),
  318. "RAY_CI_RELEASE_TESTS_AFFECTED={}".format(RAY_CI_RELEASE_TESTS_AFFECTED),
  319. "RAY_CI_COMPILED_PYTHON_AFFECTED={}".format(
  320. RAY_CI_COMPILED_PYTHON_AFFECTED
  321. ),
  322. ]
  323. )
  324. # Debug purpose
  325. print(output_string, file=sys.stderr)
  326. # Used by buildkite log format
  327. if args.output.lower() == "json":
  328. pairs = [item.split("=") for item in output_string.split(" ")]
  329. affected_vars = [key for key, affected in pairs if affected == "1"]
  330. print(json.dumps(affected_vars))
  331. else:
  332. print(output_string)