# Added: prio_queue structure + bsp/display start
This commit is contained in:
parent
5f2876e89f
commit
74cef512c7
12 changed files with 1160 additions and 2 deletions
|
|
@ -147,6 +147,7 @@
|
|||
"uart_host_mock_example",
|
||||
"test_ring_buffer",
|
||||
"test_cli",
|
||||
"test_prio_queue",
|
||||
"test_timeout_pattern",
|
||||
"uart_host_mock_example"
|
||||
]
|
||||
|
|
@ -162,6 +163,7 @@
|
|||
"test_bsp_button",
|
||||
"test_bsp_can",
|
||||
"test_cli",
|
||||
"test_prio_queue",
|
||||
"uart_host_mock_example",
|
||||
"test_ring_buffer",
|
||||
"test_timeout_pattern",
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ bsp_button_init();
|
|||
bsp_button_poll();
|
||||
|
||||
/* В основном цикле: */
|
||||
if (bsp_button_get_event_pressed(BSP_BUTTON_1)) {
|
||||
if (**bsp_button_get_event_pressed(BSP_BUTTON_1)**) {
|
||||
/* однократное срабатывание по нажатию */
|
||||
}
|
||||
|
||||
|
|
|
|||
0
bsp/display/CMakeLists.txt
Normal file
0
bsp/display/CMakeLists.txt
Normal file
0
bsp/display/README.md
Normal file
0
bsp/display/README.md
Normal file
|
|
@ -198,6 +198,7 @@ extern "C"
|
|||
* Должна вызываться из main loop на каждой итерации. НЕ вызывать из ISR.
|
||||
* Каналы MODE_PROTO пропускаются — для них используется коллбэк из ISR.
|
||||
*/
|
||||
//FIXME: poll
|
||||
void bsp_opto_process(void);
|
||||
|
||||
/**
|
||||
|
|
|
|||
268
firmware/test/arch.svg
Normal file
268
firmware/test/arch.svg
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1360" viewBox="0 0 680 1180">
|
||||
<defs>
|
||||
<marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
|
||||
<path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</marker>
|
||||
<style>
|
||||
text { font-family: system-ui, sans-serif; fill: #1a1a1a; }
|
||||
.th { font-size: 14px; font-weight: 500; }
|
||||
.ts { font-size: 12px; font-weight: 400; fill: #555; }
|
||||
.label-section { font-size: 11px; font-weight: 400; fill: #888; }
|
||||
|
||||
/* teal */
|
||||
.c-teal rect, .c-teal circle { fill: #E1F5EE; stroke: #0F6E56; }
|
||||
.c-teal .th { fill: #085041; }
|
||||
.c-teal .ts { fill: #0F6E56; }
|
||||
|
||||
/* purple */
|
||||
.c-purple rect { fill: #EEEDFE; stroke: #534AB7; }
|
||||
.c-purple .th { fill: #3C3489; }
|
||||
.c-purple .ts { fill: #534AB7; }
|
||||
|
||||
/* amber */
|
||||
.c-amber rect { fill: #FAEEDA; stroke: #854F0B; }
|
||||
.c-amber .th { fill: #633806; }
|
||||
.c-amber .ts { fill: #854F0B; }
|
||||
|
||||
/* gray */
|
||||
.c-gray rect { fill: #F1EFE8; stroke: #5F5E5A; }
|
||||
.c-gray .th { fill: #2C2C2A; }
|
||||
.c-gray .ts { fill: #5F5E5A; }
|
||||
|
||||
/* red */
|
||||
.c-red rect { fill: #FCEBEB; stroke: #A32D2D; }
|
||||
.c-red .th { fill: #501313; }
|
||||
.c-red .ts { fill: #A32D2D; }
|
||||
|
||||
.arr { stroke: #888; stroke-width: 1; fill: none; }
|
||||
.box-dashed { fill: none; stroke: #bbb; stroke-width: 0.8; stroke-dasharray: 5 3; }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════
|
||||
DIAGRAM 1: High-level architecture
|
||||
═══════════════════════════════════════════════════════ -->
|
||||
|
||||
<text x="340" y="30" text-anchor="middle" class="th" style="font-size:16px;fill:#1a1a1a;">firmware_test · high-level architecture</text>
|
||||
<text x="340" y="48" text-anchor="middle" class="ts">NXP IMXRT1052CVJ5B</text>
|
||||
|
||||
<rect x="20" y="60" width="640" height="440" rx="16" class="box-dashed"/>
|
||||
<text x="340" y="78" text-anchor="middle" class="label-section">firmware_test</text>
|
||||
|
||||
<!-- USB CDC -->
|
||||
<g class="c-teal">
|
||||
<rect x="40" y="88" width="160" height="50" rx="8" stroke-width="0.5"/>
|
||||
<text class="th" x="120" y="108" text-anchor="middle" dominant-baseline="central">USB CDC</text>
|
||||
<text class="ts" x="120" y="126" text-anchor="middle" dominant-baseline="central">virtual serial port</text>
|
||||
</g>
|
||||
<text class="ts" x="212" y="107" fill="#888">↔ Host PC</text>
|
||||
<text class="ts" x="212" y="123" fill="#888">(терминал / GUI)</text>
|
||||
|
||||
<line x1="120" y1="138" x2="120" y2="180" class="arr" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Protocol -->
|
||||
<g class="c-purple">
|
||||
<rect x="40" y="180" width="160" height="50" rx="8" stroke-width="0.5"/>
|
||||
<text class="th" x="120" y="200" text-anchor="middle" dominant-baseline="central">Protocol</text>
|
||||
<text class="ts" x="120" y="218" text-anchor="middle" dominant-baseline="central">JSON-lines · cJSON</text>
|
||||
</g>
|
||||
|
||||
<!-- Test runner -->
|
||||
<g class="c-purple">
|
||||
<rect x="250" y="180" width="190" height="50" rx="8" stroke-width="0.5"/>
|
||||
<text class="th" x="345" y="200" text-anchor="middle" dominant-baseline="central">Test runner</text>
|
||||
<text class="ts" x="345" y="218" text-anchor="middle" dominant-baseline="central">sequencer + registry</text>
|
||||
</g>
|
||||
|
||||
<!-- Local UI -->
|
||||
<g class="c-amber">
|
||||
<rect x="490" y="180" width="150" height="50" rx="8" stroke-width="0.5"/>
|
||||
<text class="th" x="565" y="200" text-anchor="middle" dominant-baseline="central">Local UI</text>
|
||||
<text class="ts" x="565" y="218" text-anchor="middle" dominant-baseline="central">LEDs + display</text>
|
||||
</g>
|
||||
|
||||
<line x1="200" y1="205" x2="250" y2="205" class="arr" marker-start="url(#arrow)" marker-end="url(#arrow)" stroke="#666"/>
|
||||
<line x1="440" y1="205" x2="490" y2="205" class="arr" marker-end="url(#arrow)" stroke="#666"/>
|
||||
|
||||
<path d="M345,230 L345,260 L180,260 L180,270" fill="none" stroke="#bbb" stroke-width="0.8" marker-end="url(#arrow)"/>
|
||||
<path d="M345,230 L345,260 L505,260 L505,270" fill="none" stroke="#bbb" stroke-width="0.8" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Self-tests region -->
|
||||
<rect x="30" y="270" width="300" height="150" rx="8" class="box-dashed"/>
|
||||
<text class="label-section" x="56" y="287">self-tests</text>
|
||||
|
||||
<g class="c-gray"><rect x="46" y="295" width="76" height="44" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="84" y="312" text-anchor="middle" dominant-baseline="central">SDRAM</text>
|
||||
<text class="ts" x="84" y="328" text-anchor="middle" dominant-baseline="central">32 MB</text></g>
|
||||
<g class="c-gray"><rect x="132" y="295" width="76" height="44" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="170" y="312" text-anchor="middle" dominant-baseline="central">QSPI</text>
|
||||
<text class="ts" x="170" y="328" text-anchor="middle" dominant-baseline="central">W25Q128</text></g>
|
||||
<g class="c-gray"><rect x="218" y="295" width="76" height="44" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="256" y="312" text-anchor="middle" dominant-baseline="central">uSD</text>
|
||||
<text class="ts" x="256" y="328" text-anchor="middle" dominant-baseline="central">SDIO</text></g>
|
||||
<g class="c-gray"><rect x="46" y="349" width="76" height="44" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="84" y="366" text-anchor="middle" dominant-baseline="central">RTC</text>
|
||||
<text class="ts" x="84" y="382" text-anchor="middle" dominant-baseline="central">BM8563</text></g>
|
||||
<g class="c-gray"><rect x="132" y="349" width="76" height="44" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="170" y="366" text-anchor="middle" dominant-baseline="central">Display</text>
|
||||
<text class="ts" x="170" y="382" text-anchor="middle" dominant-baseline="central">operator ✓</text></g>
|
||||
<g class="c-gray"><rect x="218" y="349" width="76" height="44" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="256" y="366" text-anchor="middle" dominant-baseline="central">IR</text>
|
||||
<text class="ts" x="256" y="382" text-anchor="middle" dominant-baseline="central">idle GPIO</text></g>
|
||||
|
||||
<!-- HIL tests region -->
|
||||
<rect x="350" y="270" width="290" height="150" rx="8" class="box-dashed"/>
|
||||
<text class="label-section" x="376" y="287">HIL tests · Firefly AIO-3588Q</text>
|
||||
|
||||
<g class="c-teal"><rect x="366" y="295" width="120" height="44" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="426" y="312" text-anchor="middle" dominant-baseline="central">CAN</text>
|
||||
<text class="ts" x="426" y="328" text-anchor="middle" dominant-baseline="central">frame exchange</text></g>
|
||||
<g class="c-teal"><rect x="496" y="295" width="120" height="44" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="556" y="312" text-anchor="middle" dominant-baseline="central">UART TTL</text>
|
||||
<text class="ts" x="556" y="328" text-anchor="middle" dominant-baseline="central">echo pattern</text></g>
|
||||
<g class="c-teal"><rect x="366" y="349" width="120" height="44" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="426" y="366" text-anchor="middle" dominant-baseline="central">UART ISO</text>
|
||||
<text class="ts" x="426" y="382" text-anchor="middle" dominant-baseline="central">+24V RX only</text></g>
|
||||
<g class="c-teal"><rect x="496" y="349" width="120" height="44" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="556" y="366" text-anchor="middle" dominant-baseline="central">Opto-in</text>
|
||||
<text class="ts" x="556" y="382" text-anchor="middle" dominant-baseline="central">+24V all ch.</text></g>
|
||||
|
||||
<!-- HAL / BSP -->
|
||||
<line x1="340" y1="420" x2="340" y2="448" class="arr" marker-end="url(#arrow)" stroke="#aaa"/>
|
||||
<g class="c-gray">
|
||||
<rect x="30" y="448" width="620" height="40" rx="8" stroke-width="0.5"/>
|
||||
<text class="th" x="340" y="468" text-anchor="middle" dominant-baseline="central">HAL / BSP</text>
|
||||
</g>
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════
|
||||
DIAGRAM 2: Test runner flow
|
||||
═══════════════════════════════════════════════════════ -->
|
||||
|
||||
<text x="340" y="540" text-anchor="middle" class="th" style="font-size:16px;fill:#1a1a1a;">firmware_test · test runner flow</text>
|
||||
|
||||
<!-- Boot -->
|
||||
<g class="c-gray">
|
||||
<rect x="240" y="558" width="200" height="44" rx="8" stroke-width="0.5"/>
|
||||
<text class="th" x="340" y="576" text-anchor="middle" dominant-baseline="central">Boot</text>
|
||||
<text class="ts" x="340" y="592" text-anchor="middle" dominant-baseline="central">clock, USB CDC, HAL init</text>
|
||||
</g>
|
||||
<line x1="340" y1="602" x2="340" y2="630" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
||||
|
||||
<!-- Wait for host -->
|
||||
<g class="c-purple">
|
||||
<rect x="240" y="630" width="200" height="44" rx="8" stroke-width="0.5"/>
|
||||
<text class="th" x="340" y="648" text-anchor="middle" dominant-baseline="central">Wait for host</text>
|
||||
<text class="ts" x="340" y="664" text-anchor="middle" dominant-baseline="central">USB connect or timeout</text>
|
||||
</g>
|
||||
<line x1="340" y1="674" x2="340" y2="698" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
||||
|
||||
<!-- Self-tests block -->
|
||||
<rect x="30" y="698" width="620" height="168" rx="10" class="box-dashed"/>
|
||||
<text class="label-section" x="56" y="714">self-tests</text>
|
||||
|
||||
<g class="c-red">
|
||||
<rect x="46" y="720" width="110" height="54" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="101" y="740" text-anchor="middle" dominant-baseline="central">SDRAM</text>
|
||||
<text class="ts" x="101" y="756" text-anchor="middle" dominant-baseline="central">critical</text>
|
||||
<text class="ts" x="101" y="768" text-anchor="middle" dominant-baseline="central">write/read pattern</text>
|
||||
</g>
|
||||
<g class="c-red">
|
||||
<rect x="166" y="720" width="110" height="54" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="221" y="740" text-anchor="middle" dominant-baseline="central">QSPI Flash</text>
|
||||
<text class="ts" x="221" y="756" text-anchor="middle" dominant-baseline="central">critical</text>
|
||||
<text class="ts" x="221" y="768" text-anchor="middle" dominant-baseline="central">JEDEC ID + R/W</text>
|
||||
</g>
|
||||
<g class="c-red">
|
||||
<rect x="286" y="720" width="110" height="54" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="341" y="740" text-anchor="middle" dominant-baseline="central">uSD</text>
|
||||
<text class="ts" x="341" y="756" text-anchor="middle" dominant-baseline="central">critical</text>
|
||||
<text class="ts" x="341" y="768" text-anchor="middle" dominant-baseline="central">mount + R/W</text>
|
||||
</g>
|
||||
<g class="c-gray">
|
||||
<rect x="406" y="720" width="110" height="54" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="461" y="740" text-anchor="middle" dominant-baseline="central">RTC</text>
|
||||
<text class="ts" x="461" y="756" text-anchor="middle" dominant-baseline="central">I2C presence</text>
|
||||
<text class="ts" x="461" y="768" text-anchor="middle" dominant-baseline="central">set / get</text>
|
||||
</g>
|
||||
<g class="c-amber">
|
||||
<rect x="526" y="720" width="110" height="54" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="581" y="740" text-anchor="middle" dominant-baseline="central">Display</text>
|
||||
<text class="ts" x="581" y="756" text-anchor="middle" dominant-baseline="central">R/G/B/W fill</text>
|
||||
<text class="ts" x="581" y="768" text-anchor="middle" dominant-baseline="central">btn confirm</text>
|
||||
</g>
|
||||
|
||||
<line x1="101" y1="774" x2="101" y2="794" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
||||
<line x1="221" y1="774" x2="221" y2="794" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
||||
<line x1="341" y1="774" x2="341" y2="794" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
||||
<line x1="461" y1="774" x2="461" y2="794" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
||||
<line x1="581" y1="774" x2="581" y2="794" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
||||
|
||||
<!-- Check critical failures -->
|
||||
<g class="c-gray">
|
||||
<rect x="46" y="794" width="590" height="44" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="341" y="812" text-anchor="middle" dominant-baseline="central">Check critical failures</text>
|
||||
<text class="ts" x="341" y="828" text-anchor="middle" dominant-baseline="central">SDRAM || QSPI || uSD FAIL → abort HIL, report immediately</text>
|
||||
</g>
|
||||
|
||||
<line x1="340" y1="866" x2="340" y2="890" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
||||
|
||||
<!-- HIL block -->
|
||||
<rect x="30" y="890" width="620" height="78" rx="10" class="box-dashed"/>
|
||||
<text class="label-section" x="56" y="906">HIL tests · requires Firefly</text>
|
||||
|
||||
<g class="c-teal">
|
||||
<rect x="46" y="912" width="100" height="44" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="96" y="930" text-anchor="middle" dominant-baseline="central">CAN</text>
|
||||
<text class="ts" x="96" y="946" text-anchor="middle" dominant-baseline="central">full-duplex</text>
|
||||
</g>
|
||||
<g class="c-teal">
|
||||
<rect x="156" y="912" width="100" height="44" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="206" y="930" text-anchor="middle" dominant-baseline="central">UART TTL</text>
|
||||
<text class="ts" x="206" y="946" text-anchor="middle" dominant-baseline="central">echo</text>
|
||||
</g>
|
||||
<g class="c-teal">
|
||||
<rect x="266" y="912" width="100" height="44" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="316" y="930" text-anchor="middle" dominant-baseline="central">UART ISO</text>
|
||||
<text class="ts" x="316" y="946" text-anchor="middle" dominant-baseline="central">RX only</text>
|
||||
</g>
|
||||
<g class="c-teal">
|
||||
<rect x="376" y="912" width="100" height="44" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="426" y="930" text-anchor="middle" dominant-baseline="central">Opto-in</text>
|
||||
<text class="ts" x="426" y="946" text-anchor="middle" dominant-baseline="central">all channels</text>
|
||||
</g>
|
||||
<g class="c-gray">
|
||||
<rect x="486" y="912" width="100" height="44" rx="6" stroke-width="0.5"/>
|
||||
<text class="th" x="536" y="930" text-anchor="middle" dominant-baseline="central">IR burst</text>
|
||||
<text class="ts" x="536" y="946" text-anchor="middle" dominant-baseline="central">38 kHz RX</text>
|
||||
</g>
|
||||
|
||||
<line x1="340" y1="968" x2="340" y2="996" class="arr" marker-end="url(#arrow)" stroke="#888"/>
|
||||
|
||||
<!-- Summary -->
|
||||
<g class="c-teal">
|
||||
<rect x="160" y="996" width="360" height="50" rx="8" stroke-width="0.5"/>
|
||||
<text class="th" x="340" y="1016" text-anchor="middle" dominant-baseline="central">Summary report</text>
|
||||
<text class="ts" x="340" y="1034" text-anchor="middle" dominant-baseline="central">JSON · pass/fail/skip per test · overall</text>
|
||||
</g>
|
||||
|
||||
<line x1="340" y1="1046" x2="340" y2="1074" class="arr" marker-end="url(#arrow)" stroke="#aaa" stroke-dasharray="4 3"/>
|
||||
|
||||
<!-- Provisioning -->
|
||||
<g class="c-purple">
|
||||
<rect x="160" y="1074" width="360" height="50" rx="8" stroke-width="0.5"/>
|
||||
<text class="th" x="340" y="1094" text-anchor="middle" dominant-baseline="central">Provisioning</text>
|
||||
<text class="ts" x="340" y="1112" text-anchor="middle" dominant-baseline="central">OCOTP chip_uid → host DB · only on pass</text>
|
||||
</g>
|
||||
|
||||
<text class="ts" x="536" y="1050" fill="#aaa">only if overall == pass</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<rect x="30" y="1140" width="260" height="30" rx="6" fill="none" stroke="#eee" stroke-width="0.5"/>
|
||||
<rect x="40" y="1151" width="12" height="8" rx="2" fill="#FCEBEB" stroke="#A32D2D" stroke-width="0.5"/>
|
||||
<text class="ts" x="58" y="1159">critical test</text>
|
||||
<rect x="130" y="1151" width="12" height="8" rx="2" fill="#E1F5EE" stroke="#0F6E56" stroke-width="0.5"/>
|
||||
<text class="ts" x="148" y="1159">HIL test</text>
|
||||
<rect x="200" y="1151" width="12" height="8" rx="2" fill="#FAEEDA" stroke="#854F0B" stroke-width="0.5"/>
|
||||
<text class="ts" x="218" y="1159">operator</text>
|
||||
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -173,3 +173,15 @@ add_host_test(
|
|||
${CMAKE_SOURCE_DIR}/bsp/usb_cdc/include
|
||||
MOCKS
|
||||
${BSP_MOCKS_DIR})
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# utils/prio_queue
|
||||
# -----------------------------------------------------------------------------
|
||||
add_host_test(
|
||||
NAME
|
||||
test_prio_queue
|
||||
SOURCES
|
||||
prio_queue/test_prio_queue.c
|
||||
${PROJECT_SOURCE_DIR}/utils/prio_queue/prio_queue.c
|
||||
INCLUDES
|
||||
${PROJECT_SOURCE_DIR}/utils)
|
||||
|
|
|
|||
601
tests/host/prio_queue/test_prio_queue.c
Normal file
601
tests/host/prio_queue/test_prio_queue.c
Normal file
|
|
@ -0,0 +1,601 @@
|
|||
/**
|
||||
* @file test_prio_queue.c
|
||||
* @brief Host unit-тесты для prio_queue (Категория A — без NXP SDK).
|
||||
*
|
||||
* Фреймворк: Unity.
|
||||
* Запуск: ctest --preset host-debug-test -R test_prio_queue -V
|
||||
*
|
||||
* Проверяемый инвариант:
|
||||
* - peek()/remove_at(0) всегда отдаёт элемент с наивысшим приоритетом.
|
||||
* - При равных приоритетах — FIFO (первым вошёл, первым вышел).
|
||||
*/
|
||||
|
||||
#include "prio_queue/prio_queue.h"
|
||||
#include "unity.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
/* ── Тестовые типы ─────────────────────────────────────────────────────── */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int priority;
|
||||
const char *name;
|
||||
} task_t;
|
||||
|
||||
/* cmp: меньший priority → ближе к голове (выше приоритет) */
|
||||
static int task_cmp(const void *p_a, const void *p_b)
|
||||
{
|
||||
int pa = ((const task_t *) p_a)->priority;
|
||||
int pb = ((const task_t *) p_b)->priority;
|
||||
return (pa < pb) ? -1 : (pa > pb) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* ── Общие фикстуры ────────────────────────────────────────────────────── */
|
||||
|
||||
#define CAPACITY 8U
|
||||
|
||||
static task_t s_storage[CAPACITY];
|
||||
static prio_queue_t s_q;
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
memset(s_storage, 0, sizeof(s_storage));
|
||||
prio_queue_init(&s_q, s_storage, CAPACITY, sizeof(task_t), task_cmp);
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════════
|
||||
* Группа: init
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_init_size_is_zero(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_UINT8(0U, prio_queue_size(&s_q));
|
||||
}
|
||||
|
||||
void test_init_peek_returns_null(void)
|
||||
{
|
||||
TEST_ASSERT_NULL(prio_queue_peek(&s_q));
|
||||
}
|
||||
|
||||
void test_init_at_returns_null_when_empty(void)
|
||||
{
|
||||
TEST_ASSERT_NULL(prio_queue_at(&s_q, 0U));
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════════
|
||||
* Группа: insert_order — правильная сортировка
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_insert_single_element(void)
|
||||
{
|
||||
task_t t = { .priority = 5, .name = "t5" };
|
||||
pq_status_t ret = prio_queue_insert(&s_q, &t);
|
||||
|
||||
TEST_ASSERT_EQUAL(PQ_OK, ret);
|
||||
TEST_ASSERT_EQUAL_UINT8(1U, prio_queue_size(&s_q));
|
||||
}
|
||||
|
||||
void test_insert_two_ascending_order_peek_is_higher(void)
|
||||
{
|
||||
/* Вставляем менее приоритетный первым */
|
||||
task_t low = { .priority = 10, .name = "low" };
|
||||
task_t high = { .priority = 1, .name = "high" };
|
||||
|
||||
prio_queue_insert(&s_q, &low);
|
||||
prio_queue_insert(&s_q, &high);
|
||||
|
||||
const task_t *p_top = (const task_t *) prio_queue_peek(&s_q);
|
||||
TEST_ASSERT_NOT_NULL(p_top);
|
||||
TEST_ASSERT_EQUAL_STRING("high", p_top->name);
|
||||
}
|
||||
|
||||
void test_insert_descending_input_peek_is_still_highest(void)
|
||||
{
|
||||
task_t a = { .priority = 3, .name = "a" };
|
||||
task_t b = { .priority = 2, .name = "b" };
|
||||
task_t c = { .priority = 1, .name = "c" };
|
||||
|
||||
/* Вставляем в убывающем порядке приоритета */
|
||||
prio_queue_insert(&s_q, &a);
|
||||
prio_queue_insert(&s_q, &b);
|
||||
prio_queue_insert(&s_q, &c);
|
||||
|
||||
const task_t *p_top = (const task_t *) prio_queue_peek(&s_q);
|
||||
TEST_ASSERT_NOT_NULL(p_top);
|
||||
TEST_ASSERT_EQUAL_STRING("c", p_top->name);
|
||||
}
|
||||
|
||||
void test_insert_ascending_input_order_preserved(void)
|
||||
{
|
||||
task_t a = { .priority = 1, .name = "a" };
|
||||
task_t b = { .priority = 2, .name = "b" };
|
||||
task_t c = { .priority = 3, .name = "c" };
|
||||
|
||||
prio_queue_insert(&s_q, &a);
|
||||
prio_queue_insert(&s_q, &b);
|
||||
prio_queue_insert(&s_q, &c);
|
||||
|
||||
/* Проверяем полный порядок */
|
||||
const task_t *p0 = (const task_t *) prio_queue_at(&s_q, 0U);
|
||||
const task_t *p1 = (const task_t *) prio_queue_at(&s_q, 1U);
|
||||
const task_t *p2 = (const task_t *) prio_queue_at(&s_q, 2U);
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING("a", p0->name);
|
||||
TEST_ASSERT_EQUAL_STRING("b", p1->name);
|
||||
TEST_ASSERT_EQUAL_STRING("c", p2->name);
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════════
|
||||
* Группа: fifo — FIFO при равных приоритетах
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_fifo_two_equal_priority_first_in_first_out(void)
|
||||
{
|
||||
task_t first = { .priority = 2, .name = "first" };
|
||||
task_t second = { .priority = 2, .name = "second" };
|
||||
|
||||
prio_queue_insert(&s_q, &first);
|
||||
prio_queue_insert(&s_q, &second);
|
||||
|
||||
const task_t *p_top = (const task_t *) prio_queue_peek(&s_q);
|
||||
TEST_ASSERT_NOT_NULL(p_top);
|
||||
TEST_ASSERT_EQUAL_STRING("first", p_top->name);
|
||||
}
|
||||
|
||||
void test_fifo_three_equal_priority_order_preserved(void)
|
||||
{
|
||||
task_t a = { .priority = 5, .name = "a" };
|
||||
task_t b = { .priority = 5, .name = "b" };
|
||||
task_t c = { .priority = 5, .name = "c" };
|
||||
|
||||
prio_queue_insert(&s_q, &a);
|
||||
prio_queue_insert(&s_q, &b);
|
||||
prio_queue_insert(&s_q, &c);
|
||||
|
||||
const task_t *p0 = (const task_t *) prio_queue_at(&s_q, 0U);
|
||||
const task_t *p1 = (const task_t *) prio_queue_at(&s_q, 1U);
|
||||
const task_t *p2 = (const task_t *) prio_queue_at(&s_q, 2U);
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING("a", p0->name);
|
||||
TEST_ASSERT_EQUAL_STRING("b", p1->name);
|
||||
TEST_ASSERT_EQUAL_STRING("c", p2->name);
|
||||
}
|
||||
|
||||
void test_fifo_mixed_priorities_equal_group_fifo(void)
|
||||
{
|
||||
/* Вставляем: high(1), eq_a(2), eq_b(2), low(3) */
|
||||
task_t high = { .priority = 1, .name = "high" };
|
||||
task_t eq_a = { .priority = 2, .name = "eq_a" };
|
||||
task_t eq_b = { .priority = 2, .name = "eq_b" };
|
||||
task_t low = { .priority = 3, .name = "low" };
|
||||
|
||||
prio_queue_insert(&s_q, &high);
|
||||
prio_queue_insert(&s_q, &eq_a);
|
||||
prio_queue_insert(&s_q, &eq_b);
|
||||
prio_queue_insert(&s_q, &low);
|
||||
|
||||
/* Ожидаемый порядок: high, eq_a, eq_b, low */
|
||||
const task_t *p0 = (const task_t *) prio_queue_at(&s_q, 0U);
|
||||
const task_t *p1 = (const task_t *) prio_queue_at(&s_q, 1U);
|
||||
const task_t *p2 = (const task_t *) prio_queue_at(&s_q, 2U);
|
||||
const task_t *p3 = (const task_t *) prio_queue_at(&s_q, 3U);
|
||||
|
||||
TEST_ASSERT_EQUAL_STRING("high", p0->name);
|
||||
TEST_ASSERT_EQUAL_STRING("eq_a", p1->name);
|
||||
TEST_ASSERT_EQUAL_STRING("eq_b", p2->name);
|
||||
TEST_ASSERT_EQUAL_STRING("low", p3->name);
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════════
|
||||
* Группа: full_eviction — статусы при заполнении
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
static void fill_queue_with_priority(int priority)
|
||||
{
|
||||
for (uint8_t i = 0U; i < CAPACITY; i++)
|
||||
{
|
||||
task_t t = { .priority = priority, .name = "filler" };
|
||||
prio_queue_insert(&s_q, &t);
|
||||
}
|
||||
}
|
||||
|
||||
void test_full_returns_pq_ok_until_capacity(void)
|
||||
{
|
||||
for (uint8_t i = 0U; i < CAPACITY; i++)
|
||||
{
|
||||
task_t t = { .priority = (int) i, .name = "t" };
|
||||
pq_status_t ret = prio_queue_insert(&s_q, &t);
|
||||
TEST_ASSERT_EQUAL(PQ_OK, ret);
|
||||
}
|
||||
TEST_ASSERT_EQUAL_UINT8(CAPACITY, prio_queue_size(&s_q));
|
||||
}
|
||||
|
||||
void test_full_higher_priority_returns_pq_evicted(void)
|
||||
{
|
||||
fill_queue_with_priority(10);
|
||||
|
||||
task_t better = { .priority = 1, .name = "better" };
|
||||
pq_status_t ret = prio_queue_insert(&s_q, &better);
|
||||
|
||||
TEST_ASSERT_EQUAL(PQ_EVICTED, ret);
|
||||
/* Размер не изменился */
|
||||
TEST_ASSERT_EQUAL_UINT8(CAPACITY, prio_queue_size(&s_q));
|
||||
}
|
||||
|
||||
void test_full_lower_priority_returns_pq_full(void)
|
||||
{
|
||||
fill_queue_with_priority(1);
|
||||
|
||||
task_t worst = { .priority = 100, .name = "worst" };
|
||||
pq_status_t ret = prio_queue_insert(&s_q, &worst);
|
||||
|
||||
TEST_ASSERT_EQUAL(PQ_FULL, ret);
|
||||
TEST_ASSERT_EQUAL_UINT8(CAPACITY, prio_queue_size(&s_q));
|
||||
}
|
||||
|
||||
void test_full_equal_priority_to_all_returns_pq_full(void)
|
||||
{
|
||||
/* Все элементы с prio=5; новый тоже prio=5 → FIFO: новый менее приоритетен */
|
||||
fill_queue_with_priority(5);
|
||||
|
||||
task_t same = { .priority = 5, .name = "same" };
|
||||
pq_status_t ret = prio_queue_insert(&s_q, &same);
|
||||
|
||||
TEST_ASSERT_EQUAL(PQ_FULL, ret);
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════════
|
||||
* Группа: evict_correct — правильный элемент вытесняется
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_evict_removes_least_prioritized_element(void)
|
||||
{
|
||||
/* Очередь: [1, 2, 3]; вставляем 2 → вытесняется 3 */
|
||||
task_t small_q_storage[3];
|
||||
memset(small_q_storage, 0, sizeof(small_q_storage));
|
||||
|
||||
prio_queue_t small_q;
|
||||
prio_queue_init(&small_q, small_q_storage, 3U, sizeof(task_t), task_cmp);
|
||||
|
||||
task_t a = { .priority = 1, .name = "a" };
|
||||
task_t b = { .priority = 2, .name = "b" };
|
||||
task_t c = { .priority = 3, .name = "c" };
|
||||
|
||||
prio_queue_insert(&small_q, &a);
|
||||
prio_queue_insert(&small_q, &b);
|
||||
prio_queue_insert(&small_q, &c);
|
||||
|
||||
task_t d = { .priority = 2, .name = "d" };
|
||||
pq_status_t ret = prio_queue_insert(&small_q, &d);
|
||||
|
||||
TEST_ASSERT_EQUAL(PQ_EVICTED, ret);
|
||||
|
||||
/* c(3) должна была вытесниться; d(2) должна быть внутри */
|
||||
bool found_d = false;
|
||||
bool found_c = false;
|
||||
|
||||
for (uint8_t i = 0U; i < prio_queue_size(&small_q); i++)
|
||||
{
|
||||
const task_t *p_elem = (const task_t *) prio_queue_at(&small_q, i);
|
||||
if (strcmp(p_elem->name, "d") == 0)
|
||||
{
|
||||
found_d = true;
|
||||
}
|
||||
if (strcmp(p_elem->name, "c") == 0)
|
||||
{
|
||||
found_c = true;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_ASSERT_TRUE(found_d);
|
||||
TEST_ASSERT_FALSE(found_c);
|
||||
}
|
||||
|
||||
void test_evict_top_still_correct_after_eviction(void)
|
||||
{
|
||||
task_t small_q_storage[2];
|
||||
memset(small_q_storage, 0, sizeof(small_q_storage));
|
||||
|
||||
prio_queue_t small_q;
|
||||
prio_queue_init(&small_q, small_q_storage, 2U, sizeof(task_t), task_cmp);
|
||||
|
||||
task_t lo = { .priority = 10, .name = "lo" };
|
||||
task_t hi = { .priority = 1, .name = "hi" };
|
||||
task_t best = { .priority = 0, .name = "best" };
|
||||
|
||||
prio_queue_insert(&small_q, &lo);
|
||||
prio_queue_insert(&small_q, &hi);
|
||||
/* best вытесняет lo */
|
||||
prio_queue_insert(&small_q, &best);
|
||||
|
||||
const task_t *p_top = (const task_t *) prio_queue_peek(&small_q);
|
||||
TEST_ASSERT_NOT_NULL(p_top);
|
||||
TEST_ASSERT_EQUAL_STRING("best", p_top->name);
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════════
|
||||
* Группа: peek — стабильность указателя и edge-cases
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_peek_null_when_empty(void)
|
||||
{
|
||||
TEST_ASSERT_NULL(prio_queue_peek(&s_q));
|
||||
}
|
||||
|
||||
void test_peek_does_not_remove_element(void)
|
||||
{
|
||||
task_t t = { .priority = 3, .name = "t" };
|
||||
prio_queue_insert(&s_q, &t);
|
||||
|
||||
prio_queue_peek(&s_q);
|
||||
prio_queue_peek(&s_q);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT8(1U, prio_queue_size(&s_q));
|
||||
}
|
||||
|
||||
void test_peek_pointer_into_internal_storage(void)
|
||||
{
|
||||
task_t t = { .priority = 7, .name = "stored" };
|
||||
prio_queue_insert(&s_q, &t);
|
||||
|
||||
const task_t *p_top = (const task_t *) prio_queue_peek(&s_q);
|
||||
TEST_ASSERT_EQUAL_STRING("stored", p_top->name);
|
||||
TEST_ASSERT_EQUAL_INT(7, p_top->priority);
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════════
|
||||
* Группа: remove_at — корректность удаления и сдвига
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_remove_at_first_element(void)
|
||||
{
|
||||
task_t a = { .priority = 1, .name = "a" };
|
||||
task_t b = { .priority = 2, .name = "b" };
|
||||
task_t c = { .priority = 3, .name = "c" };
|
||||
|
||||
prio_queue_insert(&s_q, &a);
|
||||
prio_queue_insert(&s_q, &b);
|
||||
prio_queue_insert(&s_q, &c);
|
||||
|
||||
prio_queue_remove_at(&s_q, 0U);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT8(2U, prio_queue_size(&s_q));
|
||||
const task_t *p_top = (const task_t *) prio_queue_peek(&s_q);
|
||||
TEST_ASSERT_EQUAL_STRING("b", p_top->name);
|
||||
}
|
||||
|
||||
void test_remove_at_middle_element(void)
|
||||
{
|
||||
task_t a = { .priority = 1, .name = "a" };
|
||||
task_t b = { .priority = 2, .name = "b" };
|
||||
task_t c = { .priority = 3, .name = "c" };
|
||||
|
||||
prio_queue_insert(&s_q, &a);
|
||||
prio_queue_insert(&s_q, &b);
|
||||
prio_queue_insert(&s_q, &c);
|
||||
|
||||
prio_queue_remove_at(&s_q, 1U); /* удаляем b */
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT8(2U, prio_queue_size(&s_q));
|
||||
|
||||
const task_t *p0 = (const task_t *) prio_queue_at(&s_q, 0U);
|
||||
const task_t *p1 = (const task_t *) prio_queue_at(&s_q, 1U);
|
||||
TEST_ASSERT_EQUAL_STRING("a", p0->name);
|
||||
TEST_ASSERT_EQUAL_STRING("c", p1->name);
|
||||
}
|
||||
|
||||
void test_remove_at_last_element(void)
|
||||
{
|
||||
task_t a = { .priority = 1, .name = "a" };
|
||||
task_t b = { .priority = 2, .name = "b" };
|
||||
|
||||
prio_queue_insert(&s_q, &a);
|
||||
prio_queue_insert(&s_q, &b);
|
||||
|
||||
prio_queue_remove_at(&s_q, 1U);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT8(1U, prio_queue_size(&s_q));
|
||||
const task_t *p_top = (const task_t *) prio_queue_peek(&s_q);
|
||||
TEST_ASSERT_EQUAL_STRING("a", p_top->name);
|
||||
}
|
||||
|
||||
void test_remove_at_out_of_bounds_is_nop(void)
|
||||
{
|
||||
task_t t = { .priority = 1, .name = "t" };
|
||||
prio_queue_insert(&s_q, &t);
|
||||
|
||||
prio_queue_remove_at(&s_q, 5U); /* idx >= count — должна быть NOP */
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT8(1U, prio_queue_size(&s_q));
|
||||
}
|
||||
|
||||
void test_remove_at_on_empty_queue_is_nop(void)
|
||||
{
|
||||
prio_queue_remove_at(&s_q, 0U); /* не должно упасть */
|
||||
TEST_ASSERT_EQUAL_UINT8(0U, prio_queue_size(&s_q));
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════════
|
||||
* Группа: at — доступ по индексу
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_at_returns_null_for_out_of_bounds(void)
|
||||
{
|
||||
task_t t = { .priority = 1, .name = "t" };
|
||||
prio_queue_insert(&s_q, &t);
|
||||
|
||||
TEST_ASSERT_NULL(prio_queue_at(&s_q, 1U));
|
||||
TEST_ASSERT_NULL(prio_queue_at(&s_q, 99U));
|
||||
}
|
||||
|
||||
void test_at_returns_correct_element(void)
|
||||
{
|
||||
task_t a = { .priority = 1, .name = "a" };
|
||||
task_t b = { .priority = 2, .name = "b" };
|
||||
|
||||
prio_queue_insert(&s_q, &a);
|
||||
prio_queue_insert(&s_q, &b);
|
||||
|
||||
task_t *p_elem = (task_t *) prio_queue_at(&s_q, 1U);
|
||||
TEST_ASSERT_NOT_NULL(p_elem);
|
||||
TEST_ASSERT_EQUAL_STRING("b", p_elem->name);
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════════
|
||||
* Группа: size — счётчик
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_size_increments_on_insert(void)
|
||||
{
|
||||
for (uint8_t i = 0U; i < 4U; i++)
|
||||
{
|
||||
task_t t = { .priority = (int) i, .name = "t" };
|
||||
prio_queue_insert(&s_q, &t);
|
||||
TEST_ASSERT_EQUAL_UINT8((uint8_t) (i + 1U), prio_queue_size(&s_q));
|
||||
}
|
||||
}
|
||||
|
||||
void test_size_decrements_on_remove(void)
|
||||
{
|
||||
task_t a = { .priority = 1, .name = "a" };
|
||||
task_t b = { .priority = 2, .name = "b" };
|
||||
|
||||
prio_queue_insert(&s_q, &a);
|
||||
prio_queue_insert(&s_q, &b);
|
||||
|
||||
prio_queue_remove_at(&s_q, 0U);
|
||||
TEST_ASSERT_EQUAL_UINT8(1U, prio_queue_size(&s_q));
|
||||
|
||||
prio_queue_remove_at(&s_q, 0U);
|
||||
TEST_ASSERT_EQUAL_UINT8(0U, prio_queue_size(&s_q));
|
||||
}
|
||||
|
||||
void test_size_unchanged_on_pq_full(void)
|
||||
{
|
||||
fill_queue_with_priority(5);
|
||||
TEST_ASSERT_EQUAL_UINT8(CAPACITY, prio_queue_size(&s_q));
|
||||
|
||||
task_t worst = { .priority = 100, .name = "worst" };
|
||||
prio_queue_insert(&s_q, &worst);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT8(CAPACITY, prio_queue_size(&s_q));
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════════
|
||||
* Группа: stress — последовательность вставок/удалений
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
void test_stress_insert_all_remove_all_in_order(void)
|
||||
{
|
||||
/* Вставляем 8 элементов с приоритетами 8..1 (убывающими) */
|
||||
for (int i = (int) CAPACITY; i >= 1; i--)
|
||||
{
|
||||
task_t t = { .priority = i, .name = "x" };
|
||||
prio_queue_insert(&s_q, &t);
|
||||
}
|
||||
|
||||
/* Вытаскиваем по одному: каждый раз первый должен быть минимальным приоритетом */
|
||||
for (int expected = 1; expected <= (int) CAPACITY; expected++)
|
||||
{
|
||||
const task_t *p_top = (const task_t *) prio_queue_peek(&s_q);
|
||||
TEST_ASSERT_NOT_NULL(p_top);
|
||||
TEST_ASSERT_EQUAL_INT(expected, p_top->priority);
|
||||
prio_queue_remove_at(&s_q, 0U);
|
||||
}
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT8(0U, prio_queue_size(&s_q));
|
||||
TEST_ASSERT_NULL(prio_queue_peek(&s_q));
|
||||
}
|
||||
|
||||
void test_stress_fifo_across_removes(void)
|
||||
{
|
||||
/* Вставляем поочерёдно prio=1 (a, b, c) и между ними prio=2 */
|
||||
task_t a = { .priority = 1, .name = "a" };
|
||||
task_t mid = { .priority = 2, .name = "mid" };
|
||||
task_t b = { .priority = 1, .name = "b" };
|
||||
task_t c = { .priority = 1, .name = "c" };
|
||||
|
||||
prio_queue_insert(&s_q, &a);
|
||||
prio_queue_insert(&s_q, &mid);
|
||||
prio_queue_insert(&s_q, &b);
|
||||
prio_queue_insert(&s_q, &c);
|
||||
|
||||
/* Порядок: a(1), b(1), c(1), mid(2) */
|
||||
const char *expected_names[] = { "a", "b", "c", "mid" };
|
||||
|
||||
for (uint8_t i = 0U; i < 4U; i++)
|
||||
{
|
||||
const task_t *p_top = (const task_t *) prio_queue_peek(&s_q);
|
||||
TEST_ASSERT_NOT_NULL(p_top);
|
||||
TEST_ASSERT_EQUAL_STRING(expected_names[i], p_top->name);
|
||||
prio_queue_remove_at(&s_q, 0U);
|
||||
}
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════════
|
||||
* main
|
||||
* ══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
|
||||
/* init */
|
||||
RUN_TEST(test_init_size_is_zero);
|
||||
RUN_TEST(test_init_peek_returns_null);
|
||||
RUN_TEST(test_init_at_returns_null_when_empty);
|
||||
|
||||
/* insert_order */
|
||||
RUN_TEST(test_insert_single_element);
|
||||
RUN_TEST(test_insert_two_ascending_order_peek_is_higher);
|
||||
RUN_TEST(test_insert_descending_input_peek_is_still_highest);
|
||||
RUN_TEST(test_insert_ascending_input_order_preserved);
|
||||
|
||||
/* fifo */
|
||||
RUN_TEST(test_fifo_two_equal_priority_first_in_first_out);
|
||||
RUN_TEST(test_fifo_three_equal_priority_order_preserved);
|
||||
RUN_TEST(test_fifo_mixed_priorities_equal_group_fifo);
|
||||
|
||||
/* full_eviction */
|
||||
RUN_TEST(test_full_returns_pq_ok_until_capacity);
|
||||
RUN_TEST(test_full_higher_priority_returns_pq_evicted);
|
||||
RUN_TEST(test_full_lower_priority_returns_pq_full);
|
||||
RUN_TEST(test_full_equal_priority_to_all_returns_pq_full);
|
||||
|
||||
/* evict_correct */
|
||||
RUN_TEST(test_evict_removes_least_prioritized_element);
|
||||
RUN_TEST(test_evict_top_still_correct_after_eviction);
|
||||
|
||||
/* peek */
|
||||
RUN_TEST(test_peek_null_when_empty);
|
||||
RUN_TEST(test_peek_does_not_remove_element);
|
||||
RUN_TEST(test_peek_pointer_into_internal_storage);
|
||||
|
||||
/* remove_at */
|
||||
RUN_TEST(test_remove_at_first_element);
|
||||
RUN_TEST(test_remove_at_middle_element);
|
||||
RUN_TEST(test_remove_at_last_element);
|
||||
RUN_TEST(test_remove_at_out_of_bounds_is_nop);
|
||||
RUN_TEST(test_remove_at_on_empty_queue_is_nop);
|
||||
|
||||
/* at */
|
||||
RUN_TEST(test_at_returns_null_for_out_of_bounds);
|
||||
RUN_TEST(test_at_returns_correct_element);
|
||||
|
||||
/* size */
|
||||
RUN_TEST(test_size_increments_on_insert);
|
||||
RUN_TEST(test_size_decrements_on_remove);
|
||||
RUN_TEST(test_size_unchanged_on_pq_full);
|
||||
|
||||
/* stress */
|
||||
RUN_TEST(test_stress_insert_all_remove_all_in_order);
|
||||
RUN_TEST(test_stress_fifo_across_removes);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
|
|
@ -7,7 +7,8 @@
|
|||
# Не зависит от конкретного железа (нет fsl_*, CMSIS, FreeRTOS). 2. Используется
|
||||
# более чем в одном месте проекта.
|
||||
|
||||
add_library(utils STATIC ring_buffer/ring_buffer.c log/log.c)
|
||||
add_library(utils STATIC ring_buffer/ring_buffer.c log/log.c
|
||||
prio_queue/prio_queue.c)
|
||||
|
||||
# PUBLIC: любой таргет, слинкованный с utils, автоматически получает корень
|
||||
# utils/ в include path → #include "ring_buffer/ring_buffer.h" #include "log.h"
|
||||
|
|
|
|||
92
utils/prio_queue/README.md
Normal file
92
utils/prio_queue/README.md
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
# util/prio_queue
|
||||
|
||||
Отсортированный массив с фиксированной ёмкостью — generic приоритетная очередь
|
||||
с вытеснением.
|
||||
|
||||
**Типичное использование:** очередь задач, событий или медиадорожек с приоритетами,
|
||||
где число элементов заранее известно и невелико (≤ ~32). При полном буфере новый
|
||||
элемент с более высоким приоритетом вытесняет наименее приоритетный.
|
||||
|
||||
| Параметр | Значение |
|
||||
|---|---|
|
||||
| Элемент | любой тип, задаётся через `item_size` |
|
||||
| Ёмкость | любая, задаётся при `prio_queue_init`; фиксирована на всё время жизни |
|
||||
| Порядок | определяется `cmp`-функцией пользователя (аналог `qsort`) |
|
||||
| Вставка | O(n) сдвиг; оптимально при n ≤ 32 |
|
||||
| Peek-top | O(1) |
|
||||
| Thread-safety | нет; при использовании из нескольких контекстов — внешняя синхронизация |
|
||||
| Зависимости | `<stdint.h>`, `<stddef.h>`, `<string.h>` |
|
||||
|
||||
## Быстрый старт
|
||||
|
||||
```c
|
||||
#include "prio_queue/prio_queue.h"
|
||||
|
||||
typedef struct { int priority; const char *name; } task_t;
|
||||
|
||||
/* Comparator: меньший priority → ближе к голове */
|
||||
static int task_cmp(const void *a, const void *b) {
|
||||
int pa = ((const task_t *)a)->priority;
|
||||
int pb = ((const task_t *)b)->priority;
|
||||
return (pa < pb) ? -1 : (pa > pb) ? 1 : 0;
|
||||
}
|
||||
|
||||
static task_t storage[8];
|
||||
static prio_queue_t q;
|
||||
|
||||
/* Инициализация */
|
||||
prio_queue_init(&q, storage, 8, sizeof(task_t), task_cmp);
|
||||
|
||||
/* Вставка */
|
||||
task_t t = { .priority = 2, .name = "send_data" };
|
||||
pq_status_t st = prio_queue_insert(&q, &t);
|
||||
/* st == PQ_OK — вставлен
|
||||
st == PQ_EVICTED — вставлен, наименее приоритетный вытеснен
|
||||
st == PQ_FULL — отклонён, новый элемент наименее приоритетен из всех */
|
||||
|
||||
/* Чтение верхнего элемента без удаления */
|
||||
const task_t *top = prio_queue_peek(&q);
|
||||
if (top != NULL) { /* обработать top */ }
|
||||
|
||||
/* Удаление верхнего элемента после обработки */
|
||||
prio_queue_remove_at(&q, 0);
|
||||
```
|
||||
|
||||
## Политика вытеснения при полном буфере
|
||||
|
||||
```
|
||||
Очередь полна [A(1) B(2) C(3)], вставляем D(2):
|
||||
→ D приоритетнее C(3) → C вытесняется → [A(1) B(2) D(2)] PQ_EVICTED
|
||||
|
||||
Очередь полна [A(1) B(2) C(3)], вставляем E(5):
|
||||
→ E менее приоритетен чем все → отклоняется PQ_FULL
|
||||
```
|
||||
|
||||
## Comparator
|
||||
|
||||
Сигнатура идентична `qsort`:
|
||||
|
||||
```c
|
||||
typedef int (*pq_cmp_fn)(const void *a, const void *b);
|
||||
```
|
||||
|
||||
| Возврат | Смысл |
|
||||
|---|---|
|
||||
| `< 0` | `a` стоит перед `b` (a приоритетнее) |
|
||||
| `> 0` | `b` стоит перед `a` |
|
||||
| `0` | равнозначны; порядок вставки сохраняется |
|
||||
|
||||
## API
|
||||
|
||||
```c
|
||||
void prio_queue_init(prio_queue_t *q, void *buf, uint8_t capacity,
|
||||
size_t item_size, pq_cmp_fn cmp);
|
||||
|
||||
pq_status_t prio_queue_insert(prio_queue_t *q, const void *item);
|
||||
const void *prio_queue_peek(const prio_queue_t *q);
|
||||
uint8_t prio_queue_size(const prio_queue_t *q);
|
||||
void *prio_queue_at(prio_queue_t *q, uint8_t idx);
|
||||
void prio_queue_remove_at(prio_queue_t *q, uint8_t idx);
|
||||
```
|
||||
|
||||
Тесты: `tests/host/test_prio_queue.c`
|
||||
111
utils/prio_queue/prio_queue.c
Normal file
111
utils/prio_queue/prio_queue.c
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* prio_queue.c
|
||||
*/
|
||||
|
||||
#include "prio_queue.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* ---------- вспомогательные ---------- */
|
||||
|
||||
static void *pq_at(prio_queue_t *p_q, uint8_t i)
|
||||
{
|
||||
return (uint8_t *) p_q->buf + (size_t) i * p_q->item_size;
|
||||
}
|
||||
|
||||
static const void *pq_at_const(const prio_queue_t *p_q, uint8_t i)
|
||||
{
|
||||
return (const uint8_t *) p_q->buf + (size_t) i * p_q->item_size;
|
||||
}
|
||||
|
||||
/* ---------- публичный API ---------- */
|
||||
|
||||
void prio_queue_init(prio_queue_t *p_q, void *p_buf, uint8_t capacity, size_t item_size,
|
||||
pq_cmp_fn p_cmp)
|
||||
{
|
||||
p_q->buf = p_buf;
|
||||
p_q->capacity = capacity;
|
||||
p_q->count = 0;
|
||||
p_q->item_size = item_size;
|
||||
p_q->cmp = p_cmp;
|
||||
memset(p_buf, 0, (size_t) capacity * item_size);
|
||||
}
|
||||
|
||||
pq_status_t prio_queue_insert(prio_queue_t *p_q, const void *p_item)
|
||||
{
|
||||
/* Найти позицию вставки: первый элемент, перед которым новый стоит в очереди.
|
||||
cmp(existing, new) > 0 означает: existing менее приоритетен, вставляем перед ним. */
|
||||
uint8_t insert_pos = p_q->count;
|
||||
for (uint8_t i = 0; i < p_q->count; i++)
|
||||
{
|
||||
if (p_q->cmp(pq_at(p_q, i), p_item) > 0)
|
||||
{
|
||||
insert_pos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_q->count < p_q->capacity)
|
||||
{
|
||||
/* Есть свободное место — сдвигаем вправо и вставляем */
|
||||
for (uint8_t i = p_q->count; i > insert_pos; i--)
|
||||
{
|
||||
memcpy(pq_at(p_q, i), pq_at(p_q, i - 1), p_q->item_size);
|
||||
}
|
||||
memcpy(pq_at(p_q, insert_pos), p_item, p_q->item_size);
|
||||
p_q->count++;
|
||||
return PQ_OK;
|
||||
}
|
||||
|
||||
/* Буфер полон */
|
||||
if (insert_pos == p_q->capacity)
|
||||
{
|
||||
/* Новый элемент наименее приоритетен — отклоняем */
|
||||
return PQ_FULL;
|
||||
}
|
||||
|
||||
/* Вытесняем последний (наименее приоритетный), сдвигаем, вставляем */
|
||||
for (uint8_t i = p_q->capacity - 1U; i > insert_pos; i--)
|
||||
{
|
||||
memcpy(pq_at(p_q, i), pq_at(p_q, i - 1), p_q->item_size);
|
||||
}
|
||||
memcpy(pq_at(p_q, insert_pos), p_item, p_q->item_size);
|
||||
return PQ_EVICTED;
|
||||
}
|
||||
|
||||
const void *prio_queue_peek(const prio_queue_t *p_q)
|
||||
{
|
||||
if (p_q->count == 0U)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return pq_at_const(p_q, 0);
|
||||
}
|
||||
|
||||
uint8_t prio_queue_size(const prio_queue_t *p_q)
|
||||
{
|
||||
return p_q->count;
|
||||
}
|
||||
|
||||
void *prio_queue_at(prio_queue_t *p_q, uint8_t idx)
|
||||
{
|
||||
if (idx >= p_q->count)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
return pq_at(p_q, idx);
|
||||
}
|
||||
|
||||
void prio_queue_remove_at(prio_queue_t *p_q, uint8_t idx)
|
||||
{
|
||||
if (idx >= p_q->count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (uint8_t i = idx; i < p_q->count - 1U; i++)
|
||||
{
|
||||
memcpy(pq_at(p_q, i), pq_at(p_q, i + 1), p_q->item_size);
|
||||
}
|
||||
memset(pq_at(p_q, p_q->count - 1U), 0, p_q->item_size);
|
||||
p_q->count--;
|
||||
}
|
||||
70
utils/prio_queue/prio_queue.h
Normal file
70
utils/prio_queue/prio_queue.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* prio_queue.h
|
||||
*
|
||||
* Generic sorted array с фиксированной ёмкостью.
|
||||
* Не знает ничего про аудио, FreeRTOS или FatFS.
|
||||
*
|
||||
* Порядок сортировки задаётся comparator-функцией:
|
||||
* cmp(a, b) < 0 → a стоит перед b (a приоритетнее)
|
||||
* cmp(a, b) > 0 → b стоит перед a
|
||||
* cmp(a, b) == 0 → равнозначны (порядок вставки сохраняется)
|
||||
*/
|
||||
|
||||
#ifndef PRIO_QUEUE_H_
|
||||
#define PRIO_QUEUE_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef int (*pq_cmp_fn)(const void *a, const void *b);
|
||||
|
||||
typedef enum
|
||||
{
|
||||
PQ_OK, /*!< Вставка прошла успешно */
|
||||
PQ_EVICTED, /*!< Вставка прошла, наименее приоритетный элемент вытеснён */
|
||||
PQ_FULL, /*!< Очередь полна, новый элемент менее приоритетен чем все */
|
||||
} pq_status_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
void *buf; /*!< Указатель на внешний буфер элементов */
|
||||
uint8_t capacity; /*!< Максимальное число элементов */
|
||||
uint8_t count; /*!< Текущее число элементов */
|
||||
size_t item_size; /*!< Размер одного элемента в байтах */
|
||||
pq_cmp_fn cmp; /*!< Функция сравнения */
|
||||
} prio_queue_t;
|
||||
|
||||
/*!
|
||||
* @brief Инициализация. Буфер @buf должен быть выделен вызывающей стороной
|
||||
* и иметь размер не менее @capacity * @item_size байт.
|
||||
*/
|
||||
void prio_queue_init(prio_queue_t *p_q, void *p_buf, uint8_t capacity, size_t item_size,
|
||||
pq_cmp_fn p_cmp);
|
||||
|
||||
/*!
|
||||
* @brief Вставить элемент с сохранением сортировки.
|
||||
* При полном буфере и более высоком приоритете — вытесняет последний.
|
||||
*/
|
||||
pq_status_t prio_queue_insert(prio_queue_t *p_q, const void *p_item);
|
||||
|
||||
/*!
|
||||
* @brief Вернуть указатель на элемент с наивысшим приоритетом (index 0).
|
||||
* NULL если очередь пуста.
|
||||
*/
|
||||
const void *prio_queue_peek(const prio_queue_t *p_q);
|
||||
|
||||
/*! Текущее число элементов. */
|
||||
uint8_t prio_queue_size(const prio_queue_t *p_q);
|
||||
|
||||
/*!
|
||||
* @brief Изменяемый доступ к элементу по индексу.
|
||||
* NULL если @idx >= count.
|
||||
*/
|
||||
void *prio_queue_at(prio_queue_t *p_q, uint8_t idx);
|
||||
|
||||
/*!
|
||||
* @brief Удалить элемент по индексу со сдвигом остальных влево.
|
||||
*/
|
||||
void prio_queue_remove_at(prio_queue_t *p_q, uint8_t idx);
|
||||
|
||||
#endif /* PRIO_QUEUE_H_ */
|
||||
Loading…
Reference in a new issue