nim-ffi/tests/e2e/c_abi/test_echo_c_abi.c

100 lines
3.2 KiB
C

/* End-to-end test for the CBOR-free `abi = c` echo bindings. Unlike the CBOR C
* backend, this header links no TinyCBOR: the flat structs in echo.h are the C
* ABI, strings are plain borrowed `const char*`. The test drives the same
* async, callback-per-call surface — constructor, an object-returning method
* and teardown — copying out what each callback delivers (owned by the binding,
* valid only for the call) and polling a `done` flag to sequence the async
* calls. A string-returning method (echoVersion) rides the CBOR-free scalar
* fast path instead of a flat `_CWire` wrapper, so it has no c_abi binding yet
* (foreign codegen for the scalar shape is a follow-up) and isn't exercised
* here. */
#include "echo.h"
#include <assert.h>
#include <stdatomic.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
/* The `done` flag is a C11 atomic: the callback fires on the FFI thread and
* stores it with release ordering after filling the waiter's fields, and the
* poller loads it with acquire ordering — so the field writes are visible
* (and race-free under TSan) once `done` is seen set. */
static void wait_done(atomic_int* done) {
for (int i = 0; i < 500 && !atomic_load_explicit(done, memory_order_acquire); i++) {
struct timespec t = {0, 10 * 1000 * 1000}; /* 10ms */
nanosleep(&t, NULL);
}
assert(atomic_load_explicit(done, memory_order_acquire));
}
typedef struct {
atomic_int done;
int err_code;
EchoCtx* ctx;
char err[256];
} CreateWaiter;
static void on_created(int ec, EchoCtx* ctx, const char* em, void* ud) {
CreateWaiter* w = (CreateWaiter*)ud;
w->err_code = ec;
w->ctx = ctx;
if (em) {
snprintf(w->err, sizeof(w->err), "%s", em);
}
atomic_store_explicit(&w->done, 1, memory_order_release);
}
static EchoCtx* make_ctx(void) {
CreateWaiter w;
memset(&w, 0, sizeof(w));
EchoConfig config = {"c-abi"};
echo_ctx_create(&config, on_created, &w);
wait_done(&w.done);
if (w.err_code != 0) {
fprintf(stderr, "create failed: %s\n", w.err[0] ? w.err : "?");
}
assert(w.err_code == 0);
assert(w.ctx != NULL);
return w.ctx;
}
typedef struct {
atomic_int done;
int err_code;
char err[256];
char text_a[256];
char text_b[256];
} ReplyWaiter;
static void on_shout(int ec, const ShoutResponse* reply, const char* em, void* ud) {
ReplyWaiter* w = (ReplyWaiter*)ud;
w->err_code = ec;
if (reply) {
if (reply->shouted)
snprintf(w->text_a, sizeof(w->text_a), "%s", reply->shouted);
if (reply->prefix)
snprintf(w->text_b, sizeof(w->text_b), "%s", reply->prefix);
}
if (em) snprintf(w->err, sizeof(w->err), "%s", em);
atomic_store_explicit(&w->done, 1, memory_order_release);
}
static void test_shout(EchoCtx* ctx) {
ReplyWaiter w;
memset(&w, 0, sizeof(w));
ShoutRequest req = {"hello"};
echo_ctx_shout(ctx, &req, on_shout, &w);
wait_done(&w.done);
assert(w.err_code == 0);
assert(strcmp(w.text_a, "c-abi: HELLO") == 0);
assert(strcmp(w.text_b, "c-abi") == 0);
}
int main(void) {
EchoCtx* ctx = make_ctx();
test_shout(ctx);
echo_ctx_destroy(ctx);
printf("all abi=c echo e2e checks passed\n");
return 0;
}