From d5608c07c301eda8e79b072f94b5a8d67cb3fbbe Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 17 Sep 2025 13:42:12 +0200 Subject: [PATCH] control: unit test for event sort After some discussions with CLAUDE it made me this unit test, which I think is ok and actually tests useful things. --- spa/plugins/control/meson.build | 30 +++++ spa/plugins/control/test-mixer-ump-sort.c | 151 ++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 spa/plugins/control/test-mixer-ump-sort.c diff --git a/spa/plugins/control/meson.build b/spa/plugins/control/meson.build index adabdfab3..b31986b05 100644 --- a/spa/plugins/control/meson.build +++ b/spa/plugins/control/meson.build @@ -8,3 +8,33 @@ controllib = shared_library('spa-control', dependencies : [ spa_dep, mathlib ], install : true, install_dir : spa_plugindir / 'control') + +test_inc = include_directories('../test') + +test_apps = [ + 'test-mixer-ump-sort', +] + +foreach a : test_apps + test(a, + executable(a, a + '.c', + dependencies : [ spa_dep ], + include_directories : [ configinc, test_inc ], + install_rpath : spa_plugindir / 'control', + install : installed_tests_enabled, + install_dir : installed_tests_execdir / 'control'), + env : [ + 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')), + ]) + + if installed_tests_enabled + test_conf = configuration_data() + test_conf.set('exec', installed_tests_execdir / 'control' / a) + configure_file( + input: installed_tests_template, + output: a + '.test', + install_dir: installed_tests_metadir / 'control', + configuration: test_conf + ) + endif +endforeach diff --git a/spa/plugins/control/test-mixer-ump-sort.c b/spa/plugins/control/test-mixer-ump-sort.c new file mode 100644 index 000000000..23a87e18d --- /dev/null +++ b/spa/plugins/control/test-mixer-ump-sort.c @@ -0,0 +1,151 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2025 Claude Code */ +/* SPDX-License-Identifier: MIT */ + +#include +#include +#include +#include + +#include +#include +#include + +/* Include the mixer source to access static inline functions */ +#include "mixer.c" + +/* Simple test framework macros */ +#define TEST_ASSERT(cond, msg) \ + do { \ + if (!(cond)) { \ + fprintf(stderr, "FAIL: %s\n", msg); \ + return 1; \ + } \ + } while (0) + +#define TEST_PASS() \ + do { \ + printf("PASS\n"); \ + return 0; \ + } while (0) + +/* Helper to create mock control structures for testing */ +static struct spa_pod_control *create_mock_control(uint8_t *buffer, size_t *offset, + uint64_t timestamp, uint32_t type, const void *data, size_t data_size) +{ + struct spa_pod_control *control = (struct spa_pod_control *)(buffer + *offset); + control->offset = timestamp; + control->type = type; + control->value.size = data_size; + control->value.type = SPA_TYPE_Bytes; + + /* Copy data after the control structure */ + memcpy(buffer + *offset + sizeof(struct spa_pod_control), data, data_size); + *offset += sizeof(struct spa_pod_control) + SPA_ROUND_UP_N(data_size, 8); + + return control; +} + +static int test_ump_event_sort_offset_priority(void) +{ + uint8_t buffer[1024]; + size_t offset = 0; + uint32_t ump_early = 0x20904060; /* Note On Ch 0 */ + uint32_t ump_late = 0x20904060; /* Note On Ch 0 */ + + struct spa_pod_control *a = create_mock_control(buffer, &offset, 100, SPA_CONTROL_UMP, &ump_early, 4); + const void *abody = (uint8_t*)a + sizeof(struct spa_pod_control); + + struct spa_pod_control *b = create_mock_control(buffer, &offset, 200, SPA_CONTROL_UMP, &ump_late, 4); + const void *bbody = (uint8_t*)b + sizeof(struct spa_pod_control); + + /* Earlier offset should sort before later offset */ + TEST_ASSERT(event_sort(a, abody, b, bbody) < 0, "Earlier offset should sort before later offset"); + /* Later offset should sort after earlier offset */ + TEST_ASSERT(event_sort(b, bbody, a, abody) > 0, "Later offset should sort after earlier offset"); + + TEST_PASS(); +} + +static int test_ump_event_sort_same_offset_different_channels(void) +{ + uint8_t buffer[1024]; + size_t offset = 0; + uint32_t ump_ch0 = 0x20904060; /* Note On Ch 0 */ + uint32_t ump_ch1 = 0x20914060; /* Note On Ch 1 */ + + struct spa_pod_control *a = create_mock_control(buffer, &offset, 100, SPA_CONTROL_UMP, &ump_ch0, 4); + const void *abody = (uint8_t*)a + sizeof(struct spa_pod_control); + + struct spa_pod_control *b = create_mock_control(buffer, &offset, 100, SPA_CONTROL_UMP, &ump_ch1, 4); + const void *bbody = (uint8_t*)b + sizeof(struct spa_pod_control); + + /* Different channels at same offset should return 0 (no preference) */ + TEST_ASSERT(event_sort(a, abody, b, bbody) == 0, "Different channels at same offset should return 0"); + TEST_ASSERT(event_sort(b, bbody, a, abody) == 0, "Different channels at same offset should return 0"); + + TEST_PASS(); +} + +static int test_ump_event_sort_priority_controller_vs_note(void) +{ + uint8_t buffer[1024]; + size_t offset = 0; + uint32_t ump_note_on = 0x20904060; /* Note On Ch 0 (priority 4) */ + uint32_t ump_controller = 0x20B04060; /* Controller Ch 0 (priority 2) */ + + struct spa_pod_control *note_on = create_mock_control(buffer, &offset, 100, SPA_CONTROL_UMP, &ump_note_on, 4); + const void *note_body = (uint8_t*)note_on + sizeof(struct spa_pod_control); + + struct spa_pod_control *controller = create_mock_control(buffer, &offset, 100, SPA_CONTROL_UMP, &ump_controller, 4); + const void *ctrl_body = (uint8_t*)controller + sizeof(struct spa_pod_control); + + /* Controller (higher priority) should sort before Note On (lower priority) */ + TEST_ASSERT(event_sort(note_on, note_body, controller, ctrl_body) > 0, "Controller should sort before Note On"); + TEST_ASSERT(event_sort(controller, ctrl_body, note_on, note_body) <= 0, "Controller should sort before Note On"); + + TEST_PASS(); +} + +static int test_event_compare_priority_table(void) +{ + /* Test controller (0xB0) vs note on (0x90) on same channel */ + TEST_ASSERT(event_compare(0x90, 0xB0) > 0, "Controller has higher priority than Note On"); + TEST_ASSERT(event_compare(0xB0, 0x90) < 0, "Controller has higher priority than Note On"); + + /* Test program change (0xC0) vs note off (0x80) on same channel */ + TEST_ASSERT(event_compare(0x80, 0xC0) > 0, "Program change has higher priority than Note Off"); + TEST_ASSERT(event_compare(0xC0, 0x80) < 0, "Program change has higher priority than Note Off"); + + /* Test different channels should return 0 */ + TEST_ASSERT(event_compare(0x90, 0x91) == 0, "Different channels should return 0"); + + TEST_PASS(); +} + +int main(void) +{ + int result = 0; + + printf("Running mixer UMP sort tests...\n"); + + printf("test_ump_event_sort_offset_priority: "); + result |= test_ump_event_sort_offset_priority(); + + printf("test_ump_event_sort_same_offset_different_channels: "); + result |= test_ump_event_sort_same_offset_different_channels(); + + printf("test_ump_event_sort_priority_controller_vs_note: "); + result |= test_ump_event_sort_priority_controller_vs_note(); + + printf("test_event_compare_priority_table: "); + result |= test_event_compare_priority_table(); + + if (result == 0) { + printf("All tests passed!\n"); + } else { + printf("Some tests failed!\n"); + } + + return result; +} \ No newline at end of file