/****************************************************************************** * @file test_tool_task.c * @brief Tests unitaires de l'exécution d'outils dans BackgroundTask. ******************************************************************************/ #include "core/tool_task.h" #include #include #include #include /** * @struct ToolTaskFixture * @brief Environnement temporaire contenant un faux outil. */ typedef struct { char *temporary_directory; char *fake_tool_path; char *original_path; } ToolTaskFixture; /** * @struct ToolTaskWaitContext * @brief Synchronisation entre une BackgroundTask et le test principal. */ typedef struct { GMainLoop *main_loop; BackgroundTask *completed_task; guint timeout_source_id; gboolean completion_called; gboolean timed_out; } ToolTaskWaitContext; /** * @struct ToolTaskCancellationContext * @brief Contexte utilisé pendant un test d'annulation. */ typedef struct { GMainLoop *main_loop; BackgroundTask *background_task; guint cancel_source_id; guint timeout_source_id; gboolean completion_called; gboolean timed_out; } ToolTaskCancellationContext; /** * @struct ToolTaskConcurrentContext * @brief Contexte d'attente de deux tâches simultanées. */ typedef struct { GMainLoop *main_loop; BackgroundTask *first_task; BackgroundTask *second_task; guint completed_count; guint timeout_source_id; gboolean timed_out; } ToolTaskConcurrentContext; static char *test_tool_task_shared_directory = NULL; static void test_tool_task_write_script( ToolTaskFixture *fixture, const char *script_content ); static void test_tool_task_fixture_setup( ToolTaskFixture *fixture, gconstpointer user_data ); static ToolRegistry *test_tool_task_create_registry( ToolTaskFixture *fixture, gboolean create_executable, gboolean refresh_registry ); static gboolean test_tool_task_on_timeout( gpointer user_data ); static void test_tool_task_on_completed( BackgroundTask *background_task, gpointer user_data ); static gboolean test_tool_task_start_and_wait( ToolTask *tool_task, ToolTaskWaitContext *wait_context, GError **error ); static void test_tool_task_assert_bytes_equal_text( GBytes *bytes, const char *expected_text ); static ToolRegistry *test_tool_task_create_registry( ToolTaskFixture *fixture, gboolean create_executable, gboolean refresh_registry ); static gboolean test_tool_task_cancel_task( gpointer user_data ); static gboolean test_tool_task_cancellation_timeout( gpointer user_data ); static void test_tool_task_on_cancellation_completed( BackgroundTask *background_task, gpointer user_data ); static gboolean test_tool_task_concurrent_timeout( gpointer user_data ); static void test_tool_task_on_concurrent_completed( BackgroundTask *background_task, gpointer user_data ); static void test_tool_task_cancellation( ToolTaskFixture *fixture, gconstpointer user_data ); static void test_tool_task_concurrent_tasks( ToolTaskFixture *fixture, gconstpointer user_data ); /** * @brief Demande l'annulation de la tâche testée. */ static gboolean test_tool_task_cancel_task( gpointer user_data ) { ToolTaskCancellationContext *cancellation_context = user_data; if (cancellation_context == NULL) { return G_SOURCE_REMOVE; } cancellation_context->cancel_source_id = 0; background_task_cancel( cancellation_context->background_task ); return G_SOURCE_REMOVE; } /** * @brief Interrompt le test d'annulation en cas de blocage. */ static gboolean test_tool_task_cancellation_timeout( gpointer user_data ) { ToolTaskCancellationContext *cancellation_context = user_data; if (cancellation_context == NULL) { return G_SOURCE_REMOVE; } cancellation_context->timeout_source_id = 0; cancellation_context->timed_out = TRUE; if (cancellation_context->main_loop != NULL) { g_main_loop_quit( cancellation_context->main_loop ); } return G_SOURCE_REMOVE; } /** * @brief Reçoit la fin de la tâche annulée. */ static void test_tool_task_on_cancellation_completed( BackgroundTask *background_task, gpointer user_data ) { ToolTaskCancellationContext *cancellation_context = user_data; if (cancellation_context == NULL) { return; } g_assert_true( background_task == cancellation_context->background_task ); cancellation_context->completion_called = TRUE; if (cancellation_context->cancel_source_id != 0) { g_source_remove( cancellation_context->cancel_source_id ); cancellation_context->cancel_source_id = 0; } if (cancellation_context->timeout_source_id != 0) { g_source_remove( cancellation_context->timeout_source_id ); cancellation_context->timeout_source_id = 0; } if (cancellation_context->main_loop != NULL) { g_main_loop_quit( cancellation_context->main_loop ); } } /** * @brief Interrompt un test resté bloqué trop longtemps. * * @param user_data Pointeur vers ToolTaskWaitContext. * * @return G_SOURCE_REMOVE. */ static gboolean test_tool_task_on_timeout( gpointer user_data ) { ToolTaskWaitContext *wait_context = user_data; if (wait_context == NULL) { return G_SOURCE_REMOVE; } wait_context->timeout_source_id = 0; wait_context->timed_out = TRUE; if (wait_context->main_loop != NULL) { g_main_loop_quit( wait_context->main_loop ); } return G_SOURCE_REMOVE; } /** * @brief Reçoit la notification finale d'une BackgroundTask. * * @param background_task Tâche terminée. * @param user_data Pointeur vers ToolTaskWaitContext. */ static void test_tool_task_on_completed( BackgroundTask *background_task, gpointer user_data ) { ToolTaskWaitContext *wait_context = user_data; if (wait_context == NULL) { return; } wait_context->completion_called = TRUE; wait_context->completed_task = background_task; if (wait_context->timeout_source_id != 0) { g_source_remove( wait_context->timeout_source_id ); wait_context->timeout_source_id = 0; } if (wait_context->main_loop != NULL) { g_main_loop_quit( wait_context->main_loop ); } } /** * @brief Démarre une ToolTask et attend sa terminaison. * * @param tool_task Tâche à démarrer. * @param wait_context Contexte d'attente à initialiser. * @param error Emplacement facultatif pour l'erreur de démarrage. * * @return TRUE si la tâche a été démarrée et terminée avant le timeout. */ static gboolean test_tool_task_start_and_wait( ToolTask *tool_task, ToolTaskWaitContext *wait_context, GError **error ) { gboolean start_success = FALSE; if (tool_task == NULL || wait_context == NULL) { return FALSE; } wait_context->main_loop = g_main_loop_new( NULL, FALSE ); wait_context->completed_task = NULL; wait_context->completion_called = FALSE; wait_context->timed_out = FALSE; wait_context->timeout_source_id = g_timeout_add_seconds( 5, test_tool_task_on_timeout, wait_context ); start_success = tool_task_start( tool_task, test_tool_task_on_completed, wait_context, NULL, error ); if (!start_success) { if (wait_context->timeout_source_id != 0) { g_source_remove( wait_context->timeout_source_id ); wait_context->timeout_source_id = 0; } g_main_loop_unref( wait_context->main_loop ); wait_context->main_loop = NULL; return FALSE; } g_main_loop_run( wait_context->main_loop ); if (wait_context->timeout_source_id != 0) { g_source_remove( wait_context->timeout_source_id ); wait_context->timeout_source_id = 0; } g_main_loop_unref( wait_context->main_loop ); wait_context->main_loop = NULL; return wait_context->completion_called && !wait_context->timed_out; } /** * @brief Vérifie qu'un GBytes contient exactement le texte attendu. * * @param bytes Données à vérifier. * @param expected_text Texte attendu. */ static void test_tool_task_assert_bytes_equal_text( GBytes *bytes, const char *expected_text ) { gconstpointer bytes_data = NULL; gsize bytes_size = 0; gsize expected_size = 0; g_assert_nonnull( bytes ); g_assert_nonnull( expected_text ); bytes_data = g_bytes_get_data( bytes, &bytes_size ); expected_size = strlen( expected_text ); g_assert_cmpuint( bytes_size, ==, expected_size ); g_assert_cmpmem( bytes_data, bytes_size, expected_text, expected_size ); } /** * @brief Vérifie que ToolTask copie toutes les données nécessaires. */ static void test_tool_task_argument_ownership( ToolTaskFixture *fixture, gconstpointer user_data ) { ToolRegistry *tool_registry = NULL; ToolTask *tool_task = NULL; BackgroundTask *background_task = NULL; const ToolTaskResult *task_result = NULL; const ToolProcessResult *process_result = NULL; ToolTaskWaitContext wait_context = {0}; GBytes *stdout_bytes = NULL; char *first_argument = NULL; char *second_argument = NULL; char *working_directory = NULL; const char *arguments[3]; GError *error = NULL; (void) user_data; test_tool_task_write_script( fixture, "#!/bin/sh\n" "for argument in \"$@\"\n" "do\n" " printf '%s\\n' \"$argument\"\n" "done\n" ); tool_registry = test_tool_task_create_registry( fixture, FALSE, TRUE ); first_argument = g_strdup( "premier argument" ); second_argument = g_strdup( "second;non-interprete" ); working_directory = g_strdup( fixture->temporary_directory ); arguments[0] = first_argument; arguments[1] = second_argument; arguments[2] = NULL; tool_task = tool_task_new( tool_registry, "test.fake", "Test de propriété", arguments, working_directory, &error ); g_assert_no_error( error ); g_assert_nonnull( tool_task ); /* * Toutes ces données doivent maintenant être indépendantes. */ g_free( first_argument ); g_free( second_argument ); g_free( working_directory ); tool_registry_free( tool_registry ); tool_registry = NULL; background_task = tool_task_get_background_task( tool_task ); g_assert_true( test_tool_task_start_and_wait( tool_task, &wait_context, &error ) ); g_assert_no_error( error ); g_assert_cmpint( background_task_get_state( background_task ), ==, BACKGROUND_TASK_STATE_COMPLETED ); task_result = tool_task_result_from_background_task( background_task ); g_assert_nonnull( task_result ); g_assert_cmpuint( tool_task_result_get_argument_count( task_result ), ==, 2 ); g_assert_cmpstr( tool_task_result_get_argument( task_result, 0 ), ==, "premier argument" ); g_assert_cmpstr( tool_task_result_get_argument( task_result, 1 ), ==, "second;non-interprete" ); g_assert_cmpstr( tool_task_result_get_working_directory( task_result ), ==, fixture->temporary_directory ); process_result = tool_task_result_get_process_result( task_result ); g_assert_nonnull( process_result ); stdout_bytes = tool_process_result_ref_stdout( process_result ); test_tool_task_assert_bytes_equal_text( stdout_bytes, "premier argument\n" "second;non-interprete\n" ); g_bytes_unref( stdout_bytes ); tool_task_free( tool_task ); } /** * @brief Vérifie une exécution complète réussie. */ static void test_tool_task_success( ToolTaskFixture *fixture, gconstpointer user_data ) { ToolRegistry *tool_registry = NULL; ToolTask *tool_task = NULL; BackgroundTask *background_task = NULL; const ToolTaskResult *task_result = NULL; const ToolProcessResult *process_result = NULL; ToolTaskWaitContext wait_context = {0}; GBytes *stdout_bytes = NULL; GBytes *stderr_bytes = NULL; const char *arguments[] = { "example.org", "nom avec espaces", NULL }; GError *error = NULL; (void) user_data; test_tool_task_write_script( fixture, "#!/bin/sh\n" "printf 'stdout:%s|%s' \"$1\" \"$2\"\n" "printf 'stderr-test' >&2\n" "exit 0\n" ); tool_registry = test_tool_task_create_registry( fixture, FALSE, TRUE ); tool_task = tool_task_new( tool_registry, "test.fake", "Exécution réussie", arguments, NULL, &error ); g_assert_no_error( error ); g_assert_nonnull( tool_task ); background_task = tool_task_get_background_task( tool_task ); g_assert_true( test_tool_task_start_and_wait( tool_task, &wait_context, &error ) ); g_assert_no_error( error ); g_assert_true( wait_context.completion_called ); g_assert_true( wait_context.completed_task == background_task ); g_assert_cmpint( background_task_get_state( background_task ), ==, BACKGROUND_TASK_STATE_COMPLETED ); g_assert_cmpfloat( background_task_get_progress( background_task ), ==, 1.0 ); task_result = tool_task_result_from_background_task( background_task ); g_assert_nonnull( task_result ); g_assert_cmpstr( tool_task_result_get_tool_identifier( task_result ), ==, "test.fake" ); g_assert_cmpstr( tool_task_result_get_executable_path( task_result ), ==, fixture->fake_tool_path ); g_assert_cmpuint( tool_task_result_get_argument_count( task_result ), ==, 2 ); process_result = tool_task_result_get_process_result( task_result ); g_assert_nonnull( process_result ); g_assert_true( tool_process_result_is_success( process_result ) ); g_assert_cmpint( tool_process_result_get_exit_status( process_result ), ==, 0 ); stdout_bytes = tool_process_result_ref_stdout( process_result ); stderr_bytes = tool_process_result_ref_stderr( process_result ); test_tool_task_assert_bytes_equal_text( stdout_bytes, "stdout:example.org|nom avec espaces" ); test_tool_task_assert_bytes_equal_text( stderr_bytes, "stderr-test" ); g_bytes_unref( stdout_bytes ); g_bytes_unref( stderr_bytes ); tool_task_free( tool_task ); tool_registry_free( tool_registry ); } /** * @brief Vérifie qu'un code non nul produit une tâche terminée. */ static void test_tool_task_nonzero_exit( ToolTaskFixture *fixture, gconstpointer user_data ) { ToolRegistry *tool_registry = NULL; ToolTask *tool_task = NULL; BackgroundTask *background_task = NULL; const ToolTaskResult *task_result = NULL; const ToolProcessResult *process_result = NULL; ToolTaskWaitContext wait_context = {0}; GError *error = NULL; (void) user_data; test_tool_task_write_script( fixture, "#!/bin/sh\n" "printf 'erreur fonctionnelle' >&2\n" "exit 7\n" ); tool_registry = test_tool_task_create_registry( fixture, FALSE, TRUE ); tool_task = tool_task_new( tool_registry, "test.fake", "Code non nul", NULL, NULL, &error ); g_assert_no_error( error ); background_task = tool_task_get_background_task( tool_task ); g_assert_true( test_tool_task_start_and_wait( tool_task, &wait_context, &error ) ); g_assert_no_error( error ); g_assert_cmpint( background_task_get_state( background_task ), ==, BACKGROUND_TASK_STATE_COMPLETED ); task_result = tool_task_result_from_background_task( background_task ); g_assert_nonnull( task_result ); process_result = tool_task_result_get_process_result( task_result ); g_assert_nonnull( process_result ); g_assert_false( tool_process_result_is_success( process_result ) ); g_assert_cmpint( tool_process_result_get_exit_status( process_result ), ==, 7 ); tool_task_free( tool_task ); tool_registry_free( tool_registry ); } /** * @brief Vérifie l'échec lorsque l'exécutable disparaît avant le lancement. */ static void test_tool_task_spawn_failure( ToolTaskFixture *fixture, gconstpointer user_data ) { ToolRegistry *tool_registry = NULL; ToolTask *tool_task = NULL; BackgroundTask *background_task = NULL; ToolTaskWaitContext wait_context = {0}; GError *task_error = NULL; GError *stored_error = NULL; (void) user_data; tool_registry = test_tool_task_create_registry( fixture, TRUE, TRUE ); tool_task = tool_task_new( tool_registry, "test.fake", "Échec de lancement", NULL, NULL, &task_error ); g_assert_no_error( task_error ); g_assert_nonnull( tool_task ); background_task = tool_task_get_background_task( tool_task ); g_assert_cmpint( g_remove( fixture->fake_tool_path ), ==, 0 ); /* * Le démarrage asynchrone réussit. C'est le worker qui découvrira * ensuite que l'exécutable a disparu. */ g_assert_true( test_tool_task_start_and_wait( tool_task, &wait_context, &task_error ) ); g_assert_no_error( task_error ); g_assert_cmpint( background_task_get_state( background_task ), ==, BACKGROUND_TASK_STATE_FAILED ); g_assert_null( tool_task_result_from_background_task( background_task ) ); stored_error = background_task_dup_error( background_task ); g_assert_nonnull( stored_error ); g_assert_error( stored_error, TOOL_TASK_ERROR, TOOL_TASK_ERROR_PROCESS ); g_clear_error( &stored_error ); tool_task_free( tool_task ); tool_registry_free( tool_registry ); } /** * @brief Vérifie la transmission du dossier de travail. */ static void test_tool_task_working_directory( ToolTaskFixture *fixture, gconstpointer user_data ) { ToolRegistry *tool_registry = NULL; ToolTask *tool_task = NULL; BackgroundTask *background_task = NULL; const ToolTaskResult *task_result = NULL; const ToolProcessResult *process_result = NULL; ToolTaskWaitContext wait_context = {0}; GBytes *stdout_bytes = NULL; char *canonical_directory = NULL; GError *error = NULL; (void) user_data; test_tool_task_write_script( fixture, "#!/bin/sh\n" "printf '%s' \"$PWD\"\n" ); tool_registry = test_tool_task_create_registry( fixture, FALSE, TRUE ); tool_task = tool_task_new( tool_registry, "test.fake", "Dossier de travail", NULL, fixture->temporary_directory, &error ); g_assert_no_error( error ); background_task = tool_task_get_background_task( tool_task ); g_assert_true( test_tool_task_start_and_wait( tool_task, &wait_context, &error ) ); g_assert_no_error( error ); task_result = tool_task_result_from_background_task( background_task ); g_assert_nonnull( task_result ); process_result = tool_task_result_get_process_result( task_result ); stdout_bytes = tool_process_result_ref_stdout( process_result ); canonical_directory = g_canonicalize_filename( fixture->temporary_directory, NULL ); test_tool_task_assert_bytes_equal_text( stdout_bytes, canonical_directory ); g_assert_cmpstr( tool_task_result_get_working_directory( task_result ), ==, fixture->temporary_directory ); g_bytes_unref( stdout_bytes ); g_free( canonical_directory ); tool_task_free( tool_task ); tool_registry_free( tool_registry ); } /** * @brief Crée un faux exécutable dans le dossier temporaire. * * @param fixture Fixture du test. * @param script_content Contenu complet du script. */ static void test_tool_task_write_script( ToolTaskFixture *fixture, const char *script_content ) { GError *error = NULL; gboolean write_success = FALSE; int chmod_result = 0; g_assert_nonnull( fixture ); g_assert_nonnull( fixture->fake_tool_path ); g_assert_nonnull( script_content ); write_success = g_file_set_contents( fixture->fake_tool_path, script_content, -1, &error ); g_assert_no_error( error ); g_assert_true( write_success ); chmod_result = g_chmod( fixture->fake_tool_path, S_IRUSR | S_IWUSR | S_IXUSR ); g_assert_cmpint( chmod_result, ==, 0 ); } static void test_tool_task_fixture_setup( ToolTaskFixture *fixture, gconstpointer user_data ) { (void) user_data; g_assert_nonnull( fixture ); g_assert_nonnull( test_tool_task_shared_directory ); fixture->temporary_directory = g_strdup( test_tool_task_shared_directory ); fixture->fake_tool_path = g_build_filename( fixture->temporary_directory, "fake_tool", NULL ); fixture->original_path = NULL; } /** * @brief Supprime les fichiers propres au test. * * Le dossier temporaire et le PATH sont partagés par toute la suite de * tests. Ils sont donc nettoyés uniquement à la fin de main(). */ static void test_tool_task_fixture_teardown( ToolTaskFixture *fixture, gconstpointer user_data ) { (void) user_data; if (fixture->fake_tool_path != NULL) { g_remove( fixture->fake_tool_path ); } g_clear_pointer( &fixture->fake_tool_path, g_free ); g_clear_pointer( &fixture->temporary_directory, g_free ); g_clear_pointer( &fixture->original_path, g_free ); } /** * @brief Crée un registre contenant le faux outil. * * @param fixture Fixture du test. * @param create_executable TRUE pour créer le faux exécutable. * @param refresh_registry TRUE pour détecter sa disponibilité. * * @return Nouveau registre. */ static ToolRegistry *test_tool_task_create_registry( ToolTaskFixture *fixture, gboolean create_executable, gboolean refresh_registry ) { ToolRegistry *tool_registry = NULL; GError *error = NULL; if (create_executable) { test_tool_task_write_script( fixture, "#!/bin/sh\n" "exit 0\n" ); } tool_registry = tool_registry_new(); g_assert_nonnull( tool_registry ); g_assert_true( tool_registry_register( tool_registry, "test.fake", "Fake Tool", "fake_tool", TOOL_REQUIREMENT_OPTIONAL, &error ) ); g_assert_no_error( error ); if (refresh_registry) { g_assert_true( tool_registry_refresh( tool_registry, &error ) ); g_assert_no_error( error ); } return tool_registry; } /** * @brief Vérifie le refus des arguments invalides. */ static void test_tool_task_invalid_arguments( ToolTaskFixture *fixture, gconstpointer user_data ) { ToolRegistry *tool_registry = NULL; ToolTask *tool_task = NULL; GError *error = NULL; (void) user_data; tool_registry = test_tool_task_create_registry( fixture, TRUE, TRUE ); tool_task = tool_task_new( NULL, "test.fake", "Tâche de test", NULL, NULL, &error ); g_assert_null( tool_task ); g_assert_error( error, TOOL_TASK_ERROR, TOOL_TASK_ERROR_INVALID_ARGUMENT ); g_clear_error( &error ); tool_task = tool_task_new( tool_registry, NULL, "Tâche de test", NULL, NULL, &error ); g_assert_null( tool_task ); g_assert_error( error, TOOL_TASK_ERROR, TOOL_TASK_ERROR_INVALID_ARGUMENT ); g_clear_error( &error ); tool_task = tool_task_new( tool_registry, "", "Tâche de test", NULL, NULL, &error ); g_assert_null( tool_task ); g_assert_error( error, TOOL_TASK_ERROR, TOOL_TASK_ERROR_INVALID_ARGUMENT ); g_clear_error( &error ); tool_task = tool_task_new( tool_registry, "test.fake", NULL, NULL, NULL, &error ); g_assert_null( tool_task ); g_assert_error( error, TOOL_TASK_ERROR, TOOL_TASK_ERROR_INVALID_ARGUMENT ); g_clear_error( &error ); tool_task = tool_task_new( tool_registry, "test.fake", "", NULL, NULL, &error ); g_assert_null( tool_task ); g_assert_error( error, TOOL_TASK_ERROR, TOOL_TASK_ERROR_INVALID_ARGUMENT ); g_clear_error( &error ); tool_task = tool_task_new( tool_registry, "test.fake", "Tâche de test", NULL, "", &error ); g_assert_null( tool_task ); g_assert_error( error, TOOL_TASK_ERROR, TOOL_TASK_ERROR_INVALID_ARGUMENT ); g_clear_error( &error ); tool_registry_free( tool_registry ); } /** * @brief Vérifie le refus d'un identifiant inconnu. */ static void test_tool_task_tool_not_found( ToolTaskFixture *fixture, gconstpointer user_data ) { ToolRegistry *tool_registry = NULL; ToolTask *tool_task = NULL; GError *error = NULL; (void) fixture; (void) user_data; tool_registry = tool_registry_new(); g_assert_nonnull( tool_registry ); tool_task = tool_task_new( tool_registry, "unknown.tool", "Tâche inconnue", NULL, NULL, &error ); g_assert_null( tool_task ); g_assert_error( error, TOOL_TASK_ERROR, TOOL_TASK_ERROR_TOOL_NOT_FOUND ); g_clear_error( &error ); tool_registry_free( tool_registry ); } /** * @brief Vérifie le refus d'un outil non encore contrôlé. */ static void test_tool_task_tool_not_checked( ToolTaskFixture *fixture, gconstpointer user_data ) { ToolRegistry *tool_registry = NULL; ToolTask *tool_task = NULL; GError *error = NULL; (void) user_data; tool_registry = test_tool_task_create_registry( fixture, TRUE, FALSE ); tool_task = tool_task_new( tool_registry, "test.fake", "Tâche non vérifiée", NULL, NULL, &error ); g_assert_null( tool_task ); g_assert_error( error, TOOL_TASK_ERROR, TOOL_TASK_ERROR_TOOL_NOT_CHECKED ); g_clear_error( &error ); tool_registry_free( tool_registry ); } /** * @brief Vérifie le refus d'un outil explicitement absent. */ static void test_tool_task_tool_missing( ToolTaskFixture *fixture, gconstpointer user_data ) { ToolRegistry *tool_registry = NULL; ToolTask *tool_task = NULL; GError *error = NULL; (void) user_data; /* * Aucun fichier fake_tool n'est créé avant le rafraîchissement. */ tool_registry = test_tool_task_create_registry( fixture, FALSE, TRUE ); tool_task = tool_task_new( tool_registry, "test.fake", "Tâche impossible", NULL, NULL, &error ); g_assert_null( tool_task ); g_assert_error( error, TOOL_TASK_ERROR, TOOL_TASK_ERROR_TOOL_MISSING ); g_clear_error( &error ); tool_registry_free( tool_registry ); } /** * @brief Vérifie la création d'une ToolTask valide. */ static void test_tool_task_create( ToolTaskFixture *fixture, gconstpointer user_data ) { ToolRegistry *tool_registry = NULL; ToolTask *tool_task = NULL; BackgroundTask *background_task = NULL; const char *arguments[] = { "premier argument", "second", NULL }; GError *error = NULL; (void) user_data; tool_registry = test_tool_task_create_registry( fixture, TRUE, TRUE ); tool_task = tool_task_new( tool_registry, "test.fake", "Exécution du faux outil", arguments, fixture->temporary_directory, &error ); g_assert_no_error( error ); g_assert_nonnull( tool_task ); background_task = tool_task_get_background_task( tool_task ); g_assert_nonnull( background_task ); g_assert_cmpstr( background_task_get_title( background_task ), ==, "Exécution du faux outil" ); g_assert_cmpint( background_task_get_state( background_task ), ==, BACKGROUND_TASK_STATE_PENDING ); g_assert_cmpfloat( background_task_get_progress( background_task ), ==, 0.0 ); /* * Vérifie aussi la destruction d'une tâche jamais démarrée. */ tool_task_free( tool_task ); tool_registry_free( tool_registry ); } /** * @brief Vérifie l'annulation d'un outil externe en cours. */ static void test_tool_task_cancellation( ToolTaskFixture *fixture, gconstpointer user_data ) { ToolRegistry *tool_registry = NULL; ToolTask *tool_task = NULL; BackgroundTask *background_task = NULL; ToolTaskCancellationContext cancellation_context = {0}; GError *error = NULL; gboolean start_success = FALSE; (void) user_data; /* * Boucle entièrement interne au shell. * Aucun programme externe comme sleep n'est lancé. */ test_tool_task_write_script( fixture, "#!/bin/sh\n" "while :\n" "do\n" " :\n" "done\n" ); tool_registry = test_tool_task_create_registry( fixture, FALSE, TRUE ); tool_task = tool_task_new( tool_registry, "test.fake", "Annulation du faux outil", NULL, NULL, &error ); g_assert_no_error( error ); g_assert_nonnull( tool_task ); background_task = tool_task_get_background_task( tool_task ); cancellation_context.main_loop = g_main_loop_new( NULL, FALSE ); cancellation_context.background_task = background_task; cancellation_context.cancel_source_id = g_timeout_add( 200, test_tool_task_cancel_task, &cancellation_context ); cancellation_context.timeout_source_id = g_timeout_add_seconds( 5, test_tool_task_cancellation_timeout, &cancellation_context ); start_success = tool_task_start( tool_task, test_tool_task_on_cancellation_completed, &cancellation_context, NULL, &error ); g_assert_no_error( error ); g_assert_true( start_success ); g_main_loop_run( cancellation_context.main_loop ); if (cancellation_context.cancel_source_id != 0) { g_source_remove( cancellation_context.cancel_source_id ); } if (cancellation_context.timeout_source_id != 0) { g_source_remove( cancellation_context.timeout_source_id ); } g_main_loop_unref( cancellation_context.main_loop ); cancellation_context.main_loop = NULL; g_assert_false( cancellation_context.timed_out ); g_assert_true( cancellation_context.completion_called ); g_assert_cmpint( background_task_get_state( background_task ), ==, BACKGROUND_TASK_STATE_CANCELLED ); g_assert_null( tool_task_result_from_background_task( background_task ) ); tool_task_free( tool_task ); tool_registry_free( tool_registry ); } /** * @brief Interrompt le test concurrent en cas de blocage. */ static gboolean test_tool_task_concurrent_timeout( gpointer user_data ) { ToolTaskConcurrentContext *concurrent_context = user_data; if (concurrent_context == NULL) { return G_SOURCE_REMOVE; } concurrent_context->timeout_source_id = 0; concurrent_context->timed_out = TRUE; if (concurrent_context->main_loop != NULL) { g_main_loop_quit( concurrent_context->main_loop ); } return G_SOURCE_REMOVE; } /** * @brief Compte les tâches concurrentes terminées. */ static void test_tool_task_on_concurrent_completed( BackgroundTask *background_task, gpointer user_data ) { ToolTaskConcurrentContext *concurrent_context = user_data; if (concurrent_context == NULL) { return; } g_assert_true( background_task == concurrent_context->first_task || background_task == concurrent_context->second_task ); concurrent_context->completed_count++; if (concurrent_context->completed_count < 2) { return; } if (concurrent_context->timeout_source_id != 0) { g_source_remove( concurrent_context->timeout_source_id ); concurrent_context->timeout_source_id = 0; } if (concurrent_context->main_loop != NULL) { g_main_loop_quit( concurrent_context->main_loop ); } } /** * @brief Vérifie l'indépendance de deux ToolTask simultanées. */ static void test_tool_task_concurrent_tasks( ToolTaskFixture *fixture, gconstpointer user_data ) { ToolRegistry *tool_registry = NULL; ToolTask *first_tool_task = NULL; ToolTask *second_tool_task = NULL; BackgroundTask *first_background_task = NULL; BackgroundTask *second_background_task = NULL; const ToolTaskResult *first_task_result = NULL; const ToolTaskResult *second_task_result = NULL; const ToolProcessResult *first_process_result = NULL; const ToolProcessResult *second_process_result = NULL; ToolTaskConcurrentContext concurrent_context = {0}; GBytes *first_stdout = NULL; GBytes *second_stdout = NULL; const char *first_arguments[] = { "premiere-tache", NULL }; const char *second_arguments[] = { "seconde-tache", NULL }; GError *error = NULL; (void) user_data; test_tool_task_write_script( fixture, "#!/bin/sh\n" "counter=0\n" "while [ \"$counter\" -lt 10000 ]\n" "do\n" " counter=$((counter + 1))\n" "done\n" "printf '%s' \"$1\"\n" ); tool_registry = test_tool_task_create_registry( fixture, FALSE, TRUE ); first_tool_task = tool_task_new( tool_registry, "test.fake", "Première tâche", first_arguments, NULL, &error ); g_assert_no_error( error ); g_assert_nonnull( first_tool_task ); second_tool_task = tool_task_new( tool_registry, "test.fake", "Seconde tâche", second_arguments, NULL, &error ); g_assert_no_error( error ); g_assert_nonnull( second_tool_task ); first_background_task = tool_task_get_background_task( first_tool_task ); second_background_task = tool_task_get_background_task( second_tool_task ); concurrent_context.main_loop = g_main_loop_new( NULL, FALSE ); concurrent_context.first_task = first_background_task; concurrent_context.second_task = second_background_task; concurrent_context.timeout_source_id = g_timeout_add_seconds( 5, test_tool_task_concurrent_timeout, &concurrent_context ); g_assert_true( tool_task_start( first_tool_task, test_tool_task_on_concurrent_completed, &concurrent_context, NULL, &error ) ); g_assert_no_error( error ); g_assert_true( tool_task_start( second_tool_task, test_tool_task_on_concurrent_completed, &concurrent_context, NULL, &error ) ); g_assert_no_error( error ); g_main_loop_run( concurrent_context.main_loop ); if (concurrent_context.timeout_source_id != 0) { g_source_remove( concurrent_context.timeout_source_id ); } g_main_loop_unref( concurrent_context.main_loop ); concurrent_context.main_loop = NULL; g_assert_false( concurrent_context.timed_out ); g_assert_cmpuint( concurrent_context.completed_count, ==, 2 ); g_assert_cmpint( background_task_get_state( first_background_task ), ==, BACKGROUND_TASK_STATE_COMPLETED ); g_assert_cmpint( background_task_get_state( second_background_task ), ==, BACKGROUND_TASK_STATE_COMPLETED ); first_task_result = tool_task_result_from_background_task( first_background_task ); second_task_result = tool_task_result_from_background_task( second_background_task ); g_assert_nonnull( first_task_result ); g_assert_nonnull( second_task_result ); g_assert_cmpstr( tool_task_result_get_argument( first_task_result, 0 ), ==, "premiere-tache" ); g_assert_cmpstr( tool_task_result_get_argument( second_task_result, 0 ), ==, "seconde-tache" ); first_process_result = tool_task_result_get_process_result( first_task_result ); second_process_result = tool_task_result_get_process_result( second_task_result ); first_stdout = tool_process_result_ref_stdout( first_process_result ); second_stdout = tool_process_result_ref_stdout( second_process_result ); test_tool_task_assert_bytes_equal_text( first_stdout, "premiere-tache" ); test_tool_task_assert_bytes_equal_text( second_stdout, "seconde-tache" ); g_bytes_unref( first_stdout ); g_bytes_unref( second_stdout ); tool_task_free( first_tool_task ); tool_task_free( second_tool_task ); tool_registry_free( tool_registry ); } int main( int argc, char **argv ) { GError *error = NULL; int test_result = 0; g_test_init( &argc, &argv, NULL ); test_tool_task_shared_directory = g_dir_make_tmp( "labfy-tool-task-XXXXXX", &error ); g_assert_no_error( error ); g_assert_nonnull( test_tool_task_shared_directory ); g_assert_true( g_setenv( "PATH", test_tool_task_shared_directory, TRUE ) ); g_test_add( "/tool_task/invalid_arguments", ToolTaskFixture, NULL, test_tool_task_fixture_setup, test_tool_task_invalid_arguments, test_tool_task_fixture_teardown ); g_test_add( "/tool_task/tool_not_found", ToolTaskFixture, NULL, test_tool_task_fixture_setup, test_tool_task_tool_not_found, test_tool_task_fixture_teardown ); g_test_add( "/tool_task/tool_not_checked", ToolTaskFixture, NULL, test_tool_task_fixture_setup, test_tool_task_tool_not_checked, test_tool_task_fixture_teardown ); g_test_add( "/tool_task/tool_missing", ToolTaskFixture, NULL, test_tool_task_fixture_setup, test_tool_task_tool_missing, test_tool_task_fixture_teardown ); g_test_add( "/tool_task/create", ToolTaskFixture, NULL, test_tool_task_fixture_setup, test_tool_task_create, test_tool_task_fixture_teardown ); g_test_add( "/tool_task/argument_ownership", ToolTaskFixture, NULL, test_tool_task_fixture_setup, test_tool_task_argument_ownership, test_tool_task_fixture_teardown ); g_test_add( "/tool_task/success", ToolTaskFixture, NULL, test_tool_task_fixture_setup, test_tool_task_success, test_tool_task_fixture_teardown ); g_test_add( "/tool_task/nonzero_exit", ToolTaskFixture, NULL, test_tool_task_fixture_setup, test_tool_task_nonzero_exit, test_tool_task_fixture_teardown ); g_test_add( "/tool_task/spawn_failure", ToolTaskFixture, NULL, test_tool_task_fixture_setup, test_tool_task_spawn_failure, test_tool_task_fixture_teardown ); g_test_add( "/tool_task/working_directory", ToolTaskFixture, NULL, test_tool_task_fixture_setup, test_tool_task_working_directory, test_tool_task_fixture_teardown ); g_test_add( "/tool_task/cancellation", ToolTaskFixture, NULL, test_tool_task_fixture_setup, test_tool_task_cancellation, test_tool_task_fixture_teardown ); g_test_add( "/tool_task/concurrent_tasks", ToolTaskFixture, NULL, test_tool_task_fixture_setup, test_tool_task_concurrent_tasks, test_tool_task_fixture_teardown ); test_result = g_test_run(); g_rmdir( test_tool_task_shared_directory ); g_clear_pointer( &test_tool_task_shared_directory, g_free ); return test_result; }