diff mbox series

[BlueZ,v4,2/8] adv_monitor: Implement unit tests for RSSI Filter

Message ID 20200917001020.BlueZ.v4.2.I5ae05701b2b792a3ea2ca98f4a5d977645b1afc2@changeid
State New
Headers show
Series None | expand

Commit Message

Miao-chen Chou Sept. 17, 2020, 7:10 a.m. UTC
From: Manish Mandlik <mmandlik@google.com>

This patch implements unit tests for the background scanning RSSI
Filtering logic.

Verified all tests PASS by running tests in unit/test-adv-monitor.c

Reviewed-by: Alain Michaud <alainm@chromium.org>
Reviewed-by: Miao-chen Chou <mcchou@chromium.org>
---

Changes in v4:
- Fix commit message

Changes in v3:
- Fix commit message

Changes in v2:
- Cast test data to void *

 Makefile.am             |   9 +
 doc/test-coverage.txt   |   3 +-
 src/adv_monitor.c       |  79 ++++++++
 src/adv_monitor.h       |  10 +
 unit/test-adv-monitor.c | 391 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 491 insertions(+), 1 deletion(-)
 create mode 100644 unit/test-adv-monitor.c

Comments

bluez.test.bot@gmail.com Sept. 17, 2020, 7:29 a.m. UTC | #1
This is automated email and please do not reply to this email!

Dear submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
While we are preparing for reviewing the patches, we found the following
issue/warning.

Test Result:
checkpatch Failed

Outputs:
WARNING:SPDX_LICENSE_TAG: Missing or malformed SPDX-License-Identifier tag in line 1
#161: FILE: unit/test-adv-monitor.c:1:
+/*

- total: 0 errors, 1 warnings, 512 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
      mechanically convert to the typical style using --fix or --fix-inplace.

Your patch has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPLIT_STRING SSCANF_TO_KSTRTO

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.



---
Regards,
Linux Bluetooth
diff mbox series

Patch

diff --git a/Makefile.am b/Makefile.am
index 22b4fa30c..6918f02b0 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -527,6 +527,15 @@  unit_test_gattrib_LDADD = lib/libbluetooth-internal.la \
 			src/libshared-glib.la \
 			$(GLIB_LIBS) $(DBUS_LIBS) -ldl -lrt
 
+unit_tests += unit/test-adv-monitor
+
+unit_test_adv_monitor_SOURCES = unit/test-adv-monitor.c \
+				src/adv_monitor.h src/adv_monitor.c \
+				src/device.h src/device.c \
+				src/log.h src/log.c
+unit_test_adv_monitor_LDADD = gdbus/libgdbus-internal.la \
+				src/libshared-glib.la $(GLIB_LIBS) $(DBUS_LIBS)
+
 if MIDI
 unit_tests += unit/test-midi
 unit_test_midi_CPPFLAGS = $(AM_CPPFLAGS) $(ALSA_CFLAGS) -DMIDI_TEST
diff --git a/doc/test-coverage.txt b/doc/test-coverage.txt
index 741492a3e..5296983e6 100644
--- a/doc/test-coverage.txt
+++ b/doc/test-coverage.txt
@@ -30,8 +30,9 @@  test-gobex-transfer	  36	OBEX transfer handling
 test-gdbus-client	  13	D-Bus client handling
 test-gatt		 180	GATT qualification test cases
 test-hog		   6	HID Over GATT qualification test cases
+test-adv-monitor	   5	Advertisement Monitor test cases
 			-----
-			 761
+			 766
 
 
 Automated end-to-end testing
diff --git a/src/adv_monitor.c b/src/adv_monitor.c
index 7baa5317f..046f5953f 100644
--- a/src/adv_monitor.c
+++ b/src/adv_monitor.c
@@ -1210,3 +1210,82 @@  static void adv_monitor_filter_rssi(struct adv_monitor *monitor,
 					      handle_device_lost_timeout, dev);
 	}
 }
+
+/* Creates the dummy adv_monitor object for unit tests */
+void *btd_adv_monitor_rssi_test_setup(int8_t high_rssi, uint16_t high_timeout,
+				      int8_t low_rssi, uint16_t low_timeout)
+{
+	struct adv_monitor *test_monitor = NULL;
+
+	test_monitor = g_new0(struct adv_monitor, 1);
+	if (!test_monitor)
+		return NULL;
+
+	test_monitor->app = g_new0(struct adv_monitor_app, 1);
+	if (!test_monitor->app)
+		goto app_failed;
+
+	test_monitor->app->manager = g_new0(struct btd_adv_monitor_manager, 1);
+	if (!test_monitor->app->manager)
+		goto manager_failed;
+
+	test_monitor->high_rssi = high_rssi;
+	test_monitor->high_rssi_timeout = high_timeout;
+	test_monitor->low_rssi = low_rssi;
+	test_monitor->low_rssi_timeout = low_timeout;
+	test_monitor->devices = queue_new();
+
+	return test_monitor;
+
+manager_failed:
+	g_free(test_monitor->app);
+
+app_failed:
+	g_free(test_monitor);
+
+	return NULL;
+}
+
+/* Cleanup after unit test is done */
+void btd_adv_monitor_rssi_test_teardown(void *monitor_obj)
+{
+	struct adv_monitor *monitor = monitor_obj;
+
+	if (!monitor)
+		return;
+
+	queue_destroy(monitor->devices, monitor_device_free);
+	g_free(monitor);
+}
+
+/* Returns the current state of device - found/lost, used in unit tests */
+bool btd_adv_monitor_test_device_state(void *monitor_obj, void *device_obj)
+{
+	struct adv_monitor *monitor = monitor_obj;
+	struct btd_device *device = device_obj;
+	struct adv_monitor_device *dev = NULL;
+
+	if (!monitor || !device)
+		return false;
+
+	dev = queue_find(monitor->devices, monitor_device_match, device);
+	if (!dev)
+		return false;
+
+	return dev->device_found;
+}
+
+/* Helper function for the RSSI Filter unit tests */
+bool btd_adv_monitor_test_rssi(void *monitor_obj, void *device_obj,
+			       int8_t adv_rssi)
+{
+	struct adv_monitor *monitor = monitor_obj;
+	struct btd_device *device = device_obj;
+
+	if (!monitor || !device)
+		return false;
+
+	adv_monitor_filter_rssi(monitor, device, adv_rssi);
+
+	return btd_adv_monitor_test_device_state(monitor, device);
+}
diff --git a/src/adv_monitor.h b/src/adv_monitor.h
index 14508e7d1..351e7f9aa 100644
--- a/src/adv_monitor.h
+++ b/src/adv_monitor.h
@@ -33,4 +33,14 @@  void btd_adv_monitor_manager_destroy(struct btd_adv_monitor_manager *manager);
 void btd_adv_monitor_device_remove(struct btd_adv_monitor_manager *manager,
 				   struct btd_device *device);
 
+/* Following functions are the helper functions used for RSSI Filter unit tests
+ * defined in unit/test-adv-monitor.c
+ */
+void *btd_adv_monitor_rssi_test_setup(int8_t high_rssi, uint16_t high_timeout,
+				      int8_t low_rssi, uint16_t low_timeout);
+void btd_adv_monitor_rssi_test_teardown(void *monitor_obj);
+bool btd_adv_monitor_test_device_state(void *monitor_obj, void *device_obj);
+bool btd_adv_monitor_test_rssi(void *monitor_obj, void *device_obj,
+			       int8_t adv_rssi);
+
 #endif /* __ADV_MONITOR_H */
diff --git a/unit/test-adv-monitor.c b/unit/test-adv-monitor.c
new file mode 100644
index 000000000..970be84b0
--- /dev/null
+++ b/unit/test-adv-monitor.c
@@ -0,0 +1,391 @@ 
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2020 Google LLC
+ *
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <glib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include "src/log.h"
+#include "src/shared/tester.h"
+
+#include "src/adv_monitor.h"
+
+#define define_test(name, type, data, setup_fn, test_fn, teardown_fn)	\
+	do {								\
+		static struct test_data test;				\
+		test.test_type = type;					\
+		test.test_name = g_strdup(name);			\
+		if (type == TEST_RSSI_FILTER) {				\
+			test.rssi_filter_test_data = (void *)&data;	\
+			test.rssi_filter_test_data->test_info = &test;	\
+		}							\
+		tester_add(name, &test, setup_fn, test_fn, teardown_fn);\
+	} while (0)
+
+#define ADV_INTERVAL		1	/* Advertisement interval in seconds */
+#define OUT_OF_RANGE		-128
+#define END_OF_RSSI_TEST	{0}
+
+#define RSSI_TEST_DONE(test_step)	\
+	(!test_step.adv_rssi && !test_step.duration && !test_step.result)
+
+#define DUMMY_BTD_DEVICE_OBJ	((void *) 0xF00)
+
+enum test_type {
+	TEST_RSSI_FILTER = 0,
+	TEST_CONTENT_FILTER,
+};
+
+enum result {
+	RESULT_DEVICE_NOT_FOUND = false,	/* Initial state of a device */
+	RESULT_DEVICE_FOUND = true,		/* Device state when the
+						 * Content/RSSI Filter match
+						 */
+	RESULT_DEVICE_LOST = false,		/* Device state when the Low
+						 * RSSI Filter match or if it
+						 * goes offline/out-of-range
+						 */
+};
+
+struct rssi_filter_test {
+	void *adv_monitor_obj;			/* struct adv_monitor object */
+	void *btd_device_obj;			/* struct btd_device object */
+	struct test_data *test_info;
+
+	const struct {
+		int8_t high_rssi_threshold;	/* High RSSI threshold */
+		uint16_t high_rssi_timeout;	/* High RSSI threshold timeout*/
+		int8_t low_rssi_threshold;	/* Low RSSI threshold */
+		uint16_t low_rssi_timeout;	/* Low RSSI threshold timeout */
+	} rssi_filter;
+
+	time_t start_time;		/* Start time of the test */
+	uint16_t resume_step;		/* Store the current sub-step of the
+					 * test before suspending that test
+					 */
+	guint out_of_range_timer;	/* Timer to simulate device offline */
+
+	const struct {
+		int8_t adv_rssi;	/* Advertisement RSSI */
+		uint16_t duration;	/* Advertisement duration in seconds */
+		enum result result;	/* Device state after every step */
+	} test_steps[];
+};
+
+/* Parent data structure to hold the test data and information,
+ * used by tester_* functions and callbacks.
+ */
+struct test_data {
+	enum test_type test_type;
+	char *test_name;
+
+	union {
+		struct rssi_filter_test *rssi_filter_test_data;
+	};
+};
+
+/* RSSI Filter Test 1:
+ * - The Device Lost event should NOT get triggered even if the Adv RSSI is
+ *   lower than LowRSSIThresh for more than LowRSSITimeout before finding
+ *   the device first.
+ * - Similarly, the Device Found event should NOT get triggered if the Adv RSSI
+ *   is greater than LowRSSIThresh but lower than HighRSSIThresh.
+ */
+static struct rssi_filter_test rssi_data_1 = {
+	.rssi_filter = {-40, 5, -60, 5},
+	.test_steps = {
+		{-70, 6, RESULT_DEVICE_NOT_FOUND},
+		{-50, 6, RESULT_DEVICE_NOT_FOUND},
+		END_OF_RSSI_TEST,
+	},
+};
+
+/* RSSI Filter Test 2:
+ * - The Device Found event should get triggered when the Adv RSSI is higher
+ *   than HighRSSIThresh for more than HighRSSITimeout.
+ * - Once the device is found, the Device Lost event should NOT get triggered
+ *   if the Adv RSSI drops below HighRSSIThresh but it is not lower than
+ *   LowRSSIThresh.
+ * - When the Adv RSSI drops below LowRSSIThresh for more than LowRSSITimeout,
+ *   the Device Lost event should get triggered.
+ */
+static struct rssi_filter_test rssi_data_2 = {
+	.rssi_filter = {-40, 5, -60, 5},
+	.test_steps = {
+		{-30, 6, RESULT_DEVICE_FOUND},
+		{-50, 6, RESULT_DEVICE_FOUND},
+		{-70, 6, RESULT_DEVICE_LOST},
+		END_OF_RSSI_TEST,
+	},
+};
+
+/* RSSI Filter Test 3:
+ * - The Device Found event should get triggered only when the Adv RSSI is
+ *   higher than HighRSSIThresh for more than HighRSSITimeout.
+ * - If the Adv RSSI drops below HighRSSIThresh, timer should reset and start
+ *   counting once the Adv RSSI is above HighRSSIThresh.
+ * - Similarly, when tracking the Low RSSI, timer should reset when the Adv RSSI
+ *   goes above LowRSSIThresh. The Device Lost event should get triggered only
+ *   when the Adv RSSI is lower than LowRSSIThresh for more than LowRSSITimeout.
+ */
+static struct rssi_filter_test rssi_data_3 = {
+	.rssi_filter = {-40, 5, -60, 5},
+	.test_steps = {
+		{-30, 2, RESULT_DEVICE_NOT_FOUND},
+		{-50, 6, RESULT_DEVICE_NOT_FOUND},
+		{-30, 4, RESULT_DEVICE_NOT_FOUND},
+		{-30, 2, RESULT_DEVICE_FOUND},
+		{-70, 2, RESULT_DEVICE_FOUND},
+		{-50, 6, RESULT_DEVICE_FOUND},
+		{-70, 4, RESULT_DEVICE_FOUND},
+		{-70, 2, RESULT_DEVICE_LOST},
+		END_OF_RSSI_TEST,
+	},
+};
+
+/* RSSI Filter Test 4:
+ * - While tracking the High RSSI, timer should reset if the device goes
+ *   offline/out-of-range for more than HighRSSITimeout.
+ * - Once the device is found, if the device goes offline/out-of-range for
+ *   more than LowRSSITimeout, the Device Lost event should get triggered.
+ */
+static struct rssi_filter_test rssi_data_4 = {
+	.rssi_filter = {-40, 5, -60, 5},
+	.test_steps = {
+		{         -30, 2, RESULT_DEVICE_NOT_FOUND},
+		{OUT_OF_RANGE, 6, RESULT_DEVICE_NOT_FOUND},
+		{         -30, 4, RESULT_DEVICE_NOT_FOUND},
+		{         -30, 2, RESULT_DEVICE_FOUND},
+		{         -70, 2, RESULT_DEVICE_FOUND},
+		{OUT_OF_RANGE, 6, RESULT_DEVICE_LOST},
+		END_OF_RSSI_TEST,
+	},
+};
+
+/* RSSI Filter Test 5:
+ * - The Device Found event should get triggered only once even if the Adv RSSI
+ *   stays higher than HighRSSIThresh for a longer period of time.
+ * - Once the device is found, while tracking the Low RSSI, timer should reset
+ *   when the Adv RSSI goes above LowRSSIThresh.
+ * - The timer should NOT reset if the device goes offline/out-of-range for
+ *   a very short period of time and comes back online/in-range before
+ *   the timeouts.
+ */
+static struct rssi_filter_test rssi_data_5 = {
+	.rssi_filter = {-40, 5, -60, 5},
+	.test_steps = {
+		{         -30, 2, RESULT_DEVICE_NOT_FOUND},
+		{OUT_OF_RANGE, 2, RESULT_DEVICE_NOT_FOUND},
+		{         -30, 2, RESULT_DEVICE_FOUND},
+		{         -30, 3, RESULT_DEVICE_FOUND},
+		{         -30, 3, RESULT_DEVICE_FOUND},
+		{         -70, 2, RESULT_DEVICE_FOUND},
+		{OUT_OF_RANGE, 2, RESULT_DEVICE_FOUND},
+		{         -50, 6, RESULT_DEVICE_FOUND},
+		{         -70, 2, RESULT_DEVICE_FOUND},
+		{OUT_OF_RANGE, 2, RESULT_DEVICE_FOUND},
+		{         -70, 2, RESULT_DEVICE_LOST},
+		END_OF_RSSI_TEST,
+	},
+};
+
+/* Initialize the data required for RSSI Filter test */
+static void setup_rssi_filter_test(gpointer data)
+{
+	struct rssi_filter_test *test = data;
+
+	test->adv_monitor_obj = btd_adv_monitor_rssi_test_setup(
+					test->rssi_filter.high_rssi_threshold,
+					test->rssi_filter.high_rssi_timeout,
+					test->rssi_filter.low_rssi_threshold,
+					test->rssi_filter.low_rssi_timeout);
+
+	/* The RSSI Filter logic uses btd_device object only as a key in the
+	 * adv_monitor->devices list, it is never dereferenced nor used to
+	 * perform any operations related to btd_device. So we can use any
+	 * dummy address for unit testing.
+	 */
+	test->btd_device_obj = DUMMY_BTD_DEVICE_OBJ;
+
+	tester_setup_complete();
+}
+
+/* Cleanup after the RSSI Filter test is done */
+static void teardown_rssi_filter_test(gpointer data)
+{
+	struct rssi_filter_test *test = data;
+
+	btd_adv_monitor_rssi_test_teardown(test->adv_monitor_obj);
+
+	tester_teardown_complete();
+}
+
+/* Execute the sub-steps of RSSI Filter test */
+static gboolean test_rssi_filter(gpointer data)
+{
+	struct rssi_filter_test *test = data;
+	time_t start_time = time(NULL);
+	bool ret = false;
+
+	uint16_t i = 0;
+	uint16_t j = 0;
+
+	/* If this is not the beginning of test, return to the sub-step
+	 * before that test was suspended
+	 */
+	if (test->resume_step) {
+		start_time = test->start_time;
+		i = test->resume_step;
+
+		/* Clear the test resume timer */
+		g_source_remove(test->out_of_range_timer);
+		test->out_of_range_timer = 0;
+
+		/* Check state of the device - found/lost, while device was
+		 * offline/out-of-range
+		 */
+		ret = btd_adv_monitor_test_device_state(test->adv_monitor_obj,
+							test->btd_device_obj);
+		tester_debug("%s: [t=%.0lf, step=%d] Test resume, "
+			     "device_found = %s",
+			     test->test_info->test_name,
+			     difftime(time(NULL), start_time), i,
+			     ret ? "true" : "false");
+		g_assert(ret == test->test_steps[i].result);
+
+		i++;
+	}
+
+	while (!RSSI_TEST_DONE(test->test_steps[i])) {
+		if (test->test_steps[i].adv_rssi == OUT_OF_RANGE) {
+			/* Simulate device offline/out-of-range by suspending
+			 * the test.
+			 *
+			 * Note: All tester_* functions run sequentially by
+			 * adding a next function to the main loop using
+			 * g_idle_add(). If a timeout function is added using
+			 * g_timeout_add_*(), it doesn't really get invoked as
+			 * soon as the timer expires. Instead, it is invoked
+			 * once the current function returns and the timer has
+			 * expired. So, to give handle_device_lost_timeout()
+			 * function a chance to run at the correct time, we
+			 * must save the current state and exit from this
+			 * function while we simulate the device offline. We can
+			 * come back later to continue with the remaining steps.
+			 */
+			test->resume_step = i;
+			test->start_time = start_time;
+			test->out_of_range_timer = g_timeout_add_seconds(
+						   test->test_steps[i].duration,
+						   test_rssi_filter, data);
+
+			/* Check the device state before suspending the test */
+			ret = btd_adv_monitor_test_device_state(
+							test->adv_monitor_obj,
+							test->btd_device_obj);
+			tester_debug("%s: [t=%.0lf, step=%d] Test suspend, "
+				     "device_found = %s",
+				     test->test_info->test_name,
+				     difftime(time(NULL), start_time), i,
+				     ret ? "true" : "false");
+			return FALSE;
+		}
+
+		for (j = 0; j < test->test_steps[i].duration; j++) {
+			ret = btd_adv_monitor_test_rssi(
+						test->adv_monitor_obj,
+						test->btd_device_obj,
+						test->test_steps[i].adv_rssi);
+			tester_debug("%s: [t=%.0lf, step=%d] Test "
+				     "advertisement RSSI %d, device_found = %s",
+				     test->test_info->test_name,
+				     difftime(time(NULL), start_time), i,
+				     test->test_steps[i].adv_rssi,
+				     ret ? "true" : "false");
+
+			/* Sleep for a second to simulate receiving
+			 * advertisement once every second
+			 */
+			sleep(ADV_INTERVAL);
+		}
+		g_assert(ret == test->test_steps[i].result);
+
+		i++;
+	}
+
+	tester_debug("%s: [t=%.0lf] Test done", test->test_info->test_name,
+		     difftime(time(NULL), start_time));
+
+	tester_test_passed();
+
+	return FALSE;
+}
+
+/* Handler function to prepare for a test */
+static void setup_handler(gconstpointer data)
+{
+	const struct test_data *test = data;
+
+	if (test->test_type == TEST_RSSI_FILTER)
+		setup_rssi_filter_test(test->rssi_filter_test_data);
+}
+
+/* Handler function to cleanup after the test is done */
+static void teardown_handler(gconstpointer data)
+{
+	const struct test_data *test = data;
+
+	if (test->test_type == TEST_RSSI_FILTER)
+		teardown_rssi_filter_test(test->rssi_filter_test_data);
+}
+
+/* Handler function to execute a test with the given data set */
+static void test_handler(gconstpointer data)
+{
+	const struct test_data *test = data;
+
+	if (test->test_type == TEST_RSSI_FILTER)
+		test_rssi_filter(test->rssi_filter_test_data);
+}
+
+int main(int argc, char *argv[])
+{
+	tester_init(&argc, &argv);
+
+	__btd_log_init("*", 0);
+
+	define_test("/advmon/rssi/1", TEST_RSSI_FILTER, rssi_data_1,
+		    setup_handler, test_handler, teardown_handler);
+	define_test("/advmon/rssi/2", TEST_RSSI_FILTER, rssi_data_2,
+		    setup_handler, test_handler, teardown_handler);
+	define_test("/advmon/rssi/3", TEST_RSSI_FILTER, rssi_data_3,
+		    setup_handler, test_handler, teardown_handler);
+	define_test("/advmon/rssi/4", TEST_RSSI_FILTER, rssi_data_4,
+		    setup_handler, test_handler, teardown_handler);
+	define_test("/advmon/rssi/5", TEST_RSSI_FILTER, rssi_data_5,
+		    setup_handler, test_handler, teardown_handler);
+
+	return tester_run();
+}