mirror of
https://github.com/esphome/esphome.git
synced 2026-10-18 12:12:48 +00:00
[ci] Run the C++ unit tests on macOS without a sanitizer whose runtime aborts (#21000)
This commit is contained in:
+53
-4
@@ -3,7 +3,9 @@ import argparse
|
||||
from functools import partial
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from build_helpers import (
|
||||
PLATFORMIO_GOOGLE_TEST_LIB,
|
||||
@@ -12,6 +14,9 @@ from build_helpers import (
|
||||
)
|
||||
from helpers import get_all_components, root_path
|
||||
|
||||
from esphome.core import EsphomeError
|
||||
from esphome.host.toolchain import find_compilers
|
||||
|
||||
# Path to /tests/components
|
||||
COMPONENTS_TESTS_DIR: Path = Path(root_path) / "tests" / "components"
|
||||
|
||||
@@ -20,11 +25,55 @@ BUILD_FLAGS = [
|
||||
"-g3", # max debug info
|
||||
"-ggdb3",
|
||||
"-DESPHOME_DEBUG", # enable debug assertions
|
||||
# Enable the address and undefined behavior sanitizers
|
||||
"-fsanitize=address",
|
||||
"-fsanitize=undefined",
|
||||
"-fno-omit-frame-pointer",
|
||||
]
|
||||
SANITIZER_FLAGS = ["-fsanitize=address", "-fsanitize=undefined"]
|
||||
|
||||
|
||||
def sanitizer_flags() -> list[str]:
|
||||
"""The sanitizer flags to build with: all of them, except on macOS the ones whose runtime
|
||||
does not start. Apple clang 15's AddressSanitizer aborts before main() on macOS 26, which
|
||||
would fail every test for a toolchain problem, so each flag is tried on an empty program
|
||||
first. A compiler that cannot build the empty program is an error, not a dropped flag.
|
||||
CI keeps every flag.
|
||||
"""
|
||||
if sys.platform != "darwin":
|
||||
return SANITIZER_FLAGS
|
||||
try:
|
||||
cxx = find_compilers().cxx
|
||||
except EsphomeError as err:
|
||||
sys.exit(str(err))
|
||||
flags: list[str] = []
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
program = Path(tmp) / "probe"
|
||||
for flag in SANITIZER_FLAGS:
|
||||
compiled = subprocess.run(
|
||||
[*cxx, flag, "-x", "c++", "-", "-o", program],
|
||||
check=False,
|
||||
input=b"int main() { return 0; }",
|
||||
capture_output=True,
|
||||
)
|
||||
if compiled.returncode != 0:
|
||||
sys.exit(
|
||||
f"{cxx[0]} cannot build an empty program with {flag}:\n"
|
||||
f"{compiled.stderr.decode(errors='replace').strip()}"
|
||||
)
|
||||
ran = subprocess.run([program], check=False, capture_output=True)
|
||||
if ran.returncode == 0:
|
||||
flags.append(flag)
|
||||
continue
|
||||
lines = [
|
||||
line
|
||||
for line in ran.stderr.decode(errors="replace").splitlines()
|
||||
if line.strip()
|
||||
]
|
||||
print(
|
||||
f"{flag} aborts at start with {cxx[0]}: "
|
||||
f"{lines[0] if lines else f'exit code {ran.returncode}'}\n"
|
||||
" building the tests without it; set CXX to a compiler whose runtime works to keep it",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return flags
|
||||
|
||||
|
||||
def run_tests(selected_components: list[str]) -> int:
|
||||
@@ -39,7 +88,7 @@ def run_tests(selected_components: list[str]) -> int:
|
||||
config_prefix="cpptests",
|
||||
friendly_name="CPP Unit Tests",
|
||||
libraries=PLATFORMIO_GOOGLE_TEST_LIB,
|
||||
build_flags=BUILD_FLAGS,
|
||||
build_flags=BUILD_FLAGS + sanitizer_flags(),
|
||||
main_entry="main.cpp",
|
||||
label="unit tests",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user