update_checks.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. #!/usr/bin/env python3
  2. """Updates the CHECK: lines in lit tests based on the AUTOUPDATE line."""
  3. __copyright__ = """
  4. Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  5. Exceptions. See /LICENSE for license information.
  6. SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  7. """
  8. from concurrent import futures
  9. import os
  10. import subprocess
  11. import sys
  12. _BINDIR = "./bazel-bin/executable_semantics"
  13. _TESTDATA = "executable_semantics/testdata"
  14. # TODO: Right now this is a static string used. In theory maybe we should use
  15. # the command; it's included for that flexibility.
  16. _AUTOUPDATE_MARKER = "// AUTOUPDATE: executable_semantics %s\n"
  17. def _get_tests():
  18. """Get the list of tests from the filesystem."""
  19. tests = set()
  20. for path in list(os.listdir(_TESTDATA)):
  21. f = os.path.basename(path)
  22. if f == "lit.cfg":
  23. # Ignore the lit config.
  24. continue
  25. basename, ext = os.path.splitext(f)
  26. if ext == ".carbon":
  27. tests.add(basename)
  28. else:
  29. sys.exit("Unrecognized file type in testdata: %s" % f)
  30. return tests
  31. def _update_check(test):
  32. """Updates the CHECK: lines for `test` by running executable_semantics."""
  33. test_file = "%s/%s.carbon" % (_TESTDATA, test)
  34. with open(test_file) as f:
  35. orig_lines = f.readlines()
  36. if _AUTOUPDATE_MARKER not in orig_lines:
  37. raise ValueError("No autoupdate marker in %s" % test_file)
  38. # Run executable_semantics to general output.
  39. # (`bazel run` would serialize)
  40. p = subprocess.run(
  41. ["%s/executable_semantics" % _BINDIR, test_file],
  42. stdout=subprocess.PIPE,
  43. stderr=subprocess.STDOUT,
  44. )
  45. out = p.stdout.decode("utf-8")
  46. # `lit` uses full paths to the test file, so use a regex to ignore paths
  47. # when used.
  48. # TODO: Maybe revisit and see if lit can be convinced to give a
  49. # root-relative path.
  50. out = out.replace(test_file, "{{.*}}/%s.carbon" % test)
  51. # Remove old OUT.
  52. lines_without_check = [
  53. x for x in orig_lines if not x.startswith("// CHECK:")
  54. ]
  55. autoupdate_index = lines_without_check.index(_AUTOUPDATE_MARKER)
  56. assert autoupdate_index >= 0
  57. with open(test_file, "w") as f:
  58. f.writelines(lines_without_check[: autoupdate_index + 1])
  59. f.writelines(["// CHECK: %s\n" % x for x in out.splitlines()])
  60. f.writelines(lines_without_check[autoupdate_index + 1 :])
  61. print(".", end="", flush=True)
  62. def _update_checks():
  63. """Runs bazel to update CHECK: lines in lit tests."""
  64. # TODO: It may be helpful if a list of tests can be passed in args; would
  65. # want to use argparse for this.
  66. tests = _get_tests()
  67. # Build all tests at once in order to allow parallel updates.
  68. print("Building executable_semantics...")
  69. subprocess.check_call(["bazel", "build", "//executable_semantics"])
  70. print("Updating %d lit tests..." % len(tests))
  71. with futures.ThreadPoolExecutor() as exec:
  72. # list() iterates to propagate exceptions.
  73. list(exec.map(_update_check, tests))
  74. # Each update call indicates progress with a dot without a newline, so put a
  75. # newline to wrap.
  76. print("\nUpdated lit tests.")
  77. def main():
  78. # Go to the repository root so that paths will match bazel's view.
  79. os.chdir(os.path.join(os.path.dirname(__file__), ".."))
  80. _update_checks()
  81. if __name__ == "__main__":
  82. main()