--- /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(); +}