diff --git a/include/core/tool_initializer.h b/include/core/tool_initializer.h index 411d7a2..48a1454 100644 --- a/include/core/tool_initializer.h +++ b/include/core/tool_initializer.h @@ -31,6 +31,25 @@ typedef struct gsize version_failed_count; } ToolInitializationSummary; +/** + * @brief Fonction appelée après la fin d’une initialisation. + * + * Le callback est exécuté sur le contexte principal GLib. + * + * summary est non NULL uniquement après une réussite. + * error est non NULL uniquement lorsqu’une erreur est disponible. + * + * Les pointeurs summary et error sont empruntés et restent valides + * uniquement pendant l’appel du callback. + */ +typedef void (*ToolInitializerCompletedCallback)( + ToolInitializer *tool_initializer, + BackgroundTaskState final_state, + const ToolInitializationSummary *summary, + const GError *error, + gpointer user_data +); + /** * @brief Codes d’erreur de ToolInitializer. */ @@ -150,6 +169,35 @@ ToolRegistry *tool_initializer_get_registry( ToolInitializer *tool_initializer ); +/** + * @brief Enregistre le callback appelé après chaque initialisation. + * + * Le même callback est conservé pour les lancements suivants. + * + * En passant callback == NULL, l’enregistrement actuel est supprimé. + * + * Lorsque destroy_notify est non NULL, ToolInitializer devient + * responsable de user_data. La fonction de destruction sera appelée + * exactement une fois lorsque : + * + * - le callback est remplacé ; + * - le callback est supprimé ; + * - l’initialiseur est détruit. + * + * Le callback est exécuté sur le contexte principal GLib. + * + * @param tool_initializer Initialiseur concerné. + * @param callback Fonction appelée après la fin, ou NULL. + * @param user_data Données transmises au callback. + * @param destroy_notify Fonction libérant user_data, ou NULL. + */ +void tool_initializer_set_completed_callback( + ToolInitializer *tool_initializer, + ToolInitializerCompletedCallback callback, + gpointer user_data, + GDestroyNotify destroy_notify +); + /** * @brief Démarre l’initialisation en arrière-plan. * diff --git a/labfy-investigation b/labfy-investigation index f314d53..a47839f 100755 Binary files a/labfy-investigation and b/labfy-investigation differ diff --git a/src/core/application.c b/src/core/application.c index cdd741e..40884a0 100644 --- a/src/core/application.c +++ b/src/core/application.c @@ -77,6 +77,107 @@ static void application_present_error( const char *message ); +/** + * @brief Traite la fin de l’initialisation des outils externes. + * + * Ce callback est exécuté sur le contexte principal GLib. + * + * @param tool_initializer Initialiseur ayant terminé. + * @param final_state État final de la tâche. + * @param summary Résumé emprunté en cas de réussite. + * @param error Erreur empruntée en cas d’échec. + * @param user_data Pointeur emprunté vers Application. + */ +static void application_on_tool_initialization_completed( + ToolInitializer *tool_initializer, + BackgroundTaskState final_state, + const ToolInitializationSummary *summary, + const GError *error, + gpointer user_data +) +{ + Application *application = user_data; + + if (application == NULL || + tool_initializer == NULL) + { + return; + } + + if (tool_initializer != + application->tool_initializer) + { + g_warning( + "Notification reçue depuis un initialiseur inconnu." + ); + + return; + } + + switch (final_state) + { + case BACKGROUND_TASK_STATE_COMPLETED: + if (summary == NULL) + { + g_warning( + "L’initialisation des outils est terminée " + "sans résumé valide." + ); + + return; + } + + g_message( + "Outils externes initialisés : " + "%" G_GSIZE_FORMAT " disponibles, " + "%" G_GSIZE_FORMAT " absents, " + "%" G_GSIZE_FORMAT " versions détectées, " + "%" G_GSIZE_FORMAT " versions non détectées.", + summary->available_count, + summary->missing_count, + summary->version_detected_count, + summary->version_failed_count + ); + + break; + + case BACKGROUND_TASK_STATE_CANCELLED: + g_message( + "L’initialisation des outils externes a été annulée." + ); + + break; + + case BACKGROUND_TASK_STATE_FAILED: + g_warning( + "L’initialisation des outils externes a échoué : %s", + error != NULL + ? error->message + : "erreur inconnue" + ); + + application_present_error( + application, + "Initialisation des outils impossible", + error != NULL + ? error->message + : "Les outils externes n’ont pas pu être initialisés." + ); + + break; + + case BACKGROUND_TASK_STATE_PENDING: + case BACKGROUND_TASK_STATE_RUNNING: + default: + g_warning( + "Notification de fin reçue avec un état invalide : %d.", + (int) final_state + ); + + break; + } +} + /** * @brief Démarre la détection asynchrone des outils externes. * @@ -154,6 +255,20 @@ static void application_present_error( ); } +static void application_on_tool_initialization_completed( + ToolInitializer *tool_initializer, + BackgroundTaskState final_state, + const ToolInitializationSummary *summary, + const GError *error, + gpointer user_data +); + +static void application_present_error( + Application *application, + const char *title, + const char *message +); + /** * @brief Installe une nouvelle session et son arbre dans l'application. * @@ -1145,6 +1260,13 @@ Application *application_new(void) return NULL; } + tool_initializer_set_completed_callback( + application->tool_initializer, + application_on_tool_initialization_completed, + application, + NULL + ); + application->gtk_application = gtk_application_new( APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS @@ -1219,6 +1341,16 @@ void application_free( return; } + if (application->tool_initializer != NULL) + { + tool_initializer_set_completed_callback( + application->tool_initializer, + NULL, + NULL, + NULL + ); + } + task_manager_cancel_all( application->task_manager ); diff --git a/src/core/tool_initializer.c b/src/core/tool_initializer.c index 0c91b60..eebb633 100644 --- a/src/core/tool_initializer.c +++ b/src/core/tool_initializer.c @@ -9,6 +9,15 @@ #include #include +typedef struct +{ + gatomicrefcount reference_count; + + ToolInitializerCompletedCallback callback; + gpointer user_data; + GDestroyNotify destroy_notify; +} ToolInitializerCallbackRegistration; + /** * @struct ToolInitializer * @brief État interne de l’initialiseur d’outils. @@ -24,11 +33,89 @@ struct ToolInitializer BackgroundTask *task; ToolInitializationSummary last_summary; - + ToolInitializerCallbackRegistration *callback_registration; gboolean has_last_summary; gboolean is_running; }; +/** + * @brief Enregistrement immuable d’un callback de fin. + */ +static ToolInitializerCallbackRegistration * +tool_initializer_callback_registration_new( + ToolInitializerCompletedCallback callback, + gpointer user_data, + GDestroyNotify destroy_notify +) +{ + ToolInitializerCallbackRegistration *registration = NULL; + + if (callback == NULL) + { + return NULL; + } + + registration = g_new0( + ToolInitializerCallbackRegistration, + 1 + ); + + g_atomic_ref_count_init( + ®istration->reference_count + ); + + registration->callback = callback; + registration->user_data = user_data; + registration->destroy_notify = destroy_notify; + + return registration; +} + +static ToolInitializerCallbackRegistration * +tool_initializer_callback_registration_ref( + ToolInitializerCallbackRegistration *registration +) +{ + if (registration == NULL) + { + return NULL; + } + + g_atomic_ref_count_inc( + ®istration->reference_count + ); + + return registration; +} + +static void tool_initializer_callback_registration_unref( + ToolInitializerCallbackRegistration *registration +) +{ + if (registration == NULL) + { + return; + } + + if (!g_atomic_ref_count_dec( + ®istration->reference_count + )) + { + return; + } + + if (registration->destroy_notify != NULL) + { + registration->destroy_notify( + registration->user_data + ); + } + + g_free( + registration + ); +} + static gboolean tool_initializer_ensure_catalog_registered( ToolInitializer *tool_initializer, GError **error @@ -69,6 +156,8 @@ static void tool_initializer_unref_internal( BackgroundTask *task = NULL; ToolRegistry *tool_registry = NULL; + ToolInitializerCallbackRegistration *callback_registration = NULL; + if (tool_initializer == NULL) { return; @@ -85,11 +174,18 @@ static void tool_initializer_unref_internal( &tool_initializer->mutex ); - task = tool_initializer->task; - tool_registry = tool_initializer->tool_registry; + task = + tool_initializer->task; + + tool_registry = + tool_initializer->tool_registry; + + callback_registration = + tool_initializer->callback_registration; tool_initializer->task = NULL; tool_initializer->tool_registry = NULL; + tool_initializer->callback_registration = NULL; tool_initializer->task_manager = NULL; tool_initializer->is_running = FALSE; @@ -101,6 +197,10 @@ static void tool_initializer_unref_internal( task ); + tool_initializer_callback_registration_unref( + callback_registration + ); + tool_registry_free( tool_registry ); @@ -771,8 +871,16 @@ static void tool_initializer_bootstrap_completed( ToolInitializer *tool_initializer = user_data; - const ToolInitializationSummary *summary = - NULL; + ToolInitializerCallbackRegistration *callback_registration = NULL; + + BackgroundTaskState final_state; + + const ToolInitializationSummary *task_summary = NULL; + const ToolInitializationSummary *callback_summary = NULL; + + ToolInitializationSummary summary_copy = {0}; + + GError *callback_error = NULL; if (task == NULL || tool_initializer == NULL) @@ -780,33 +888,89 @@ static void tool_initializer_bootstrap_completed( return; } - if (background_task_get_state( - task - ) == BACKGROUND_TASK_STATE_COMPLETED) - { - summary = background_task_get_result( + final_state = + background_task_get_state( task ); + + if (final_state == BACKGROUND_TASK_STATE_COMPLETED) + { + task_summary = + background_task_get_result( + task + ); + + if (task_summary != NULL) + { + summary_copy = + *task_summary; + + callback_summary = + &summary_copy; + } + } + else if (final_state == BACKGROUND_TASK_STATE_FAILED) + { + callback_error = + background_task_dup_error( + task + ); } g_mutex_lock( &tool_initializer->mutex ); - if (summary != NULL) + if (callback_summary != NULL) { tool_initializer->last_summary = - *summary; + summary_copy; tool_initializer->has_last_summary = TRUE; } + else + { + tool_initializer->last_summary = + (ToolInitializationSummary) {0}; + + tool_initializer->has_last_summary = + FALSE; + } tool_initializer->is_running = FALSE; + callback_registration = + tool_initializer_callback_registration_ref( + tool_initializer->callback_registration + ); + g_mutex_unlock( &tool_initializer->mutex ); + + /* + * Le callback utilisateur est toujours appelé hors du mutex. + * Il peut donc consulter l’initialiseur ou remplacer son callback. + */ + if (callback_registration != NULL) + { + callback_registration->callback( + tool_initializer, + final_state, + callback_summary, + callback_error, + callback_registration->user_data + ); + } + + tool_initializer_callback_registration_unref( + callback_registration + ); + + g_clear_error( + &callback_error + ); } GQuark tool_initializer_error_quark(void) @@ -877,6 +1041,17 @@ void tool_initializer_free( return; } + /* + * Le propriétaire public abandonne l’initialiseur. + * Aucun callback ne doit désormais utiliser son user_data. + */ + tool_initializer_set_completed_callback( + tool_initializer, + NULL, + NULL, + NULL + ); + task = tool_initializer_get_task( tool_initializer ); @@ -1031,16 +1206,16 @@ gboolean tool_initializer_start( tool_initializer->is_running = FALSE; - g_mutex_unlock( - &tool_initializer->mutex - ); - tool_initializer->last_summary = previous_summary; tool_initializer->has_last_summary = previous_has_last_summary; + g_mutex_unlock( + &tool_initializer->mutex + ); + tool_initializer_set_wrapped_error( error, TOOL_INITIALIZER_ERROR_TASK_REGISTRATION, @@ -1175,6 +1350,77 @@ gboolean tool_initializer_start( return TRUE; } +void tool_initializer_set_completed_callback( + ToolInitializer *tool_initializer, + ToolInitializerCompletedCallback callback, + gpointer user_data, + GDestroyNotify destroy_notify +) +{ + ToolInitializerCallbackRegistration *new_registration = NULL; + ToolInitializerCallbackRegistration *previous_registration = NULL; + + if (callback != NULL) + { + new_registration = + tool_initializer_callback_registration_new( + callback, + user_data, + destroy_notify + ); + } + + if (tool_initializer == NULL) + { + tool_initializer_callback_registration_unref( + new_registration + ); + + /* + * Aucun enregistrement n’a pu prendre possession des données. + */ + if (new_registration == NULL && + destroy_notify != NULL) + { + destroy_notify( + user_data + ); + } + + return; + } + + g_mutex_lock( + &tool_initializer->mutex + ); + + previous_registration = + tool_initializer->callback_registration; + + tool_initializer->callback_registration = + new_registration; + + g_mutex_unlock( + &tool_initializer->mutex + ); + + tool_initializer_callback_registration_unref( + previous_registration + ); + + /* + * callback == NULL signifie suppression. + * Les éventuelles nouvelles données ne sont pas conservées. + */ + if (callback == NULL && + destroy_notify != NULL) + { + destroy_notify( + user_data + ); + } +} + gboolean tool_initializer_cancel( ToolInitializer *tool_initializer, GError **error diff --git a/tests/test_tool_initializer b/tests/test_tool_initializer index 8ac05fd..b9ce449 100755 Binary files a/tests/test_tool_initializer and b/tests/test_tool_initializer differ diff --git a/tests/test_tool_initializer.c b/tests/test_tool_initializer.c index b8fd0ea..fc26583 100644 --- a/tests/test_tool_initializer.c +++ b/tests/test_tool_initializer.c @@ -14,6 +14,163 @@ static char *test_tool_initializer_directory = NULL; +typedef struct +{ + guint callback_count; + guint *observed_callback_count; + guint *destroy_count; + + BackgroundTaskState final_state; + + gboolean received_summary; + gboolean received_error; + gboolean initializer_was_running; + + ToolInitializationSummary summary; + + gsize registry_count; + + char *curl_version; + GError *error; + + GThread *callback_thread; +} TestToolInitializerCallbackData; + +static void test_tool_initializer_callback_data_free( + gpointer user_data +) +{ + TestToolInitializerCallbackData *callback_data = + user_data; + + if (callback_data == NULL) + { + return; + } + + if (callback_data->destroy_count != NULL) + { + (*callback_data->destroy_count)++; + } + + g_clear_pointer( + &callback_data->curl_version, + g_free + ); + + g_clear_error( + &callback_data->error + ); + + g_free( + callback_data + ); +} + +static void test_tool_initializer_completed_callback( + ToolInitializer *tool_initializer, + BackgroundTaskState final_state, + const ToolInitializationSummary *summary, + const GError *error, + gpointer user_data +) +{ + TestToolInitializerCallbackData *callback_data = + user_data; + + ToolRegistry *tool_registry = NULL; + const ToolInfo *curl_info = NULL; + + g_assert_nonnull( + tool_initializer + ); + + g_assert_nonnull( + callback_data + ); + + callback_data->callback_count++; + + if (callback_data->observed_callback_count != NULL) + { + (*callback_data->observed_callback_count)++; + } + + callback_data->final_state = + final_state; + + callback_data->initializer_was_running = + tool_initializer_is_running( + tool_initializer + ); + + callback_data->callback_thread = + g_thread_self(); + + callback_data->received_summary = + summary != NULL; + + if (summary != NULL) + { + callback_data->summary = + *summary; + } + else + { + callback_data->summary = + (ToolInitializationSummary) {0}; + } + + callback_data->received_error = + error != NULL; + + g_clear_error( + &callback_data->error + ); + + if (error != NULL) + { + callback_data->error = + g_error_copy( + error + ); + } + + tool_registry = + tool_initializer_get_registry( + tool_initializer + ); + + if (tool_registry != NULL) + { + callback_data->registry_count = + tool_registry_get_count( + tool_registry + ); + + curl_info = + tool_registry_find( + tool_registry, + "http.curl" + ); + } + + g_clear_pointer( + &callback_data->curl_version, + g_free + ); + + if (curl_info != NULL) + { + callback_data->curl_version = + g_strdup( + tool_info_get_detected_version( + curl_info + ) + ); + } +} + static gboolean test_tool_initializer_wait_for_progress( ToolInitializer *tool_initializer, double minimum_progress @@ -253,6 +410,69 @@ static gboolean test_tool_initializer_wait_until_stopped( ); } +static gboolean test_tool_initializer_wait_for_task_terminal( + BackgroundTask *task +) +{ + gboolean timed_out = FALSE; + gboolean is_terminal = FALSE; + guint timeout_source_id = 0; + + g_assert_nonnull( + task + ); + + timeout_source_id = + g_timeout_add_seconds( + TEST_TOOL_INITIALIZER_TIMEOUT_SECONDS, + test_tool_initializer_timeout, + &timed_out + ); + + g_assert_cmpuint( + timeout_source_id, + !=, + 0 + ); + + while (!timed_out) + { + BackgroundTaskState task_state; + + task_state = + background_task_get_state( + task + ); + + is_terminal = + task_state == BACKGROUND_TASK_STATE_COMPLETED || + task_state == BACKGROUND_TASK_STATE_FAILED || + task_state == BACKGROUND_TASK_STATE_CANCELLED; + + if (is_terminal) + { + break; + } + + g_main_context_iteration( + NULL, + TRUE + ); + } + + if (!timed_out) + { + g_assert_true( + g_source_remove( + timeout_source_id + ) + ); + } + + return !timed_out && + is_terminal; +} + static void test_tool_initializer_new_invalid_arguments(void) { ToolInitializer *tool_initializer = NULL; @@ -1528,6 +1748,1079 @@ static void test_tool_initializer_restart_after_cancellation(void) ); } +static void test_tool_initializer_callback_success(void) +{ + TaskManager *task_manager = NULL; + ToolInitializer *tool_initializer = NULL; + + TestToolInitializerCallbackData *callback_data = NULL; + + guint destroy_count = 0; + + GError *error = NULL; + + test_tool_initializer_reset_fake_tools(); + + task_manager = + task_manager_new(); + + tool_initializer = + tool_initializer_new( + task_manager, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + tool_initializer + ); + + callback_data = + g_new0( + TestToolInitializerCallbackData, + 1 + ); + + callback_data->destroy_count = + &destroy_count; + + tool_initializer_set_completed_callback( + tool_initializer, + test_tool_initializer_completed_callback, + callback_data, + test_tool_initializer_callback_data_free + ); + + g_assert_true( + tool_initializer_start( + tool_initializer, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_true( + test_tool_initializer_wait_until_stopped( + tool_initializer + ) + ); + + g_assert_cmpuint( + callback_data->callback_count, + ==, + 1 + ); + + g_assert_cmpint( + callback_data->final_state, + ==, + BACKGROUND_TASK_STATE_COMPLETED + ); + + g_assert_false( + callback_data->initializer_was_running + ); + + g_assert_true( + callback_data->received_summary + ); + + g_assert_false( + callback_data->received_error + ); + + g_assert_null( + callback_data->error + ); + + tool_initializer_free( + tool_initializer + ); + + g_assert_cmpuint( + destroy_count, + ==, + 1 + ); + + task_manager_free( + task_manager + ); +} + +static void test_tool_initializer_callback_summary_and_registry(void) +{ + TaskManager *task_manager = NULL; + ToolInitializer *tool_initializer = NULL; + + TestToolInitializerCallbackData *callback_data = NULL; + + guint destroy_count = 0; + + GError *error = NULL; + + test_tool_initializer_reset_fake_tools(); + + test_tool_initializer_write_fake_tool( + "curl", + "#!/bin/sh\n" + "printf 'Fake curl 5.4.3\\n'\n" + ); + + task_manager = + task_manager_new(); + + tool_initializer = + tool_initializer_new( + task_manager, + &error + ); + + g_assert_no_error( + error + ); + + callback_data = + g_new0( + TestToolInitializerCallbackData, + 1 + ); + + callback_data->destroy_count = + &destroy_count; + + tool_initializer_set_completed_callback( + tool_initializer, + test_tool_initializer_completed_callback, + callback_data, + test_tool_initializer_callback_data_free + ); + + g_assert_true( + tool_initializer_start( + tool_initializer, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_true( + test_tool_initializer_wait_until_stopped( + tool_initializer + ) + ); + + g_assert_cmpuint( + callback_data->callback_count, + ==, + 1 + ); + + g_assert_true( + callback_data->received_summary + ); + + g_assert_cmpuint( + callback_data->summary.total_count, + ==, + tool_catalog_get_count() + ); + + g_assert_cmpuint( + callback_data->summary.available_count, + ==, + 1 + ); + + g_assert_cmpuint( + callback_data->summary.missing_count, + ==, + tool_catalog_get_count() - 1 + ); + + g_assert_cmpuint( + callback_data->summary.version_detected_count, + ==, + 1 + ); + + g_assert_cmpuint( + callback_data->summary.version_failed_count, + ==, + 0 + ); + + /* + * Le catalogue et les versions doivent déjà être enregistrés + * lorsque le callback est exécuté. + */ + g_assert_cmpuint( + callback_data->registry_count, + ==, + tool_catalog_get_count() + ); + + g_assert_cmpstr( + callback_data->curl_version, + ==, + "Fake curl 5.4.3" + ); + + tool_initializer_free( + tool_initializer + ); + + g_assert_cmpuint( + destroy_count, + ==, + 1 + ); + + task_manager_free( + task_manager + ); +} + +static void test_tool_initializer_callback_replacement(void) +{ + TaskManager *task_manager = NULL; + ToolInitializer *tool_initializer = NULL; + + TestToolInitializerCallbackData *first_callback_data = NULL; + TestToolInitializerCallbackData *second_callback_data = NULL; + + guint first_callback_count = 0; + guint second_callback_count = 0; + + guint first_destroy_count = 0; + guint second_destroy_count = 0; + + GError *error = NULL; + + test_tool_initializer_reset_fake_tools(); + + task_manager = + task_manager_new(); + + tool_initializer = + tool_initializer_new( + task_manager, + &error + ); + + g_assert_no_error( + error + ); + + first_callback_data = + g_new0( + TestToolInitializerCallbackData, + 1 + ); + + first_callback_data->observed_callback_count = + &first_callback_count; + + first_callback_data->destroy_count = + &first_destroy_count; + + second_callback_data = + g_new0( + TestToolInitializerCallbackData, + 1 + ); + + second_callback_data->observed_callback_count = + &second_callback_count; + + second_callback_data->destroy_count = + &second_destroy_count; + + tool_initializer_set_completed_callback( + tool_initializer, + test_tool_initializer_completed_callback, + first_callback_data, + test_tool_initializer_callback_data_free + ); + + tool_initializer_set_completed_callback( + tool_initializer, + test_tool_initializer_completed_callback, + second_callback_data, + test_tool_initializer_callback_data_free + ); + + /* + * Le premier enregistrement a été remplacé et doit déjà être + * détruit. + */ + g_assert_cmpuint( + first_destroy_count, + ==, + 1 + ); + + g_assert_cmpuint( + second_destroy_count, + ==, + 0 + ); + + g_assert_cmpuint( + first_callback_count, + ==, + 0 + ); + + g_assert_true( + tool_initializer_start( + tool_initializer, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_true( + test_tool_initializer_wait_until_stopped( + tool_initializer + ) + ); + + g_assert_cmpuint( + first_callback_count, + ==, + 0 + ); + + g_assert_cmpuint( + second_callback_count, + ==, + 1 + ); + + g_assert_cmpuint( + second_callback_data->callback_count, + ==, + 1 + ); + + tool_initializer_free( + tool_initializer + ); + + g_assert_cmpuint( + first_destroy_count, + ==, + 1 + ); + + g_assert_cmpuint( + second_destroy_count, + ==, + 1 + ); + + task_manager_free( + task_manager + ); +} + +static void test_tool_initializer_callback_destroy_notify(void) +{ + TaskManager *task_manager = NULL; + ToolInitializer *tool_initializer = NULL; + + TestToolInitializerCallbackData *callback_data = NULL; + + guint destroy_count = 0; + + GError *error = NULL; + + task_manager = + task_manager_new(); + + tool_initializer = + tool_initializer_new( + task_manager, + &error + ); + + g_assert_no_error( + error + ); + + callback_data = + g_new0( + TestToolInitializerCallbackData, + 1 + ); + + callback_data->destroy_count = + &destroy_count; + + tool_initializer_set_completed_callback( + tool_initializer, + test_tool_initializer_completed_callback, + callback_data, + test_tool_initializer_callback_data_free + ); + + g_assert_cmpuint( + destroy_count, + ==, + 0 + ); + + tool_initializer_set_completed_callback( + tool_initializer, + NULL, + NULL, + NULL + ); + + g_assert_cmpuint( + destroy_count, + ==, + 1 + ); + + /* + * La destruction de l’initialiseur ne doit pas appeler une seconde + * fois le destroy notify de l’ancien enregistrement. + */ + tool_initializer_free( + tool_initializer + ); + + g_assert_cmpuint( + destroy_count, + ==, + 1 + ); + + task_manager_free( + task_manager + ); +} + +static void test_tool_initializer_callback_cancellation(void) +{ + TaskManager *task_manager = NULL; + ToolInitializer *tool_initializer = NULL; + + TestToolInitializerCallbackData *callback_data = NULL; + + guint destroy_count = 0; + + GError *error = NULL; + + test_tool_initializer_reset_fake_tools(); + + test_tool_initializer_write_fake_tool( + "dig", + "#!/bin/sh\n" + "exec /bin/sleep 30\n" + ); + + task_manager = + task_manager_new(); + + tool_initializer = + tool_initializer_new( + task_manager, + &error + ); + + g_assert_no_error( + error + ); + + callback_data = + g_new0( + TestToolInitializerCallbackData, + 1 + ); + + callback_data->destroy_count = + &destroy_count; + + tool_initializer_set_completed_callback( + tool_initializer, + test_tool_initializer_completed_callback, + callback_data, + test_tool_initializer_callback_data_free + ); + + g_assert_true( + tool_initializer_start( + tool_initializer, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_true( + test_tool_initializer_wait_for_progress( + tool_initializer, + 0.15 + ) + ); + + g_assert_true( + tool_initializer_cancel( + tool_initializer, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_true( + test_tool_initializer_wait_until_stopped( + tool_initializer + ) + ); + + g_assert_cmpuint( + callback_data->callback_count, + ==, + 1 + ); + + g_assert_cmpint( + callback_data->final_state, + ==, + BACKGROUND_TASK_STATE_CANCELLED + ); + + g_assert_false( + callback_data->received_summary + ); + + g_assert_false( + callback_data->received_error + ); + + g_assert_false( + callback_data->initializer_was_running + ); + + tool_initializer_free( + tool_initializer + ); + + g_assert_cmpuint( + destroy_count, + ==, + 1 + ); + + task_manager_free( + task_manager + ); +} + +static void test_tool_initializer_callback_failure(void) +{ + TaskManager *task_manager = NULL; + ToolInitializer *tool_initializer = NULL; + ToolRegistry *tool_registry = NULL; + + TestToolInitializerCallbackData *callback_data = NULL; + + guint destroy_count = 0; + + GError *error = NULL; + + test_tool_initializer_reset_fake_tools(); + + task_manager = + task_manager_new(); + + tool_initializer = + tool_initializer_new( + task_manager, + &error + ); + + g_assert_no_error( + error + ); + + tool_registry = + tool_initializer_get_registry( + tool_initializer + ); + + g_assert_nonnull( + tool_registry + ); + + /* + * Le registre ne correspondra plus exactement au catalogue. + * Le worker doit donc échouer pendant sa vérification initiale. + */ + g_assert_true( + tool_registry_register( + tool_registry, + "test.extra", + "Outil supplémentaire", + "extra", + TOOL_REQUIREMENT_OPTIONAL, + &error + ) + ); + + g_assert_no_error( + error + ); + + callback_data = + g_new0( + TestToolInitializerCallbackData, + 1 + ); + + callback_data->destroy_count = + &destroy_count; + + tool_initializer_set_completed_callback( + tool_initializer, + test_tool_initializer_completed_callback, + callback_data, + test_tool_initializer_callback_data_free + ); + + g_assert_true( + tool_initializer_start( + tool_initializer, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_true( + test_tool_initializer_wait_until_stopped( + tool_initializer + ) + ); + + g_assert_cmpuint( + callback_data->callback_count, + ==, + 1 + ); + + g_assert_cmpint( + callback_data->final_state, + ==, + BACKGROUND_TASK_STATE_FAILED + ); + + g_assert_false( + callback_data->received_summary + ); + + g_assert_true( + callback_data->received_error + ); + + g_assert_nonnull( + callback_data->error + ); + + g_assert_error( + callback_data->error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_CATALOG_REGISTRATION + ); + + g_assert_false( + callback_data->initializer_was_running + ); + + tool_initializer_free( + tool_initializer + ); + + g_assert_cmpuint( + destroy_count, + ==, + 1 + ); + + task_manager_free( + task_manager + ); +} + +static void test_tool_initializer_callback_main_context(void) +{ + TaskManager *task_manager = NULL; + ToolInitializer *tool_initializer = NULL; + + TestToolInitializerCallbackData *callback_data = NULL; + + GThread *main_thread = NULL; + + guint destroy_count = 0; + + GError *error = NULL; + + test_tool_initializer_reset_fake_tools(); + + main_thread = + g_thread_self(); + + task_manager = + task_manager_new(); + + tool_initializer = + tool_initializer_new( + task_manager, + &error + ); + + g_assert_no_error( + error + ); + + callback_data = + g_new0( + TestToolInitializerCallbackData, + 1 + ); + + callback_data->destroy_count = + &destroy_count; + + tool_initializer_set_completed_callback( + tool_initializer, + test_tool_initializer_completed_callback, + callback_data, + test_tool_initializer_callback_data_free + ); + + g_assert_true( + tool_initializer_start( + tool_initializer, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_true( + test_tool_initializer_wait_until_stopped( + tool_initializer + ) + ); + + g_assert_cmpuint( + callback_data->callback_count, + ==, + 1 + ); + + g_assert_true( + callback_data->callback_thread == + main_thread + ); + + tool_initializer_free( + tool_initializer + ); + + g_assert_cmpuint( + destroy_count, + ==, + 1 + ); + + task_manager_free( + task_manager + ); +} + +static void test_tool_initializer_callback_restart(void) +{ + TaskManager *task_manager = NULL; + ToolInitializer *tool_initializer = NULL; + + TestToolInitializerCallbackData *callback_data = NULL; + + guint destroy_count = 0; + + GError *error = NULL; + + test_tool_initializer_reset_fake_tools(); + + task_manager = + task_manager_new(); + + tool_initializer = + tool_initializer_new( + task_manager, + &error + ); + + g_assert_no_error( + error + ); + + callback_data = + g_new0( + TestToolInitializerCallbackData, + 1 + ); + + callback_data->destroy_count = + &destroy_count; + + tool_initializer_set_completed_callback( + tool_initializer, + test_tool_initializer_completed_callback, + callback_data, + test_tool_initializer_callback_data_free + ); + + g_assert_true( + tool_initializer_start( + tool_initializer, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_true( + test_tool_initializer_wait_until_stopped( + tool_initializer + ) + ); + + g_assert_cmpuint( + callback_data->callback_count, + ==, + 1 + ); + + g_assert_true( + tool_initializer_start( + tool_initializer, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_true( + test_tool_initializer_wait_until_stopped( + tool_initializer + ) + ); + + g_assert_cmpuint( + callback_data->callback_count, + ==, + 2 + ); + + g_assert_cmpint( + callback_data->final_state, + ==, + BACKGROUND_TASK_STATE_COMPLETED + ); + + g_assert_cmpuint( + task_manager_get_count( + task_manager + ), + ==, + 2 + ); + + tool_initializer_free( + tool_initializer + ); + + g_assert_cmpuint( + destroy_count, + ==, + 1 + ); + + task_manager_free( + task_manager + ); +} + +static void test_tool_initializer_free_while_running(void) +{ + TaskManager *task_manager = NULL; + ToolInitializer *tool_initializer = NULL; + BackgroundTask *task = NULL; + + TestToolInitializerCallbackData *callback_data = NULL; + + guint callback_count = 0; + guint destroy_count = 0; + + GError *error = NULL; + + test_tool_initializer_reset_fake_tools(); + + test_tool_initializer_write_fake_tool( + "dig", + "#!/bin/sh\n" + "exec /bin/sleep 30\n" + ); + + task_manager = + task_manager_new(); + + tool_initializer = + tool_initializer_new( + task_manager, + &error + ); + + g_assert_no_error( + error + ); + + callback_data = + g_new0( + TestToolInitializerCallbackData, + 1 + ); + + callback_data->observed_callback_count = + &callback_count; + + callback_data->destroy_count = + &destroy_count; + + tool_initializer_set_completed_callback( + tool_initializer, + test_tool_initializer_completed_callback, + callback_data, + test_tool_initializer_callback_data_free + ); + + g_assert_true( + tool_initializer_start( + tool_initializer, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_true( + test_tool_initializer_wait_for_progress( + tool_initializer, + 0.15 + ) + ); + + task = + tool_initializer_get_task( + tool_initializer + ); + + g_assert_nonnull( + task + ); + + /* + * La libération publique supprime le callback et demande + * l’annulation, sans attendre la fin du worker. + */ + tool_initializer_free( + tool_initializer + ); + + tool_initializer = NULL; + + g_assert_cmpuint( + destroy_count, + ==, + 1 + ); + + g_assert_cmpuint( + callback_count, + ==, + 0 + ); + + g_assert_true( + test_tool_initializer_wait_for_task_terminal( + task + ) + ); + + g_assert_cmpint( + background_task_get_state( + task + ), + ==, + BACKGROUND_TASK_STATE_CANCELLED + ); + + /* + * Aucun callback utilisateur ne doit être déclenché après free(). + */ + g_assert_cmpuint( + callback_count, + ==, + 0 + ); + + g_assert_cmpuint( + destroy_count, + ==, + 1 + ); + + background_task_unref( + task + ); + + task_manager_free( + task_manager + ); +} + int main( int argc, char **argv @@ -1625,6 +2918,50 @@ int main( test_tool_initializer_restart_after_cancellation ); + g_test_add_func( + "/tool_initializer/callback_success", + test_tool_initializer_callback_success + ); + + g_test_add_func( + "/tool_initializer/callback_summary_and_registry", + test_tool_initializer_callback_summary_and_registry + ); + + g_test_add_func( + "/tool_initializer/callback_replacement", + test_tool_initializer_callback_replacement + ); + + g_test_add_func( + "/tool_initializer/callback_destroy_notify", + test_tool_initializer_callback_destroy_notify + ); + + g_test_add_func( + "/tool_initializer/callback_cancellation", + test_tool_initializer_callback_cancellation + ); + + g_test_add_func( + "/tool_initializer/callback_failure", + test_tool_initializer_callback_failure + ); + + g_test_add_func( + "/tool_initializer/callback_main_context", + test_tool_initializer_callback_main_context + ); + + g_test_add_func( + "/tool_initializer/callback_restart", + test_tool_initializer_callback_restart + ); + + g_test_add_func( + "/tool_initializer/free_while_running", + test_tool_initializer_free_while_running + ); { int test_result = 0;