install_test.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. #!/usr/bin/env python3
  2. """Integration tests for the Carbon toolchain installation."""
  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 pathlib import Path
  9. import subprocess
  10. import os
  11. import platform
  12. import sys
  13. import textwrap
  14. import unittest
  15. from bazel_tools.tools.python.runfiles import runfiles
  16. class InstallTest(unittest.TestCase):
  17. def setUp(self) -> None:
  18. # The install root is adjacent to the test script
  19. self.install_root = Path(sys.argv[0]).parent
  20. self.tmpdir = Path(os.environ["TEST_TMPDIR"])
  21. self.test_o_file = self.tmpdir / "test.o"
  22. self.test_o_file.touch()
  23. self.runfiles = runfiles.Create()
  24. self.prebuilt_runtimes = self.runfiles.Rlocation(
  25. "carbon/toolchain/install/carbon_stage1_runtimes_build"
  26. )
  27. def get_link_cmd(self, clang: Path) -> list[str | Path]:
  28. return [
  29. clang,
  30. # Verbose printing to help with debugging.
  31. "-v",
  32. # Pass a parameter to the underlying Carbon busybox using `-Xcarbon`
  33. # to switch it to use the prebuilt runtimes rather than building
  34. # runtimes on demand.
  35. f"-Xcarbon=--prebuilt-runtimes={self.prebuilt_runtimes}",
  36. # Print out the link command rather than running it.
  37. "-###",
  38. # Give the link command an output.
  39. "-o",
  40. self.tmpdir / "test",
  41. # A test input file. This won't be read though.
  42. self.test_o_file,
  43. ]
  44. def unsupported(self, stderr: str) -> None:
  45. self.fail(f"Unsupported platform '{platform.uname()}':\n{stderr}")
  46. # Note that we can't test `clang` vs. `clang++` portably. The only commands
  47. # with useful differences are _link_ commands, and those need to build
  48. # runtime libraries on demand, which requires the host to be able to compile
  49. # and link for the target. Instead, we test linking with the default target
  50. # (the host), as that is the one that should reliably work if we're
  51. # developing Carbon, and encode all the different platform results in the
  52. # test expectations.
  53. def test_clang(self) -> None:
  54. bin = self.install_root / "llvm/bin/clang"
  55. # Most errors are caught by ensuring the command succeeds.
  56. run = subprocess.run(
  57. self.get_link_cmd(bin), check=True, capture_output=True, text=True
  58. )
  59. # Also ensure that it correctly didn't imply a C++ link.
  60. self.assertNotRegex(run.stderr, r'"-lc\+\+"')
  61. self.assertNotRegex(run.stderr, r'"-lstdc\+\+"')
  62. # Note that we can't test `clang` vs. `clang++` portably. See the comment on
  63. # `test_clang` for details.
  64. def test_clangplusplus(self) -> None:
  65. bin = self.install_root / "llvm/bin/clang++"
  66. run = subprocess.run(
  67. self.get_link_cmd(bin), check=True, capture_output=True, text=True
  68. )
  69. # Ensure that this binary _does_ imply a C++ link. Also ensure it uses
  70. # `libc++`, as we default our Clang to use that on all platforms.
  71. self.assertRegex(run.stderr, r'"-lc\+\+"')
  72. def test_clang_cl(self) -> None:
  73. bin = self.install_root / "llvm/bin/clang-cl"
  74. run = subprocess.run(
  75. # Use the `cl.exe`-specific help flag to test the mode.
  76. [bin, "/?"],
  77. check=True,
  78. capture_output=True,
  79. text=True,
  80. )
  81. # This should print the help string, including `cl.exe` specifics.
  82. self.assertRegex(run.stdout, r"CL.EXE COMPATIBILITY OPTIONS:")
  83. def test_clang_cpp(self) -> None:
  84. # Note that this is a test of the C-preprocessor mode, not C++ mode.
  85. # Create a test file that we'll preprocess.
  86. text_file = self.tmpdir / "test.txt"
  87. with open(text_file, "w") as f:
  88. f.write("TEST\n")
  89. # Run the preprocessor using a CPP-specific command line reading from
  90. # the test file and writing to stdout. We define a macro that we'll
  91. # check is expanded.
  92. bin = self.install_root / "llvm/bin/clang-cpp"
  93. try:
  94. run = subprocess.run(
  95. [bin, "-D", "TEST=SUCCESS", text_file, "-"],
  96. check=True,
  97. capture_output=True,
  98. text=True,
  99. )
  100. except subprocess.CalledProcessError as err:
  101. print(err.stderr, file=sys.stderr)
  102. raise
  103. self.assertEqual(run.stderr, "")
  104. self.assertRegex(run.stdout, r"(^|\n)SUCCESS\n")
  105. def run_carbon_test(
  106. self, name: str, source: str, use_prebuilt: bool, expected_output: str
  107. ) -> None:
  108. src_file = self.tmpdir / f"{name}.carbon"
  109. src_file.write_text(textwrap.dedent(source).lstrip())
  110. output_bin = self.tmpdir / name
  111. carbon = self.runfiles.Rlocation(
  112. "carbon/toolchain/install/carbon-busybox"
  113. )
  114. try:
  115. obj_file = self.tmpdir / f"{name}.o"
  116. subprocess.run(
  117. [carbon, "compile", f"--output={obj_file}", src_file],
  118. check=True,
  119. capture_output=True,
  120. text=True,
  121. )
  122. link_cmd = [carbon]
  123. if use_prebuilt:
  124. link_cmd.append(f"--prebuilt-runtimes={self.prebuilt_runtimes}")
  125. link_cmd.extend(["link", f"--output={output_bin}", obj_file])
  126. subprocess.run(link_cmd, check=True, capture_output=True, text=True)
  127. except subprocess.CalledProcessError as err:
  128. self.fail(f"Subprocess failed: {err.stderr}")
  129. run = subprocess.run(
  130. [output_bin], check=True, capture_output=True, text=True
  131. )
  132. self.assertEqual(run.returncode, 0)
  133. self.assertEqual(run.stdout.strip(), expected_output)
  134. def run_cpp_test(
  135. self, name: str, source: str, use_prebuilt: bool, expected_output: str
  136. ) -> None:
  137. src_file = self.tmpdir / f"{name}.cpp"
  138. src_file.write_text(textwrap.dedent(source).lstrip())
  139. output_bin = self.tmpdir / name
  140. clang = self.install_root / "llvm/bin/clang++"
  141. try:
  142. cmd = [clang, f"-o{output_bin}", src_file]
  143. if use_prebuilt:
  144. cmd.append(
  145. f"-Xcarbon=--prebuilt-runtimes={self.prebuilt_runtimes}"
  146. )
  147. subprocess.run(cmd, check=True, capture_output=True, text=True)
  148. except subprocess.CalledProcessError as err:
  149. self.fail(f"Subprocess failed: {err.stderr}")
  150. run = subprocess.run(
  151. [output_bin], check=True, capture_output=True, text=True
  152. )
  153. self.assertEqual(run.returncode, 0)
  154. self.assertEqual(run.stdout.strip(), expected_output)
  155. def test_carbon_end_to_end(self) -> None:
  156. source = r"""
  157. import Cpp library "<iostream>";
  158. fn Run() -> i32 {
  159. Cpp.std.cout << "Hello from Carbon\n";
  160. return 0;
  161. }
  162. """
  163. for use_prebuilt in [True, False]:
  164. with self.subTest(use_prebuilt=use_prebuilt):
  165. self.run_carbon_test(
  166. "simple_carbon", source, use_prebuilt, "Hello from Carbon"
  167. )
  168. def test_cpp_end_to_end(self) -> None:
  169. source = r"""
  170. #include <iostream>
  171. int main() {
  172. std::cout << "Hello from C++" << std::endl;
  173. return 0;
  174. }
  175. """
  176. for use_prebuilt in [True, False]:
  177. with self.subTest(use_prebuilt=use_prebuilt):
  178. self.run_cpp_test(
  179. "simple_cpp", source, use_prebuilt, "Hello from C++"
  180. )
  181. if __name__ == "__main__":
  182. unittest.main()