diff options
| author | eunhhu <[email protected]> | 2026-09-24 17:14:54 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-24 17:14:54 +0100 |
| commit | b1ce1927022d374198441afafeb27199fc8a052a (patch) | |
| tree | 6021f175f4c67ed03b44240eaa2f90d846f12f6f /prototypes/connections/test-runtime.c | |
| parent | d34f9426d5317a1948f12f229439946728bc342a (diff) | |
| download | pure-b1ce1927022d374198441afafeb27199fc8a052a.tar.gz pure-b1ce1927022d374198441afafeb27199fc8a052a.zip | |
Establish native micro runtime checkpoint
Diffstat (limited to 'prototypes/connections/test-runtime.c')
| -rw-r--r-- | prototypes/connections/test-runtime.c | 356 |
1 files changed, 356 insertions, 0 deletions
diff --git a/prototypes/connections/test-runtime.c b/prototypes/connections/test-runtime.c new file mode 100644 index 0000000..0883ec8 --- /dev/null +++ b/prototypes/connections/test-runtime.c @@ -0,0 +1,356 @@ +#define _POSIX_C_SOURCE 200809L +#include "runtime.h" +#include "store-port.h" +#include "store.h" +#include <assert.h> +#include <dirent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +static const PureProgram identity = { + .read = {.to_end = true}, .write = {.to_end = true}, + .reply = {.to_end = true}, .reply_input = true +}; + +static void remove_store(const char *path) { + DIR *directory = opendir(path); + assert(directory); + struct dirent *entry; + while ((entry = readdir(directory))) { + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; + assert(unlinkat(dirfd(directory), entry->d_name, 0) == 0); + } + assert(closedir(directory) == 0 && rmdir(path) == 0); +} +typedef struct { + size_t allocations, writes, frees, live; + bool allocation_failure, invalid_allocation, oversized_read, foreign_reply, reenter, reenter_release; + PureRuntime *runtime; + PureTicket release_ticket; + unsigned char received[256]; + size_t received_size; +} Host; +typedef struct { Host *host; unsigned char bytes[]; } Allocation; + +static void free_input(void *pointer, size_t size) { + Allocation *a = pointer; + assert(size > 0 && a->host->live > 0); + if (a->host->reenter_release) { + PureTicket ignored = {0}; + assert(!pure_runtime_release(a->host->release_ticket)); + assert(pure_runtime_accept(a->host->runtime, PURE_INPUT_WRITE, + "reenter", 0, 0, &ignored) == PURE_INVALID); + } + ++a->host->frees; + --a->host->live; + free(a); +} + +static bool allocate(void *context, size_t size, PureResource *out) { + Host *host = context; + if (host->allocation_failure) return false; + Allocation *a = malloc(sizeof(*a) + size); + assert(a); + a->host = host; + ++host->allocations; + ++host->live; + *out = (PureResource){.body = {a->bytes, size + (host->invalid_allocation ? 1u : 0u)}, .allocation = a, + .allocation_size = size, .release = free_input}; + return true; +} + +static PureResult write_value(void *context, const void *definition, const char *key, + int64_t expected, PureSpan input, PureSpan *reply, + int64_t *revision) { + Host *host = context; + (void)key; + PureSpan body; + if (!pure_program_input(definition, input, &body, reply)) return PURE_LIMIT; + if (host->foreign_reply) { + *reply = (PureSpan){(const unsigned char *)"outside", 7}; + *revision = 1; + return PURE_OK; + } + if (host->reenter) { + PureTicket ignored; + assert(pure_runtime_accept(host->runtime, PURE_INPUT_WRITE, "reenter", 0, 0, &ignored) == PURE_INVALID); + assert(!pure_runtime_step(host->runtime)); + } + if (expected == 99) { *revision = 1; return PURE_CONFLICT; } + ++host->writes; + assert(body.size <= sizeof(host->received)); + if (body.size) memcpy(host->received, body.data, body.size); + host->received_size = body.size; + *revision = 1; + return PURE_OK; +} + +static PureResult read_value(void *context, const void *definition, const char *key, + size_t available, PureResource *out, int64_t *revision) { + Host *host = context; + (void)definition; (void)key; + if (!host->oversized_read) return PURE_NOT_FOUND; + assert(available < 1000); + assert(allocate(host, available + 1, out)); + *revision = 1; + return PURE_OK; /* Deliberately violates the available-credit contract. */ +} + +static PureHost port(Host *host) { + return (PureHost){.context = host, .allocate = allocate, .read = read_value, .write = write_value}; +} + +static PureTicket submit(PureRuntime *r, const char *key, const void *bytes, size_t length) { + PureTicket ticket; + assert(pure_runtime_accept(r, PURE_INPUT_WRITE, key, 0, length, &ticket) == PURE_OK); + size_t capacity; + unsigned char *input = pure_runtime_ingress(ticket, &capacity); + assert(capacity == length); + if (length) memcpy(input, bytes, length); + assert(pure_runtime_ready(ticket)); + assert(!pure_runtime_ready(ticket)); + return ticket; +} + +static PureOutcome completed(PureRuntime *r, PureTicket ticket) { + PureOutcome result; + while (!pure_runtime_result(ticket, &result)) assert(pure_runtime_step(r)); + return result; +} + +static void admission_and_lifecycle(void) { + Host host = {0}; + PureRuntime r; + PureWork work[3]; + assert(pure_runtime_init(&r, work, 3, &identity, port(&host), 5)); + host.runtime = &r; + PureTicket a, untouched = {0}, out = untouched; + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "../bad", 0, 1, &out) == PURE_INVALID); + assert(!out.runtime && !host.allocations); + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "a", 0, 4, &a) == PURE_OK); + assert(r.retained == 4 && !pure_runtime_step(&r) && !host.writes); + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "b", 0, 2, &out) == PURE_BUDGET); + assert(!out.runtime && host.allocations == 1); + host.reenter_release = true; + host.release_ticket = a; + assert(pure_runtime_cancel(a)); + host.reenter_release = false; + PureOutcome cancelled; + assert(pure_runtime_result(a, &cancelled) && cancelled.status == PURE_CANCELLED); + assert(r.retained == 0 && host.frees == 1 && !pure_runtime_cancel(a)); + assert(pure_runtime_release(a) && !pure_runtime_release(a)); + assert(!pure_runtime_result(a, &cancelled)); + + PureTicket first = submit(&r, "first", "1", 1); + PureTicket middle = submit(&r, "middle", "2", 1); + PureTicket last = submit(&r, "last", "3", 1); + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "full", 0, 0, &out) == PURE_BUSY); + assert(pure_runtime_cancel(middle)); + assert(pure_runtime_release(middle)); + pure_runtime_stop(&r); + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "closed", 0, 0, &out) == PURE_CLOSED); + assert(!pure_runtime_restart(&r, &identity, port(&host), 5)); + host.reenter = true; + assert(completed(&r, first).status == PURE_OK && host.received[0] == '1'); + assert(completed(&r, last).status == PURE_OK && host.received[0] == '3'); + assert(!pure_runtime_step(&r)); + assert(pure_runtime_release(first) && pure_runtime_release(last)); + assert(r.active == 0 && r.retained == 0 && host.live == 0); + assert(pure_runtime_restart(&r, &identity, port(&host), 5)); + PureTicket next = submit(&r, "next", "4", 1); + assert(next.generation > last.generation && !pure_runtime_ready(a)); + assert(!pure_runtime_release(last)); + host.reenter_release = true; + host.release_ticket = next; + assert(pure_runtime_release(next)); /* queued input: no effect */ + host.reenter_release = false; + assert(host.writes == 2 && !pure_runtime_step(&r)); + + host.allocation_failure = true; + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "no_memory", 0, 1, &out) == PURE_ALLOCATION); + assert(r.active == 0 && r.retained == 0); + host.allocation_failure = false; + host.invalid_allocation = host.reenter_release = true; + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "bad_allocation", 0, 1, &out) == PURE_ALLOCATION); + assert(r.active == 0 && r.retained == 0 && host.live == 0); + host.invalid_allocation = host.reenter_release = false; + PureHost denied = port(&host); + denied.write = NULL; + assert(pure_runtime_restart(&r, &identity, denied, 5)); + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "denied", 0, 1, &out) == PURE_DENIED); + assert(pure_runtime_restart(&r, &identity, port(&host), 5)); + host.oversized_read = true; + assert(pure_runtime_accept(&r, PURE_INPUT_READ, "bad_host", 0, 0, &out) == PURE_OK); + assert(completed(&r, out).status == PURE_BUDGET && host.live == 0 && r.retained == 0); + assert(pure_runtime_release(out)); + host.foreign_reply = true; + PureTicket invalid_reply = submit(&r, "bad_reply", "x", 1); + assert(completed(&r, invalid_reply).status == PURE_STORE_ERROR); + assert(r.retained == 0 && host.live == 0 && pure_runtime_release(invalid_reply)); + host.foreign_reply = false; + r.serial = UINT64_MAX; + assert(pure_runtime_accept(&r, PURE_INPUT_WRITE, "exhausted", 0, 0, &out) == PURE_LIMIT); +} + +static void forwarding(void) { + Host a = {0}, b = {0}, c = {0}; + PureRuntime source, left, right; + PureWork source_work[1], left_work[1], right_work[1]; + assert(pure_runtime_init(&source, source_work, 1, &identity, port(&a), 8)); + assert(pure_runtime_init(&left, left_work, 1, &identity, port(&b), 2)); + assert(pure_runtime_init(&right, right_work, 1, &identity, port(&c), 8)); + PureTicket original = submit(&source, "original", "value", 5), x = {0}, y; + PureOutcome result = completed(&source, original); + assert(result.status == PURE_OK); + const unsigned char *backing = result.body.data; + assert(pure_runtime_forward(original, &left, "left", 0, &x) == PURE_BUDGET); + assert(!x.runtime && source.work[0].pins == 0); + assert(pure_runtime_restart(&left, &identity, port(&b), 8)); + assert(pure_runtime_forward(original, &left, "left", 0, &x) == PURE_OK); + assert(pure_runtime_forward(original, &right, "right", 0, &y) == PURE_OK); + assert(pure_runtime_release(original)); + assert(!pure_runtime_result(original, &result)); + pure_runtime_stop(&source); + assert(!pure_runtime_restart(&source, &identity, port(&a), 8)); + assert(a.live == 1 && !b.allocations && !c.allocations); + assert(pure_runtime_cancel(x) && pure_runtime_release(x)); + assert(a.live == 1 && !b.writes); + result = completed(&right, y); + assert(result.body.data == backing && result.body.size == 5 && !memcmp(result.body.data, "value", 5)); + assert(c.writes == 1 && !c.allocations && source.retained == 5); + assert(!pure_runtime_cancel(y)); /* effects cannot be rolled back */ + assert(pure_runtime_release(y)); + assert(a.frees == 1 && !source.active && !source.retained && !right.active && !right.retained); + assert(pure_runtime_restart(&source, &identity, port(&a), 8)); + + /* A completed consumer does not retain its input when the program has no + * input-backed reply. The producing domain can then be reclaimed early. */ + PureProgram no_reply = identity; + no_reply.reply_input = false; + assert(pure_runtime_restart(&right, &no_reply, port(&c), 8)); + original = submit(&source, "again", "last", 4); + assert(completed(&source, original).status == PURE_OK); + assert(pure_runtime_forward(original, &right, "no_reply", 0, &y) == PURE_OK); + assert(pure_runtime_release(original)); + assert(completed(&right, y).body.size == 0 && a.live == 0); + assert(source.active == 0 && right.active == 1 && right.retained == 0); + assert(pure_runtime_release(y)); +} + +static void idle_binding(void) { + Host a = {0}, b = {0}; + PureRuntime source, destination; + PureWork source_work[1], destination_work[1]; + PureTicket first = {0}; + assert(pure_runtime_init(&source, NULL, 0, &identity, port(&a), 8)); + assert(pure_runtime_init(&destination, destination_work, 1, &identity, port(&b), 8)); + assert(pure_runtime_accept(&source, PURE_INPUT_WRITE, "idle", 0, 1, &first) == PURE_BUSY); + assert(!first.runtime && !pure_runtime_bind(&source, NULL, 1)); + assert(pure_runtime_bind(&source, source_work, 1)); + first = submit(&source, "source", "abc", 3); + assert(!pure_runtime_bind(&source, NULL, 0)); + assert(completed(&source, first).status == PURE_OK); + PureTicket receiver; + assert(pure_runtime_forward(first, &destination, "receiver", 0, &receiver) == PURE_OK); + assert(pure_runtime_release(first)); + assert(!pure_runtime_bind(&source, NULL, 0)); /* The receiver still pins its backing. */ + assert(completed(&destination, receiver).status == PURE_OK); + assert(pure_runtime_release(receiver)); + assert(!source.active && !source.retained && !a.live); + assert(pure_runtime_bind(&source, NULL, 0)); + assert(!pure_runtime_result(first, &(PureOutcome){0})); + assert(pure_runtime_bind(&source, source_work, 1)); + PureTicket second = submit(&source, "again", "d", 1); + assert(second.generation > first.generation && !pure_runtime_ready(first)); + assert(completed(&source, second).status == PURE_OK); + assert(pure_runtime_release(second)); + assert(pure_runtime_bind(&source, NULL, 0)); + assert(source.accepted == 2 && source.releases == 2 && source.serial == second.generation); +} + +static void long_chain(void) { + enum { COUNT = 1024 }; + PureWork *work = calloc(COUNT, sizeof(*work)); + assert(work); + Host host = {0}; + PureRuntime r; + assert(pure_runtime_init(&r, work, COUNT, &identity, port(&host), COUNT)); + PureTicket ticket = submit(&r, "chain", "x", 1); + for (size_t i = 1; i < COUNT; ++i) { + assert(completed(&r, ticket).status == PURE_OK); + PureTicket next; + assert(pure_runtime_forward(ticket, &r, "chain", 0, &next) == PURE_OK); + assert(pure_runtime_release(ticket)); + ticket = next; + } + assert(completed(&r, ticket).status == PURE_OK); + assert(host.allocations == 1 && r.active == COUNT); + assert(pure_runtime_release(ticket)); + assert(r.retained == 0 && r.active == 0 && host.frees == 1); + free(work); +} + +static void actual_storage(void) { + char directory[] = "/tmp/pure-runtime-check-XXXXXX"; + assert(mkdtemp(directory)); + char source_path[128], target_path[128]; + snprintf(source_path, sizeof(source_path), "%s/source", directory); + snprintf(target_path, sizeof(target_path), "%s/target", directory); + assert(pure_store_init(source_path) == PURE_OK && pure_store_init(target_path) == PURE_OK); + PureProgram selection; + char error[160]; + assert(pure_program_load("examples/selection.pure", &selection, error, sizeof(error))); + PureRuntime source, target; + PureWork a[2], b[2]; + assert(pure_runtime_init(&source, a, 2, &selection, pure_store_host(source_path), 128)); + assert(pure_runtime_init(&target, b, 2, &identity, pure_store_host(target_path), 128)); + const unsigned char bytes[] = {1,0,0,0, 3,0,0,0, 'a','b','c','d','e'}; + PureTicket ticket = submit(&source, "excerpt", bytes, sizeof(bytes)), consumer; + PureOutcome result = completed(&source, ticket); + assert(result.status == PURE_OK && result.body.size == 3 && !memcmp(result.body.data, "bcd", 3)); + assert(pure_runtime_forward(ticket, &target, "archive", 0, &consumer) == PURE_OK); + assert(pure_runtime_release(ticket)); + assert(source.active == 1 && source.retained == sizeof(bytes)); + assert(completed(&target, consumer).status == PURE_OK); + assert(pure_runtime_release(consumer) && source.active == 0); + PureStoredView stored = {0}; + assert(pure_store_get(target_path, "archive", 128, &stored) == PURE_OK); + assert(stored.body.size == 3 && !memcmp(stored.body.data, "bcd", 3)); + pure_store_release(&stored); + + assert(pure_runtime_accept(&source, PURE_INPUT_READ, "excerpt", 0, 0, &ticket) == PURE_OK); + assert(completed(&source, ticket).status == PURE_OK); + assert(pure_runtime_forward(ticket, &target, "snapshot", 0, &consumer) == PURE_OK); + assert(pure_runtime_release(ticket)); + int64_t revision; + assert(pure_store_put(source_path, "excerpt", 1, (PureSpan){(const unsigned char *)"new", 3}, &revision) == PURE_OK); + result = completed(&target, consumer); + assert(result.body.size == 3 && !memcmp(result.body.data, "bcd", 3)); + assert(pure_runtime_release(consumer)); + + unsigned char invalid[] = {255,255,255,255, 1,0,0,0, 'x'}; + ticket = submit(&source, "invalid", invalid, sizeof(invalid)); + assert(completed(&source, ticket).status == PURE_LIMIT); + assert(pure_runtime_release(ticket)); + assert(pure_store_get(source_path, "invalid", 128, &stored) == PURE_NOT_FOUND); + assert(source.active == 0 && target.active == 0 && source.retained == 0 && target.retained == 0); + remove_store(source_path); + remove_store(target_path); + assert(rmdir(directory) == 0); +} + +int main(void) { + admission_and_lifecycle(); + forwarding(); + idle_binding(); + long_chain(); + actual_storage(); + printf("{\"runtime_tests\":\"passed\",\"domain_bytes\":%zu,\"work_slot_bytes\":%zu," + "\"ticket_bytes\":%zu,\"prepared_program_bytes\":%zu,\"forwarding_chain\":1024," + "\"native_storage_pipeline\":true,\"idle_slot_binding\":true," + "\"release_reentrancy_guard\":true}\n", sizeof(PureRuntime), sizeof(PureWork), + sizeof(PureTicket), sizeof(PureProgram)); + return 0; +} |
