mirror of
https://github.com/retroSoC/retroSoC.git
synced 2026-11-04 22:32:36 +00:00
feat: add RTL linting support with Verilator integration
- Introduced RTL linting capabilities using Verilator in the Makefile. - Added RTL_LINT_FLAGS for specifying linting options and created targets for linting. - Implemented a new script to analyze RTL lint warnings and check against a baseline. - Updated regression scripts to include RTL linting in the command sets. - Enhanced test coverage for RTL linting functionality, ensuring proper warning handling. - Cleaned up build directories to include RTL lint outputs.
This commit is contained in:
11 files changed
+9288
-25
No files matched your search
@@ -171,7 +171,7 @@ endif
|
||||
regress-smoke regress-pr regress-nightly sim-asm format format-check sw-format sw-format-check mk-format \
|
||||
mk-format-check rtl-format rtl-format-check sw-policy-check sw-host-test \
|
||||
pin-map check-pin-map soc-topology check-soc-topology user-extensions check-user-extensions \
|
||||
check-clock-reset-domains tech-cell-test
|
||||
check-clock-reset-domains tech-cell-test rtl-lint check-rtl-lint
|
||||
.NOTPARALLEL: setup
|
||||
|
||||
help:
|
||||
@@ -195,6 +195,7 @@ help:
|
||||
' user-extensions generate the selected scalar user-extension bindings' \
|
||||
' check-user-extensions validate the canonical user-extension map' \
|
||||
' check-clock-reset-domains validate the root clock/reset and CDC inventory' \
|
||||
' rtl-lint | check-rtl-lint run/check strict Verilator RTL lint warnings' \
|
||||
' tech-cell-test test GF180/SKY130 technology IO and clock wrappers' \
|
||||
' check-warnings | metrics analyze flow logs and reports' \
|
||||
' check-metrics apply the committed metrics policy' \
|
||||
|
||||
@@ -123,6 +123,7 @@ make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk SIMU=IVERILOG \
|
||||
| Yosys synthesis | `make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk SYNTH=YOSYS synth` |
|
||||
| Icarus netlist simulation after synthesis | `make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk SIMU=IVERILOG netsim` |
|
||||
| OpenSTA core timing analysis after synthesis | `make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk STA=OPENSTA sta` |
|
||||
| Strict Verilator RTL lint | `make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk SIMU=VERILATOR HAVE_SVA=YES check-rtl-lint` |
|
||||
| IHP130 fast smoke suite | `make regress-smoke` |
|
||||
| Pull-request regression suite | `make regress-pr` |
|
||||
| Nightly regression suite | `make regress-nightly` |
|
||||
@@ -131,7 +132,8 @@ make CONFIG=configs/ci/hazard3-rv32im-ihp130.mk SIMU=IVERILOG \
|
||||
| Script and policy checks | `make sw-policy-check sw-host-test` |
|
||||
|
||||
`make regress-smoke` builds the IHP130 firmware, compiles the Verilator SVA
|
||||
configuration, and runs the Icarus assembly self-test. It omits synthesis,
|
||||
configuration, and runs the Icarus assembly self-test. It runs strict RTL lint
|
||||
before those flows and omits synthesis,
|
||||
timing, and netlist simulation for fast feedback. `make regress-pr` runs the
|
||||
supported IHP130, GF180, and SKY130 PR matrices in sequence, including
|
||||
slow-corner OpenSTA core timing analysis for each PDK.
|
||||
|
||||
+4
-4
@@ -4,10 +4,10 @@
|
||||
|
||||
| Tier | Profile | Automated coverage |
|
||||
| --- | --- | --- |
|
||||
| Smoke | `configs/ci/hazard3-rv32im-ihp130.mk` | firmware, Verilator SVA compilation, Icarus assembly self-test |
|
||||
| Pull request | `configs/ci/hazard3-rv32im-ihp130.mk` | firmware, Verilator, Icarus, Yosys, netlist Icarus, OpenSTA |
|
||||
| Pull request | `configs/ci/hazard3-rv32im-gf180.mk` | firmware, Verilator, Icarus, Yosys, netlist Icarus, OpenSTA |
|
||||
| Pull request | `configs/ci/hazard3-rv32im-sky130.mk` | firmware, Verilator, Icarus, Yosys, netlist Icarus, OpenSTA |
|
||||
| Smoke | `configs/ci/hazard3-rv32im-ihp130.mk` | strict Verilator RTL lint, firmware, Verilator SVA compilation, Icarus assembly self-test |
|
||||
| Pull request | `configs/ci/hazard3-rv32im-ihp130.mk` | strict Verilator RTL lint, firmware, Verilator, Icarus, Yosys, netlist Icarus, OpenSTA |
|
||||
| Pull request | `configs/ci/hazard3-rv32im-gf180.mk` | strict Verilator RTL lint, firmware, Verilator, Icarus, Yosys, netlist Icarus, OpenSTA |
|
||||
| Pull request | `configs/ci/hazard3-rv32im-sky130.mk` | strict Verilator RTL lint, firmware, Verilator, Icarus, Yosys, netlist Icarus, OpenSTA |
|
||||
| Pull request | `configs/ci/mdd-rv32im-ihp130.mk` | firmware and Verilator |
|
||||
| Nightly | `configs/nightly/picorv32-rv32im-ihp130.mk` | firmware, Verilator, and Icarus |
|
||||
| Cluster | `configs/cluster/hazard3-ics55.mk` | configuration only; run with site PDK/tool access |
|
||||
|
||||
@@ -3,5 +3,9 @@
|
||||
Baselines are scoped by committed profile and tool. Each entry stores a normalized warning signature,
|
||||
its accepted count, and one example. Path roots and source line numbers are removed before hashing.
|
||||
|
||||
`rtl-lint.json` is independent from `verilator.json`: it contains warnings from the strict
|
||||
`rtl-lint` elaboration, while `verilator.json` contains warnings emitted by emulator generation
|
||||
and simulation.
|
||||
|
||||
Do not hand-edit signatures. Regenerate only from a successful flow using
|
||||
`scripts/analyze_warnings.py baseline`, review the full diff, and run `make check-warnings`.
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,4 +1,5 @@
|
||||
BUILD_DIR := $(SIM_BUILD_ROOT)
|
||||
RTL_LINT_DIR := $(VARIANT_ROOT)/lint/verilator
|
||||
SOC_CSRC_HOME += $(RTL_PATH)/dv/verilator/csrc
|
||||
SOC_CSRC_LIB_HOME += $(RTL_PATH)/dv/verilator/csrc
|
||||
SOC_CXXFILES += $(sort $(wildcard $(SOC_CSRC_HOME)/*.cpp))
|
||||
@@ -27,10 +28,16 @@ VERILATOR_FLAGS += -o $(BUILD_DIR)/emu
|
||||
VERILATOR_FLAGS += -Mdir $(SOC_COMPILE_HOME)
|
||||
VERILATOR_FLAGS += $(SOC_VSRC_INCLPATH) $(SOC_CXXFILES) $(SOC_VXXFILES)
|
||||
|
||||
RTL_LINT_FLAGS := --lint-only --no-timing --top-module $(SOC_VSRC_TOP)
|
||||
RTL_LINT_FLAGS += --assert --Wall --timescale "1ns/1ns" -Wno-fatal
|
||||
RTL_LINT_FLAGS += $(SOC_VSRC_INCLPATH) $(SOC_VXXFILES)
|
||||
|
||||
SOC_SIM_TIME ?= 180
|
||||
VERILATOR_STAMP := $(BUILD_DIR)/verilate.stamp
|
||||
VERILATOR_DEPFILE := $(BUILD_DIR)/verilate.d
|
||||
VERILATOR_EMU := $(BUILD_DIR)/emu
|
||||
RTL_LINT_STAMP := $(RTL_LINT_DIR)/rtl-lint.stamp
|
||||
RTL_LINT_DEPFILE := $(RTL_LINT_DIR)/rtl-lint.d
|
||||
VERILATOR_EXTRA_SOURCES := $(RTL_PATH)/dv/model/ESP_PSRAM64H.sv \
|
||||
$(RTL_PATH)/dv/verilator/rtl/flash_read_binder.sv \
|
||||
$(RTL_PATH)/dv/verilator/rtl/QSPIFlash.sv \
|
||||
@@ -38,7 +45,7 @@ VERILATOR_EXTRA_SOURCES := $(RTL_PATH)/dv/model/ESP_PSRAM64H.sv \
|
||||
$(SOC_CXXFILES) $(wildcard $(SOC_CSRC_HOME)/*.h) \
|
||||
$(wildcard $(SOC_CSRC_HOME)/*.hpp)
|
||||
|
||||
-include $(VERILATOR_DEPFILE)
|
||||
-include $(VERILATOR_DEPFILE) $(RTL_LINT_DEPFILE)
|
||||
|
||||
CCACHE := $(shell command -v ccache 2>/dev/null)
|
||||
ifneq ($(CCACHE),)
|
||||
@@ -64,7 +71,24 @@ $(VERILATOR_EMU): $(VERILATOR_STAMP)
|
||||
-- make -j$(VERILATOR_JOBS) VM_PARALLEL_BUILDS=1 OPT_FAST=-O3 -C $(SOC_COMPILE_HOME) \
|
||||
-f V$(SOC_VSRC_TOP).mk
|
||||
|
||||
lint: $(VERILATOR_STAMP)
|
||||
$(RTL_LINT_STAMP): $(MPW_VARIANT_STAMP) $(FILELIST_STAMP) $(VERILATOR_EXTRA_SOURCES)
|
||||
@mkdir -p $(RTL_LINT_DIR)
|
||||
python3 $(ROOT_PATH)/scripts/run_flow.py --tool rtl-lint \
|
||||
--log $(RTL_LINT_DIR)/lint.log --result $(RTL_LINT_DIR)/result-rtl-lint.json \
|
||||
-- $(VERILATOR) $(RTL_LINT_FLAGS)
|
||||
python3 $(RTL_PATH)/script/filelist_deps.py $(RTL_FLIST) \
|
||||
$(foreach source,$(VERILATOR_EXTRA_SOURCES),--extra $(source)) \
|
||||
--target $@ --output $(RTL_LINT_DEPFILE)
|
||||
@touch $@
|
||||
|
||||
rtl-lint: $(RTL_LINT_STAMP) | manifest
|
||||
|
||||
check-rtl-lint: rtl-lint
|
||||
python3 $(ROOT_PATH)/scripts/analyze_warnings.py check --root $(ROOT_PATH) \
|
||||
--profile $(PROFILE_NAME) --variant-root $(VARIANT_ROOT) --tool rtl-lint \
|
||||
--output $(META_DIR)/rtl-lint-warnings.json
|
||||
|
||||
lint: rtl-lint
|
||||
comp: $(VERILATOR_EMU)
|
||||
|
||||
sim: comp
|
||||
@@ -83,5 +107,6 @@ wave:
|
||||
|
||||
clean:
|
||||
python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(BUILD_DIR)
|
||||
python3 $(ROOT_PATH)/scripts/clean.py --root $(ROOT_PATH) --path $(RTL_LINT_DIR)
|
||||
|
||||
.PHONY: comp sim clean
|
||||
.PHONY: lint rtl-lint check-rtl-lint comp sim clean
|
||||
+11
-10
@@ -16,6 +16,9 @@ sys.path.insert(0, str(ROOT))
|
||||
from scripts.setup_helpers import atomic_write # noqa: E402
|
||||
|
||||
|
||||
WARNING_TOOLS = ("iverilog", "verilator", "rtl-lint", "vcs", "yosys", "opensta")
|
||||
|
||||
|
||||
ANSI_RE = re.compile(r"\x1b\[[0-9;]*m")
|
||||
TIMESTAMP_RE = re.compile(r"^\[[^]]+\]\s*")
|
||||
LOCATION_RE = re.compile(
|
||||
@@ -28,6 +31,7 @@ LOCATION_RE = re.compile(
|
||||
def warning_line(tool: str, line: str) -> tuple[str, str] | None:
|
||||
patterns = {
|
||||
"verilator": re.compile(r"%Warning-([A-Z0-9_]+):\s*(.*)"),
|
||||
"rtl-lint": re.compile(r"%Warning-([A-Z0-9_]+):\s*(.*)"),
|
||||
"iverilog": re.compile(r"\b(warning|sorry):\s*(.*)", re.IGNORECASE),
|
||||
"yosys": re.compile(r"\b(?:ABC:\s*)?Warning:\s*(.*)", re.IGNORECASE),
|
||||
"opensta": re.compile(r"\bWarning\s+(\d+):\s*(.*)", re.IGNORECASE),
|
||||
@@ -36,7 +40,7 @@ def warning_line(tool: str, line: str) -> tuple[str, str] | None:
|
||||
match = patterns[tool].search(line)
|
||||
if not match:
|
||||
return None
|
||||
if tool in ("verilator", "opensta"):
|
||||
if tool in ("verilator", "rtl-lint", "opensta"):
|
||||
return match.group(1), match.group(2)
|
||||
if tool == "iverilog":
|
||||
return match.group(1).lower(), match.group(2)
|
||||
@@ -84,6 +88,7 @@ def discover(variant_root: Path) -> dict[str, list[Path]]:
|
||||
return {
|
||||
"iverilog": sorted((variant_root / "sim/iverilog").glob("*/*.log")),
|
||||
"verilator": sorted((variant_root / "sim/verilator").glob("*.log")),
|
||||
"rtl-lint": sorted((variant_root / "lint/verilator").glob("lint.log")),
|
||||
"vcs": sorted((variant_root / "sim/vcs").glob("*/*.log")),
|
||||
"yosys": sorted((variant_root / "syn/yosys").glob("*.log")),
|
||||
"opensta": sorted((variant_root / "sta/opensta").glob("*.log")),
|
||||
@@ -106,7 +111,7 @@ def write_baseline(args: argparse.Namespace) -> int:
|
||||
def check(args: argparse.Namespace) -> int:
|
||||
root = args.root.resolve()
|
||||
variant_root = args.variant_root.resolve()
|
||||
tools = [args.tool] if args.tool else ["iverilog", "verilator", "vcs", "yosys", "opensta"]
|
||||
tools = [args.tool] if args.tool else WARNING_TOOLS
|
||||
logs = discover(variant_root)
|
||||
reports: dict[str, object] = {}
|
||||
failures: list[str] = []
|
||||
@@ -119,12 +124,8 @@ def check(args: argparse.Namespace) -> int:
|
||||
baseline_entries: dict[str, int] = {}
|
||||
if baseline_path.is_file():
|
||||
baseline = json.loads(baseline_path.read_text(encoding="utf-8"))
|
||||
baseline_entries = {
|
||||
entry["signature"]: entry["count"] for entry in baseline["entries"]
|
||||
}
|
||||
current_entries = {
|
||||
entry["signature"]: entry["count"] for entry in current["entries"]
|
||||
}
|
||||
baseline_entries = {entry["signature"]: entry["count"] for entry in baseline["entries"]}
|
||||
current_entries = {entry["signature"]: entry["count"] for entry in current["entries"]}
|
||||
new = sorted(set(current_entries) - set(baseline_entries))
|
||||
increased = sorted(
|
||||
signature
|
||||
@@ -159,14 +160,14 @@ def parse_args() -> argparse.Namespace:
|
||||
baseline = subparsers.add_parser("baseline")
|
||||
baseline.add_argument("--root", type=Path, default=ROOT)
|
||||
baseline.add_argument("--profile", required=True)
|
||||
baseline.add_argument("--tool", choices=("iverilog", "verilator", "vcs", "yosys", "opensta"), required=True)
|
||||
baseline.add_argument("--tool", choices=WARNING_TOOLS, required=True)
|
||||
baseline.add_argument("--log", type=Path, action="append", required=True)
|
||||
baseline.add_argument("--output", type=Path, required=True)
|
||||
check_parser = subparsers.add_parser("check")
|
||||
check_parser.add_argument("--root", type=Path, default=ROOT)
|
||||
check_parser.add_argument("--profile", required=True)
|
||||
check_parser.add_argument("--variant-root", type=Path, required=True)
|
||||
check_parser.add_argument("--tool", choices=("iverilog", "verilator", "vcs", "yosys", "opensta"))
|
||||
check_parser.add_argument("--tool", choices=WARNING_TOOLS)
|
||||
check_parser.add_argument("--output", type=Path, required=True)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
@@ -15,7 +15,11 @@ sys.path.insert(0, str(ROOT))
|
||||
from scripts.check_c_warnings import self_owned_warnings # noqa: E402
|
||||
|
||||
|
||||
RTL_LINT_VALUES = ("SIMU=VERILATOR", "HAVE_SVA=YES", "rtl-lint", "check-rtl-lint")
|
||||
|
||||
|
||||
PR_COMMANDS = (
|
||||
("configs/ci/hazard3-rv32im-ihp130.mk", RTL_LINT_VALUES),
|
||||
("configs/ci/hazard3-rv32im-ihp130.mk", ("firmware",)),
|
||||
("configs/ci/hazard3-rv32im-ihp130-shell.mk", ("firmware",)),
|
||||
("configs/ci/hazard3-rv32im-ihp130-ip-mdd-shell.mk", ("firmware",)),
|
||||
@@ -49,6 +53,7 @@ PR_COMMANDS = (
|
||||
("configs/ci/hazard3-rv32im-ihp130.mk", ("STA=OPENSTA", "sta")),
|
||||
)
|
||||
SMOKE_COMMANDS = (
|
||||
("configs/ci/hazard3-rv32im-ihp130.mk", RTL_LINT_VALUES),
|
||||
("configs/ci/hazard3-rv32im-ihp130.mk", ("firmware",)),
|
||||
(
|
||||
"configs/ci/hazard3-rv32im-ihp130.mk",
|
||||
@@ -86,6 +91,7 @@ PDK_PR_PROFILES = {
|
||||
|
||||
def pdk_pr_commands(profile: str) -> tuple[tuple[str, tuple[str, ...]], ...]:
|
||||
return (
|
||||
(profile, RTL_LINT_VALUES),
|
||||
(profile, ("firmware",)),
|
||||
(profile, ("SIMU=VERILATOR", "HAVE_SVA=YES", "firmware", "sim")),
|
||||
(profile, ("SIMU=IVERILOG", "RTL_SIM_TIMEOUT=5200000", "sim-asm")),
|
||||
|
||||
@@ -31,6 +31,7 @@ from scripts.regress import ( # noqa: E402
|
||||
NIGHTLY_COMMANDS,
|
||||
PDK_PR_PROFILES,
|
||||
PR_COMMANDS,
|
||||
RTL_LINT_VALUES,
|
||||
SMOKE_COMMANDS,
|
||||
pdk_pr_commands,
|
||||
regression_environment,
|
||||
@@ -296,9 +297,7 @@ def test_dependency_lock_and_config_key_include_a_fixed_timestamp(tmp_path: Path
|
||||
lock = load_lock(ROOT / "config/dependencies.lock.json")
|
||||
assert lock["schema_version"] == 1
|
||||
assert len(lock["sources"]["mpw"]["revision"]) == 40
|
||||
assert lock["sources"]["pdk_sky130"]["submodules"] == [
|
||||
"libraries/sky130_fd_sc_hd/latest"
|
||||
]
|
||||
assert lock["sources"]["pdk_sky130"]["submodules"] == ["libraries/sky130_fd_sc_hd/latest"]
|
||||
|
||||
command = (
|
||||
sys.executable,
|
||||
@@ -360,7 +359,10 @@ def test_regression_runner_uses_one_build_timestamp(monkeypatch) -> None:
|
||||
|
||||
def test_verilator_simulations_use_uniform_timeout() -> None:
|
||||
verilator_makefile = ROOT / "rtl/mini/mk/verilator.mk"
|
||||
assert "SOC_SIM_TIME ?= 180" in verilator_makefile.read_text(encoding="utf-8")
|
||||
verilator_source = verilator_makefile.read_text(encoding="utf-8")
|
||||
assert "SOC_SIM_TIME ?= 180" in verilator_source
|
||||
assert "RTL_LINT_FLAGS := --lint-only --no-timing" in verilator_source
|
||||
assert "--assert --Wall" in verilator_source
|
||||
|
||||
regression_commands = (*SMOKE_COMMANDS, *PR_COMMANDS, *NIGHTLY_COMMANDS)
|
||||
for _, values in regression_commands:
|
||||
@@ -375,12 +377,12 @@ def test_smoke_regression_uses_ihp130_behavioral_coverage_only() -> None:
|
||||
assert commands == SMOKE_COMMANDS
|
||||
assert profiles == ()
|
||||
command_values = [values for _, values in commands]
|
||||
assert command_values[0] == RTL_LINT_VALUES
|
||||
assert ("firmware",) in command_values
|
||||
assert ("SIMU=VERILATOR", "HAVE_SVA=YES", "comp") in command_values
|
||||
assert ("SIMU=IVERILOG", "RTL_SIM_TIMEOUT=5200000", "sim-asm") in command_values
|
||||
assert not any(
|
||||
"synth" in values or "sta" in values or "netsim" in values
|
||||
for values in command_values
|
||||
"synth" in values or "sta" in values or "netsim" in values for values in command_values
|
||||
)
|
||||
|
||||
dry_run = run(
|
||||
@@ -421,6 +423,7 @@ def test_pdk_pr_regressions_cover_firmware_rtl_and_netlist() -> None:
|
||||
for profile in PDK_PR_PROFILES.values():
|
||||
commands = pdk_pr_commands(profile)
|
||||
command_values = [values for _, values in commands]
|
||||
assert command_values[0] == RTL_LINT_VALUES
|
||||
assert ("firmware",) in command_values
|
||||
assert any("SIMU=VERILATOR" in values and "firmware" in values for values in command_values)
|
||||
assert any("SIMU=IVERILOG" in values and "sim-asm" in values for values in command_values)
|
||||
@@ -429,6 +432,76 @@ def test_pdk_pr_regressions_cover_firmware_rtl_and_netlist() -> None:
|
||||
assert any("SIMU=IVERILOG" in values and "netsim" in values for values in command_values)
|
||||
|
||||
|
||||
def test_rtl_lint_warning_baseline_is_independent(tmp_path: Path) -> None:
|
||||
profile = "unit-profile"
|
||||
lint_log = tmp_path / "variant/lint/verilator/lint.log"
|
||||
lint_log.parent.mkdir(parents=True)
|
||||
lint_log.write_text(
|
||||
f"%Warning-WIDTH: {tmp_path}/rtl/top.sv:12: width mismatch\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
baseline = tmp_path / f"quality/warnings/{profile}/rtl-lint.json"
|
||||
run(
|
||||
sys.executable,
|
||||
str(ROOT / "scripts/analyze_warnings.py"),
|
||||
"baseline",
|
||||
"--root",
|
||||
str(tmp_path),
|
||||
"--profile",
|
||||
profile,
|
||||
"--tool",
|
||||
"rtl-lint",
|
||||
"--log",
|
||||
str(lint_log),
|
||||
"--output",
|
||||
str(baseline),
|
||||
)
|
||||
report = tmp_path / "rtl-lint-warnings.json"
|
||||
run(
|
||||
sys.executable,
|
||||
str(ROOT / "scripts/analyze_warnings.py"),
|
||||
"check",
|
||||
"--root",
|
||||
str(tmp_path),
|
||||
"--profile",
|
||||
profile,
|
||||
"--variant-root",
|
||||
str(tmp_path / "variant"),
|
||||
"--tool",
|
||||
"rtl-lint",
|
||||
"--output",
|
||||
str(report),
|
||||
)
|
||||
assert json.loads(report.read_text(encoding="utf-8"))["status"] == "passed"
|
||||
|
||||
lint_log.write_text(
|
||||
lint_log.read_text(encoding="utf-8")
|
||||
+ f"%Warning-UNUSED: {tmp_path}/rtl/top.sv:20: unused signal\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
failed = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(ROOT / "scripts/analyze_warnings.py"),
|
||||
"check",
|
||||
"--root",
|
||||
str(tmp_path),
|
||||
"--profile",
|
||||
profile,
|
||||
"--variant-root",
|
||||
str(tmp_path / "variant"),
|
||||
"--tool",
|
||||
"rtl-lint",
|
||||
"--output",
|
||||
str(report),
|
||||
],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
assert failed.returncode != 0
|
||||
assert json.loads(report.read_text(encoding="utf-8"))["failed_tools"] == ["rtl-lint"]
|
||||
|
||||
|
||||
def test_run_flow_writes_structured_result(tmp_path: Path) -> None:
|
||||
log = tmp_path / "flow.log"
|
||||
result = tmp_path / "result.json"
|
||||
|
||||
Reference in new issue
Block a user