aboutsummaryrefslogtreecommitdiffstats
path: root/prototypes/connections/test-splice.c
diff options
context:
space:
mode:
Diffstat (limited to 'prototypes/connections/test-splice.c')
-rw-r--r--prototypes/connections/test-splice.c190
1 files changed, 190 insertions, 0 deletions
diff --git a/prototypes/connections/test-splice.c b/prototypes/connections/test-splice.c
new file mode 100644
index 0000000..bf3c0a9
--- /dev/null
+++ b/prototypes/connections/test-splice.c
@@ -0,0 +1,190 @@
+#define _POSIX_C_SOURCE 200809L
+#include "runtime.h"
+#include "store-port.h"
+#include "store.h"
+#include <dirent.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define CHECK(x) do { if (!(x)) { fprintf(stderr, "line %d: %s\n", __LINE__, #x); exit(1); } } while (0)
+static int failed_stage = -1;
+int pure_store_test_fault(PureStoreStage stage) { return (int)stage == failed_stage; }
+
+static void expect(const char *path, const char *key, int64_t revision, PureSpan bytes) {
+ PureStoredView view;
+ CHECK(pure_store_get(path, key, PURE_OBJECT_LIMIT, &view) == PURE_OK);
+ CHECK(view.revision == revision && view.body.size == bytes.size);
+ CHECK(!bytes.size || !memcmp(view.body.data, bytes.data, bytes.size));
+ pure_store_release(&view);
+}
+
+static void parser(void) {
+ const char *get = "on get { let o = store.read(request.key); reply(o, o.body); }";
+ const char *layout = "layout E { marker: bytes[1]; remove: u32le; start: u32le; insert: bytes; }";
+ char source[2048], error[160];
+ PureProgram p;
+ snprintf(source, sizeof(source), "%s %s on put { let e = view(E, request.body); "
+ "let s = store.splice(request.key, request.revision, e.start, e.remove, e.insert); reply(s, e.insert); }", layout, get);
+ CHECK(pure_program_parse(source, strlen(source), &p, error, sizeof(error)));
+ CHECK(p.splice && p.input_required == 9 && p.splice_offset.value == 5 && p.splice_remove.value == 1);
+ CHECK(p.splice_limit == PURE_OBJECT_LIMIT);
+ unsigned char input[] = {0, 2,0,0,0, 3,0,0,0, 0,255};
+ PureSpan write, reply;
+ size_t offset, remove;
+ for (size_t n = 0; n < 9; ++n)
+ CHECK(!pure_program_input(&p, (PureSpan){input, n}, &write, &reply));
+ CHECK(pure_program_input(&p, (PureSpan){input, sizeof(input)}, &write, &reply));
+ CHECK(write.size == 2 && write.data == input + 9 && reply.data == write.data);
+ CHECK(pure_program_splice(&p, (PureSpan){input, sizeof(input)}, &offset, &remove));
+ CHECK(offset == 3 && remove == 2);
+
+ snprintf(source, sizeof(source), "%s on put { let s = store.splice(request.key, request.revision, 0, 0, request.body); reply(s); }", get);
+ CHECK(pure_program_parse(source, strlen(source), &p, error, sizeof(error)));
+ CHECK(pure_program_splice(&p, (PureSpan){0}, &offset, &remove) && offset == 0 && remove == 0);
+ snprintf(source, sizeof(source), "%s on put { let s = store.splice(request.key, request.revision, 0, 0, request.body, 8); reply(s); }", get);
+ CHECK(pure_program_parse(source, strlen(source), &p, error, sizeof(error)) && p.splice_limit == 8);
+ const char *invalid[] = {
+ "let s = store.splice(request.key, request.revision, e.insert, 0, e.insert); reply(s);",
+ "let s = store.splice(request.key, request.revision, 0, 1449985, e.insert); reply(s);",
+ "let s = store.splice(request.key, request.revision, 0, 0, e.start); reply(s);",
+ "let s = store.splice(request.key, request.revision, 0, 0, e.insert); let t = store.write(request.key, request.revision, e.insert); reply(t);",
+ "let s = store.splice(request.key, request.revision, 0, 0, e.insert, e.start); reply(s);",
+ "let s = store.splice(request.key, request.revision, 0, 0, e.insert, 1449985); reply(s);"
+ };
+ for (size_t i = 0; i < sizeof(invalid) / sizeof(*invalid); ++i) {
+ snprintf(source, sizeof(source), "%s %s on put { let e = view(E, request.body); %s }", layout, get, invalid[i]);
+ unsigned char before[sizeof(p)];
+ memcpy(before, &p, sizeof(p));
+ CHECK(!pure_program_parse(source, strlen(source), &p, error, sizeof(error)));
+ CHECK(*error && !memcmp(before, &p, sizeof(p)));
+ }
+}
+
+static void storage(const char *path) {
+ /* Reference whole-value assembly exists only in this test oracle. */
+ unsigned char oracle[65536], before[65536], insert[9000];
+ for (size_t i = 0; i < sizeof(insert); ++i) insert[i] = (unsigned char)(i * 19u);
+ size_t length = 49157;
+ for (size_t i = 0; i < length; ++i) oracle[i] = (unsigned char)((i * 17u) ^ (i >> 8));
+ int64_t revision;
+ CHECK(pure_store_splice(path, "work", 0, 0, 0, (PureSpan){oracle, length}, PURE_OBJECT_LIMIT, &revision) == PURE_OK);
+ CHECK(revision == 1);
+#if defined(PURE_STORE_CFR_COPY) && defined(__linux__)
+ /* A short kernel copy followed by an unsupported result must continue
+ * from exactly the remaining source and destination byte positions. */
+ unsigned char replacement = 'Z';
+ failed_stage = PURE_COPY_RANGE_UNSUPPORTED_AFTER_PARTIAL;
+ CHECK(pure_store_splice(path, "work", revision, 32768, 1,
+ (PureSpan){&replacement, 1}, PURE_OBJECT_LIMIT, &revision) == PURE_OK);
+ failed_stage = -1;
+ oracle[32768] = replacement;
+ expect(path, "work", revision, (PureSpan){oracle, length});
+#endif
+ /* Prefix/suffix copies cross both scratch and mapping page boundaries. */
+ const size_t edits[][3] = {{16381, 7, 9000}, {0, 0, 5}, {50000, 1, 0}, {4095, 4097, 3}, {0, 0, 0}};
+ for (size_t i = 0; i < sizeof(edits) / sizeof(*edits); ++i) {
+ size_t offset = edits[i][0], remove = edits[i][1], count = edits[i][2], previous = length;
+ memcpy(before, oracle, length);
+ PureStoredView old;
+ CHECK(pure_store_get(path, "work", PURE_OBJECT_LIMIT, &old) == PURE_OK);
+ int64_t expected = revision;
+ CHECK(pure_store_splice(path, "work", expected, offset, remove, (PureSpan){insert, count}, PURE_OBJECT_LIMIT, &revision) == PURE_OK);
+ CHECK(revision == expected + 1);
+ memmove(oracle + offset + count, oracle + offset + remove, length - offset - remove);
+ memcpy(oracle + offset, insert, count);
+ length = length - remove + count;
+ expect(path, "work", revision, (PureSpan){oracle, length});
+ CHECK(old.body.size == previous && !memcmp(old.body.data, before, previous));
+ pure_store_release(&old);
+ }
+ int64_t latest = revision;
+ CHECK(pure_store_splice(path, "work", latest - 1, 0, 0, (PureSpan){insert, 1}, PURE_OBJECT_LIMIT, &revision) == PURE_CONFLICT);
+ CHECK(revision == latest);
+ CHECK(pure_store_splice(path, "work", latest, SIZE_MAX, 0, (PureSpan){0}, PURE_OBJECT_LIMIT, &revision) == PURE_LIMIT);
+ CHECK(pure_store_splice(path, "work", latest, 0, SIZE_MAX, (PureSpan){0}, PURE_OBJECT_LIMIT, &revision) == PURE_LIMIT);
+ CHECK(pure_store_splice(path, "work", latest, length + 1, 0, (PureSpan){0}, PURE_OBJECT_LIMIT, &revision) == PURE_LIMIT);
+ /* Rejected before reading the intentionally undersized insertion pointer. */
+ CHECK(pure_store_splice(path, "work", latest, 0, 0, (PureSpan){insert, PURE_OBJECT_LIMIT}, PURE_OBJECT_LIMIT, &revision) == PURE_LIMIT);
+ CHECK(pure_store_splice(path, "work", latest, 0, 0, (PureSpan){insert, 1}, length, &revision) == PURE_LIMIT);
+ CHECK(revision == latest);
+ expect(path, "work", latest, (PureSpan){oracle, length});
+ CHECK(pure_store_splice(path, "missing", 0, 1, 0, (PureSpan){0}, PURE_OBJECT_LIMIT, &revision) == PURE_LIMIT);
+ PureStoredView view;
+ CHECK(pure_store_get(path, "missing", 0, &view) == PURE_NOT_FOUND);
+ for (failed_stage = PURE_AFTER_HEADER; failed_stage <= PURE_BEFORE_RENAME; ++failed_stage) {
+ CHECK(pure_store_splice(path, "work", latest, 1, 3, (PureSpan){insert, 5}, PURE_OBJECT_LIMIT, &revision) == PURE_STORE_ERROR);
+ expect(path, "work", latest, (PureSpan){oracle, length});
+ }
+ failed_stage = PURE_BEFORE_DIR_SYNC;
+ CHECK(pure_store_splice(path, "work", latest, length, 0, (PureSpan){insert, 5}, PURE_OBJECT_LIMIT, &revision) == PURE_UNCERTAIN);
+ CHECK(revision == latest + 1);
+ failed_stage = -1;
+ memcpy(oracle + length, insert, 5); length += 5;
+ expect(path, "work", revision, (PureSpan){oracle, length});
+ CHECK(pure_store_splice(path, "work", revision, 0, length, (PureSpan){0}, PURE_OBJECT_LIMIT, &revision) == PURE_OK);
+ expect(path, "work", revision, (PureSpan){0});
+ char file[256];
+ snprintf(file, sizeof(file), "%s/obj-work", path);
+ int fd = open(file, O_WRONLY);
+ const unsigned char maximum[] = {255,255,255,255,255,255,255,127};
+ CHECK(fd >= 0 && pwrite(fd, maximum, 8, 8) == 8 && close(fd) == 0);
+ CHECK(pure_store_splice(path, "work", INT64_MAX, 0, 0, (PureSpan){0}, PURE_OBJECT_LIMIT, &revision) == PURE_LIMIT);
+}
+
+static void runtime(const char *path) {
+ PureProgram program;
+ char error[160];
+ CHECK(pure_program_load("examples/editor.pure", &program, error, sizeof(error)));
+ PureRuntime runtime;
+ PureWork slots[1];
+ PureHost denied = pure_store_host(path);
+ denied.write = NULL; /* no write effect grants no splice permission */
+ CHECK(pure_runtime_init(&runtime, slots, 1, &program, denied, 32));
+ PureTicket ticket = {0};
+ CHECK(pure_runtime_accept(&runtime, PURE_INPUT_WRITE, "native", 0, 9, &ticket) == PURE_DENIED);
+ CHECK(!ticket.runtime && runtime.active == 0 && runtime.retained == 0);
+ CHECK(pure_runtime_restart(&runtime, &program, pure_store_host(path), 32));
+ CHECK(pure_runtime_accept(&runtime, PURE_INPUT_WRITE, "native", 0, 9, &ticket) == PURE_OK);
+ CHECK(pure_runtime_cancel(ticket) && pure_runtime_release(ticket));
+ CHECK(!pure_runtime_step(&runtime));
+ PureStoredView view;
+ CHECK(pure_store_get(path, "native", 32, &view) == PURE_NOT_FOUND);
+ CHECK(pure_runtime_accept(&runtime, PURE_INPUT_WRITE, "native", 0, 7, &ticket) == PURE_OK);
+ memset(pure_runtime_ingress(ticket, NULL), 0, 7);
+ CHECK(pure_runtime_ready(ticket) && pure_runtime_step(&runtime));
+ PureOutcome result;
+ CHECK(pure_runtime_result(ticket, &result) && result.status == PURE_LIMIT);
+ CHECK(pure_runtime_release(ticket) && runtime.commits == 0);
+ CHECK(pure_runtime_accept(&runtime, PURE_INPUT_WRITE, "native", 0, 9, &ticket) == PURE_OK);
+ unsigned char *input = pure_runtime_ingress(ticket, NULL);
+ memset(input, 0, 8); input[8] = 'x';
+ CHECK(pure_runtime_ready(ticket) && pure_runtime_step(&runtime));
+ CHECK(pure_runtime_result(ticket, &result) && result.status == PURE_OK && result.revision == 1);
+ CHECK(result.body.size == 1 && result.body.data == input + 8 && runtime.retained == 9);
+ expect(path, "native", 1, (PureSpan){(const unsigned char *)"x", 1});
+ CHECK(pure_runtime_release(ticket) && runtime.retained == 0 && runtime.active == 0);
+}
+
+int main(void) {
+ parser();
+ char path[] = "/tmp/pure-splice-check-XXXXXX";
+ CHECK(mkdtemp(path) && pure_store_init(path) == PURE_OK);
+ storage(path);
+ runtime(path);
+ DIR *entries = opendir(path);
+ CHECK(entries);
+ struct dirent *entry;
+ while ((entry = readdir(entries))) {
+ if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue;
+ CHECK(strncmp(entry->d_name, ".pending-", 9));
+ CHECK(unlinkat(dirfd(entries), entry->d_name, 0) == 0);
+ }
+ CHECK(closedir(entries) == 0 && rmdir(path) == 0);
+ puts("{\"splice_tests\":\"passed\",\"checks\":[\"authored-fields-and-atomic-parse-failure\","
+ "\"binary-edit-oracle-and-old-snapshot\",\"bounds-conflict-size-overflow\","
+ "\"publication-failures-and-uncertain-durability\",\"cancel-admission-and-borrowed-reply\"]}");
+ return 0;
+}