Coverage for tests/unit_tests/common/test_build_report.py: 100%

43 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-09 01:55 +0000

1"""Test for build_report.py.""" 

2 

3from pathlib import Path 

4import pytest 

5from pytest import LogCaptureFixture 

6from tests.conftest import ApioRunner 

7from apio.common.apio_console import cunstyle 

8from apio.common.build_report import ( 

9 ResourceReport, 

10 ClockReport, 

11 BuildReport, 

12 read_build_report, 

13) 

14 

15# -- Test json for ECP5. Having 'TRELLIS' in a utilization key indicates 

16# -- that's it's ECP5. 

17ECP5_TEST_SUMMARY = """ 

18{ 

19 "other-stuff": { 

20 "bla-bla": "bla-bla" 

21 }, 

22 "utilization": { 

23 "TRELLIS_ECLKBUF": { 

24 "available": 8, 

25 "used": 0 

26 }, 

27 "SIOLOGIC": { 

28 "available": 69, 

29 "used": 0 

30 }, 

31 "TRELLIS_COMB": { 

32 "available": 24288, 

33 "used": 12144 

34 } 

35 }, 

36 "fmax": { 

37 "$glbnet$MY_CLK$TRELLIS_IO_IN": { 

38 "achieved": 295.420, 

39 "constraint": 12 

40 } 

41 } 

42} 

43""" 

44 

45 

46# -- Test json for non ECP5. The utilization keys do not contain "TRELLIS". 

47# -- Note that the user clock name "CLK" is at different part of the fmax keys 

48# -- compared to ECP5 above. 

49NON_ECP5_TEST_SUMMARY = """ 

50{ 

51 "other-stuff": { 

52 "bla-bla": "bla-bla" 

53 }, 

54 "utilization": { 

55 "ICESTORM_PLL": { 

56 "available": 2, 

57 "used": 0 

58 }, 

59 "ICESTORM_LC": { 

60 "available": 7680, 

61 "used": 27 

62 } 

63 }, 

64 "fmax": { 

65 "MY_CLK$SB_IO_IN_$glb_clk": { 

66 "achieved": 194.363, 

67 "constraint": 12 

68 } 

69 } 

70} 

71""" 

72 

73 

74def test_ecp5_read_build_report(apio_runner): 

75 """Tests the read_build_report() function for ECP 5 hardware.pnr.""" 

76 

77 with apio_runner.in_sandbox() as sb: 

78 

79 file_path = Path("_build/default/hardware.pnr") 

80 

81 sb.write_file(file_path, ECP5_TEST_SUMMARY) 

82 

83 build_report = read_build_report(file_path) 

84 assert isinstance(build_report, BuildReport) 

85 

86 print(build_report) 

87 

88 assert build_report == BuildReport( 

89 resources=[ 

90 ResourceReport( 

91 name="SIOLOGIC", available=69, used=0, percentage=0.0 

92 ), 

93 ResourceReport( 

94 name="TRELLIS_COMB", 

95 available=24288, 

96 used=12144, 

97 percentage=50.0, 

98 ), 

99 ResourceReport( 

100 name="TRELLIS_ECLKBUF", available=8, used=0, percentage=0.0 

101 ), 

102 ], 

103 clocks=[ClockReport(name="MY_CLK", fmax_mhz=295.420)], 

104 ) 

105 

106 

107def test_non_ecp5_read_build_report( 

108 apio_runner: ApioRunner, capsys: LogCaptureFixture 

109): 

110 """Tests the read_build_report() function for non ECP 5 hardware.pnr.""" 

111 

112 with apio_runner.in_sandbox() as sb: 

113 

114 file_path = Path("_build/default/hardware.pnr") 

115 

116 sb.write_file(file_path, NON_ECP5_TEST_SUMMARY) 

117 

118 build_report = read_build_report(file_path) 

119 assert isinstance(build_report, BuildReport) 

120 

121 print(build_report) 

122 

123 assert build_report == BuildReport( 

124 resources=[ 

125 ResourceReport( 

126 name="ICESTORM_LC", 

127 available=7680, 

128 used=27, 

129 percentage=0.3515625, 

130 ), 

131 ResourceReport( 

132 name="ICESTORM_PLL", available=2, used=0, percentage=0.0 

133 ), 

134 ], 

135 clocks=[ClockReport(name="MY_CLK", fmax_mhz=194.363)], 

136 ) 

137 

138 

139def test_hardware_pnr_reading_failure( 

140 apio_runner: ApioRunner, capsys: LogCaptureFixture 

141): 

142 """Tests the case where reading hardware.pne fails.""" 

143 with apio_runner.in_sandbox(): 

144 file_path = Path("_build/default/hardware.pnr") 

145 capsys.readouterr() # Reset capture 

146 with pytest.raises(SystemExit) as e: 

147 # -- Since we didn't create hardware.pnr, reading should fail. 

148 read_build_report(file_path) 

149 captured = capsys.readouterr() 

150 assert e.value.code == 1 

151 assert "Error: Failed to read" in cunstyle(captured.out) 

152 

153 

154def test_hardware_pnr_parsing_failure( 

155 apio_runner: ApioRunner, capsys: LogCaptureFixture 

156): 

157 """Tests the case where reading hardware.pne fails.""" 

158 with apio_runner.in_sandbox() as sb: 

159 file_path = Path("_build/default/hardware.pnr") 

160 sb.write_file(file_path, "Broken JSON file") 

161 capsys.readouterr() # Reset capture 

162 with pytest.raises(SystemExit) as e: 

163 # -- Since we didn't create hardware.pnr, reading should fail. 

164 read_build_report(file_path) 

165 captured = capsys.readouterr() 

166 assert e.value.code == 1 

167 assert "Error: Failed parsing json file" in cunstyle(captured.out)