From e3f730a667c75af03ebcde81cc5db113c9681471 Mon Sep 17 00:00:00 2001 From: Andries Kruithof Date: Mon, 18 Nov 2024 13:47:28 +0100 Subject: [PATCH] Bluetooth: Audio: Add tests for the CAP cancel command This adds unittests and babblesim tests for the CAP cancel command Signed-off-by: Andries Kruithof --- .../audio/cap_commander/CMakeLists.txt | 1 + .../audio/cap_commander/src/test_cancel.c | 183 ++++++++++++++++++ .../uut/bap_broadcast_assistant.c | 16 ++ .../bluetooth/audio/src/cap_commander_test.c | 94 ++++++++- .../audio/test_scripts/cap_cancel.sh | 43 ++++ 5 files changed, 332 insertions(+), 5 deletions(-) create mode 100644 tests/bluetooth/audio/cap_commander/src/test_cancel.c create mode 100755 tests/bsim/bluetooth/audio/test_scripts/cap_cancel.sh diff --git a/tests/bluetooth/audio/cap_commander/CMakeLists.txt b/tests/bluetooth/audio/cap_commander/CMakeLists.txt index f5a82565f5183d..218f6abae9380e 100644 --- a/tests/bluetooth/audio/cap_commander/CMakeLists.txt +++ b/tests/bluetooth/audio/cap_commander/CMakeLists.txt @@ -20,4 +20,5 @@ target_sources(testbinary src/test_micp.c src/test_broadcast_reception.c src/test_distribute_broadcast_code.c + src/test_cancel.c ) diff --git a/tests/bluetooth/audio/cap_commander/src/test_cancel.c b/tests/bluetooth/audio/cap_commander/src/test_cancel.c new file mode 100644 index 00000000000000..c7d7bc592aab9d --- /dev/null +++ b/tests/bluetooth/audio/cap_commander/src/test_cancel.c @@ -0,0 +1,183 @@ +/* test_cancel.c - unit test for cancel command */ + +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include + +#include "bluetooth.h" +#include "cap_commander.h" +#include "conn.h" +#include "expects_util.h" +#include "cap_mocks.h" +#include "test_common.h" + +#include + +void set_skip_add_src(int nr_to_skip); + +LOG_MODULE_REGISTER(bt_cancel, CONFIG_BT_CAP_COMMANDER_LOG_LEVEL); + +#define FFF_GLOBALS + +struct cap_commander_test_cancel_fixture { + struct bt_conn conns[CONFIG_BT_MAX_CONN]; + + struct bt_bap_bass_subgroup subgroups[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS]; + struct bt_cap_commander_broadcast_reception_start_member_param + start_member_params[CONFIG_BT_MAX_CONN]; + struct bt_cap_commander_broadcast_reception_start_param start_param; +}; + +static void test_start_param_init(void *f) +{ + struct cap_commander_test_cancel_fixture *fixture = f; + int err; + + fixture->start_param.type = BT_CAP_SET_TYPE_AD_HOC; + fixture->start_param.param = fixture->start_member_params; + + fixture->start_param.count = ARRAY_SIZE(fixture->start_member_params); + + for (size_t i = 0; i < ARRAY_SIZE(fixture->subgroups); i++) { + fixture->subgroups[i].bis_sync = 1 << i; + fixture->subgroups[i].metadata_len = 0; + } + + for (size_t i = 0U; i < ARRAY_SIZE(fixture->start_member_params); i++) { + fixture->start_member_params[i].member.member = &fixture->conns[i]; + bt_addr_le_copy(&fixture->start_member_params[i].addr, BT_ADDR_LE_ANY); + fixture->start_member_params[i].adv_sid = 0; + fixture->start_member_params[i].pa_interval = 10; + fixture->start_member_params[i].broadcast_id = 0; + memcpy(fixture->start_member_params[i].subgroups, &fixture->subgroups[0], + sizeof(struct bt_bap_bass_subgroup) * CONFIG_BT_BAP_BASS_MAX_SUBGROUPS); + fixture->start_member_params[i].num_subgroups = CONFIG_BT_BAP_BASS_MAX_SUBGROUPS; + } + + for (size_t i = 0; i < ARRAY_SIZE(fixture->conns); i++) { + err = bt_cap_commander_discover(&fixture->conns[i]); + zassert_equal(0, err, "Unexpected return value %d", err); + } +} + +static void +cap_commander_test_cancel_fixture_init(struct cap_commander_test_cancel_fixture *fixture) +{ + for (size_t i = 0; i < ARRAY_SIZE(fixture->conns); i++) { + test_conn_init(&fixture->conns[i]); + fixture->conns[i].index = i; + } + + test_start_param_init(fixture); +} + +static void *cap_commander_test_cancel_setup(void) +{ + struct cap_commander_test_cancel_fixture *fixture; + + fixture = malloc(sizeof(*fixture)); + zassert_not_null(fixture); + + return fixture; +} + +static void cap_commander_test_cancel_before(void *f) +{ + int err; + struct cap_commander_test_cancel_fixture *fixture = f; + + memset(f, 0, sizeof(struct cap_commander_test_cancel_fixture)); + cap_commander_test_cancel_fixture_init(fixture); + + for (size_t i = 0; i < ARRAY_SIZE(fixture->conns); i++) { + err = bt_cap_commander_discover(&fixture->conns[i]); + zassert_equal(0, err, "Unexpected return value %d", err); + } +} + +static void cap_commander_test_cancel_after(void *f) +{ + struct cap_commander_test_cancel_fixture *fixture = f; + + bt_cap_commander_unregister_cb(&mock_cap_commander_cb); + + for (size_t i = 0; i < ARRAY_SIZE(fixture->conns); i++) { + mock_bt_conn_disconnected(&fixture->conns[i], BT_HCI_ERR_REMOTE_USER_TERM_CONN); + } +} + +static void cap_commander_test_cancel_teardown(void *f) +{ + free(f); +} + +static void test_cancel(void) +{ + int err; + + err = bt_cap_commander_cancel(); + zassert_equal(0, err, "Unexpected return value %d", err); + + zexpect_call_count("bt_cap_commander_cb.broadcast_reception_start", 1, + mock_cap_commander_broadcast_reception_start_cb_fake.call_count); + zassert_equal(-ECANCELED, + mock_cap_commander_broadcast_reception_start_cb_fake.arg1_history[0]); +} + +ZTEST_SUITE(cap_commander_test_cancel, NULL, cap_commander_test_cancel_setup, + cap_commander_test_cancel_before, cap_commander_test_cancel_after, + cap_commander_test_cancel_teardown); + +ZTEST_F(cap_commander_test_cancel, test_commander_cancel) +{ + int err; + + err = bt_cap_commander_register_cb(&mock_cap_commander_cb); + zassert_equal(0, err, "Unexpected return value %d", err); + + /* Do not run the add_src callback, so that the broadcast reception start procedure does not + * run until completion + */ + set_skip_add_src(1); + + /* initiate a CAP procedure; for this test we use broadcast reception start*/ + err = bt_cap_commander_broadcast_reception_start(&fixture->start_param); + zassert_equal(0, err, "Could not start CAP procedure: %d", err); + + test_cancel(); +} + +ZTEST_F(cap_commander_test_cancel, test_commander_cancel_double) +{ + int err; + + err = bt_cap_commander_register_cb(&mock_cap_commander_cb); + zassert_equal(0, err, "Unexpected return value %d", err); + + set_skip_add_src(1); + err = bt_cap_commander_broadcast_reception_start(&fixture->start_param); + zassert_equal(0, err, "Could not start CAP procedure: %d", err); + + test_cancel(); + + err = bt_cap_commander_cancel(); + zassert_equal(-EALREADY, err, "Unexpected return value %d", err); +} + +ZTEST_F(cap_commander_test_cancel, test_commander_cancel_no_proc_in_progress) +{ + int err; + + err = bt_cap_commander_register_cb(&mock_cap_commander_cb); + zassert_equal(0, err, "Unexpected return value %d", err); + + err = bt_cap_commander_cancel(); + zassert_equal(-EALREADY, err, "Unexpected return value %d", err); +} diff --git a/tests/bluetooth/audio/cap_commander/uut/bap_broadcast_assistant.c b/tests/bluetooth/audio/cap_commander/uut/bap_broadcast_assistant.c index 8331c292e0c998..653d9fc16e41e8 100644 --- a/tests/bluetooth/audio/cap_commander/uut/bap_broadcast_assistant.c +++ b/tests/bluetooth/audio/cap_commander/uut/bap_broadcast_assistant.c @@ -9,6 +9,16 @@ static sys_slist_t broadcast_assistant_cbs = SYS_SLIST_STATIC_INIT(&broadcast_assistant_cbs); +/* when > 0 immediately return from the add_src callback the specified number of times + * This allows us to test the CAP cancel command + */ +static int add_src_skip; + +void set_skip_add_src(int setting) +{ + add_src_skip = setting; +} + struct bap_broadcast_assistant_recv_state_info { uint8_t src_id; /** Cached PAST available */ @@ -83,6 +93,12 @@ int bt_bap_broadcast_assistant_add_src(struct bt_conn *conn, struct bt_bap_scan_delegator_recv_state state; struct bt_bap_broadcast_assistant_cb *listener, *next; + if (add_src_skip != 0) { + add_src_skip--; + + return 0; + } + /* Note that proper parameter checking is done in the caller */ zassert_not_null(conn, "conn is NULL"); zassert_not_null(param, "param is NULL"); diff --git a/tests/bsim/bluetooth/audio/src/cap_commander_test.c b/tests/bsim/bluetooth/audio/src/cap_commander_test.c index cbd91fe265d9ca..7f7a777e8e39c3 100644 --- a/tests/bsim/bluetooth/audio/src/cap_commander_test.c +++ b/tests/bsim/bluetooth/audio/src/cap_commander_test.c @@ -101,6 +101,16 @@ static void cap_discovery_complete_cb(struct bt_conn *conn, int err, } #if defined(CONFIG_BT_VCP_VOL_CTLR) +static void cap_volume_changed_fail_cb(struct bt_conn *conn, int err) +{ + if (err != -ECANCELED) { + FAIL("CAP command not cancelled for conn %p: %d\n", conn, err); + return; + } + + SET_FLAG(flag_volume_changed); +} + static void cap_volume_changed_cb(struct bt_conn *conn, int err) { if (err != 0) { @@ -811,8 +821,7 @@ static void discover_mics(size_t acceptor_cnt) } } } - -static void test_change_volume(void) +static int init_change_volume(void) { union bt_cap_set_member members[CONFIG_BT_MAX_CONN]; const struct bt_cap_commander_change_volume_param param = { @@ -824,7 +833,6 @@ static void test_change_volume(void) int err; printk("Changing volume to %u\n", param.volume); - UNSET_FLAG(flag_volume_changed); for (size_t i = 0U; i < param.count; i++) { param.members[i].member = connected_conns[i]; @@ -833,11 +841,19 @@ static void test_change_volume(void) err = bt_cap_commander_change_volume(¶m); if (err != 0) { FAIL("Failed to change volume: %d\n", err); - return; + return err; } + return param.volume; +} +static void test_change_volume(void) +{ + int new_volume; + UNSET_FLAG(flag_volume_changed); + new_volume = init_change_volume(); WAIT_FOR_FLAG(flag_volume_changed); - printk("Volume changed to %u\n", param.volume); + + printk("Volume changed to %u\n", new_volume); } static void test_change_volume_mute(bool mute) @@ -1026,6 +1042,18 @@ static void test_broadcast_reception_stop(size_t acceptor_count) WAIT_FOR_FLAG(flag_broadcast_reception_stop); } +static void test_cancel(bool cap_in_progress) +{ + const int expected_err = cap_in_progress ? 0 : -EALREADY; + int err; + + err = bt_cap_commander_cancel(); + if (err != expected_err) { + FAIL("Could not cancel CAP command: %d\n", err); + return; + } +} + static void test_main_cap_commander_capture_and_render(void) { const size_t acceptor_cnt = get_dev_cnt() - 1; /* Assume all other devices are acceptors @@ -1117,6 +1145,56 @@ static void test_main_cap_commander_broadcast_reception(void) PASS("Broadcast reception passed\n"); } +static void test_main_cap_commander_cancel(void) +{ + size_t acceptor_count; + + /* The test consists of N devices + * 1 device is the broadcast source + * 1 device is the CAP commander + * This leaves N - 2 devices for the acceptor + */ + acceptor_count = get_dev_cnt() - 1; + printk("Acceptor count: %d\n", acceptor_count); + + init(acceptor_count); + + for (size_t i = 0U; i < acceptor_count; i++) { + scan_and_connect(); + + WAIT_FOR_FLAG(flag_mtu_exchanged); + } + + /* TODO: We should use CSIP to find set members */ + discover_cas(acceptor_count); + + if (IS_ENABLED(CONFIG_BT_CSIP_SET_COORDINATOR)) { + if (IS_ENABLED(CONFIG_BT_VCP_VOL_CTLR)) { + /* As a result of the cancel command the callback is called with err == + * -ECANCELED so we can not use the default callback + */ + cap_cb.volume_changed = cap_volume_changed_fail_cb; + + discover_vcs(acceptor_count); + + init_change_volume(); + + test_cancel(true); + WAIT_FOR_FLAG(flag_volume_changed); + } + } + + test_cancel(false); + + /* Disconnect all CAP acceptors */ + disconnect_acl(acceptor_count); + + /* restore the default callback */ + cap_cb.volume_changed = cap_volume_changed_cb; + + PASS("Broadcast reception passed\n"); +} + static const struct bst_test_instance test_cap_commander[] = { { .test_id = "cap_commander_capture_and_render", @@ -1130,6 +1208,12 @@ static const struct bst_test_instance test_cap_commander[] = { .test_tick_f = test_tick, .test_main_f = test_main_cap_commander_broadcast_reception, }, + { + .test_id = "cap_commander_cancel", + .test_post_init_f = test_init, + .test_tick_f = test_tick, + .test_main_f = test_main_cap_commander_cancel, + }, BSTEST_END_MARKER, }; diff --git a/tests/bsim/bluetooth/audio/test_scripts/cap_cancel.sh b/tests/bsim/bluetooth/audio/test_scripts/cap_cancel.sh new file mode 100755 index 00000000000000..8484820c1c971b --- /dev/null +++ b/tests/bsim/bluetooth/audio/test_scripts/cap_cancel.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +SIMULATION_ID="cap_broadcast_reception" +VERBOSITY_LEVEL=2 +NR_OF_DEVICES=3 +EXECUTE_TIMEOUT=180 + +source ${ZEPHYR_BASE}/tests/bsim/sh_common.source + +cd ${BSIM_OUT_PATH}/bin + +printf "\n\n======== Running CAP commander broadcast reception start and stop test =========\n\n" + +Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=0 -testid=cap_commander_cancel \ + -RealEncryption=1 -rs=46 -D=${NR_OF_DEVICES} + +Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=1 -testid=cap_acceptor_capture_and_render \ + -RealEncryption=1 -rs=23 -D=${NR_OF_DEVICES} + +Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_audio_prj_conf \ + -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=2 --testid=cap_acceptor_capture_and_render \ + -RealEncryption=1 -rs=69 -D=${NR_OF_DEVICES} +#Execute ./bs_${BOARD_TS}_tests_bsim_bluetooth_audio_prj_conf \ +# -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} -d=2 -testid=cap_acceptor_broadcast_reception \ +# -RealEncryption=1 -rs=69 -D=${NR_OF_DEVICES} + + + + + +# Simulation time should be larger than the WAIT_TIME in common.h +Execute ./bs_2G4_phy_v1 -v=${VERBOSITY_LEVEL} -s=${SIMULATION_ID} \ + -D=${NR_OF_DEVICES} -sim_length=60e6 $@ + + + +wait_for_background_jobs