""" Tests for compute_groups() and compute_bdshot() in fc_doc_generator.py. """ import fc_doc_generator as fcdg def _parse(board_path): """Helper: run both timer parsers and return (timers, channels).""" tc = fcdg.parse_timer_config(board_path) return tc.get("timers", []), tc.get("channels", []) # --------------------------------------------------------------------------- # compute_groups # --------------------------------------------------------------------------- class TestComputeGroups: def test_stm32h7_all_dshot_group_count(self, board_stm32h7_all_dshot): timers, channels = _parse(board_stm32h7_all_dshot) groups = fcdg.compute_groups(timers, channels) assert len(groups) == 4 def test_stm32h7_all_dshot_outputs_per_group(self, board_stm32h7_all_dshot): timers, channels = _parse(board_stm32h7_all_dshot) groups = fcdg.compute_groups(timers, channels) assert all(len(g["outputs"]) == 2 for g in groups) def test_stm32h7_all_dshot_all_dshot(self, board_stm32h7_all_dshot): timers, channels = _parse(board_stm32h7_all_dshot) groups = fcdg.compute_groups(timers, channels) assert all(g["dshot"] for g in groups) def test_stm32h7_all_dshot_output_indices(self, board_stm32h7_all_dshot): timers, channels = _parse(board_stm32h7_all_dshot) groups = fcdg.compute_groups(timers, channels) all_outputs = sorted(o for g in groups for o in g["outputs"]) assert all_outputs == list(range(1, 9)) def test_mixed_io_group_count(self, board_stm32h7_mixed_io): timers, channels = _parse(board_stm32h7_mixed_io) groups = fcdg.compute_groups(timers, channels) assert len(groups) == 3 def test_mixed_io_timer12_not_dshot(self, board_stm32h7_mixed_io): timers, channels = _parse(board_stm32h7_mixed_io) groups = fcdg.compute_groups(timers, channels) timer12_group = next(g for g in groups if "Timer12" in g["timer"]) assert timer12_group["dshot"] is False assert timer12_group["non_dshot_outputs"] == timer12_group["outputs"] def test_mixed_io_dshot_timers(self, board_stm32h7_mixed_io): timers, channels = _parse(board_stm32h7_mixed_io) groups = fcdg.compute_groups(timers, channels) dshot_groups = [g for g in groups if g["dshot"]] assert len(dshot_groups) == 2 def test_imxrt_groups_by_pwm_prefix(self, board_imxrt): timers, channels = _parse(board_imxrt) groups = fcdg.compute_groups(timers, channels) timer_names = {g["timer"] for g in groups} assert "PWM2" in timer_names assert "PWM4" in timer_names def test_imxrt_total_outputs(self, board_imxrt): timers, channels = _parse(board_imxrt) groups = fcdg.compute_groups(timers, channels) all_outputs = sorted(o for g in groups for o in g["outputs"]) assert all_outputs == list(range(1, 9)) def test_empty_input(self): groups = fcdg.compute_groups([], []) assert groups == [] def test_single_channel(self): timers = [{"timer": "Timer1", "dshot": True}] channels = [{"output_index": 1, "timer": "Timer1", "channel": "Channel1", "channel_index": 0, "is_dshot_channel": True}] groups = fcdg.compute_groups(timers, channels) assert len(groups) == 1 assert groups[0]["outputs"] == [1] assert groups[0]["dshot"] is True # --------------------------------------------------------------------------- # compute_bdshot # --------------------------------------------------------------------------- class TestComputeBdshot: def test_stm32h7_all_dshot_full_bdshot(self, board_stm32h7_all_dshot): timers, channels = _parse(board_stm32h7_all_dshot) groups = fcdg.compute_groups(timers, channels) groups = fcdg.compute_bdshot(groups, channels, "stm32h7") for g in groups: assert g["bdshot"] is True assert set(g["bdshot_outputs"]) == set(g["outputs"]) assert g["bdshot_output_only"] == [] def test_stm32h7_timer4_ch4_bdshot_output_only(self): # Timer4, channel indices 0,1,2 → full bdshot; index 3 (CH4) → output_only # output_index is 1-based (matching real parse_timer_config behaviour) timers = [{"timer": "Timer4", "dshot": True}] channels = [ {"output_index": 1, "timer": "Timer4", "channel": "Channel1", "channel_index": 0, "is_dshot_channel": True}, {"output_index": 2, "timer": "Timer4", "channel": "Channel2", "channel_index": 1, "is_dshot_channel": True}, {"output_index": 3, "timer": "Timer4", "channel": "Channel3", "channel_index": 2, "is_dshot_channel": True}, {"output_index": 4, "timer": "Timer4", "channel": "Channel4", "channel_index": 3, "is_dshot_channel": True}, ] groups = fcdg.compute_groups(timers, channels) groups = fcdg.compute_bdshot(groups, channels, "stm32h7") g = groups[0] assert g["bdshot"] is True assert 1 in g["bdshot_outputs"] # output 1 = channel_index 0 → full assert 4 in g["bdshot_output_only"] # output 4 = channel_index 3 → output-only def test_stm32f4_no_bdshot(self, board_stm32f4): timers, channels = _parse(board_stm32f4) groups = fcdg.compute_groups(timers, channels) groups = fcdg.compute_bdshot(groups, channels, "stm32f4") assert all(not g["bdshot"] for g in groups) assert all(g["bdshot_outputs"] == [] for g in groups) def test_imxrt_all_bdshot(self, board_imxrt): timers, channels = _parse(board_imxrt) groups = fcdg.compute_groups(timers, channels) groups = fcdg.compute_bdshot(groups, channels, "imxrt") dshot_groups = [g for g in groups if g["dshot"]] for g in dshot_groups: assert g["bdshot"] is True assert set(g["bdshot_outputs"]) == set(g["dshot_outputs"]) assert g["bdshot_output_only"] == [] def test_no_dshot_group_has_no_bdshot(self, board_stm32h7_mixed_io): timers, channels = _parse(board_stm32h7_mixed_io) groups = fcdg.compute_groups(timers, channels) groups = fcdg.compute_bdshot(groups, channels, "stm32h7") no_dshot = [g for g in groups if not g["dshot"]] for g in no_dshot: assert g["bdshot"] is False assert g["bdshot_outputs"] == [] assert g["bdshot_output_only"] == [] def test_unknown_family_no_full_bdshot(self): # For unknown family, timer is not in cap_map → falls to else branch: # bdshot_outputs=[], bdshot_output_only=group["outputs"] (output-only DShot) timers = [{"timer": "Timer1", "dshot": True}] channels = [{"output_index": 1, "timer": "Timer1", "channel": "Channel1", "channel_index": 0, "is_dshot_channel": True}] groups = fcdg.compute_groups(timers, channels) groups = fcdg.compute_bdshot(groups, channels, "unknown") assert all(g["bdshot_outputs"] == [] for g in groups) def test_capture_channel_no_bdshot_on_no_dma_timers(self, board_stm32h7_capture_channels): # Timer1/Timer8/Timer12 have no DMA → dshot=False → bdshot=False timers, channels = _parse(board_stm32h7_capture_channels) groups = fcdg.compute_groups(timers, channels) groups = fcdg.compute_bdshot(groups, channels, "stm32h7") for g in groups: if g["timer"] in ("Timer1", "Timer8", "Timer12"): assert g["dshot"] is False assert g["bdshot"] is False assert g["bdshot_outputs"] == [] class TestComputeGroupsCapture: def test_capture_group_count(self, board_stm32h7_capture_channels): timers, channels = _parse(board_stm32h7_capture_channels) groups = fcdg.compute_groups(timers, channels) assert len(groups) == 5 def test_capture_total_outputs(self, board_stm32h7_capture_channels): timers, channels = _parse(board_stm32h7_capture_channels) groups = fcdg.compute_groups(timers, channels) all_outputs = sorted(o for g in groups for o in g["outputs"]) assert all_outputs == list(range(1, 17)) def test_capture_dshot_only_on_dma_timers(self, board_stm32h7_capture_channels): timers, channels = _parse(board_stm32h7_capture_channels) groups = fcdg.compute_groups(timers, channels) dshot_groups = {g["timer"] for g in groups if g["dshot"]} assert dshot_groups == {"Timer5", "Timer4"} non_dshot_groups = {g["timer"] for g in groups if not g["dshot"]} assert non_dshot_groups == {"Timer1", "Timer8", "Timer12"} def test_capture_timer_group_memberships(self, board_stm32h7_capture_channels): timers, channels = _parse(board_stm32h7_capture_channels) groups = fcdg.compute_groups(timers, channels) by_timer = {g["timer"]: g["outputs"] for g in groups} assert by_timer["Timer5"] == [1, 2, 3, 4] assert by_timer["Timer4"] == [5, 6, 7, 8] assert by_timer["Timer1"] == [9, 10, 11] assert by_timer["Timer8"] == [12, 13, 14] assert by_timer["Timer12"] == [15, 16]