CIRCT 23.0.0git
Loading...
Searching...
No Matches
conftest.py
Go to the documentation of this file.
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
5from __future__ import annotations
6
7import os
8from pathlib import Path
9import logging
10import shutil
11import subprocess
12from typing import Optional
13
14import pytest
15
16_logger = logging.getLogger(__name__)
17
18ROOT_DIR = Path(__file__).resolve().parent
19HW_DIR = ROOT_DIR / "hw"
20SW_DIR = ROOT_DIR / "sw"
21
22from tests.conftest import get_runtime_root # noqa: F401 – re-exported
23
24
25def require_tool(tool: str) -> None:
26 if shutil.which(tool) is None:
27 pytest.skip(f"Required tool not found in PATH: {tool}")
28
29
30def require_env(var_name: str) -> str:
31 value = os.environ.get(var_name)
32 if not value:
33 pytest.skip(f"Required environment variable not set: {var_name}")
34 return value
35
36
37def run_cmd(cmd, **kwargs) -> str:
38 """Run a command, capture stdout, and return it. Raises on failure."""
39 _logger.info("run_cmd: %s", cmd)
40 result = subprocess.run(cmd,
41 check=True,
42 capture_output=True,
43 text=True,
44 **kwargs)
45 _logger.debug("stdout: %s", result.stdout)
46 _logger.debug("stderr: %s", result.stderr)
47 return result.stdout
48
49
50def check_lines(stdout: str, expected: list[str]) -> None:
51 """Assert that every expected substring appears in stdout in order."""
52 remaining = stdout
53 for line in expected:
54 idx = remaining.find(line)
55 assert idx >= 0, \
56 f"Expected output not found: {line!r}"
57 remaining = remaining[idx + len(line):]
58
59
60def _runtime_env() -> dict[str, str]:
61 """Return an environment which can load ESI runtime shared libraries."""
62 env = os.environ.copy()
63 if os.name != "nt":
64 return env
65
66 runtime_root = get_runtime_root()
67 dll_dirs = [
68 path for path in (runtime_root, runtime_root / "bin",
69 runtime_root / "lib") if path.exists()
70 ]
71 existing_path = env.get("PATH", "")
72 env["PATH"] = os.pathsep.join(str(path) for path in dll_dirs) + \
73 os.pathsep + existing_path
74 return env
75
76
77# ---------------------------------------------------------------------------
78# Shared C++ build helper
79# ---------------------------------------------------------------------------
80
81
82def build_cpp_test(sources_dir: Path,
83 target: str,
84 header_subdir: str,
85 build_subdir: Optional[str] = None) -> Path:
86 """Configure + build a C++ integration test target, returning the binary.
87
88 * ``sources_dir``: root provided by ``cosim_test`` (contains ``generated/``).
89 * ``target``: CMake target name (e.g. ``loopback_test``).
90 * ``header_subdir``: subdirectory under the include root where the generated
91 headers are copied (e.g. ``"loopback"`` or ``"test_codegen"``).
92 * ``build_subdir``: name for the build directory under ``sources_dir``.
93 Defaults to ``target``.
94
95 The configure step is skipped when the Ninja build file already exists;
96 ``cmake --build`` always runs so that CMake's own dependency tracking
97 picks up any source or generated-header changes.
98 """
99 require_tool("cmake")
100 require_tool("ninja")
101
102 if build_subdir is None:
103 build_subdir = target
104 build_dir = sources_dir / build_subdir
105 exe_suffix = ".exe" if os.name == "nt" else ""
106 binary = build_dir / (target + exe_suffix)
107
108 runtime_root = get_runtime_root()
109 include_dir = sources_dir / "cpp_include"
110 generated_dir = include_dir / header_subdir
111
112 if not (build_dir / "build.ninja").exists():
113 if build_dir.exists():
114 shutil.rmtree(build_dir)
115
116 generated_dir.mkdir(parents=True, exist_ok=True)
117
118 codegen_src = sources_dir / "generated"
119 if codegen_src.exists():
120 for item in codegen_src.iterdir():
121 if item.is_file():
122 shutil.copy(item, generated_dir)
123
124 result = subprocess.run(
125 [
126 "cmake",
127 "-G",
128 "Ninja",
129 "-S",
130 str(SW_DIR),
131 "-B",
132 str(build_dir),
133 "-DCMAKE_BUILD_TYPE=Release",
134 f"-DLOOPBACK_GENERATED_DIR={include_dir}",
135 f"-DESI_RUNTIME_ROOT={runtime_root}",
136 ],
137 capture_output=True,
138 text=True,
139 )
140 assert result.returncode == 0, (
141 f"cmake configure failed (rc={result.returncode}):\n"
142 f"--- stdout ---\n{result.stdout}\n"
143 f"--- stderr ---\n{result.stderr}")
144
145 result = subprocess.run(
146 [
147 "cmake", "--build",
148 str(build_dir), "--target", target, "--config", "Release"
149 ],
150 capture_output=True,
151 text=True,
152 )
153 assert result.returncode == 0, (
154 f"cmake build failed (rc={result.returncode}):\n"
155 f"--- stdout ---\n{result.stdout}\n"
156 f"--- stderr ---\n{result.stderr}")
157
158 return binary
159
160
161def run_probe(binary: Path,
162 host: str,
163 port: int,
164 probe: str,
165 expected: Optional[list[str]] = None) -> str:
166 """Invoke ``binary --probe <probe>`` and assert it prints ``<probe> ok``.
167
168 If *expected* is given, those substrings are checked (in order) against
169 stdout instead of the default ``["<probe> ok"]``.
170
171 Returns the captured stdout for further assertions if needed.
172 """
173 result = subprocess.run(
174 [str(binary), "--probe", probe, "cosim", f"{host}:{port}"],
175 capture_output=True,
176 env=_runtime_env(),
177 text=True,
178 timeout=60,
179 )
180 assert result.returncode == 0, (
181 f"{binary.name} --probe {probe} failed (rc={result.returncode}):\n"
182 f"--- stdout ---\n{result.stdout}\n"
183 f"--- stderr ---\n{result.stderr}")
184 check_lines(result.stdout, expected or [f"{probe} ok"])
185 return result.stdout
static mlir::Operation * resolve(Context &context, mlir::SymbolRefAttr sym)
dict[str, str] _runtime_env()
Definition conftest.py:60
str require_env(str var_name)
Definition conftest.py:30