mirror of
https://github.com/logos-storage/easylibstorage.git
synced 2026-02-09 11:23:06 +00:00
Implement easylibstorage wrapper with tests, wire up console commands
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
6e6d049577
commit
691c2a8d8a
@ -2,7 +2,9 @@ cmake_minimum_required(VERSION 3.14)
|
||||
project(storageconsole C)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
||||
add_executable(storageconsole main.c
|
||||
add_executable(storageconsole
|
||||
main.c
|
||||
easylibstorage.c
|
||||
easylibstorage.h
|
||||
)
|
||||
|
||||
@ -28,3 +30,18 @@ else()
|
||||
message(WARNING "libstorage not found. Build or provide it before linking.")
|
||||
endif()
|
||||
|
||||
# --- Tests ---
|
||||
enable_testing()
|
||||
|
||||
add_executable(test_runner
|
||||
tests/test_runner.c
|
||||
easylibstorage.c
|
||||
tests/mock_libstorage.c
|
||||
)
|
||||
|
||||
target_include_directories(test_runner PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
"${LOGOS_STORAGE_NIM_ROOT}/library"
|
||||
)
|
||||
|
||||
add_test(NAME easylibstorage_tests COMMAND test_runner)
|
||||
|
||||
@ -7,7 +7,7 @@ The headers for `libstorage` are located at `/home/giuliano/Work/Status/logos-st
|
||||
|
||||
The code for libstorage is located at `home/giuliano/Work/Status/logos-storage-nim/library`.
|
||||
|
||||
There are examples for how libstorage can be used in `/home/giuliano/Work/Status/logos-storage-nim/examples/c/examples.c`.
|
||||
There are examples for how libstorage can be used in `/home/giuliano/Work/Status/logos-storage-nim/examples/c/storage.c`.
|
||||
|
||||
# Development Process (TDD)
|
||||
You MUST follow a Test Driven Development approach. Since no test environment exists yet, your FIRST task is to:
|
||||
@ -22,6 +22,8 @@ Then, for *each* function in `easylibstorage.h`:
|
||||
**CRITICAL**: This refactoring step is VERY IMPORTANT. You MUST look for ways to simplify, deduplicate, and coalesce code here, WITHOUT OVERCOMPLICATING. **SIMPLICITY IS KEY AND YOUR GUIDING PRINCIPLE.**
|
||||
|
||||
# API Implementation Details
|
||||
- **Configuration JSON.** The keys described in the node_config struct should be passed as kebab-case into the config JSON string. See an example
|
||||
in the `storage.c` file. `bootstrap-node` must be passed as a string array.
|
||||
- **Memory Management**: Clearly document who owns returned pointers (e.g., CIDs). Ensure no memory leaks.
|
||||
- **Log Levels**: Map the `char *log_level` in the config to the internal `enum log_level`.
|
||||
- **Download Return**: Note that `e_storage_download` currently returns `STORAGE_NODE` in the header. If this is a mistake, change it to return `int` (status code) and update the header.
|
||||
|
||||
240
easylibstorage.c
Normal file
240
easylibstorage.c
Normal file
@ -0,0 +1,240 @@
|
||||
#include "easylibstorage.h"
|
||||
#include "libstorage.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define MAX_RETRIES 250
|
||||
#define POLL_INTERVAL_US (100 * 1000)
|
||||
#define DEFAULT_CHUNK_SIZE (64 * 1024)
|
||||
|
||||
typedef struct {
|
||||
int ret;
|
||||
char *msg;
|
||||
size_t len;
|
||||
progress_callback pcb;
|
||||
int bytes_done;
|
||||
} resp;
|
||||
|
||||
static resp *resp_alloc(void) {
|
||||
resp *r = calloc(1, sizeof(resp));
|
||||
r->ret = -1;
|
||||
return r;
|
||||
}
|
||||
|
||||
static void resp_free(resp *r) {
|
||||
if (!r) return;
|
||||
free(r->msg);
|
||||
free(r);
|
||||
}
|
||||
|
||||
static void resp_wait(resp *r) {
|
||||
for (int i = 0; i < MAX_RETRIES && r->ret == -1; i++) {
|
||||
usleep(POLL_INTERVAL_US);
|
||||
}
|
||||
}
|
||||
|
||||
// Callback for simple (non-progress) async operations.
|
||||
static void on_complete(int ret, const char *msg, size_t len, void *userData) {
|
||||
resp *r = userData;
|
||||
if (!r) return;
|
||||
|
||||
free(r->msg);
|
||||
r->msg = NULL;
|
||||
r->len = 0;
|
||||
|
||||
if (msg && len > 0) {
|
||||
r->msg = malloc(len + 1);
|
||||
if (r->msg) {
|
||||
memcpy(r->msg, msg, len);
|
||||
r->msg[len] = '\0';
|
||||
r->len = len;
|
||||
}
|
||||
}
|
||||
|
||||
r->ret = ret;
|
||||
}
|
||||
|
||||
// Callback for operations that report progress before completing.
|
||||
static void on_progress(int ret, const char *msg, size_t len, void *userData) {
|
||||
resp *r = userData;
|
||||
if (!r) return;
|
||||
|
||||
if (ret == RET_PROGRESS) {
|
||||
r->bytes_done += (int) len;
|
||||
if (r->pcb) {
|
||||
r->pcb(0, r->bytes_done, ret);
|
||||
}
|
||||
return; // don't set r->ret yet — still in progress
|
||||
}
|
||||
|
||||
// Final callback (RET_OK or RET_ERR)
|
||||
free(r->msg);
|
||||
r->msg = NULL;
|
||||
r->len = 0;
|
||||
|
||||
if (msg && len > 0) {
|
||||
r->msg = malloc(len + 1);
|
||||
if (r->msg) {
|
||||
memcpy(r->msg, msg, len);
|
||||
r->msg[len] = '\0';
|
||||
r->len = len;
|
||||
}
|
||||
}
|
||||
|
||||
r->ret = ret;
|
||||
}
|
||||
|
||||
// Dispatches an async call, waits for completion, extracts the result.
|
||||
// Returns RET_OK/RET_ERR. If dispatch_ret != RET_OK, returns RET_ERR immediately.
|
||||
// If out is non-NULL and the response has a message, *out receives a strdup'd copy.
|
||||
static int call_wait(int dispatch_ret, resp *r, char **out) {
|
||||
if (dispatch_ret != RET_OK) {
|
||||
resp_free(r);
|
||||
return RET_ERR;
|
||||
}
|
||||
|
||||
resp_wait(r);
|
||||
|
||||
int result = (r->ret == RET_OK) ? RET_OK : RET_ERR;
|
||||
|
||||
if (out) {
|
||||
*out = r->msg ? strdup(r->msg) : NULL;
|
||||
}
|
||||
|
||||
resp_free(r);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int nim_initialized = 0;
|
||||
|
||||
STORAGE_NODE e_storage_new(node_config config) {
|
||||
if (!nim_initialized) {
|
||||
extern void libstorageNimMain(void);
|
||||
libstorageNimMain();
|
||||
nim_initialized = 1;
|
||||
}
|
||||
|
||||
// Build JSON config string.
|
||||
// Format: {"api-port":N,"disc-port":N,"data-dir":"...","log-level":"...","bootstrap-node":["..."]}
|
||||
char json[2048];
|
||||
int pos = 0;
|
||||
|
||||
pos += snprintf(json + pos, sizeof(json) - pos, "{\"api-port\":%d,\"disc-port\":%d", config.api_port,
|
||||
config.disc_port);
|
||||
|
||||
if (config.data_dir) {
|
||||
pos += snprintf(json + pos, sizeof(json) - pos, ",\"data-dir\":\"%s\"", config.data_dir);
|
||||
}
|
||||
|
||||
if (config.log_level) {
|
||||
pos += snprintf(json + pos, sizeof(json) - pos, ",\"log-level\":\"%s\"", config.log_level);
|
||||
}
|
||||
|
||||
if (config.bootstrap_node) {
|
||||
pos += snprintf(json + pos, sizeof(json) - pos, ",\"bootstrap-node\":[\"%s\"]", config.bootstrap_node);
|
||||
}
|
||||
|
||||
snprintf(json + pos, sizeof(json) - pos, "}");
|
||||
|
||||
resp *r = resp_alloc();
|
||||
void *ctx = storage_new(json, (StorageCallback) on_complete, r);
|
||||
|
||||
if (!ctx) {
|
||||
resp_free(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
resp_wait(r);
|
||||
|
||||
if (r->ret != RET_OK) {
|
||||
resp_free(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
resp_free(r);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
int e_storage_start(STORAGE_NODE node) {
|
||||
if (!node) return RET_ERR;
|
||||
resp *r = resp_alloc();
|
||||
return call_wait(storage_start(node, (StorageCallback) on_complete, r), r, NULL);
|
||||
}
|
||||
|
||||
int e_storage_stop(STORAGE_NODE node) {
|
||||
if (!node) return RET_ERR;
|
||||
resp *r = resp_alloc();
|
||||
return call_wait(storage_stop(node, (StorageCallback) on_complete, r), r, NULL);
|
||||
}
|
||||
|
||||
int e_storage_destroy(STORAGE_NODE node) {
|
||||
if (!node) return RET_ERR;
|
||||
|
||||
// Close first (tolerate failure)
|
||||
resp *r = resp_alloc();
|
||||
call_wait(storage_close(node, (StorageCallback) on_complete, r), r, NULL);
|
||||
|
||||
// Destroy
|
||||
r = resp_alloc();
|
||||
return call_wait(storage_destroy(node, (StorageCallback) on_complete, r), r, NULL);
|
||||
}
|
||||
|
||||
char *e_storage_upload(STORAGE_NODE node, const char *filepath, progress_callback cb) {
|
||||
if (!node || !filepath) return NULL;
|
||||
|
||||
// Init upload session
|
||||
resp *r = resp_alloc();
|
||||
char *session_id = NULL;
|
||||
int ret = call_wait(storage_upload_init(node, filepath, DEFAULT_CHUNK_SIZE, (StorageCallback) on_complete, r), r,
|
||||
&session_id);
|
||||
if (ret != RET_OK || !session_id) {
|
||||
free(session_id);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Upload file with progress
|
||||
r = resp_alloc();
|
||||
r->pcb = cb;
|
||||
char *cid = NULL;
|
||||
ret = call_wait(storage_upload_file(node, session_id, (StorageCallback) on_progress, r), r, &cid);
|
||||
free(session_id);
|
||||
|
||||
if (ret != RET_OK) {
|
||||
free(cid);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (cb) {
|
||||
printf("\n"); // newline after progress output
|
||||
}
|
||||
|
||||
return cid;
|
||||
}
|
||||
|
||||
int e_storage_download(STORAGE_NODE node, const char *cid, const char *filepath, progress_callback cb) {
|
||||
if (!node || !cid || !filepath) return RET_ERR;
|
||||
|
||||
// Init download
|
||||
resp *r = resp_alloc();
|
||||
int ret =
|
||||
call_wait(storage_download_init(node, cid, DEFAULT_CHUNK_SIZE, false, (StorageCallback) on_complete, r), r,
|
||||
NULL);
|
||||
if (ret != RET_OK) return RET_ERR;
|
||||
|
||||
// Stream to file with progress
|
||||
r = resp_alloc();
|
||||
r->pcb = cb;
|
||||
ret = call_wait(
|
||||
storage_download_stream(node, cid, DEFAULT_CHUNK_SIZE, false, filepath, (StorageCallback) on_progress, r),
|
||||
r, NULL);
|
||||
|
||||
if (cb) {
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -1,16 +1,7 @@
|
||||
#ifndef STORAGECONSOLE_EASYLIBSTORAGE_H
|
||||
#define STORAGECONSOLE_EASYLIBSTORAGE_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#define STORAGE_NODE void *
|
||||
#define CID char *
|
||||
|
||||
enum log_level {
|
||||
WARN,
|
||||
INFO,
|
||||
DEBUG,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
int api_port;
|
||||
@ -22,11 +13,17 @@ typedef struct {
|
||||
|
||||
typedef void (*progress_callback)(int total, int complete, int status);
|
||||
|
||||
// Creates a new storage node. Returns opaque pointer, or NULL on failure.
|
||||
STORAGE_NODE e_storage_new(node_config config);
|
||||
|
||||
int e_storage_start(STORAGE_NODE node);
|
||||
int e_storage_stop(STORAGE_NODE node);
|
||||
int e_storage_destroy(STORAGE_NODE node);
|
||||
CID e_storage_upload(STORAGE_NODE node, FILE *input, progress_callback cb);
|
||||
STORAGE_NODE e_storage_download(STORAGE_NODE node, CID cid, FILE *output, progress_callback cb);
|
||||
|
||||
// Uploads a file. Returns CID string on success (caller must free), or NULL on failure.
|
||||
char *e_storage_upload(STORAGE_NODE node, const char *filepath, progress_callback cb);
|
||||
|
||||
// Downloads content identified by cid to filepath. Returns 0 on success.
|
||||
int e_storage_download(STORAGE_NODE node, const char *cid, const char *filepath, progress_callback cb);
|
||||
|
||||
#endif // STORAGECONSOLE_EASYLIBSTORAGE_H
|
||||
|
||||
139
main.c
139
main.c
@ -1,5 +1,6 @@
|
||||
#include "easylibstorage.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@ -8,7 +9,7 @@ typedef struct {
|
||||
void *ctx;
|
||||
} console;
|
||||
|
||||
typedef void (*fn)(void *, console *);
|
||||
typedef void (*fn)(char *, console *);
|
||||
|
||||
struct command {
|
||||
const char *name;
|
||||
@ -16,20 +17,16 @@ struct command {
|
||||
fn command;
|
||||
};
|
||||
|
||||
int n_commands();
|
||||
int n_commands(void);
|
||||
static const struct command commands[];
|
||||
|
||||
void cmd_help(void *_, console *c) {
|
||||
void cmd_help(char *args, console *c) {
|
||||
printf("Commands:\n");
|
||||
for (int i = 0; i < n_commands(); i++) {
|
||||
printf(" [%s]: %s\n", commands[i].name, commands[i].desc);
|
||||
}
|
||||
}
|
||||
|
||||
void cmd_quit(void *_, console *c) {
|
||||
|
||||
}
|
||||
|
||||
void progress_print(int total, int complete, int status) {
|
||||
if (total > 0) {
|
||||
printf("\r %d / %d bytes", complete, total);
|
||||
@ -39,32 +36,127 @@ void progress_print(int total, int complete, int status) {
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void cmd_start(void *args, console *c) {
|
||||
void cmd_start(char *args, console *c) {
|
||||
if (c->ctx) {
|
||||
printf("Node already running. Stop it first.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int api_port = 0, disc_port = 0;
|
||||
char bootstrap[2048] = {0};
|
||||
|
||||
if (!args || sscanf(args, "%d %d %2047s", &api_port, &disc_port, bootstrap) < 2) {
|
||||
printf("Usage: start [API_PORT] [DISC_PORT] [BOOTSTRAP_NODE]\n");
|
||||
return;
|
||||
}
|
||||
|
||||
node_config cfg = {0};
|
||||
cfg.api_port = api_port;
|
||||
cfg.disc_port = disc_port;
|
||||
cfg.data_dir = "./data";
|
||||
cfg.log_level = "INFO";
|
||||
cfg.bootstrap_node = bootstrap[0] ? bootstrap : NULL;
|
||||
|
||||
printf("Creating node...\n");
|
||||
STORAGE_NODE node = e_storage_new(cfg);
|
||||
if (!node) {
|
||||
printf("Failed to create node.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Starting node...\n");
|
||||
if (e_storage_start(node) != 0) {
|
||||
printf("Failed to start node.\n");
|
||||
e_storage_destroy(node);
|
||||
return;
|
||||
}
|
||||
|
||||
c->ctx = node;
|
||||
printf("Node started on API port %d, discovery port %d.\n", api_port, disc_port);
|
||||
}
|
||||
|
||||
void cmd_stop(void *args, console *c) {
|
||||
void cmd_stop(char *args, console *c) {
|
||||
if (!c->ctx) {
|
||||
printf("No node running.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Stopping node...\n");
|
||||
e_storage_stop(c->ctx);
|
||||
e_storage_destroy(c->ctx);
|
||||
c->ctx = NULL;
|
||||
printf("Node stopped.\n");
|
||||
}
|
||||
|
||||
void cmd_upload(void *args, console *c) {
|
||||
void cmd_upload(char *args, console *c) {
|
||||
if (!c->ctx) {
|
||||
printf("No node running. Start one first.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args || args[0] == '\0') {
|
||||
printf("Usage: upload [PATH]\n");
|
||||
return;
|
||||
}
|
||||
|
||||
char resolved[PATH_MAX];
|
||||
if (!realpath(args, resolved)) {
|
||||
printf("File not found: %s\n", args);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Uploading %s...\n", resolved);
|
||||
char *cid = e_storage_upload(c->ctx, resolved, progress_print);
|
||||
if (cid) {
|
||||
printf("CID: %s\n", cid);
|
||||
free(cid);
|
||||
} else {
|
||||
printf("Upload failed.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void cmd_download(void *args, console *c) {
|
||||
void cmd_download(char *args, console *c) {
|
||||
if (!c->ctx) {
|
||||
printf("No node running. Start one first.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
char cid[256] = {0};
|
||||
char path[2048] = {0};
|
||||
|
||||
if (!args || sscanf(args, "%255s %2047s", cid, path) < 2) {
|
||||
printf("Usage: download [CID] [PATH]\n");
|
||||
return;
|
||||
}
|
||||
|
||||
printf("Downloading %s to %s...\n", cid, path);
|
||||
if (e_storage_download(c->ctx, cid, path, progress_print) == 0) {
|
||||
printf("Download complete.\n");
|
||||
} else {
|
||||
printf("Download failed.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void cmd_quit(char *args, console *c) {
|
||||
if (c->ctx) {
|
||||
printf("Stopping node...\n");
|
||||
e_storage_stop(c->ctx);
|
||||
e_storage_destroy(c->ctx);
|
||||
c->ctx = NULL;
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static const struct command commands[] = {
|
||||
{"help", "prints this help message", cmd_help},
|
||||
{"quit", "quits this program", cmd_quit},
|
||||
{"start", "[API PORT] [DISC PORT] [BOOTSTRAP NODE] creates and starts a node", cmd_start},
|
||||
{"start", "[API_PORT] [DISC_PORT] [BOOTSTRAP_NODE] creates and starts a node", cmd_start},
|
||||
{"stop", "stops and destroys the node", cmd_stop},
|
||||
{"upload", "[PATH] uploads a file to the node", cmd_upload},
|
||||
{"download", "[CID] [PATH] downloads content to a file", cmd_download},
|
||||
};
|
||||
|
||||
int n_commands() { return sizeof(commands) / sizeof(commands[0]); }
|
||||
int n_commands(void) { return sizeof(commands) / sizeof(commands[0]); }
|
||||
|
||||
int main(void) {
|
||||
char buf[4096];
|
||||
@ -82,22 +174,29 @@ int main(void) {
|
||||
}
|
||||
buf[strcspn(buf, "\n")] = 0;
|
||||
|
||||
char *cmd = strtok(buf, " ");
|
||||
if (cmd == NULL) {
|
||||
// user has input an empty string
|
||||
if (buf[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Split at first space: cmd points to command name, rest points to arguments
|
||||
char *rest = strchr(buf, ' ');
|
||||
if (rest) {
|
||||
*rest = '\0';
|
||||
rest++;
|
||||
// Skip leading spaces in arguments
|
||||
while (*rest == ' ') rest++;
|
||||
if (*rest == '\0') rest = NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < n_commands(); i++) {
|
||||
if (strcmp(cmd, commands[i].name) == 0) {
|
||||
char *arg = strtok(NULL, " ");
|
||||
commands[i].command(arg, &c);
|
||||
if (strcmp(buf, commands[i].name) == 0) {
|
||||
commands[i].command(rest, &c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == n_commands()) {
|
||||
printf("Invalid command %s\n", buf);
|
||||
printf("Invalid command: %s\n", buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
90
tests/mock_libstorage.c
Normal file
90
tests/mock_libstorage.c
Normal file
@ -0,0 +1,90 @@
|
||||
#include "libstorage.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// A fake context to return from storage_new.
|
||||
static int fake_ctx_data = 42;
|
||||
|
||||
void libstorageNimMain(void) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
void *storage_new(const char *configJson, StorageCallback callback, void *userData) {
|
||||
if (callback) {
|
||||
callback(RET_OK, "ok", 2, userData);
|
||||
}
|
||||
return &fake_ctx_data;
|
||||
}
|
||||
|
||||
int storage_start(void *ctx, StorageCallback callback, void *userData) {
|
||||
if (!ctx) return RET_ERR;
|
||||
if (callback) {
|
||||
callback(RET_OK, "started", 7, userData);
|
||||
}
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
int storage_stop(void *ctx, StorageCallback callback, void *userData) {
|
||||
if (!ctx) return RET_ERR;
|
||||
if (callback) {
|
||||
callback(RET_OK, "stopped", 7, userData);
|
||||
}
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
int storage_close(void *ctx, StorageCallback callback, void *userData) {
|
||||
if (!ctx) return RET_ERR;
|
||||
if (callback) {
|
||||
callback(RET_OK, "closed", 6, userData);
|
||||
}
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
int storage_destroy(void *ctx, StorageCallback callback, void *userData) {
|
||||
if (!ctx) return RET_ERR;
|
||||
if (callback) {
|
||||
callback(RET_OK, "destroyed", 9, userData);
|
||||
}
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
int storage_upload_init(void *ctx, const char *filepath, size_t chunkSize, StorageCallback callback, void *userData) {
|
||||
if (!ctx) return RET_ERR;
|
||||
// Return a fake session ID
|
||||
const char *session_id = "mock-session-123";
|
||||
if (callback) {
|
||||
callback(RET_OK, session_id, strlen(session_id), userData);
|
||||
}
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
int storage_upload_file(void *ctx, const char *sessionId, StorageCallback callback, void *userData) {
|
||||
if (!ctx) return RET_ERR;
|
||||
// Fire a progress callback first, then final OK with CID
|
||||
if (callback) {
|
||||
callback(RET_PROGRESS, "chunk", 5, userData);
|
||||
const char *cid = "zDvZRwzmAbCdEfGhIjKlMnOpQrStUvWxYz0123456789ABCD";
|
||||
callback(RET_OK, cid, strlen(cid), userData);
|
||||
}
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
int storage_download_init(void *ctx, const char *cid, size_t chunkSize, bool local, StorageCallback callback,
|
||||
void *userData) {
|
||||
if (!ctx) return RET_ERR;
|
||||
if (callback) {
|
||||
callback(RET_OK, "init", 4, userData);
|
||||
}
|
||||
return RET_OK;
|
||||
}
|
||||
|
||||
int storage_download_stream(void *ctx, const char *cid, size_t chunkSize, bool local, const char *filepath,
|
||||
StorageCallback callback, void *userData) {
|
||||
if (!ctx) return RET_ERR;
|
||||
if (callback) {
|
||||
callback(RET_PROGRESS, "data", 4, userData);
|
||||
callback(RET_OK, "done", 4, userData);
|
||||
}
|
||||
return RET_OK;
|
||||
}
|
||||
155
tests/test_runner.c
Normal file
155
tests/test_runner.c
Normal file
@ -0,0 +1,155 @@
|
||||
#include "easylibstorage.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define RET_OK 0
|
||||
#define RET_ERR 1
|
||||
|
||||
static int tests_run = 0;
|
||||
static int tests_passed = 0;
|
||||
|
||||
#define RUN_TEST(fn) \
|
||||
do { \
|
||||
tests_run++; \
|
||||
printf(" %-30s", #fn); \
|
||||
fn(); \
|
||||
tests_passed++; \
|
||||
printf(" OK\n"); \
|
||||
} while (0)
|
||||
|
||||
static node_config default_config(void) {
|
||||
node_config cfg = {0};
|
||||
cfg.api_port = 8080;
|
||||
cfg.disc_port = 8090;
|
||||
cfg.data_dir = "./test-data";
|
||||
cfg.log_level = "WARN";
|
||||
cfg.bootstrap_node = NULL;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
// --- Tests ---
|
||||
|
||||
static void test_new(void) {
|
||||
node_config cfg = default_config();
|
||||
cfg.bootstrap_node = "spr:abc123";
|
||||
STORAGE_NODE node = e_storage_new(cfg);
|
||||
assert(node != NULL);
|
||||
}
|
||||
|
||||
static void test_new_defaults(void) {
|
||||
node_config cfg = {0};
|
||||
cfg.api_port = 9000;
|
||||
cfg.disc_port = 9010;
|
||||
STORAGE_NODE node = e_storage_new(cfg);
|
||||
assert(node != NULL);
|
||||
}
|
||||
|
||||
static void test_start(void) {
|
||||
node_config cfg = default_config();
|
||||
STORAGE_NODE node = e_storage_new(cfg);
|
||||
assert(node != NULL);
|
||||
int ret = e_storage_start(node);
|
||||
assert(ret == RET_OK);
|
||||
}
|
||||
|
||||
static void test_start_null(void) {
|
||||
int ret = e_storage_start(NULL);
|
||||
assert(ret == RET_ERR);
|
||||
}
|
||||
|
||||
static void test_stop(void) {
|
||||
node_config cfg = default_config();
|
||||
STORAGE_NODE node = e_storage_new(cfg);
|
||||
assert(node != NULL);
|
||||
e_storage_start(node);
|
||||
int ret = e_storage_stop(node);
|
||||
assert(ret == RET_OK);
|
||||
}
|
||||
|
||||
static void test_destroy(void) {
|
||||
node_config cfg = default_config();
|
||||
STORAGE_NODE node = e_storage_new(cfg);
|
||||
assert(node != NULL);
|
||||
int ret = e_storage_destroy(node);
|
||||
assert(ret == RET_OK);
|
||||
}
|
||||
|
||||
static void test_destroy_null(void) {
|
||||
int ret = e_storage_destroy(NULL);
|
||||
assert(ret == RET_ERR);
|
||||
}
|
||||
|
||||
static void test_upload(void) {
|
||||
node_config cfg = default_config();
|
||||
STORAGE_NODE node = e_storage_new(cfg);
|
||||
assert(node != NULL);
|
||||
e_storage_start(node);
|
||||
|
||||
char *cid = e_storage_upload(node, "/tmp/test.txt", NULL);
|
||||
assert(cid != NULL);
|
||||
assert(strlen(cid) > 0);
|
||||
free(cid);
|
||||
}
|
||||
|
||||
static void test_upload_null(void) {
|
||||
char *cid = e_storage_upload(NULL, "/tmp/test.txt", NULL);
|
||||
assert(cid == NULL);
|
||||
}
|
||||
|
||||
static void test_download(void) {
|
||||
node_config cfg = default_config();
|
||||
STORAGE_NODE node = e_storage_new(cfg);
|
||||
assert(node != NULL);
|
||||
e_storage_start(node);
|
||||
|
||||
int ret = e_storage_download(node, "zDvZRwzmSomeCid", "/tmp/out.dat", NULL);
|
||||
assert(ret == RET_OK);
|
||||
}
|
||||
|
||||
static void test_download_null(void) {
|
||||
int ret = e_storage_download(NULL, "zDvZRwzmSomeCid", "/tmp/out.dat", NULL);
|
||||
assert(ret == RET_ERR);
|
||||
}
|
||||
|
||||
static void test_full_lifecycle(void) {
|
||||
node_config cfg = default_config();
|
||||
cfg.bootstrap_node = "spr:node1";
|
||||
|
||||
STORAGE_NODE node = e_storage_new(cfg);
|
||||
assert(node != NULL);
|
||||
|
||||
assert(e_storage_start(node) == RET_OK);
|
||||
|
||||
char *cid = e_storage_upload(node, "/tmp/lifecycle.txt", NULL);
|
||||
assert(cid != NULL);
|
||||
assert(strlen(cid) > 0);
|
||||
|
||||
assert(e_storage_download(node, cid, "/tmp/lifecycle_out.dat", NULL) == RET_OK);
|
||||
free(cid);
|
||||
|
||||
assert(e_storage_stop(node) == RET_OK);
|
||||
assert(e_storage_destroy(node) == RET_OK);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("Running easylibstorage tests...\n");
|
||||
|
||||
RUN_TEST(test_new);
|
||||
RUN_TEST(test_new_defaults);
|
||||
RUN_TEST(test_start);
|
||||
RUN_TEST(test_start_null);
|
||||
RUN_TEST(test_stop);
|
||||
RUN_TEST(test_destroy);
|
||||
RUN_TEST(test_destroy_null);
|
||||
RUN_TEST(test_upload);
|
||||
RUN_TEST(test_upload_null);
|
||||
RUN_TEST(test_download);
|
||||
RUN_TEST(test_download_null);
|
||||
RUN_TEST(test_full_lifecycle);
|
||||
|
||||
printf("\n%d/%d tests passed.\n", tests_passed, tests_run);
|
||||
return (tests_passed == tests_run) ? 0 : 1;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user