4"""Pytest integration for ESI cosimulation tests.
6Provides the ``@cosim_test`` decorator which automates the full lifecycle of a
7cosimulation test: running a PyCDE hardware script, compiling the design with
8a simulator (e.g. Verilator), launching the simulator, injecting connection
9parameters into the test function, and tearing everything down afterwards.
11Decorated functions run in an isolated child process (via ``fork``) so that
12simulator state never leaks between tests. When applied to a class, the
13hardware compilation is performed once and shared across all ``test_*`` methods.
17 from esiaccel.cosim.pytest import cosim_test
19 @cosim_test("path/to/hw_script.py")
20 def test_my_design(conn: AcceleratorConnection):
21 # conn is already connected to the running simulator
25from __future__
import annotations
28from dataclasses
import dataclass, field, replace
33import multiprocessing.connection
35from pathlib
import Path
43from typing
import Any, Callable, Dict, Optional, Pattern, Sequence, Union
49from .simulator
import (available_simulators, get_simulator,
50 is_simulator_available, load_macro_definitions,
51 Simulator, SourceFiles)
53LogMatcher = Union[str, Pattern[str], Callable[[str, str], bool]]
54SourceGeneratorFunc = Callable[[
"CosimPytestConfig", Path], Path]
55SourceGeneratorArg = Union[str, Path, SourceGeneratorFunc]
57_logger = logging.getLogger(
"esiaccel.cosim.pytest")
58_DEFAULT_FAILURE_PATTERN = re.compile(
r"\berror\b", re.IGNORECASE)
59_DEFAULT_WARN_PATTERN = re.compile(
r"\bwarn(ing)?\b", re.IGNORECASE)
62_DEFAULT_TIMEOUT_S: float = 120.0
66 """Read a boolean environment variable.
69 var_name: Name of the environment variable to read.
70 default: Default value if the variable is not set.
73 The boolean value of the environment variable, or the default value.
75 value = os.environ.get(var_name)
78 return value.lower()
in (
"true",
"1",
"yes",
"on")
82 """Read a path environment variable.
85 var_name: Name of the environment variable to read.
88 The path value of the environment variable, or None if not set.
90 value = os.environ.get(var_name)
91 return Path(value)
if value
else None
95 """Get a unique identifier for this pytest invocation.
97 For normal runs, uses the current process PID (the pytest process itself).
98 For xdist workers, uses the parent PID (the main pytest controller) so that
99 all workers share the same top-level run directory.
101 if os.environ.get(
"PYTEST_XDIST_WORKER"):
103 return f
"pytest-{os.getppid()}"
104 return f
"pytest-{os.getpid()}"
108 """Get the xdist worker ID if running under pytest-xdist.
110 Returns the worker ID (e.g., "gw0", "gw1") or None if not using xdist.
112 return os.environ.get(
"PYTEST_XDIST_WORKER")
116 """Build a test directory path including pytest run ID and xdist worker (if present).
118 For class-based tests with xdist: "pytest-{pid}/gw{n}/ClassName/test_method".
119 For class-based tests without xdist: "pytest-{pid}/ClassName/test_method".
120 For function tests with xdist: "pytest-{pid}/gw{n}/test_function".
121 For function tests without xdist: "pytest-{pid}/test_function".
125 if config.pytest_run_id:
126 parts.append(config.pytest_run_id)
128 if config.xdist_worker_id:
129 parts.append(config.xdist_worker_id)
131 if config.class_name:
132 parts.append(config.class_name)
135 parts.append(config.test_name)
138 return "unknown-test"
140 return "/".join(parts)
143@dataclass(frozen=True)
145 """Immutable configuration for a single cosim test or test class.
148 source_generator: Path to the PyCDE hardware generation script, or a
149 callable ``(config, tmp_dir) -> sources_dir``.
150 args: Arguments passed to the script; ``{tmp_dir}`` is interpolated.
151 simulator: Simulator backend name (e.g. ``"verilator"``).
152 top: Top-level module name for the simulator.
153 debug: If True, enable verbose simulator output.
154 timeout_s: Maximum wall-clock seconds before the test is killed.
155 failure_matcher: Pattern applied to simulator output to detect errors.
156 warning_matcher: Pattern applied to simulator output to detect warnings.
157 tmp_dir_root: Root directory for temporary directories. If None, uses system temp.
158 delete_tmp_dir: If True, delete temporary directories after test completion.
159 class_name: Name of the test class (for class-based tests). None for function tests.
160 test_name: Name of the test function or method.
161 pytest_run_id: Unique identifier for the pytest run (e.g., "pytest-12345").
162 xdist_worker_id: ID of the xdist worker if running under pytest-xdist (e.g., "gw0").
163 save_waveform: If True, dump waveform file. Format depends on backend. Requires debug=True.
164 macro_definitions: RTL macros to define during compilation. A value of None
165 defines the macro without assigning a value.
166 macro_definitions_file: JSON file of macro definitions to read. A relative
167 path is resolved against the generated sources directory, which is what
168 lets a source generator produce the file. `macro_definitions` is layered
169 on top of whatever it contains.
172 source_generator: SourceGeneratorArg
173 args: Sequence[str] = (
"{tmp_dir}",)
174 simulator: str =
"verilator"
175 top: str =
"ESI_Cosim_Top"
177 timeout_s: float = _DEFAULT_TIMEOUT_S
178 failure_matcher: Optional[LogMatcher] = _DEFAULT_FAILURE_PATTERN
179 warning_matcher: Optional[LogMatcher] = _DEFAULT_WARN_PATTERN
180 tmp_dir_root: Optional[Path] =
None
181 delete_tmp_dir: bool =
True
182 class_name: Optional[str] =
None
183 test_name: Optional[str] =
None
184 pytest_run_id: Optional[str] =
None
185 xdist_worker_id: Optional[str] =
None
186 save_waveform: bool =
False
187 macro_definitions: Optional[Dict[str, Optional[str]]] =
None
188 macro_definitions_file: Optional[Union[str, Path]] =
None
193 """Cached compilation artifacts shared across methods of a test class."""
201 """Outcome of a child-process test execution, passed back via queue."""
205 failure_lines: Sequence[str] = field(default_factory=list)
206 warning_lines: Sequence[str] = field(default_factory=list)
207 stdout_lines: Sequence[str] = field(default_factory=list)
208 stderr_lines: Sequence[str] = field(default_factory=list)
211@contextlib.contextmanager
213 """Context manager that temporarily changes the working directory."""
223 """Return True if *line* matches the given matcher.
225 The matcher may be a plain string (regex search), a compiled regex,
226 or a callable ``(line, stream) -> bool``.
228 if isinstance(matcher, str):
229 return bool(re.search(matcher, line, re.IGNORECASE))
230 elif isinstance(matcher, re.Pattern):
231 return bool(matcher.search(line))
233 return matcher(line, stream)
237 stdout_lines: Sequence[str],
238 stderr_lines: Sequence[str],
239 config: CosimPytestConfig,
240) -> tuple[list[str], list[str]]:
241 """Scan simulator output for failures and warnings.
244 A ``(failures, warnings)`` tuple of tagged log lines.
246 failures: list[str] = []
247 warnings: list[str] = []
249 for stream, lines
in ((
"stdout", stdout_lines), (
"stderr", stderr_lines)):
251 tagged = f
"[{stream}] {line}"
252 if config.failure_matcher
and _line_matches(config.failure_matcher, line,
254 failures.append(tagged)
255 if config.warning_matcher
and _line_matches(config.warning_matcher, line,
257 warnings.append(tagged)
259 return failures, warnings
263 """Interpolate ``{tmp_dir}`` placeholders in script arguments."""
264 return [arg.format(tmp_dir=tmp_dir)
for arg
in args]
268 """Generate hardware sources via a normalized source-generator callable.
270 If ``config.source_generator`` is a string/path, it is wrapped into a callable
271 that runs the PyCDE script. If it is already callable, it is used directly.
273 source_spec = config.source_generator
274 if isinstance(source_spec, (str, Path)):
275 source_generator: SourceGeneratorFunc = (
277 source_spec, inner_config, inner_tmp_dir))
279 source_generator = source_spec
280 return source_generator(config, tmp_dir)
284 config: CosimPytestConfig,
285 sources_dir: Path) -> Optional[Dict[str, Optional[str]]]:
286 """Collect the RTL macros to compile with.
288 Reads `macro_definitions_file` if one was given, then layers the test's own
289 `macro_definitions` on top. A relative file path is resolved against
290 *sources_dir* rather than the working directory, so a source generator can
291 write the file into the directory it returns -- the path is not knowable
294 macros: Dict[str, Optional[str]] = {}
296 if config.macro_definitions_file
is not None:
297 macros_file = Path(config.macro_definitions_file)
298 if not macros_file.is_absolute():
299 macros_file = sources_dir / macros_file
300 macros.update(load_macro_definitions(macros_file))
301 _logger.debug(
"Read %d macro(s) from %s", len(macros), macros_file)
303 if config.macro_definitions:
304 macros.update(config.macro_definitions)
306 return macros
or None
310 run_dir: Path) -> Simulator:
311 """Instantiate a ``Simulator`` from the generated source files."""
313 hw_dir = sources_dir /
"hw"
314 sources.add_dir(hw_dir
if hw_dir.exists()
else sources_dir)
316 return get_simulator(config.simulator, sources, run_dir, config.debug,
317 config.save_waveform,
322 tmp_dir: Path) -> Path:
323 """Execute the PyCDE hardware script and run codegen if a manifest exists.
326 The directory containing the generated sources (same as *tmp_dir*).
328 script = Path(script_path).
resolve()
331 subprocess.run([sys.executable, str(script), *script_args],
334 timeout=config.timeout_s)
337 manifest_path = tmp_dir /
"esi_system_manifest.json"
338 if manifest_path.exists():
339 generated_dir = tmp_dir /
"generated"
340 generated_dir.mkdir(parents=
True, exist_ok=
True)
344 sys.executable,
"-m",
"esiaccel.codegen",
"--file",
345 str(manifest_path),
"--output-dir",
350 timeout=config.timeout_s,
352 except (subprocess.CalledProcessError, subprocess.TimeoutExpired)
as e:
354 _logger.warning(
"codegen failed (non-fatal): %s", e)
360 "host",
"hostname",
"port",
"sources_dir",
"conn",
"accelerator"
362_INJECTED_ANNOTATIONS = frozenset({Accelerator, AcceleratorConnection})
366 """Return True if *name*/*annotation* will be supplied by the decorator."""
367 return name
in _INJECTED_NAMES
or annotation
in _INJECTED_ANNOTATIONS
371 target: Callable[..., Any],
372 kwargs: Dict[str, Any],
375 sources_dir: Optional[Path] =
None,
377 """Build the keyword arguments to inject into the test function.
379 Inspects the target's signature and automatically supplies ``host``,
380 ``port``, ``sources_dir``, ``AcceleratorConnection``, or ``Accelerator``
381 parameters that the test declares but the caller did not provide.
383 sig = inspect.signature(target)
384 updated = dict(kwargs)
386 for name, param
in sig.parameters.items():
389 if name
in (
"host",
"hostname"):
393 elif name ==
"sources_dir" and sources_dir
is not None:
394 updated[name] = sources_dir
395 elif param.annotation
is AcceleratorConnection
or name ==
"conn":
397 elif param.annotation
is Accelerator
or name ==
"accelerator":
399 updated[name] = conn.build_accelerator()
405 """Return a signature with injected parameters removed.
407 Pytest uses function signatures to determine fixture requirements. This
408 hides the parameters that the decorator injects (``host``, ``port``,
409 ``conn``, etc.) so pytest does not try to resolve them as fixtures.
410 Uses :func:`_is_injected_param` as the single source of truth.
412 sig = inspect.signature(target)
414 p
for p
in sig.parameters.values()
417 return sig.replace(parameters=kept)
421 """Copy pre-compiled simulator artifacts into the per-test run directory.
423 Copies the *entire* compile directory so that all backends (Verilator,
424 Questa, etc.) find their artefacts regardless of internal layout.
426 if compile_dir
is None:
428 run_dir.mkdir(parents=
True, exist_ok=
True)
429 for item
in compile_dir.iterdir():
430 dst = run_dir / item.name
432 shutil.copytree(item, dst, dirs_exist_ok=
True)
434 shutil.copy2(item, dst)
438 """Run the hw script and compile the simulator once for a whole test class.
440 The resulting ``_ClassCompileCache`` is reused by each test method to avoid
441 redundant compilations.
445 if config.tmp_dir_root
is not None:
448 compile_root = config.tmp_dir_root / test_dir /
"__compile__"
449 compile_root.mkdir(parents=
True, exist_ok=
True)
451 compile_root = Path(tempfile.mkdtemp(prefix=
"esi-pytest-class-compile-",))
454 compile_dir = compile_root /
"compile"
459 raise RuntimeError(f
"Simulator compile failed with exit code {rc}")
462 if config.delete_tmp_dir
and not config.debug:
463 shutil.rmtree(compile_root, ignore_errors=
True)
468 result_pipe: Optional[multiprocessing.connection.Connection],
469 target: Callable[..., Any],
470 config: CosimPytestConfig,
472 kwargs: Dict[str, Any],
473 class_cache: Optional[_ClassCompileCache],
475 """Entry point for the forked child process.
477 Compiles (or reuses cached compilation), starts the simulator, injects
478 connection parameters, calls the test function, scans logs for failures,
479 and sends a ``_ChildResult`` through *result_pipe*. When *result_pipe* is
480 ``None``, returns the result directly for callers that already have process
481 isolation (Windows pytest-xdist workers).
485 compile_stdout: list[str] = []
486 compile_stderr: list[str] = []
487 stdout_lines: list[str] = []
488 stderr_lines: list[str] = []
490 def on_stdout(line: str):
491 stdout_lines.append(line)
493 def on_stderr(line: str):
494 stderr_lines.append(line)
503 if config.tmp_dir_root
is not None:
506 run_root = config.tmp_dir_root / test_dir
507 run_root.mkdir(parents=
True, exist_ok=
True)
509 run_root = Path(tempfile.mkdtemp(prefix=
"esi-pytest-run-",))
510 if class_cache
is None:
514 sim._run_stdout_cb = on_stdout
515 sim._run_stderr_cb = on_stderr
516 sim._compile_stdout_cb = compile_stdout.append
517 sim._compile_stderr_cb = compile_stderr.append
521 raise RuntimeError(f
"Simulator compile failed with exit code {rc}")
523 sources_dir = class_cache.sources_dir
526 sim._run_stdout_cb = on_stdout
527 sim._run_stderr_cb = on_stderr
531 sim_proc = sim.run_proc()
536 sources_dir=sources_dir)
537 target(*args, **injected_kwargs)
539 failure_lines, warning_lines =
_scan_logs(stdout_lines, stderr_lines,
542 raise AssertionError(
"Detected simulator failures:\n" +
543 "\n".join(failure_lines))
546 warning_lines=warning_lines,
547 failure_lines=failure_lines,
548 stdout_lines=compile_stdout + stdout_lines,
549 stderr_lines=compile_stderr + stderr_lines)
552 traceback=traceback.format_exc(),
553 warning_lines=
_scan_logs(stdout_lines, stderr_lines,
555 stdout_lines=compile_stdout + stdout_lines,
556 stderr_lines=compile_stderr + stderr_lines)
558 if sim_proc
is not None and sim_proc.proc.poll()
is None:
559 sim_proc.force_stop()
562 should_delete = config.delete_tmp_dir
and not config.debug
563 if run_root
is not None and should_delete:
564 shutil.rmtree(run_root, ignore_errors=
True)
566 if result_pipe
is not None:
568 result_pipe.send(result)
579 target: Callable[..., Any],
580 config: CosimPytestConfig,
582 kwargs: Dict[str, Any],
583 class_cache: Optional[_ClassCompileCache] =
None,
585 """Fork a child process to run *target* and wait for its result.
587 Handles timeouts, collects warnings, and re-raises any failure from
588 the child as an ``AssertionError`` in the parent.
591 ctx: Any = multiprocessing.get_context(
"fork")
596 import pytest
as _pytest
597 if os.environ.get(
"PYTEST_XDIST_WORKER")
is None:
598 _pytest.skip(
"Windows cosim tests require pytest-xdist isolation; run "
599 "with `pytest -n 1` or more")
602 inline_result =
_run_child(
None, target, config, args, kwargs, class_cache)
603 for line
in inline_result.stdout_lines:
604 _logger.debug(
"sim stdout: %s", line)
605 for line
in inline_result.stderr_lines:
606 _logger.debug(
"sim stderr: %s", line)
607 for warning
in inline_result.warning_lines:
608 _logger.warning(
"cosim warning: %s", warning)
609 if not inline_result.success:
610 parts = [inline_result.traceback]
611 if inline_result.stdout_lines:
612 parts.append(
"\n=== Simulator stdout ===")
613 parts.extend(inline_result.stdout_lines[-200:])
614 if inline_result.stderr_lines:
615 parts.append(
"\n=== Simulator stderr ===")
616 parts.extend(inline_result.stderr_lines[-200:])
617 raise AssertionError(
"\n".join(parts))
619 reader, writer = ctx.Pipe(duplex=
False)
620 process = ctx.Process(
622 args=(writer, target, config, args, kwargs, class_cache),
629 result: Optional[_ChildResult] =
None
630 if reader.poll(timeout=config.timeout_s):
631 result = reader.recv()
633 process.join(timeout=10)
634 if process.is_alive():
636 process.join(timeout=5)
639 if config.timeout_s
is not None:
640 raise AssertionError(
641 f
"Cosim test timed out after {config.timeout_s} seconds")
643 f
"Cosim child exited without returning a result (exit code: {process.exitcode})"
647 for line
in result.stdout_lines:
648 _logger.debug(
"sim stdout: %s", line)
649 for line
in result.stderr_lines:
650 _logger.debug(
"sim stderr: %s", line)
651 for warning
in result.warning_lines:
652 _logger.warning(
"cosim warning: %s", warning)
654 if not result.success:
655 parts = [result.traceback]
656 if result.stdout_lines:
657 parts.append(
"\n=== Simulator stdout ===")
658 parts.extend(result.stdout_lines[-200:])
659 if result.stderr_lines:
660 parts.append(
"\n=== Simulator stderr ===")
661 parts.extend(result.stderr_lines[-200:])
662 raise AssertionError(
"\n".join(parts))
666 target: Callable[..., Any],
667 config: CosimPytestConfig,
668 class_cache_getter: Optional[Callable[[], _ClassCompileCache]] =
None,
669) -> Callable[..., Any]:
670 """Wrap a single test function so it runs inside ``_run_isolated``."""
672 test_config = replace(config, test_name=target.__name__)
674 @functools.wraps(target)
675 def _wrapper(*args, **kwargs):
676 if not is_simulator_available(test_config.simulator):
677 available = available_simulators()
679 f
"Simulator '{test_config.simulator}' not available; available simulators: "
680 f
"{', '.join(available) if available else 'none'}")
681 cache = class_cache_getter()
if class_cache_getter
is not None else None
682 _run_isolated(target, test_config, args, kwargs, class_cache=cache)
689 """Wrap every ``test_*`` method of a class with cosim isolation.
691 Compilation is performed once (lazily, on first method invocation) and
692 the resulting artifacts are shared across all methods via a thread-safe
696 class_config = replace(config, class_name=target_cls.__name__)
697 lock = threading.Lock()
698 cache_holder: dict[str, _ClassCompileCache] = {}
700 def _get_cache() -> _ClassCompileCache:
702 if "cache" not in cache_holder:
704 return cache_holder[
"cache"]
706 for name, member
in list(vars(target_cls).items()):
707 if name.startswith(
"test")
and callable(member):
713 class_cache_getter=_get_cache),
719 source_generator: SourceGeneratorArg,
720 args: Sequence[str] = (
"{tmp_dir}",),
721 simulator: str =
"verilator",
722 top: str =
"ESI_Cosim_Top",
723 debug: Optional[bool] =
None,
724 timeout_s: float = _DEFAULT_TIMEOUT_S,
725 failure_matcher: Optional[LogMatcher] = _DEFAULT_FAILURE_PATTERN,
726 warning_matcher: Optional[LogMatcher] = _DEFAULT_WARN_PATTERN,
727 tmp_dir_root: Optional[Path] =
None,
728 delete_tmp_dir: Optional[bool] =
None,
729 save_waveform: Optional[bool] =
None,
730 macro_definitions: Optional[Dict[str, Optional[str]]] =
None,
731 macro_definitions_file: Optional[Union[str, Path]] =
None,
733 """Decorator that turns a function or class into a cosimulation test.
735 The decorated target is executed in a forked child process with a freshly
736 compiled and running simulator. Connection parameters (``host``, ``port``,
737 ``acc``, etc.) are injected automatically based on the function signature.
739 When applied to a class, the hardware script is run and compiled once;
740 each ``test_*`` method gets its own simulator process but skips
744 source_generator: Path to the PyCDE script that generates the hardware, or
745 a callable ``(config, tmp_dir) -> sources_dir`` that generates
747 args: Arguments forwarded to the script; ``{tmp_dir}`` is interpolated
748 with the temporary build directory.
749 simulator: Simulator backend (default ``"verilator"``).
750 top: Top-level module name.
751 debug: Enable verbose simulator output. Defaults to the value of the
752 ``ESIACCEL_PYTEST_DEBUG`` environment variable if set, otherwise False.
753 timeout_s: Wall-clock timeout in seconds (default 120).
754 failure_matcher: Pattern to detect errors in simulator output.
755 warning_matcher: Pattern to detect warnings in simulator output.
756 tmp_dir_root: Root directory for temporary test files. Defaults to the value
757 of the ``ESIACCEL_PYTEST_TMP_DIR`` environment variable if set, otherwise
758 uses the system temporary directory. Run directories are organized in a
759 hierarchy to avoid collisions during parallel execution:
761 - Normal run: ``pytest-{pid}/ClassName/test_method/`` or
762 ``pytest-{pid}/test_function/``
763 - xdist parallel: ``pytest-{pid}/gw0/ClassName/test_method/`` (gw0, gw1, etc.
764 for different workers)
765 delete_tmp_dir: Whether to delete temporary directories after test
766 completion. When tmp_dir_root is set (custom debugging directory),
767 directories are kept by default; set ``ESIACCEL_PYTEST_DELETE_TMP_DIR=true``
768 to delete them. When using the system temp directory, defaults to True
769 (delete to avoid clutter). Always False when debug mode is enabled.
770 save_waveform: Whether to save waveform dumps (format depends on the
771 simulator backend, e.g. FST for Verilator, VCD for Questa). Requires
772 debug mode to be enabled. Defaults to the value of the
773 ``ESIACCEL_PYTEST_SAVE_WAVEFORM`` environment variable if set, otherwise
775 macro_definitions: RTL macros to define during compilation. A value of
776 None defines the macro without assigning a value.
777 macro_definitions_file: JSON file of macro definitions to read, as written
778 by e.g. a source generator. A relative path is resolved against the
779 generated sources directory. ``macro_definitions`` takes precedence over
785 if tmp_dir_root
is None:
787 if delete_tmp_dir
is None:
791 default_delete = tmp_dir_root
is None
792 delete_tmp_dir =
_get_env_bool(
"ESIACCEL_PYTEST_DELETE_TMP_DIR",
794 if save_waveform
is None:
795 save_waveform =
_get_env_bool(
"ESIACCEL_PYTEST_SAVE_WAVEFORM",
False)
798 if save_waveform
and not debug:
800 "save_waveform requires debug mode to be enabled; disabling waveform dumping"
802 save_waveform =
False
809 source_generator=source_generator,
815 failure_matcher=failure_matcher,
816 warning_matcher=warning_matcher,
817 tmp_dir_root=tmp_dir_root,
818 delete_tmp_dir=delete_tmp_dir,
819 pytest_run_id=pytest_run_id,
820 xdist_worker_id=xdist_worker_id,
821 save_waveform=save_waveform,
822 macro_definitions=macro_definitions,
823 macro_definitions_file=macro_definitions_file,
826 def _decorator(target):
827 if inspect.isclass(target):
831 raise TypeError(
"@cosim_test can decorate functions or classes")
static mlir::Operation * resolve(Context &context, mlir::SymbolRefAttr sym)
"AcceleratorConnection" connect(str platform, str connection_str)
Optional[Path] _get_env_path(str var_name)
_copy_compiled_artifacts(Optional[Path] compile_dir, Path run_dir)
type _decorate_class(type target_cls, CosimPytestConfig config)
_run_isolated(Callable[..., Any] target, CosimPytestConfig config, Sequence[Any] args, Dict[str, Any] kwargs, Optional[_ClassCompileCache] class_cache=None)
Optional[Dict[str, Optional[str]]] _resolve_macro_definitions(CosimPytestConfig config, Path sources_dir)
tuple[list[str], list[str]] _scan_logs(Sequence[str] stdout_lines, Sequence[str] stderr_lines, CosimPytestConfig config)
Dict[str, Any] _resolve_injected_params(Callable[..., Any] target, Dict[str, Any] kwargs, str host, int port, Optional[Path] sources_dir=None)
Path _run_hw_script(Union[str, Path] script_path, CosimPytestConfig config, Path tmp_dir)
Path _generate_sources(CosimPytestConfig config, Path tmp_dir)
bool _line_matches(LogMatcher matcher, str line, str stream)
str _get_test_dir_name(CosimPytestConfig config)
Simulator _create_simulator(CosimPytestConfig config, Path sources_dir, Path run_dir)
list[str] _render_args(Sequence[str] args, Path tmp_dir)
Optional[str] _get_xdist_worker_id()
inspect.Signature _visible_signature(Callable[..., Any] target)
bool _get_env_bool(str var_name, bool default)
bool _is_injected_param(str name, Any annotation)
_ChildResult _run_child(Optional[multiprocessing.connection.Connection] result_pipe, Callable[..., Any] target, CosimPytestConfig config, Sequence[Any] args, Dict[str, Any] kwargs, Optional[_ClassCompileCache] class_cache)
_ClassCompileCache _compile_once_for_class(CosimPytestConfig config)
Callable[..., Any] _decorate_function(Callable[..., Any] target, CosimPytestConfig config, Optional[Callable[[], _ClassCompileCache]] class_cache_getter=None)