feat: publish Dever core with project scaffolding and versioned AI skills
This commit is contained in:
1 parent
70fa73cdc2
commit
ac458ffa7f
960 files changed
+221319
-1163
No files matched your search
@@ -0,0 +1,23 @@
|
||||
#include <stdint.h>
|
||||
|
||||
extern uint32_t dever_rt_v1_version(void);
|
||||
extern void *dever_coro_probe_create(void);
|
||||
extern int32_t dever_coro_probe_progress(void);
|
||||
extern void dever_coro_probe_resume(void *frame);
|
||||
extern uint8_t dever_coro_probe_done(void *frame);
|
||||
extern void dever_coro_probe_destroy(void *frame);
|
||||
extern int32_t dever_coro_root_run(void);
|
||||
|
||||
int main(void) {
|
||||
if (dever_rt_v1_version() != 1) return 1;
|
||||
void *frame = dever_coro_probe_create();
|
||||
if (!frame || dever_coro_probe_progress() != 0 || dever_coro_probe_done(frame)) return 2;
|
||||
dever_coro_probe_resume(frame);
|
||||
if (dever_coro_probe_progress() != 42 || !dever_coro_probe_done(frame)) return 3;
|
||||
dever_coro_probe_destroy(frame);
|
||||
frame = dever_coro_probe_create();
|
||||
if (!frame || dever_coro_probe_done(frame)) return 4;
|
||||
dever_coro_probe_destroy(frame);
|
||||
if (dever_coro_root_run() != 0) return 5;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,470 @@
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "runtime.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
static _Atomic int unit_destroy_count;
|
||||
static _Atomic int blocking_done_count;
|
||||
static _Atomic int group_sync_done_count;
|
||||
|
||||
typedef struct {
|
||||
DeverRtPollState *poll;
|
||||
void *output;
|
||||
void *fault;
|
||||
DeverRtAsyncOp *operation;
|
||||
DeverRtAsyncOp *blocked_send;
|
||||
DeverRtAsyncTask *task;
|
||||
DeverRtAsyncGroup *group;
|
||||
DeverRtAsyncChannel *channel;
|
||||
uint32_t phase;
|
||||
uint8_t done;
|
||||
uint8_t pending;
|
||||
uint8_t group_pending;
|
||||
} Frame;
|
||||
|
||||
static void move_int(void *source, void *destination) {
|
||||
*(int64_t *)destination = *(int64_t *)source;
|
||||
*(int64_t *)source = 0;
|
||||
}
|
||||
|
||||
static void drop_int(void *value) { (void)value; }
|
||||
static void move_unit(void *source, void *destination) { (void)source; (void)destination; }
|
||||
static void drop_unit(void *value) { (void)value; }
|
||||
|
||||
typedef struct {
|
||||
uint8_t present;
|
||||
int64_t value;
|
||||
} OptionalInt;
|
||||
|
||||
static void move_optional(void *source, void *destination) {
|
||||
OptionalInt *old = source;
|
||||
OptionalInt *new_value = destination;
|
||||
new_value->present = old->present;
|
||||
new_value->value = old->value;
|
||||
old->present = 0;
|
||||
old->value = 0;
|
||||
}
|
||||
|
||||
static const DeverRtOwnedType integer = { 8, 8, move_int, drop_int, 1 };
|
||||
static const DeverRtOwnedType unit = { 0, 1, move_unit, drop_unit, 1 };
|
||||
static const DeverRtOwnedType optional_integer = {
|
||||
sizeof(OptionalInt), _Alignof(OptionalInt), move_optional, drop_int, 1
|
||||
};
|
||||
|
||||
static void *create(DeverRtPollState *poll, const void *input, void *output, void *fault) {
|
||||
(void)input;
|
||||
Frame *frame = calloc(1, sizeof(*frame));
|
||||
if (frame) {
|
||||
frame->poll = poll;
|
||||
frame->output = output;
|
||||
frame->fault = fault;
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
static void destroy(void *pointer) {
|
||||
Frame *frame = pointer;
|
||||
if (frame->operation) dever_rt_v1_async_op_release(frame->operation);
|
||||
if (frame->blocked_send) dever_rt_v1_async_op_release(frame->blocked_send);
|
||||
if (frame->task) dever_rt_v1_async_task_release(frame->task);
|
||||
if (frame->group) dever_rt_v1_async_group_release(frame->group);
|
||||
if (frame->channel) dever_rt_v1_async_channel_release(frame->channel);
|
||||
free(frame);
|
||||
}
|
||||
|
||||
static void destroy_unit(void *pointer) {
|
||||
atomic_fetch_add(&unit_destroy_count, 1);
|
||||
destroy(pointer);
|
||||
}
|
||||
|
||||
static uint8_t done(void *pointer) { return ((Frame *)pointer)->done; }
|
||||
|
||||
static void complete(Frame *frame, uint32_t status) {
|
||||
frame->done = 1;
|
||||
dever_rt_v1_async_complete(frame->poll, status);
|
||||
}
|
||||
|
||||
static void resume_child(void *pointer, uint8_t has_value, int64_t delay) {
|
||||
Frame *frame = pointer;
|
||||
DeverRtBuffer error = { 0 };
|
||||
if (!frame->operation) frame->operation = dever_rt_v1_async_sleep(delay);
|
||||
uint32_t status = dever_rt_v1_async_op_poll(frame->poll, frame->operation,
|
||||
NULL, NULL, NULL, &error);
|
||||
if (status == DEVER_RT_ASYNC_PENDING) return;
|
||||
if (status != DEVER_RT_ASYNC_READY_OK) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = NULL;
|
||||
if (has_value) *(int64_t *)frame->output = 7;
|
||||
complete(frame, DEVER_RT_ASYNC_READY_OK);
|
||||
}
|
||||
|
||||
static void value_resume(void *pointer) { resume_child(pointer, 1, 2); }
|
||||
static void unit_resume(void *pointer) { resume_child(pointer, 0, 1000); }
|
||||
static void fault_resume(void *pointer) {
|
||||
Frame *frame = pointer;
|
||||
*(int64_t *)frame->fault = 99;
|
||||
complete(frame, DEVER_RT_ASYNC_READY_FAULT);
|
||||
}
|
||||
|
||||
static const DeverRtAsyncFunction value_child = {
|
||||
&integer, &integer, create, value_resume, destroy, done, 1
|
||||
};
|
||||
static const DeverRtAsyncFunction unit_child = {
|
||||
&unit, &integer, create, unit_resume, destroy_unit, done, 1
|
||||
};
|
||||
static const DeverRtAsyncFunction fault_child = {
|
||||
&unit, &integer, create, fault_resume, destroy, done, 1
|
||||
};
|
||||
|
||||
static uint32_t twice(void *input, void *output, void *fault) {
|
||||
(void)fault;
|
||||
struct timespec delay = { 0, 80000000 };
|
||||
nanosleep(&delay, NULL);
|
||||
*(int64_t *)output = *(int64_t *)input * 2;
|
||||
atomic_fetch_add(&blocking_done_count, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const DeverRtSyncFunction synchronous_twice = {
|
||||
&integer, &integer, &integer, twice, 1
|
||||
};
|
||||
|
||||
static uint32_t group_sync(void *input, void *output, void *fault) {
|
||||
(void)output;
|
||||
(void)fault;
|
||||
if (*(int64_t *)input != 21) abort();
|
||||
atomic_fetch_add(&group_sync_done_count, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const DeverRtSyncFunction synchronous_unit = {
|
||||
&integer, &unit, &integer, group_sync, 1
|
||||
};
|
||||
|
||||
static uint32_t poll(Frame *frame, DeverRtAsyncOp *operation, void *output,
|
||||
uint8_t *present, void *fault) {
|
||||
DeverRtBuffer error = { 0 };
|
||||
uint32_t status = dever_rt_v1_async_op_poll(frame->poll, operation,
|
||||
output, present, fault, &error);
|
||||
if (status == DEVER_RT_ASYNC_PENDING) frame->pending = 1;
|
||||
if (error.ptr) dever_rt_v1_buffer_free(error.ptr, error.len);
|
||||
return status;
|
||||
}
|
||||
|
||||
static void root_resume(void *pointer) {
|
||||
Frame *frame = pointer;
|
||||
DeverRtBuffer error = { 0 };
|
||||
uint32_t status;
|
||||
int64_t item;
|
||||
int64_t fault_value;
|
||||
OptionalInt optional;
|
||||
uint8_t present;
|
||||
for (;;) {
|
||||
switch (frame->phase) {
|
||||
case 0:
|
||||
frame->operation = dever_rt_v1_async_task_run(&value_child, NULL, &error);
|
||||
if (!frame->operation) abort();
|
||||
frame->phase = 1;
|
||||
break;
|
||||
case 1:
|
||||
status = poll(frame, frame->operation, &frame->task, NULL, NULL);
|
||||
if (status == DEVER_RT_ASYNC_PENDING) return;
|
||||
if (status != DEVER_RT_ASYNC_READY_OK || !frame->task) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
DeverRtAsyncTask *task_alias = dever_rt_v1_async_task_retain(frame->task);
|
||||
if (!task_alias) abort();
|
||||
frame->operation = dever_rt_v1_async_task_wait(frame->task);
|
||||
frame->task = NULL;
|
||||
dever_rt_v1_async_task_release(task_alias);
|
||||
frame->phase = 2;
|
||||
break;
|
||||
case 2:
|
||||
status = poll(frame, frame->operation, &item, NULL, frame->fault);
|
||||
if (status == DEVER_RT_ASYNC_PENDING) return;
|
||||
if (status != DEVER_RT_ASYNC_READY_OK || item != 7 || !frame->pending) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = NULL;
|
||||
frame->operation = dever_rt_v1_async_task_run(&unit_child, NULL, &error);
|
||||
if (!frame->operation) abort();
|
||||
frame->phase = 12;
|
||||
break;
|
||||
case 12:
|
||||
status = poll(frame, frame->operation, &frame->task, NULL, NULL);
|
||||
if (status == DEVER_RT_ASYNC_PENDING) return;
|
||||
if (status != DEVER_RT_ASYNC_READY_OK || !frame->task) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = dever_rt_v1_async_task_stop(frame->task);
|
||||
frame->task = NULL;
|
||||
frame->phase = 13;
|
||||
break;
|
||||
case 13:
|
||||
status = poll(frame, frame->operation, NULL, NULL, frame->fault);
|
||||
if (status == DEVER_RT_ASYNC_PENDING) return;
|
||||
if (status != DEVER_RT_ASYNC_READY_OK || atomic_load(&unit_destroy_count) < 1) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = dever_rt_v1_async_task_run(&fault_child, NULL, &error);
|
||||
if (!frame->operation) abort();
|
||||
frame->phase = 14;
|
||||
break;
|
||||
case 14:
|
||||
status = poll(frame, frame->operation, &frame->task, NULL, NULL);
|
||||
if (status == DEVER_RT_ASYNC_PENDING) return;
|
||||
if (status != DEVER_RT_ASYNC_READY_OK || !frame->task) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = dever_rt_v1_async_task_wait(frame->task);
|
||||
frame->task = NULL;
|
||||
frame->phase = 15;
|
||||
break;
|
||||
case 15:
|
||||
status = poll(frame, frame->operation, NULL, NULL, &fault_value);
|
||||
if (status == DEVER_RT_ASYNC_PENDING) return;
|
||||
if (status != DEVER_RT_ASYNC_READY_FAULT || fault_value != 99) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = NULL;
|
||||
if (dever_rt_v1_async_group_new(1, &frame->group, &error) != DEVER_RT_ASYNC_PENDING) abort();
|
||||
frame->operation = dever_rt_v1_async_group_run(frame->group, &unit_child, NULL, &error);
|
||||
if (!frame->operation) abort();
|
||||
frame->phase = 3;
|
||||
break;
|
||||
case 3:
|
||||
status = poll(frame, frame->operation, NULL, NULL, frame->fault);
|
||||
if (status == DEVER_RT_ASYNC_PENDING) return;
|
||||
if (status != DEVER_RT_ASYNC_READY_OK) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = dever_rt_v1_async_group_run(frame->group, &unit_child, NULL, &error);
|
||||
if (!frame->operation) abort();
|
||||
frame->phase = 4;
|
||||
break;
|
||||
case 4:
|
||||
status = poll(frame, frame->operation, NULL, NULL, frame->fault);
|
||||
if (status != DEVER_RT_ASYNC_PENDING) abort();
|
||||
frame->group_pending = 1;
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
DeverRtAsyncGroup *group_alias = dever_rt_v1_async_group_retain(frame->group);
|
||||
if (!group_alias) abort();
|
||||
frame->operation = dever_rt_v1_async_group_stop(frame->group);
|
||||
frame->group = NULL;
|
||||
dever_rt_v1_async_group_release(group_alias);
|
||||
frame->phase = 5;
|
||||
break;
|
||||
case 5:
|
||||
status = poll(frame, frame->operation, NULL, NULL, frame->fault);
|
||||
if (status == DEVER_RT_ASYNC_PENDING) return;
|
||||
if (status != DEVER_RT_ASYNC_READY_OK || atomic_load(&unit_destroy_count) < 2) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = NULL;
|
||||
if (dever_rt_v1_async_group_new(1, &frame->group, &error) != DEVER_RT_ASYNC_PENDING) abort();
|
||||
frame->operation = dever_rt_v1_async_group_run(frame->group, &fault_child, NULL, &error);
|
||||
if (!frame->operation) abort();
|
||||
frame->phase = 18;
|
||||
break;
|
||||
case 18:
|
||||
status = poll(frame, frame->operation, NULL, NULL, &fault_value);
|
||||
if (status == DEVER_RT_ASYNC_PENDING) return;
|
||||
if (status != DEVER_RT_ASYNC_READY_OK) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = dever_rt_v1_async_group_wait(frame->group);
|
||||
frame->group = NULL;
|
||||
frame->phase = 19;
|
||||
break;
|
||||
case 19:
|
||||
status = poll(frame, frame->operation, NULL, NULL, &fault_value);
|
||||
if (status == DEVER_RT_ASYNC_PENDING) return;
|
||||
if (status != DEVER_RT_ASYNC_READY_FAULT || fault_value != 99) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = NULL;
|
||||
if (dever_rt_v1_async_channel_new(1, &frame->channel, &error) != DEVER_RT_ASYNC_PENDING) abort();
|
||||
item = 11;
|
||||
frame->operation = dever_rt_v1_async_channel_send(frame->channel, &integer, &item, &error);
|
||||
if (!frame->operation || item != 0) abort();
|
||||
frame->phase = 6;
|
||||
break;
|
||||
case 6:
|
||||
status = poll(frame, frame->operation, NULL, NULL, frame->fault);
|
||||
if (status != DEVER_RT_ASYNC_READY_OK) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
item = 12;
|
||||
frame->blocked_send = dever_rt_v1_async_channel_send(frame->channel, &integer, &item, &error);
|
||||
if (!frame->blocked_send || item != 0) abort();
|
||||
if (poll(frame, frame->blocked_send, NULL, NULL, frame->fault) != DEVER_RT_ASYNC_PENDING) abort();
|
||||
frame->operation = dever_rt_v1_async_channel_receive(frame->channel);
|
||||
frame->phase = 7;
|
||||
break;
|
||||
case 7:
|
||||
status = poll(frame, frame->operation, &item, &present, frame->fault);
|
||||
if (status != DEVER_RT_ASYNC_READY_OK || !present || item != 11) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = NULL;
|
||||
status = poll(frame, frame->blocked_send, NULL, NULL, frame->fault);
|
||||
frame->phase = 11;
|
||||
if (status == DEVER_RT_ASYNC_PENDING) return;
|
||||
if (status != DEVER_RT_ASYNC_READY_OK) abort();
|
||||
dever_rt_v1_async_op_release(frame->blocked_send);
|
||||
frame->blocked_send = NULL;
|
||||
frame->operation = dever_rt_v1_async_channel_close(frame->channel);
|
||||
frame->phase = 8;
|
||||
break;
|
||||
case 11:
|
||||
status = poll(frame, frame->blocked_send, NULL, NULL, frame->fault);
|
||||
if (status == DEVER_RT_ASYNC_PENDING) return;
|
||||
if (status != DEVER_RT_ASYNC_READY_OK) abort();
|
||||
dever_rt_v1_async_op_release(frame->blocked_send);
|
||||
frame->blocked_send = NULL;
|
||||
frame->operation = dever_rt_v1_async_channel_close(frame->channel);
|
||||
frame->phase = 8;
|
||||
break;
|
||||
case 8:
|
||||
status = poll(frame, frame->operation, NULL, NULL, frame->fault);
|
||||
if (status != DEVER_RT_ASYNC_READY_OK) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = dever_rt_v1_async_channel_receive(frame->channel);
|
||||
frame->phase = 9;
|
||||
break;
|
||||
case 9:
|
||||
status = poll(frame, frame->operation, &item, &present, frame->fault);
|
||||
if (status != DEVER_RT_ASYNC_READY_OK || !present || item != 12) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = dever_rt_v1_async_channel_receive(frame->channel);
|
||||
frame->phase = 10;
|
||||
break;
|
||||
case 10:
|
||||
status = poll(frame, frame->operation, &item, &present, frame->fault);
|
||||
if (status != DEVER_RT_ASYNC_READY_OK || present) abort();
|
||||
if (!frame->group_pending) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
dever_rt_v1_async_channel_release(frame->channel);
|
||||
frame->channel = NULL;
|
||||
if (dever_rt_v1_async_channel_new(1, &frame->channel, &error) != DEVER_RT_ASYNC_PENDING) abort();
|
||||
optional.present = 0;
|
||||
optional.value = 0;
|
||||
frame->operation = dever_rt_v1_async_channel_send(frame->channel, &optional_integer, &optional, &error);
|
||||
if (!frame->operation) abort();
|
||||
frame->phase = 24;
|
||||
break;
|
||||
case 24:
|
||||
status = poll(frame, frame->operation, NULL, NULL, frame->fault);
|
||||
if (status != DEVER_RT_ASYNC_READY_OK) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = dever_rt_v1_async_channel_close(frame->channel);
|
||||
frame->phase = 25;
|
||||
break;
|
||||
case 25:
|
||||
status = poll(frame, frame->operation, NULL, NULL, frame->fault);
|
||||
if (status != DEVER_RT_ASYNC_READY_OK) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = dever_rt_v1_async_channel_receive(frame->channel);
|
||||
frame->phase = 26;
|
||||
break;
|
||||
case 26:
|
||||
status = poll(frame, frame->operation, &optional, &present, frame->fault);
|
||||
if (status != DEVER_RT_ASYNC_READY_OK || !present || optional.present) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = dever_rt_v1_async_channel_receive(frame->channel);
|
||||
frame->phase = 27;
|
||||
break;
|
||||
case 27:
|
||||
status = poll(frame, frame->operation, &optional, &present, frame->fault);
|
||||
if (status != DEVER_RT_ASYNC_READY_OK || present) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
dever_rt_v1_async_channel_release(frame->channel);
|
||||
frame->channel = NULL;
|
||||
item = 21;
|
||||
frame->operation = dever_rt_v1_async_parallel(&synchronous_twice, &item, &error);
|
||||
if (!frame->operation || item != 0) abort();
|
||||
frame->phase = 16;
|
||||
break;
|
||||
case 16:
|
||||
status = poll(frame, frame->operation, &item, NULL, frame->fault);
|
||||
if (status == DEVER_RT_ASYNC_PENDING) return;
|
||||
if (status != DEVER_RT_ASYNC_READY_OK || item != 42) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
item = 21;
|
||||
frame->operation = dever_rt_v1_async_task_run_sync(&synchronous_twice, &item, &error);
|
||||
if (!frame->operation || item != 0) abort();
|
||||
frame->phase = 20;
|
||||
break;
|
||||
case 20:
|
||||
status = poll(frame, frame->operation, &frame->task, NULL, frame->fault);
|
||||
if (status == DEVER_RT_ASYNC_PENDING) return;
|
||||
if (status != DEVER_RT_ASYNC_READY_OK || !frame->task) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = dever_rt_v1_async_task_wait(frame->task);
|
||||
frame->task = NULL;
|
||||
frame->phase = 21;
|
||||
break;
|
||||
case 21:
|
||||
status = poll(frame, frame->operation, &item, NULL, frame->fault);
|
||||
if (status == DEVER_RT_ASYNC_PENDING) return;
|
||||
if (status != DEVER_RT_ASYNC_READY_OK || item != 42) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
if (dever_rt_v1_async_group_new(1, &frame->group, &error) != DEVER_RT_ASYNC_PENDING) abort();
|
||||
item = 21;
|
||||
frame->operation = dever_rt_v1_async_group_run_sync(frame->group, &synchronous_unit, &item, &error);
|
||||
if (!frame->operation || item != 0) abort();
|
||||
frame->phase = 22;
|
||||
break;
|
||||
case 22:
|
||||
status = poll(frame, frame->operation, NULL, NULL, frame->fault);
|
||||
if (status == DEVER_RT_ASYNC_PENDING) return;
|
||||
if (status != DEVER_RT_ASYNC_READY_OK) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = dever_rt_v1_async_group_wait(frame->group);
|
||||
frame->group = NULL;
|
||||
frame->phase = 23;
|
||||
break;
|
||||
case 23:
|
||||
status = poll(frame, frame->operation, NULL, NULL, frame->fault);
|
||||
if (status == DEVER_RT_ASYNC_PENDING) return;
|
||||
if (status != DEVER_RT_ASYNC_READY_OK || atomic_load(&group_sync_done_count) != 1) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
item = 21;
|
||||
frame->operation = dever_rt_v1_async_blocking(&synchronous_twice, &item, &error);
|
||||
if (!frame->operation || item != 0) abort();
|
||||
frame->phase = 17;
|
||||
break;
|
||||
case 17:
|
||||
status = poll(frame, frame->operation, &item, NULL, frame->fault);
|
||||
if (status != DEVER_RT_ASYNC_PENDING) abort();
|
||||
dever_rt_v1_async_op_release(frame->operation);
|
||||
frame->operation = NULL;
|
||||
*(int64_t *)frame->output = 42;
|
||||
complete(frame, DEVER_RT_ASYNC_READY_OK);
|
||||
return;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const DeverRtAsyncFunction root = {
|
||||
&integer, &integer, create, root_resume, destroy, done, 0
|
||||
};
|
||||
|
||||
int main(void) {
|
||||
int64_t output = 0;
|
||||
int64_t fault = 0;
|
||||
DeverRtBuffer error = { 0 };
|
||||
if (dever_rt_v1_async_op_poll(NULL, NULL, NULL, NULL, NULL, &error)
|
||||
!= DEVER_RT_ASYNC_INVALID_INPUT || !error.ptr) return 4;
|
||||
dever_rt_v1_buffer_free(error.ptr, error.len);
|
||||
error = (DeverRtBuffer){ 0 };
|
||||
uint8_t unit_output = 7;
|
||||
if (dever_rt_v1_time_sleep(0, &unit_output, &error) != DEVER_RT_OK
|
||||
|| unit_output != 0 || error.ptr) return 2;
|
||||
unit_output = 7;
|
||||
if (dever_rt_v1_time_sleep(-1, &unit_output, &error) != DEVER_RT_RUNTIME_ERROR
|
||||
|| unit_output != 7 || !error.ptr
|
||||
|| error.len != sizeof("sleep duration must not be negative") - 1
|
||||
|| memcmp(error.ptr, "sleep duration must not be negative", error.len)) return 3;
|
||||
dever_rt_v1_buffer_free(error.ptr, error.len);
|
||||
error = (DeverRtBuffer){ 0 };
|
||||
uint32_t status = dever_rt_v1_async_root(&root, NULL, &output, &fault, &error);
|
||||
if (error.ptr) dever_rt_v1_buffer_free(error.ptr, error.len);
|
||||
return status == DEVER_RT_ASYNC_READY_OK && output == 42
|
||||
&& atomic_load(&blocking_done_count) == 3
|
||||
&& atomic_load(&group_sync_done_count) == 1 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/* Database ABI ownership without settings, pools or database connections.
|
||||
* The existing managed-driver supplies warm 1 + 64 and the strict ledger. */
|
||||
#include "runtime.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
uint32_t kind;
|
||||
void *message;
|
||||
void *cause;
|
||||
} Fault;
|
||||
|
||||
typedef struct {
|
||||
uint8_t present;
|
||||
void *text;
|
||||
} OptionalText;
|
||||
|
||||
static unsigned packs;
|
||||
static unsigned moves;
|
||||
static unsigned drops;
|
||||
|
||||
static void release_buffer(DeverRtBuffer *buffer) {
|
||||
dever_rt_v1_buffer_free(buffer->ptr, buffer->len);
|
||||
*buffer = (DeverRtBuffer){0};
|
||||
}
|
||||
|
||||
static int buffer_is(const DeverRtBuffer *buffer, const char *expected) {
|
||||
size_t length = strlen(expected);
|
||||
return buffer->ptr && buffer->len == length &&
|
||||
memcmp(buffer->ptr, expected, length) == 0;
|
||||
}
|
||||
|
||||
static int text_is(const void *text, const char *expected) {
|
||||
DeverRtBuffer buffer = {0};
|
||||
uint32_t status = dever_rt_v1_text_export(text, &buffer);
|
||||
int matches = status == 0 && buffer_is(&buffer, expected);
|
||||
release_buffer(&buffer);
|
||||
return matches;
|
||||
}
|
||||
|
||||
static int new_text(const char *text, void **out) {
|
||||
DeverRtBuffer buffer = {0};
|
||||
uint32_t status = dever_rt_v1_text_new(
|
||||
(const uint8_t *)text, strlen(text), out, &buffer);
|
||||
release_buffer(&buffer);
|
||||
return status == 0;
|
||||
}
|
||||
|
||||
static void move_fault(void *source, void *destination) {
|
||||
*(Fault *)destination = *(Fault *)source;
|
||||
*(Fault *)source = (Fault){0};
|
||||
++moves;
|
||||
}
|
||||
|
||||
static void drop_fault(void *value) {
|
||||
Fault *fault = value;
|
||||
dever_rt_v1_text_release(fault->message);
|
||||
dever_rt_v1_text_release(fault->cause);
|
||||
*fault = (Fault){0};
|
||||
++drops;
|
||||
}
|
||||
|
||||
static void pack_fault(uint32_t kind, const void *message, void *out) {
|
||||
*(Fault *)out = (Fault){kind, dever_rt_v1_text_retain(message), NULL};
|
||||
++packs;
|
||||
}
|
||||
|
||||
static void append_cause(void *value, const void *message) {
|
||||
dever_rt_v1_db_cause_append(&((Fault *)value)->cause, message);
|
||||
}
|
||||
|
||||
static const DeverRtOwnedType fault_type = {
|
||||
sizeof(Fault), _Alignof(Fault), move_fault, drop_fault, 1
|
||||
};
|
||||
static const DeverRtDbError errors = {&fault_type, pack_fault, append_cause};
|
||||
|
||||
static void clone_optional(const void *source, void *destination) {
|
||||
const OptionalText *from = source;
|
||||
*(OptionalText *)destination = (OptionalText){
|
||||
from->present, from->present ? dever_rt_v1_text_retain(from->text) : NULL
|
||||
};
|
||||
}
|
||||
|
||||
static void drop_optional(void *value) {
|
||||
OptionalText *text = value;
|
||||
if (text->present) dever_rt_v1_text_release(text->text);
|
||||
*text = (OptionalText){0};
|
||||
}
|
||||
|
||||
static uint8_t equal_optional(const void *left, const void *right) {
|
||||
const OptionalText *a = left;
|
||||
const OptionalText *b = right;
|
||||
return a->present == b->present &&
|
||||
(!a->present || dever_rt_v1_text_equal(a->text, b->text));
|
||||
}
|
||||
|
||||
static const DeverRtType optional_type = {
|
||||
sizeof(OptionalText), _Alignof(OptionalText),
|
||||
clone_optional, drop_optional, equal_optional, NULL
|
||||
};
|
||||
|
||||
/* Preserve cleanup on an assertion failure so the first diagnostic identifies
|
||||
* the broken contract, not a second error caused by the test's own resources. */
|
||||
#define CHECK(condition) do { if (!(condition)) { result = __LINE__; goto cleanup; } } while (0)
|
||||
|
||||
static int error_kinds(void) {
|
||||
int result = 0;
|
||||
DeverRtBuffer buffer = {0};
|
||||
Fault fault = {0};
|
||||
void *message = NULL;
|
||||
unsigned before_packs = packs;
|
||||
unsigned before_moves = moves;
|
||||
unsigned before_drops = drops;
|
||||
for (uint32_t kind = 0; kind < 10; ++kind) {
|
||||
CHECK(new_text("original database diagnostic: \xe6\x95\xb0\xe6\x8d\xae", &message));
|
||||
CHECK(dever_rt_v1_db_error(kind, message, &errors, &fault, &buffer) == 1);
|
||||
CHECK(buffer.ptr == NULL && buffer.len == 0 && fault.kind == kind);
|
||||
dever_rt_v1_text_release(message);
|
||||
message = NULL;
|
||||
/* pack's borrowed handle is retainable after the bridge returns. */
|
||||
CHECK(text_is(fault.message, "original database diagnostic: \xe6\x95\xb0\xe6\x8d\xae"));
|
||||
CHECK(new_text("rollback failed", &message));
|
||||
append_cause(&fault, message);
|
||||
append_cause(&fault, message);
|
||||
dever_rt_v1_text_release(message);
|
||||
message = NULL;
|
||||
CHECK(fault.kind == kind && text_is(fault.cause,
|
||||
"caused by: rollback failed; caused by: rollback failed"));
|
||||
drop_fault(&fault);
|
||||
}
|
||||
CHECK(packs == before_packs + 10 && moves == before_moves + 10 &&
|
||||
drops == before_drops + 10);
|
||||
cleanup:
|
||||
dever_rt_v1_text_release(message);
|
||||
if (fault.message || fault.cause) drop_fault(&fault);
|
||||
release_buffer(&buffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int related_owners(void) {
|
||||
int result = 0;
|
||||
DeverRtBuffer buffer = {0};
|
||||
OptionalText source = {1, NULL}, extracted = {0}, absent = {0};
|
||||
void *related = NULL, *alias = NULL, *loaded_null = NULL, *second_null = NULL;
|
||||
CHECK(new_text("related-owned", &source.text));
|
||||
CHECK(dever_rt_v1_db_related_new(&optional_type, &source, &related, &buffer) == 0);
|
||||
alias = dever_rt_v1_db_related_retain(related);
|
||||
drop_optional(&source);
|
||||
dever_rt_v1_db_related_release(related);
|
||||
related = NULL;
|
||||
CHECK(dever_rt_v1_db_related_get(alias, &extracted, &buffer) == 0);
|
||||
dever_rt_v1_db_related_release(alias);
|
||||
alias = NULL;
|
||||
CHECK(extracted.present == 1 && text_is(extracted.text, "related-owned"));
|
||||
CHECK(dever_rt_v1_db_related_new(&optional_type, &absent, &loaded_null, &buffer) == 0);
|
||||
CHECK(dever_rt_v1_db_related_new(&optional_type, &absent, &second_null, &buffer) == 0);
|
||||
CHECK(loaded_null != NULL && dever_rt_v1_db_related_equal(loaded_null, second_null) == 1);
|
||||
CHECK(dever_rt_v1_db_related_equal(NULL, loaded_null) == 0);
|
||||
CHECK(dever_rt_v1_db_related_equal(NULL, NULL) == 1);
|
||||
drop_optional(&extracted);
|
||||
CHECK(dever_rt_v1_db_related_get(loaded_null, &extracted, &buffer) == 0);
|
||||
CHECK(extracted.present == 0 && extracted.text == NULL);
|
||||
cleanup:
|
||||
drop_optional(&source);
|
||||
drop_optional(&extracted);
|
||||
dever_rt_v1_db_related_release(related);
|
||||
dever_rt_v1_db_related_release(alias);
|
||||
dever_rt_v1_db_related_release(loaded_null);
|
||||
dever_rt_v1_db_related_release(second_null);
|
||||
release_buffer(&buffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int invalid_destinations(void) {
|
||||
int result = 0;
|
||||
DeverRtBuffer buffer = {0};
|
||||
Fault fault = {37, NULL, NULL};
|
||||
int64_t output = 41;
|
||||
unsigned before = packs;
|
||||
CHECK(dever_rt_v1_db_cursor_size(0, 7, NULL, &errors, &fault, &buffer) == 2);
|
||||
CHECK(buffer_is(&buffer, "invalid ABI output pointer") && packs == before && fault.kind == 37);
|
||||
release_buffer(&buffer);
|
||||
_Alignas(int64_t) unsigned char storage[sizeof(int64_t) + 1];
|
||||
memset(storage, 53, sizeof(storage));
|
||||
CHECK(dever_rt_v1_db_stream_capacity(0, 7, (int64_t *)(storage + 1), &errors, &fault, &buffer) == 2);
|
||||
CHECK(packs == before && fault.kind == 37);
|
||||
for (size_t i = 0; i < sizeof(storage); ++i) CHECK(storage[i] == 53);
|
||||
release_buffer(&buffer);
|
||||
CHECK(dever_rt_v1_db_cursor_size(0, 7, &output, &errors, NULL, &buffer) == 2);
|
||||
CHECK(output == 41 && packs == before && buffer_is(&buffer, "invalid database fault output"));
|
||||
release_buffer(&buffer);
|
||||
CHECK(dever_rt_v1_db_cursor_size(0, 7, &output, &errors, &fault, &buffer) == 1);
|
||||
CHECK(output == 41 && fault.kind == 8 && text_is(fault.message, "cursor size must be between 1 and 7"));
|
||||
drop_fault(&fault);
|
||||
CHECK(dever_rt_v1_db_stream_capacity(0, 7, &output, &errors, &fault, &buffer) == 1);
|
||||
CHECK(output == 41 && fault.kind == 8 && text_is(fault.message, "stream buffer size must be between 1 and 7"));
|
||||
drop_fault(&fault);
|
||||
CHECK(dever_rt_v1_db_cursor_size(3, 7, &output, &errors, &fault, &buffer) == 0 && output == 3);
|
||||
CHECK(packs == before + 2 && buffer.ptr == NULL && buffer.len == 0);
|
||||
cleanup:
|
||||
if (fault.message || fault.cause) drop_fault(&fault);
|
||||
release_buffer(&buffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int invalid_constructors(void) {
|
||||
int result = 0;
|
||||
DeverRtBuffer buffer = {0};
|
||||
Fault primary = {6, NULL, NULL};
|
||||
DeverRtAsyncOp *operation = NULL;
|
||||
void *session = (void *)(uintptr_t)47;
|
||||
unsigned before_moves = moves, before_drops = drops;
|
||||
CHECK(new_text("primary-owned", &primary.message));
|
||||
operation = dever_rt_v1_db_rollback_take(NULL, &primary, &fault_type, &errors, &buffer);
|
||||
CHECK(operation == NULL && buffer_is(&buffer, "invalid ABI handle"));
|
||||
CHECK(moves == before_moves && drops == before_drops && primary.kind == 6 && text_is(primary.message, "primary-owned"));
|
||||
release_buffer(&buffer);
|
||||
|
||||
const DeverRtDbBinding bad_flag = {NULL, {(const uint8_t *)"fixture", 7}, 2};
|
||||
CHECK(dever_rt_v1_db_session_new(&bad_flag, 1, NULL, 0, NULL, &buffer) == 2);
|
||||
CHECK(buffer_is(&buffer, "invalid ABI output pointer"));
|
||||
release_buffer(&buffer);
|
||||
/* Metadata rejects before looking for config/setting.json or opening pools. */
|
||||
CHECK(dever_rt_v1_db_session_new(&bad_flag, 1, NULL, 0, &session, &buffer) == 2);
|
||||
CHECK(session == (void *)(uintptr_t)47 && buffer_is(&buffer, "invalid database ABI boolean"));
|
||||
release_buffer(&buffer);
|
||||
_Alignas(DeverRtDbBinding) unsigned char unaligned[sizeof(DeverRtDbBinding) + 1];
|
||||
CHECK(dever_rt_v1_db_session_new((const DeverRtDbBinding *)(unaligned + 1), 1, NULL, 0, &session, &buffer) == 2);
|
||||
CHECK(session == (void *)(uintptr_t)47 && buffer_is(&buffer, "invalid database ABI array"));
|
||||
cleanup:
|
||||
dever_rt_v1_async_op_release(operation);
|
||||
drop_fault(&primary);
|
||||
release_buffer(&buffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
int32_t dever_test_run(void) {
|
||||
packs = moves = drops = 0;
|
||||
int result = error_kinds();
|
||||
if (result == 0) result = related_owners();
|
||||
if (result == 0) result = invalid_destinations();
|
||||
if (result == 0) result = invalid_constructors();
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "../../crates/dever-backend-bridge/include/runtime.h"
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static int initialized;
|
||||
static size_t calls;
|
||||
static pthread_key_t cache_key;
|
||||
static pthread_once_t cache_once = PTHREAD_ONCE_INIT;
|
||||
|
||||
#if DEVER_LEDGER_SCENARIO == 1
|
||||
static void *volatile leaked;
|
||||
#endif
|
||||
|
||||
static void create_cache_key(void) {
|
||||
if (pthread_key_create(&cache_key, free) != 0) abort();
|
||||
}
|
||||
|
||||
uint32_t dever_rt_v1_process_init(void) {
|
||||
initialized = 1;
|
||||
return DEVER_RT_OK;
|
||||
}
|
||||
|
||||
int dever_test_run(void) {
|
||||
if (!initialized) return 9;
|
||||
++calls;
|
||||
/* Deterministically initialize TLS after warmup, as a later full queue can. */
|
||||
if (calls > 1) {
|
||||
if (pthread_once(&cache_once, create_cache_key) != 0) abort();
|
||||
if (pthread_getspecific(cache_key) == NULL) {
|
||||
void *cache = malloc(48);
|
||||
if (cache == NULL || pthread_setspecific(cache_key, cache) != 0) abort();
|
||||
}
|
||||
}
|
||||
#if DEVER_LEDGER_SCENARIO == 1
|
||||
/* Keep the allocation observable to the optimizer and deliberately leak it. */
|
||||
leaked = malloc(1);
|
||||
if (leaked == NULL) abort();
|
||||
#elif DEVER_LEDGER_SCENARIO == 2
|
||||
if (calls > 1) return 7;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
#include "runtime.h"
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <signal.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef DEVER_LLVM_ABI
|
||||
extern int32_t dever_abi_probe(void);
|
||||
#endif
|
||||
|
||||
/* This harness consumes only the canonical ABI header, never Rust layouts. */
|
||||
static int expect_buffer(DeverRtBuffer *buffer, const char *expected) {
|
||||
const size_t len = strlen(expected);
|
||||
const int okay = buffer->len == len &&
|
||||
(len == 0 || (buffer->ptr != NULL && memcmp(buffer->ptr, expected, len) == 0));
|
||||
dever_rt_v1_buffer_free(buffer->ptr, buffer->len);
|
||||
buffer->ptr = NULL;
|
||||
buffer->len = 0;
|
||||
return okay;
|
||||
}
|
||||
|
||||
#define EXPECT(condition) do { \
|
||||
if (!(condition)) { fprintf(stderr, "ABI assertion failed at line %d: %s\n", __LINE__, #condition); return 1; } \
|
||||
} while (0)
|
||||
|
||||
typedef struct {
|
||||
void *label;
|
||||
int64_t count;
|
||||
} FixtureRecord;
|
||||
|
||||
static void text_clone(const void *source, void *destination) {
|
||||
*(void **)destination = dever_rt_v1_text_retain(*(void *const *)source);
|
||||
}
|
||||
|
||||
static void text_drop(void *value) {
|
||||
dever_rt_v1_text_release(*(void **)value);
|
||||
}
|
||||
|
||||
static uint8_t text_equal(const void *left, const void *right) {
|
||||
return dever_rt_v1_text_equal(*(void *const *)left, *(void *const *)right);
|
||||
}
|
||||
|
||||
static uint64_t text_hash(const void *value) {
|
||||
return dever_rt_v1_text_hash(*(void *const *)value);
|
||||
}
|
||||
|
||||
static void record_clone(const void *source, void *destination) {
|
||||
const FixtureRecord *record = source;
|
||||
*(FixtureRecord *)destination = (FixtureRecord){
|
||||
dever_rt_v1_text_retain(record->label), record->count
|
||||
};
|
||||
}
|
||||
|
||||
static void record_drop(void *value) {
|
||||
dever_rt_v1_text_release(((FixtureRecord *)value)->label);
|
||||
}
|
||||
|
||||
static uint8_t record_equal(const void *left, const void *right) {
|
||||
const FixtureRecord *first = left;
|
||||
const FixtureRecord *second = right;
|
||||
return first->count == second->count &&
|
||||
dever_rt_v1_text_equal(first->label, second->label);
|
||||
}
|
||||
|
||||
static const DeverRtType TEXT_TYPE = {
|
||||
sizeof(void *), _Alignof(void *), text_clone, text_drop, text_equal, text_hash
|
||||
};
|
||||
static const DeverRtType RECORD_TYPE = {
|
||||
sizeof(FixtureRecord), _Alignof(FixtureRecord),
|
||||
record_clone, record_drop, record_equal, NULL
|
||||
};
|
||||
|
||||
static int managed_values(void) {
|
||||
DeverRtBuffer error = {NULL, 0};
|
||||
void *label = NULL;
|
||||
void *key = NULL;
|
||||
void *second_key = NULL;
|
||||
EXPECT(dever_rt_v1_text_new((const uint8_t *)"label", 5, &label, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_text_new((const uint8_t *)"first", 5, &key, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_text_new((const uint8_t *)"second", 6, &second_key, &error) == DEVER_RT_OK);
|
||||
/* These source views are borrowed; only the ABI's clones own row fields. */
|
||||
const FixtureRecord source = {label, 42};
|
||||
const void *rows[] = {&source, &source};
|
||||
void *list = NULL;
|
||||
void *changed = NULL;
|
||||
int64_t size = 0;
|
||||
EXPECT(dever_rt_v1_list_new(&RECORD_TYPE, rows, 2, &list, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_list_append_take(dever_rt_v1_list_retain(list), &source, &changed, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_list_length(list, &size, &error) == DEVER_RT_OK && size == 2);
|
||||
EXPECT(dever_rt_v1_list_length(changed, &size, &error) == DEVER_RT_OK && size == 3);
|
||||
FixtureRecord extracted;
|
||||
uint8_t present = 0;
|
||||
EXPECT(dever_rt_v1_list_first_take(dever_rt_v1_list_retain(list), &extracted, &present, &error) == DEVER_RT_OK && present);
|
||||
EXPECT(record_equal(&source, &extracted));
|
||||
record_drop(&extracted);
|
||||
void *cursor = NULL;
|
||||
EXPECT(dever_rt_v1_list_cursor_take(changed, &cursor, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_list_cursor_next(cursor, &extracted, &present, &error) == DEVER_RT_OK && present);
|
||||
EXPECT(record_equal(&source, &extracted));
|
||||
record_drop(&extracted);
|
||||
/* Early cursor release must destroy the unvisited rows too. */
|
||||
dever_rt_v1_list_cursor_release(cursor);
|
||||
dever_rt_v1_list_release(list);
|
||||
|
||||
const void *keys[] = {&key, &second_key};
|
||||
void *map = NULL;
|
||||
EXPECT(dever_rt_v1_map_new(&TEXT_TYPE, &RECORD_TYPE, keys, rows, 2, &map, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_map_get_take(dever_rt_v1_map_retain(map), &key, &extracted, &present, &error) == DEVER_RT_OK && present);
|
||||
EXPECT(record_equal(&source, &extracted));
|
||||
record_drop(&extracted);
|
||||
const FixtureRecord replacement = {label, 99};
|
||||
EXPECT(dever_rt_v1_map_put_take(dever_rt_v1_map_retain(map), &key, &replacement, &changed, &error) == DEVER_RT_OK);
|
||||
EXPECT(!dever_rt_v1_map_equal(map, changed));
|
||||
EXPECT(dever_rt_v1_map_get_take(changed, &key, &extracted, &present, &error) == DEVER_RT_OK && present);
|
||||
EXPECT(extracted.count == 99);
|
||||
record_drop(&extracted);
|
||||
EXPECT(dever_rt_v1_map_cursor_take(map, &cursor, &error) == DEVER_RT_OK);
|
||||
void *extracted_key = NULL;
|
||||
EXPECT(dever_rt_v1_map_cursor_next(cursor, &extracted_key, &extracted, &present, &error) == DEVER_RT_OK && present);
|
||||
EXPECT(dever_rt_v1_text_equal(key, extracted_key));
|
||||
text_drop(&extracted_key);
|
||||
record_drop(&extracted);
|
||||
dever_rt_v1_map_cursor_release(cursor);
|
||||
const void *duplicate_keys[] = {&key, &key};
|
||||
EXPECT(dever_rt_v1_map_new(&TEXT_TYPE, &RECORD_TYPE, duplicate_keys, rows, 2, &map, &error) == DEVER_RT_RUNTIME_ERROR);
|
||||
EXPECT(expect_buffer(&error, "duplicate Map key"));
|
||||
|
||||
void *text_list = NULL;
|
||||
void *separator = NULL;
|
||||
EXPECT(dever_rt_v1_text_new((const uint8_t *)"a", 1, &separator, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_text_split(label, separator, &TEXT_TYPE, &text_list, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_list_length(text_list, &size, &error) == DEVER_RT_OK && size == 2);
|
||||
dever_rt_v1_list_release(text_list);
|
||||
dever_rt_v1_text_release(separator);
|
||||
void *bytes = NULL;
|
||||
void *slice = NULL;
|
||||
void *decoded = NULL;
|
||||
EXPECT(dever_rt_v1_bytes_from_text(label, &bytes, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_bytes_slice(bytes, 1, 4, &slice, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_bytes_to_text_handle(slice, &decoded, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_text_export(decoded, &error) == DEVER_RT_OK);
|
||||
EXPECT(expect_buffer(&error, "abe"));
|
||||
dever_rt_v1_text_release(decoded);
|
||||
dever_rt_v1_bytes_release(slice);
|
||||
dever_rt_v1_bytes_release(bytes);
|
||||
dever_rt_v1_text_release(label);
|
||||
dever_rt_v1_text_release(key);
|
||||
dever_rt_v1_text_release(second_key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
#ifdef SIGPIPE
|
||||
EXPECT(signal(SIGPIPE, SIG_DFL) != SIG_ERR);
|
||||
#endif
|
||||
EXPECT(dever_rt_v1_process_init() == DEVER_RT_OK);
|
||||
#ifdef SIGPIPE
|
||||
EXPECT(raise(SIGPIPE) == 0);
|
||||
#endif
|
||||
const char *arguments[] = {"suite", "1", NULL};
|
||||
uint64_t selected = 99;
|
||||
EXPECT(dever_rt_v1_process_test_index(2, arguments, 2, &selected) == DEVER_RT_OK);
|
||||
EXPECT(selected == 1);
|
||||
const char *invalid_indices[] = {"", "-1", "+1", " 1", "1x", "2", "18446744073709551616"};
|
||||
for (size_t index = 0; index < sizeof(invalid_indices) / sizeof(invalid_indices[0]); index++) {
|
||||
arguments[1] = invalid_indices[index];
|
||||
selected = 99;
|
||||
EXPECT(dever_rt_v1_process_test_index(2, arguments, 2, &selected) == DEVER_RT_INVALID_INPUT);
|
||||
EXPECT(selected == 99);
|
||||
}
|
||||
EXPECT(dever_rt_v1_process_test_index(1, arguments, 2, &selected) == DEVER_RT_INVALID_INPUT);
|
||||
EXPECT(dever_rt_v1_process_test_index(2, NULL, 2, &selected) == DEVER_RT_INVALID_INPUT);
|
||||
EXPECT(dever_rt_v1_process_stderr(NULL, 0) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_process_stderr(NULL, 1) == DEVER_RT_INVALID_INPUT);
|
||||
EXPECT(dever_rt_v1_process_error(NULL) == DEVER_RT_INVALID_INPUT);
|
||||
DeverRtBuffer buffer = {NULL, 0};
|
||||
int64_t integer = 0;
|
||||
double floating = 0.0;
|
||||
DeverRtDecimal seven, two, zero, decimal;
|
||||
EXPECT(dever_rt_v1_version() == 1);
|
||||
EXPECT(dever_rt_v1_int_add(40, 2, &integer, &buffer) == DEVER_RT_OK && integer == 42);
|
||||
EXPECT(buffer.ptr == NULL && buffer.len == 0);
|
||||
EXPECT(dever_rt_v1_int_sub(8, 3, &integer, &buffer) == DEVER_RT_OK && integer == 5);
|
||||
EXPECT(dever_rt_v1_int_mul(7, 6, &integer, &buffer) == DEVER_RT_OK && integer == 42);
|
||||
EXPECT(dever_rt_v1_int_div(-7, 3, &integer, &buffer) == DEVER_RT_OK && integer == -2);
|
||||
EXPECT(dever_rt_v1_int_rem(-7, 3, &integer, &buffer) == DEVER_RT_OK && integer == -1);
|
||||
EXPECT(dever_rt_v1_int_neg(-42, &integer, &buffer) == DEVER_RT_OK && integer == 42);
|
||||
EXPECT(dever_rt_v1_int_add(INT64_MAX, 1, &integer, &buffer) == DEVER_RT_RUNTIME_ERROR);
|
||||
EXPECT(expect_buffer(&buffer, "Int overflow"));
|
||||
EXPECT(dever_rt_v1_int_div(1, 0, &integer, &buffer) == DEVER_RT_RUNTIME_ERROR);
|
||||
EXPECT(expect_buffer(&buffer, "Int division by zero"));
|
||||
EXPECT(dever_rt_v1_int_neg(INT64_MIN, &integer, &buffer) == DEVER_RT_RUNTIME_ERROR);
|
||||
EXPECT(expect_buffer(&buffer, "Int overflow"));
|
||||
EXPECT(dever_rt_v1_int_add(1, 2, NULL, &buffer) == DEVER_RT_INVALID_INPUT);
|
||||
EXPECT(buffer.ptr != NULL && buffer.len != 0);
|
||||
dever_rt_v1_buffer_free(buffer.ptr, buffer.len);
|
||||
buffer = (DeverRtBuffer){NULL, 0};
|
||||
EXPECT(dever_rt_v1_int_add(1, 2, &integer, NULL) == DEVER_RT_INVALID_INPUT);
|
||||
|
||||
EXPECT(dever_rt_v1_float_div(0.0, 0.0, &floating, &buffer) == DEVER_RT_OK && isnan(floating));
|
||||
EXPECT(dever_rt_v1_float_div(1.0, 0.0, &floating, &buffer) == DEVER_RT_OK && isinf(floating));
|
||||
EXPECT(dever_rt_v1_float_add(1.5, 2.5, &floating, &buffer) == DEVER_RT_OK && floating == 4.0);
|
||||
EXPECT(dever_rt_v1_float_rem(7.0, 3.0, &floating, &buffer) == DEVER_RT_OK && floating == 1.0);
|
||||
EXPECT(dever_rt_v1_float_neg(0.0, &floating, &buffer) == DEVER_RT_OK && signbit(floating));
|
||||
EXPECT(dever_rt_v1_float_to_text(3.0, &buffer) == DEVER_RT_OK);
|
||||
EXPECT(expect_buffer(&buffer, "3"));
|
||||
|
||||
EXPECT(dever_rt_v1_decimal_from_int(7, &seven, &buffer) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_decimal_from_int(2, &two, &buffer) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_decimal_from_int(0, &zero, &buffer) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_decimal_div(seven.low, seven.high, two.low, two.high, &decimal, &buffer) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_decimal_to_text(decimal.low, decimal.high, &buffer) == DEVER_RT_OK);
|
||||
EXPECT(expect_buffer(&buffer, "3.5"));
|
||||
EXPECT(dever_rt_v1_decimal_round(decimal.low, decimal.high, 0, &decimal, &buffer) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_decimal_to_text(decimal.low, decimal.high, &buffer) == DEVER_RT_OK);
|
||||
EXPECT(expect_buffer(&buffer, "4"));
|
||||
EXPECT(dever_rt_v1_decimal_div(seven.low, seven.high, zero.low, zero.high, &decimal, &buffer) == DEVER_RT_RUNTIME_ERROR);
|
||||
EXPECT(expect_buffer(&buffer, "Decimal division by zero"));
|
||||
EXPECT(dever_rt_v1_int_to_text(INT64_MIN, &buffer) == DEVER_RT_OK);
|
||||
EXPECT(expect_buffer(&buffer, "-9223372036854775808"));
|
||||
|
||||
const uint8_t text[] = {0x61, 0xe4, 0xb8, 0xad};
|
||||
EXPECT(dever_rt_v1_text_copy_utf8(text, sizeof(text), &buffer) == DEVER_RT_OK);
|
||||
EXPECT(expect_buffer(&buffer, "a\xe4\xb8\xad"));
|
||||
const uint8_t bytes[] = {0, 255, 42};
|
||||
EXPECT(dever_rt_v1_bytes_copy(bytes, sizeof(bytes), &buffer) == DEVER_RT_OK);
|
||||
EXPECT(buffer.len == sizeof(bytes) && memcmp(buffer.ptr, bytes, sizeof(bytes)) == 0);
|
||||
dever_rt_v1_buffer_free(buffer.ptr, buffer.len);
|
||||
buffer = (DeverRtBuffer){NULL, 0};
|
||||
EXPECT(dever_rt_v1_bytes_to_text(text, sizeof(text), &buffer) == DEVER_RT_OK);
|
||||
EXPECT(expect_buffer(&buffer, "a\xe4\xb8\xad"));
|
||||
const uint8_t invalid[] = {255};
|
||||
EXPECT(dever_rt_v1_bytes_to_text(invalid, sizeof(invalid), &buffer) == DEVER_RT_RUNTIME_ERROR);
|
||||
EXPECT(buffer.ptr != NULL && buffer.len != 0);
|
||||
dever_rt_v1_buffer_free(buffer.ptr, buffer.len);
|
||||
buffer = (DeverRtBuffer){NULL, 0};
|
||||
EXPECT(dever_rt_v1_text_copy_utf8(NULL, 0, &buffer) == DEVER_RT_OK);
|
||||
EXPECT(buffer.ptr == NULL && buffer.len == 0);
|
||||
EXPECT(dever_rt_v1_bytes_copy(NULL, 1, &buffer) == DEVER_RT_INVALID_INPUT);
|
||||
EXPECT(buffer.ptr != NULL && buffer.len != 0);
|
||||
dever_rt_v1_buffer_free(buffer.ptr, buffer.len);
|
||||
dever_rt_v1_buffer_free(NULL, 0);
|
||||
#ifdef DEVER_LLVM_ABI
|
||||
EXPECT(dever_abi_probe() == 0);
|
||||
#endif
|
||||
EXPECT(managed_values() == 0);
|
||||
puts("native runtime ABI verified");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#include "runtime.h"
|
||||
|
||||
/* Link with managed-driver.c: the first flush initializes the logger; every
|
||||
* later flush must finish without outstanding per-call or deferred owners. */
|
||||
int dever_test_run(void) {
|
||||
dever_rt_v1_log_flush();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
#include "../../crates/dever-backend-bridge/include/runtime.h"
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdatomic.h>
|
||||
#include <pthread.h>
|
||||
#include <string.h>
|
||||
|
||||
extern int dever_test_run(void);
|
||||
|
||||
#ifndef DEVER_TEST_ITERATIONS
|
||||
#define DEVER_TEST_ITERATIONS 64
|
||||
#endif
|
||||
|
||||
/* Link-time wrappers observe allocations in this fixture's static runtime.
|
||||
* This is a bounded ownership regression, not a general memory profiler. */
|
||||
static _Atomic long long live_allocations;
|
||||
|
||||
void *__real_malloc(size_t size);
|
||||
void *__real_calloc(size_t count, size_t size);
|
||||
void *__real_realloc(void *pointer, size_t size);
|
||||
void __real_free(void *pointer);
|
||||
int __real_posix_memalign(void **pointer, size_t alignment, size_t size);
|
||||
char *__real_realpath(const char *path, char *resolved);
|
||||
|
||||
/* glibc allocates this returned owner inside its DSO, bypassing --wrap=malloc.
|
||||
* Rust canonicalize frees it through the executable's wrapped free. */
|
||||
char *__wrap_realpath(const char *path, char *resolved) {
|
||||
char *pointer = __real_realpath(path, resolved);
|
||||
atomic_fetch_add(&live_allocations, pointer != NULL && resolved == NULL);
|
||||
return pointer;
|
||||
}
|
||||
|
||||
void *__wrap_malloc(size_t size) {
|
||||
void *pointer = __real_malloc(size);
|
||||
atomic_fetch_add(&live_allocations, pointer != NULL);
|
||||
return pointer;
|
||||
}
|
||||
|
||||
void *__wrap_calloc(size_t count, size_t size) {
|
||||
void *pointer = __real_calloc(count, size);
|
||||
atomic_fetch_add(&live_allocations, pointer != NULL);
|
||||
return pointer;
|
||||
}
|
||||
|
||||
void *__wrap_realloc(void *pointer, size_t size) {
|
||||
int had_pointer = pointer != NULL;
|
||||
void *resized = __real_realloc(pointer, size);
|
||||
if (!had_pointer && resized != NULL) {
|
||||
atomic_fetch_add(&live_allocations, 1);
|
||||
} else if (had_pointer && resized == NULL && size == 0) {
|
||||
atomic_fetch_sub(&live_allocations, 1);
|
||||
}
|
||||
return resized;
|
||||
}
|
||||
|
||||
void __wrap_free(void *pointer) {
|
||||
atomic_fetch_sub(&live_allocations, pointer != NULL);
|
||||
__real_free(pointer);
|
||||
}
|
||||
|
||||
int __wrap_posix_memalign(void **pointer, size_t alignment, size_t size) {
|
||||
int status = __real_posix_memalign(pointer, alignment, size);
|
||||
atomic_fetch_add(&live_allocations, status == 0 && *pointer != NULL);
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Standard-library queue caches live until the host thread exits. Join before
|
||||
* measuring so lazy TLS initialization cannot masquerade as a per-call leak. */
|
||||
static void *invoke_kernel(void *result) {
|
||||
*(int *)result = dever_test_run();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int run_kernel(void) {
|
||||
pthread_t thread;
|
||||
int result = 0;
|
||||
int status = pthread_create(&thread, NULL, invoke_kernel, &result);
|
||||
if (status == 0) {
|
||||
status = pthread_join(thread, NULL);
|
||||
}
|
||||
if (status != 0) {
|
||||
fprintf(stderr, "managed kernel thread failed: %s\n", strerror(status));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
/* This C process owns signal policy just like the generated process main. */
|
||||
if (dever_rt_v1_process_init() != DEVER_RT_OK) {
|
||||
fprintf(stderr, "could not initialize the managed kernel process\n");
|
||||
return 1;
|
||||
}
|
||||
/* Permit process-wide lazy initialization, then require no per-call leak. */
|
||||
int result = run_kernel();
|
||||
if (result != 0) {
|
||||
fprintf(stderr, "managed kernel returned an unexpected result: %d\n", result);
|
||||
return 1;
|
||||
}
|
||||
long long baseline = atomic_load(&live_allocations);
|
||||
for (size_t remaining = DEVER_TEST_ITERATIONS; remaining != 0; --remaining) {
|
||||
result = run_kernel();
|
||||
if (result != 0) {
|
||||
fprintf(stderr, "managed kernel changed its result on repeated execution: %d\n", result);
|
||||
return 1;
|
||||
}
|
||||
long long current = atomic_load(&live_allocations);
|
||||
if (current != baseline) {
|
||||
fprintf(stderr, "managed kernel allocation imbalance: %lld\n",
|
||||
current - baseline);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
#include "runtime.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* These deliberately invalid domain inputs fail before opening a connection.
|
||||
* Retrying the same operation after invalid output slots proves poll did not
|
||||
* advance the future or mark it completed on the rejected call. */
|
||||
static unsigned frames_created;
|
||||
static unsigned frames_destroyed;
|
||||
|
||||
static void release_buffer(DeverRtBuffer *buffer) {
|
||||
dever_rt_v1_buffer_free(buffer->ptr, buffer->len);
|
||||
buffer->ptr = NULL;
|
||||
buffer->len = 0;
|
||||
}
|
||||
|
||||
static int message_is(const DeverRtBuffer *buffer, const char *expected) {
|
||||
size_t length = strlen(expected);
|
||||
return buffer->len == length && buffer->ptr &&
|
||||
memcmp(buffer->ptr, expected, length) == 0;
|
||||
}
|
||||
|
||||
static int text(const char *value, void **out) {
|
||||
DeverRtBuffer error = {0};
|
||||
uint32_t status = dever_rt_v1_text_new((const uint8_t *)value,
|
||||
(uint64_t)strlen(value), out, &error);
|
||||
release_buffer(&error);
|
||||
return status == DEVER_RT_OK;
|
||||
}
|
||||
|
||||
static int probe_slots(DeverRtPollState *state, DeverRtAsyncOp *operation,
|
||||
const char *runtime_message) {
|
||||
if (!operation) return 1;
|
||||
DeverRtBuffer error = {0};
|
||||
uint8_t present = 37;
|
||||
int64_t fault = 41;
|
||||
void *output = (void *)(uintptr_t)47;
|
||||
uint32_t status = dever_rt_v1_async_op_poll(state, operation, NULL, &present,
|
||||
&fault, &error);
|
||||
int valid = status == DEVER_RT_ASYNC_INVALID_INPUT &&
|
||||
message_is(&error, "invalid protocol handle output") &&
|
||||
present == 37 && fault == 41;
|
||||
release_buffer(&error);
|
||||
if (!valid) return 2;
|
||||
|
||||
_Alignas(void *) unsigned char unaligned[sizeof(void *) + 1];
|
||||
memset(unaligned, 53, sizeof(unaligned));
|
||||
status = dever_rt_v1_async_op_poll(state, operation, unaligned + 1,
|
||||
&present, &fault, &error);
|
||||
valid = status == DEVER_RT_ASYNC_INVALID_INPUT &&
|
||||
message_is(&error, "invalid protocol handle output") &&
|
||||
present == 37 && fault == 41;
|
||||
for (size_t index = 0; index < sizeof(unaligned); ++index) {
|
||||
valid = valid && unaligned[index] == 53;
|
||||
}
|
||||
release_buffer(&error);
|
||||
if (!valid) return 3;
|
||||
|
||||
status = dever_rt_v1_async_op_poll(state, operation, &output, &present,
|
||||
&fault, &error);
|
||||
valid = status == DEVER_RT_ASYNC_READY_RUNTIME_ERROR &&
|
||||
message_is(&error, runtime_message) &&
|
||||
output == (void *)(uintptr_t)47 && present == 37 && fault == 41;
|
||||
release_buffer(&error);
|
||||
if (!valid) return 4;
|
||||
|
||||
status = dever_rt_v1_async_op_poll(state, operation, &output, &present,
|
||||
&fault, &error);
|
||||
valid = status == DEVER_RT_ASYNC_INVALID_INPUT &&
|
||||
message_is(&error, "async operation is not polling");
|
||||
release_buffer(&error);
|
||||
return valid ? 0 : 5;
|
||||
}
|
||||
|
||||
static int probe_http(DeverRtPollState *state) {
|
||||
void *host = NULL, *method = NULL, *target = NULL, *empty = NULL;
|
||||
void *headers = NULL, *body = NULL, *request = NULL;
|
||||
DeverRtAsyncOp *operation = NULL;
|
||||
DeverRtBuffer error = {0};
|
||||
int result = 10;
|
||||
if (!text("127.0.0.1", &host) || !text("GET", &method) ||
|
||||
!text("/", &target) || !text("", &empty)) goto cleanup;
|
||||
if (dever_rt_v1_http_headers_new(&headers, &error) != DEVER_RT_OK) goto cleanup;
|
||||
if (dever_rt_v1_bytes_from_text(empty, &body, &error) != DEVER_RT_OK) goto cleanup;
|
||||
DeverRtHttpRequestFields fields = {method, target, headers, body};
|
||||
if (dever_rt_v1_http_request_new(&fields, &request, &error) != DEVER_RT_OK) goto cleanup;
|
||||
DeverRtHttpLimits limits = {1, 0, 100, 1, NULL};
|
||||
operation = dever_rt_v1_async_http_send(host, 0, request, &limits);
|
||||
/* Constructor ownership survives releasing every borrowed input. */
|
||||
dever_rt_v1_http_request_release(request);
|
||||
request = NULL;
|
||||
dever_rt_v1_text_release(host);
|
||||
host = NULL;
|
||||
result = probe_slots(state, operation, "HTTP header_bytes must be at least 8192");
|
||||
cleanup:
|
||||
dever_rt_v1_async_op_release(operation);
|
||||
dever_rt_v1_http_request_release(request);
|
||||
dever_rt_v1_http_headers_release(headers);
|
||||
dever_rt_v1_bytes_release(body);
|
||||
dever_rt_v1_text_release(host);
|
||||
dever_rt_v1_text_release(method);
|
||||
dever_rt_v1_text_release(target);
|
||||
dever_rt_v1_text_release(empty);
|
||||
release_buffer(&error);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int probe_websocket_open(DeverRtPollState *state) {
|
||||
void *url = NULL, *tls = NULL;
|
||||
DeverRtAsyncOp *operation = NULL;
|
||||
DeverRtBuffer error = {0};
|
||||
int result = 20;
|
||||
if (!text("http://fixture.invalid/", &url)) goto cleanup;
|
||||
if (dever_rt_v1_tls_system(&tls, &error) != DEVER_RT_OK) goto cleanup;
|
||||
DeverRtWsLimits limits = {128, 100, 100};
|
||||
operation = dever_rt_v1_async_ws_open(url, tls, &limits);
|
||||
dever_rt_v1_text_release(url);
|
||||
url = NULL;
|
||||
dever_rt_v1_client_tls_release(tls);
|
||||
tls = NULL;
|
||||
result = probe_slots(state, operation, "WebSocket URL must use ws or wss");
|
||||
cleanup:
|
||||
dever_rt_v1_async_op_release(operation);
|
||||
dever_rt_v1_text_release(url);
|
||||
dever_rt_v1_client_tls_release(tls);
|
||||
release_buffer(&error);
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
DeverRtPollState *state;
|
||||
int64_t *output;
|
||||
uint8_t completed;
|
||||
} ProbeFrame;
|
||||
|
||||
static void move_int(void *source, void *destination) {
|
||||
*(int64_t *)destination = *(int64_t *)source;
|
||||
*(int64_t *)source = 0;
|
||||
}
|
||||
|
||||
static void drop_int(void *value) { *(int64_t *)value = 0; }
|
||||
|
||||
static void *create(DeverRtPollState *state, const void *input, void *output, void *fault) {
|
||||
(void)input;
|
||||
(void)fault;
|
||||
ProbeFrame *frame = calloc(1, sizeof(*frame));
|
||||
if (!frame) return NULL;
|
||||
frame->state = state;
|
||||
frame->output = output;
|
||||
++frames_created;
|
||||
return frame;
|
||||
}
|
||||
|
||||
static void resume(void *raw) {
|
||||
ProbeFrame *frame = raw;
|
||||
int result = probe_http(frame->state);
|
||||
if (result == 0) result = probe_websocket_open(frame->state);
|
||||
*frame->output = result;
|
||||
frame->completed = 1;
|
||||
dever_rt_v1_async_complete(frame->state, DEVER_RT_ASYNC_READY_OK);
|
||||
}
|
||||
|
||||
static void destroy(void *raw) { ++frames_destroyed; free(raw); }
|
||||
static uint8_t done(void *raw) { return ((ProbeFrame *)raw)->completed; }
|
||||
|
||||
int main(void) {
|
||||
static const DeverRtOwnedType integer = {
|
||||
sizeof(int64_t), _Alignof(int64_t), move_int, drop_int, 1
|
||||
};
|
||||
static const DeverRtAsyncFunction function = {
|
||||
&integer, &integer, create, resume, destroy, done, 1
|
||||
};
|
||||
int64_t output = -1, fault = -1;
|
||||
DeverRtBuffer error = {0};
|
||||
uint32_t status = dever_rt_v1_async_root(&function, NULL, &output, &fault, &error);
|
||||
int valid = status == DEVER_RT_ASYNC_READY_OK && output == 0 &&
|
||||
fault == -1 && error.ptr == NULL && error.len == 0 &&
|
||||
frames_created == 1 && frames_destroyed == 1;
|
||||
release_buffer(&error);
|
||||
return valid ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
#include "runtime.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define EXPECT(condition) do { \
|
||||
if (!(condition)) { \
|
||||
fprintf(stderr, "resource ABI assertion failed at line %d: %s\n", __LINE__, #condition); \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
typedef struct {
|
||||
uint64_t kind;
|
||||
void *value;
|
||||
} ReadEvent;
|
||||
|
||||
static unsigned event_retains;
|
||||
static unsigned event_drops;
|
||||
|
||||
static void event_clone(const void *source, void *destination) {
|
||||
const ReadEvent *event = source;
|
||||
ReadEvent *copy = destination;
|
||||
copy->kind = event->kind;
|
||||
copy->value = event->kind == 1
|
||||
? dever_rt_v1_bytes_retain(event->value)
|
||||
: dever_rt_v1_text_retain(event->value);
|
||||
++event_retains;
|
||||
}
|
||||
|
||||
static void event_drop(void *value) {
|
||||
ReadEvent *event = value;
|
||||
if (event->kind == 1) {
|
||||
dever_rt_v1_bytes_release(event->value);
|
||||
} else {
|
||||
dever_rt_v1_text_release(event->value);
|
||||
}
|
||||
++event_drops;
|
||||
}
|
||||
|
||||
static uint8_t event_equal(const void *left, const void *right) {
|
||||
const ReadEvent *first = left;
|
||||
const ReadEvent *second = right;
|
||||
if (first->kind != second->kind) return 0;
|
||||
return first->kind == 1
|
||||
? dever_rt_v1_bytes_equal(first->value, second->value)
|
||||
: dever_rt_v1_text_equal(first->value, second->value);
|
||||
}
|
||||
|
||||
static void event_chunk(const void *bytes, void *out) {
|
||||
*(ReadEvent *)out = (ReadEvent){1, dever_rt_v1_bytes_retain(bytes)};
|
||||
++event_retains;
|
||||
}
|
||||
|
||||
static void event_failed(const void *text, void *out) {
|
||||
*(ReadEvent *)out = (ReadEvent){2, dever_rt_v1_text_retain(text)};
|
||||
++event_retains;
|
||||
}
|
||||
|
||||
static const DeverRtType EVENT_TYPE = {
|
||||
sizeof(ReadEvent), _Alignof(ReadEvent), event_clone, event_drop, event_equal, NULL
|
||||
};
|
||||
static const DeverRtReadEvent READ_EVENT = {
|
||||
&EVENT_TYPE, event_chunk, event_failed
|
||||
};
|
||||
|
||||
static int expect_buffer(DeverRtBuffer *buffer, const char *expected) {
|
||||
size_t length = strlen(expected);
|
||||
int equal = buffer->len == length &&
|
||||
(length == 0 || (buffer->ptr && memcmp(buffer->ptr, expected, length) == 0));
|
||||
dever_rt_v1_buffer_free(buffer->ptr, buffer->len);
|
||||
*buffer = (DeverRtBuffer){NULL, 0};
|
||||
return equal;
|
||||
}
|
||||
|
||||
static int expect_nonempty(DeverRtBuffer *buffer) {
|
||||
int nonempty = buffer->ptr != NULL && buffer->len != 0;
|
||||
dever_rt_v1_buffer_free(buffer->ptr, buffer->len);
|
||||
*buffer = (DeverRtBuffer){NULL, 0};
|
||||
return nonempty;
|
||||
}
|
||||
|
||||
static int expect_event_bytes(ReadEvent *event, const char *expected) {
|
||||
DeverRtBuffer bytes = {NULL, 0};
|
||||
if (event->kind != 1 || dever_rt_v1_bytes_export(event->value, &bytes) != DEVER_RT_OK) {
|
||||
return 0;
|
||||
}
|
||||
return expect_buffer(&bytes, expected);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
EXPECT(argc == 2);
|
||||
DeverRtBuffer error = {NULL, 0};
|
||||
void *path = NULL;
|
||||
void *file = NULL;
|
||||
void *bytes = NULL;
|
||||
void *stream = NULL;
|
||||
uint8_t done = 0;
|
||||
uint8_t present = 0;
|
||||
EXPECT(dever_rt_v1_file_retain(NULL) == NULL);
|
||||
dever_rt_v1_file_release(NULL);
|
||||
EXPECT(dever_rt_v1_stream_retain(NULL) == NULL);
|
||||
dever_rt_v1_stream_release(NULL);
|
||||
EXPECT(dever_rt_v1_text_new((const uint8_t *)argv[1], strlen(argv[1]), &path, &error) == DEVER_RT_OK);
|
||||
char missing_path[4096];
|
||||
int missing_length = snprintf(missing_path, sizeof(missing_path), "%s.missing", argv[1]);
|
||||
EXPECT(missing_length > 0 && (size_t)missing_length < sizeof(missing_path));
|
||||
void *missing = NULL;
|
||||
EXPECT(dever_rt_v1_text_new((const uint8_t *)missing_path, (uint64_t)missing_length, &missing, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_file_open(missing, &file, &error) == DEVER_RT_RUNTIME_ERROR);
|
||||
EXPECT(expect_nonempty(&error));
|
||||
dever_rt_v1_text_release(missing);
|
||||
|
||||
/* Invalid output must not create the file. */
|
||||
EXPECT(dever_rt_v1_file_create(path, NULL, &error) == DEVER_RT_INVALID_INPUT);
|
||||
EXPECT(expect_buffer(&error, "invalid ABI output pointer"));
|
||||
EXPECT(dever_rt_v1_file_create(path, &file, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_file_create(path, &bytes, &error) == DEVER_RT_RUNTIME_ERROR);
|
||||
EXPECT(expect_nonempty(&error));
|
||||
EXPECT(dever_rt_v1_bytes_new((const uint8_t *)"abcd", 4, &bytes, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_file_write(file, bytes, &done, &error) == DEVER_RT_OK && done == 1);
|
||||
void *alias = dever_rt_v1_file_retain(file);
|
||||
EXPECT(dever_rt_v1_file_close(file, &done, &error) == DEVER_RT_OK && done == 1);
|
||||
EXPECT(dever_rt_v1_file_write(alias, bytes, &done, &error) == DEVER_RT_RUNTIME_ERROR);
|
||||
EXPECT(expect_buffer(&error, "resource is closed"));
|
||||
EXPECT(dever_rt_v1_file_close(alias, &done, &error) == DEVER_RT_RUNTIME_ERROR);
|
||||
EXPECT(expect_buffer(&error, "resource is closed"));
|
||||
dever_rt_v1_file_release(alias);
|
||||
dever_rt_v1_file_release(file);
|
||||
dever_rt_v1_bytes_release(bytes);
|
||||
|
||||
EXPECT(dever_rt_v1_file_open(path, &file, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_file_read(file, 0, &bytes, &present, &error) == DEVER_RT_RUNTIME_ERROR);
|
||||
EXPECT(expect_buffer(&error, "read size must be a positive Int"));
|
||||
EXPECT(dever_rt_v1_file_read(file, 4, NULL, &present, &error) == DEVER_RT_INVALID_INPUT);
|
||||
EXPECT(expect_buffer(&error, "invalid ABI output pointer"));
|
||||
EXPECT(dever_rt_v1_file_read(file, 4, &bytes, &present, &error) == DEVER_RT_OK && present);
|
||||
EXPECT(dever_rt_v1_bytes_export(bytes, &error) == DEVER_RT_OK);
|
||||
EXPECT(expect_buffer(&error, "abcd"));
|
||||
dever_rt_v1_bytes_release(bytes);
|
||||
bytes = NULL;
|
||||
EXPECT(dever_rt_v1_file_read(file, 4, &bytes, &present, &error) == DEVER_RT_OK && !present);
|
||||
EXPECT(bytes == NULL);
|
||||
EXPECT(dever_rt_v1_file_close(file, &done, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_file_chunks(file, 2, &READ_EVENT, &stream, &error) == DEVER_RT_RUNTIME_ERROR);
|
||||
EXPECT(expect_buffer(&error, "resource is closed"));
|
||||
dever_rt_v1_file_release(file);
|
||||
|
||||
EXPECT(dever_rt_v1_file_open(path, &file, &error) == DEVER_RT_OK);
|
||||
DeverRtReadEvent invalid_event = {&EVENT_TYPE, event_chunk, NULL};
|
||||
EXPECT(dever_rt_v1_file_chunks(file, 2, &invalid_event, &stream, &error) == DEVER_RT_INVALID_INPUT);
|
||||
EXPECT(expect_buffer(&error, "incomplete ABI read event descriptor"));
|
||||
EXPECT(dever_rt_v1_file_chunks(file, 0, &READ_EVENT, &stream, &error) == DEVER_RT_RUNTIME_ERROR);
|
||||
EXPECT(expect_buffer(&error, "read size must be a positive Int"));
|
||||
EXPECT(dever_rt_v1_file_chunks(file, 2, &READ_EVENT, &stream, &error) == DEVER_RT_OK);
|
||||
alias = dever_rt_v1_stream_retain(stream);
|
||||
ReadEvent event;
|
||||
EXPECT(dever_rt_v1_stream_pull(stream, NULL, &present, &error) == DEVER_RT_INVALID_INPUT);
|
||||
EXPECT(expect_buffer(&error, "invalid ABI output pointer"));
|
||||
EXPECT(dever_rt_v1_stream_pull(alias, &event, &present, &error) == DEVER_RT_OK && present);
|
||||
EXPECT(expect_event_bytes(&event, "ab"));
|
||||
event_drop(&event);
|
||||
EXPECT(dever_rt_v1_stream_pull(stream, &event, &present, &error) == DEVER_RT_OK && present);
|
||||
EXPECT(expect_event_bytes(&event, "cd"));
|
||||
event_drop(&event);
|
||||
EXPECT(dever_rt_v1_stream_pull(alias, &event, &present, &error) == DEVER_RT_OK && !present);
|
||||
EXPECT(dever_rt_v1_stream_close(stream, &done, &error) == DEVER_RT_OK && done == 1);
|
||||
EXPECT(dever_rt_v1_stream_close(alias, &done, &error) == DEVER_RT_OK && done == 1);
|
||||
dever_rt_v1_stream_release(alias);
|
||||
dever_rt_v1_stream_release(stream);
|
||||
EXPECT(dever_rt_v1_file_close(file, &done, &error) == DEVER_RT_OK);
|
||||
dever_rt_v1_file_release(file);
|
||||
|
||||
EXPECT(dever_rt_v1_file_open(path, &file, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_file_chunks(file, 1, &READ_EVENT, &stream, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_stream_pull(stream, &event, &present, &error) == DEVER_RT_OK && present);
|
||||
EXPECT(expect_event_bytes(&event, "a"));
|
||||
event_drop(&event);
|
||||
EXPECT(dever_rt_v1_stream_close(stream, &done, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_stream_pull(stream, &event, &present, &error) == DEVER_RT_OK && !present);
|
||||
dever_rt_v1_stream_release(stream);
|
||||
EXPECT(dever_rt_v1_file_close(file, &done, &error) == DEVER_RT_OK);
|
||||
dever_rt_v1_file_release(file);
|
||||
|
||||
EXPECT(dever_rt_v1_file_open(path, &file, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_file_chunks(file, 2, &READ_EVENT, &stream, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_file_close(file, &done, &error) == DEVER_RT_OK);
|
||||
EXPECT(dever_rt_v1_stream_pull(stream, &event, &present, &error) == DEVER_RT_OK && present);
|
||||
EXPECT(event.kind == 2);
|
||||
EXPECT(dever_rt_v1_text_export(event.value, &error) == DEVER_RT_OK);
|
||||
EXPECT(expect_buffer(&error, "resource is closed"));
|
||||
event_drop(&event);
|
||||
EXPECT(dever_rt_v1_stream_pull(stream, &event, &present, &error) == DEVER_RT_OK && !present);
|
||||
dever_rt_v1_stream_release(stream);
|
||||
dever_rt_v1_file_release(file);
|
||||
|
||||
EXPECT(dever_rt_v1_file_chunks(NULL, 2, &READ_EVENT, &stream, &error) == DEVER_RT_INVALID_INPUT);
|
||||
EXPECT(expect_buffer(&error, "invalid ABI handle"));
|
||||
EXPECT(dever_rt_v1_file_open(NULL, &file, &error) == DEVER_RT_INVALID_INPUT);
|
||||
EXPECT(expect_buffer(&error, "invalid ABI handle"));
|
||||
dever_rt_v1_text_release(path);
|
||||
EXPECT(event_retains == event_drops);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#include "../../crates/dever-backend-bridge/include/runtime.h"
|
||||
#include <errno.h>
|
||||
|
||||
/* Keep the same allocation oracle as application/kernel probes. Only case
|
||||
* selection and the suite's indexed fault ownership differ. */
|
||||
#define main managed_main
|
||||
#include "managed-driver.c"
|
||||
#undef main
|
||||
|
||||
extern int32_t dever_test_entry(int64_t index, void *out, void *fault);
|
||||
extern uint64_t dever_test_fault_size(int64_t index);
|
||||
extern void dever_test_fault_release(int64_t index, void *fault);
|
||||
extern int32_t dever_test_fault_render(int64_t index, const void *fault,
|
||||
void **out, DeverRtBuffer *error);
|
||||
extern int64_t dever_test_count(void);
|
||||
|
||||
static int64_t case_index;
|
||||
static int32_t expected_status;
|
||||
|
||||
int dever_test_run(void) {
|
||||
uint64_t size = dever_test_fault_size(case_index);
|
||||
if (size == 0 || size > 1024 * 1024) return 2;
|
||||
void *fault = calloc(1, (size_t)size);
|
||||
if (fault == NULL) return 3;
|
||||
uint8_t unit = 0;
|
||||
int32_t status = dever_test_entry(case_index, &unit, fault);
|
||||
int failed = status != expected_status;
|
||||
if (status != 0) {
|
||||
void *rendered = NULL;
|
||||
DeverRtBuffer error = {0};
|
||||
int32_t render_status = dever_test_fault_render(case_index, fault, &rendered, &error);
|
||||
if (render_status == 0) {
|
||||
DeverRtBuffer text = {0};
|
||||
if (dever_rt_v1_text_export(rendered, &text) != 0) {
|
||||
failed = 1;
|
||||
} else {
|
||||
fwrite(text.ptr, 1, (size_t)text.len, stderr);
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
dever_rt_v1_buffer_free(text.ptr, text.len);
|
||||
dever_rt_v1_text_release(rendered);
|
||||
} else {
|
||||
failed = 1;
|
||||
fwrite(error.ptr, 1, (size_t)error.len, stderr);
|
||||
}
|
||||
dever_rt_v1_buffer_free(error.ptr, error.len);
|
||||
}
|
||||
dever_test_fault_release(case_index, fault);
|
||||
free(fault);
|
||||
return failed;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc != 3) return 4;
|
||||
char *end = NULL;
|
||||
errno = 0;
|
||||
long long index = strtoll(argv[1], &end, 10);
|
||||
if (errno != 0 || end == argv[1] || *end != '\0' || index < 0 || index >= dever_test_count()) return 5;
|
||||
case_index = (int64_t)index;
|
||||
if (argv[2][0] == '0' && argv[2][1] == '\0') expected_status = 0;
|
||||
else if (argv[2][0] == '1' && argv[2][1] == '\0') expected_status = 1;
|
||||
else return 6;
|
||||
return managed_main();
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/* Canonical wire callbacks; repeated by the existing atomic allocation ledger. */
|
||||
#include "runtime.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct { void *title; int64_t count; } Row;
|
||||
static const DeverRtWireName fields[] = {
|
||||
{ (const uint8_t *)"title", 5 },
|
||||
{ (const uint8_t *)"count", 5 }
|
||||
};
|
||||
static uint32_t calls;
|
||||
|
||||
static void row_clone(const void *source, void *target) {
|
||||
const Row *from = source;
|
||||
Row *to = target;
|
||||
to->title = dever_rt_v1_text_retain(from->title);
|
||||
to->count = from->count;
|
||||
}
|
||||
|
||||
static void row_drop(void *value) {
|
||||
Row *row = value;
|
||||
dever_rt_v1_text_release(row->title);
|
||||
row->title = NULL;
|
||||
}
|
||||
|
||||
static uint8_t row_equal(const void *left, const void *right) {
|
||||
const Row *a = left;
|
||||
const Row *b = right;
|
||||
return a->count == b->count && dever_rt_v1_text_equal(a->title, b->title);
|
||||
}
|
||||
|
||||
static const DeverRtType row_type = {
|
||||
sizeof(Row), _Alignof(Row), row_clone, row_drop, row_equal, NULL
|
||||
};
|
||||
|
||||
static void error_release(DeverRtBuffer *error) {
|
||||
dever_rt_v1_buffer_free(error->ptr, error->len);
|
||||
*error = (DeverRtBuffer){0};
|
||||
}
|
||||
|
||||
static uint32_t row_decode(const void *node, void *output, DeverRtBuffer *error) {
|
||||
++calls;
|
||||
Row *row = output;
|
||||
*row = (Row){0};
|
||||
uint32_t status = dever_rt_v1_wire_node_fields(node, fields, 2, error);
|
||||
const void *child = NULL;
|
||||
uint8_t present = 0;
|
||||
if (status == 0) status = dever_rt_v1_wire_node_field(node, &fields[0], 1, &child, &present, error);
|
||||
if (status == 0) status = dever_rt_v1_wire_node_text(child, &row->title, error);
|
||||
if (status == 0) status = dever_rt_v1_wire_node_field(node, &fields[1], 1, &child, &present, error);
|
||||
if (status == 0) status = dever_rt_v1_wire_node_int(child, &row->count, error);
|
||||
if (status != 0) row_drop(row);
|
||||
return status;
|
||||
}
|
||||
|
||||
static uint32_t row_encode(const void *value, void *encoder, DeverRtBuffer *error) {
|
||||
++calls;
|
||||
const Row *row = value;
|
||||
uint32_t status = dever_rt_v1_wire_encoder_begin_object(encoder, error);
|
||||
if (status == 0) status = dever_rt_v1_wire_encoder_key(encoder, &fields[0], error);
|
||||
if (status == 0) status = dever_rt_v1_wire_encoder_text(encoder, row->title, error);
|
||||
if (status == 0) status = dever_rt_v1_wire_encoder_key(encoder, &fields[1], error);
|
||||
if (status == 0) status = dever_rt_v1_wire_encoder_int(encoder, row->count, error);
|
||||
if (status == 0) status = dever_rt_v1_wire_encoder_end(encoder, error);
|
||||
return status;
|
||||
}
|
||||
|
||||
static uint32_t bad_success_decode(const void *node, void *output, DeverRtBuffer *error) {
|
||||
uint32_t status = row_decode(node, output, error);
|
||||
if (status != 0) return status;
|
||||
int64_t ignored;
|
||||
(void)dever_rt_v1_int_div(1, 0, &ignored, error);
|
||||
/* A successful codec must not leave an error Buffer alongside its owner. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Invalid destinations must not inspect a Node or advance a scoped encoder. */
|
||||
static uint32_t destination_probe(const void *node, void *output, DeverRtBuffer *error) {
|
||||
const void *child = (const void *)(uintptr_t)17;
|
||||
uint8_t present = 91;
|
||||
if (dever_rt_v1_wire_node_field(NULL, &fields[0], 1, NULL, &present, error) != 2 || present != 91) return 2;
|
||||
error_release(error);
|
||||
if (dever_rt_v1_wire_node_field(NULL, &fields[0], 1, &child, NULL, error) != 2 || child != (const void *)(uintptr_t)17) return 2;
|
||||
error_release(error);
|
||||
unsigned char unaligned[sizeof(int64_t) + _Alignof(int64_t)];
|
||||
uintptr_t base = (uintptr_t)unaligned;
|
||||
size_t offset = (_Alignof(int64_t) - base % _Alignof(int64_t)) % _Alignof(int64_t);
|
||||
int64_t *bad = (int64_t *)(unaligned + offset + 1);
|
||||
if (dever_rt_v1_wire_node_int(NULL, bad, error) != 2) return 2;
|
||||
error_release(error);
|
||||
/* Correcting the destination still sees the original parsed object. */
|
||||
return row_decode(node, output, error);
|
||||
}
|
||||
|
||||
static uint32_t command_decode(void *inputs, void *output, DeverRtBuffer *error) {
|
||||
const DeverRtWireName field = { (const uint8_t *)"record", 6 };
|
||||
void *raw = NULL;
|
||||
uint8_t present = 91;
|
||||
*(Row *)output = (Row){0};
|
||||
if (dever_rt_v1_cmd_input_field(inputs, &field, 1, NULL, &present, error) != 2 || present != 91) return 2;
|
||||
error_release(error);
|
||||
uint32_t status = dever_rt_v1_cmd_input_field(inputs, &field, 1, &raw, &present, error);
|
||||
if (status == 0) status = dever_rt_v1_wire_decode(raw, &row_type, row_decode, output, error);
|
||||
dever_rt_v1_text_release(raw);
|
||||
return status;
|
||||
}
|
||||
|
||||
static int text_equals(void *text, const char *expected) {
|
||||
DeverRtBuffer bytes = {0};
|
||||
uint32_t status = dever_rt_v1_text_export(text, &bytes);
|
||||
int equal = status == 0 && bytes.len == strlen(expected) && memcmp(bytes.ptr, expected, (size_t)bytes.len) == 0;
|
||||
error_release(&bytes);
|
||||
return equal;
|
||||
}
|
||||
|
||||
static void bytes_clone(const void *source, void *target) {
|
||||
*(void **)target = dever_rt_v1_bytes_retain(*(void *const *)source);
|
||||
}
|
||||
|
||||
static void bytes_drop(void *value) {
|
||||
dever_rt_v1_bytes_release(*(void **)value);
|
||||
*(void **)value = NULL;
|
||||
}
|
||||
|
||||
static uint8_t bytes_equal(const void *left, const void *right) {
|
||||
return dever_rt_v1_bytes_equal(*(void *const *)left, *(void *const *)right);
|
||||
}
|
||||
|
||||
static const DeverRtType bytes_type = {
|
||||
sizeof(void *), _Alignof(void *), bytes_clone, bytes_drop, bytes_equal, NULL
|
||||
};
|
||||
|
||||
static uint32_t bytes_decode(const void *node, void *output, DeverRtBuffer *error) {
|
||||
if (dever_rt_v1_wire_node_bytes(NULL, NULL, error) != 2) return 2;
|
||||
error_release(error);
|
||||
return dever_rt_v1_wire_node_bytes(node, output, error);
|
||||
}
|
||||
|
||||
static uint32_t bytes_encode(const void *value, void *encoder, DeverRtBuffer *error) {
|
||||
return dever_rt_v1_wire_encoder_bytes(encoder, *(void *const *)value, error);
|
||||
}
|
||||
|
||||
static int bytes_round_trip(void) {
|
||||
DeverRtBuffer error = {0};
|
||||
void *text = NULL, *bytes = NULL, *encoded = NULL;
|
||||
const char *json = "\"AP+ACg==\"";
|
||||
const uint8_t expected[] = {0, 255, 128, 10};
|
||||
if (dever_rt_v1_text_new((const uint8_t *)json, strlen(json), &text, &error) != 0) return 20;
|
||||
if (dever_rt_v1_wire_decode(text, &bytes_type, bytes_decode, &bytes, &error) != 0) return 21;
|
||||
DeverRtBuffer actual = {0};
|
||||
if (dever_rt_v1_bytes_export(bytes, &actual) != 0 || actual.len != sizeof(expected) || memcmp(actual.ptr, expected, sizeof(expected)) != 0) return 22;
|
||||
error_release(&actual);
|
||||
if (dever_rt_v1_wire_encode(&bytes, &bytes_type, bytes_encode, &encoded, &error) != 0 || !text_equals(encoded, json)) return 23;
|
||||
dever_rt_v1_text_release(encoded);
|
||||
dever_rt_v1_text_release(text);
|
||||
bytes_drop(&bytes);
|
||||
const char *invalid[] = {"null", "[0,255]", "\"/w\"", "\"/x==\"", "\"_w==\""};
|
||||
for (size_t i = 0; i < sizeof(invalid) / sizeof(invalid[0]); ++i) {
|
||||
if (dever_rt_v1_text_new((const uint8_t *)invalid[i], strlen(invalid[i]), &text, &error) != 0) return 24;
|
||||
if (dever_rt_v1_wire_decode(text, &bytes_type, bytes_decode, &bytes, &error) != 1 || bytes != NULL || error.len == 0) return 25;
|
||||
error_release(&error);
|
||||
dever_rt_v1_text_release(text);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t dever_test_run(void) {
|
||||
int bytes_status = bytes_round_trip();
|
||||
if (bytes_status != 0) return bytes_status;
|
||||
DeverRtBuffer error = {0};
|
||||
void *text = NULL;
|
||||
Row row = {0};
|
||||
void *encoded = NULL;
|
||||
const char *json = "{\"title\":\"owned\",\"count\":9223372036854775807}";
|
||||
if (dever_rt_v1_text_new((const uint8_t *)json, strlen(json), &text, &error) != 0) return 1;
|
||||
calls = 0;
|
||||
if (dever_rt_v1_wire_decode(text, &row_type, row_decode, NULL, &error) != 2 || calls != 0) return 2;
|
||||
error_release(&error);
|
||||
if (dever_rt_v1_wire_decode(text, &row_type, NULL, &row, &error) != 2 || calls != 0) return 3;
|
||||
error_release(&error);
|
||||
if (dever_rt_v1_wire_decode(text, &row_type, destination_probe, &row, &error) != 0 || row.count != INT64_MAX || !text_equals(row.title, "owned")) return 4;
|
||||
calls = 0;
|
||||
if (dever_rt_v1_wire_encode(&row, &row_type, row_encode, NULL, &error) != 2 || calls != 0) return 5;
|
||||
error_release(&error);
|
||||
if (dever_rt_v1_wire_encode(&row, &row_type, row_encode, &encoded, &error) != 0 || calls != 1 || !text_equals(encoded, json)) return 6;
|
||||
dever_rt_v1_text_release(encoded);
|
||||
row_drop(&row);
|
||||
if (dever_rt_v1_wire_decode(text, &row_type, bad_success_decode, &row, &error) != 2 || row.title != NULL) return 9;
|
||||
error_release(&error);
|
||||
dever_rt_v1_text_release(text);
|
||||
|
||||
const char *invalid[] = {
|
||||
"{\"title\":\"partially-owned\",\"count\":\"wrong\"}",
|
||||
"{\"title\":\"partially-owned\"}",
|
||||
"{\"title\":\"partially-owned\",\"count\":9223372036854775808}",
|
||||
"{\"title\":\"one\",\"title\":\"duplicate\",\"count\":1}",
|
||||
"{\"title\":\"owned\",\"count\":1,\"extra\":false}",
|
||||
"[]",
|
||||
"{broken"
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(invalid) / sizeof(invalid[0]); ++i) {
|
||||
if (dever_rt_v1_text_new((const uint8_t *)invalid[i], strlen(invalid[i]), &text, &error) != 0) return 7;
|
||||
row = (Row){0};
|
||||
if (dever_rt_v1_wire_decode(text, &row_type, row_decode, &row, &error) != 1 || row.title != NULL || error.len == 0) return 8;
|
||||
error_release(&error);
|
||||
dever_rt_v1_text_release(text);
|
||||
}
|
||||
|
||||
const char *command = "{\"record\":{\"title\":\"command-owned\",\"count\":42}}";
|
||||
if (dever_rt_v1_text_new((const uint8_t *)command, strlen(command), &text, &error) != 0) return 10;
|
||||
if (dever_rt_v1_cmd_decode(text, &row_type, command_decode, &row, &error) != 0 || row.count != 42 || !text_equals(row.title, "command-owned")) return 11;
|
||||
row_drop(&row);
|
||||
dever_rt_v1_text_release(text);
|
||||
|
||||
const char *bad_commands[] = {
|
||||
"{\"record\":{\"title\":\"complete-owned\",\"count\":42},\"unknown\":false}",
|
||||
"{\"record\":{\"title\":\"partial-owned\",\"count\":\"bad\"},\"unknown\":false}",
|
||||
"{}"
|
||||
};
|
||||
const char *messages[] = {
|
||||
"unknown input 'unknown'", "expected wire integer", "input 'record' is required"
|
||||
};
|
||||
for (size_t i = 0; i < sizeof(bad_commands) / sizeof(bad_commands[0]); ++i) {
|
||||
if (dever_rt_v1_text_new((const uint8_t *)bad_commands[i], strlen(bad_commands[i]), &text, &error) != 0) return 12;
|
||||
row = (Row){0};
|
||||
if (dever_rt_v1_cmd_decode(text, &row_type, command_decode, &row, &error) != 1 || row.title != NULL || error.len != strlen(messages[i]) || memcmp(error.ptr, messages[i], (size_t)error.len) != 0) return 13;
|
||||
error_release(&error);
|
||||
dever_rt_v1_text_release(text);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user