|
| 1 | +# ################################################################################################## |
| 2 | +# The MIT License (MIT) |
| 3 | +# Copyright (c) 2025-2026 Advanced Micro Devices, Inc. All rights reserved. |
| 4 | +# |
| 5 | +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software |
| 6 | +# and associated documentation files (the "Software"), to deal in the Software without restriction, |
| 7 | +# including without limitation the rights to use, copy, modify, merge, publish, distribute, |
| 8 | +# sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is |
| 9 | +# furnished to do so, subject to the following conditions: |
| 10 | +# |
| 11 | +# The above copyright notice and this permission notice shall be included in all copies or |
| 12 | +# substantial portions of the Software. |
| 13 | +# |
| 14 | +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT |
| 15 | +# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| 16 | +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
| 17 | +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 18 | +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 19 | +# ################################################################################################## |
| 20 | + |
| 21 | +"""Tests for core.command_config — pre-synth Tcl resolution and merging.""" |
| 22 | + |
| 23 | +import argparse |
| 24 | +import textwrap |
| 25 | +from pathlib import Path |
| 26 | +from unittest import mock |
| 27 | + |
| 28 | +import pytest |
| 29 | + |
| 30 | +from slashkit.core import command_config |
| 31 | +from slashkit.core.command_config import LinkerConfiguration |
| 32 | + |
| 33 | +_FIXTURE_COMPONENT = ( |
| 34 | + Path(__file__).resolve().parents[1] |
| 35 | + / "fixtures" / "dma_in" / "hls" / "impl" / "ip" / "component.xml" |
| 36 | +) |
| 37 | + |
| 38 | + |
| 39 | +def _make_args(tmp_path: Path, *, config: Path, cli_pre_synth): |
| 40 | + vivado = tmp_path / "vivado" |
| 41 | + vivado.write_text("#!/bin/sh\n") |
| 42 | + vivado.chmod(0o755) |
| 43 | + return argparse.Namespace( |
| 44 | + vivado=vivado, |
| 45 | + jobs=8, |
| 46 | + config=config, |
| 47 | + kernels=[_FIXTURE_COMPONENT], |
| 48 | + out=tmp_path / "out.vbin", |
| 49 | + platform="hw", |
| 50 | + pre_synth_tcls=list(cli_pre_synth), |
| 51 | + clock_hz=None, |
| 52 | + ) |
| 53 | + |
| 54 | + |
| 55 | +def _build_config(tmp_path: Path, *, config: Path, cli_pre_synth): |
| 56 | + """Construct a LinkerConfiguration with the heavy, environment-dependent |
| 57 | + lookups stubbed out, so only the pre-synth resolution/merging is exercised.""" |
| 58 | + args = _make_args(tmp_path, config=config, cli_pre_synth=cli_pre_synth) |
| 59 | + with mock.patch.object(command_config, "_find_vitis_include", |
| 60 | + return_value=tmp_path), \ |
| 61 | + mock.patch.object(command_config, "apply_config_to_instances", |
| 62 | + return_value=[]): |
| 63 | + return LinkerConfiguration(args) |
| 64 | + |
| 65 | + |
| 66 | +def _write_cfg(tmp_path: Path, body: str) -> Path: |
| 67 | + cfg = tmp_path / "config.cfg" |
| 68 | + cfg.write_text(textwrap.dedent(body)) |
| 69 | + return cfg |
| 70 | + |
| 71 | + |
| 72 | +def test_config_cfg_pre_synth_reaches_pre_synth_tcls(tmp_path): |
| 73 | + """[user_region] pre_synth= entries from the config file must be honoured.""" |
| 74 | + a = tmp_path / "a.tcl" |
| 75 | + a.write_text("# a\n") |
| 76 | + cfg = _write_cfg(tmp_path, """ |
| 77 | + [connectivity] |
| 78 | + [user_region] |
| 79 | + pre_synth=a.tcl |
| 80 | + """) |
| 81 | + config = _build_config(tmp_path, config=cfg, cli_pre_synth=[]) |
| 82 | + assert config.pre_synth_tcls == [a.resolve()] |
| 83 | + |
| 84 | + |
| 85 | +def test_config_cfg_and_cli_pre_synth_are_merged_in_order(tmp_path): |
| 86 | + """Config-declared scripts run first, then CLI ones.""" |
| 87 | + a = tmp_path / "a.tcl" |
| 88 | + a.write_text("# a\n") |
| 89 | + b = tmp_path / "b.tcl" |
| 90 | + b.write_text("# b\n") |
| 91 | + cli = tmp_path / "cli.tcl" |
| 92 | + cli.write_text("# cli\n") |
| 93 | + cfg = _write_cfg(tmp_path, """ |
| 94 | + [connectivity] |
| 95 | + [user_region] |
| 96 | + pre_synth=a.tcl |
| 97 | + pre_synth=b.tcl |
| 98 | + """) |
| 99 | + config = _build_config(tmp_path, config=cfg, cli_pre_synth=[cli]) |
| 100 | + assert config.pre_synth_tcls == [a.resolve(), b.resolve(), cli.resolve()] |
| 101 | + |
| 102 | + |
| 103 | +def test_pre_synth_duplicates_are_de_duplicated(tmp_path): |
| 104 | + """A script named in both the config and on the CLI is sourced once.""" |
| 105 | + a = tmp_path / "a.tcl" |
| 106 | + a.write_text("# a\n") |
| 107 | + cfg = _write_cfg(tmp_path, """ |
| 108 | + [connectivity] |
| 109 | + [user_region] |
| 110 | + pre_synth=a.tcl |
| 111 | + """) |
| 112 | + config = _build_config(tmp_path, config=cfg, cli_pre_synth=[a]) |
| 113 | + assert config.pre_synth_tcls == [a.resolve()] |
| 114 | + |
| 115 | + |
| 116 | +def test_missing_config_cfg_pre_synth_raises(tmp_path): |
| 117 | + """A non-existent [user_region] pre_synth path fails fast.""" |
| 118 | + cfg = _write_cfg(tmp_path, """ |
| 119 | + [connectivity] |
| 120 | + [user_region] |
| 121 | + pre_synth=does_not_exist.tcl |
| 122 | + """) |
| 123 | + with pytest.raises(FileNotFoundError): |
| 124 | + _build_config(tmp_path, config=cfg, cli_pre_synth=[]) |
0 commit comments