Coverage for tests/unit_tests/test_resources.py: 98%

82 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-06 10:20 +0000

1""" 

2Tests of apio_context.py 

3""" 

4 

5import re 

6from tests.conftest import ApioRunner 

7from apio.apio_context import ( 

8 ApioContext, 

9 PackagesPolicy, 

10 ProjectPolicy, 

11 RemoteConfigPolicy, 

12) 

13from apio.utils.resource_util import ( 

14 validate_config, 

15 validate_packages, 

16 validate_platforms, 

17 _validate_board_info, 

18 _validate_fpga_info, 

19 _validate_programmer_info, 

20 collect_project_resources, 

21 validate_project_resources, 

22) 

23 

24 

25def lc_part_num(part_num: str) -> str: 

26 """Convert an fpga part number to a lower-case id.""" 

27 return part_num.lower().replace("/", "-") 

28 

29 

30def test_resources_references(apio_runner: ApioRunner): 

31 """Tests the consistency of the board references to fpgas and 

32 programmers.""" 

33 

34 with apio_runner.in_sandbox(): 

35 

36 # -- Create an apio context so we can access the resources. 

37 apio_ctx = ApioContext( 

38 project_policy=ProjectPolicy.NO_PROJECT, 

39 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

40 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

41 ) 

42 

43 unused_programmers = set(apio_ctx.programmers.keys()) 

44 

45 for board_id, board_info in apio_ctx.boards.items(): 

46 # -- Prepare a context message for failing assertions. 

47 board_msg = f"While testing board {board_id}" 

48 

49 # -- Assert that required fields exist. 

50 assert "fpga-id" in board_info, board_msg 

51 assert "programmer" in board_info, board_msg 

52 assert "id" in board_info["programmer"], board_msg 

53 

54 # -- Check that the fpga exists. 

55 board_fpga_id = board_info["fpga-id"] 

56 assert apio_ctx.fpgas[board_fpga_id], board_msg 

57 

58 # -- Check that the programmer exists. 

59 board_programmer_id = board_info["programmer"]["id"] 

60 assert apio_ctx.programmers[board_programmer_id], board_msg 

61 

62 # -- Track unused programmers. Since a programmer may be used 

63 # -- by more than one board, it may already be removed. 

64 if board_programmer_id in unused_programmers: 

65 unused_programmers.remove(board_programmer_id) 

66 

67 # -- We should end up with an empty set of unused programmers. 

68 assert not unused_programmers, unused_programmers 

69 

70 

71def test_resources_ids_and_order(apio_runner: ApioRunner): 

72 """Tests the formats of boards, fpgas, and programmers names.""" 

73 

74 # -- For boards we allow lower-case-0-9. 

75 board_id_regex = re.compile(r"^[a-z][a-z0-9-]*$") 

76 

77 # -- For fpga ids we allow lower-case-0-9. 

78 fpga_id_regex = re.compile(r"^[a-z][a-z0-9-/]*$") 

79 

80 # -- For programmer ids we allow lower-case-0-9. 

81 programmer_id_regex = re.compile(r"^[a-z][a-z0-9-]*$") 

82 

83 with apio_runner.in_sandbox(): 

84 

85 # -- Create an apio context so we can access the resources. 

86 apio_ctx = ApioContext( 

87 project_policy=ProjectPolicy.NO_PROJECT, 

88 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

89 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

90 ) 

91 

92 # -- Test the format of the board ids. 

93 for board_id in apio_ctx.boards.keys(): 

94 assert board_id_regex.match(board_id), f"{board_id=}" 

95 

96 # -- Test the format of the fpgas ids and part numbers. 

97 for fpga_id, fgpa_info in apio_ctx.fpgas.items(): 

98 assert fpga_id_regex.match(fpga_id), f"{fpga_id=}" 

99 # Fpga id is either the fpga part num converted to lower-case 

100 # or its the lower-case part num with a suffix that starts with 

101 # '-'. E.g, for part num 'PART-NUM', the fpga id can be 'part-num' 

102 # or 'part-num-somethings' 

103 lc_part = lc_part_num(fgpa_info["part-num"]) 

104 assert fpga_id == lc_part or fpga_id.startswith( 

105 lc_part + "-" 

106 ), f"{fpga_id=}" 

107 

108 # -- Test the format of the programmers ids. 

109 for programmer_id in apio_ctx.programmers.keys(): 

110 assert programmer_id_regex.match( 

111 programmer_id 

112 ), f"{programmer_id=}" 

113 

114 

115def test_resources_are_valid(apio_runner: ApioRunner): 

116 """Validate resources against a schema.""" 

117 with apio_runner.in_sandbox(): 

118 

119 apio_ctx = ApioContext( 

120 project_policy=ProjectPolicy.NO_PROJECT, 

121 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

122 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

123 ) 

124 

125 validate_config(apio_ctx.config) 

126 validate_packages(apio_ctx.all_packages) 

127 validate_platforms(apio_ctx.platforms) 

128 

129 for fpga_id, fpga_info in apio_ctx.fpgas.items(): 

130 _validate_fpga_info(fpga_id, fpga_info) 

131 

132 for programmer_id, programmer_info in apio_ctx.programmers.items(): 

133 _validate_programmer_info(programmer_id, programmer_info) 

134 

135 for board_id, board_info in apio_ctx.boards.items(): 

136 _validate_board_info(board_id, board_info) 

137 

138 # -- Collect project resources for this board. This tests that 

139 # -- the references are ok. 

140 project_resources = collect_project_resources( 

141 board_id, apio_ctx.boards, apio_ctx.fpgas, apio_ctx.programmers 

142 ) 

143 

144 # -- Validate the project resources. 

145 validate_project_resources(project_resources) 

146 

147 

148def test_fpga_definitions(apio_runner: ApioRunner): 

149 """Tests the fields of the fpga definitions.""" 

150 

151 with apio_runner.in_sandbox(): 

152 

153 # -- Create an apio context so we can access the resources. 

154 apio_ctx = ApioContext( 

155 project_policy=ProjectPolicy.NO_PROJECT, 

156 remote_config_policy=RemoteConfigPolicy.CACHED_OK, 

157 packages_policy=PackagesPolicy.ENSURE_PACKAGES, 

158 ) 

159 

160 for fpga_id, fpga_info in apio_ctx.fpgas.items(): 

161 

162 context = f"In fpga definition {fpga_id}" 

163 

164 # -- Verify the "arch" field. 

165 assert "arch" in fpga_info, context 

166 arch = fpga_info["arch"] 

167 

168 # -- Ice40 

169 if arch == "ice40": 

170 assert fpga_info.keys() == { 

171 "part-num", 

172 "arch", 

173 "size", 

174 "type", 

175 "pack", 

176 }, context 

177 assert fpga_info["part-num"], context 

178 assert fpga_info["arch"], context 

179 assert fpga_info["size"], context 

180 assert fpga_info["type"], context 

181 assert fpga_info["pack"], context 

182 continue 

183 

184 # -- Ecp5 

185 if arch == "ecp5": 

186 assert set(fpga_info.keys()) == { 

187 "part-num", 

188 "arch", 

189 "size", 

190 "type", 

191 "pack", 

192 "speed", 

193 }, context 

194 assert fpga_info["part-num"], context 

195 assert fpga_info["arch"], context 

196 assert fpga_info["size"], context 

197 assert fpga_info["type"], context 

198 assert fpga_info["pack"], context 

199 assert fpga_info["speed"], context 

200 continue 

201 

202 # -- Gowin 

203 if arch == "gowin": 203 ↛ 217line 203 didn't jump to line 217 because the condition on line 203 was always true

204 assert fpga_info.keys() == { 

205 "part-num", 

206 "arch", 

207 "size", 

208 "type", 

209 }, context 

210 assert fpga_info["part-num"], context 

211 assert fpga_info["arch"], context 

212 assert fpga_info["size"], context 

213 assert fpga_info["type"], context 

214 continue 

215 

216 # -- Unknown arch 

217 raise ValueError(f"Unknown arch value: {arch}")