lardon3d/tests/test_selected_execution.c

329 lines
17 KiB
C

#include <sqlite3.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <lardon3d/project_db.h>
#define CHECK(condition) do { if (!(condition)) { \
fprintf(stderr, "selected execution failure at line %d: %s\n", __LINE__, #condition); \
return false; } } while (0)
static bool sql(const char *path, const char *text) {
sqlite3 *connection = NULL;
if (sqlite3_open(path, &connection) != SQLITE_OK) return false;
int code = sqlite3_exec(connection, text, NULL, NULL, NULL);
return sqlite3_close(connection) == SQLITE_OK && code == SQLITE_OK;
}
static void feature_asset_path(
const unsigned char hash[LARDON3D_PROJECT_DB_SHA256_SIZE],
char path[LARDON3D_PROJECT_DB_PATH_CAPACITY]) {
static const char digits[] = "0123456789abcdef";
char hex[LARDON3D_PROJECT_DB_SHA256_SIZE * 2 + 1];
for (size_t index = 0; index < LARDON3D_PROJECT_DB_SHA256_SIZE; ++index) {
hex[index * 2] = digits[hash[index] >> 4];
hex[index * 2 + 1] = digits[hash[index] & 15U];
}
hex[sizeof(hex) - 1] = '\0';
(void)snprintf(path, LARDON3D_PROJECT_DB_PATH_CAPACITY,
"assets/features/%c%c/%s", hex[0], hex[1], hex);
}
static bool seed_v21(const char *path) {
Lardon3DProjectDb *database = NULL;
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK);
lardon3d_project_db_close(database);
/* The two group namespaces intentionally use different values. This fixture
proves that v22 retains the explicit bridge rather than equating IDs. */
return sql(path,
"INSERT INTO scansets(scanset_id,name,created_at,updated_at) VALUES(1,'scope',1,1);"
"INSERT INTO tasks VALUES(1,'quality','photo_quality.triage',1,5,5,100,1,0,0,0,0,1);"
"INSERT INTO tasks VALUES(2,'campaign','acquisition_campaign.run',1,5,5,100,1,0,0,0,0,1);"
"INSERT INTO photo_quality_triage_tasks VALUES(1,1,2,1,X'01');"
"INSERT INTO photo_quality_triage_results VALUES(1,1,0,1,0,1,0,100,100,100,100,"
"1.0,1.0,0.0,0.0,1.0,1.0,0.0,'GOOD');"
"INSERT INTO acquisition_campaign_tasks VALUES(2,1,2,2,X'02');"
"INSERT INTO captures(capture_id,scanset_id,created_at) VALUES(1,1,1);"
"INSERT INTO acquisition_campaign_captures VALUES(2,2,1);"
"INSERT INTO image_assets(asset_id,sha256,path,size_bytes,state,created_at) "
"VALUES(1,zeroblob(32),'assets/images/00/test',1,1,1);"
"INSERT INTO image_assets(asset_id,sha256,path,size_bytes,state,created_at) "
"VALUES(2,randomblob(32),'assets/images/00/raw',1,1,1);"
"INSERT INTO capture_assets(capture_id,asset_id,role) VALUES(1,2,1);"
"INSERT INTO capture_source_assets(capture_id,asset_id,source_kind) VALUES(1,2,2);"
"INSERT INTO images(image_id,scanset_id,asset_id,original_name,source_path,imported_at) "
"VALUES(1,1,1,'selected.jpg','/source/selected.jpg',1);"
"INSERT INTO capture_images VALUES(1,1);"
"INSERT INTO sparse_calibrations VALUES(1,randomblob(32),1,1,100,100,50,50,50,50,0,0,0,0,"
"2,randomblob(32));"
"INSERT INTO sparse_calibration_scopes VALUES(1,randomblob(32),1);"
"INSERT INTO sparse_calibration_scope_images VALUES(1,1,1);"
/* This fixture is a true v21 database; future additive objects must not
remain merely because it was generated from a temporary current DB. */
"DROP TABLE feature_extract_batch_tasks;"
"DROP TABLE raw_development_batch_tasks;"
"DROP TABLE capture_calibration_selections;"
"DROP TABLE optical_calibration_profiles;"
"DROP TABLE capture_optical_configurations;"
"DROP INDEX acquisition_campaign_capture_identity_v23;"
"DROP TABLE acquisition_campaign_group_optics;"
"DROP TABLE optical_configurations;"
"DROP TABLE lens_profile_aliases;DROP TABLE lens_profiles;"
"DROP TABLE camera_body_aliases;DROP TABLE camera_body_profiles;"
"DROP TABLE selected_execution_items;DROP TABLE selected_executions;"
"UPDATE metadata SET value=21 WHERE key='schema_version';");
}
static bool run(void) {
char directory[] = "/tmp/lardon3d-selected-XXXXXX";
CHECK(mkdtemp(directory) != NULL);
char path[512];
CHECK(snprintf(path, sizeof(path), "%s/project.db", directory) > 0);
CHECK(seed_v21(path));
Lardon3DProjectDb *database = NULL;
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V22", "1", 1) == 0);
Lardon3DProjectDbResult migration_result = lardon3d_project_db_open(path, &database, error);
CHECK(migration_result != LARDON3D_PROJECT_DB_OK);
CHECK(unsetenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V22") == 0);
CHECK(sql(path, "CREATE TEMP TABLE migration_check(value INTEGER);"));
sqlite3 *raw = NULL;
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
sqlite3_stmt *query = NULL;
CHECK(sqlite3_prepare_v2(raw, "SELECT value FROM metadata WHERE key='schema_version'", -1,
&query, NULL) == SQLITE_OK);
CHECK(sqlite3_step(query) == SQLITE_ROW && sqlite3_column_int(query, 0) == 21);
sqlite3_finalize(query);
CHECK(sqlite3_close(raw) == SQLITE_OK);
migration_result = lardon3d_project_db_open(path, &database, error);
if (migration_result != LARDON3D_PROJECT_DB_OK)
fprintf(stderr, "retry migration result=%d error=%s\n", (int)migration_result, error);
CHECK(migration_result == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) ==
LARDON3D_PROJECT_DB_SCHEMA_VERSION);
CHECK(sql(path, "SELECT 1 FROM raw_development_batch_tasks WHERE 0;"));
lardon3d_project_db_close(database);
database = NULL;
CHECK(sql(path, "DROP TABLE feature_extract_batch_tasks;"
"DROP TABLE raw_development_batch_tasks;"
"UPDATE metadata SET value=23 WHERE key='schema_version';"));
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V24", "1", 1) == 0);
CHECK(lardon3d_project_db_open(path, &database, error) != LARDON3D_PROJECT_DB_OK);
CHECK(database == NULL);
CHECK(unsetenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V24") == 0);
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
CHECK(sqlite3_prepare_v2(raw,
"SELECT value FROM metadata WHERE key='schema_version'", -1,
&query, NULL) == SQLITE_OK);
CHECK(sqlite3_step(query) == SQLITE_ROW && sqlite3_column_int(query, 0) == 23);
sqlite3_finalize(query);
CHECK(sqlite3_close(raw) == SQLITE_OK);
migration_result = lardon3d_project_db_open(path, &database, error);
if (migration_result != LARDON3D_PROJECT_DB_OK)
fprintf(stderr, "v24 retry result=%d error=%s\n", (int)migration_result, error);
CHECK(migration_result == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) ==
LARDON3D_PROJECT_DB_SCHEMA_VERSION);
Lardon3DProjectDbSelectedExecutionItem item = {
.item_index = 0,
.quality_group_id = 1,
.campaign_group_id = 2,
.capture_id = 1,
.representation_source = LARDON3D_SELECTED_REPRESENTATION_RAW_ASSET,
.source_asset_id = 2};
Lardon3DProjectDbSelectedExecution execution;
Lardon3DProjectDbSelectedExecutionItem unattached = item;
unattached.source_asset_id = 1;
CHECK(lardon3d_project_db_create_selected_execution(database, 1, 2, &unattached, 1, 10,
&execution) ==
LARDON3D_PROJECT_DB_CONSTRAINT);
CHECK(lardon3d_project_db_create_selected_execution(database, 1, 2, &item, 1, 10,
&execution) == LARDON3D_PROJECT_DB_OK);
CHECK(execution.execution_id > 0 && execution.stage ==
LARDON3D_SELECTED_EXECUTION_REPRESENTATIONS && execution.next_item_index == 0);
uint64_t execution_id = execution.execution_id;
CHECK(lardon3d_project_db_create_selected_execution(database, 1, 2, &item, 1, 10,
&execution) == LARDON3D_PROJECT_DB_OK);
CHECK(execution.execution_id == execution_id);
Lardon3DProjectDbSelectedExecutionItem conflict = item;
conflict.representation_source = LARDON3D_SELECTED_REPRESENTATION_SOURCE_IMAGE;
conflict.source_asset_id = 0;
CHECK(lardon3d_project_db_create_selected_execution(database, 1, 2, &conflict, 1, 10,
&execution) ==
LARDON3D_PROJECT_DB_CONSTRAINT);
CHECK(lardon3d_project_db_record_selected_representation(database, execution_id, 0, 1, 1) ==
LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_record_selected_representation(database, execution_id, 0, 1, 1) ==
LARDON3D_PROJECT_DB_OK);
lardon3d_project_db_close(database);
database = NULL;
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_load_selected_execution(database, execution_id, &execution) ==
LARDON3D_PROJECT_DB_OK);
CHECK(execution.stage == LARDON3D_SELECTED_EXECUTION_CALIBRATION &&
execution.next_item_index == 1 && !execution.has_calibration_scope);
Lardon3DProjectDbSelectedExecutionItem loaded;
CHECK(lardon3d_project_db_load_selected_execution_item(database, execution_id, 0, &loaded) ==
LARDON3D_PROJECT_DB_OK);
CHECK(loaded.quality_group_id == 1 && loaded.campaign_group_id == 2 &&
loaded.capture_id == 1 &&
loaded.representation_source == LARDON3D_SELECTED_REPRESENTATION_RAW_ASSET &&
loaded.source_asset_id == 2 && loaded.has_image && loaded.image_id == 1);
CHECK(lardon3d_project_db_assign_selected_calibration_scope(database, execution_id, 1) ==
LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_assign_selected_calibration_scope(database, execution_id, 1) ==
LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_load_selected_execution(database, execution_id, &execution) ==
LARDON3D_PROJECT_DB_OK && execution.stage == LARDON3D_SELECTED_EXECUTION_READY &&
execution.has_calibration_scope && execution.calibration_scope_id == 1);
CHECK(lardon3d_project_db_create_selected_execution(database, 1, 2, &item, 1, 10,
&execution) == LARDON3D_PROJECT_DB_OK &&
execution.execution_id == execution_id &&
execution.stage == LARDON3D_SELECTED_EXECUTION_READY);
Lardon3DTaskDurableSnapshot batch_snapshot = {
.id = 3,
.estimate = {.memory_fixed_bytes = 64U * 1024U * 1024U,
.memory_bytes_per_item = 512U * 1024U * 1024U,
.minimum_batch_size = 1,
.maximum_batch_size = 12,
.desired_cpu_threads = 12,
.desired_io_slots = 1,
.task_class = LARDON3D_RESOURCE_TASK_CPU},
.saved_state = TASK_PENDING,
.recovery_state = TASK_PENDING,
};
(void)snprintf(batch_snapshot.name, sizeof(batch_snapshot.name), "Feature batch");
Lardon3DProjectDbFeatureExtractBatchTask batch = {
.task_id = 3,
.selected_execution_id = execution_id,
.next_item_index = 2,
.extractor_version = 1,
.max_features = 512,
.pyramid_levels = 4,
.fast_threshold = 10,
.parameter_fingerprint = {7},
};
(void)snprintf(batch.extractor_kind, sizeof(batch.extractor_kind), "orb");
/* An invalid new cursor must roll back both generic and typed state; v25
cannot publish a Task whose durable prefix exceeds its immutable domain. */
CHECK(lardon3d_project_db_record_feature_extract_batch_task(
database, &batch_snapshot, "features.extract.batch", 1, NULL,
&batch, 20) == LARDON3D_PROJECT_DB_CONSTRAINT);
Lardon3DProjectDbTask absent_batch_task;
CHECK(lardon3d_project_db_load_task(database, 3, &absent_batch_task) ==
LARDON3D_PROJECT_DB_NOT_FOUND);
batch.next_item_index = 0;
CHECK(lardon3d_project_db_record_feature_extract_batch_task(
database, &batch_snapshot, "features.extract.batch", 1, NULL,
&batch, 21) == LARDON3D_PROJECT_DB_OK);
Lardon3DProjectDbFeatureExtractBatchTask loaded_batch;
CHECK(lardon3d_project_db_load_feature_extract_batch_task(database, 3,
&loaded_batch) ==
LARDON3D_PROJECT_DB_OK &&
loaded_batch.selected_execution_id == execution_id &&
loaded_batch.next_item_index == 0 &&
memcmp(loaded_batch.parameter_fingerprint,
batch.parameter_fingerprint, 32) == 0);
CHECK(lardon3d_project_db_advance_feature_extract_batch_task(database, 3, 0,
1) ==
LARDON3D_PROJECT_DB_CONSTRAINT);
unsigned char source_hash[32] = {0};
unsigned char feature_hash[32] = {9};
char feature_path[LARDON3D_PROJECT_DB_PATH_CAPACITY];
feature_asset_path(feature_hash, feature_path);
Lardon3DProjectDbFeatureSet feature_set;
CHECK(lardon3d_project_db_register_feature_set(
database, 1, "orb", 1, batch.parameter_fingerprint, source_hash,
1, 1, 32, feature_hash, feature_path, 32,
LARDON3D_DB_FEATURE_ASSET_DURABLE, 3, 22, &feature_set) ==
LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_advance_feature_extract_batch_task(database, 3, 0,
1) ==
LARDON3D_PROJECT_DB_OK &&
lardon3d_project_db_advance_feature_extract_batch_task(database, 3, 0,
1) ==
LARDON3D_PROJECT_DB_OK);
/* A lagging generic checkpoint may repeat cursor zero, but the typed prefix
remains at one. Changing any immutable ORB field is a durable conflict. */
CHECK(lardon3d_project_db_record_feature_extract_batch_task(
database, &batch_snapshot, "features.extract.batch", 1, NULL,
&batch, 23) == LARDON3D_PROJECT_DB_OK &&
lardon3d_project_db_load_feature_extract_batch_task(database, 3,
&loaded_batch) ==
LARDON3D_PROJECT_DB_OK &&
loaded_batch.next_item_index == 1);
batch.max_features++;
CHECK(lardon3d_project_db_record_feature_extract_batch_task(
database, &batch_snapshot, "features.extract.batch", 1, NULL,
&batch, 24) == LARDON3D_PROJECT_DB_CONSTRAINT);
lardon3d_project_db_close(database);
database = NULL;
/* CHECK constraints are bypassed deliberately to verify wide-value and
stage/cursor corruption rejection at the public read boundary. */
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
CHECK(sqlite3_exec(raw, "PRAGMA ignore_check_constraints=ON;"
"UPDATE selected_execution_items SET representation_source=1 "
"WHERE execution_id=1;", NULL, NULL, NULL) == SQLITE_OK);
CHECK(sqlite3_close(raw) == SQLITE_OK);
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_load_selected_execution_item(database, execution_id, 0, &loaded) ==
LARDON3D_PROJECT_DB_CORRUPT);
lardon3d_project_db_close(database);
CHECK(sql(path, "UPDATE selected_execution_items SET representation_source=2 "
"WHERE execution_id=1;"));
/* Numeric prefixes in TEXT demonstrate why positive-value checks alone are
insufficient: sqlite3_column_int64() would coerce these to valid IDs. */
CHECK(sql(path, "UPDATE selected_executions SET calibration_scope_id='1x' "
"WHERE execution_id=1;"));
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_load_selected_execution(database, execution_id, &execution) ==
LARDON3D_PROJECT_DB_CORRUPT);
lardon3d_project_db_close(database);
CHECK(sql(path, "UPDATE selected_executions SET calibration_scope_id=1 "
"WHERE execution_id=1;"));
CHECK(sql(path, "UPDATE selected_execution_items SET source_asset_id='2x' "
"WHERE execution_id=1;"));
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_load_selected_execution_item(database, execution_id, 0, &loaded) ==
LARDON3D_PROJECT_DB_CORRUPT);
lardon3d_project_db_close(database);
CHECK(sql(path, "UPDATE selected_execution_items SET source_asset_id=2 "
"WHERE execution_id=1;"));
CHECK(sql(path, "UPDATE selected_execution_items SET image_id='1x' "
"WHERE execution_id=1;"));
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_load_selected_execution_item(database, execution_id, 0, &loaded) ==
LARDON3D_PROJECT_DB_CORRUPT);
lardon3d_project_db_close(database);
CHECK(sql(path, "UPDATE selected_execution_items SET image_id=1 "
"WHERE execution_id=1;"));
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
CHECK(sqlite3_exec(raw, "PRAGMA ignore_check_constraints=ON;"
"UPDATE selected_executions SET next_item_index=4294967296 "
"WHERE execution_id=1;", NULL, NULL, NULL) == SQLITE_OK);
CHECK(sqlite3_close(raw) == SQLITE_OK);
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_load_selected_execution(database, execution_id, &execution) ==
LARDON3D_PROJECT_DB_CORRUPT);
lardon3d_project_db_close(database);
CHECK(unlink(path) == 0);
CHECK(rmdir(directory) == 0);
return true;
}
int main(void) { return run() ? EXIT_SUCCESS : EXIT_FAILURE; }