Coverage for tests/test_edges.py: 92%

247 statements  

« prev     ^ index     » next       coverage.py v7.4.3, created at 2024-04-21 11:16 +0000

1"""Tests for signal edges.""" 

2 

3import os 

4from typing import Literal 

5 

6import pytest 

7 

8from signal_edges.signal import VoltageSignal 

9from signal_edges.signal.edges import IntPointPolicy, Type 

10from signal_edges.signal.generator import SignalGenerator 

11from signal_edges.signal.state_levels import StateLevels 

12 

13from .conftest import env_plots 

14 

15 

16class TestEdges: 

17 """A collection of tests for signal edges.""" 

18 

19 # pylint: disable=no-self-use 

20 

21 ## Private API ##################################################################################################### 

22 

23 ## Protected API ################################################################################################### 

24 def _get_signal_gen(self, vinit: float) -> SignalGenerator: 

25 """Creates a signal generator for testing. 

26 

27 :param vinit: The initial value. 

28 :return: The signal generator.""" 

29 return SignalGenerator(0.0, 1.0, 50, -50, vinit) 

30 

31 def _get_state_levels(self) -> StateLevels: 

32 """Generates a predefined state levels for testing. 

33 

34 :return: State levels for testing.""" 

35 return StateLevels( 

36 highest=50.0, 

37 high=40.0, 

38 high_runt=20.0, 

39 intermediate=0.0, 

40 low_runt=-20.0, 

41 low=-40.0, 

42 lowest=-50.0, 

43 ) 

44 

45 def _v(self, value_id: Literal["high", "int_high_0", "int_high_1", "int_low_0", "int_low_1", "low"]) -> float: 

46 """Returns a fixed value from a given value identifier, as described below. 

47 

48 A ``high`` value is in the ``high`` threshold. 

49 A ``int_high_1`` value is in the ``int_high`` and ``runt_high`` thresholds. 

50 A ``int_high_0`` value is in the ``int_high``, ``runt_low`` and ``runt_high`` thresholds. 

51 A ``int_low_0`` value is in the ``int_low``, ``runt_low`` and ``runt_high`` thresholds. 

52 A ``int_low_1`` value is in the ``int_low`` and ``runt_low`` thresholds. 

53 A ``low`` value is in the ``low`` threshold. 

54 

55 :param value_id: The value identifier. 

56 :param offset: An optional offset to apply to the values, keep it in the range [-4, 4]. 

57 :return: Fixed value within the specified threshold.""" 

58 if value_id == "high": 

59 return 95.0 

60 if value_id == "int_high_1": 

61 return 70.0 

62 if value_id == "int_high_0": 

63 return 60.0 

64 if value_id == "int_low_0": 

65 return 40.0 

66 if value_id == "int_low_1": 

67 return 30.0 

68 if value_id == "low": 68 ↛ 71line 68 didn't jump to line 71, because the condition on line 68 was never false

69 return 5.0 

70 

71 raise RuntimeError(f"The value identifier '{value_id}' is invalid.") 

72 

73 ## Public API ###################################################################################################### 

74 @pytest.mark.parametrize("adir", ["edges/test_falling_and_rising_edges"], indirect=True) 

75 @pytest.mark.parametrize("sval", [1, 2, 3, 8]) 

76 @pytest.mark.parametrize("ipol", list(IntPointPolicy)) 

77 def test_falling_and_rising_edges(self, adir: str, sval: int, ipol: IntPointPolicy) -> None: 

78 """Tests a signal with falling and rising edges. 

79 

80 :param adir: The path where the plots will be stored. 

81 :param sval: Number of values per section in the generated signal. 

82 :param ipol: The intermediate point policy to use.""" 

83 # Create signal generator. 

84 gen = self._get_signal_gen(self._v("high")) 

85 

86 # Build signal. 

87 gen.add_flat(sval) 

88 gen.add_edge("falling", self._v("low"), sval) 

89 gen.add_flat(sval) 

90 gen.add_edge("rising", self._v("high"), sval) 

91 gen.add_flat(sval) 

92 gen.add_edge("falling", self._v("low"), sval) 

93 gen.add_flat(sval) 

94 gen.add_edge("rising", self._v("high"), sval) 

95 gen.add_flat(sval) 

96 

97 # Generate signal and get edges. 

98 signal = VoltageSignal(*gen.generate()) 

99 edges = signal.edges(self._get_state_levels(), ipol) 

100 

101 # Plot to file. 

102 if env_plots(): 102 ↛ 103line 102 didn't jump to line 103, because the condition on line 102 was never true

103 signal.edges_plot(os.path.join(adir, f"sval{sval}_ipol{ipol}.png"), edges) 

104 

105 # Perform assertions on edges. 

106 assert len(edges) == 4 

107 

108 # Perform assertions on edge #0. 

109 assert edges[0]["edge_type"] is Type.FALLING 

110 assert edges[0]["ibegin"] == sval * 1 

111 assert edges[0]["iend"] == sval * 2 

112 assert edges[0]["ibegin"] <= edges[0]["iintermediate"] <= edges[0]["iend"] 

113 

114 # Perform assertions on edge #1. 

115 assert edges[1]["edge_type"] is Type.RISING 

116 assert edges[1]["ibegin"] == sval * 3 

117 assert edges[1]["iend"] == sval * 4 

118 assert edges[1]["ibegin"] <= edges[1]["iintermediate"] <= edges[1]["iend"] 

119 

120 # Perform assertions on edge #2. 

121 assert edges[2]["edge_type"] is Type.FALLING 

122 assert edges[2]["ibegin"] == sval * 5 

123 assert edges[2]["iend"] == sval * 6 

124 assert edges[2]["ibegin"] <= edges[2]["iintermediate"] <= edges[2]["iend"] 

125 

126 # Perform assertions on edge #3. 

127 assert edges[3]["edge_type"] is Type.RISING 

128 assert edges[3]["ibegin"] == sval * 7 

129 assert edges[3]["iend"] == sval * 8 

130 assert edges[3]["ibegin"] <= edges[3]["iintermediate"] <= edges[3]["iend"] 

131 

132 @pytest.mark.parametrize("adir", ["edges/test_runt_falling_and_rising_edges"], indirect=True) 

133 @pytest.mark.parametrize("sval", [1, 2, 3, 8]) 

134 @pytest.mark.parametrize("ipol", list(IntPointPolicy)) 

135 @pytest.mark.parametrize("rint", [0, 1]) 

136 def test_runt_falling_and_rising_edges(self, adir: str, sval: int, ipol: IntPointPolicy, rint: int) -> None: 

137 """Tests a signal with runt falling and rising edges. 

138 

139 :param adir: The path where the plots will be stored. 

140 :param sval: Number of values per section in the generated signal. 

141 :param ipol: The intermediate point policy to use. 

142 :param rint: Whether to use ``int_low_0`` or ``int_low_1`` values.""" 

143 # Create signal generator. 

144 gen = self._get_signal_gen(self._v("high")) 

145 

146 # Build signal. 

147 gen.add_flat(sval) 

148 gen.add_edge("falling", self._v("int_low_1" if rint == 1 else "int_low_0"), sval) 

149 gen.add_flat(sval) 

150 gen.add_edge("rising", self._v("high"), sval) 

151 gen.add_flat(sval) 

152 gen.add_edge("falling", self._v("int_low_1" if rint == 1 else "int_low_0"), sval) 

153 gen.add_flat(sval) 

154 gen.add_edge("rising", self._v("high"), sval) 

155 gen.add_flat(sval) 

156 

157 # Generate signal and get edges. 

158 signal = VoltageSignal(*gen.generate()) 

159 edges = signal.edges(self._get_state_levels(), ipol) 

160 

161 # Plot to file. 

162 if env_plots(): 162 ↛ 163line 162 didn't jump to line 163, because the condition on line 162 was never true

163 signal.edges_plot(os.path.join(adir, f"rint_{rint}_sval{sval}_ipol{ipol}.png"), edges) 

164 

165 # Perform assertions on edges. 

166 assert len(edges) == 4 

167 

168 # Perform assertions on edge #0. 

169 assert edges[0]["edge_type"] is Type.FALLING_RUNT 

170 assert edges[0]["ibegin"] == sval * 1 

171 assert edges[0]["iend"] == sval * 2 

172 assert edges[0]["ibegin"] <= edges[0]["iintermediate"] <= edges[0]["iend"] 

173 

174 # Perform assertions on edge #1. 

175 assert edges[1]["edge_type"] is Type.RISING_RUNT 

176 assert edges[1]["ibegin"] == sval * 3 

177 assert edges[1]["iend"] == sval * 4 

178 assert edges[1]["ibegin"] <= edges[1]["iintermediate"] <= edges[1]["iend"] 

179 

180 # Perform assertions on edge #2. 

181 assert edges[2]["edge_type"] is Type.FALLING_RUNT 

182 assert edges[2]["ibegin"] == sval * 5 

183 assert edges[2]["iend"] == sval * 6 

184 assert edges[2]["ibegin"] <= edges[2]["iintermediate"] <= edges[2]["iend"] 

185 

186 # Perform assertions on edge #3. 

187 assert edges[3]["edge_type"] is Type.RISING_RUNT 

188 assert edges[3]["ibegin"] == sval * 7 

189 assert edges[3]["iend"] == sval * 8 

190 assert edges[3]["ibegin"] <= edges[3]["iintermediate"] <= edges[3]["iend"] 

191 

192 @pytest.mark.parametrize("adir", ["edges/test_rising_and_falling_edges"], indirect=True) 

193 @pytest.mark.parametrize("sval", [1, 2, 3, 8]) 

194 @pytest.mark.parametrize("ipol", list(IntPointPolicy)) 

195 def test_rising_and_falling_edges(self, adir: str, sval: int, ipol: IntPointPolicy) -> None: 

196 """Tests a signal with rising and falling edges. 

197 

198 :param adir: The path where the plots will be stored. 

199 :param sval: Number of values per section in the generated signal. 

200 :param ipol: The intermediate point policy to use.""" 

201 # Create signal generator. 

202 gen = self._get_signal_gen(self._v("low")) 

203 

204 # Build signal. 

205 gen.add_flat(sval) 

206 gen.add_edge("rising", self._v("high"), sval) 

207 gen.add_flat(sval) 

208 gen.add_edge("falling", self._v("low"), sval) 

209 gen.add_flat(sval) 

210 gen.add_edge("rising", self._v("high"), sval) 

211 gen.add_flat(sval) 

212 gen.add_edge("falling", self._v("low"), sval) 

213 gen.add_flat(sval) 

214 

215 # Generate signal and get edges. 

216 signal = VoltageSignal(*gen.generate()) 

217 edges = signal.edges(self._get_state_levels(), ipol) 

218 

219 # Plot to file. 

220 if env_plots(): 220 ↛ 221line 220 didn't jump to line 221, because the condition on line 220 was never true

221 signal.edges_plot(os.path.join(adir, f"sv{sval}_ip{ipol}.png"), edges) 

222 

223 # Perform assertions on edges. 

224 assert len(edges) == 4 

225 

226 # Perform assertions on edge #0. 

227 assert edges[0]["edge_type"] is Type.RISING 

228 assert edges[0]["ibegin"] == sval * 1 

229 assert edges[0]["iend"] == sval * 2 

230 assert edges[0]["ibegin"] <= edges[0]["iintermediate"] <= edges[0]["iend"] 

231 

232 # Perform assertions on edge #1. 

233 assert edges[1]["edge_type"] is Type.FALLING 

234 assert edges[1]["ibegin"] == sval * 3 

235 assert edges[1]["iend"] == sval * 4 

236 assert edges[1]["ibegin"] <= edges[1]["iintermediate"] <= edges[1]["iend"] 

237 

238 # Perform assertions on edge #2. 

239 assert edges[2]["edge_type"] is Type.RISING 

240 assert edges[2]["ibegin"] == sval * 5 

241 assert edges[2]["iend"] == sval * 6 

242 assert edges[2]["ibegin"] <= edges[2]["iintermediate"] <= edges[2]["iend"] 

243 

244 # Perform assertions on edge #3. 

245 assert edges[3]["edge_type"] is Type.FALLING 

246 assert edges[3]["ibegin"] == sval * 7 

247 assert edges[3]["iend"] == sval * 8 

248 assert edges[3]["ibegin"] <= edges[3]["iintermediate"] <= edges[3]["iend"] 

249 

250 @pytest.mark.parametrize("adir", ["edges/test_runt_rising_and_falling_edges"], indirect=True) 

251 @pytest.mark.parametrize("sval", [1, 2, 3, 8]) 

252 @pytest.mark.parametrize("ipol", list(IntPointPolicy)) 

253 @pytest.mark.parametrize("rint", [0, 1]) 

254 def test_runt_rising_and_falling_edges(self, adir: str, sval: int, ipol: IntPointPolicy, rint: int) -> None: 

255 """Tests a signal with runt rising and falling edges. 

256 

257 :param adir: The path where the plots will be stored. 

258 :param sval: Number of values per section in the generated signal. 

259 :param ipol: The intermediate point policy to use. 

260 :param rint: Whether to use ``int_high_0`` or ``int_high_1`` values.""" 

261 # Create signal generator. 

262 gen = self._get_signal_gen(self._v("low")) 

263 

264 # Build signal. 

265 gen.add_flat(sval) 

266 gen.add_edge("rising", self._v("int_high_1" if rint == 1 else "int_high_0"), sval) 

267 gen.add_flat(sval) 

268 gen.add_edge("falling", self._v("low"), sval) 

269 gen.add_flat(sval) 

270 gen.add_edge("rising", self._v("int_high_1" if rint == 1 else "int_high_0"), sval) 

271 gen.add_flat(sval) 

272 gen.add_edge("falling", self._v("low"), sval) 

273 gen.add_flat(sval) 

274 

275 # Generate signal and get edges. 

276 signal = VoltageSignal(*gen.generate()) 

277 edges = signal.edges(self._get_state_levels(), ipol) 

278 

279 # Plot to file. 

280 if env_plots(): 280 ↛ 281line 280 didn't jump to line 281, because the condition on line 280 was never true

281 signal.edges_plot(os.path.join(adir, f"rint_{rint}_sval{sval}_ipol{ipol}.png"), edges) 

282 

283 # Perform assertions on edges. 

284 assert len(edges) == 4 

285 

286 # Perform assertions on edge #0. 

287 assert edges[0]["edge_type"] is Type.RISING_RUNT 

288 assert edges[0]["ibegin"] == sval * 1 

289 assert edges[0]["iend"] == sval * 2 

290 assert edges[0]["ibegin"] <= edges[0]["iintermediate"] <= edges[0]["iend"] 

291 

292 # Perform assertions on edge #1. 

293 assert edges[1]["edge_type"] is Type.FALLING_RUNT 

294 assert edges[1]["ibegin"] == sval * 3 

295 assert edges[1]["iend"] == sval * 4 

296 assert edges[1]["ibegin"] <= edges[1]["iintermediate"] <= edges[1]["iend"] 

297 

298 # Perform assertions on edge #2. 

299 assert edges[2]["edge_type"] is Type.RISING_RUNT 

300 assert edges[2]["ibegin"] == sval * 5 

301 assert edges[2]["iend"] == sval * 6 

302 assert edges[2]["ibegin"] <= edges[2]["iintermediate"] <= edges[2]["iend"] 

303 

304 # Perform assertions on edge #3. 

305 assert edges[3]["edge_type"] is Type.FALLING_RUNT 

306 assert edges[3]["ibegin"] == sval * 7 

307 assert edges[3]["iend"] == sval * 8 

308 assert edges[3]["ibegin"] <= edges[3]["iintermediate"] <= edges[3]["iend"] 

309 

310 @pytest.mark.parametrize("adir", ["edges/test_special_edges"], indirect=True) 

311 @pytest.mark.parametrize("sval", [1, 2, 3, 8]) 

312 def test_special_edges(self, adir: str, sval: int) -> None: 

313 """Tests special cases that do not fit anywhere else. 

314 

315 :param adir: The path where the plots will be stored. 

316 :param sval: Number of values per section in the generated signal.""" 

317 # pylint: disable=too-many-statements 

318 

319 signal_num = 0 

320 

321 ## 

322 ## Signal without valid low or high values that oscillates in intermediate values. 

323 ## 

324 

325 # Create signal generator. 

326 gen = self._get_signal_gen(self._v("int_high_0")) 

327 

328 # Build signal. 

329 gen.add_flat(sval) 

330 gen.add_edge("falling", self._v("int_low_0"), sval) 

331 gen.add_flat(sval) 

332 gen.add_edge("rising", self._v("int_high_0"), sval) 

333 gen.add_flat(sval) 

334 gen.add_edge("falling", self._v("int_low_0"), sval) 

335 gen.add_flat(sval) 

336 

337 # Generate signal. 

338 signal = VoltageSignal(*gen.generate()) 

339 edges = signal.edges(self._get_state_levels()) 

340 

341 # Plot to file. 

342 if env_plots(): 342 ↛ 343line 342 didn't jump to line 343, because the condition on line 342 was never true

343 signal.edges_plot(os.path.join(adir, f"{signal_num:03}_sval{sval}.png"), edges) 

344 signal_num += 1 

345 

346 # Perform assertions on edges. 

347 assert len(edges) == 0 

348 

349 ## 

350 ## Signal that settles at runt low after going low. 

351 ## 

352 

353 # Create signal generator. 

354 gen = self._get_signal_gen(self._v("high")) 

355 

356 # Build signal. 

357 gen.add_flat(sval) 

358 gen.add_edge("falling", self._v("low"), sval) 

359 gen.add_flat(sval) 

360 gen.add_edge("rising", self._v("int_low_0"), sval) 

361 gen.add_flat(sval) 

362 

363 # Generate signal. 

364 signal = VoltageSignal(*gen.generate()) 

365 edges = signal.edges(self._get_state_levels()) 

366 

367 # Plot to file. 

368 if env_plots(): 368 ↛ 369line 368 didn't jump to line 369, because the condition on line 368 was never true

369 signal.edges_plot(os.path.join(adir, f"{signal_num:03}_sval{sval}.png"), edges) 

370 signal_num += 1 

371 

372 # Perform assertions on edges. 

373 assert len(edges) == 1 

374 assert edges[0]["edge_type"] is Type.FALLING 

375 

376 ## 

377 ## Signal that settles at runt high after going high. 

378 ## 

379 

380 # Create signal generator. 

381 gen = self._get_signal_gen(self._v("low")) 

382 

383 # Build signal. 

384 gen.add_flat(sval) 

385 gen.add_edge("rising", self._v("high"), sval) 

386 gen.add_flat(sval) 

387 gen.add_edge("falling", self._v("int_high_0"), sval) 

388 gen.add_flat(sval) 

389 

390 # Generate signal. 

391 signal = VoltageSignal(*gen.generate()) 

392 edges = signal.edges(self._get_state_levels()) 

393 

394 # Plot to file. 

395 if env_plots(): 395 ↛ 396line 395 didn't jump to line 396, because the condition on line 395 was never true

396 signal.edges_plot(os.path.join(adir, f"{signal_num:03}_sval{sval}.png"), edges) 

397 signal_num += 1 

398 

399 # Perform assertions on edges. 

400 assert len(edges) == 1 

401 assert edges[0]["edge_type"] is Type.RISING 

402 

403 ## 

404 ## Signal that settles at runt low and then goes high and low. 

405 ## 

406 

407 # Create signal generator. 

408 gen = self._get_signal_gen(self._v("high")) 

409 

410 # Build signal. 

411 gen.add_flat(sval) 

412 gen.add_edge("falling", self._v("int_low_0"), sval) 

413 gen.add_flat(sval) 

414 gen.add_edge("rising", self._v("high"), sval) 

415 gen.add_flat(sval) 

416 gen.add_edge("falling", self._v("low"), sval) 

417 gen.add_flat(sval) 

418 

419 # Generate signal. 

420 signal = VoltageSignal(*gen.generate()) 

421 edges = signal.edges(self._get_state_levels()) 

422 

423 # Plot to file. 

424 if env_plots(): 424 ↛ 425line 424 didn't jump to line 425, because the condition on line 424 was never true

425 signal.edges_plot(os.path.join(adir, f"{signal_num:03}_sval{sval}.png"), edges) 

426 signal_num += 1 

427 

428 # Perform assertions on edges. 

429 assert len(edges) == 3 

430 assert edges[0]["edge_type"] is Type.FALLING_RUNT 

431 assert edges[1]["edge_type"] is Type.RISING_RUNT 

432 assert edges[2]["edge_type"] is Type.FALLING 

433 

434 ## 

435 ## Signal that settles at runt high and then goes low and high. 

436 ## 

437 

438 # Create signal generator. 

439 gen = self._get_signal_gen(self._v("low")) 

440 

441 # Build signal. 

442 gen.add_flat(sval) 

443 gen.add_edge("rising", self._v("int_high_0"), sval) 

444 gen.add_flat(sval) 

445 gen.add_edge("falling", self._v("low"), sval) 

446 gen.add_flat(sval) 

447 gen.add_edge("rising", self._v("high"), sval) 

448 gen.add_flat(sval) 

449 

450 # Generate signal. 

451 signal = VoltageSignal(*gen.generate()) 

452 edges = signal.edges(self._get_state_levels()) 

453 

454 # Plot to file. 

455 if env_plots(): 455 ↛ 456line 455 didn't jump to line 456, because the condition on line 455 was never true

456 signal.edges_plot(os.path.join(adir, f"{signal_num:03}_sval{sval}.png"), edges) 

457 signal_num += 1 

458 

459 # Perform assertions on edges. 

460 assert len(edges) == 3 

461 assert edges[0]["edge_type"] is Type.RISING_RUNT 

462 assert edges[1]["edge_type"] is Type.FALLING_RUNT 

463 assert edges[2]["edge_type"] is Type.RISING