--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/tests/test_scheduled_task.c Thu Jul 24 23:33:18 2025 -0500 @@ -0,0 +1,443 @@ +/* + * 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_scheduled_task_new(void) { + PurpleScheduledTask *task = NULL; + + task = purple_scheduled_task_new("test-task", "A task for testing", TRUE); + + birb_assert_type(task, PURPLE_TYPE_SCHEDULED_TASK); + + g_assert_finalize_object(task); +} + +static void +test_purple_scheduled_task_properties(void) { + PurpleScheduledTask *task = NULL; + PurpleScheduledTaskState state = PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED; + PurpleTags *tags = NULL; + GDateTime *execute_at = NULL; + gboolean cancellable = TRUE; + gboolean persistent = FALSE; + char *id = NULL; + char *subtitle = NULL; + char *task_type = NULL; + char *title = NULL; + + task = g_object_new( + PURPLE_TYPE_SCHEDULED_TASK, + "cancellable", FALSE, + "id", "0xabc123", + "persistent", TRUE, + "subtitle", "a task for testing", + "task-type", "test", + "title", "Test", + NULL); + + g_assert_true(PURPLE_IS_SCHEDULED_TASK(task)); + + g_object_get( + G_OBJECT(task), + "cancellable", &cancellable, + "execute-at", &execute_at, + "id", &id, + "persistent", &persistent, + "state", &state, + "subtitle", &subtitle, + "tags", &tags, + "task-type", &task_type, + "title", &title, + NULL); + + g_assert_false(cancellable); + + /* The task hasn't been scheduled so it doesn't have an execution time. */ + g_assert_null(execute_at); + + g_assert_cmpstr(id, ==, "0xabc123"); + g_clear_pointer(&id, g_free); + + g_assert_true(persistent); + + g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED); + + g_assert_cmpstr(subtitle, ==, "a task for testing"); + g_clear_pointer(&subtitle, g_free); + + birb_assert_type(tags, PURPLE_TYPE_TAGS); + g_clear_object(&tags); + + g_assert_cmpstr(task_type, ==, "test"); + g_clear_pointer(&task_type, g_free); + + g_assert_cmpstr(title, ==, "Test"); + g_clear_pointer(&title, g_free); + + g_assert_finalize_object(task); +} + +/****************************************************************************** + * Schedule Tests + *****************************************************************************/ +static void +test_purple_scheduled_task_executed_counter_cb(PurpleScheduledTask *task, + gpointer data) +{ + PurpleScheduledTaskState state = PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED; + guint *counter = data; + + birb_assert_type(task, PURPLE_TYPE_SCHEDULED_TASK); + + state = purple_scheduled_task_get_state(task); + g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_EXECUTING); + + *counter = *counter + 1; +} + +static void +test_purple_scheduled_task_executed_cancel_cb(PurpleScheduledTask *task, + G_GNUC_UNUSED gpointer data) +{ + GError *error = NULL; + gboolean result = FALSE; + + result = purple_scheduled_task_schedule_relative(task, + 10 * G_TIME_SPAN_MINUTE, + &error); + g_assert_error(error, + PURPLE_SCHEDULED_TASK_ERROR, + PURPLE_SCHEDULED_TASK_ERROR_RESCHEDULE_EXECUTING_TASK); + g_clear_error(&error); + g_assert_false(result); +} + +static void +test_purple_scheduled_task_executed_quit_cb(PurpleScheduledTask *task, + gpointer data) +{ + birb_assert_type(task, PURPLE_TYPE_SCHEDULED_TASK); + + g_main_loop_quit(data); +} + +static void +test_purple_scheduled_task_main_loop_timeout_cb(gpointer data) { + g_main_loop_quit(data); + + g_assert_not_reached(); +} + +static void +test_purple_scheduled_task_schedule_normal(void) { + PurpleScheduledTask *task = NULL; + PurpleScheduledTaskState state = PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED; + GDateTime *execute_at = NULL; + GError *error = NULL; + GMainLoop *loop = NULL; + guint counter = 0; + gboolean result = FALSE; + + task = purple_scheduled_task_new("test", "Test task", TRUE); + g_signal_connect(task, "execute", + G_CALLBACK(test_purple_scheduled_task_executed_counter_cb), + &counter); + + /* Add another signal handler to verify that you can't reschedule an + * executing task. + */ + g_signal_connect(task, "execute", + G_CALLBACK(test_purple_scheduled_task_executed_cancel_cb), + NULL); + + /* Verify the default state is unscheduled. */ + state = purple_scheduled_task_get_state(task); + g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED); + + /* Make sure that we don't currently have an execute-at. */ + execute_at = purple_scheduled_task_get_execute_at(task); + g_assert_null(execute_at); + + /* Now schedule the task to execute 10 milliseconds from now. */ + result = purple_scheduled_task_schedule_relative(task, + 10 * G_TIME_SPAN_MILLISECOND, + &error); + g_assert_no_error(error); + g_assert_true(result); + + /* Make sure the execute-at property got set. */ + execute_at = purple_scheduled_task_get_execute_at(task); + g_assert_nonnull(execute_at); + + /* Make sure the state was set to scheduled. */ + state = purple_scheduled_task_get_state(task); + g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_SCHEDULED); + + /* Create our main loop and add a timeout to avoid runaways. */ + loop = g_main_loop_new(NULL, FALSE); + g_signal_connect(task, "execute", + G_CALLBACK(test_purple_scheduled_task_executed_quit_cb), + loop); + + g_timeout_add_seconds_once(10, + test_purple_scheduled_task_main_loop_timeout_cb, + loop); + g_main_loop_run(loop); + g_main_loop_unref(loop); + + /* Make sure that the execute signal was emitted. */ + g_assert_cmpuint(counter, ==, 1); + + /* Make sure the state was updated to executed. */ + state = purple_scheduled_task_get_state(task); + g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_EXECUTED); + + g_assert_finalize_object(task); +} + +static void +test_purple_scheduled_task_schedule_cancelled(void) { + PurpleScheduledTask *task = NULL; + PurpleScheduledTaskState state = PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED; + GError *error = NULL; + gboolean result = FALSE; + + task = purple_scheduled_task_new("test", "Test task", TRUE); + + state = purple_scheduled_task_get_state(task); + g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED); + + /* Cancelling a task that is not currently scheduled should do nothing. */ + purple_scheduled_task_cancel(task); + + state = purple_scheduled_task_get_state(task); + g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED); + + /* Schedule the task. */ + result = purple_scheduled_task_schedule_relative(task, + 10 * G_TIME_SPAN_MINUTE, + &error); + g_assert_no_error(error); + g_assert_true(result); + + state = purple_scheduled_task_get_state(task); + g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_SCHEDULED); + + /* Cancel the task. */ + purple_scheduled_task_cancel(task); + + state = purple_scheduled_task_get_state(task); + g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_CANCELLED); + + g_assert_finalize_object(task); +} + +static void +test_purple_scheduled_task_schedule_reschedule(void) { + PurpleScheduledTask *task = NULL; + PurpleScheduledTaskState state = PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED; + GDateTime *now = NULL; + GDateTime *later = NULL; + GDateTime *execute_at = NULL; + GError *error = NULL; + gboolean result = FALSE; + + task = purple_scheduled_task_new("test", "Test task", TRUE); + + state = purple_scheduled_task_get_state(task); + g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED); + + /* Schedule the task. */ + result = purple_scheduled_task_schedule_relative(task, + 10 * G_TIME_SPAN_MINUTE, + &error); + g_assert_no_error(error); + g_assert_true(result); + + state = purple_scheduled_task_get_state(task); + g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_SCHEDULED); + + /* Reschedule the task for an hour. */ + now = g_date_time_new_now_local(); + later = g_date_time_add(now, 1 * G_TIME_SPAN_HOUR); + g_clear_pointer(&now, g_date_time_unref); + + result = purple_scheduled_task_schedule(task, later, &error); + g_assert_no_error(error); + g_assert_true(result); + + /* Verify that the execute-at property is correct. */ + execute_at = purple_scheduled_task_get_execute_at(task); + g_assert_true(birb_date_time_equal(execute_at, later)); + g_clear_pointer(&later, g_date_time_unref); + + state = purple_scheduled_task_get_state(task); + g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_SCHEDULED); + + /* Cancel the task to clear the timeout and the reference it holds. */ + purple_scheduled_task_cancel(task); + + g_assert_finalize_object(task); +} + +static void +test_purple_scheduled_task_schedule_past(void) { + PurpleScheduledTask *task = NULL; + PurpleScheduledTaskState state = PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED; + GError *error = NULL; + gboolean result = FALSE; + + task = purple_scheduled_task_new("test", "Test task", TRUE); + + state = purple_scheduled_task_get_state(task); + g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED); + + /* Schedule the task for 10 minutes ago. */ + result = purple_scheduled_task_schedule_relative(task, + -10 * G_TIME_SPAN_MINUTE, + &error); + g_assert_error(error, + PURPLE_SCHEDULED_TASK_ERROR, + PURPLE_SCHEDULED_TASK_ERROR_EXECUTE_AT_IN_PAST); + g_clear_error(&error); + g_assert_false(result); + + g_assert_finalize_object(task); +} + +static void +test_purple_scheduled_task_schedule_reuse(void) { + PurpleScheduledTask *task = NULL; + PurpleScheduledTaskState state = PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED; + GDateTime *execute_at = NULL; + GError *error = NULL; + GMainLoop *loop = NULL; + guint counter = 0; + gboolean result = FALSE; + + task = purple_scheduled_task_new("test", "Test task", TRUE); + g_signal_connect(task, "execute", + G_CALLBACK(test_purple_scheduled_task_executed_counter_cb), + &counter); + + /* Verify the default state is unscheduled. */ + state = purple_scheduled_task_get_state(task); + g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_UNSCHEDULED); + + /* Make sure that we don't currently have an execute-at. */ + execute_at = purple_scheduled_task_get_execute_at(task); + g_assert_null(execute_at); + + /* Now schedule the task to execute 10 milliseconds from now. */ + result = purple_scheduled_task_schedule_relative(task, + 10 * G_TIME_SPAN_MILLISECOND, + &error); + g_assert_no_error(error); + g_assert_true(result); + + /* Make sure the execute-at property got set. */ + execute_at = purple_scheduled_task_get_execute_at(task); + g_assert_nonnull(execute_at); + + /* Make sure the state was set to scheduled. */ + state = purple_scheduled_task_get_state(task); + g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_SCHEDULED); + + /* Create our main loop and add a timeout to avoid runaways. */ + loop = g_main_loop_new(NULL, FALSE); + g_signal_connect(task, "execute", + G_CALLBACK(test_purple_scheduled_task_executed_quit_cb), + loop); + + g_timeout_add_seconds_once(10, + test_purple_scheduled_task_main_loop_timeout_cb, + loop); + g_main_loop_run(loop); + + /* Make sure that the execute signal was emitted. */ + g_assert_cmpuint(counter, ==, 1); + + /* Make sure the state was updated to executed. */ + state = purple_scheduled_task_get_state(task); + g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_EXECUTED); + + /* Now reset everything. */ + counter = 0; + g_signal_connect(task, "execute", + G_CALLBACK(test_purple_scheduled_task_executed_quit_cb), + loop); + g_timeout_add_seconds_once(10, + test_purple_scheduled_task_main_loop_timeout_cb, + loop); + + /* Now schedule the task to execute 10 milliseconds from now. */ + result = purple_scheduled_task_schedule_relative(task, + 10 * G_TIME_SPAN_MILLISECOND, + &error); + g_assert_no_error(error); + g_assert_true(result); + + /* Run the loop. */ + g_main_loop_run(loop); + + /* Make sure that the execute signal was emitted. */ + g_assert_cmpuint(counter, ==, 1); + + /* Make sure the state was updated to executed. */ + state = purple_scheduled_task_get_state(task); + g_assert_cmpuint(state, ==, PURPLE_SCHEDULED_TASK_STATE_EXECUTED); + + g_main_loop_unref(loop); + + g_assert_finalize_object(task); +} + +/****************************************************************************** + * Main + *****************************************************************************/ +int +main(int argc, char *argv[]) { + g_test_init(&argc, &argv, NULL); + g_test_set_nonfatal_assertions(); + + g_test_add_func("/scheduled-task/new", test_purple_scheduled_task_new); + g_test_add_func("/scheduled-task/properties", + test_purple_scheduled_task_properties); + + g_test_add_func("/scheduled-task/schedule/normal", + test_purple_scheduled_task_schedule_normal); + g_test_add_func("/scheduled-task/schedule/cancelled", + test_purple_scheduled_task_schedule_cancelled); + g_test_add_func("/scheduled-task/schedule/reschedule", + test_purple_scheduled_task_schedule_reschedule); + g_test_add_func("/scheduled-task/schedule/past", + test_purple_scheduled_task_schedule_past); + g_test_add_func("/scheduled-task/schedule/reuse", + test_purple_scheduled_task_schedule_reuse); + + return g_test_run(); +}