Skip to content

Commit 8660fca

Browse files
authored
[ESI][Runtime] Move runtime C++ unit tests (#9966)
Moves the ESI runtime C++ unit tests out of CIRCT’s `unittests/` tree and into the standalone ESI runtime CMake project, then runs them via a pytest wrapper that executes individual GoogleTest cases.
1 parent a86e5e3 commit 8660fca

File tree

11 files changed

+206
-22
lines changed

11 files changed

+206
-22
lines changed

.github/workflows/testPycdeESI.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,5 @@ jobs:
131131
export PYTHONPATH="$PWD/build/tools/circt/python_packages/pycde:$PWD/build/tools/circt/lib/Dialect/ESI/runtime/python"
132132
export PATH="$PWD/build/bin:$PATH"
133133
export LD_LIBRARY_PATH="$PWD/build/lib:$LD_LIBRARY_PATH"
134+
ninja -C build ESIRuntimeCppTests -j$(nproc)
134135
python3 -m pytest lib/Dialect/ESI/runtime/tests/ -v --log-cli-level=INFO

lib/Dialect/ESI/runtime/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,3 +458,9 @@ install(FILES ${ESICppRuntimeBackendHeaders}
458458
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/esi/backends
459459
COMPONENT ESIRuntime
460460
)
461+
462+
# C++ unit tests (self-contained, no CIRCT/LLVM dependency).
463+
option(BUILD_TESTING "Build ESI runtime C++ unit tests" ON)
464+
if(BUILD_TESTING)
465+
add_subdirectory(tests/cpp)
466+
endif()

lib/Dialect/ESI/runtime/tests/conftest.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
# See https://llvm.org/LICENSE.txt for license information.
33
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
44

5+
from pathlib import Path
6+
57
collect_ignore_glob = [
68
"integration/hw/*.py",
79
]
10+
11+
12+
def get_runtime_root() -> Path:
13+
import esiaccel
14+
return Path(esiaccel.__file__).resolve().parent.parent.parent
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
##===- CMakeLists.txt - ESI runtime C++ unit tests --------*- cmake -*-===//
2+
##
3+
## Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
## See https://llvm.org/LICENSE.txt for license information.
5+
## SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
##
7+
##===----------------------------------------------------------------------===//
8+
##
9+
## Self-contained C++ unit tests for the ESI runtime. Built as part of the
10+
## ESI runtime CMake project without any dependency on CIRCT or LLVM.
11+
## GoogleTest is re-used from the parent build when available, found via
12+
## find_package, or fetched from GitHub as a last resort.
13+
##
14+
##===----------------------------------------------------------------------===//
15+
16+
# --- Locate GoogleTest ------------------------------------------------------
17+
# 1. LLVM/CIRCT parent build provides bare `gtest_main` — create aliases.
18+
# 2. System / user install via find_package.
19+
# 3. FetchContent download (standalone builds).
20+
21+
if(TARGET gtest_main AND NOT TARGET GTest::gtest_main)
22+
add_library(GTest::gtest_main ALIAS gtest_main)
23+
add_library(GTest::gtest ALIAS gtest)
24+
endif()
25+
26+
if(NOT TARGET GTest::gtest_main)
27+
find_package(GTest QUIET)
28+
endif()
29+
30+
if(NOT TARGET GTest::gtest_main)
31+
include(FetchContent)
32+
FetchContent_Declare(
33+
googletest
34+
GIT_REPOSITORY https://github.com/google/googletest.git
35+
GIT_TAG v1.15.2
36+
EXCLUDE_FROM_ALL
37+
)
38+
# Prevent overriding the parent project's compiler/linker settings on Windows.
39+
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
40+
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
41+
FetchContent_MakeAvailable(googletest)
42+
endif()
43+
44+
set(CMAKE_CXX_STANDARD 20)
45+
set(CMAKE_CXX_STANDARD_REQUIRED YES)
46+
47+
add_executable(ESIRuntimeCppTests
48+
ESIRuntimeTest.cpp
49+
ESIRuntimeValuesTest.cpp
50+
ESIRuntimeTypedPortsTest.cpp
51+
)
52+
53+
# Place the binary where the pytest wrapper expects it — under
54+
# <runtime-build-root>/tests/cpp/ — rather than the global bin/ directory.
55+
set_target_properties(ESIRuntimeCppTests PROPERTIES
56+
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
57+
58+
target_link_libraries(ESIRuntimeCppTests PRIVATE
59+
ESICppRuntime
60+
GTest::gtest_main
61+
)
62+
63+
if(NOT MSVC)
64+
target_link_options(ESIRuntimeCppTests PRIVATE -pthread)
65+
endif()
File renamed without changes.

unittests/Dialect/ESI/runtime/ESIRuntimeTypedPortsTest.cpp renamed to lib/Dialect/ESI/runtime/tests/cpp/ESIRuntimeTypedPortsTest.cpp

File renamed without changes.

unittests/Dialect/ESI/runtime/ESIRuntimeValuesTest.cpp renamed to lib/Dialect/ESI/runtime/tests/cpp/ESIRuntimeValuesTest.cpp

File renamed without changes.

lib/Dialect/ESI/runtime/tests/integration/conftest.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,7 @@
1818
HW_DIR = ROOT_DIR / "hw"
1919
SW_DIR = ROOT_DIR / "sw"
2020

21-
22-
def get_runtime_root() -> Path:
23-
import esiaccel
24-
return Path(esiaccel.__file__).resolve().parent.parent.parent
21+
from tests.conftest import get_runtime_root # noqa: F401 – re-exported
2522

2623

2724
def require_tool(tool: str) -> None:
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
2+
# See https://llvm.org/LICENSE.txt for license information.
3+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
"""
5+
Pytest wrapper that exposes each GoogleTest case inside ESIRuntimeCppTests as
6+
an individual pytest item.
7+
8+
The binary is located by (in order):
9+
1. The ``ESI_RUNTIME_TESTS_BIN`` environment variable (explicit override).
10+
2. ``tests/cpp/ESIRuntimeCppTests`` relative to the ESI runtime root, which
11+
is derived from the ``esiaccel`` package location — the same convention
12+
used by the integration tests (three ``parent`` levels up from
13+
``esiaccel.__file__``).
14+
15+
The entire module is skipped when the binary cannot be found.
16+
"""
17+
18+
from __future__ import annotations
19+
20+
import os
21+
import subprocess
22+
import sys
23+
from pathlib import Path
24+
25+
import pytest
26+
27+
from tests.conftest import get_runtime_root
28+
29+
# ---------------------------------------------------------------------------
30+
# Locate the test binary
31+
# ---------------------------------------------------------------------------
32+
33+
_BIN_NAME = "ESIRuntimeCppTests" + (".exe" if sys.platform == "win32" else "")
34+
35+
36+
def _find_binary() -> Path | None:
37+
# 1. Explicit override.
38+
env = os.environ.get("ESI_RUNTIME_TESTS_BIN")
39+
if env:
40+
p = Path(env)
41+
if p.is_file():
42+
return p
43+
44+
# 2. Relative to the ESI runtime root derived from the esiaccel package —
45+
# the same convention used by the integration tests. Works when the
46+
# standalone runtime build's esiaccel is the active one on PYTHONPATH.
47+
try:
48+
candidate = get_runtime_root() / "tests" / "cpp" / _BIN_NAME
49+
if candidate.is_file():
50+
return candidate
51+
except ImportError:
52+
pass
53+
54+
return None
55+
56+
57+
_BINARY = _find_binary()
58+
59+
if _BINARY is None:
60+
pytest.skip(
61+
"ESIRuntimeCppTests binary not found – make sure you've built that "
62+
"target and/or set ESI_RUNTIME_TESTS_BIN",
63+
allow_module_level=True,
64+
)
65+
66+
# ---------------------------------------------------------------------------
67+
# Enumerate gtest cases
68+
# ---------------------------------------------------------------------------
69+
70+
71+
def _list_tests() -> list[str]:
72+
"""Return a list of 'Suite.TestName' strings from --gtest_list_tests."""
73+
result = subprocess.run(
74+
[str(_BINARY), "--gtest_list_tests"],
75+
capture_output=True,
76+
text=True,
77+
)
78+
if result.returncode != 0:
79+
pytest.fail(
80+
f"Failed to list gtest cases (rc={result.returncode}):\n"
81+
f"--- stdout ---\n{result.stdout}\n"
82+
f"--- stderr ---\n{result.stderr}",
83+
pytrace=False,
84+
)
85+
tests: list[str] = []
86+
suite = ""
87+
for line in result.stdout.splitlines():
88+
# Suite header line ends with '.', e.g. "ESITypesTest."
89+
# Use a non-indent check so parameterized suites like "Suite/0." also match.
90+
if not line.startswith(" ") and line.rstrip().endswith("."):
91+
suite = line.strip()
92+
# Individual test line is indented, e.g. " VoidTypeSerialization"
93+
elif line.startswith(" "):
94+
test_name = line.strip().split()[0] # strip trailing comments
95+
if suite and test_name:
96+
tests.append(f"{suite}{test_name}")
97+
return tests
98+
99+
100+
_ALL_TESTS = _list_tests()
101+
102+
# ---------------------------------------------------------------------------
103+
# Parametrize
104+
# ---------------------------------------------------------------------------
105+
106+
107+
def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
108+
if "gtest_case" in metafunc.fixturenames:
109+
metafunc.parametrize("gtest_case", _ALL_TESTS, ids=_ALL_TESTS)
110+
111+
112+
def test_cpp_runtime(gtest_case: str) -> None:
113+
result = subprocess.run(
114+
[str(_BINARY), f"--gtest_filter={gtest_case}"],
115+
capture_output=True,
116+
text=True,
117+
)
118+
if result.returncode != 0:
119+
pytest.fail(
120+
f"gtest case '{gtest_case}' failed:\n"
121+
f"--- stdout ---\n{result.stdout}\n"
122+
f"--- stderr ---\n{result.stderr}",
123+
pytrace=False,
124+
)

unittests/Dialect/ESI/CMakeLists.txt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,5 @@ target_link_libraries(CIRCTESITests
88
CIRCTESI
99
)
1010

11-
if(ESI_RUNTIME)
12-
add_subdirectory(runtime)
13-
endif()
11+
# ESI runtime C++ unit tests are built inside the runtime CMake project
12+
# (lib/Dialect/ESI/runtime/tests/cpp) and run via pytest.

0 commit comments

Comments
 (0)