From 2cdb50b8d063d8dd752eba01a9e2a3af07a96f13 Mon Sep 17 00:00:00 2001 From: Arnaud Date: Thu, 18 Dec 2025 10:27:29 +0100 Subject: [PATCH] Add C tests --- .gitignore | 3 + examples/c/hello_world.txt | 1 + examples/c/storage.c | 908 +++++++++++++++++++++++++++++++++++++ 3 files changed, 912 insertions(+) create mode 100644 examples/c/hello_world.txt create mode 100644 examples/c/storage.c diff --git a/.gitignore b/.gitignore index f6292dda..c4aa0d4d 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,6 @@ nim.cfg tests/integration/logs data/ + +examples/c/data-dir +examples/c/downloaded_hello.txt diff --git a/examples/c/hello_world.txt b/examples/c/hello_world.txt new file mode 100644 index 00000000..c57eff55 --- /dev/null +++ b/examples/c/hello_world.txt @@ -0,0 +1 @@ +Hello World! \ No newline at end of file diff --git a/examples/c/storage.c b/examples/c/storage.c new file mode 100644 index 00000000..688f57df --- /dev/null +++ b/examples/c/storage.c @@ -0,0 +1,908 @@ +#include +#include +#include +#include +#include +#include +#include "../../library/libstorage.h" + +#define MAX_RETRIES 500 + +typedef struct +{ + int ret; + char *msg; + char *chunk; + size_t len; +} Resp; + +static Resp *alloc_resp(void) +{ + Resp *r = (Resp *)calloc(1, sizeof(Resp)); + r->ret = -1; + return r; +} + +static void free_resp(Resp *r) +{ + if (!r) + { + return; + } + + if (r->msg) + { + free(r->msg); + } + + if (r->chunk) + { + free(r->chunk); + } + + free(r); +} + +static int get_ret(Resp *r) +{ + if (!r) + { + return RET_ERR; + } + + return r->ret; +} + +static void wait_resp(Resp *r) +{ + int retries = 0; + + while (get_ret(r) == -1 && retries < MAX_RETRIES) + { + usleep(1000 * 100); // 100 ms + retries++; + } +} + +static int is_resp_ok(Resp *r, char **res) +{ + if (!r) + { + return RET_ERR; + } + + wait_resp(r); + + int ret = (r->ret == RET_OK) ? RET_OK : RET_ERR; + + if (res) + { + *res = NULL; + } + + if (r->chunk) + { + *res = strdup(r->chunk); + } + else if (res && r->msg) + { + *res = strdup(r->msg); + } + + free_resp(r); + + return ret; +} + +static void callback(int ret, const char *msg, size_t len, void *userData) +{ + Resp *r = (Resp *)userData; + + if (!r) + { + return; + } + + r->ret = ret; + + if (r->msg) + { + free(r->msg); + r->msg = NULL; + r->len = 0; + } + + if (ret == RET_PROGRESS && msg && len > 0 && r->chunk) + { + memcpy(r->chunk, msg, len); + r->chunk[len] = '\0'; + r->len = len; + } + + if (msg && len > 0) + { + r->msg = (char *)malloc(len + 1); + + if (!r->msg) + { + r->len = 0; + return; + } + + memcpy(r->msg, msg, len); + r->msg[len] = '\0'; + r->len = len; + } + else + { + r->msg = NULL; + r->len = 0; + } +} + +static int read_file(const char *filepath, char **res) +{ + FILE *file; + char c; + char content[100]; + + file = fopen(filepath, "r"); + + if (file == NULL) + { + return RET_ERR; + } + + fgets(content, 100, file); + + *res = strdup(content); + + fclose(file); + + return RET_OK; +} + +int setup(void **storage_ctx) +{ + // Initialize Nim runtime + extern void libstorageNimMain(void); + libstorageNimMain(); + + Resp *r = alloc_resp(); + const char *cfg = "{\"log-level\":\"WARN\",\"data-dir\":\"./data-dir\"}"; + void *ctx = storage_new(cfg, (StorageCallback)callback, r); + + if (!ctx) + { + free_resp(r); + return RET_ERR; + } + + wait_resp(r); + + if (r->ret != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + (*storage_ctx) = ctx; + + free_resp(r); + + return RET_OK; +} + +int start(void *storage_ctx) +{ + Resp *r = alloc_resp(); + + if (storage_start(storage_ctx, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + return is_resp_ok(r, NULL); +} + +int cleanup(void *storage_ctx) +{ + Resp *r = alloc_resp(); + + // Stop node + if (storage_stop(storage_ctx, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + if (is_resp_ok(r, NULL) != RET_OK) + { + return RET_ERR; + } + + r = alloc_resp(); + + // Close node + if (storage_close(storage_ctx, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + if (is_resp_ok(r, NULL) != RET_OK) + { + return RET_ERR; + } + + r = alloc_resp(); + + // Destroy node + // No need to wait here as storage_destroy is synchronous + if (storage_destroy(storage_ctx, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + free_resp(r); + + return RET_OK; +} + +int check_version(void *storage_ctx) +{ + char *res = NULL; + + Resp *r = alloc_resp(); + + // No need to wait here as storage_version is synchronous + if (storage_version(storage_ctx, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + free_resp(r); + + return RET_OK; +} + +int check_repo(void *storage_ctx) +{ + Resp *r = alloc_resp(); + char *res = NULL; + + if (storage_repo(storage_ctx, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + int ret = is_resp_ok(r, &res); + + if (strcmp(res, "./data-dir") != 0) + { + printf("repo mismatch: %s\n", res); + ret = RET_ERR; + } + + free(res); + + return ret; +} + +int check_debug(void *storage_ctx) +{ + Resp *r = alloc_resp(); + char *res = NULL; + + if (storage_debug(storage_ctx, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + int ret = is_resp_ok(r, &res); + + // Simple check to ensure the response contains spr + if (strstr(res, "spr") == NULL) + { + fprintf(stderr, "debug content mismatch, res:%s\n", res); + ret = RET_ERR; + } + + free(res); + + return ret; +} + +int check_spr(void *storage_ctx) +{ + Resp *r = alloc_resp(); + char *res = NULL; + + if (storage_spr(storage_ctx, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + int ret = is_resp_ok(r, &res); + + if (strstr(res, "spr") == NULL) + { + fprintf(stderr, "spr content mismatch, res:%s\n", res); + ret = RET_ERR; + } + + free(res); + + return ret; +} + +int check_peer_id(void *storage_ctx) +{ + Resp *r = alloc_resp(); + char *res = NULL; + + if (storage_peer_id(storage_ctx, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + return is_resp_ok(r, &res); +} + +int update_log_level(void *storage_ctx, const char *log_level) +{ + char *res = NULL; + + Resp *r = alloc_resp(); + + if (storage_log_level(storage_ctx, log_level, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + return is_resp_ok(r, NULL); +} + +int check_upload_chunk(void *storage_ctx, const char *filepath) +{ + Resp *r = alloc_resp(); + char *res = NULL; + char *session_id = NULL; + const char *payload = "hello world"; + size_t chunk_size = strlen(payload); + + if (storage_upload_init(storage_ctx, filepath, chunk_size, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + if (is_resp_ok(r, &session_id) != RET_OK) + { + return RET_ERR; + } + + uint8_t *chunk = malloc(chunk_size); + if (!chunk) + { + free(session_id); + return RET_ERR; + } + memcpy(chunk, payload, chunk_size); + + r = alloc_resp(); + + if (storage_upload_chunk(storage_ctx, session_id, chunk, chunk_size, (StorageCallback)callback, r) != RET_OK) + { + free(session_id); + free_resp(r); + free(chunk); + return RET_ERR; + } + + if (is_resp_ok(r, NULL) != RET_OK) + { + free(session_id); + free(chunk); + return RET_ERR; + } + + free(chunk); + r = alloc_resp(); + + if (storage_upload_finalize(storage_ctx, session_id, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + free(session_id); + return RET_ERR; + } + + free(session_id); + + int ret = is_resp_ok(r, &res); + + if (res == NULL || strlen(res) == 0) + { + fprintf(stderr, "CID is missing\n"); + ret = RET_ERR; + } + + free(res); + + return ret; +} + +int upload_cancel(void *storage_ctx) +{ + Resp *r = alloc_resp(); + char *session_id = NULL; + size_t chunk_size = 64 * 1024; + + if (storage_upload_init(storage_ctx, "hello.txt", chunk_size, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + if (is_resp_ok(r, &session_id) != RET_OK) + { + return RET_ERR; + } + + r = alloc_resp(); + + if (storage_upload_cancel(storage_ctx, session_id, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + free(session_id); + return RET_ERR; + } + + free(session_id); + + return is_resp_ok(r, NULL); +} + +int check_upload_file(void *storage_ctx, const char *filepath, char **res) +{ + Resp *r = alloc_resp(); + char *session_id = NULL; + size_t chunk_size = 64 * 1024; + + if (storage_upload_init(storage_ctx, filepath, chunk_size, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + if (is_resp_ok(r, &session_id) != RET_OK) + { + return RET_ERR; + } + + r = alloc_resp(); + + if (storage_upload_file(storage_ctx, session_id, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + free(session_id); + return RET_ERR; + } + + free(session_id); + + int ret = is_resp_ok(r, res); + + if (res == NULL || strlen(*res) == 0) + { + fprintf(stderr, "CID is missing\n"); + return RET_ERR; + } + + return ret; +} + +int check_download_stream(void *storage_ctx, const char *cid, const char *filepath) +{ + Resp *r = alloc_resp(); + char *res = NULL; + size_t chunk_size = 64 * 1024; + bool local = true; + + if (storage_download_init(storage_ctx, cid, chunk_size, local, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + if (is_resp_ok(r, NULL) != RET_OK) + { + return RET_ERR; + } + + r = alloc_resp(); + r->chunk = malloc(chunk_size + 1); + + if (storage_download_stream(storage_ctx, cid, chunk_size, local, filepath, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + int ret = is_resp_ok(r, &res); + + if (strncmp(res, "Hello World!", strlen("Hello World!")) != 0) + { + fprintf(stderr, "downloaded content mismatch, res:%s\n", res); + ret = RET_ERR; + } + + if (read_file("downloaded_hello.txt", &res) != RET_OK) + { + fprintf(stderr, "read downloaded file failed\n"); + ret = RET_ERR; + } + + if (strncmp(res, "Hello World!", strlen("Hello World!")) != 0) + { + fprintf(stderr, "downloaded content mismatch, res:%s\n", res); + ret = RET_ERR; + } + + free(res); + + return ret; +} + +int check_download_chunk(void *storage_ctx, const char *cid) +{ + Resp *r = alloc_resp(); + char *res = NULL; + size_t chunk_size = 64 * 1024; + bool local = true; + + if (storage_download_init(storage_ctx, cid, chunk_size, local, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + if (is_resp_ok(r, NULL) != RET_OK) + { + return RET_ERR; + } + + r = alloc_resp(); + r->chunk = malloc(chunk_size + 1); + + if (storage_download_chunk(storage_ctx, cid, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + int ret = is_resp_ok(r, &res); + + if (strncmp(res, "Hello World!", strlen("Hello World!")) != 0) + { + fprintf(stderr, "downloaded chunk content mismatch, res:%s\n", res); + ret = RET_ERR; + } + + free(res); + + return ret; +} + +int check_download_cancel(void *storage_ctx, const char *cid) +{ + Resp *r = alloc_resp(); + + if (storage_download_cancel(storage_ctx, cid, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + return is_resp_ok(r, NULL); +} + +int check_download_manifest(void *storage_ctx, const char *cid) +{ + Resp *r = alloc_resp(); + char *res = NULL; + + if (storage_download_manifest(storage_ctx, cid, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + int ret = is_resp_ok(r, &res); + + const char *expected_manifest = "{\"treeCid\":\"zDzSvJTf8JYwvysKPmG7BtzpbiAHfuwFMRphxm4hdvnMJ4XPJjKX\",\"datasetSize\":12,\"blockSize\":65536,\"filename\":\"hello_world.txt\",\"mimetype\":\"text/plain\",\"protected\":false}"; + + if (strncmp(res, expected_manifest, strlen(expected_manifest)) != 0) + { + fprintf(stderr, "downloaded manifest content mismatch, res:%s\n", res); + ret = RET_ERR; + } + + free(res); + + return ret; +} + +int check_list(void *storage_ctx) +{ + Resp *r = alloc_resp(); + char *res = NULL; + + if (storage_list(storage_ctx, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + int ret = is_resp_ok(r, &res); + + const char *expected_manifest = "{\"treeCid\":\"zDzSvJTf8JYwvysKPmG7BtzpbiAHfuwFMRphxm4hdvnMJ4XPJjKX\",\"datasetSize\":12,\"blockSize\":65536,\"filename\":\"hello_world.txt\",\"mimetype\":\"text/plain\",\"protected\":false}"; + + if (strstr(res, expected_manifest) == NULL) + { + fprintf(stderr, "downloaded manifest content mismatch, res:%s\n", res); + ret = RET_ERR; + } + + free(res); + + return ret; +} + +int check_space(void *storage_ctx) +{ + Resp *r = alloc_resp(); + char *res = NULL; + + if (storage_space(storage_ctx, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + int ret = is_resp_ok(r, &res); + + // Simple check to ensure the response contains totalBlocks + if (strstr(res, "totalBlocks") == NULL) + { + fprintf(stderr, "list content mismatch, res:%s\n", res); + ret = RET_ERR; + } + + free(res); + + return ret; +} + +int check_exists(void *storage_ctx, const char *cid, bool expected) +{ + Resp *r = alloc_resp(); + char *res = NULL; + + if (storage_exists(storage_ctx, cid, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + int ret = is_resp_ok(r, &res); + + if (expected) + { + if (strcmp(res, "true") != 0) + { + fprintf(stderr, "exists content mismatch, res:%s\n", res); + ret = RET_ERR; + } + } + else + { + if (strcmp(res, "false") != 0) + { + fprintf(stderr, "exists content mismatch, res:%s\n", res); + ret = RET_ERR; + } + } + + free(res); + + return ret; +} + +int check_delete(void *storage_ctx, const char *cid) +{ + Resp *r = alloc_resp(); + + if (storage_delete(storage_ctx, cid, (StorageCallback)callback, r) != RET_OK) + { + free_resp(r); + return RET_ERR; + } + + return is_resp_ok(r, NULL); +} + +// TODO: implement check_fetch +// It is a bit complicated because it requires two nodes +// connected together to fetch from peers. +// A good idea would be to use connect function using addresses. +// This test will be quite important when the block engine is re-implemented. +int check_fetch(void *storage_ctx, const char *cid) +{ + return RET_OK; +} + +int main(void) +{ + void *storage_ctx = NULL; + char *res = NULL; + char *cid = NULL; + + if (setup(&storage_ctx) != RET_OK) + { + fprintf(stderr, "setup failed\n"); + return RET_ERR; + } + + if (check_version(storage_ctx) != RET_OK) + { + fprintf(stderr, "check version failed\n"); + return RET_ERR; + } + + if (start(storage_ctx) != RET_OK) + { + fprintf(stderr, "start failed\n"); + return RET_ERR; + } + + if (check_repo(storage_ctx) != RET_OK) + { + fprintf(stderr, "check repo failed\n"); + return RET_ERR; + } + + if (check_debug(storage_ctx) != RET_OK) + { + fprintf(stderr, "check debug failed\n"); + return RET_ERR; + } + + if (check_spr(storage_ctx) != RET_OK) + { + fprintf(stderr, "check spr failed\n"); + return RET_ERR; + } + + if (check_peer_id(storage_ctx) != RET_OK) + { + fprintf(stderr, "check peer_id failed\n"); + return RET_ERR; + } + + if (check_upload_chunk(storage_ctx, "hello_world.txt") != RET_OK) + { + fprintf(stderr, "upload chunk failed\n"); + return RET_ERR; + } + + if (upload_cancel(storage_ctx) != RET_OK) + { + fprintf(stderr, "upload cancel failed\n"); + return RET_ERR; + } + + char *path = realpath("hello_world.txt", NULL); + + if (!path) + { + fprintf(stderr, "realpath failed\n"); + return RET_ERR; + } + + if (check_upload_file(storage_ctx, path, &cid) != RET_OK) + { + fprintf(stderr, "upload file failed\n"); + free(path); + return RET_ERR; + } + + free(path); + + if (check_download_stream(storage_ctx, cid, "downloaded_hello.txt") != RET_OK) + { + fprintf(stderr, "download stream failed\n"); + free(cid); + return RET_ERR; + } + + if (check_download_chunk(storage_ctx, cid) != RET_OK) + { + fprintf(stderr, "download chunk failed\n"); + free(cid); + return RET_ERR; + } + + if (check_download_cancel(storage_ctx, cid) != RET_OK) + { + fprintf(stderr, "download cancel failed\n"); + free(cid); + return RET_ERR; + } + + if (check_download_manifest(storage_ctx, cid) != RET_OK) + { + fprintf(stderr, "download manifest failed\n"); + free(cid); + return RET_ERR; + } + + if (check_list(storage_ctx) != RET_OK) + { + fprintf(stderr, "list failed\n"); + free(cid); + return RET_ERR; + } + + if (check_space(storage_ctx) != RET_OK) + { + fprintf(stderr, "space failed\n"); + free(cid); + return RET_ERR; + } + + if (check_exists(storage_ctx, cid, true) != RET_OK) + { + fprintf(stderr, "exists failed\n"); + free(cid); + return RET_ERR; + } + + if (check_delete(storage_ctx, cid) != RET_OK) + { + fprintf(stderr, "delete failed\n"); + free(cid); + return RET_ERR; + } + + if (check_exists(storage_ctx, cid, false) != RET_OK) + { + fprintf(stderr, "exists failed\n"); + free(cid); + return RET_ERR; + } + + free(cid); + + if (update_log_level(storage_ctx, "INFO") != RET_OK) + { + fprintf(stderr, "update log level failed\n"); + return RET_ERR; + } + + if (cleanup(storage_ctx) != RET_OK) + { + fprintf(stderr, "cleanup failed\n"); + return RET_ERR; + } + + return RET_OK; +} \ No newline at end of file