| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- #!/usr/bin/env python3
- """Updates the CHECK: lines in lit tests based on the AUTOUPDATE line."""
- __copyright__ = """
- Part of the Carbon Language project, under the Apache License v2.0 with LLVM
- Exceptions. See /LICENSE for license information.
- SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- """
- from concurrent import futures
- import os
- import subprocess
- import sys
- from typing import Set
- _BIN = "./bazel-bin/executable_semantics/executable_semantics"
- _TESTDATA = "executable_semantics/testdata"
- # A prefix followed by a command to run for autoupdating checked output.
- _AUTOUPDATE_MARKER = "// AUTOUPDATE: "
- # Indicates no autoupdate is requested.
- _NOAUTOUPDATE_MARKER = "// NOAUTOUPDATE"
- def _get_tests() -> Set[str]:
- """Get the list of tests from the filesystem."""
- tests = set()
- for root, _, files in os.walk(_TESTDATA):
- for f in files:
- if f in {"lit.cfg.py", "BUILD"}:
- # Ignore the lit config.
- continue
- if os.path.splitext(f)[1] == ".carbon":
- tests.add(os.path.join(root, f))
- else:
- sys.exit("Unrecognized file type in testdata: %s" % f)
- return tests
- def _update_check_once(test: str) -> bool:
- """Updates the CHECK: lines for `test` by running executable_semantics.
- Returns True if the number of lines changes.
- """
- with open(test) as f:
- orig_lines = f.readlines()
- # Remove old OUT.
- lines_without_check = [
- x for x in orig_lines if not x.startswith("// CHECK")
- ]
- num_orig_check_lines = len(orig_lines) - len(lines_without_check)
- autoupdate_index = None
- noautoupdate_index = None
- for line_index, line in enumerate(lines_without_check):
- if line.startswith(_AUTOUPDATE_MARKER):
- autoupdate_index = line_index
- autoupdate_cmd = line[len(_AUTOUPDATE_MARKER) :]
- if line.startswith(_NOAUTOUPDATE_MARKER):
- noautoupdate_index = line_index
- if autoupdate_index is None:
- if noautoupdate_index is None:
- raise ValueError(
- "%s must have either '%s' or '%s'"
- % (test, _AUTOUPDATE_MARKER, _NOAUTOUPDATE_MARKER)
- )
- else:
- return False
- elif noautoupdate_index is not None:
- raise ValueError(
- "%s has both '%s' and '%s', must have only one"
- % (test, _AUTOUPDATE_MARKER, _NOAUTOUPDATE_MARKER)
- )
- # Mirror lit.cfg.py substitutions; bazel runs don't need --prelude.
- autoupdate_cmd = autoupdate_cmd.replace("%{executable_semantics}", _BIN)
- # Run the autoupdate command to generate output.
- # (`bazel run` would serialize)
- p = subprocess.run(
- autoupdate_cmd % test,
- shell=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- )
- out = p.stdout.decode("utf-8")
- # `lit` uses full paths to the test file, so use a regex to ignore paths
- # when used.
- # TODO: Maybe revisit and see if lit can be convinced to give a
- # root-relative path.
- out = out.replace(test, "{{.*}}/%s" % test)
- out_lines = out.splitlines()
- # Interleave the new CHECK: lines with the tested content.
- with open(test, "w") as f:
- f.writelines(lines_without_check[: autoupdate_index + 1])
- for line in out_lines:
- line = line.rstrip()
- if line:
- f.write("// CHECK: %s\n" % line)
- else:
- f.write("// CHECK-EMPTY:\n")
- f.writelines(lines_without_check[autoupdate_index + 1 :])
- # Compares the number of CHECK: lines originally with the number added.
- return num_orig_check_lines != len(out_lines)
- def _update_check(test: str) -> None:
- """Wraps CHECK: updates for test files."""
- if _update_check_once(test):
- # If the number of output lines changes, run again because output can be
- # line-specific. However, output should stabilize quickly.
- if _update_check_once(test):
- raise ValueError("The output of %s kept changing" % test)
- print(".", end="", flush=True)
- def _update_checks() -> None:
- """Runs bazel to update CHECK: lines in lit tests."""
- # TODO: It may be helpful if a list of tests can be passed in args; would
- # want to use argparse for this.
- tests = _get_tests()
- # Build all tests at once in order to allow parallel updates.
- print("Building executable_semantics...")
- subprocess.check_call(["bazel", "build", "//executable_semantics"])
- print("Updating %d lit tests..." % len(tests))
- with futures.ThreadPoolExecutor() as exec:
- # list() iterates to propagate exceptions.
- list(exec.map(_update_check, tests))
- # Each update call indicates progress with a dot without a newline, so put a
- # newline to wrap.
- print("\nUpdated lit tests.")
- def main() -> None:
- # Go to the repository root so that paths will match bazel's view.
- os.chdir(os.path.join(os.path.dirname(__file__), ".."))
- _update_checks()
- if __name__ == "__main__":
- main()
|