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 <andries.kruithof@nordicsemi.no>
This commit is contained in:
Andries Kruithof 2024-11-18 13:47:28 +01:00 committed by Benjamin Cabé
parent 1115cd5698
commit c9a9f0ab08
7 changed files with 365 additions and 4 deletions

View file

@ -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
)

View file

@ -0,0 +1,12 @@
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef UUT_BAP_BROADCAST_ASSISTANT_H_
#define UUT_BAP_BROADCAST_ASSISTANT_H_
void set_skip_add_src(unsigned int nr_to_skip);
#endif /* UUT_BAP_BROADCAST_ASSISTANT_H_ */

View file

@ -129,7 +129,7 @@ static void test_start_param_init(void *f)
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].bis_sync = BIT(i);
fixture->subgroups[i].metadata_len = 0;
}

View file

@ -0,0 +1,179 @@
/* test_cancel.c - unit test for cancel command */
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/fff.h>
#include "bluetooth.h"
#include "cap_commander.h"
#include "conn.h"
#include "expects_util.h"
#include "cap_mocks.h"
#include "test_common.h"
#include "bap_broadcast_assistant.h"
#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 = BIT(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]);
}
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)
{
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);
}
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;
if (CONFIG_BT_MAX_CONN == 1) {
ztest_test_skip();
}
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;
if (CONFIG_BT_MAX_CONN == 1) {
ztest_test_skip();
}
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);
}

View file

@ -7,8 +7,25 @@
#include "zephyr/bluetooth/audio/bap.h"
#include "test_common.h"
#include "bap_broadcast_assistant.h"
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 by not successfully sending all the
* requests, so that we can cancel before the CAP commander implementation is done
* with the procedure.
* This is based on the fact that we are not actually sending any requests on air, and
* that we are using this function as a synchronous function, rather than an asynchronous
* function.
*/
static unsigned int add_src_skip;
void set_skip_add_src(unsigned int setting)
{
add_src_skip = setting;
}
struct bap_broadcast_assistant_recv_state_info {
uint8_t src_id;
/** Cached PAST available */
@ -83,6 +100,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");

View file

@ -61,6 +61,7 @@ static struct k_sem sem_mics_discovered;
static struct k_sem sem_bass_discovered;
CREATE_FLAG(flag_mtu_exchanged);
CREATE_FLAG(flag_cap_canceled);
CREATE_FLAG(flag_volume_changed);
CREATE_FLAG(flag_volume_mute_changed);
CREATE_FLAG(flag_volume_offset_changed);
@ -104,6 +105,12 @@ static void cap_discovery_complete_cb(struct bt_conn *conn, int err,
#if defined(CONFIG_BT_VCP_VOL_CTLR)
static void cap_volume_changed_cb(struct bt_conn *conn, int err)
{
if (err == -ECANCELED) {
printk("CAP command cancelled for conn %p\n", conn);
SET_FLAG(flag_cap_canceled);
return;
}
if (err != 0) {
FAIL("Failed to change volume for conn %p: %d\n", conn, err);
return;
@ -114,6 +121,12 @@ static void cap_volume_changed_cb(struct bt_conn *conn, int err)
static void cap_volume_mute_changed_cb(struct bt_conn *conn, int err)
{
if (err == -ECANCELED) {
printk("CAP command cancelled for conn %p\n", conn);
SET_FLAG(flag_cap_canceled);
return;
}
if (err != 0) {
FAIL("Failed to change volume mute for conn %p: %d\n", conn, err);
return;
@ -125,6 +138,12 @@ static void cap_volume_mute_changed_cb(struct bt_conn *conn, int err)
#if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS)
static void cap_volume_offset_changed_cb(struct bt_conn *conn, int err)
{
if (err == -ECANCELED) {
printk("CAP command cancelled for conn %p\n", conn);
SET_FLAG(flag_cap_canceled);
return;
}
if (err != 0) {
FAIL("Failed to change volume offset for conn %p: %d\n", conn, err);
return;
@ -138,6 +157,12 @@ static void cap_volume_offset_changed_cb(struct bt_conn *conn, int err)
#if defined(CONFIG_BT_MICP_MIC_CTLR)
static void cap_microphone_mute_changed_cb(struct bt_conn *conn, int err)
{
if (err == -ECANCELED) {
printk("CAP command cancelled for conn %p\n", conn);
SET_FLAG(flag_cap_canceled);
return;
}
if (err != 0) {
FAIL("Failed to change microphone mute for conn %p: %d\n", conn, err);
return;
@ -149,6 +174,12 @@ static void cap_microphone_mute_changed_cb(struct bt_conn *conn, int err)
#if defined(CONFIG_BT_MICP_MIC_CTLR_AICS)
static void cap_microphone_gain_changed_cb(struct bt_conn *conn, int err)
{
if (err == -ECANCELED) {
printk("CAP command cancelled for conn %p\n", conn);
SET_FLAG(flag_cap_canceled);
return;
}
if (err != 0) {
FAIL("Failed to change microphone gain for conn %p: %d\n", conn, err);
return;
@ -162,6 +193,12 @@ static void cap_microphone_gain_changed_cb(struct bt_conn *conn, int err)
#if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT)
static void cap_broadcast_reception_start_cb(struct bt_conn *conn, int err)
{
if (err == -ECANCELED) {
printk("CAP command cancelled for conn %p\n", conn);
SET_FLAG(flag_cap_canceled);
return;
}
if (err != 0) {
FAIL("Failed to perform broadcast reception start for conn %p: %d\n", conn, err);
return;
@ -172,6 +209,12 @@ static void cap_broadcast_reception_start_cb(struct bt_conn *conn, int err)
static void cap_broadcast_reception_stop_cb(struct bt_conn *conn, int err)
{
if (err == -ECANCELED) {
printk("CAP command cancelled for conn %p\n", conn);
SET_FLAG(flag_cap_canceled);
return;
}
if (err != 0) {
FAIL("Failed to perform broadcast reception stop for conn %p: %d\n", conn, err);
return;
@ -631,6 +674,7 @@ static void init(size_t acceptor_cnt)
k_sem_init(&sem_mics_discovered, 0, acceptor_cnt);
UNSET_FLAG(flag_mtu_exchanged);
UNSET_FLAG(flag_cap_canceled);
UNSET_FLAG(flag_volume_changed);
UNSET_FLAG(flag_volume_mute_changed);
UNSET_FLAG(flag_volume_offset_changed);
@ -843,7 +887,7 @@ static void discover_mics(size_t acceptor_cnt)
}
}
static void test_change_volume(void)
static void init_change_volume(void)
{
union bt_cap_set_member members[CONFIG_BT_MAX_CONN];
const struct bt_cap_commander_change_volume_param param = {
@ -855,7 +899,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];
@ -866,9 +909,13 @@ static void test_change_volume(void)
FAIL("Failed to change volume: %d\n", err);
return;
}
}
static void test_change_volume(void)
{
UNSET_FLAG(flag_volume_changed);
init_change_volume();
WAIT_FOR_FLAG(flag_volume_changed);
printk("Volume changed to %u\n", param.volume);
}
static void test_change_volume_mute(bool mute)
@ -1078,6 +1125,18 @@ static void test_distribute_broadcast_code(size_t acceptor_count)
bt_cap_commander_distribute_broadcast_code(&distribute_broadcast_code_param);
}
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
@ -1169,6 +1228,51 @@ 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)) {
discover_vcs(acceptor_count);
init_change_volume();
test_cancel(true);
WAIT_FOR_FLAG(flag_cap_canceled);
}
}
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",
@ -1182,6 +1286,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,
};

View file

@ -0,0 +1,36 @@
#!/usr/bin/env bash
#
# Copyright (c) 2024 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
SIMULATION_ID="cap_cancel"
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 cancel 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}
# 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