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
« prev ^ index » next coverage.py v7.4.3, created at 2024-04-21 11:16 +0000
1"""Tests for signal edges."""
3import os
4from typing import Literal
6import pytest
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
13from .conftest import env_plots
16class TestEdges:
17 """A collection of tests for signal edges."""
19 # pylint: disable=no-self-use
21 ## Private API #####################################################################################################
23 ## Protected API ###################################################################################################
24 def _get_signal_gen(self, vinit: float) -> SignalGenerator:
25 """Creates a signal generator for testing.
27 :param vinit: The initial value.
28 :return: The signal generator."""
29 return SignalGenerator(0.0, 1.0, 50, -50, vinit)
31 def _get_state_levels(self) -> StateLevels:
32 """Generates a predefined state levels for testing.
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 )
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.
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.
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
71 raise RuntimeError(f"The value identifier '{value_id}' is invalid.")
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.
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"))
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)
97 # Generate signal and get edges.
98 signal = VoltageSignal(*gen.generate())
99 edges = signal.edges(self._get_state_levels(), ipol)
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)
105 # Perform assertions on edges.
106 assert len(edges) == 4
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"]
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"]
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"]
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"]
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.
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"))
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)
157 # Generate signal and get edges.
158 signal = VoltageSignal(*gen.generate())
159 edges = signal.edges(self._get_state_levels(), ipol)
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)
165 # Perform assertions on edges.
166 assert len(edges) == 4
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"]
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"]
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"]
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"]
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.
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"))
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)
215 # Generate signal and get edges.
216 signal = VoltageSignal(*gen.generate())
217 edges = signal.edges(self._get_state_levels(), ipol)
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)
223 # Perform assertions on edges.
224 assert len(edges) == 4
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"]
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"]
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"]
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"]
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.
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"))
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)
275 # Generate signal and get edges.
276 signal = VoltageSignal(*gen.generate())
277 edges = signal.edges(self._get_state_levels(), ipol)
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)
283 # Perform assertions on edges.
284 assert len(edges) == 4
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"]
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"]
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"]
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"]
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.
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
319 signal_num = 0
321 ##
322 ## Signal without valid low or high values that oscillates in intermediate values.
323 ##
325 # Create signal generator.
326 gen = self._get_signal_gen(self._v("int_high_0"))
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)
337 # Generate signal.
338 signal = VoltageSignal(*gen.generate())
339 edges = signal.edges(self._get_state_levels())
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
346 # Perform assertions on edges.
347 assert len(edges) == 0
349 ##
350 ## Signal that settles at runt low after going low.
351 ##
353 # Create signal generator.
354 gen = self._get_signal_gen(self._v("high"))
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)
363 # Generate signal.
364 signal = VoltageSignal(*gen.generate())
365 edges = signal.edges(self._get_state_levels())
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
372 # Perform assertions on edges.
373 assert len(edges) == 1
374 assert edges[0]["edge_type"] is Type.FALLING
376 ##
377 ## Signal that settles at runt high after going high.
378 ##
380 # Create signal generator.
381 gen = self._get_signal_gen(self._v("low"))
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)
390 # Generate signal.
391 signal = VoltageSignal(*gen.generate())
392 edges = signal.edges(self._get_state_levels())
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
399 # Perform assertions on edges.
400 assert len(edges) == 1
401 assert edges[0]["edge_type"] is Type.RISING
403 ##
404 ## Signal that settles at runt low and then goes high and low.
405 ##
407 # Create signal generator.
408 gen = self._get_signal_gen(self._v("high"))
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)
419 # Generate signal.
420 signal = VoltageSignal(*gen.generate())
421 edges = signal.edges(self._get_state_levels())
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
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
434 ##
435 ## Signal that settles at runt high and then goes low and high.
436 ##
438 # Create signal generator.
439 gen = self._get_signal_gen(self._v("low"))
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)
450 # Generate signal.
451 signal = VoltageSignal(*gen.generate())
452 edges = signal.edges(self._get_state_levels())
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
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