diff --git a/Makefile b/Makefile index 79e42a4..6a5adcf 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,7 @@ TEST_TOOL_REGISTRY := tests/test_tool_registry TEST_TOOL_PROCESS := tests/test_tool_process TEST_TOOL_TASK := tests/test_tool_task TEST_TOOL_CATALOG := tests/test_tool_catalog +TEST_TOOL_INITIALIZER := tests/test_tool_initializer all: $(TARGET) @@ -179,6 +180,16 @@ $(TEST_TOOL_CATALOG): \ src/core/tool_process.c $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) +$(TEST_TOOL_INITIALIZER): \ + tests/test_tool_initializer.c \ + src/core/tool_initializer.c \ + src/core/task_manager.c \ + src/core/background_task.c \ + src/core/tool_registry.c \ + src/core/tool_catalog.c \ + src/core/tool_process.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) + test: \ $(TEST_NODE) \ $(TEST_TREE_MODEL) \ @@ -196,7 +207,8 @@ test: \ $(TEST_TOOL_REGISTRY) \ $(TEST_TOOL_PROCESS) \ $(TEST_TOOL_TASK) \ - $(TEST_TOOL_CATALOG) + $(TEST_TOOL_CATALOG) \ + $(TEST_TOOL_INITIALIZER) @echo "Exécution des tests..." @./$(TEST_NODE) @./$(TEST_TREE_MODEL) @@ -215,6 +227,7 @@ test: \ @$(TEST_TOOL_PROCESS) @$(TEST_TOOL_TASK) @$(TEST_TOOL_CATALOG) + @$(TEST_TOOL_INITIALIZER) @echo "Tous les tests sont valides." %.o: %.c @@ -241,6 +254,7 @@ clean: $(TEST_TOOL_REGISTRY) \ $(TEST_TOOL_PROCESS) \ $(TEST_TOOL_TASK) \ - $(TEST_TOOL_CATALOG) + $(TEST_TOOL_CATALOG) \ + $(TEST_TOOL_INITIALIZER) .PHONY: clean run test diff --git a/include/core/tool_initializer.h b/include/core/tool_initializer.h new file mode 100644 index 0000000..411d7a2 --- /dev/null +++ b/include/core/tool_initializer.h @@ -0,0 +1,237 @@ +/****************************************************************************** + * @file tool_initializer.h + * @brief Initialisation asynchrone des outils externes. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_TOOL_INITIALIZER_H +#define LABFY_INVESTIGATION_TOOL_INITIALIZER_H + +#include "core/background_task.h" +#include "core/task_manager.h" +#include "core/tool_registry.h" + +#include + +G_BEGIN_DECLS + +/** + * @brief Représentation opaque de l’initialiseur d’outils. + */ +typedef struct ToolInitializer ToolInitializer; + +/** + * @brief Résumé d’une initialisation terminée avec succès. + */ +typedef struct +{ + gsize total_count; + gsize available_count; + gsize missing_count; + gsize version_detected_count; + gsize version_failed_count; +} ToolInitializationSummary; + +/** + * @brief Codes d’erreur de ToolInitializer. + */ +typedef enum +{ + /** + * Un argument transmis au module est invalide. + */ + TOOL_INITIALIZER_ERROR_INVALID_ARGUMENT, + + /** + * Une initialisation est déjà en cours. + */ + TOOL_INITIALIZER_ERROR_ALREADY_RUNNING, + + /** + * Aucune initialisation n’est actuellement en cours. + */ + TOOL_INITIALIZER_ERROR_NOT_RUNNING, + + /** + * La tâche d’arrière-plan n’a pas pu être créée. + */ + TOOL_INITIALIZER_ERROR_TASK_CREATION, + + /** + * La tâche n’a pas pu être ajoutée au TaskManager. + */ + TOOL_INITIALIZER_ERROR_TASK_REGISTRATION, + + /** + * La tâche n’a pas pu être démarrée. + */ + TOOL_INITIALIZER_ERROR_TASK_START, + + /** + * Le catalogue n’a pas pu être enregistré. + */ + TOOL_INITIALIZER_ERROR_CATALOG_REGISTRATION, + + /** + * Le registre n’a pas pu rechercher les exécutables. + */ + TOOL_INITIALIZER_ERROR_REGISTRY_REFRESH, + + /** + * L’état interne de l’initialiseur est incohérent. + */ + TOOL_INITIALIZER_ERROR_INTERNAL_STATE +} ToolInitializerError; + +/** + * @brief Domaine d’erreur du module. + */ +#define TOOL_INITIALIZER_ERROR \ + tool_initializer_error_quark() + +/** + * @brief Retourne le domaine d’erreur de ToolInitializer. + * + * @return Quark GLib du domaine d’erreur. + */ +GQuark tool_initializer_error_quark(void); + +/** + * @brief Crée un initialiseur d’outils externes. + * + * L’initialiseur : + * + * - conserve une référence empruntée vers task_manager ; + * - crée et possède son propre ToolRegistry ; + * - ne prend pas possession de task_manager. + * + * task_manager doit rester valide pendant les appels publics utilisant + * l’initialiseur. + * + * @param task_manager Gestionnaire recevant la tâche d’initialisation. + * @param error Emplacement facultatif recevant une erreur. + * + * @return Nouvel initialiseur, ou NULL en cas d’échec. + */ +ToolInitializer *tool_initializer_new( + TaskManager *task_manager, + GError **error +); + +/** + * @brief Libère la référence détenue par l’appelant. + * + * Si une initialisation est encore active, son annulation est demandée. + * La fonction ne bloque pas en attendant la fin du worker. + * + * Une référence interne conserve les ressources nécessaires jusqu’à la + * finalisation réelle de la tâche. + * + * La fonction accepte tool_initializer == NULL. + * + * @param tool_initializer Initialiseur à libérer. + */ +void tool_initializer_free( + ToolInitializer *tool_initializer +); + +/** + * @brief Retourne le registre appartenant à l’initialiseur. + * + * Le pointeur retourné est emprunté et ne doit jamais être libéré par + * l’appelant. + * + * Il ne doit plus être utilisé après tool_initializer_free(). + * + * @param tool_initializer Initialiseur consulté. + * + * @return Registre emprunté, ou NULL. + */ +ToolRegistry *tool_initializer_get_registry( + ToolInitializer *tool_initializer +); + +/** + * @brief Démarre l’initialisation en arrière-plan. + * + * La fonction : + * + * - crée une nouvelle BackgroundTask ; + * - l’ajoute au TaskManager ; + * - lance son worker ; + * - refuse un second démarrage tant qu’une tâche est active. + * + * Une nouvelle exécution peut être lancée après la fin ou l’annulation + * de la précédente. + * + * @param tool_initializer Initialiseur concerné. + * @param error Emplacement facultatif recevant une erreur. + * + * @return TRUE si la tâche a été lancée, sinon FALSE. + */ +gboolean tool_initializer_start( + ToolInitializer *tool_initializer, + GError **error +); + +/** + * @brief Demande l’annulation de l’initialisation active. + * + * L’annulation est coopérative. + * + * @param tool_initializer Initialiseur concerné. + * @param error Emplacement facultatif recevant une erreur. + * + * @return TRUE si une annulation a été demandée, sinon FALSE. + */ +gboolean tool_initializer_cancel( + ToolInitializer *tool_initializer, + GError **error +); + +/** + * @brief Indique si une initialisation est en cours. + * + * @param tool_initializer Initialiseur consulté. + * + * @return TRUE si une tâche est en attente ou en cours, sinon FALSE. + */ +gboolean tool_initializer_is_running( + const ToolInitializer *tool_initializer +); + +/** + * @brief Retourne une référence vers la tâche actuelle ou la dernière tâche. + * + * L’appelant devient propriétaire d’une référence et doit la libérer avec : + * + * @code + * background_task_unref(task); + * @endcode + * + * @param tool_initializer Initialiseur consulté. + * + * @return Nouvelle référence vers la tâche, ou NULL. + */ +BackgroundTask *tool_initializer_get_task( + const ToolInitializer *tool_initializer +); + +/** + * @brief Copie le résumé de la dernière initialisation réussie. + * + * Le résumé n’est disponible que lorsque la dernière tâche s’est terminée + * dans l’état BACKGROUND_TASK_STATE_COMPLETED. + * + * @param tool_initializer Initialiseur consulté. + * @param out_summary Structure recevant une copie du résumé. + * + * @return TRUE si un résumé est disponible, sinon FALSE. + */ +gboolean tool_initializer_get_last_summary( + const ToolInitializer *tool_initializer, + ToolInitializationSummary *out_summary +); + +G_END_DECLS + +#endif diff --git a/labfy-investigation b/labfy-investigation index 915aaec..843eaa8 100755 Binary files a/labfy-investigation and b/labfy-investigation differ diff --git a/src/core/tool_initializer.c b/src/core/tool_initializer.c new file mode 100644 index 0000000..0c91b60 --- /dev/null +++ b/src/core/tool_initializer.c @@ -0,0 +1,1338 @@ +/****************************************************************************** + * @file tool_initializer.c + * @brief Initialisation asynchrone des outils externes. + ******************************************************************************/ + +#include "core/tool_initializer.h" +#include "core/tool_catalog.h" + +#include +#include + +/** + * @struct ToolInitializer + * @brief État interne de l’initialiseur d’outils. + */ +struct ToolInitializer +{ + gatomicrefcount reference_count; + GMutex mutex; + + TaskManager *task_manager; + ToolRegistry *tool_registry; + + BackgroundTask *task; + + ToolInitializationSummary last_summary; + + gboolean has_last_summary; + gboolean is_running; +}; + +static gboolean tool_initializer_ensure_catalog_registered( + ToolInitializer *tool_initializer, + GError **error +); + +/** + * @brief Ajoute une référence interne à l’initialiseur. + * + * @param tool_initializer Initialiseur concerné. + * + * @return L’initialiseur fourni, ou NULL. + */ +static ToolInitializer *tool_initializer_ref_internal( + ToolInitializer *tool_initializer +) +{ + if (tool_initializer == NULL) + { + return NULL; + } + + g_atomic_ref_count_inc( + &tool_initializer->reference_count + ); + + return tool_initializer; +} + +/** + * @brief Libère une référence interne à l’initialiseur. + * + * @param tool_initializer Initialiseur concerné. + */ +static void tool_initializer_unref_internal( + ToolInitializer *tool_initializer +) +{ + BackgroundTask *task = NULL; + ToolRegistry *tool_registry = NULL; + + if (tool_initializer == NULL) + { + return; + } + + if (!g_atomic_ref_count_dec( + &tool_initializer->reference_count + )) + { + return; + } + + g_mutex_lock( + &tool_initializer->mutex + ); + + task = tool_initializer->task; + tool_registry = tool_initializer->tool_registry; + + tool_initializer->task = NULL; + tool_initializer->tool_registry = NULL; + tool_initializer->task_manager = NULL; + tool_initializer->is_running = FALSE; + + g_mutex_unlock( + &tool_initializer->mutex + ); + + background_task_unref( + task + ); + + tool_registry_free( + tool_registry + ); + + g_mutex_clear( + &tool_initializer->mutex + ); + + g_free( + tool_initializer + ); +} + +/** + * @brief Adapte tool_initializer_unref_internal() à GDestroyNotify. + * + * @param user_data Initialiseur concerné. + */ +static void tool_initializer_unref_notify( + gpointer user_data +) +{ + tool_initializer_unref_internal( + user_data + ); +} + +/** + * @brief Produit une erreur ToolInitializer à partir d’une erreur interne. + * + * @param error Emplacement recevant l’erreur. + * @param error_code Code ToolInitializer. + * @param message Message principal. + * @param cause Erreur interne facultative. + */ +static void tool_initializer_set_wrapped_error( + GError **error, + ToolInitializerError error_code, + const char *message, + const GError *cause +) +{ + if (cause != NULL) + { + g_set_error( + error, + TOOL_INITIALIZER_ERROR, + error_code, + "%s : %s", + message, + cause->message + ); + + return; + } + + g_set_error_literal( + error, + TOOL_INITIALIZER_ERROR, + error_code, + message + ); +} + +/** + * @brief Enregistre, recherche et interroge les outils externes. + */ +static gboolean tool_initializer_bootstrap_worker( + BackgroundTask *task, + GCancellable *cancellable, + gpointer worker_data, + gpointer *result, + GError **error +) +{ + ToolInitializer *tool_initializer = + worker_data; + + ToolRegistry *tool_registry = NULL; + + ToolInitializationSummary *summary = NULL; + + gsize tool_count = 0; + gsize tool_index = 0; + + GError *local_error = NULL; + + if (task == NULL || + cancellable == NULL || + tool_initializer == NULL || + result == NULL) + { + g_set_error_literal( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_INTERNAL_STATE, + "Le contexte du worker d’initialisation est invalide." + ); + + return FALSE; + } + + tool_registry = + tool_initializer->tool_registry; + + if (tool_registry == NULL) + { + g_set_error_literal( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_INTERNAL_STATE, + "Le registre interne des outils est invalide." + ); + + return FALSE; + } + + if (g_cancellable_set_error_if_cancelled( + cancellable, + error + )) + { + return FALSE; + } + + background_task_report_progress( + task, + 0.05, + "Enregistrement du catalogue des outils" + ); + + if (!tool_initializer_ensure_catalog_registered( + tool_initializer, + error + )) + { + return FALSE; + } + + if (g_cancellable_set_error_if_cancelled( + cancellable, + error + )) + { + return FALSE; + } + + background_task_report_progress( + task, + 0.15, + "Recherche des exécutables installés" + ); + + if (!tool_registry_refresh( + tool_registry, + &local_error + )) + { + tool_initializer_set_wrapped_error( + error, + TOOL_INITIALIZER_ERROR_REGISTRY_REFRESH, + "La recherche des exécutables a échoué", + local_error + ); + + g_clear_error( + &local_error + ); + + return FALSE; + } + + summary = g_new0( + ToolInitializationSummary, + 1 + ); + + tool_count = + tool_catalog_get_count(); + + summary->total_count = + tool_count; + + for (tool_index = 0; + tool_index < tool_count; + tool_index++) + { + const ToolCatalogEntry *catalog_entry = NULL; + const ToolInfo *tool_info = NULL; + + const char *identifier = NULL; + const char *display_name = NULL; + + char *detected_version = NULL; + char *status_message = NULL; + + double progress = 0.0; + + if (g_cancellable_set_error_if_cancelled( + cancellable, + error + )) + { + g_free( + summary + ); + + return FALSE; + } + + catalog_entry = + tool_catalog_get_entry( + tool_index + ); + + if (catalog_entry != NULL) + { + identifier = + tool_catalog_entry_get_identifier( + catalog_entry + ); + + display_name = + tool_catalog_entry_get_display_name( + catalog_entry + ); + } + + if (catalog_entry == NULL || + identifier == NULL || + display_name == NULL) + { + g_set_error( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_INTERNAL_STATE, + "L’entrée de catalogue numéro %" + G_GSIZE_FORMAT + " est invalide.", + tool_index + ); + + g_free( + summary + ); + + return FALSE; + } + + status_message = + g_strdup_printf( + "Vérification de %s", + display_name + ); + + progress = + 0.15 + + ( + 0.80 * + ( + (double) tool_index / + (double) tool_count + ) + ); + + background_task_report_progress( + task, + progress, + status_message + ); + + g_free( + status_message + ); + + tool_info = + tool_registry_find( + tool_registry, + identifier + ); + + if (tool_info == NULL) + { + g_set_error( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_INTERNAL_STATE, + "L’outil '%s' est absent du registre.", + identifier + ); + + g_free( + summary + ); + + return FALSE; + } + + switch (tool_info_get_availability( + tool_info + )) + { + case TOOL_AVAILABILITY_MISSING: + summary->missing_count++; + + /* + * Une nouvelle détection peut rendre absent un outil + * précédemment disponible. Sa version devient alors + * obsolète et doit être effacée. + */ + if (!tool_registry_set_version( + tool_registry, + identifier, + NULL, + &local_error + )) + { + tool_initializer_set_wrapped_error( + error, + TOOL_INITIALIZER_ERROR_INTERNAL_STATE, + "La version obsolète n’a pas pu être effacée", + local_error + ); + + g_clear_error( + &local_error + ); + + g_free( + summary + ); + + return FALSE; + } + + break; + + case TOOL_AVAILABILITY_AVAILABLE: + summary->available_count++; + + if (tool_catalog_detect_version( + tool_registry, + identifier, + cancellable, + &detected_version, + &local_error + )) + { + if (!tool_registry_set_version( + tool_registry, + identifier, + detected_version, + &local_error + )) + { + tool_initializer_set_wrapped_error( + error, + TOOL_INITIALIZER_ERROR_INTERNAL_STATE, + "La version détectée n’a pas pu être enregistrée", + local_error + ); + + g_clear_error( + &local_error + ); + + g_free( + detected_version + ); + + g_free( + summary + ); + + return FALSE; + } + + summary->version_detected_count++; + } + else + { + /* + * Une annulation arrête toute la tâche. + * + * Une erreur propre à la commande de version reste + * locale à l’outil : l’exécutable demeure disponible. + */ + if (g_error_matches( + local_error, + G_IO_ERROR, + G_IO_ERROR_CANCELLED + )) + { + g_propagate_error( + error, + local_error + ); + + g_free( + detected_version + ); + + g_free( + summary + ); + + return FALSE; + } + + g_clear_error( + &local_error + ); + + if (!tool_registry_set_version( + tool_registry, + identifier, + NULL, + &local_error + )) + { + tool_initializer_set_wrapped_error( + error, + TOOL_INITIALIZER_ERROR_INTERNAL_STATE, + "La version précédente n’a pas pu être effacée", + local_error + ); + + g_clear_error( + &local_error + ); + + g_free( + detected_version + ); + + g_free( + summary + ); + + return FALSE; + } + + summary->version_failed_count++; + } + + g_free( + detected_version + ); + + break; + + case TOOL_AVAILABILITY_UNKNOWN: + default: + g_set_error( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_INTERNAL_STATE, + "La disponibilité de l’outil '%s' est incohérente.", + identifier + ); + + g_free( + summary + ); + + return FALSE; + } + } + + if (g_cancellable_set_error_if_cancelled( + cancellable, + error + )) + { + g_free( + summary + ); + + return FALSE; + } + + { + char *summary_message = NULL; + + summary_message = + g_strdup_printf( + "Initialisation terminée : %" + G_GSIZE_FORMAT + " disponibles, %" + G_GSIZE_FORMAT + " absents, %" + G_GSIZE_FORMAT + " versions détectées, %" + G_GSIZE_FORMAT + " non détectées", + summary->available_count, + summary->missing_count, + summary->version_detected_count, + summary->version_failed_count + ); + + background_task_report_progress( + task, + 1.0, + summary_message + ); + + g_free( + summary_message + ); + } + + *result = summary; + + return TRUE; +} + +/** + * @brief Garantit que le registre contient exactement le catalogue initial. + * + * Le premier démarrage enregistre les outils. + * Les démarrages suivants réutilisent le registre existant. + * + * @param tool_initializer Initialiseur concerné. + * @param error Emplacement recevant une erreur. + * + * @return TRUE si le registre est prêt. + */ +static gboolean tool_initializer_ensure_catalog_registered( + ToolInitializer *tool_initializer, + GError **error +) +{ + ToolRegistry *tool_registry = NULL; + + gsize registry_count = 0; + gsize catalog_count = 0; + gsize entry_index = 0; + + GError *local_error = NULL; + + if (tool_initializer == NULL || + tool_initializer->tool_registry == NULL) + { + g_set_error_literal( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_INTERNAL_STATE, + "Le registre interne des outils est invalide." + ); + + return FALSE; + } + + tool_registry = + tool_initializer->tool_registry; + + registry_count = + tool_registry_get_count( + tool_registry + ); + + catalog_count = + tool_catalog_get_count(); + + if (registry_count == 0) + { + if (!tool_catalog_register_defaults( + tool_registry, + &local_error + )) + { + tool_initializer_set_wrapped_error( + error, + TOOL_INITIALIZER_ERROR_CATALOG_REGISTRATION, + "Le catalogue d’outils n’a pas pu être enregistré", + local_error + ); + + g_clear_error( + &local_error + ); + + return FALSE; + } + + return TRUE; + } + + /* + * Le registre est conservé entre deux initialisations. + * + * Un registre partiellement rempli indique une incohérence, + * car tool_catalog_register_defaults() est atomique. + */ + if (registry_count != catalog_count) + { + g_set_error( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_CATALOG_REGISTRATION, + "Le registre contient %" G_GSIZE_FORMAT + " outils alors que le catalogue en contient %" + G_GSIZE_FORMAT ".", + registry_count, + catalog_count + ); + + return FALSE; + } + + for (entry_index = 0; + entry_index < catalog_count; + entry_index++) + { + const ToolCatalogEntry *catalog_entry = NULL; + const char *identifier = NULL; + + catalog_entry = + tool_catalog_get_entry( + entry_index + ); + + + if (catalog_entry != NULL) + { + identifier = + tool_catalog_entry_get_identifier( + catalog_entry + ); + } + + if (catalog_entry == NULL || + identifier == NULL || + tool_registry_find( + tool_registry, + identifier + ) == NULL) + { + g_set_error( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_CATALOG_REGISTRATION, + "L’entrée de catalogue numéro %" + G_GSIZE_FORMAT + " est absente ou invalide.", + entry_index + ); + + return FALSE; + } + } + + return TRUE; +} + +/** + * @brief Finalise l’état de ToolInitializer sur le contexte principal. + * + * @param task Tâche terminée. + * @param user_data Initialiseur concerné. + */ +static void tool_initializer_bootstrap_completed( + BackgroundTask *task, + gpointer user_data +) +{ + ToolInitializer *tool_initializer = + user_data; + + const ToolInitializationSummary *summary = + NULL; + + if (task == NULL || + tool_initializer == NULL) + { + return; + } + + if (background_task_get_state( + task + ) == BACKGROUND_TASK_STATE_COMPLETED) + { + summary = background_task_get_result( + task + ); + } + + g_mutex_lock( + &tool_initializer->mutex + ); + + if (summary != NULL) + { + tool_initializer->last_summary = + *summary; + + tool_initializer->has_last_summary = + TRUE; + } + + tool_initializer->is_running = FALSE; + + g_mutex_unlock( + &tool_initializer->mutex + ); +} + +GQuark tool_initializer_error_quark(void) +{ + return g_quark_from_static_string( + "labfy-investigation-tool-initializer-error" + ); +} + +ToolInitializer *tool_initializer_new( + TaskManager *task_manager, + GError **error +) +{ + ToolInitializer *tool_initializer = NULL; + + g_return_val_if_fail( + error == NULL || *error == NULL, + NULL + ); + + if (task_manager == NULL) + { + g_set_error_literal( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_INVALID_ARGUMENT, + "Le gestionnaire de tâches est invalide." + ); + + return NULL; + } + + tool_initializer = g_new0( + ToolInitializer, + 1 + ); + + g_atomic_ref_count_init( + &tool_initializer->reference_count + ); + + g_mutex_init( + &tool_initializer->mutex + ); + + tool_initializer->task_manager = + task_manager; + + tool_initializer->tool_registry = + tool_registry_new(); + + tool_initializer->task = NULL; + tool_initializer->has_last_summary = FALSE; + tool_initializer->is_running = FALSE; + + return tool_initializer; +} + +void tool_initializer_free( + ToolInitializer *tool_initializer +) +{ + BackgroundTask *task = NULL; + + if (tool_initializer == NULL) + { + return; + } + + task = tool_initializer_get_task( + tool_initializer + ); + + if (task != NULL) + { + background_task_cancel( + task + ); + + background_task_unref( + task + ); + } + + tool_initializer_unref_internal( + tool_initializer + ); +} + +ToolRegistry *tool_initializer_get_registry( + ToolInitializer *tool_initializer +) +{ + if (tool_initializer == NULL) + { + return NULL; + } + + return tool_initializer->tool_registry; +} + +gboolean tool_initializer_start( + ToolInitializer *tool_initializer, + GError **error +) +{ + ToolInitializationSummary previous_summary = {0}; + gboolean previous_has_last_summary = FALSE; + BackgroundTask *new_task = NULL; + BackgroundTask *previous_task = NULL; + + ToolInitializer *worker_reference = NULL; + ToolInitializer *completion_reference = NULL; + + TaskManager *task_manager = NULL; + + GError *local_error = NULL; + + g_return_val_if_fail( + error == NULL || *error == NULL, + FALSE + ); + + if (tool_initializer == NULL) + { + g_set_error_literal( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_INVALID_ARGUMENT, + "L’initialiseur d’outils est invalide." + ); + + return FALSE; + } + + /* + * is_running sert également d’état STARTING. + * + * Cela empêche deux appels simultanés de créer chacun une tâche. + */ + g_mutex_lock( + &tool_initializer->mutex + ); + + if (tool_initializer->is_running) + { + g_mutex_unlock( + &tool_initializer->mutex + ); + + g_set_error_literal( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_ALREADY_RUNNING, + "Une initialisation des outils est déjà en cours." + ); + + return FALSE; + } + + previous_summary = + tool_initializer->last_summary; + + previous_has_last_summary = + tool_initializer->has_last_summary; + + tool_initializer->last_summary = + (ToolInitializationSummary) {0}; + + tool_initializer->has_last_summary = + FALSE; + + tool_initializer->is_running = TRUE; + task_manager = tool_initializer->task_manager; + + g_mutex_unlock( + &tool_initializer->mutex + ); + + new_task = background_task_new( + "Initialisation des outils externes" + ); + + if (new_task == NULL) + { + g_mutex_lock( + &tool_initializer->mutex + ); + + tool_initializer->is_running = FALSE; + + tool_initializer->last_summary = + previous_summary; + + tool_initializer->has_last_summary = + previous_has_last_summary; + + g_mutex_unlock( + &tool_initializer->mutex + ); + + g_set_error_literal( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_TASK_CREATION, + "La tâche d’initialisation n’a pas pu être créée." + ); + + return FALSE; + } + + if (!task_manager_add( + task_manager, + new_task, + &local_error + )) + { + g_mutex_lock( + &tool_initializer->mutex + ); + + 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; + + tool_initializer_set_wrapped_error( + error, + TOOL_INITIALIZER_ERROR_TASK_REGISTRATION, + "La tâche d’initialisation n’a pas pu être enregistrée", + local_error + ); + + g_clear_error( + &local_error + ); + + background_task_unref( + new_task + ); + + return FALSE; + } + + /* + * La référence initiale de new_task devient la référence possédée + * par ToolInitializer. + */ + g_mutex_lock( + &tool_initializer->mutex + ); + + previous_task = tool_initializer->task; + tool_initializer->task = new_task; + + g_mutex_unlock( + &tool_initializer->mutex + ); + + worker_reference = + tool_initializer_ref_internal( + tool_initializer + ); + + completion_reference = + tool_initializer_ref_internal( + tool_initializer + ); + + if (!background_task_start( + new_task, + tool_initializer_bootstrap_worker, + worker_reference, + tool_initializer_unref_notify, + g_free, + tool_initializer_bootstrap_completed, + completion_reference, + tool_initializer_unref_notify, + &local_error + )) + { + /* + * En cas d’échec, BackgroundTask ne possède ni worker_reference + * ni completion_reference. + */ + tool_initializer_unref_internal( + worker_reference + ); + + tool_initializer_unref_internal( + completion_reference + ); + + g_mutex_lock( + &tool_initializer->mutex + ); + + tool_initializer->last_summary = + previous_summary; + + tool_initializer->has_last_summary = + previous_has_last_summary; + + tool_initializer->task = + previous_task; + + tool_initializer->is_running = + FALSE; + + g_mutex_unlock( + &tool_initializer->mutex + ); + + task_manager_remove( + task_manager, + new_task + ); + + tool_initializer_set_wrapped_error( + error, + TOOL_INITIALIZER_ERROR_TASK_START, + "La tâche d’initialisation n’a pas pu être démarrée", + local_error + ); + + g_clear_error( + &local_error + ); + + g_mutex_lock( + &tool_initializer->mutex + ); + + tool_initializer->last_summary = + (ToolInitializationSummary) {0}; + + tool_initializer->has_last_summary = + FALSE; + + g_mutex_unlock( + &tool_initializer->mutex + ); + + background_task_unref( + new_task + ); + + return FALSE; + } + + /* + * L’ancienne tâche n’est plus la tâche courante. + */ + background_task_unref( + previous_task + ); + + return TRUE; +} + +gboolean tool_initializer_cancel( + ToolInitializer *tool_initializer, + GError **error +) +{ + BackgroundTask *task = NULL; + + g_return_val_if_fail( + error == NULL || *error == NULL, + FALSE + ); + + if (tool_initializer == NULL) + { + g_set_error_literal( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_INVALID_ARGUMENT, + "L’initialiseur d’outils est invalide." + ); + + return FALSE; + } + + g_mutex_lock( + &tool_initializer->mutex + ); + + if (tool_initializer->is_running && + tool_initializer->task != NULL) + { + task = background_task_ref( + tool_initializer->task + ); + } + + g_mutex_unlock( + &tool_initializer->mutex + ); + + if (task == NULL) + { + g_set_error_literal( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_NOT_RUNNING, + "Aucune initialisation des outils n’est en cours." + ); + + return FALSE; + } + + background_task_cancel( + task + ); + + background_task_unref( + task + ); + + return TRUE; +} + +gboolean tool_initializer_is_running( + const ToolInitializer *tool_initializer +) +{ + ToolInitializer *mutable_initializer = NULL; + gboolean is_running = FALSE; + + if (tool_initializer == NULL) + { + return FALSE; + } + + mutable_initializer = + (ToolInitializer *) tool_initializer; + + g_mutex_lock( + &mutable_initializer->mutex + ); + + is_running = + mutable_initializer->is_running; + + g_mutex_unlock( + &mutable_initializer->mutex + ); + + return is_running; +} + +BackgroundTask *tool_initializer_get_task( + const ToolInitializer *tool_initializer +) +{ + ToolInitializer *mutable_initializer = NULL; + BackgroundTask *task = NULL; + + if (tool_initializer == NULL) + { + return NULL; + } + + mutable_initializer = + (ToolInitializer *) tool_initializer; + + g_mutex_lock( + &mutable_initializer->mutex + ); + + if (mutable_initializer->task != NULL) + { + task = background_task_ref( + mutable_initializer->task + ); + } + + g_mutex_unlock( + &mutable_initializer->mutex + ); + + return task; +} + +gboolean tool_initializer_get_last_summary( + const ToolInitializer *tool_initializer, + ToolInitializationSummary *out_summary +) +{ + ToolInitializer *mutable_initializer = NULL; + gboolean has_last_summary = FALSE; + + if (tool_initializer == NULL || + out_summary == NULL) + { + return FALSE; + } + + mutable_initializer = + (ToolInitializer *) tool_initializer; + + g_mutex_lock( + &mutable_initializer->mutex + ); + + has_last_summary = + mutable_initializer->has_last_summary; + + if (has_last_summary) + { + *out_summary = + mutable_initializer->last_summary; + } + + g_mutex_unlock( + &mutable_initializer->mutex + ); + + return has_last_summary; +} diff --git a/tests/test_tool_initializer b/tests/test_tool_initializer new file mode 100755 index 0000000..8ac05fd Binary files /dev/null and b/tests/test_tool_initializer differ diff --git a/tests/test_tool_initializer.c b/tests/test_tool_initializer.c new file mode 100644 index 0000000..b8fd0ea --- /dev/null +++ b/tests/test_tool_initializer.c @@ -0,0 +1,1647 @@ +/****************************************************************************** + * @file test_tool_initializer.c + * @brief Tests du composant ToolInitializer. + ******************************************************************************/ + +#include "core/tool_initializer.h" +#include "core/tool_catalog.h" + +#include +#include +#include + +#define TEST_TOOL_INITIALIZER_TIMEOUT_SECONDS 3 + +static char *test_tool_initializer_directory = NULL; + +static gboolean test_tool_initializer_wait_for_progress( + ToolInitializer *tool_initializer, + double minimum_progress +) +{ + const gint64 timeout_microseconds = + 3 * G_TIME_SPAN_SECOND; + + gint64 deadline = 0; + + g_assert_nonnull( + tool_initializer + ); + + deadline = + g_get_monotonic_time() + + timeout_microseconds; + + while (g_get_monotonic_time() < deadline) + { + BackgroundTask *task = NULL; + BackgroundTaskState task_state; + double progress = 0.0; + + task = + tool_initializer_get_task( + tool_initializer + ); + + if (task != NULL) + { + task_state = + background_task_get_state( + task + ); + + progress = + background_task_get_progress( + task + ); + + background_task_unref( + task + ); + + if (progress >= minimum_progress) + { + return TRUE; + } + + if (task_state == BACKGROUND_TASK_STATE_COMPLETED || + task_state == BACKGROUND_TASK_STATE_FAILED || + task_state == BACKGROUND_TASK_STATE_CANCELLED) + { + return FALSE; + } + } + + while (g_main_context_iteration( + NULL, + FALSE + )) + { + } + + g_usleep( + 1000 + ); + } + + return FALSE; +} + +static void test_tool_initializer_reset_fake_tools(void) +{ + gsize tool_index = 0; + + for (tool_index = 0; + tool_index < tool_catalog_get_count(); + tool_index++) + { + const ToolCatalogEntry *catalog_entry = NULL; + const char *executable_name = NULL; + char *executable_path = NULL; + + catalog_entry = + tool_catalog_get_entry( + tool_index + ); + + executable_name = + tool_catalog_entry_get_executable_name( + catalog_entry + ); + + if (executable_name == NULL) + { + continue; + } + + executable_path = + g_build_filename( + test_tool_initializer_directory, + executable_name, + NULL + ); + + g_remove( + executable_path + ); + + g_free( + executable_path + ); + } +} + +static void test_tool_initializer_write_fake_tool( + const char *executable_name, + const char *script_content +) +{ + char *executable_path = NULL; + GError *error = NULL; + + g_assert_nonnull( + test_tool_initializer_directory + ); + + g_assert_nonnull( + executable_name + ); + + g_assert_nonnull( + script_content + ); + + executable_path = + g_build_filename( + test_tool_initializer_directory, + executable_name, + NULL + ); + + g_assert_true( + g_file_set_contents( + executable_path, + script_content, + -1, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_cmpint( + g_chmod( + executable_path, + S_IRUSR | + S_IWUSR | + S_IXUSR + ), + ==, + 0 + ); + + g_free( + executable_path + ); +} + +static gboolean test_tool_initializer_timeout( + gpointer user_data +) +{ + gboolean *timed_out = + user_data; + + g_assert_nonnull( + timed_out + ); + + *timed_out = TRUE; + + return G_SOURCE_REMOVE; +} + +static gboolean test_tool_initializer_wait_until_stopped( + ToolInitializer *tool_initializer +) +{ + gboolean timed_out = FALSE; + guint timeout_source_id = 0; + + g_assert_nonnull( + tool_initializer + ); + + 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 (tool_initializer_is_running( + tool_initializer + ) && + !timed_out) + { + g_main_context_iteration( + NULL, + TRUE + ); + } + + if (!timed_out) + { + g_assert_true( + g_source_remove( + timeout_source_id + ) + ); + } + + return !timed_out && + !tool_initializer_is_running( + tool_initializer + ); +} + +static void test_tool_initializer_new_invalid_arguments(void) +{ + ToolInitializer *tool_initializer = NULL; + GError *error = NULL; + + tool_initializer = tool_initializer_new( + NULL, + &error + ); + + g_assert_null( + tool_initializer + ); + + g_assert_error( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + tool_initializer = tool_initializer_new( + NULL, + NULL + ); + + g_assert_null( + tool_initializer + ); +} + +static void test_tool_initializer_new_valid(void) +{ + TaskManager *task_manager = NULL; + ToolInitializer *tool_initializer = NULL; + ToolRegistry *tool_registry = NULL; + GError *error = NULL; + + task_manager = task_manager_new(); + + g_assert_nonnull( + task_manager + ); + + tool_initializer = tool_initializer_new( + task_manager, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + tool_initializer + ); + + tool_registry = tool_initializer_get_registry( + tool_initializer + ); + + g_assert_nonnull( + tool_registry + ); + + g_assert_cmpuint( + tool_registry_get_count( + tool_registry + ), + ==, + 0 + ); + + g_assert_false( + tool_initializer_is_running( + tool_initializer + ) + ); + + g_assert_null( + tool_initializer_get_task( + tool_initializer + ) + ); + + tool_initializer_free( + tool_initializer + ); + + /* + * ToolInitializer ne possède pas le TaskManager. + */ + g_assert_cmpuint( + task_manager_get_count( + task_manager + ), + ==, + 0 + ); + + task_manager_free( + task_manager + ); +} + +static void test_tool_initializer_null_accessors(void) +{ + ToolInitializationSummary summary = {0}; + + g_assert_null( + tool_initializer_get_registry( + NULL + ) + ); + + g_assert_false( + tool_initializer_is_running( + NULL + ) + ); + + g_assert_null( + tool_initializer_get_task( + NULL + ) + ); + + g_assert_false( + tool_initializer_get_last_summary( + NULL, + &summary + ) + ); + + tool_initializer_free( + NULL + ); +} + +static void test_tool_initializer_registry_is_owned(void) +{ + TaskManager *task_manager = NULL; + + ToolInitializer *first_initializer = NULL; + ToolInitializer *second_initializer = NULL; + + ToolRegistry *first_registry = NULL; + ToolRegistry *second_registry = NULL; + + GError *error = NULL; + + task_manager = task_manager_new(); + + g_assert_nonnull( + task_manager + ); + + first_initializer = tool_initializer_new( + task_manager, + &error + ); + + g_assert_no_error( + error + ); + + second_initializer = tool_initializer_new( + task_manager, + &error + ); + + g_assert_no_error( + error + ); + + first_registry = tool_initializer_get_registry( + first_initializer + ); + + second_registry = tool_initializer_get_registry( + second_initializer + ); + + g_assert_nonnull( + first_registry + ); + + g_assert_nonnull( + second_registry + ); + + /* + * Chaque initialiseur possède son propre registre. + */ + g_assert_true( + first_registry != second_registry + ); + + tool_initializer_free( + first_initializer + ); + + tool_initializer_free( + second_initializer + ); + + task_manager_free( + task_manager + ); +} + +static void test_tool_initializer_start_invalid_arguments(void) +{ + TaskManager *task_manager = NULL; + + ToolInitializer *tool_initializer = NULL; + GError *error = NULL; + + g_assert_false( + tool_initializer_start( + NULL, + &error + ) + ); + + g_assert_error( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + task_manager = task_manager_new(); + + tool_initializer = tool_initializer_new( + task_manager, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_false( + tool_initializer_cancel( + tool_initializer, + &error + ) + ); + + g_assert_error( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_NOT_RUNNING + ); + + g_clear_error( + &error + ); + + g_assert_false( + tool_initializer_cancel( + NULL, + &error + ) + ); + + g_assert_error( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_INVALID_ARGUMENT + ); + + g_clear_error( + &error + ); + + tool_initializer_free( + tool_initializer + ); + + task_manager_free( + task_manager + ); +} + +static void test_tool_initializer_start(void) +{ + test_tool_initializer_reset_fake_tools(); + + TaskManager *task_manager = NULL; + ToolInitializer *tool_initializer = NULL; + BackgroundTask *task = NULL; + + ToolInitializationSummary summary = {0}; + + GError *error = NULL; + + task_manager = task_manager_new(); + + tool_initializer = tool_initializer_new( + task_manager, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_false( + tool_initializer_get_last_summary( + tool_initializer, + &summary + ) + ); + + g_assert_true( + tool_initializer_start( + tool_initializer, + &error + ) + ); + + g_assert_no_error( + error + ); + + g_assert_true( + tool_initializer_is_running( + tool_initializer + ) + ); + + g_assert_cmpuint( + task_manager_get_count( + task_manager + ), + ==, + 1 + ); + + task = tool_initializer_get_task( + tool_initializer + ); + + g_assert_nonnull( + task + ); + + g_assert_cmpstr( + background_task_get_title( + task + ), + ==, + "Initialisation des outils externes" + ); + + g_assert_true( + test_tool_initializer_wait_until_stopped( + tool_initializer + ) + ); + + g_assert_cmpint( + background_task_get_state( + task + ), + ==, + BACKGROUND_TASK_STATE_COMPLETED + ); + + g_assert_true( + tool_initializer_get_last_summary( + tool_initializer, + &summary + ) + ); + + g_assert_cmpuint( + summary.total_count, + ==, + tool_catalog_get_count() + ); + + g_assert_cmpuint( + summary.available_count, + ==, + 0 + ); + + g_assert_cmpuint( + summary.missing_count, + ==, + tool_catalog_get_count() + ); + + g_assert_cmpuint( + summary.version_detected_count, + ==, + 0 + ); + + g_assert_cmpuint( + summary.version_failed_count, + ==, + 0 + ); + + g_assert_cmpuint( + tool_registry_get_count( + tool_initializer_get_registry( + tool_initializer + ) + ), + ==, + tool_catalog_get_count() + ); + + g_assert_cmpfloat( + background_task_get_progress( + task + ), + ==, + 1.0 + ); + + background_task_unref( + task + ); + + tool_initializer_free( + tool_initializer + ); + + task_manager_free( + task_manager + ); +} + +static void test_tool_initializer_double_start(void) +{ + TaskManager *task_manager = NULL; + ToolInitializer *tool_initializer = NULL; + GError *error = NULL; + + task_manager = task_manager_new(); + + tool_initializer = tool_initializer_new( + task_manager, + &error + ); + + g_assert_no_error( + error + ); + + g_assert_true( + tool_initializer_start( + tool_initializer, + &error + ) + ); + + g_assert_no_error( + error + ); + + /* + * Le callback de fin n’a pas encore été distribué par la boucle + * principale : l’initialiseur est donc toujours considéré actif. + */ + g_assert_false( + tool_initializer_start( + tool_initializer, + &error + ) + ); + + g_assert_error( + error, + TOOL_INITIALIZER_ERROR, + TOOL_INITIALIZER_ERROR_ALREADY_RUNNING + ); + + g_clear_error( + &error + ); + + g_assert_cmpuint( + task_manager_get_count( + task_manager + ), + ==, + 1 + ); + + g_assert_true( + test_tool_initializer_wait_until_stopped( + tool_initializer + ) + ); + + tool_initializer_free( + tool_initializer + ); + + task_manager_free( + task_manager + ); +} + +static void test_tool_initializer_detect_versions(void) +{ + TaskManager *task_manager = NULL; + ToolInitializer *tool_initializer = NULL; + ToolRegistry *tool_registry = NULL; + BackgroundTask *task = NULL; + + const ToolInfo *curl_info = NULL; + const ToolInfo *openssl_info = NULL; + + ToolInitializationSummary summary = {0}; + + GError *error = NULL; + + test_tool_initializer_reset_fake_tools(); + + test_tool_initializer_write_fake_tool( + "curl", + "#!/bin/sh\n" + "printf 'Fake curl 1.2.3\\n'\n" + ); + + test_tool_initializer_write_fake_tool( + "openssl", + "#!/bin/sh\n" + "printf 'Fake OpenSSL 3.0.0\\n' >&2\n" + ); + + task_manager = + task_manager_new(); + + tool_initializer = + tool_initializer_new( + task_manager, + &error + ); + + g_assert_no_error( + error + ); + + 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 + ) + ); + + task = + tool_initializer_get_task( + tool_initializer + ); + + g_assert_nonnull( + task + ); + + g_assert_cmpint( + background_task_get_state( + task + ), + ==, + BACKGROUND_TASK_STATE_COMPLETED + ); + + g_assert_true( + tool_initializer_get_last_summary( + tool_initializer, + &summary + ) + ); + + g_assert_cmpuint( + summary.total_count, + ==, + 5 + ); + + g_assert_cmpuint( + summary.available_count, + ==, + 2 + ); + + g_assert_cmpuint( + summary.missing_count, + ==, + 3 + ); + + g_assert_cmpuint( + summary.version_detected_count, + ==, + 2 + ); + + g_assert_cmpuint( + summary.version_failed_count, + ==, + 0 + ); + + tool_registry = + tool_initializer_get_registry( + tool_initializer + ); + + curl_info = + tool_registry_find( + tool_registry, + "http.curl" + ); + + openssl_info = + tool_registry_find( + tool_registry, + "tls.openssl" + ); + + g_assert_nonnull( + curl_info + ); + + g_assert_nonnull( + openssl_info + ); + + g_assert_cmpstr( + tool_info_get_detected_version( + curl_info + ), + ==, + "Fake curl 1.2.3" + ); + + g_assert_cmpstr( + tool_info_get_detected_version( + openssl_info + ), + ==, + "Fake OpenSSL 3.0.0" + ); + + background_task_unref( + task + ); + + tool_initializer_free( + tool_initializer + ); + + task_manager_free( + task_manager + ); +} + +static void test_tool_initializer_version_failure_is_nonfatal(void) +{ + TaskManager *task_manager = NULL; + ToolInitializer *tool_initializer = NULL; + ToolRegistry *tool_registry = NULL; + BackgroundTask *task = NULL; + + const ToolInfo *curl_info = NULL; + const ToolInfo *openssl_info = NULL; + + ToolInitializationSummary summary = {0}; + + GError *error = NULL; + + test_tool_initializer_reset_fake_tools(); + + test_tool_initializer_write_fake_tool( + "curl", + "#!/bin/sh\n" + "printf 'Version inutilisable\\n'\n" + "exit 7\n" + ); + + test_tool_initializer_write_fake_tool( + "openssl", + "#!/bin/sh\n" + "printf 'Fake OpenSSL 4.0.0\\n'\n" + ); + + task_manager = + task_manager_new(); + + tool_initializer = + tool_initializer_new( + task_manager, + &error + ); + + g_assert_no_error( + error + ); + + 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 + ) + ); + + task = + tool_initializer_get_task( + tool_initializer + ); + + g_assert_nonnull( + task + ); + + g_assert_cmpint( + background_task_get_state( + task + ), + ==, + BACKGROUND_TASK_STATE_COMPLETED + ); + + g_assert_true( + tool_initializer_get_last_summary( + tool_initializer, + &summary + ) + ); + + g_assert_cmpuint( + summary.available_count, + ==, + 2 + ); + + g_assert_cmpuint( + summary.missing_count, + ==, + 3 + ); + + g_assert_cmpuint( + summary.version_detected_count, + ==, + 1 + ); + + g_assert_cmpuint( + summary.version_failed_count, + ==, + 1 + ); + + tool_registry = + tool_initializer_get_registry( + tool_initializer + ); + + curl_info = + tool_registry_find( + tool_registry, + "http.curl" + ); + + openssl_info = + tool_registry_find( + tool_registry, + "tls.openssl" + ); + + g_assert_null( + tool_info_get_detected_version( + curl_info + ) + ); + + g_assert_cmpstr( + tool_info_get_detected_version( + openssl_info + ), + ==, + "Fake OpenSSL 4.0.0" + ); + + background_task_unref( + task + ); + + tool_initializer_free( + tool_initializer + ); + + task_manager_free( + task_manager + ); +} + +static void test_tool_initializer_cancel_running(void) +{ + TaskManager *task_manager = NULL; + ToolInitializer *tool_initializer = NULL; + BackgroundTask *task = NULL; + + ToolInitializationSummary summary = {0}; + + GError *error = NULL; + + test_tool_initializer_reset_fake_tools(); + + /* + * dig est le premier outil du catalogue. + * + * Le processus reste actif suffisamment longtemps pour permettre + * de tester l’annulation de ToolProcess. + */ + 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 + ); + + 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 + ); + + 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_cmpint( + background_task_get_state( + task + ), + ==, + BACKGROUND_TASK_STATE_CANCELLED + ); + + /* + * Une initialisation annulée ne produit pas de résumé valide. + */ + g_assert_false( + tool_initializer_get_last_summary( + tool_initializer, + &summary + ) + ); + + background_task_unref( + task + ); + + tool_initializer_free( + tool_initializer + ); + + task_manager_free( + task_manager + ); +} + +static void test_tool_initializer_restart_after_completion(void) +{ + TaskManager *task_manager = NULL; + ToolInitializer *tool_initializer = NULL; + ToolRegistry *tool_registry = NULL; + + const ToolInfo *curl_info = NULL; + + ToolInitializationSummary summary = {0}; + + GError *error = NULL; + + test_tool_initializer_reset_fake_tools(); + + test_tool_initializer_write_fake_tool( + "curl", + "#!/bin/sh\n" + "printf 'Fake curl 1.0.0\\n'\n" + ); + + task_manager = + task_manager_new(); + + tool_initializer = + tool_initializer_new( + task_manager, + &error + ); + + g_assert_no_error( + error + ); + + 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 + ) + ); + + tool_registry = + tool_initializer_get_registry( + tool_initializer + ); + + curl_info = + tool_registry_find( + tool_registry, + "http.curl" + ); + + g_assert_nonnull( + curl_info + ); + + g_assert_cmpstr( + tool_info_get_detected_version( + curl_info + ), + ==, + "Fake curl 1.0.0" + ); + + test_tool_initializer_write_fake_tool( + "curl", + "#!/bin/sh\n" + "/bin/sleep 1\n" + "printf 'Fake curl 2.0.0\\n'\n" + ); + + g_assert_true( + tool_initializer_start( + tool_initializer, + &error + ) + ); + + g_assert_no_error( + error + ); + + /* + * Le résumé précédent n’est plus valide pendant cette nouvelle + * exécution. + */ + g_assert_false( + tool_initializer_get_last_summary( + tool_initializer, + &summary + ) + ); + + g_assert_true( + test_tool_initializer_wait_until_stopped( + tool_initializer + ) + ); + + curl_info = + tool_registry_find( + tool_registry, + "http.curl" + ); + + g_assert_cmpstr( + tool_info_get_detected_version( + curl_info + ), + ==, + "Fake curl 2.0.0" + ); + + g_assert_true( + tool_initializer_get_last_summary( + tool_initializer, + &summary + ) + ); + + g_assert_cmpuint( + summary.available_count, + ==, + 1 + ); + + g_assert_cmpuint( + summary.missing_count, + ==, + tool_catalog_get_count() - 1 + ); + + /* + * TaskManager conserve les deux tâches terminées. + */ + g_assert_cmpuint( + task_manager_get_count( + task_manager + ), + ==, + 2 + ); + + tool_initializer_free( + tool_initializer + ); + + task_manager_free( + task_manager + ); +} + +static void test_tool_initializer_restart_after_cancellation(void) +{ + TaskManager *task_manager = NULL; + ToolInitializer *tool_initializer = NULL; + ToolRegistry *tool_registry = NULL; + + const ToolInfo *curl_info = NULL; + + ToolInitializationSummary summary = {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 + ); + + g_assert_true( + tool_initializer_start( + tool_initializer, + &error + ) + ); + + g_assert_true( + tool_initializer_is_running( + tool_initializer + ) + ); + + 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_false( + tool_initializer_get_last_summary( + tool_initializer, + &summary + ) + ); + + /* + * Le second démarrage utilise un environnement différent. + */ + test_tool_initializer_reset_fake_tools(); + + test_tool_initializer_write_fake_tool( + "curl", + "#!/bin/sh\n" + "printf 'Fake curl 3.0.0\\n'\n" + ); + + 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_true( + tool_initializer_get_last_summary( + tool_initializer, + &summary + ) + ); + + g_assert_cmpuint( + summary.available_count, + ==, + 1 + ); + + g_assert_cmpuint( + summary.version_detected_count, + ==, + 1 + ); + + tool_registry = + tool_initializer_get_registry( + tool_initializer + ); + + curl_info = + tool_registry_find( + tool_registry, + "http.curl" + ); + + g_assert_nonnull( + curl_info + ); + + g_assert_cmpstr( + tool_info_get_detected_version( + curl_info + ), + ==, + "Fake curl 3.0.0" + ); + + g_assert_cmpuint( + task_manager_get_count( + task_manager + ), + ==, + 2 + ); + + tool_initializer_free( + tool_initializer + ); + + task_manager_free( + task_manager + ); +} + +int main( + int argc, + char **argv +) +{ + g_test_init( + &argc, + &argv, + NULL + ); + + { + GError *error = NULL; + + test_tool_initializer_directory = + g_dir_make_tmp( + "labfy-tool-initializer-XXXXXX", + &error + ); + + g_assert_no_error( + error + ); + + g_assert_nonnull( + test_tool_initializer_directory + ); + + g_assert_true( + g_setenv( + "PATH", + test_tool_initializer_directory, + TRUE + ) + ); + } + + g_test_add_func( + "/tool_initializer/detect_versions", + test_tool_initializer_detect_versions + ); + + g_test_add_func( + "/tool_initializer/version_failure_is_nonfatal", + test_tool_initializer_version_failure_is_nonfatal + ); + + g_test_add_func( + "/tool_initializer/new_invalid_arguments", + test_tool_initializer_new_invalid_arguments + ); + + g_test_add_func( + "/tool_initializer/new_valid", + test_tool_initializer_new_valid + ); + + g_test_add_func( + "/tool_initializer/null_accessors", + test_tool_initializer_null_accessors + ); + + g_test_add_func( + "/tool_initializer/registry_is_owned", + test_tool_initializer_registry_is_owned + ); + + g_test_add_func( + "/tool_initializer/start_invalid_arguments", + test_tool_initializer_start_invalid_arguments + ); + + g_test_add_func( + "/tool_initializer/start", + test_tool_initializer_start + ); + + g_test_add_func( + "/tool_initializer/double_start", + test_tool_initializer_double_start + ); + + g_test_add_func( + "/tool_initializer/cancel_running", + test_tool_initializer_cancel_running + ); + + g_test_add_func( + "/tool_initializer/restart_after_completion", + test_tool_initializer_restart_after_completion + ); + + g_test_add_func( + "/tool_initializer/restart_after_cancellation", + test_tool_initializer_restart_after_cancellation + ); + + { + int test_result = 0; + + test_result = + g_test_run(); + + test_tool_initializer_reset_fake_tools(); + + g_rmdir( + test_tool_initializer_directory + ); + + g_clear_pointer( + &test_tool_initializer_directory, + g_free + ); + + return test_result; + } +}