lardon3d/tests/test_candidate_pair_task.c
2026-08-09 00:34:51 +02:00

294 lines
12 KiB
C

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <sched.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <lardon3d/candidate_pair_gen.h>
#include <lardon3d/candidate_pair_task.h>
#include <lardon3d/feature_store.h>
#include <lardon3d/feature_task.h>
#include <lardon3d/image_catalog.h>
#include <lardon3d/project.h>
#include <lardon3d/sift_task.h>
#include <lardon3d/task_queue.h>
#include <lardon3d/visual_index.h>
#include <lardon3d/visual_index_task.h>
#define CHECK(x) \
do { \
if (!(x)) { \
fprintf(stderr, "Echec ligne %d: %s\n", __LINE__, #x); \
return false; \
} \
} while (0)
static bool join_path(char out[PATH_MAX], const char *a, const char *b) {
int n = snprintf(out, PATH_MAX, "%s/%s", a, b);
return n > 0 && (size_t)n < PATH_MAX;
}
static bool remove_tree(const char *p) {
struct stat s;
if (lstat(p, &s) != 0) return errno == ENOENT;
if (!S_ISDIR(s.st_mode)) return unlink(p) == 0;
DIR *d = opendir(p);
if (!d) return false;
bool ok = true;
for (struct dirent *e = readdir(d); e; e = readdir(d)) {
if (!strcmp(e->d_name, ".") || !strcmp(e->d_name, "..")) continue;
char c[PATH_MAX];
if (!join_path(c, p, e->d_name) || !remove_tree(c)) ok = false;
}
if (closedir(d) || rmdir(p)) ok = false;
return ok;
}
static bool write_pgm(const char *p, unsigned seed) {
FILE *f = fopen(p, "wb");
if (!f) return false;
fprintf(f, "P5\n192 192\n255\n");
for (unsigned y = 0; y < 192; y++) {
for (unsigned x = 0; x < 192; x++) {
unsigned base = ((((x / 12) ^ (y / 12)) & 1U) != 0U) ? 205U : 35U;
unsigned texture =
((x * 17U + y * 29U + seed * 31U + (x * y) % 37U) % 43U);
int dx = (int)x - 96, dy = (int)y - 96;
unsigned ring =
(dx * dx + dy * dy > 32 * 32 &&
dx * dx + dy * dy < 42 * 42) ? 45U : 0U;
unsigned char v = (unsigned char)(base + texture + ring > 255U
? 255U
: base + texture + ring);
if (fwrite(&v, 1, 1, f) != 1) {
fclose(f);
return false;
}
}
}
return fclose(f) == 0;
}
static bool runtime(Lardon3DAppState *s) {
s->hardware_profile = (Lardon3DHardwareProfile){
.logical_cpu_count = 64,
.page_size_bytes = 4096,
.memory_total_bytes = UINT64_MAX,
.cpu_architecture = "test"};
Lardon3DResourcePolicy p = {.maximum_cpu_load_ratio = 1,
.maximum_io_pressure_avg10 = 100,
.io_slot_capacity = 2};
s->resource_governor =
lardon3d_resource_governor_create(&s->hardware_profile, &p);
s->task_queue = s->resource_governor
? lardon3d_task_queue_create(s->resource_governor, 4)
: NULL;
return s->task_queue != NULL;
}
static bool wait_state(Lardon3DTaskQueue *q, uint64_t id,
Lardon3DTaskState wanted, Lardon3DTaskSnapshot *out) {
for (size_t i = 0; i < 2000000; i++) {
if (lardon3d_task_queue_get(q, id, out) &&
out->state == wanted) {
return true;
}
sched_yield();
}
return false;
}
static bool run_test(void) {
char root[] = "/tmp/lardon3d-candidate-pair-task-XXXXXX";
CHECK(mkdtemp(root) &&
setenv("LARDON3D_PROJECTS_ROOT", root, 1) == 0);
/* --- Créer 3 images distinctes --- */
char source_a[PATH_MAX], source_b[PATH_MAX], source_c[PATH_MAX];
CHECK(join_path(source_a, root, "a.pgm") && write_pgm(source_a, 1));
CHECK(join_path(source_b, root, "b.pgm") && write_pgm(source_b, 2));
CHECK(join_path(source_c, root, "c.pgm") && write_pgm(source_c, 3));
Lardon3DAppState state;
lardon3d_app_state_init(&state);
CHECK(runtime(&state) && lardon3d_project_create(&state, "CPTask"));
Lardon3DProjectDbScanSet scanset;
CHECK(lardon3d_image_catalog_create_scanset(&state, "S", &scanset));
Lardon3DProjectDbImage img_a, img_b, img_c;
Lardon3DProjectDbImageAsset asset_a, asset_b, asset_c;
CHECK(lardon3d_image_catalog_import_file(&state, scanset.scanset_id,
source_a, 0, &img_a,
&asset_a) ==
LARDON3D_IMAGE_CATALOG_IMPORTED &&
lardon3d_image_catalog_import_file(&state, scanset.scanset_id,
source_b, 0, &img_b,
&asset_b) ==
LARDON3D_IMAGE_CATALOG_IMPORTED &&
lardon3d_image_catalog_import_file(&state, scanset.scanset_id,
source_c, 0, &img_c,
&asset_c) ==
LARDON3D_IMAGE_CATALOG_IMPORTED);
/* Extraction feature ORB */
Lardon3DFeatureExtractorParameters orb_params = {512, 4, 10};
uint64_t orb_a_id = 0, orb_b_id = 0, orb_c_id = 0;
CHECK(lardon3d_project_enqueue_feature_extract(
&state, img_a.image_id, &orb_params, &orb_a_id) &&
lardon3d_project_enqueue_feature_extract(
&state, img_b.image_id, &orb_params, &orb_b_id) &&
lardon3d_project_enqueue_feature_extract(
&state, img_c.image_id, &orb_params, &orb_c_id));
Lardon3DTaskSnapshot snap;
CHECK(wait_state(state.task_queue, orb_a_id, TASK_COMPLETED, &snap));
CHECK(wait_state(state.task_queue, orb_b_id, TASK_COMPLETED, &snap));
CHECK(wait_state(state.task_queue, orb_c_id, TASK_COMPLETED, &snap));
unsigned char fp[32];
lardon3d_feature_extractor_parameter_fingerprint(&orb_params, fp);
Lardon3DProjectDbFeatureSet fs_a, fs_b, fs_c;
CHECK(lardon3d_project_db_find_feature_set(
state.project_db, img_a.image_id,
LARDON3D_FEATURE_EXTRACTOR_KIND, 1, fp, &fs_a) ==
LARDON3D_PROJECT_DB_OK &&
lardon3d_project_db_find_feature_set(
state.project_db, img_b.image_id,
LARDON3D_FEATURE_EXTRACTOR_KIND, 1, fp, &fs_b) ==
LARDON3D_PROJECT_DB_OK &&
lardon3d_project_db_find_feature_set(
state.project_db, img_c.image_id,
LARDON3D_FEATURE_EXTRACTOR_KIND, 1, fp, &fs_c) ==
LARDON3D_PROJECT_DB_OK);
/* Visual Index */
Lardon3DVisualIndexConfiguration index_cfg = {1, 256, 128};
uint64_t visual_index_id = 0;
CHECK(lardon3d_visual_index_create(state.project_db, &fs_a, &index_cfg,
&visual_index_id) ==
LARDON3D_VISUAL_INDEX_OK);
uint64_t vi_task_id = 0;
CHECK(lardon3d_project_enqueue_visual_index_update(
&state, visual_index_id, &vi_task_id));
CHECK(wait_state(state.task_queue, vi_task_id, TASK_COMPLETED, &snap));
/* --- Test 1: Création task Candidate Pair --- */
Lardon3DVisualIndexQueryOptions qopts = {
.top_k = 16,
.minimum_evidence_count = 10,
.scanset_filter = LARDON3D_VISUAL_INDEX_ANY_SCANSET,
.exclude_same_asset = false,
};
uint64_t cp_task_id = 0;
CHECK(lardon3d_project_enqueue_candidate_pair_generate(
&state, visual_index_id, &qopts, &cp_task_id));
CHECK(cp_task_id != 0);
CHECK(wait_state(state.task_queue, cp_task_id, TASK_COMPLETED, &snap));
CHECK(snap.progress == 100);
/* Vérification : des paires ont été générées */
Lardon3DProjectDbCandidatePair pairs[16];
size_t pair_count = 0;
CHECK(lardon3d_project_db_list_candidate_pairs(
state.project_db, 0, pairs, 16, &pair_count) ==
LARDON3D_PROJECT_DB_OK &&
pair_count > 0);
/* --- Test 2: Idempotence --- */
uint64_t cp_task2_id = 0;
CHECK(lardon3d_project_enqueue_candidate_pair_generate(
&state, visual_index_id, &qopts, &cp_task2_id));
CHECK(cp_task2_id != cp_task_id);
CHECK(wait_state(state.task_queue, cp_task2_id, TASK_COMPLETED, &snap));
CHECK(snap.progress == 100);
size_t pair_count2 = 0;
CHECK(lardon3d_project_db_list_candidate_pairs(
state.project_db, 0, pairs, 16, &pair_count2) ==
LARDON3D_PROJECT_DB_OK &&
pair_count2 == pair_count);
/* --- Test 3: Checkpoint / reprise --- */
/* Pause après premier batch */
CHECK(setenv("LARDON3D_TEST_CANDIDATE_PAIR_PAUSE_AFTER_BATCH", "1",
1) == 0 &&
setenv("LARDON3D_TEST_CANDIDATE_PAIR_SKIP_FINISHED_CHECKPOINT",
"1", 1) == 0);
Lardon3DVisualIndexQueryOptions qopts_b = {
.top_k = 8, .minimum_evidence_count = 0,
.scanset_filter = LARDON3D_VISUAL_INDEX_ANY_SCANSET,
.exclude_same_asset = false,
};
uint64_t cp_task3_id = 0;
CHECK(lardon3d_project_enqueue_candidate_pair_generate(
&state, visual_index_id, &qopts_b, &cp_task3_id));
CHECK(wait_state(state.task_queue, cp_task3_id, TASK_PAUSED, &snap));
lardon3d_task_queue_destroy(state.task_queue);
state.task_queue = NULL;
lardon3d_project_close(&state);
lardon3d_resource_governor_destroy(state.resource_governor);
state.resource_governor = NULL;
CHECK(unsetenv("LARDON3D_TEST_CANDIDATE_PAIR_PAUSE_AFTER_BATCH") == 0 &&
unsetenv("LARDON3D_TEST_CANDIDATE_PAIR_SKIP_FINISHED_CHECKPOINT") ==
0);
lardon3d_app_state_init(&state);
CHECK(runtime(&state) && lardon3d_project_open(&state, "CPTask"));
Lardon3DProjectRecoverySummary summary;
CHECK(lardon3d_project_last_recovery_summary(&state, &summary) &&
summary.resumed == 1);
CHECK(wait_state(state.task_queue, cp_task3_id, TASK_COMPLETED, &snap) &&
snap.progress == 100);
/* --- Test 4: Reconstruction --- */
/* Attendre que la tâche se termine puis ré-ouvrir sans reprise */
lardon3d_task_queue_destroy(state.task_queue);
state.task_queue = NULL;
lardon3d_project_close(&state);
lardon3d_resource_governor_destroy(state.resource_governor);
state.resource_governor = NULL;
lardon3d_app_state_init(&state);
CHECK(runtime(&state) && lardon3d_project_open(&state, "CPTask"));
CHECK(lardon3d_project_last_recovery_summary(&state, &summary) &&
summary.resumed == 0);
/* Vérifier que la DB task est sauvegardée */
Lardon3DProjectDbCandidatePairGenerateTask saved;
CHECK(lardon3d_project_db_load_candidate_pair_generate_task(
state.project_db, cp_task_id, &saved) ==
LARDON3D_PROJECT_DB_OK &&
saved.top_k == 16);
/* --- Test 5: Ordre canonique vérifié --- */
size_t final_count = 0;
Lardon3DProjectDbCandidatePair final_pairs[32];
CHECK(lardon3d_project_db_list_candidate_pairs(
state.project_db, 0, final_pairs, 32, &final_count) ==
LARDON3D_PROJECT_DB_OK &&
final_count > 0);
for (size_t i = 0; i < final_count; ++i) {
CHECK(final_pairs[i].image_id_a < final_pairs[i].image_id_b);
}
lardon3d_task_queue_destroy(state.task_queue);
state.task_queue = NULL;
lardon3d_project_close(&state);
lardon3d_resource_governor_destroy(state.resource_governor);
CHECK(unlink(source_a) == 0 && unlink(source_b) == 0 &&
unlink(source_c) == 0 && remove_tree(root));
return true;
}
int main(void) { return run_test() ? 0 : 1; }