libpurple/tests/test_scheduler.c

changeset 43293
f5d33dbc18a9
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/tests/test_scheduler.c	Thu Jul 24 23:35:13 2025 -0500
@@ -0,0 +1,489 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * This library 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 of the License, or (at your option) any later version.
+ *
+ * This library 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.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <glib.h>
+
+#include <birb.h>
+
+#include <purple.h>
+
+/******************************************************************************
+ * Tests
+ *****************************************************************************/
+static void
+test_purple_scheduler_new(void) {
+	PurpleScheduler *scheduler = NULL;
+
+	scheduler = purple_scheduler_new();
+	birb_assert_type(scheduler, PURPLE_TYPE_SCHEDULER);
+	g_assert_true(G_IS_LIST_MODEL(scheduler));
+
+	g_assert_finalize_object(scheduler);
+}
+
+static void
+test_purple_scheduler_properties(void) {
+	PurpleScheduler *scheduler = NULL;
+	GType item_type = G_TYPE_INVALID;
+	guint n_items = 0;
+
+	scheduler = g_object_new(
+		PURPLE_TYPE_SCHEDULER,
+		NULL);
+
+	g_object_get(
+		G_OBJECT(scheduler),
+		"item-type", &item_type,
+		"n-items", &n_items,
+		NULL);
+
+	g_assert_cmpuint(item_type, ==, PURPLE_TYPE_SCHEDULED_TASK);
+
+	g_assert_cmpuint(n_items, ==, 0);
+
+	g_assert_finalize_object(scheduler);
+}
+
+static void
+test_purple_scheduler_add_remove(void) {
+	PurpleScheduledTask *task = NULL;
+	PurpleScheduledTaskState state = PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED;
+	PurpleScheduler *scheduler = NULL;
+	GDateTime *execute_at = NULL;
+	GDateTime *now = NULL;
+	GError *error = NULL;
+	guint counter = 0;
+	gboolean result = FALSE;
+
+	scheduler = purple_scheduler_new();
+	birb_assert_type(scheduler, PURPLE_TYPE_SCHEDULER);
+	g_assert_true(G_IS_LIST_MODEL(scheduler));
+
+	/* Create our execute_at time. */
+	now = g_date_time_new_now_local();
+	execute_at = g_date_time_add(now, 10 * G_TIME_SPAN_MINUTE);
+	g_clear_pointer(&now, g_date_time_unref);
+
+	/* Wire up our signals. */
+	birb_count_list_model_items_changed(G_LIST_MODEL(scheduler), &counter);
+
+	/* Create the task. */
+	task = purple_scheduled_task_new("test-scheduler", "Scheduler Tests",
+	                                 TRUE);
+	birb_assert_type(task, PURPLE_TYPE_SCHEDULED_TASK);
+
+	/* Make sure the task is unscheduled. */
+	state = purple_scheduled_task_get_state(task);
+	g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED);
+
+	/* Add the task to the scheduler. */
+	counter = 0;
+	result = purple_scheduler_add_task(scheduler, task, execute_at, &error);
+
+	g_assert_no_error(error);
+	g_assert_true(result);
+
+	/* Make sure items changed was called once and that we have 1 item in the
+	 * list model.
+	 */
+	g_assert_cmpuint(counter, ==, 1);
+	birb_assert_list_model_n_items(scheduler, 1);
+
+	/* Make sure that the task got scheduled. */
+	state = purple_scheduled_task_get_state(task);
+	g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_SCHEDULED);
+
+	/* Remove the task. */
+	counter = 0;
+
+	result = purple_scheduler_remove_task(scheduler,
+	                                      purple_scheduled_task_get_id(task));
+	g_assert_true(result);
+
+	/* Make sure items changed was called once and that the model empty. */
+	g_assert_cmpuint(counter, ==, 1);
+	birb_assert_list_model_n_items(scheduler, 0);
+
+	/* Make sure the that the task got cancelled. */
+	state = purple_scheduled_task_get_state(task);
+	g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_CANCELLED);
+
+	/* After removal from the scheduler, only our reference should exist. */
+	g_assert_finalize_object(task);
+
+	/* Clean up the scheduler. */
+	g_assert_finalize_object(scheduler);
+
+	g_clear_pointer(&execute_at, g_date_time_unref);
+}
+
+static void
+test_purple_scheduler_add_already_scheduled(void) {
+	PurpleScheduledTask *task = NULL;
+	PurpleScheduler *scheduler = NULL;
+	GDateTime *original_execute_at = NULL;
+	GDateTime *updated_execute_at = NULL;
+	GError *error = NULL;
+	gboolean result = FALSE;
+
+	scheduler = purple_scheduler_new();
+
+	/* Create the task. */
+	task = purple_scheduled_task_new("test-scheduler", "Scheduler Tests",
+	                                 TRUE);
+
+	/* Schedule the task and store the original execute_at. */
+	result = purple_scheduled_task_schedule_relative(task,
+	                                                 10 * G_TIME_SPAN_MILLISECOND,
+	                                                 &error);
+	g_assert_no_error(error);
+	g_assert_true(result);
+
+	original_execute_at = purple_scheduled_task_get_execute_at(task);
+	if(original_execute_at != NULL) {
+		g_date_time_ref(original_execute_at);
+	}
+
+	/* Add the task to the scheduler. */
+	result = purple_scheduler_add_task_relative(scheduler,
+	                                            task,
+	                                            100 * G_TIME_SPAN_MILLISECOND,
+	                                            &error);
+	g_assert_no_error(error);
+	g_assert_true(result);
+
+	/* Get the execute time of the task and verify that it is not the same as
+	 * the original time.
+	 */
+	updated_execute_at = purple_scheduled_task_get_execute_at(task);
+	g_assert_false(birb_date_time_equal(original_execute_at,
+	                                    updated_execute_at));
+
+	/* Clean up everything. */
+	g_assert_finalize_object(scheduler);
+	g_assert_finalize_object(task);
+	g_clear_pointer(&original_execute_at, g_date_time_unref);
+}
+
+static void
+test_purple_scheduler_double_add(void) {
+	PurpleScheduledTask *task = NULL;
+	PurpleScheduledTaskState state = PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED;
+	PurpleScheduler *scheduler = NULL;
+	GDateTime *execute_at = NULL;
+	GDateTime *now = NULL;
+	GError *error = NULL;
+	guint counter = 0;
+	gboolean result = FALSE;
+
+	scheduler = purple_scheduler_new();
+	birb_assert_type(scheduler, PURPLE_TYPE_SCHEDULER);
+	g_assert_true(G_IS_LIST_MODEL(scheduler));
+
+	/* Create our execute_at time. */
+	now = g_date_time_new_now_local();
+	execute_at = g_date_time_add(now, 10 * G_TIME_SPAN_MINUTE);
+	g_clear_pointer(&now, g_date_time_unref);
+
+	/* Wire up our signals. */
+	birb_count_list_model_items_changed(G_LIST_MODEL(scheduler), &counter);
+
+	/* Create the task. */
+	task = purple_scheduled_task_new("test-scheduler", "Scheduler Tests",
+	                                 TRUE);
+	birb_assert_type(task, PURPLE_TYPE_SCHEDULED_TASK);
+
+	/* Make sure the task is unscheduled. */
+	state = purple_scheduled_task_get_state(task);
+	g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED);
+
+	/* Add the task to the scheduler. */
+	counter = 0;
+	result = purple_scheduler_add_task(scheduler, task, execute_at, &error);
+
+	g_assert_no_error(error);
+	g_assert_true(result);
+
+	/* Make sure items changed was called once and that we have 1 item in the
+	 * list model.
+	 */
+	g_assert_cmpuint(counter, ==, 1);
+	birb_assert_list_model_n_items(scheduler, 1);
+
+	/* Make sure that the task got scheduled. */
+	state = purple_scheduled_task_get_state(task);
+	g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_SCHEDULED);
+
+	/* Now add the task again. */
+	counter = 0;
+	result = purple_scheduler_add_task(scheduler, task, execute_at, &error);
+	g_assert_error(error,
+	               PURPLE_SCHEDULER_ERROR,
+	               PURPLE_SCHEDULER_ERROR_TASK_EXISTS);
+	g_clear_error(&error);
+	g_assert_false(result);
+
+	/* Make sure the items-changed signal wasn't called and that we still only
+	 * have one item in the list.
+	 */
+	g_assert_cmpuint(counter, ==, 0);
+	birb_assert_list_model_n_items(scheduler, 1);
+
+	/* Cleanup. We don't remove the task because we want to make sure the
+	 * scheduler will cancel it when it shuts down. Also the task is last as
+	 * it's still known to the scheduler.
+	 */
+	g_assert_finalize_object(scheduler);
+
+	state = purple_scheduled_task_get_state(task);
+	g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_CANCELLED);
+
+	g_assert_finalize_object(task);
+
+	g_clear_pointer(&execute_at, g_date_time_unref);
+}
+
+static void
+test_purple_scheduler_double_remove(void) {
+	PurpleScheduledTask *task = NULL;
+	PurpleScheduledTaskState state = PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED;
+	PurpleScheduler *scheduler = NULL;
+	GDateTime *execute_at = NULL;
+	GDateTime *now = NULL;
+	GError *error = NULL;
+	guint counter = 0;
+	gboolean result = FALSE;
+
+	scheduler = purple_scheduler_new();
+	birb_assert_type(scheduler, PURPLE_TYPE_SCHEDULER);
+	g_assert_true(G_IS_LIST_MODEL(scheduler));
+
+	/* Create our execute_at time. */
+	now = g_date_time_new_now_local();
+	execute_at = g_date_time_add(now, 10 * G_TIME_SPAN_MINUTE);
+	g_clear_pointer(&now, g_date_time_unref);
+
+	/* Wire up our signals. */
+	birb_count_list_model_items_changed(G_LIST_MODEL(scheduler), &counter);
+
+	/* Create the task. */
+	task = purple_scheduled_task_new("test-scheduler", "Scheduler Tests",
+	                                 TRUE);
+	birb_assert_type(task, PURPLE_TYPE_SCHEDULED_TASK);
+
+	/* Make sure the task is unscheduled. */
+	state = purple_scheduled_task_get_state(task);
+	g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED);
+
+	/* Add the task to the scheduler. */
+	counter = 0;
+	result = purple_scheduler_add_task(scheduler, task, execute_at, &error);
+
+	g_assert_no_error(error);
+	g_assert_true(result);
+
+	/* Make sure items changed was called once and that we have 1 item in the
+	 * list model.
+	 */
+	g_assert_cmpuint(counter, ==, 1);
+	birb_assert_list_model_n_items(scheduler, 1);
+
+	/* Make sure that the task got scheduled. */
+	state = purple_scheduled_task_get_state(task);
+	g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_SCHEDULED);
+
+	/* Remove the task. */
+	counter = 0;
+	result = purple_scheduler_remove_task(scheduler,
+	                                      purple_scheduled_task_get_id(task));
+	g_assert_true(result);
+
+	/* Make sure the items-changed signal got called once and that we no longer
+	 * have any items in the model.
+	 */
+	g_assert_cmpuint(counter, ==, 1);
+	birb_assert_list_model_n_items(scheduler, 0);
+
+	/* Attempt to remove the task again. */
+	counter = 0;
+	result = purple_scheduler_remove_task(scheduler,
+	                                      purple_scheduled_task_get_id(task));
+	g_assert_false(result);
+
+	/* Make sure the items-changed signal wasn't called and that we still don't
+	 * have any items in the model.
+	 */
+	g_assert_cmpuint(counter, ==, 0);
+	birb_assert_list_model_n_items(scheduler, 0);
+
+	/* Cleanup. We remove task first because the scheduler shouldn't know about
+	 * it anymore.
+	 */
+	g_assert_finalize_object(task);
+	g_assert_finalize_object(scheduler);
+
+	g_clear_pointer(&execute_at, g_date_time_unref);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+static void
+test_purple_scheduler_execute_task_cb(PurpleScheduler *scheduler,
+                                      PurpleScheduledTask *task,
+                                      const char *task_type,
+                                      gpointer data)
+{
+	PurpleScheduledTaskState state = PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED;
+	const char *actual_task_type = NULL;
+	guint *counter = data;
+
+	birb_assert_type(scheduler, PURPLE_TYPE_SCHEDULER);
+
+	state = purple_scheduled_task_get_state(task);
+	g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_EXECUTING);
+
+	actual_task_type = purple_scheduled_task_get_task_type(task);
+	g_assert_cmpstr(actual_task_type, ==, task_type);
+
+	*counter = *counter + 1;
+}
+
+static void
+test_purple_scheduler_signals_quit_cb(G_GNUC_UNUSED PurpleScheduler *scheduler,
+                                      G_GNUC_UNUSED PurpleScheduledTask *task,
+                                      G_GNUC_UNUSED const char *task_type,
+                                      gpointer data)
+{
+	g_main_loop_quit(data);
+}
+
+static void
+test_purple_scheduler_signals_timeout_cb(gpointer data) {
+	g_main_loop_quit(data);
+
+	g_assert_not_reached();
+}
+
+static void
+test_purple_scheduler_signals(void) {
+	PurpleScheduledTask *task1 = NULL;
+	PurpleScheduledTask *task2 = NULL;
+	PurpleScheduler *scheduler = NULL;
+	GError *error = NULL;
+	GMainLoop *loop = NULL;
+	guint all_counter = 0;
+	guint detailed_counter = 0;
+	gboolean result = FALSE;
+
+	/* This test creates 2 tasks, one for 10ms from now and the second for 20ms
+	 * from now. When the 20ms task is executed the main loop will be quit and
+	 * allow the rest of the test to finish.
+	 *
+	 * There is a 2 second timeout to make sure we don't hang the unit tests if
+	 * something unexpected happens. We use 2 seconds because internally the
+	 * tasks are scheduled with g_timeout_add_seconds which tries to schedule
+	 * timeouts together to avoid excessive CPU wake ups, so 2 seconds should
+	 * cover that.
+	 */
+
+	/* Create the counter and add our signal handlers with and without the
+	 * detail.
+	 */
+	scheduler = purple_scheduler_new();
+	g_signal_connect(scheduler,
+	                 "execute-task",
+	                 G_CALLBACK(test_purple_scheduler_execute_task_cb),
+	                 &all_counter);
+	g_signal_connect(scheduler,
+	                 "execute-task::scheduler-test-2",
+	                 G_CALLBACK(test_purple_scheduler_execute_task_cb),
+	                 &detailed_counter);
+
+	/* Add the first task. */
+	task1 = purple_scheduled_task_new("scheduler-test-1", "Scheduler Test 1",
+	                                  TRUE);
+	result = purple_scheduler_add_task_relative(scheduler,
+	                                            task1,
+	                                            10 * G_TIME_SPAN_MILLISECOND,
+	                                            &error);
+	g_assert_no_error(error);
+	g_assert_true(result);
+
+	/* Add the second task. */
+	task2 = purple_scheduled_task_new("scheduler-test-2", "Scheduler Test 2",
+	                                  FALSE);
+	result = purple_scheduler_add_task_relative(scheduler,
+	                                            task2,
+	                                            20 * G_TIME_SPAN_MILLISECOND,
+	                                            &error);
+	g_assert_no_error(error);
+	g_assert_true(result);
+
+	/* Create the main loop to run the tasks. */
+	loop = g_main_loop_new(NULL, FALSE);
+
+	/* Add a handler to quit the main loop when the second task is executed. */
+	g_signal_connect(scheduler,
+	                 "execute-task::scheduler-test-2",
+	                 G_CALLBACK(test_purple_scheduler_signals_quit_cb),
+	                 loop);
+
+	/* Add a timeout to avoid hangs on unexpected behavior. */
+	g_timeout_add_seconds_once(2, test_purple_scheduler_signals_timeout_cb,
+	                           loop);
+
+	/* Run the main loop. */
+	g_main_loop_run(loop);
+
+	g_clear_pointer(&loop, g_main_loop_unref);
+
+	/* Make sure the counters are correct. */
+	g_assert_cmpuint(all_counter, ==, 2);
+	g_assert_cmpuint(detailed_counter, ==, 1);
+
+	/* Make sure the scheduler is empty. */
+	birb_assert_list_model_n_items(scheduler, 0);
+
+	g_assert_finalize_object(scheduler);
+	g_assert_finalize_object(task1);
+	g_assert_finalize_object(task2);
+}
+
+/******************************************************************************
+ * Main
+ *****************************************************************************/
+int
+main(int argc, char *argv[]) {
+	g_test_init(&argc, &argv, NULL);
+	g_test_set_nonfatal_assertions();
+
+	g_test_add_func("/scheduler/new", test_purple_scheduler_new);
+	g_test_add_func("/scheduler/properties", test_purple_scheduler_properties);
+
+	g_test_add_func("/scheduler/add-remove", test_purple_scheduler_add_remove);
+	g_test_add_func("/scheduler/add-already-scheduled",
+	                test_purple_scheduler_add_already_scheduled);
+	g_test_add_func("/scheduler/double-add", test_purple_scheduler_double_add);
+	g_test_add_func("/scheduler/double-remove",
+	                test_purple_scheduler_double_remove);
+
+	g_test_add_func("/scheduler/signals", test_purple_scheduler_signals);
+
+	return g_test_run();
+}

mercurial