labfy-investigation/tests/test_investigation_graph_load_task.c
2026-07-21 09:39:52 +02:00

1878 lines
37 KiB
C

/******************************************************************************
* @file test_investigation_graph_load_task.c
* @brief Tests du chargement asynchrone du graphe d'enquête.
******************************************************************************/
#include "core/investigation_graph_load_task.h"
#include "core/investigation_graph_load_task_test.h"
#include "dao/entity_dao.h"
#include "dao/relation_dao.h"
#include "database/database.h"
#include "models/entity_record.h"
#include "models/investigation_graph_model.h"
#include "models/relation_record.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <glib.h>
#include <glib/gstdio.h>
#define TEST_ENTITY_A_IDENTIFIER \
"10000000-0000-4000-8000-000000000001"
#define TEST_ENTITY_B_IDENTIFIER \
"20000000-0000-4000-8000-000000000001"
#define TEST_ENTITY_C_IDENTIFIER \
"30000000-0000-4000-8000-000000000001"
#define TEST_RELATION_AB_IDENTIFIER \
"40000000-0000-4000-8000-000000000001"
#define TEST_RELATION_AC_IDENTIFIER \
"40000000-0000-4000-8000-000000000002"
#define TEST_RELATION_CA_IDENTIFIER \
"40000000-0000-4000-8000-000000000003"
/**
* @brief Base SQLite temporaire utilisée par les tests.
*/
typedef struct
{
char *temporary_directory;
char *database_path;
Database *database;
EntityDao *entity_dao;
RelationDao *relation_dao;
} TestGraphLoadTaskFixture;
/**
* @brief Résultats observés depuis le callback asynchrone.
*/
typedef struct
{
GMainLoop *main_loop;
GThread *main_thread;
guint callback_count;
guint destroy_count;
gboolean timed_out;
InvestigationGraphModel *graph_model;
GError *error;
} TestGraphLoadTaskObservation;
/**
* @brief Données possédées par une exécution asynchrone.
*/
typedef struct
{
TestGraphLoadTaskObservation *observation;
} TestGraphLoadTaskCallbackData;
/**
* @brief Vérifie le domaine et le code d'une erreur publique.
*/
static void test_graph_load_task_assert_error(
const GError *error,
InvestigationGraphLoadTaskError expected_code
)
{
assert(error != NULL);
assert(
error->domain ==
INVESTIGATION_GRAPH_LOAD_TASK_ERROR
);
assert(
error->code ==
(gint) expected_code
);
assert(error->message != NULL);
assert(error->message[0] != '\0');
}
/**
* @brief Supprime un fichier temporaire s'il existe.
*/
static void test_graph_load_task_remove_optional_file(
const char *path
)
{
if (path == NULL)
{
return;
}
if (g_file_test(
path,
G_FILE_TEST_EXISTS
))
{
assert(
g_remove(
path
) == 0
);
}
}
/**
* @brief Crée une fixture SQLite réelle.
*/
static TestGraphLoadTaskFixture
test_graph_load_task_fixture_create(void)
{
TestGraphLoadTaskFixture fixture =
{0};
GError *error =
NULL;
fixture.temporary_directory =
g_dir_make_tmp(
"labfy-graph-load-task-test-XXXXXX",
&error
);
assert(fixture.temporary_directory != NULL);
assert(error == NULL);
fixture.database_path =
g_build_filename(
fixture.temporary_directory,
"Enquete.sqlite",
NULL
);
assert(fixture.database_path != NULL);
assert(
database_initialize(
fixture.database_path,
"Enquete_GraphLoadTask",
fixture.temporary_directory
)
);
fixture.database =
database_open(
fixture.database_path
);
assert(fixture.database != NULL);
fixture.entity_dao =
entity_dao_new(
fixture.database,
&error
);
assert(fixture.entity_dao != NULL);
assert(error == NULL);
fixture.relation_dao =
relation_dao_new(
fixture.database,
&error
);
assert(fixture.relation_dao != NULL);
assert(error == NULL);
return fixture;
}
/**
* @brief Ferme les objets d'écriture avant le démarrage du worker.
*/
static void test_graph_load_task_fixture_close_database(
TestGraphLoadTaskFixture *fixture
)
{
assert(fixture != NULL);
if (fixture->relation_dao != NULL)
{
relation_dao_free(
fixture->relation_dao
);
fixture->relation_dao =
NULL;
}
if (fixture->entity_dao != NULL)
{
entity_dao_free(
fixture->entity_dao
);
fixture->entity_dao =
NULL;
}
if (fixture->database != NULL)
{
database_close(
fixture->database
);
fixture->database =
NULL;
}
}
/**
* @brief Détruit la fixture et ses éventuels fichiers SQLite auxiliaires.
*/
static void test_graph_load_task_fixture_clear(
TestGraphLoadTaskFixture *fixture
)
{
char *write_ahead_log_path =
NULL;
char *shared_memory_path =
NULL;
char *journal_path =
NULL;
assert(fixture != NULL);
test_graph_load_task_fixture_close_database(
fixture
);
write_ahead_log_path =
g_strconcat(
fixture->database_path,
"-wal",
NULL
);
shared_memory_path =
g_strconcat(
fixture->database_path,
"-shm",
NULL
);
journal_path =
g_strconcat(
fixture->database_path,
"-journal",
NULL
);
test_graph_load_task_remove_optional_file(
write_ahead_log_path
);
test_graph_load_task_remove_optional_file(
shared_memory_path
);
test_graph_load_task_remove_optional_file(
journal_path
);
test_graph_load_task_remove_optional_file(
fixture->database_path
);
assert(
g_rmdir(
fixture->temporary_directory
) == 0
);
g_free(
journal_path
);
g_free(
shared_memory_path
);
g_free(
write_ahead_log_path
);
g_free(
fixture->database_path
);
g_free(
fixture->temporary_directory
);
memset(
fixture,
0,
sizeof(*fixture)
);
}
/**
* @brief Crée une entité valide.
*/
static EntityRecord *test_graph_load_task_create_entity(
const char *identifier,
const char *value
)
{
EntityRecord *entity_record =
NULL;
GError *error =
NULL;
entity_record =
entity_record_new(
identifier,
"person",
value,
NULL,
NULL,
80,
"2026-07-21T22:00:00Z",
"2026-07-21T22:00:00Z",
ENTITY_STATUS_ACTIVE,
&error
);
assert(entity_record != NULL);
assert(error == NULL);
return entity_record;
}
/**
* @brief Insère une entité avec le DAO réel.
*/
static void test_graph_load_task_insert_entity(
TestGraphLoadTaskFixture *fixture,
const char *identifier,
const char *value
)
{
EntityRecord *entity_record =
NULL;
GError *error =
NULL;
assert(fixture != NULL);
assert(fixture->entity_dao != NULL);
entity_record =
test_graph_load_task_create_entity(
identifier,
value
);
assert(
entity_dao_insert(
fixture->entity_dao,
entity_record,
&error
)
);
assert(error == NULL);
entity_record_free(
entity_record
);
}
/**
* @brief Crée une relation valide.
*/
static RelationRecord *test_graph_load_task_create_relation(
const char *identifier,
const char *source_identifier,
const char *target_identifier,
const char *relation_type
)
{
RelationRecord *relation_record =
NULL;
GError *error =
NULL;
relation_record =
relation_record_new(
identifier,
source_identifier,
target_identifier,
relation_type,
NULL,
"Relation asynchrone de test.",
75,
"2026-07-21T22:10:00Z",
"2026-07-21T22:10:00Z",
RELATION_STATUS_ACTIVE,
&error
);
assert(relation_record != NULL);
assert(error == NULL);
return relation_record;
}
/**
* @brief Insère une relation avec le DAO réel.
*/
static void test_graph_load_task_insert_relation(
TestGraphLoadTaskFixture *fixture,
const char *identifier,
const char *source_identifier,
const char *target_identifier,
const char *relation_type
)
{
RelationRecord *relation_record =
NULL;
GError *error =
NULL;
assert(fixture != NULL);
assert(fixture->relation_dao != NULL);
relation_record =
test_graph_load_task_create_relation(
identifier,
source_identifier,
target_identifier,
relation_type
);
assert(
relation_dao_insert(
fixture->relation_dao,
relation_record,
&error
)
);
assert(error == NULL);
relation_record_free(
relation_record
);
}
/**
* @brief Insère le graphe standard utilisé par plusieurs tests.
*/
static void test_graph_load_task_insert_standard_graph(
TestGraphLoadTaskFixture *fixture
)
{
test_graph_load_task_insert_entity(
fixture,
TEST_ENTITY_A_IDENTIFIER,
"Personne A"
);
test_graph_load_task_insert_entity(
fixture,
TEST_ENTITY_B_IDENTIFIER,
"Personne B"
);
test_graph_load_task_insert_entity(
fixture,
TEST_ENTITY_C_IDENTIFIER,
"Personne C"
);
test_graph_load_task_insert_relation(
fixture,
TEST_RELATION_AB_IDENTIFIER,
TEST_ENTITY_A_IDENTIFIER,
TEST_ENTITY_B_IDENTIFIER,
"uses"
);
test_graph_load_task_insert_relation(
fixture,
TEST_RELATION_AC_IDENTIFIER,
TEST_ENTITY_A_IDENTIFIER,
TEST_ENTITY_C_IDENTIFIER,
"controls"
);
test_graph_load_task_insert_relation(
fixture,
TEST_RELATION_CA_IDENTIFIER,
TEST_ENTITY_C_IDENTIFIER,
TEST_ENTITY_A_IDENTIFIER,
"knows"
);
}
/**
* @brief Initialise une observation asynchrone.
*/
static TestGraphLoadTaskObservation
test_graph_load_task_observation_create(void)
{
TestGraphLoadTaskObservation observation =
{0};
observation.main_loop =
g_main_loop_new(
NULL,
FALSE
);
assert(observation.main_loop != NULL);
observation.main_thread =
g_thread_self();
return observation;
}
/**
* @brief Libère le résultat d'une observation.
*/
static void test_graph_load_task_observation_clear(
TestGraphLoadTaskObservation *observation
)
{
assert(observation != NULL);
investigation_graph_model_free(
observation->graph_model
);
g_clear_error(
&observation->error
);
g_main_loop_unref(
observation->main_loop
);
memset(
observation,
0,
sizeof(*observation)
);
}
/**
* @brief Copie le résultat final reçu sur le thread principal.
*/
static void test_graph_load_task_callback(
InvestigationGraphLoadTask *load_task,
InvestigationGraphModel *graph_model,
const GError *error,
gpointer user_data
)
{
TestGraphLoadTaskCallbackData *callback_data =
user_data;
TestGraphLoadTaskObservation *observation =
NULL;
assert(load_task != NULL);
assert(callback_data != NULL);
assert(callback_data->observation != NULL);
observation =
callback_data->observation;
assert(
g_thread_self() ==
observation->main_thread
);
observation->callback_count++;
assert(
observation->callback_count ==
1
);
assert(
(graph_model != NULL && error == NULL) ||
(graph_model == NULL && error != NULL)
);
observation->graph_model =
graph_model;
if (error != NULL)
{
observation->error =
g_error_copy(
error
);
}
g_main_loop_quit(
observation->main_loop
);
}
/**
* @brief Vérifie la destruction unique des données utilisateur.
*/
static void test_graph_load_task_callback_data_free(
gpointer user_data
)
{
TestGraphLoadTaskCallbackData *callback_data =
user_data;
assert(callback_data != NULL);
assert(callback_data->observation != NULL);
callback_data->observation->destroy_count++;
g_free(
callback_data
);
}
/**
* @brief Démarre une tâche avec une observation possédée.
*/
static void test_graph_load_task_start_or_abort(
InvestigationGraphLoadTask *load_task,
TestGraphLoadTaskObservation *observation
)
{
TestGraphLoadTaskCallbackData *callback_data =
NULL;
GError *error =
NULL;
callback_data =
g_new0(
TestGraphLoadTaskCallbackData,
1
);
callback_data->observation =
observation;
assert(
investigation_graph_load_task_start(
load_task,
test_graph_load_task_callback,
callback_data,
test_graph_load_task_callback_data_free,
&error
)
);
assert(error == NULL);
}
/**
* @brief Source de sécurité arrêtant une attente anormalement longue.
*/
static gboolean test_graph_load_task_timeout(
gpointer user_data
)
{
TestGraphLoadTaskObservation *observation =
user_data;
assert(observation != NULL);
observation->timed_out =
TRUE;
g_main_loop_quit(
observation->main_loop
);
return G_SOURCE_REMOVE;
}
/**
* @brief Attend le callback final avec un timeout uniquement défensif.
*/
static void test_graph_load_task_wait_for_callback(
TestGraphLoadTaskObservation *observation
)
{
guint timeout_source =
0;
assert(observation != NULL);
timeout_source =
g_timeout_add_seconds(
5,
test_graph_load_task_timeout,
observation
);
assert(timeout_source != 0);
if (observation->callback_count == 0)
{
g_main_loop_run(
observation->main_loop
);
}
if (!observation->timed_out)
{
assert(
g_source_remove(
timeout_source
)
);
}
assert(!observation->timed_out);
assert(observation->callback_count == 1);
assert(observation->destroy_count == 1);
}
/**
* @brief Exécute une source idle pour vérifier la réactivité du contexte.
*/
static gboolean test_graph_load_task_mark_idle(
gpointer user_data
)
{
gboolean *idle_executed =
user_data;
assert(idle_executed != NULL);
*idle_executed =
TRUE;
return G_SOURCE_REMOVE;
}
/**
* @brief Attend la finalisation interne d'une tâche détruite.
*/
static void test_graph_load_task_wait_for_internal_completion(void)
{
gint64 deadline =
g_get_monotonic_time() +
(5 * G_TIME_SPAN_SECOND);
while (investigation_graph_load_task_test_get_completion_count() == 0 &&
g_get_monotonic_time() < deadline)
{
g_main_context_iteration(
NULL,
TRUE
);
}
assert(
investigation_graph_load_task_test_get_completion_count() ==
1
);
}
/**
* @brief Vérifie la construction, les arguments et l'état initial.
*/
static void test_graph_load_task_lifecycle(void)
{
InvestigationGraphLoadTask *load_task =
NULL;
GError *error =
NULL;
load_task =
investigation_graph_load_task_new(
NULL,
&error
);
assert(load_task == NULL);
test_graph_load_task_assert_error(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_INVALID_ARGUMENT
);
g_clear_error(
&error
);
load_task =
investigation_graph_load_task_new(
"",
NULL
);
assert(load_task == NULL);
load_task =
investigation_graph_load_task_new(
"/tmp/labfy-unused.sqlite",
&error
);
assert(load_task != NULL);
assert(error == NULL);
assert(
!investigation_graph_load_task_is_running(
load_task
)
);
assert(
!investigation_graph_load_task_is_running(
NULL
)
);
assert(
!investigation_graph_load_task_start(
load_task,
NULL,
NULL,
NULL,
&error
)
);
test_graph_load_task_assert_error(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_INVALID_ARGUMENT
);
g_clear_error(
&error
);
investigation_graph_load_task_cancel(
NULL
);
investigation_graph_load_task_free(
load_task
);
investigation_graph_load_task_free(
NULL
);
}
/**
* @brief Vérifie une base vide, l'état actif et l'absence de blocage.
*/
static void test_graph_load_task_empty_database(void)
{
TestGraphLoadTaskFixture fixture =
test_graph_load_task_fixture_create();
InvestigationGraphLoadTask *load_task =
NULL;
TestGraphLoadTaskObservation observation =
test_graph_load_task_observation_create();
gboolean idle_executed =
FALSE;
GError *error =
NULL;
test_graph_load_task_fixture_close_database(
&fixture
);
investigation_graph_load_task_test_reset();
investigation_graph_load_task_test_pause_before_open();
load_task =
investigation_graph_load_task_new(
fixture.database_path,
&error
);
assert(load_task != NULL);
assert(error == NULL);
test_graph_load_task_start_or_abort(
load_task,
&observation
);
investigation_graph_load_task_test_wait_until_before_open();
assert(
investigation_graph_load_task_is_running(
load_task
)
);
assert(
g_idle_add(
test_graph_load_task_mark_idle,
&idle_executed
) != 0
);
while (!idle_executed)
{
g_main_context_iteration(
NULL,
TRUE
);
}
assert(idle_executed);
assert(
!investigation_graph_load_task_start(
load_task,
test_graph_load_task_callback,
NULL,
NULL,
&error
)
);
test_graph_load_task_assert_error(
error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_ALREADY_RUNNING
);
g_clear_error(
&error
);
investigation_graph_load_task_test_release_before_open();
test_graph_load_task_wait_for_callback(
&observation
);
assert(observation.error == NULL);
assert(observation.graph_model != NULL);
assert(
investigation_graph_model_get_entity_count(
observation.graph_model
) == 0
);
assert(
investigation_graph_model_get_relation_count(
observation.graph_model
) == 0
);
assert(
!investigation_graph_load_task_is_running(
load_task
)
);
investigation_graph_load_task_free(
load_task
);
test_graph_load_task_observation_clear(
&observation
);
test_graph_load_task_fixture_clear(
&fixture
);
}
/**
* @brief Vérifie le chargement complet du graphe et de ses index.
*/
static void test_graph_load_task_complete_graph(void)
{
TestGraphLoadTaskFixture fixture =
test_graph_load_task_fixture_create();
InvestigationGraphLoadTask *load_task =
NULL;
TestGraphLoadTaskObservation observation =
test_graph_load_task_observation_create();
const RelationRecord *relation_ab =
NULL;
GPtrArray *outgoing_relations =
NULL;
GPtrArray *incoming_relations =
NULL;
GError *error =
NULL;
test_graph_load_task_insert_standard_graph(
&fixture
);
test_graph_load_task_fixture_close_database(
&fixture
);
investigation_graph_load_task_test_reset();
load_task =
investigation_graph_load_task_new(
fixture.database_path,
&error
);
assert(load_task != NULL);
assert(error == NULL);
test_graph_load_task_start_or_abort(
load_task,
&observation
);
test_graph_load_task_wait_for_callback(
&observation
);
assert(observation.error == NULL);
assert(observation.graph_model != NULL);
assert(
investigation_graph_model_get_entity_count(
observation.graph_model
) == 3
);
assert(
investigation_graph_model_get_relation_count(
observation.graph_model
) == 3
);
relation_ab =
investigation_graph_model_find_relation(
observation.graph_model,
TEST_RELATION_AB_IDENTIFIER
);
assert(relation_ab != NULL);
assert(
strcmp(
relation_record_get_source_entity_identifier(
relation_ab
),
TEST_ENTITY_A_IDENTIFIER
) == 0
);
assert(
strcmp(
relation_record_get_target_entity_identifier(
relation_ab
),
TEST_ENTITY_B_IDENTIFIER
) == 0
);
outgoing_relations =
investigation_graph_model_list_outgoing_relations(
observation.graph_model,
TEST_ENTITY_A_IDENTIFIER,
&error
);
assert(outgoing_relations != NULL);
assert(error == NULL);
assert(outgoing_relations->len == 2);
incoming_relations =
investigation_graph_model_list_incoming_relations(
observation.graph_model,
TEST_ENTITY_A_IDENTIFIER,
&error
);
assert(incoming_relations != NULL);
assert(error == NULL);
assert(incoming_relations->len == 1);
g_ptr_array_unref(
incoming_relations
);
g_ptr_array_unref(
outgoing_relations
);
investigation_graph_load_task_free(
load_task
);
test_graph_load_task_observation_clear(
&observation
);
test_graph_load_task_fixture_clear(
&fixture
);
}
/**
* @brief Vérifie l'annulation avant l'ouverture SQLite.
*/
static void test_graph_load_task_cancel_before_open(void)
{
TestGraphLoadTaskFixture fixture =
test_graph_load_task_fixture_create();
InvestigationGraphLoadTask *load_task =
NULL;
TestGraphLoadTaskObservation observation =
test_graph_load_task_observation_create();
GError *error =
NULL;
test_graph_load_task_fixture_close_database(
&fixture
);
investigation_graph_load_task_test_reset();
investigation_graph_load_task_test_pause_before_open();
load_task =
investigation_graph_load_task_new(
fixture.database_path,
&error
);
assert(load_task != NULL);
assert(error == NULL);
test_graph_load_task_start_or_abort(
load_task,
&observation
);
investigation_graph_load_task_test_wait_until_before_open();
investigation_graph_load_task_cancel(
load_task
);
investigation_graph_load_task_cancel(
load_task
);
investigation_graph_load_task_test_release_before_open();
test_graph_load_task_wait_for_callback(
&observation
);
assert(observation.graph_model == NULL);
test_graph_load_task_assert_error(
observation.error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_CANCELLED
);
investigation_graph_load_task_free(
load_task
);
test_graph_load_task_observation_clear(
&observation
);
test_graph_load_task_fixture_clear(
&fixture
);
}
/**
* @brief Vérifie l'annulation après l'ouverture de la connexion du worker.
*/
static void test_graph_load_task_cancel_after_open(void)
{
TestGraphLoadTaskFixture fixture =
test_graph_load_task_fixture_create();
InvestigationGraphLoadTask *load_task =
NULL;
TestGraphLoadTaskObservation observation =
test_graph_load_task_observation_create();
GError *error =
NULL;
test_graph_load_task_fixture_close_database(
&fixture
);
investigation_graph_load_task_test_reset();
investigation_graph_load_task_test_pause_after_open();
load_task =
investigation_graph_load_task_new(
fixture.database_path,
&error
);
assert(load_task != NULL);
assert(error == NULL);
test_graph_load_task_start_or_abort(
load_task,
&observation
);
investigation_graph_load_task_test_wait_until_after_open();
investigation_graph_load_task_cancel(
load_task
);
investigation_graph_load_task_test_release_after_open();
test_graph_load_task_wait_for_callback(
&observation
);
assert(observation.graph_model == NULL);
test_graph_load_task_assert_error(
observation.error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_CANCELLED
);
investigation_graph_load_task_free(
load_task
);
test_graph_load_task_observation_clear(
&observation
);
test_graph_load_task_fixture_clear(
&fixture
);
}
/**
* @brief Vérifie qu'une destruction active supprime le callback utilisateur.
*/
static void test_graph_load_task_free_while_running(void)
{
TestGraphLoadTaskFixture fixture =
test_graph_load_task_fixture_create();
InvestigationGraphLoadTask *load_task =
NULL;
TestGraphLoadTaskObservation observation =
test_graph_load_task_observation_create();
GError *error =
NULL;
test_graph_load_task_fixture_close_database(
&fixture
);
investigation_graph_load_task_test_reset();
investigation_graph_load_task_test_pause_before_open();
load_task =
investigation_graph_load_task_new(
fixture.database_path,
&error
);
assert(load_task != NULL);
assert(error == NULL);
test_graph_load_task_start_or_abort(
load_task,
&observation
);
investigation_graph_load_task_test_wait_until_before_open();
investigation_graph_load_task_free(
load_task
);
load_task =
NULL;
assert(observation.callback_count == 0);
assert(observation.destroy_count == 1);
investigation_graph_load_task_test_release_before_open();
test_graph_load_task_wait_for_internal_completion();
assert(observation.callback_count == 0);
assert(observation.destroy_count == 1);
assert(observation.graph_model == NULL);
assert(observation.error == NULL);
test_graph_load_task_observation_clear(
&observation
);
test_graph_load_task_fixture_clear(
&fixture
);
}
/**
* @brief Vérifie la propagation d'un échec d'ouverture SQLite.
*/
static void test_graph_load_task_database_open_failure(void)
{
char *temporary_directory =
NULL;
char *missing_directory =
NULL;
char *database_path =
NULL;
InvestigationGraphLoadTask *load_task =
NULL;
TestGraphLoadTaskObservation observation =
test_graph_load_task_observation_create();
GError *error =
NULL;
temporary_directory =
g_dir_make_tmp(
"labfy-graph-open-failure-test-XXXXXX",
&error
);
assert(temporary_directory != NULL);
assert(error == NULL);
missing_directory =
g_build_filename(
temporary_directory,
"repertoire-absent",
NULL
);
database_path =
g_build_filename(
missing_directory,
"Enquete.sqlite",
NULL
);
investigation_graph_load_task_test_reset();
load_task =
investigation_graph_load_task_new(
database_path,
&error
);
assert(load_task != NULL);
assert(error == NULL);
test_graph_load_task_start_or_abort(
load_task,
&observation
);
test_graph_load_task_wait_for_callback(
&observation
);
assert(observation.graph_model == NULL);
test_graph_load_task_assert_error(
observation.error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_DATABASE_OPEN
);
assert(
strstr(
observation.error->message,
database_path
) != NULL
);
investigation_graph_load_task_free(
load_task
);
test_graph_load_task_observation_clear(
&observation
);
assert(
g_rmdir(
temporary_directory
) == 0
);
g_free(
database_path
);
g_free(
missing_directory
);
g_free(
temporary_directory
);
}
/**
* @brief Vérifie la propagation d'un échec provenant du chargeur synchrone.
*/
static void test_graph_load_task_load_failure(void)
{
char *temporary_directory =
NULL;
char *database_path =
NULL;
InvestigationGraphLoadTask *load_task =
NULL;
TestGraphLoadTaskObservation observation =
test_graph_load_task_observation_create();
GError *error =
NULL;
temporary_directory =
g_dir_make_tmp(
"labfy-graph-load-failure-test-XXXXXX",
&error
);
assert(temporary_directory != NULL);
assert(error == NULL);
database_path =
g_build_filename(
temporary_directory,
"SansSchema.sqlite",
NULL
);
assert(
g_file_set_contents(
database_path,
"",
0,
&error
)
);
assert(error == NULL);
investigation_graph_load_task_test_reset();
load_task =
investigation_graph_load_task_new(
database_path,
&error
);
assert(load_task != NULL);
assert(error == NULL);
test_graph_load_task_start_or_abort(
load_task,
&observation
);
test_graph_load_task_wait_for_callback(
&observation
);
assert(observation.graph_model == NULL);
test_graph_load_task_assert_error(
observation.error,
INVESTIGATION_GRAPH_LOAD_TASK_ERROR_LOAD
);
assert(
strstr(
observation.error->message,
"graphe"
) != NULL
);
investigation_graph_load_task_free(
load_task
);
test_graph_load_task_observation_clear(
&observation
);
test_graph_load_task_remove_optional_file(
database_path
);
assert(
g_rmdir(
temporary_directory
) == 0
);
g_free(
database_path
);
g_free(
temporary_directory
);
}
/**
* @brief Vérifie la réutilisation après une première finalisation.
*/
static void test_graph_load_task_reuse(void)
{
TestGraphLoadTaskFixture fixture =
test_graph_load_task_fixture_create();
InvestigationGraphLoadTask *load_task =
NULL;
TestGraphLoadTaskObservation first_observation =
test_graph_load_task_observation_create();
TestGraphLoadTaskObservation second_observation =
test_graph_load_task_observation_create();
GError *error =
NULL;
test_graph_load_task_insert_entity(
&fixture,
TEST_ENTITY_A_IDENTIFIER,
"Personne A"
);
test_graph_load_task_fixture_close_database(
&fixture
);
investigation_graph_load_task_test_reset();
load_task =
investigation_graph_load_task_new(
fixture.database_path,
&error
);
assert(load_task != NULL);
assert(error == NULL);
test_graph_load_task_start_or_abort(
load_task,
&first_observation
);
test_graph_load_task_wait_for_callback(
&first_observation
);
assert(first_observation.graph_model != NULL);
assert(first_observation.error == NULL);
assert(
!investigation_graph_load_task_is_running(
load_task
)
);
test_graph_load_task_start_or_abort(
load_task,
&second_observation
);
test_graph_load_task_wait_for_callback(
&second_observation
);
assert(second_observation.graph_model != NULL);
assert(second_observation.error == NULL);
assert(
first_observation.graph_model !=
second_observation.graph_model
);
assert(
investigation_graph_model_find_entity(
first_observation.graph_model,
TEST_ENTITY_A_IDENTIFIER
) !=
investigation_graph_model_find_entity(
second_observation.graph_model,
TEST_ENTITY_A_IDENTIFIER
)
);
investigation_graph_load_task_free(
load_task
);
test_graph_load_task_observation_clear(
&second_observation
);
test_graph_load_task_observation_clear(
&first_observation
);
test_graph_load_task_fixture_clear(
&fixture
);
}
/**
* @brief Vérifie deux tâches simultanées et deux connexions indépendantes.
*/
static void test_graph_load_task_two_simultaneous_tasks(void)
{
TestGraphLoadTaskFixture fixture =
test_graph_load_task_fixture_create();
InvestigationGraphLoadTask *first_task =
NULL;
InvestigationGraphLoadTask *second_task =
NULL;
TestGraphLoadTaskObservation first_observation =
{0};
TestGraphLoadTaskObservation second_observation =
{0};
GMainLoop *shared_main_loop =
NULL;
guint timeout_source =
0;
GError *error =
NULL;
test_graph_load_task_insert_standard_graph(
&fixture
);
test_graph_load_task_fixture_close_database(
&fixture
);
shared_main_loop =
g_main_loop_new(
NULL,
FALSE
);
assert(shared_main_loop != NULL);
first_observation.main_loop =
shared_main_loop;
first_observation.main_thread =
g_thread_self();
second_observation.main_loop =
shared_main_loop;
second_observation.main_thread =
g_thread_self();
investigation_graph_load_task_test_reset();
first_task =
investigation_graph_load_task_new(
fixture.database_path,
&error
);
assert(first_task != NULL);
assert(error == NULL);
second_task =
investigation_graph_load_task_new(
fixture.database_path,
&error
);
assert(second_task != NULL);
assert(error == NULL);
test_graph_load_task_start_or_abort(
first_task,
&first_observation
);
test_graph_load_task_start_or_abort(
second_task,
&second_observation
);
timeout_source =
g_timeout_add_seconds(
5,
test_graph_load_task_timeout,
&first_observation
);
assert(timeout_source != 0);
while (first_observation.callback_count == 0 ||
second_observation.callback_count == 0)
{
g_main_loop_run(
shared_main_loop
);
assert(!first_observation.timed_out);
}
assert(
g_source_remove(
timeout_source
)
);
assert(first_observation.destroy_count == 1);
assert(second_observation.destroy_count == 1);
assert(first_observation.error == NULL);
assert(second_observation.error == NULL);
assert(first_observation.graph_model != NULL);
assert(second_observation.graph_model != NULL);
assert(
first_observation.graph_model !=
second_observation.graph_model
);
investigation_graph_load_task_free(
second_task
);
investigation_graph_load_task_free(
first_task
);
investigation_graph_model_free(
second_observation.graph_model
);
investigation_graph_model_free(
first_observation.graph_model
);
g_clear_error(
&second_observation.error
);
g_clear_error(
&first_observation.error
);
g_main_loop_unref(
shared_main_loop
);
test_graph_load_task_fixture_clear(
&fixture
);
}
int main(
int argc,
char **argv
)
{
g_test_init(
&argc,
&argv,
NULL
);
/*
* Certains scénarios provoquent volontairement des avertissements de la
* couche SQLite. Ils ne doivent pas masquer les erreurs structurées
* vérifiées par les tests.
*/
g_log_set_always_fatal(
G_LOG_FATAL_MASK
);
test_graph_load_task_lifecycle();
test_graph_load_task_empty_database();
test_graph_load_task_complete_graph();
test_graph_load_task_cancel_before_open();
test_graph_load_task_cancel_after_open();
test_graph_load_task_free_while_running();
test_graph_load_task_database_open_failure();
test_graph_load_task_load_failure();
test_graph_load_task_reuse();
test_graph_load_task_two_simultaneous_tasks();
printf(
"InvestigationGraphLoadTask : tous les tests sont valides.\n"
);
return 0;
}