617 lines
20 KiB
C
617 lines
20 KiB
C
/*
|
|
* test-alsa-ctl.c
|
|
* SPDX-FileCopyrightText: 2016-2025 BlueALSA developers
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
|
|
#if HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <libgen.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <check.h>
|
|
#include <alsa/asoundlib.h>
|
|
|
|
#include "shared/log.h"
|
|
|
|
#include "inc/check.inc"
|
|
#include "inc/mock.inc"
|
|
#include "inc/preload.inc"
|
|
#include "inc/spawn.inc"
|
|
|
|
static int test_ctl_open(struct spawn_process *sp_ba_mock, snd_ctl_t **ctl, int mode) {
|
|
if (spawn_bluealsa_mock(sp_ba_mock, NULL, true,
|
|
"--timeout=1000",
|
|
"--profile=a2dp-source",
|
|
"--profile=a2dp-sink",
|
|
"--profile=hfp-ag",
|
|
NULL) == -1)
|
|
return -1;
|
|
return snd_ctl_open(ctl, "bluealsa", mode);
|
|
}
|
|
|
|
static int test_pcm_close(struct spawn_process *sp_ba_mock, snd_ctl_t *ctl) {
|
|
int rv = 0;
|
|
if (ctl != NULL)
|
|
rv = snd_ctl_close(ctl);
|
|
if (sp_ba_mock != NULL) {
|
|
spawn_terminate(sp_ba_mock, 0);
|
|
spawn_close(sp_ba_mock, NULL);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
#if DEBUG
|
|
static const char *test_ctl_event_elem_get_mask_name(snd_ctl_event_t *event) {
|
|
switch (snd_ctl_event_elem_get_mask(event)) {
|
|
case SND_CTL_EVENT_MASK_ADD:
|
|
return "ADD";
|
|
case SND_CTL_EVENT_MASK_REMOVE:
|
|
return "REMOVE";
|
|
case SND_CTL_EVENT_MASK_VALUE:
|
|
return "VALUE";
|
|
case SND_CTL_EVENT_MASK_INFO:
|
|
return "INFO";
|
|
case SND_CTL_EVENT_MASK_TLV:
|
|
return "TLV";
|
|
default:
|
|
return "UNKNOWN";
|
|
}
|
|
}
|
|
#endif
|
|
|
|
CK_START_TEST(test_controls) {
|
|
|
|
struct spawn_process sp_ba_mock;
|
|
snd_ctl_t *ctl = NULL;
|
|
|
|
ck_assert_int_eq(test_ctl_open(&sp_ba_mock, &ctl, 0), 0);
|
|
|
|
snd_ctl_elem_list_t *elems;
|
|
snd_ctl_elem_list_alloca(&elems);
|
|
|
|
ck_assert_int_eq(snd_ctl_elem_list(ctl, elems), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_list_get_count(elems), 12);
|
|
ck_assert_int_eq(snd_ctl_elem_list_alloc_space(elems, 12), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_list(ctl, elems), 0);
|
|
|
|
ck_assert_int_eq(snd_ctl_elem_list_get_used(elems), 12);
|
|
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 0), "12:34:56:78:9A:BC A2DP Playback Switch");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 1), "12:34:56:78:9A:BC A2DP Playback Volume");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 2), "12:34:56:78:9A:BC A2DP Capture Switch");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 3), "12:34:56:78:9A:BC A2DP Capture Volume");
|
|
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 4), "12:34:56:78:9A:BC SCO Playback Switch");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 5), "12:34:56:78:9A:BC SCO Playback Volume");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 6), "12:34:56:78:9A:BC SCO Capture Switch");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 7), "12:34:56:78:9A:BC SCO Capture Volume");
|
|
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 8), "23:45:67:89:AB:CD A2DP Playback Switch");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 9), "23:45:67:89:AB:CD A2DP Playback Volume");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 10), "23:45:67:89:AB:CD A2DP Capture Switch");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 11), "23:45:67:89:AB:CD A2DP Capture Volume");
|
|
|
|
snd_ctl_elem_list_free_space(elems);
|
|
ck_assert_int_eq(test_pcm_close(&sp_ba_mock, ctl), 0);
|
|
|
|
} CK_END_TEST
|
|
|
|
CK_START_TEST(test_controls_battery) {
|
|
|
|
struct spawn_process sp_ba_mock;
|
|
snd_ctl_t *ctl = NULL;
|
|
|
|
ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true,
|
|
"--timeout=1000",
|
|
"--profile=hsp-ag",
|
|
NULL), -1);
|
|
|
|
ck_assert_int_eq(snd_ctl_open(&ctl, "bluealsa:EXT=battery", 0), 0);
|
|
|
|
snd_ctl_elem_list_t *elems;
|
|
snd_ctl_elem_list_alloca(&elems);
|
|
|
|
ck_assert_int_eq(snd_ctl_elem_list(ctl, elems), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_list_get_count(elems), 5);
|
|
ck_assert_int_eq(snd_ctl_elem_list_alloc_space(elems, 5), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_list(ctl, elems), 0);
|
|
|
|
ck_assert_int_eq(snd_ctl_elem_list_get_used(elems), 5);
|
|
|
|
/* battery control element shall be last */
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 4), "23:45:67:89:AB:CD | Battery Playback Volume");
|
|
|
|
snd_ctl_elem_value_t *elem;
|
|
snd_ctl_elem_value_alloca(&elem);
|
|
snd_ctl_elem_value_set_numid(elem, snd_ctl_elem_list_get_numid(elems, 4));
|
|
|
|
ck_assert_int_eq(snd_ctl_elem_read(ctl, elem), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_value_get_integer(elem, 0), 75);
|
|
/* battery control element is read-only */
|
|
ck_assert_int_eq(snd_ctl_elem_write(ctl, elem), -EINVAL);
|
|
|
|
snd_ctl_elem_list_free_space(elems);
|
|
ck_assert_int_eq(test_pcm_close(&sp_ba_mock, ctl), 0);
|
|
|
|
} CK_END_TEST
|
|
|
|
CK_START_TEST(test_controls_extended) {
|
|
|
|
struct spawn_process sp_ba_mock;
|
|
snd_ctl_t *ctl = NULL;
|
|
|
|
ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true,
|
|
"--timeout=1000",
|
|
"--profile=a2dp-source",
|
|
"--profile=hfp-ag",
|
|
NULL), -1);
|
|
|
|
ck_assert_int_eq(snd_ctl_open(&ctl, "bluealsa:EXT=yes", 0), 0);
|
|
|
|
snd_ctl_elem_list_t *elems;
|
|
snd_ctl_elem_list_alloca(&elems);
|
|
|
|
ck_assert_int_eq(snd_ctl_elem_list(ctl, elems), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_list_get_count(elems), 20);
|
|
ck_assert_int_eq(snd_ctl_elem_list_alloc_space(elems, 20), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_list(ctl, elems), 0);
|
|
|
|
/* codec control element shall be after playback/capture elements */
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 3), "12:34:56:78:9A:BC A2DP Codec Enum");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 11), "12:34:56:78:9A:BC SCO Codec Enum");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 18), "23:45:67:89:AB:CD A2DP Codec Enum");
|
|
|
|
int sco_codec_enum_items = 1;
|
|
#if ENABLE_MSBC
|
|
sco_codec_enum_items += 1;
|
|
#endif
|
|
#if ENABLE_LC3_SWB
|
|
sco_codec_enum_items += 1;
|
|
#endif
|
|
|
|
snd_ctl_elem_info_t *info;
|
|
snd_ctl_elem_info_alloca(&info);
|
|
|
|
/* 12:34:56:78:9A:BC SCO Codec Enum */
|
|
snd_ctl_elem_info_set_numid(info, snd_ctl_elem_list_get_numid(elems, 11));
|
|
ck_assert_int_eq(snd_ctl_elem_info(ctl, info), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_info_get_items(info), sco_codec_enum_items);
|
|
snd_ctl_elem_info_set_item(info, 0);
|
|
ck_assert_int_eq(snd_ctl_elem_info(ctl, info), 0);
|
|
ck_assert_str_eq(snd_ctl_elem_info_get_item_name(info), "CVSD");
|
|
#if ENABLE_MSBC
|
|
snd_ctl_elem_info_set_item(info, 1);
|
|
ck_assert_int_eq(snd_ctl_elem_info(ctl, info), 0);
|
|
ck_assert_str_eq(snd_ctl_elem_info_get_item_name(info), "mSBC");
|
|
#endif
|
|
|
|
snd_ctl_elem_value_t *elem;
|
|
snd_ctl_elem_value_alloca(&elem);
|
|
|
|
/* 12:34:56:78:9A:BC A2DP Codec Enum */
|
|
snd_ctl_elem_value_set_numid(elem, snd_ctl_elem_list_get_numid(elems, 3));
|
|
/* get currently selected A2DP codec */
|
|
ck_assert_int_eq(snd_ctl_elem_read(ctl, elem), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_value_get_enumerated(elem, 0), 0);
|
|
/* select A2DP SBC codec */
|
|
snd_ctl_elem_value_set_enumerated(elem, 0, 0);
|
|
/* write reports 0 because we are setting currently selected codec */
|
|
ck_assert_int_eq(snd_ctl_elem_write(ctl, elem), 0);
|
|
|
|
/* 12:34:56:78:9A:BC SCO Codec Enum */
|
|
snd_ctl_elem_value_set_numid(elem, snd_ctl_elem_list_get_numid(elems, 11));
|
|
/* get currently selected SCO codec */
|
|
ck_assert_int_eq(snd_ctl_elem_read(ctl, elem), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_value_get_enumerated(elem, 0),
|
|
sco_codec_enum_items > 1 ? 1 : 0);
|
|
#if ENABLE_MSBC
|
|
/* select SCO CVSD codec */
|
|
snd_ctl_elem_value_set_enumerated(elem, 0, 0);
|
|
ck_assert_int_eq(snd_ctl_elem_write(ctl, elem), 1);
|
|
#endif
|
|
|
|
snd_ctl_elem_list_free_space(elems);
|
|
ck_assert_int_eq(test_pcm_close(&sp_ba_mock, ctl), 0);
|
|
|
|
} CK_END_TEST
|
|
|
|
CK_START_TEST(test_bidirectional_a2dp) {
|
|
#if ENABLE_FASTSTREAM
|
|
|
|
struct spawn_process sp_ba_mock;
|
|
snd_ctl_t *ctl = NULL;
|
|
|
|
ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true,
|
|
"--timeout=1000",
|
|
"--profile=a2dp-source",
|
|
"--profile=a2dp-sink",
|
|
"--codec=FastStream",
|
|
NULL), -1);
|
|
|
|
ck_assert_int_eq(snd_ctl_open(&ctl, "bluealsa:BTT=yes", 0), 0);
|
|
|
|
snd_ctl_elem_list_t *elems;
|
|
snd_ctl_elem_list_alloca(&elems);
|
|
|
|
ck_assert_int_eq(snd_ctl_elem_list(ctl, elems), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_list_get_count(elems), 10);
|
|
ck_assert_int_eq(snd_ctl_elem_list_alloc_space(elems, 10), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_list(ctl, elems), 0);
|
|
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 4), "23:45:67:89:AB:C A2DP-SRC Playback Switch");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 5), "23:45:67:89:AB:C A2DP-SRC Playback Volume");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 6), "23:45:67:89:AB:C A2DP-SRC Capture Switch");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 7), "23:45:67:89:AB:C A2DP-SRC Capture Volume");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 8), "23:45:67:89:AB:C A2DP-SNK Capture Switch");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 9), "23:45:67:89:AB:C A2DP-SNK Capture Volume");
|
|
|
|
snd_ctl_elem_list_free_space(elems);
|
|
ck_assert_int_eq(test_pcm_close(&sp_ba_mock, ctl), 0);
|
|
|
|
#endif
|
|
} CK_END_TEST
|
|
|
|
CK_START_TEST(test_device_name_duplicates) {
|
|
|
|
struct spawn_process sp_ba_mock;
|
|
snd_ctl_t *ctl = NULL;
|
|
|
|
ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true,
|
|
"--timeout=1000",
|
|
"--profile=a2dp-source",
|
|
"--device-name=12:34:56:78:9A:BC:Long Bluetooth Device Name",
|
|
"--device-name=23:45:67:89:AB:CD:Long Bluetooth Device Name",
|
|
NULL), -1);
|
|
|
|
ck_assert_int_eq(snd_ctl_open(&ctl, "bluealsa", 0), 0);
|
|
|
|
snd_ctl_elem_list_t *elems;
|
|
snd_ctl_elem_list_alloca(&elems);
|
|
|
|
ck_assert_int_eq(snd_ctl_elem_list(ctl, elems), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_list_get_count(elems), 4);
|
|
ck_assert_int_eq(snd_ctl_elem_list_alloc_space(elems, 4), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_list(ctl, elems), 0);
|
|
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 0), "Long Bluetooth Devi #1 A2DP Playback Switch");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 1), "Long Bluetooth Devi #1 A2DP Playback Volume");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 2), "Long Bluetooth Devi #2 A2DP Playback Switch");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 3), "Long Bluetooth Devi #2 A2DP Playback Volume");
|
|
|
|
snd_ctl_elem_list_free_space(elems);
|
|
ck_assert_int_eq(test_pcm_close(&sp_ba_mock, ctl), 0);
|
|
|
|
} CK_END_TEST
|
|
|
|
CK_START_TEST(test_mute_and_volume) {
|
|
|
|
struct spawn_process sp_ba_mock;
|
|
snd_ctl_t *ctl = NULL;
|
|
|
|
ck_assert_int_eq(test_ctl_open(&sp_ba_mock, &ctl, 0), 0);
|
|
|
|
snd_ctl_elem_value_t *elem_switch;
|
|
snd_ctl_elem_value_alloca(&elem_switch);
|
|
/* 23:45:67:89:AB:CD A2DP Playback Switch */
|
|
snd_ctl_elem_value_set_numid(elem_switch, 9);
|
|
|
|
ck_assert_int_eq(snd_ctl_elem_read(ctl, elem_switch), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_value_get_boolean(elem_switch, 0), 1);
|
|
ck_assert_int_eq(snd_ctl_elem_value_get_boolean(elem_switch, 1), 1);
|
|
|
|
snd_ctl_elem_value_set_boolean(elem_switch, 0, 0);
|
|
snd_ctl_elem_value_set_boolean(elem_switch, 1, 0);
|
|
ck_assert_int_gt(snd_ctl_elem_write(ctl, elem_switch), 0);
|
|
|
|
snd_ctl_elem_value_t *elem_volume;
|
|
snd_ctl_elem_value_alloca(&elem_volume);
|
|
/* 23:45:67:89:AB:CD A2DP Playback Volume */
|
|
snd_ctl_elem_value_set_numid(elem_volume, 10);
|
|
|
|
ck_assert_int_eq(snd_ctl_elem_read(ctl, elem_volume), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_value_get_integer(elem_volume, 0), 50);
|
|
ck_assert_int_eq(snd_ctl_elem_value_get_integer(elem_volume, 1), 50);
|
|
|
|
snd_ctl_elem_value_set_integer(elem_volume, 0, 42);
|
|
snd_ctl_elem_value_set_integer(elem_volume, 1, 42);
|
|
ck_assert_int_gt(snd_ctl_elem_write(ctl, elem_volume), 0);
|
|
|
|
ck_assert_int_eq(snd_ctl_elem_read(ctl, elem_volume), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_value_get_integer(elem_volume, 0), 42);
|
|
ck_assert_int_eq(snd_ctl_elem_value_get_integer(elem_volume, 1), 42);
|
|
|
|
ck_assert_int_eq(test_pcm_close(&sp_ba_mock, ctl), 0);
|
|
|
|
} CK_END_TEST
|
|
|
|
CK_START_TEST(test_volume_db_range) {
|
|
|
|
struct spawn_process sp_ba_mock;
|
|
snd_ctl_t *ctl = NULL;
|
|
|
|
ck_assert_int_eq(test_ctl_open(&sp_ba_mock, &ctl, 0), 0);
|
|
|
|
snd_ctl_elem_id_t *elem;
|
|
snd_ctl_elem_id_alloca(&elem);
|
|
/* 12:34:56:78:9A:BC A2DP Playback Volume */
|
|
snd_ctl_elem_id_set_numid(elem, 2);
|
|
|
|
long min, max;
|
|
ck_assert_int_eq(snd_ctl_get_dB_range(ctl, elem, &min, &max), 0);
|
|
ck_assert_int_eq(min, -9600);
|
|
ck_assert_int_eq(max, 0);
|
|
|
|
ck_assert_int_eq(test_pcm_close(&sp_ba_mock, ctl), 0);
|
|
|
|
} CK_END_TEST
|
|
|
|
CK_START_TEST(test_single_device) {
|
|
|
|
struct spawn_process sp_ba_mock;
|
|
snd_ctl_t *ctl = NULL;
|
|
|
|
ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, "test", true,
|
|
"--timeout=1000",
|
|
"--profile=a2dp-source",
|
|
"--profile=a2dp-sink",
|
|
NULL), -1);
|
|
|
|
ck_assert_int_eq(snd_ctl_open(&ctl,
|
|
"bluealsa:DEV=00:00:00:00:00:00,SRV=org.bluealsa.test", 0), 0);
|
|
|
|
snd_ctl_card_info_t *info;
|
|
snd_ctl_card_info_alloca(&info);
|
|
|
|
ck_assert_int_eq(snd_ctl_card_info(ctl, info), 0);
|
|
ck_assert_str_eq(snd_ctl_card_info_get_name(info), "23:45:67:89:AB:CD");
|
|
|
|
snd_ctl_elem_list_t *elems;
|
|
snd_ctl_elem_list_alloca(&elems);
|
|
|
|
ck_assert_int_eq(snd_ctl_elem_list(ctl, elems), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_list_get_count(elems), 4);
|
|
ck_assert_int_eq(snd_ctl_elem_list_alloc_space(elems, 4), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_list(ctl, elems), 0);
|
|
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 0), "A2DP Playback Switch");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 1), "A2DP Playback Volume");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 2), "A2DP Capture Switch");
|
|
ck_assert_str_eq(snd_ctl_elem_list_get_name(elems, 3), "A2DP Capture Volume");
|
|
|
|
snd_ctl_elem_list_free_space(elems);
|
|
ck_assert_int_eq(test_pcm_close(&sp_ba_mock, ctl), 0);
|
|
|
|
} CK_END_TEST
|
|
|
|
CK_START_TEST(test_single_device_not_connected) {
|
|
|
|
struct spawn_process sp_ba_mock;
|
|
snd_ctl_t *ctl = NULL;
|
|
|
|
ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true,
|
|
"--timeout=1000",
|
|
NULL), -1);
|
|
|
|
ck_assert_int_eq(snd_ctl_open(&ctl,
|
|
"bluealsa:DEV=00:00:00:00:00:00", 0), -ENODEV);
|
|
|
|
ck_assert_int_eq(test_pcm_close(&sp_ba_mock, ctl), 0);
|
|
|
|
} CK_END_TEST
|
|
|
|
CK_START_TEST(test_single_device_no_such_device) {
|
|
|
|
struct spawn_process sp_ba_mock;
|
|
snd_ctl_t *ctl = NULL;
|
|
|
|
ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true,
|
|
"--timeout=1000",
|
|
"--profile=a2dp-source",
|
|
NULL), -1);
|
|
|
|
ck_assert_int_eq(snd_ctl_open(&ctl,
|
|
"bluealsa:DEV=DE:AD:12:34:56:78", 0), -ENODEV);
|
|
|
|
ck_assert_int_eq(test_pcm_close(&sp_ba_mock, ctl), 0);
|
|
|
|
} CK_END_TEST
|
|
|
|
CK_START_TEST(test_single_device_non_dynamic) {
|
|
|
|
struct spawn_process sp_ba_mock;
|
|
snd_ctl_t *ctl = NULL;
|
|
|
|
ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, true,
|
|
"--timeout=0",
|
|
"--profile=a2dp-sink",
|
|
"--profile=hsp-ag",
|
|
"--fuzzing=500",
|
|
NULL), -1);
|
|
|
|
ck_assert_int_eq(snd_ctl_open(&ctl,
|
|
"bluealsa:DEV=23:45:67:89:AB:CD,DYN=no", 0), 0);
|
|
ck_assert_int_eq(snd_ctl_subscribe_events(ctl, 1), 0);
|
|
|
|
snd_ctl_elem_list_t *elems;
|
|
snd_ctl_elem_list_alloca(&elems);
|
|
|
|
snd_ctl_event_t *event;
|
|
snd_ctl_event_malloc(&event);
|
|
|
|
ck_assert_int_eq(snd_ctl_elem_list(ctl, elems), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_list_get_count(elems), 6);
|
|
|
|
snd_ctl_elem_value_t *elem_volume;
|
|
snd_ctl_elem_value_alloca(&elem_volume);
|
|
/* A2DP Capture Volume */
|
|
snd_ctl_elem_value_set_numid(elem_volume, 2);
|
|
|
|
snd_ctl_elem_value_set_integer(elem_volume, 0, 42);
|
|
ck_assert_int_gt(snd_ctl_elem_write(ctl, elem_volume), 0);
|
|
|
|
/* check whether element value was updated */
|
|
ck_assert_int_eq(snd_ctl_elem_read(ctl, elem_volume), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_value_get_integer(elem_volume, 0), 42);
|
|
|
|
/* Process events until we will be notified about A2DP profile disconnection.
|
|
* We shall get 4 events from previous value update and 2 events for profile
|
|
* disconnection (one event per switch/volume element). */
|
|
for (size_t events = 0; events < 4 + 2 + 2;) {
|
|
ck_assert_int_eq(snd_ctl_wait(ctl, 750), 1);
|
|
while (snd_ctl_read(ctl, event) == 1)
|
|
events++;
|
|
}
|
|
|
|
/* the number of elements shall not change */
|
|
ck_assert_int_eq(snd_ctl_elem_list(ctl, elems), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_list_get_count(elems), 6);
|
|
|
|
/* element shall be "deactivated" */
|
|
ck_assert_int_eq(snd_ctl_elem_read(ctl, elem_volume), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_value_get_integer(elem_volume, 0), 0);
|
|
|
|
snd_ctl_elem_value_set_integer(elem_volume, 0, 42);
|
|
ck_assert_int_gt(snd_ctl_elem_write(ctl, elem_volume), 0);
|
|
|
|
ck_assert_int_eq(snd_ctl_elem_read(ctl, elem_volume), 0);
|
|
ck_assert_int_eq(snd_ctl_elem_value_get_integer(elem_volume, 0), 0);
|
|
|
|
snd_ctl_event_free(event);
|
|
ck_assert_int_eq(test_pcm_close(&sp_ba_mock, ctl), 0);
|
|
|
|
} CK_END_TEST
|
|
|
|
CK_START_TEST(test_notifications) {
|
|
|
|
struct spawn_process sp_ba_mock;
|
|
snd_ctl_t *ctl = NULL;
|
|
|
|
ck_assert_int_ne(spawn_bluealsa_mock(&sp_ba_mock, NULL, false,
|
|
"--timeout=10000",
|
|
"--profile=a2dp-source",
|
|
"--profile=hfp-ag",
|
|
"--fuzzing=250",
|
|
NULL), -1);
|
|
|
|
ck_assert_int_eq(snd_ctl_open(&ctl, "bluealsa:EXT=battery", 0), 0);
|
|
|
|
snd_ctl_event_t *event;
|
|
snd_ctl_event_malloc(&event);
|
|
|
|
ck_assert_int_eq(snd_ctl_subscribe_events(ctl, 1), 0);
|
|
|
|
size_t events = 0;
|
|
while (snd_ctl_wait(ctl, 500) == 1)
|
|
while (snd_ctl_read(ctl, event) == 1) {
|
|
ck_assert_int_eq(snd_ctl_event_get_type(event), SND_CTL_EVENT_ELEM);
|
|
debug("Event: %s: %s", test_ctl_event_elem_get_mask_name(event),
|
|
snd_ctl_event_elem_get_name(event));
|
|
events++;
|
|
}
|
|
|
|
ck_assert_int_eq(snd_ctl_subscribe_events(ctl, 0), 0);
|
|
|
|
size_t events_update_codec = 0;
|
|
#if ENABLE_HFP_CODEC_SELECTION
|
|
events_update_codec += 4;
|
|
# if ENABLE_MSBC
|
|
events_update_codec += 4;
|
|
# endif
|
|
# if ENABLE_LC3_SWB
|
|
events_update_codec += 4;
|
|
# endif
|
|
#endif
|
|
|
|
/* Processed events:
|
|
* - 0 removes; 2 new elems (12:34:... A2DP)
|
|
* - 4 updates per new A2DP (updated delay and volume)
|
|
* - 2 removes; 4 new elems (12:34:... A2DP, 23:45:... A2DP)
|
|
* - 4 updates per new A2DP (updated delay and volume)
|
|
* - 4 removes; 7 new elems (2x A2DP, SCO playback, battery)
|
|
* - 7 removes; 9 new elems (2x A2DP, SCO playback/capture, battery)
|
|
* - 4 updates per codec (SCO codec updates if codec selection is supported)
|
|
*/
|
|
size_t expected_events =
|
|
(0 + 2) + 4 + (2 + 4) + 4 + (4 + 7) + (7 + 9) + events_update_codec;
|
|
|
|
/* XXX: It is possible that the battery element (RFCOMM D-Bus path) will not
|
|
* be exported in time. In such case, the number of events will be less
|
|
* by 2 when RFCOMM D-Bus path is not available during the playback SCO
|
|
* addition and less by another 1 when the path is not available during
|
|
* the capture SCO addition. We shall account for this in the test, as
|
|
* it is not an error. */
|
|
int result = events == expected_events ||
|
|
events == expected_events - 2 ||
|
|
events == expected_events - 3;
|
|
ck_assert_int_eq(result, 1);
|
|
|
|
snd_ctl_event_free(event);
|
|
ck_assert_int_eq(test_pcm_close(&sp_ba_mock, ctl), 0);
|
|
|
|
} CK_END_TEST
|
|
|
|
CK_START_TEST(test_alsa_high_level_control_interface) {
|
|
|
|
struct spawn_process sp_ba_mock;
|
|
snd_ctl_t *ctl = NULL;
|
|
snd_hctl_t *hctl = NULL;
|
|
|
|
ck_assert_int_eq(test_ctl_open(&sp_ba_mock, &ctl, 0), 0);
|
|
ck_assert_int_eq(snd_hctl_open_ctl(&hctl, ctl), 0);
|
|
|
|
ck_assert_int_eq(snd_hctl_load(hctl), 0);
|
|
ck_assert_int_eq(snd_hctl_get_count(hctl), 12);
|
|
ck_assert_int_eq(snd_hctl_free(hctl), 0);
|
|
|
|
ck_assert_int_eq(snd_hctl_close(hctl), 0);
|
|
ck_assert_int_eq(test_pcm_close(&sp_ba_mock, NULL), 0);
|
|
|
|
} CK_END_TEST
|
|
|
|
int main(int argc, char *argv[]) {
|
|
preload(argc, argv, ".libs/libaloader.so");
|
|
|
|
char *argv_0 = strdup(argv[0]);
|
|
snprintf(bluealsad_mock_path, sizeof(bluealsad_mock_path),
|
|
"%s/mock/bluealsad-mock", dirname(argv_0));
|
|
|
|
Suite *s = suite_create(__FILE__);
|
|
TCase *tc = tcase_create(__FILE__);
|
|
SRunner *sr = srunner_create(s);
|
|
|
|
suite_add_tcase(s, tc);
|
|
|
|
/* Boost timeout since the single_device_non_dynamic test takes more than
|
|
* 3.5 seconds and from time to time can exceed default 4 seconds. */
|
|
tcase_set_timeout(tc, 6);
|
|
|
|
tcase_add_test(tc, test_controls);
|
|
tcase_add_test(tc, test_controls_battery);
|
|
tcase_add_test(tc, test_controls_extended);
|
|
tcase_add_test(tc, test_bidirectional_a2dp);
|
|
tcase_add_test(tc, test_device_name_duplicates);
|
|
tcase_add_test(tc, test_mute_and_volume);
|
|
tcase_add_test(tc, test_volume_db_range);
|
|
tcase_add_test(tc, test_single_device);
|
|
tcase_add_test(tc, test_single_device_not_connected);
|
|
tcase_add_test(tc, test_single_device_no_such_device);
|
|
tcase_add_test(tc, test_single_device_non_dynamic);
|
|
tcase_add_test(tc, test_notifications);
|
|
tcase_add_test(tc, test_alsa_high_level_control_interface);
|
|
|
|
srunner_run_all(sr, CK_ENV);
|
|
int nf = srunner_ntests_failed(sr);
|
|
|
|
srunner_free(sr);
|
|
free(argv_0);
|
|
|
|
return nf == 0 ? 0 : 1;
|
|
}
|