Skip to content

Commit 33a4245

Browse files
MechanicV1anhmolt
andcommitted
tests: ble_hrs: unit tests for ble_hrs
Adding unit tests to increase coverage for ble_hrs library Signed-off-by: Nirmal Krishna <[email protected]> Co-authored-by: Andreas Moltumyr <[email protected]>
1 parent 60df6af commit 33a4245

File tree

5 files changed

+387
-0
lines changed

5 files changed

+387
-0
lines changed

CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
/tests/lib/ble_adv/ @nrfconnect/ncs-bm-test
9393
/tests/lib/bm_storage/ @nrfconnect/ncs-bm
9494
/tests/lib/bm_timer/ @nrfconnect/ncs-bm @nrfconnect/ncs-bm-test
95+
/tests/subsys/bluetooth/services/ble_hrs/ @nrfconnect/ncs-bm-test
9596

9697
# Zephyr module
9798
/zephyr/ @nrfconnect/ncs-co-build-system
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor ASA
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
#
6+
7+
cmake_minimum_required(VERSION 3.20.0)
8+
9+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
10+
11+
project(unit_test_ble_hrs)
12+
13+
set(SOFTDEVICE_VARIANT "s115")
14+
set(SOFTDEVICE_INCLUDE_DIR "${ZEPHYR_NRF_BM_MODULE_DIR}/components/softdevice/\
15+
${SOFTDEVICE_VARIANT}/${SOFTDEVICE_VARIANT}_API/include")
16+
17+
cmock_handle(${SOFTDEVICE_INCLUDE_DIR}/ble_gatts.h)
18+
19+
target_compile_definitions(app PRIVATE
20+
NRF54L15_XXAA
21+
SVCALL_AS_NORMAL_FUNCTION
22+
SUPPRESS_INLINE_IMPLEMENTATION
23+
CONFIG_NRF_SDH_BLE_TOTAL_LINK_COUNT=1
24+
CONFIG_BLE_HRS_MAX_BUFFERED_RR_INTERVALS=20
25+
CONFIG_NRF_SDH_BLE_GATT_MAX_MTU_SIZE=23
26+
)
27+
28+
target_include_directories(app PRIVATE
29+
${SOFTDEVICE_INCLUDE_DIR}
30+
${ZEPHYR_NRF_BM_MODULE_DIR}/include
31+
${ZEPHYR_HAL_NORDIC_MODULE_DIR}/nrfx/mdk
32+
)
33+
34+
# Generate and add test file
35+
test_runner_generate(src/unity_test.c)
36+
target_sources(app PRIVATE src/unity_test.c)
37+
38+
# Unit under test
39+
target_sources(app PRIVATE ${ZEPHYR_NRF_BM_MODULE_DIR}/subsys/bluetooth/services/ble_hrs/hrs.c)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor ASA
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
#
6+
CONFIG_UNITY=y
Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
*/
6+
#include <unity.h>
7+
8+
#include <errno.h>
9+
#include <stddef.h>
10+
#include <stdint.h>
11+
#include <string.h>
12+
13+
#include <bluetooth/services/ble_hrs.h>
14+
#include <bluetooth/services/uuid.h>
15+
16+
#include "cmock_ble_gatts.h"
17+
18+
void ble_hrs_evt_handler(struct ble_hrs *hrs, const struct ble_hrs_evt *evt)
19+
{
20+
}
21+
22+
void ble_hrs_on_ble_evt(const ble_evt_t *evt, struct ble_hrs *hrs);
23+
24+
uint32_t stub_sd_ble_gatts_hvx(
25+
uint16_t conn_handle, const ble_gatts_hvx_params_t *p_hvx_params, int cmock_num_calls)
26+
{
27+
if (p_hvx_params && p_hvx_params->p_len) {
28+
*(p_hvx_params->p_len) = 0;
29+
}
30+
return NRF_SUCCESS;
31+
}
32+
33+
uint32_t stub_sd_ble_gatts_characteristic_add(
34+
uint16_t service_handle,
35+
const ble_gatts_char_md_t *p_char_md,
36+
const ble_gatts_attr_t *p_attr_char_value,
37+
ble_gatts_char_handles_t *p_handles,
38+
int cmock_num_calls)
39+
{
40+
TEST_ASSERT_NOT_NULL(p_char_md);
41+
TEST_ASSERT_NOT_NULL(p_attr_char_value);
42+
TEST_ASSERT_NOT_NULL(p_handles);
43+
if (p_attr_char_value->p_uuid->uuid == BLE_UUID_HEART_RATE_MEASUREMENT_CHAR) {
44+
TEST_ASSERT_EQUAL(BLE_GATTS_VLOC_STACK, p_char_md->p_cccd_md->vloc);
45+
TEST_ASSERT_TRUE(p_char_md->char_props.notify);
46+
TEST_ASSERT_TRUE(p_attr_char_value->p_attr_md->vlen);
47+
TEST_ASSERT_GREATER_OR_EQUAL(0, p_attr_char_value->init_len);
48+
TEST_ASSERT_TRUE(p_attr_char_value->max_len);
49+
} else if (p_attr_char_value->p_uuid->uuid == BLE_UUID_BODY_SENSOR_LOCATION_CHAR) {
50+
TEST_ASSERT_TRUE(p_char_md->char_props.read);
51+
TEST_ASSERT_EQUAL(sizeof(uint8_t), p_attr_char_value->init_len);
52+
TEST_ASSERT_EQUAL(sizeof(uint8_t), p_attr_char_value->max_len);
53+
TEST_ASSERT_EQUAL(BLE_GATTS_VLOC_STACK, p_attr_char_value->p_attr_md->vloc);
54+
} else {
55+
TEST_FAIL_MESSAGE("Unexpected UUID in characteristic add stub");
56+
}
57+
TEST_ASSERT_EQUAL(BLE_UUID_TYPE_BLE, p_attr_char_value->p_uuid->type);
58+
TEST_ASSERT_NOT_NULL(p_attr_char_value->p_value);
59+
60+
return NRF_SUCCESS;
61+
}
62+
63+
void test_ble_hrs_rr_interval_add(void)
64+
{
65+
struct ble_hrs hrs = {
66+
.evt_handler = ble_hrs_evt_handler,
67+
.service_handle = 0,
68+
.conn_handle = BLE_CONN_HANDLE_INVALID,
69+
.rr_interval_count = 0,
70+
.max_hrm_len = 0,
71+
.is_sensor_contact_supported = false,
72+
.is_sensor_contact_detected = false
73+
};
74+
int err;
75+
76+
/* Initialize the heart rate service. */
77+
memset(&hrs, 0, sizeof(hrs));
78+
79+
/* Add RR interval measurement. */
80+
err = ble_hrs_rr_interval_add(&hrs, 100);
81+
TEST_ASSERT_EQUAL(0, err);
82+
TEST_ASSERT_EQUAL(1, hrs.rr_interval_count);
83+
TEST_ASSERT_EQUAL(100, hrs.rr_interval[0]);
84+
85+
/* Add another RR interval measurement. */
86+
err = ble_hrs_rr_interval_add(&hrs, 200);
87+
TEST_ASSERT_EQUAL(0, err);
88+
TEST_ASSERT_EQUAL(2, hrs.rr_interval_count);
89+
TEST_ASSERT_EQUAL(200, hrs.rr_interval[1]);
90+
91+
/* Add a third RR interval measurement. */
92+
err = ble_hrs_rr_interval_add(&hrs, 300);
93+
TEST_ASSERT_EQUAL(0, err);
94+
TEST_ASSERT_EQUAL(3, hrs.rr_interval_count);
95+
TEST_ASSERT_EQUAL(300, hrs.rr_interval[2]);
96+
}
97+
98+
void test_ble_hrs_rr_interval_add_efault(void)
99+
{
100+
/* Try to use null for hrs struct */
101+
int err = ble_hrs_rr_interval_add(NULL, 0);
102+
103+
TEST_ASSERT_EQUAL(-EFAULT, err);
104+
}
105+
106+
void test_ble_hrs_rr_interval_add_overflow(void)
107+
{
108+
struct ble_hrs hrs = {
109+
.max_hrm_len = CONFIG_BLE_HRS_MAX_BUFFERED_RR_INTERVALS * sizeof(uint16_t) + 1
110+
};
111+
uint16_t rr_interval_second = 0;
112+
int err;
113+
114+
/* Fill the buffer to max */
115+
for (int i = 0; i < CONFIG_BLE_HRS_MAX_BUFFERED_RR_INTERVALS; i++) {
116+
err = ble_hrs_rr_interval_add(&hrs, i + 1);
117+
TEST_ASSERT_EQUAL(0, err);
118+
}
119+
rr_interval_second = hrs.rr_interval[1];
120+
121+
/* Now adding one more should remove the first one and add new to last*/
122+
err = ble_hrs_rr_interval_add(&hrs, 999);
123+
124+
TEST_ASSERT_EQUAL(0, err);
125+
TEST_ASSERT_EQUAL(CONFIG_BLE_HRS_MAX_BUFFERED_RR_INTERVALS, hrs.rr_interval_count);
126+
TEST_ASSERT_EQUAL(999, hrs.rr_interval[CONFIG_BLE_HRS_MAX_BUFFERED_RR_INTERVALS - 1]);
127+
TEST_ASSERT_EQUAL(rr_interval_second, hrs.rr_interval[0]);
128+
}
129+
130+
void test_ble_hrs_rr_interval_buffer_is_full(void)
131+
{
132+
struct ble_hrs hrs = {0};
133+
int err;
134+
135+
/* Check if buffer is empty */
136+
TEST_ASSERT_FALSE(ble_hrs_rr_interval_buffer_is_full(&hrs));
137+
138+
/* Fill the buffer to max */
139+
for (int i = 0; i < CONFIG_BLE_HRS_MAX_BUFFERED_RR_INTERVALS; i++) {
140+
err = ble_hrs_rr_interval_add(&hrs, i + 1);
141+
TEST_ASSERT_EQUAL(0, err);
142+
}
143+
144+
/* Check if buffer is full */
145+
TEST_ASSERT_TRUE(ble_hrs_rr_interval_buffer_is_full(&hrs));
146+
}
147+
148+
void test_ble_hrs_sensor_contact_supported_set(void)
149+
{
150+
struct ble_hrs hrs = {0};
151+
int err;
152+
153+
hrs.conn_handle = BLE_CONN_HANDLE_INVALID;
154+
/* Set sensor contact supported to true */
155+
err = ble_hrs_sensor_contact_supported_set(&hrs, true);
156+
TEST_ASSERT_EQUAL(0, err);
157+
TEST_ASSERT_TRUE(hrs.is_sensor_contact_supported);
158+
}
159+
160+
void test_ble_hrs_sensor_contact_supported_set_eisconn(void)
161+
{
162+
struct ble_hrs hrs = {0};
163+
int err;
164+
165+
/* Try to set sensor contact supported while in connection
166+
* Simulate being in a connection
167+
*/
168+
const ble_evt_t evt = {
169+
.header.evt_id = BLE_GAP_EVT_CONNECTED
170+
};
171+
ble_hrs_on_ble_evt(&evt, &hrs);
172+
err = ble_hrs_sensor_contact_supported_set(&hrs, true);
173+
TEST_ASSERT_EQUAL(-EISCONN, err);
174+
}
175+
176+
void test_ble_hrs_sensor_contact_supported_set_efault(void)
177+
{
178+
int err;
179+
180+
/* Try to use null for hrs struct */
181+
err = ble_hrs_sensor_contact_supported_set(NULL, false);
182+
TEST_ASSERT_EQUAL(-EFAULT, err);
183+
}
184+
185+
void test_ble_hrs_sensor_contact_detected_update(void)
186+
{
187+
struct ble_hrs hrs = {0};
188+
int err;
189+
190+
/* Update sensor contact detected state */
191+
err = ble_hrs_sensor_contact_detected_update(&hrs, true);
192+
TEST_ASSERT_EQUAL(0, err);
193+
TEST_ASSERT_TRUE(hrs.is_sensor_contact_detected);
194+
}
195+
196+
void test_ble_hrs_body_sensor_location_set(void)
197+
{
198+
struct ble_hrs hrs = {0};
199+
uint8_t body_sensor_location = BLE_HRS_BODY_SENSOR_LOCATION_FINGER;
200+
int err;
201+
202+
__cmock_sd_ble_gatts_value_set_ExpectAndReturn(
203+
hrs.conn_handle, hrs.bsl_handles.value_handle, NULL, NRF_SUCCESS);
204+
__cmock_sd_ble_gatts_value_set_IgnoreArg_p_value();
205+
err = ble_hrs_body_sensor_location_set(&hrs, body_sensor_location);
206+
TEST_ASSERT_EQUAL(0, err);
207+
208+
__cmock_sd_ble_gatts_value_set_ExpectAndReturn(
209+
hrs.conn_handle, hrs.bsl_handles.value_handle, NULL, NRF_ERROR_INVALID_ADDR);
210+
__cmock_sd_ble_gatts_value_set_IgnoreArg_p_value();
211+
err = ble_hrs_body_sensor_location_set(&hrs, body_sensor_location);
212+
TEST_ASSERT_EQUAL(-EINVAL, err);
213+
214+
/* Try to use null for hrs struct */
215+
err = ble_hrs_body_sensor_location_set(NULL, body_sensor_location);
216+
TEST_ASSERT_EQUAL(-EFAULT, err);
217+
}
218+
219+
void test_ble_hrs_heart_rate_measurement_send_enotconn(void)
220+
{
221+
struct ble_hrs hrs = {
222+
.evt_handler = ble_hrs_evt_handler,
223+
.service_handle = 0,
224+
.conn_handle = BLE_CONN_HANDLE_INVALID,
225+
.rr_interval_count = 2,
226+
.max_hrm_len = 0,
227+
.is_sensor_contact_supported = true,
228+
.is_sensor_contact_detected = false
229+
};
230+
uint16_t heart_rate_measurement = 72;
231+
int err;
232+
233+
__cmock_sd_ble_gatts_hvx_IgnoreAndReturn(BLE_ERROR_INVALID_CONN_HANDLE);
234+
err = ble_hrs_heart_rate_measurement_send(&hrs, heart_rate_measurement);
235+
TEST_ASSERT_EQUAL(-ENOTCONN, err);
236+
}
237+
238+
void test_ble_hrs_heart_rate_measurement_send_epipe(void)
239+
{
240+
struct ble_hrs hrs = {
241+
.evt_handler = ble_hrs_evt_handler,
242+
.service_handle = 0,
243+
.conn_handle = BLE_CONN_HANDLE_INVALID,
244+
.rr_interval_count = 2,
245+
.max_hrm_len = 0,
246+
.is_sensor_contact_supported = true,
247+
.is_sensor_contact_detected = false
248+
};
249+
uint16_t heart_rate_measurement = 72;
250+
int err;
251+
252+
__cmock_sd_ble_gatts_hvx_ExpectAndReturn(hrs.conn_handle, NULL, NRF_ERROR_INVALID_STATE);
253+
__cmock_sd_ble_gatts_hvx_IgnoreArg_p_hvx_params();
254+
err = ble_hrs_heart_rate_measurement_send(&hrs, heart_rate_measurement);
255+
TEST_ASSERT_EQUAL(-EPIPE, err);
256+
}
257+
258+
void test_ble_hrs_heart_rate_measurement_send_einval(void)
259+
{
260+
struct ble_hrs hrs = {
261+
.evt_handler = ble_hrs_evt_handler,
262+
.service_handle = 0,
263+
.conn_handle = BLE_CONN_HANDLE_INVALID,
264+
.rr_interval_count = 2,
265+
.max_hrm_len = 0,
266+
.is_sensor_contact_supported = true,
267+
.is_sensor_contact_detected = false
268+
};
269+
uint16_t heart_rate_measurement = 72;
270+
int err;
271+
272+
__cmock_sd_ble_gatts_hvx_ExpectAndReturn(hrs.conn_handle, NULL, NRF_ERROR_BUSY);
273+
__cmock_sd_ble_gatts_hvx_IgnoreArg_p_hvx_params();
274+
err = ble_hrs_heart_rate_measurement_send(&hrs, heart_rate_measurement);
275+
TEST_ASSERT_EQUAL(-EINVAL, err);
276+
277+
/* Set up the stub to manipulate p_len */
278+
__cmock_sd_ble_gatts_hvx_Stub(stub_sd_ble_gatts_hvx);
279+
err = ble_hrs_heart_rate_measurement_send(&hrs, heart_rate_measurement);
280+
TEST_ASSERT_EQUAL(-EINVAL, err);
281+
}
282+
283+
void test_ble_hrs_heart_rate_measurement_send_efault(void)
284+
{
285+
uint16_t heart_rate_measurement = 72;
286+
int err;
287+
288+
err = ble_hrs_heart_rate_measurement_send(NULL, heart_rate_measurement);
289+
TEST_ASSERT_EQUAL(-EFAULT, err);
290+
291+
}
292+
293+
void test_ble_hrs_init_efault(void)
294+
{
295+
int err;
296+
297+
err = ble_hrs_init(NULL, NULL);
298+
TEST_ASSERT_EQUAL(-EFAULT, err);
299+
}
300+
301+
void test_ble_hrs_init_einval(void)
302+
{
303+
struct ble_hrs hrs = {0};
304+
struct ble_hrs_config hrs_config = {
305+
.evt_handler = ble_hrs_evt_handler,
306+
.is_sensor_contact_supported = true,
307+
.body_sensor_location = (uint8_t[]){ BLE_HRS_BODY_SENSOR_LOCATION_FINGER }
308+
};
309+
int err;
310+
311+
__cmock_sd_ble_gatts_service_add_IgnoreAndReturn(NRF_ERROR_INVALID_ADDR);
312+
err = ble_hrs_init(&hrs, &hrs_config);
313+
TEST_ASSERT_EQUAL(-EINVAL, err);
314+
}
315+
316+
void test_ble_hrs_init(void)
317+
{
318+
struct ble_hrs hrs = {0};
319+
struct ble_hrs_config hrs_config = {
320+
.evt_handler = ble_hrs_evt_handler,
321+
.is_sensor_contact_supported = true,
322+
.body_sensor_location = (uint8_t[]){ BLE_HRS_BODY_SENSOR_LOCATION_FINGER }
323+
};
324+
int err;
325+
326+
__cmock_sd_ble_gatts_service_add_IgnoreAndReturn(NRF_SUCCESS);
327+
__cmock_sd_ble_gatts_characteristic_add_Stub(stub_sd_ble_gatts_characteristic_add);
328+
err = ble_hrs_init(&hrs, &hrs_config);
329+
TEST_ASSERT_EQUAL(0, err);
330+
}
331+
332+
extern int unity_main(void);
333+
334+
int main(void)
335+
{
336+
return unity_main();
337+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
tests:
2+
lib.ble_hrs:
3+
platform_allow: native_sim
4+
tags: unittest

0 commit comments

Comments
 (0)