484 lines
18 KiB
C
484 lines
18 KiB
C
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <sched.h>
|
|
#include <sqlite3.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/feature_store.h>
|
|
#include <lardon3d/matcher_task.h>
|
|
#include <lardon3d/project.h>
|
|
#include <lardon3d/task_queue.h>
|
|
|
|
#define CHECK(condition) \
|
|
do { \
|
|
if (!(condition)) { \
|
|
(void)fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); \
|
|
return false; \
|
|
} \
|
|
} while (0)
|
|
|
|
enum {
|
|
IMAGE_COUNT = 42,
|
|
PAIR_COUNT = IMAGE_COUNT - 1,
|
|
PERSISTED_PAIR_COUNT = PAIR_COUNT - 1,
|
|
};
|
|
|
|
typedef struct {
|
|
char root[PATH_MAX];
|
|
Lardon3DAppState state;
|
|
Lardon3DProjectDbScanSet scanset;
|
|
Lardon3DProjectDbImage images[IMAGE_COUNT];
|
|
Lardon3DProjectDbCandidatePair pairs[PAIR_COUNT];
|
|
unsigned char feature_fingerprint[32];
|
|
} Fixture;
|
|
|
|
static Lardon3DResourcePolicy interactive_policy(void) {
|
|
return (Lardon3DResourcePolicy){
|
|
.system_memory_reserve_bytes = 4ULL * 1024 * 1024 * 1024,
|
|
.emergency_memory_floor_bytes = 2ULL * 1024 * 1024 * 1024,
|
|
.system_cpu_reserve = 4,
|
|
.maximum_cpu_load_ratio = 1.0,
|
|
.maximum_cpu_pressure_avg10 = 100.0,
|
|
.maximum_memory_pressure_avg10 = 100.0,
|
|
.maximum_io_pressure_avg10 = 100.0,
|
|
.io_slot_capacity = 1,
|
|
.gpu_slot_capacity = 1,
|
|
};
|
|
}
|
|
|
|
static bool join_path(char output[PATH_MAX], const char *left,
|
|
const char *right) {
|
|
int written = snprintf(output, PATH_MAX, "%s/%s", left, right);
|
|
return written > 0 && (size_t)written < PATH_MAX;
|
|
}
|
|
|
|
static bool query_integer(const char *path, const char *sql,
|
|
sqlite3_int64 expected) {
|
|
sqlite3 *connection = NULL;
|
|
sqlite3_stmt *statement = NULL;
|
|
bool success = sqlite3_open_v2(path, &connection, SQLITE_OPEN_READONLY,
|
|
NULL) == SQLITE_OK &&
|
|
sqlite3_prepare_v2(connection, sql, -1, &statement, NULL) ==
|
|
SQLITE_OK &&
|
|
sqlite3_step(statement) == SQLITE_ROW &&
|
|
sqlite3_column_int64(statement, 0) == expected;
|
|
if (statement) {
|
|
(void)sqlite3_finalize(statement);
|
|
}
|
|
if (connection) {
|
|
(void)sqlite3_close(connection);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
static bool downgrade_project_to_historical_v10(const char *database_path) {
|
|
sqlite3 *connection = NULL;
|
|
if (sqlite3_open_v2(database_path, &connection, SQLITE_OPEN_READWRITE,
|
|
NULL) != SQLITE_OK) {
|
|
if (connection) {
|
|
(void)sqlite3_close(connection);
|
|
}
|
|
return false;
|
|
}
|
|
static const char sql[] =
|
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
|
"DROP TABLE geometric_verifier_tasks;"
|
|
"DROP TABLE geometric_verification_results;"
|
|
"DROP TABLE matcher_tasks;"
|
|
"UPDATE metadata SET value=10 WHERE key='schema_version';"
|
|
"COMMIT;PRAGMA foreign_keys=ON;";
|
|
bool success = sqlite3_exec(connection, sql, NULL, NULL, NULL) == SQLITE_OK;
|
|
return sqlite3_close(connection) == SQLITE_OK && success;
|
|
}
|
|
|
|
static bool remove_tree(const char *path) {
|
|
struct stat information;
|
|
if (lstat(path, &information) != 0) {
|
|
return errno == ENOENT;
|
|
}
|
|
if (!S_ISDIR(information.st_mode)) {
|
|
return unlink(path) == 0;
|
|
}
|
|
DIR *directory = opendir(path);
|
|
if (!directory) {
|
|
return false;
|
|
}
|
|
bool success = true;
|
|
for (struct dirent *entry = readdir(directory); entry;
|
|
entry = readdir(directory)) {
|
|
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
|
continue;
|
|
}
|
|
char child[PATH_MAX];
|
|
if (!join_path(child, path, entry->d_name) || !remove_tree(child)) {
|
|
success = false;
|
|
}
|
|
}
|
|
if (closedir(directory) != 0 || rmdir(path) != 0) {
|
|
success = false;
|
|
}
|
|
return success;
|
|
}
|
|
|
|
static bool create_runtime(Lardon3DAppState *state) {
|
|
state->hardware_profile = (Lardon3DHardwareProfile){
|
|
.logical_cpu_count = 16,
|
|
.page_size_bytes = 4096,
|
|
.memory_total_bytes = 16ULL * 1024 * 1024 * 1024,
|
|
.gpu_available = true,
|
|
.gpu_uses_shared_memory = true,
|
|
.cpu_architecture = "test",
|
|
};
|
|
Lardon3DResourcePolicy policy = interactive_policy();
|
|
state->resource_governor =
|
|
lardon3d_resource_governor_create(&state->hardware_profile, &policy);
|
|
state->orb_vulkan_backend = lardon3d_orb_vulkan_backend_create();
|
|
state->task_queue =
|
|
state->resource_governor && state->orb_vulkan_backend
|
|
? lardon3d_task_queue_create(state->resource_governor, 16)
|
|
: NULL;
|
|
return state->task_queue != NULL;
|
|
}
|
|
|
|
static bool wait_state(Lardon3DTaskQueue *queue, uint64_t task_id,
|
|
Lardon3DTaskState wanted,
|
|
Lardon3DTaskSnapshot *snapshot) {
|
|
for (size_t attempt = 0; attempt < 2000000; ++attempt) {
|
|
if (lardon3d_task_queue_get(queue, task_id, snapshot) &&
|
|
snapshot->state == wanted) {
|
|
return true;
|
|
}
|
|
sched_yield();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool wait_durable_state(Lardon3DProjectDb *database, uint64_t task_id,
|
|
Lardon3DTaskState wanted,
|
|
Lardon3DProjectDbTask *durable_task) {
|
|
for (size_t attempt = 0; attempt < 2000000; ++attempt) {
|
|
if (lardon3d_project_db_load_task(database, task_id, durable_task) ==
|
|
LARDON3D_PROJECT_DB_OK &&
|
|
durable_task->saved_state == wanted) {
|
|
return true;
|
|
}
|
|
sched_yield();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void image_asset_path(const unsigned char hash[32], char path[4096]) {
|
|
static const char digits[] = "0123456789abcdef";
|
|
char hex[65];
|
|
for (size_t index = 0; index < 32; ++index) {
|
|
hex[2 * index] = digits[hash[index] >> 4];
|
|
hex[2 * index + 1] = digits[hash[index] & 15U];
|
|
}
|
|
hex[64] = '\0';
|
|
(void)snprintf(path, 4096, "assets/images/%c%c/%s", hex[0], hex[1], hex);
|
|
}
|
|
|
|
static bool register_image(Fixture *fixture, unsigned char seed, size_t index) {
|
|
unsigned char hash[32];
|
|
memset(hash, seed, sizeof(hash));
|
|
char path[4096];
|
|
image_asset_path(hash, path);
|
|
Lardon3DProjectDbImageRegisterStatus status;
|
|
return lardon3d_project_db_register_image(
|
|
fixture->state.project_db, fixture->scanset.scanset_id, hash, path,
|
|
1, "fixture.bin", "/synthetic/fixture.bin", 0, seed, &status,
|
|
&fixture->images[index]) == LARDON3D_PROJECT_DB_OK;
|
|
}
|
|
|
|
static bool publish_features(Fixture *fixture, size_t image_index) {
|
|
unsigned char descriptor[32];
|
|
memset(descriptor, (int)image_index, sizeof(descriptor));
|
|
Lardon3DFeatureKeypoint keypoint = {
|
|
.size = 1.0F,
|
|
};
|
|
Lardon3DExtractedFeatures features = {
|
|
.image_width = 64,
|
|
.image_height = 64,
|
|
.feature_count = 1,
|
|
.keypoints = &keypoint,
|
|
.descriptors = descriptor,
|
|
.descriptor_bytes = sizeof(descriptor),
|
|
};
|
|
Lardon3DProjectDbFeatureSet feature_set;
|
|
return lardon3d_feature_store_publish_v2(
|
|
&fixture->state, fixture->images[image_index].image_id, 0, "orb",
|
|
1, fixture->feature_fingerprint, LARDON3D_FEATURE_DESCRIPTOR_U8,
|
|
32, 0, &features, &feature_set) == LARDON3D_FEATURE_STORE_OK;
|
|
}
|
|
|
|
static bool fixture_create(Fixture *fixture) {
|
|
memset(fixture, 0, sizeof(*fixture));
|
|
char root[] = "/tmp/lardon3d-matcher-task-XXXXXX";
|
|
char *created = mkdtemp(root);
|
|
if (!created ||
|
|
snprintf(fixture->root, sizeof(fixture->root), "%s", created) <= 0 ||
|
|
setenv("LARDON3D_PROJECTS_ROOT", fixture->root, 1) != 0) {
|
|
return false;
|
|
}
|
|
memset(fixture->feature_fingerprint, 0x5A,
|
|
sizeof(fixture->feature_fingerprint));
|
|
lardon3d_app_state_init(&fixture->state);
|
|
if (!create_runtime(&fixture->state) ||
|
|
!lardon3d_project_create(&fixture->state, "MatcherTask") ||
|
|
lardon3d_project_db_create_scanset(fixture->state.project_db,
|
|
"matcher-task", &fixture->scanset) !=
|
|
LARDON3D_PROJECT_DB_OK) {
|
|
return false;
|
|
}
|
|
for (size_t index = 0; index < IMAGE_COUNT; ++index) {
|
|
if (!register_image(fixture, (unsigned char)(index + 1), index) ||
|
|
!publish_features(fixture, index)) {
|
|
return false;
|
|
}
|
|
}
|
|
for (size_t index = 0; index < PAIR_COUNT; ++index) {
|
|
if (lardon3d_project_db_create_candidate_pair(
|
|
fixture->state.project_db, fixture->images[index].image_id,
|
|
fixture->images[index + 1].image_id, (int64_t)index,
|
|
&fixture->pairs[index]) != LARDON3D_PROJECT_DB_OK) {
|
|
return false;
|
|
}
|
|
}
|
|
char database_path[PATH_MAX];
|
|
if (!join_path(database_path, fixture->state.project_path, "project.db")) {
|
|
return false;
|
|
}
|
|
sqlite3 *connection = NULL;
|
|
if (sqlite3_open_v2(database_path, &connection, SQLITE_OPEN_READWRITE,
|
|
NULL) != SQLITE_OK) {
|
|
if (connection) {
|
|
sqlite3_close(connection);
|
|
}
|
|
return false;
|
|
}
|
|
char sql[128];
|
|
int written =
|
|
snprintf(sql, sizeof(sql),
|
|
"DELETE FROM candidate_pairs WHERE candidate_pair_id=%lu",
|
|
(unsigned long)fixture->pairs[2].candidate_pair_id);
|
|
bool deleted = written > 0 && (size_t)written < sizeof(sql) &&
|
|
sqlite3_exec(connection, sql, NULL, NULL, NULL) == SQLITE_OK &&
|
|
sqlite3_changes(connection) == 1;
|
|
(void)sqlite3_close(connection);
|
|
if (!deleted) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void stop_runtime(Fixture *fixture) {
|
|
if (fixture->state.task_queue) {
|
|
lardon3d_task_queue_destroy(fixture->state.task_queue);
|
|
fixture->state.task_queue = NULL;
|
|
}
|
|
lardon3d_project_close(&fixture->state);
|
|
if (fixture->state.resource_governor) {
|
|
lardon3d_resource_governor_destroy(fixture->state.resource_governor);
|
|
fixture->state.resource_governor = NULL;
|
|
}
|
|
lardon3d_orb_vulkan_backend_destroy(fixture->state.orb_vulkan_backend);
|
|
fixture->state.orb_vulkan_backend = NULL;
|
|
}
|
|
|
|
static bool reopen_runtime(Fixture *fixture) {
|
|
lardon3d_app_state_init(&fixture->state);
|
|
return create_runtime(&fixture->state) &&
|
|
lardon3d_project_open(&fixture->state, "MatcherTask");
|
|
}
|
|
|
|
static Lardon3DMatcherTaskConfiguration configuration(const Fixture *fixture) {
|
|
Lardon3DMatcherTaskConfiguration result = {
|
|
.feature_extractor_version = 1,
|
|
.matcher =
|
|
{
|
|
.kind = LARDON3D_MATCHER_ORB_BF,
|
|
.ratio_threshold = 0.75F,
|
|
},
|
|
};
|
|
(void)snprintf(result.feature_extractor_kind,
|
|
sizeof(result.feature_extractor_kind), "orb");
|
|
memcpy(result.feature_parameter_fingerprint, fixture->feature_fingerprint,
|
|
sizeof(result.feature_parameter_fingerprint));
|
|
return result;
|
|
}
|
|
|
|
static bool count_results(Fixture *fixture, size_t *count) {
|
|
Lardon3DProjectDbMatchResult results[16];
|
|
uint64_t cursor = 0;
|
|
*count = 0;
|
|
for (;;) {
|
|
size_t page_count = 0;
|
|
if (lardon3d_project_db_list_match_results(
|
|
fixture->state.project_db, cursor, results, 16, &page_count) !=
|
|
LARDON3D_PROJECT_DB_OK) {
|
|
return false;
|
|
}
|
|
*count += page_count;
|
|
if (page_count < 16) {
|
|
return true;
|
|
}
|
|
cursor = results[page_count - 1].match_result_id;
|
|
}
|
|
}
|
|
|
|
static bool run_test(void) {
|
|
Fixture fixture;
|
|
CHECK(fixture_create(&fixture));
|
|
char database_path[PATH_MAX];
|
|
CHECK(join_path(database_path, fixture.state.project_path, "project.db"));
|
|
stop_runtime(&fixture);
|
|
CHECK(downgrade_project_to_historical_v10(database_path));
|
|
CHECK(query_integer(database_path,
|
|
"SELECT value FROM metadata WHERE key='schema_version'",
|
|
10));
|
|
CHECK(query_integer(database_path,
|
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
|
"name='match_results'",
|
|
1));
|
|
CHECK(query_integer(database_path,
|
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
|
"name='matcher_tasks'",
|
|
0));
|
|
CHECK(reopen_runtime(&fixture));
|
|
CHECK(lardon3d_project_db_schema_version(fixture.state.project_db) == 13);
|
|
CHECK(query_integer(database_path,
|
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
|
"name='matcher_tasks'",
|
|
1));
|
|
const Lardon3DTaskKindRegistry *registry =
|
|
lardon3d_task_kind_registry_production();
|
|
const Lardon3DTaskKindDescriptor *descriptor = NULL;
|
|
CHECK(lardon3d_task_kind_registry_lookup(registry, LARDON3D_MATCHER_TASK_KIND,
|
|
LARDON3D_MATCHER_TASK_KIND_VERSION,
|
|
&descriptor) ==
|
|
LARDON3D_TASK_KIND_OK &&
|
|
descriptor != NULL);
|
|
|
|
Lardon3DMatcherTaskConfiguration settings = configuration(&fixture);
|
|
Lardon3DMatcherTaskConfiguration invalid = settings;
|
|
invalid.matcher.kind = LARDON3D_MATCHER_SIFT_BF;
|
|
uint64_t invalid_task_id = 0;
|
|
CHECK(!lardon3d_project_create_matcher_task(&fixture.state, &invalid,
|
|
&invalid_task_id));
|
|
CHECK(invalid_task_id == 0);
|
|
CHECK(setenv("LARDON3D_TEST_MATCHER_PAUSE_AFTER_PUBLICATION", "1", 1) == 0);
|
|
CHECK(setenv("LARDON3D_TEST_MATCHER_SKIP_FINISHED_CHECKPOINT", "1", 1) == 0);
|
|
uint64_t task_id = 0;
|
|
CHECK(lardon3d_project_enqueue_matcher_task(&fixture.state, &settings,
|
|
&task_id));
|
|
Lardon3DTaskSnapshot snapshot;
|
|
CHECK(wait_state(fixture.state.task_queue, task_id, TASK_PAUSED, &snapshot));
|
|
|
|
size_t result_count = 0;
|
|
CHECK(count_results(&fixture, &result_count) && result_count == 1);
|
|
Lardon3DProjectDbMatcherTask saved;
|
|
CHECK(lardon3d_project_db_load_matcher_task(fixture.state.project_db, task_id,
|
|
&saved) ==
|
|
LARDON3D_PROJECT_DB_OK);
|
|
CHECK(saved.after_candidate_pair_id == 0);
|
|
CHECK(saved.matcher_kind == LARDON3D_MATCHER_ORB_BF);
|
|
CHECK(saved.ratio_threshold == 0.75F);
|
|
|
|
stop_runtime(&fixture);
|
|
CHECK(unsetenv("LARDON3D_TEST_MATCHER_PAUSE_AFTER_PUBLICATION") == 0);
|
|
CHECK(unsetenv("LARDON3D_TEST_MATCHER_SKIP_FINISHED_CHECKPOINT") == 0);
|
|
CHECK(reopen_runtime(&fixture));
|
|
Lardon3DProjectRecoverySummary recovery;
|
|
CHECK(lardon3d_project_last_recovery_summary(&fixture.state, &recovery));
|
|
CHECK(recovery.resumed == 1);
|
|
CHECK(
|
|
wait_state(fixture.state.task_queue, task_id, TASK_COMPLETED, &snapshot));
|
|
CHECK(snapshot.progress == 100);
|
|
Lardon3DProjectDbTask durable_task;
|
|
CHECK(wait_durable_state(fixture.state.project_db, task_id, TASK_COMPLETED,
|
|
&durable_task));
|
|
CHECK(durable_task.sequence_count >= 1);
|
|
CHECK(count_results(&fixture, &result_count) &&
|
|
result_count == PERSISTED_PAIR_COUNT);
|
|
CHECK(lardon3d_project_db_load_matcher_task(fixture.state.project_db, task_id,
|
|
&saved) ==
|
|
LARDON3D_PROJECT_DB_OK);
|
|
CHECK(saved.after_candidate_pair_id ==
|
|
fixture.pairs[PAIR_COUNT - 1].candidate_pair_id);
|
|
|
|
CHECK(setenv("LARDON3D_TEST_MATCHER_PAUSE_AFTER_PUBLICATION", "1", 1) == 0);
|
|
uint64_t cancelled_id = 0;
|
|
CHECK(lardon3d_project_enqueue_matcher_task(&fixture.state, &settings,
|
|
&cancelled_id));
|
|
CHECK(wait_state(fixture.state.task_queue, cancelled_id, TASK_PAUSED,
|
|
&snapshot));
|
|
CHECK(lardon3d_task_queue_cancel(fixture.state.task_queue, cancelled_id));
|
|
CHECK(wait_state(fixture.state.task_queue, cancelled_id, TASK_CANCELLED,
|
|
&snapshot));
|
|
CHECK(unsetenv("LARDON3D_TEST_MATCHER_PAUSE_AFTER_PUBLICATION") == 0);
|
|
|
|
CHECK(setenv("LARDON3D_TEST_MATCHER_PAUSE_AFTER_PUBLICATION", "1", 1) == 0);
|
|
uint64_t resumed_id = 0;
|
|
CHECK(lardon3d_project_enqueue_matcher_task(&fixture.state, &settings,
|
|
&resumed_id));
|
|
CHECK(
|
|
wait_state(fixture.state.task_queue, resumed_id, TASK_PAUSED, &snapshot));
|
|
CHECK(unsetenv("LARDON3D_TEST_MATCHER_PAUSE_AFTER_PUBLICATION") == 0);
|
|
CHECK(lardon3d_task_queue_resume(fixture.state.task_queue, resumed_id));
|
|
CHECK(wait_state(fixture.state.task_queue, resumed_id, TASK_COMPLETED,
|
|
&snapshot));
|
|
|
|
CHECK(setenv("LARDON3D_TEST_MATCHER_PAUSE_AFTER_BATCH", "1", 1) == 0);
|
|
uint64_t pressure_id = 0;
|
|
CHECK(lardon3d_project_enqueue_matcher_task(&fixture.state, &settings,
|
|
&pressure_id));
|
|
CHECK(wait_state(fixture.state.task_queue, pressure_id, TASK_PAUSED,
|
|
&snapshot));
|
|
Lardon3DResourcePolicy pressure_policy = interactive_policy();
|
|
pressure_policy.system_memory_reserve_bytes =
|
|
fixture.state.hardware_profile.memory_total_bytes - 16ULL * 1024 * 1024;
|
|
pressure_policy.emergency_memory_floor_bytes =
|
|
pressure_policy.system_memory_reserve_bytes;
|
|
CHECK(lardon3d_resource_governor_set_policy(fixture.state.resource_governor,
|
|
&pressure_policy));
|
|
CHECK(unsetenv("LARDON3D_TEST_MATCHER_PAUSE_AFTER_BATCH") == 0);
|
|
CHECK(lardon3d_task_queue_resume(fixture.state.task_queue, pressure_id));
|
|
for (size_t attempt = 0; attempt < 2000000; ++attempt) {
|
|
if (lardon3d_resource_governor_pressure(fixture.state.resource_governor) ==
|
|
LARDON3D_RESOURCE_PRESSURE_RED) {
|
|
break;
|
|
}
|
|
sched_yield();
|
|
}
|
|
CHECK(lardon3d_resource_governor_pressure(fixture.state.resource_governor) ==
|
|
LARDON3D_RESOURCE_PRESSURE_RED);
|
|
Lardon3DResourcePolicy normal_policy = interactive_policy();
|
|
CHECK(lardon3d_resource_governor_set_policy(fixture.state.resource_governor,
|
|
&normal_policy));
|
|
CHECK(wait_state(fixture.state.task_queue, pressure_id, TASK_COMPLETED,
|
|
&snapshot));
|
|
|
|
uint64_t estimate_task_id = 0;
|
|
Lardon3DTask *estimate_task = lardon3d_project_create_matcher_task(
|
|
&fixture.state, &settings, &estimate_task_id);
|
|
Lardon3DTaskDurableSnapshot estimate_snapshot;
|
|
CHECK(estimate_task != NULL && estimate_task_id != 0);
|
|
CHECK(lardon3d_task_durable_snapshot(estimate_task, &estimate_snapshot));
|
|
CHECK(estimate_snapshot.estimate.gpu_memory_fixed_bytes ==
|
|
LARDON3D_ORB_VULKAN_PERMANENT_BUFFER_BYTES);
|
|
CHECK(estimate_snapshot.estimate.desired_gpu_slots == 1);
|
|
lardon3d_task_destroy(estimate_task);
|
|
|
|
stop_runtime(&fixture);
|
|
CHECK(remove_tree(fixture.root));
|
|
return true;
|
|
}
|
|
|
|
int main(void) { return run_test() ? EXIT_SUCCESS : EXIT_FAILURE; }
|