Files
carbon-lang/toolchain/install/install_test.py
T
Chandler CarruthandDana Jansens 26ddada3ae Switch from Bazel platforms to build settings (#7052)
The runtimes and bootstrap Bazel logic was previously built around
defining custom Bazel platforms constrained with `constraint_settings`.
The use of platforms added significant complexity, including the need to
"save" and "restore" the original platform, and other complexity
stemming from changing the platform as a whole.

This PR switches to use the simpler tool of build settings, and
`target_settings` on the toolchain rather than platform compatibility.
This remove the entire need to save and restore the platform, and also
generally simplifies things.

This PR also fixes some bugs in the bootstrap that were hidden by the
use of platforms, such as the need to carefully manage the different
inputs to the runtimes build so that generated inputs pick up the
correct exec configuration -- the exec transition happened to do this
"automatically", but it seems better to handle explicitly. And it cleans
up an extraneous copy of `carbon_runtimes.bzl` that snuck in somehow.

Assisted-by: Antigravity with Gemini

---------

Co-authored-by: Dana Jansens <danakj@orodu.net>
2026-04-14 21:52:48 +00:00

214 lines
7.7 KiB
Python

#!/usr/bin/env python3
"""Integration tests for the Carbon toolchain installation."""
__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 pathlib import Path
import subprocess
import os
import platform
import sys
import textwrap
import unittest
from bazel_tools.tools.python.runfiles import runfiles
class InstallTest(unittest.TestCase):
def setUp(self) -> None:
# The install root is adjacent to the test script
self.install_root = Path(sys.argv[0]).parent
self.tmpdir = Path(os.environ["TEST_TMPDIR"])
self.test_o_file = self.tmpdir / "test.o"
self.test_o_file.touch()
self.runfiles = runfiles.Create()
self.prebuilt_runtimes = self.runfiles.Rlocation(
"carbon/toolchain/install/carbon_stage1_runtimes_build"
)
def get_link_cmd(self, clang: Path) -> list[str | Path]:
return [
clang,
# Verbose printing to help with debugging.
"-v",
# Pass a parameter to the underlying Carbon busybox using `-Xcarbon`
# to switch it to use the prebuilt runtimes rather than building
# runtimes on demand.
f"-Xcarbon=--prebuilt-runtimes={self.prebuilt_runtimes}",
# Print out the link command rather than running it.
"-###",
# Give the link command an output.
"-o",
self.tmpdir / "test",
# A test input file. This won't be read though.
self.test_o_file,
]
def unsupported(self, stderr: str) -> None:
self.fail(f"Unsupported platform '{platform.uname()}':\n{stderr}")
# Note that we can't test `clang` vs. `clang++` portably. The only commands
# with useful differences are _link_ commands, and those need to build
# runtime libraries on demand, which requires the host to be able to compile
# and link for the target. Instead, we test linking with the default target
# (the host), as that is the one that should reliably work if we're
# developing Carbon, and encode all the different platform results in the
# test expectations.
def test_clang(self) -> None:
bin = self.install_root / "llvm/bin/clang"
# Most errors are caught by ensuring the command succeeds.
run = subprocess.run(
self.get_link_cmd(bin), check=True, capture_output=True, text=True
)
# Also ensure that it correctly didn't imply a C++ link.
self.assertNotRegex(run.stderr, r'"-lc\+\+"')
self.assertNotRegex(run.stderr, r'"-lstdc\+\+"')
# Note that we can't test `clang` vs. `clang++` portably. See the comment on
# `test_clang` for details.
def test_clangplusplus(self) -> None:
bin = self.install_root / "llvm/bin/clang++"
run = subprocess.run(
self.get_link_cmd(bin), check=True, capture_output=True, text=True
)
# Ensure that this binary _does_ imply a C++ link. Also ensure it uses
# `libc++`, as we default our Clang to use that on all platforms.
self.assertRegex(run.stderr, r'"-lc\+\+"')
def test_clang_cl(self) -> None:
bin = self.install_root / "llvm/bin/clang-cl"
run = subprocess.run(
# Use the `cl.exe`-specific help flag to test the mode.
[bin, "/?"],
check=True,
capture_output=True,
text=True,
)
# This should print the help string, including `cl.exe` specifics.
self.assertRegex(run.stdout, r"CL.EXE COMPATIBILITY OPTIONS:")
def test_clang_cpp(self) -> None:
# Note that this is a test of the C-preprocessor mode, not C++ mode.
# Create a test file that we'll preprocess.
text_file = self.tmpdir / "test.txt"
with open(text_file, "w") as f:
f.write("TEST\n")
# Run the preprocessor using a CPP-specific command line reading from
# the test file and writing to stdout. We define a macro that we'll
# check is expanded.
bin = self.install_root / "llvm/bin/clang-cpp"
try:
run = subprocess.run(
[bin, "-D", "TEST=SUCCESS", text_file, "-"],
check=True,
capture_output=True,
text=True,
)
except subprocess.CalledProcessError as err:
print(err.stderr, file=sys.stderr)
raise
self.assertEqual(run.stderr, "")
self.assertRegex(run.stdout, r"(^|\n)SUCCESS\n")
def run_carbon_test(
self, name: str, source: str, use_prebuilt: bool, expected_output: str
) -> None:
src_file = self.tmpdir / f"{name}.carbon"
src_file.write_text(textwrap.dedent(source).lstrip())
output_bin = self.tmpdir / name
carbon = self.runfiles.Rlocation(
"carbon/toolchain/install/carbon-busybox"
)
try:
obj_file = self.tmpdir / f"{name}.o"
subprocess.run(
[carbon, "compile", f"--output={obj_file}", src_file],
check=True,
capture_output=True,
text=True,
)
link_cmd = [carbon]
if use_prebuilt:
link_cmd.append(f"--prebuilt-runtimes={self.prebuilt_runtimes}")
link_cmd.extend(["link", f"--output={output_bin}", obj_file])
subprocess.run(link_cmd, check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as err:
self.fail(f"Subprocess failed: {err.stderr}")
run = subprocess.run(
[output_bin], check=True, capture_output=True, text=True
)
self.assertEqual(run.returncode, 0)
self.assertEqual(run.stdout.strip(), expected_output)
def run_cpp_test(
self, name: str, source: str, use_prebuilt: bool, expected_output: str
) -> None:
src_file = self.tmpdir / f"{name}.cpp"
src_file.write_text(textwrap.dedent(source).lstrip())
output_bin = self.tmpdir / name
clang = self.install_root / "llvm/bin/clang++"
try:
cmd = [clang, f"-o{output_bin}", src_file]
if use_prebuilt:
cmd.append(
f"-Xcarbon=--prebuilt-runtimes={self.prebuilt_runtimes}"
)
subprocess.run(cmd, check=True, capture_output=True, text=True)
except subprocess.CalledProcessError as err:
self.fail(f"Subprocess failed: {err.stderr}")
run = subprocess.run(
[output_bin], check=True, capture_output=True, text=True
)
self.assertEqual(run.returncode, 0)
self.assertEqual(run.stdout.strip(), expected_output)
def test_carbon_end_to_end(self) -> None:
source = r"""
import Cpp library "<iostream>";
fn Run() -> i32 {
Cpp.std.cout << "Hello from Carbon\n";
return 0;
}
"""
for use_prebuilt in [True, False]:
with self.subTest(use_prebuilt=use_prebuilt):
self.run_carbon_test(
"simple_carbon", source, use_prebuilt, "Hello from Carbon"
)
def test_cpp_end_to_end(self) -> None:
source = r"""
#include <iostream>
int main() {
std::cout << "Hello from C++" << std::endl;
return 0;
}
"""
for use_prebuilt in [True, False]:
with self.subTest(use_prebuilt=use_prebuilt):
self.run_cpp_test(
"simple_cpp", source, use_prebuilt, "Hello from C++"
)
if __name__ == "__main__":
unittest.main()