Coverage for tests/unit_tests/common/test_build_report.py: 100%
39 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-23 03:53 +0000
« prev ^ index » next coverage.py v7.16.1, created at 2026-09-23 03:53 +0000
1"""Test for build_report.py."""
3from pathlib import Path
4import pytest
5from tests.conftest import ApioRunner
6from apio.common.build_report import (
7 ResourceReport,
8 ClockReport,
9 BuildReport,
10 read_build_report,
11)
13# -- Test json for ECP5. Having 'TRELLIS' in a utilization key indicates
14# -- that's it's ECP5.
15ECP5_TEST_SUMMARY = """
16{
17 "other-stuff": {
18 "bla-bla": "bla-bla"
19 },
20 "utilization": {
21 "TRELLIS_ECLKBUF": {
22 "available": 8,
23 "used": 0
24 },
25 "SIOLOGIC": {
26 "available": 69,
27 "used": 0
28 },
29 "TRELLIS_COMB": {
30 "available": 24288,
31 "used": 12144
32 }
33 },
34 "fmax": {
35 "$glbnet$MY_CLK$TRELLIS_IO_IN": {
36 "achieved": 295.420,
37 "constraint": 12
38 }
39 }
40}
41"""
44# -- Test json for non ECP5. The utilization keys do not contain "TRELLIS".
45# -- Note that the user clock name "CLK" is at different part of the fmax keys
46# -- compared to ECP5 above.
47NON_ECP5_TEST_SUMMARY = """
48{
49 "other-stuff": {
50 "bla-bla": "bla-bla"
51 },
52 "utilization": {
53 "ICESTORM_PLL": {
54 "available": 2,
55 "used": 0
56 },
57 "ICESTORM_LC": {
58 "available": 7680,
59 "used": 27
60 }
61 },
62 "fmax": {
63 "MY_CLK$SB_IO_IN_$glb_clk": {
64 "achieved": 194.363,
65 "constraint": 12
66 }
67 }
68}
69"""
72def test_ecp5_read_build_report(apio_runner):
73 """Tests the read_build_report() function for ECP 5 hardware.pnr."""
75 with apio_runner.in_sandbox() as sb:
77 file_path = Path("_build/default/hardware.pnr")
79 sb.write_file(file_path, ECP5_TEST_SUMMARY)
81 build_report = read_build_report(file_path)
82 assert isinstance(build_report, BuildReport)
84 print(build_report)
86 assert build_report == BuildReport(
87 resources=[
88 ResourceReport(
89 name="SIOLOGIC", available=69, used=0, percentage=0.0
90 ),
91 ResourceReport(
92 name="TRELLIS_COMB",
93 available=24288,
94 used=12144,
95 percentage=50.0,
96 ),
97 ResourceReport(
98 name="TRELLIS_ECLKBUF", available=8, used=0, percentage=0.0
99 ),
100 ],
101 clocks=[ClockReport(name="MY_CLK", fmax_mhz=295.420)],
102 )
105def test_non_ecp5_read_build_report(apio_runner: ApioRunner):
106 """Tests the read_build_report() function for non ECP 5 hardware.pnr."""
108 with apio_runner.in_sandbox() as sb:
110 file_path = Path("_build/default/hardware.pnr")
112 sb.write_file(file_path, NON_ECP5_TEST_SUMMARY)
114 build_report = read_build_report(file_path)
115 assert isinstance(build_report, BuildReport)
117 print(build_report)
119 assert build_report == BuildReport(
120 resources=[
121 ResourceReport(
122 name="ICESTORM_LC",
123 available=7680,
124 used=27,
125 percentage=0.3515625,
126 ),
127 ResourceReport(
128 name="ICESTORM_PLL", available=2, used=0, percentage=0.0
129 ),
130 ],
131 clocks=[ClockReport(name="MY_CLK", fmax_mhz=194.363)],
132 )
135def test_hardware_pnr_reading_failure(apio_runner: ApioRunner):
136 """Tests the case where reading hardware.pne fails."""
137 with apio_runner.in_sandbox():
138 file_path = Path("_build/default/hardware.pnr")
139 with apio_runner.with_logger() as log:
140 with pytest.raises(SystemExit) as e:
141 # -- Since we didn't create hardware.pnr, reading should fail.
142 read_build_report(file_path)
143 assert e.value.code == 1
144 assert "Error: Failed to read" in log.out
147def test_hardware_pnr_parsing_failure(apio_runner: ApioRunner):
148 """Tests the case where reading hardware.pne fails."""
149 with apio_runner.in_sandbox() as sb:
150 file_path = Path("_build/default/hardware.pnr")
151 sb.write_file(file_path, "Broken JSON file")
152 with apio_runner.with_logger() as log:
153 with pytest.raises(SystemExit) as e:
154 # -- Since we didn't create hardware.pnr, reading should fail.
155 read_build_report(file_path)
156 assert e.value.code == 1
157 assert "Error: Failed parsing json file" in log.out