diff options
Diffstat (limited to 'prototypes')
| -rw-r--r-- | prototypes/connections/Makefile | 2 | ||||
| -rw-r--r-- | prototypes/connections/README.md | 2 | ||||
| -rw-r--r-- | prototypes/connections/state.h | 31 | ||||
| -rw-r--r-- | prototypes/connections/store-tree.c | 193 | ||||
| -rw-r--r-- | prototypes/connections/test-tree-store.c | 218 |
5 files changed, 437 insertions, 9 deletions
diff --git a/prototypes/connections/Makefile b/prototypes/connections/Makefile index 661fbcd..a416bee 100644 --- a/prototypes/connections/Makefile +++ b/prototypes/connections/Makefile @@ -11,7 +11,7 @@ HOST = $(STORE) store-port.c evaluate.c # Document browsers list keys through the store. The tree store keeps its own # catalog; per-object file stores share a directory listing. KEYS = $(if $(filter store-tree.c,$(STORE)),,store-keys.c) -HEADERS = runtime.h program.h store.h store-port.h flow.h result.h +HEADERS = runtime.h program.h store.h store-port.h state.h flow.h result.h all: core fixed-editor core: $(BUILD)/libpure-execution.a async-core: $(BUILD)/libpure-execution-async.a diff --git a/prototypes/connections/README.md b/prototypes/connections/README.md index f0b041e..94d05f0 100644 --- a/prototypes/connections/README.md +++ b/prototypes/connections/README.md @@ -148,6 +148,8 @@ python3 prototypes/connections/export_flat_store.py \ 트리로 둔다. 편집은 바뀐 leaf와 경로만 기록하고, 읽기는 잠그지 않는다. 기존 런타임·부분 수정· 비동기·실제 PTY 검사와 자체 고장·오라클 검사를 함께 실행한다. 형식이 달라 별도 `--store` 디렉터리를 쓴다. [설계·측정·한계](../../docs/research/073-single-file-persistent-tree.md). +같은 저장소에서 런타임이 앱의 작업 버전을 소유하는 draft API는 [`state.h`](state.h)이며, +설계와 측정은 [074](../../docs/research/074-runtime-owned-drafts.md)에 있다. ```sh make -C prototypes/connections check-tree diff --git a/prototypes/connections/state.h b/prototypes/connections/state.h new file mode 100644 index 0000000..d1bc0fa --- /dev/null +++ b/prototypes/connections/state.h @@ -0,0 +1,31 @@ +#ifndef PURE_STATE_H +#define PURE_STATE_H +#include "flow.h" +#include "result.h" +#include <stdint.h> + +/* Runtime-owned working versions of stored values (selected tree store). + * A draft starts at the committed version of a key and changes only in + * memory: an edit copies the leaf and path it touches and shares every other + * extent with the stored version. An app therefore keeps no copy of the value + * and never compares a copy with storage to find what it changed. Commit + * appends only the draft's new extents. One owner thread per draft. */ +typedef struct PureDraft PureDraft; + +/* A missing key opens as an empty draft at revision 0; commit creates it. */ +PureResult pure_draft_open(const char *path, const char *key, PureDraft **out); +/* Memory only. Fails without change on a bad range or when memory runs out. */ +PureResult pure_draft_splice(PureDraft *, size_t offset, size_t remove, PureSpan insert); +size_t pure_draft_size(const PureDraft *); +/* The committed revision this draft is based on; 0 if the key was absent. */ +int64_t pure_draft_revision(const PureDraft *); +/* Visit [offset, offset + length) in order as borrowed extents, without + * copying. Returning false stops early. Spans stay valid until the next + * splice, commit or close of this draft. */ +PureResult pure_draft_read(const PureDraft *, size_t offset, size_t length, + bool (*visit)(void *, PureSpan), void *context); +/* Publish if the key is still at the draft's base revision. On success the + * draft continues from the new revision; on any failure it is unchanged. */ +PureResult pure_draft_commit(PureDraft *, int64_t *revision); +void pure_draft_close(PureDraft *); +#endif diff --git a/prototypes/connections/store-tree.c b/prototypes/connections/store-tree.c index 618028a..1c0ad91 100644 --- a/prototypes/connections/store-tree.c +++ b/prototypes/connections/store-tree.c @@ -10,6 +10,7 @@ #define _DARWIN_C_SOURCE #endif #include "store.h" +#include "state.h" #include <errno.h> #include <fcntl.h> #include <inttypes.h> @@ -20,6 +21,7 @@ #include <sys/stat.h> #include <unistd.h> +enum { FREE = 0xffff }; enum { SLOT = 64, DATA = 2 * SLOT, LEAF_MAX = 4096, LEAF_MIN = 1024, FAN_MAX = 32, FAN_MIN = 8, RECORD = 96, KEY = 64, HEIGHT_MAX = 12, BUFFER = 65536 }; @@ -43,14 +45,15 @@ typedef struct { Entry *items; size_t count, capacity; } List; typedef struct { unsigned char *bytes; /* leaf only */ Entry *children; /* branch only */ - size_t size; /* leaf bytes or child count */ - unsigned height; + size_t size; /* leaf bytes, child count, or next free slot + 1 */ + unsigned height; /* FREE for a reclaimed slot */ } Node; typedef struct { const unsigned char *map; uint64_t end; Node *nodes; - size_t count, capacity; + size_t count, capacity, free_head, free_count; /* free_head is slot + 1 */ + size_t bytes; /* payload held by arena nodes, including unreachable ones */ } Tree; typedef struct { uint64_t seq, end, catalog, catalog_bytes, live; } Head; typedef struct { @@ -309,11 +312,23 @@ static bool push(List *list, Entry entry) { return true; } +static size_t node_bytes(const Node *node) { + return node->bytes ? node->size : node->size * sizeof(Entry); +} + static bool add_node(Tree *tree, Node node, uint64_t *index) { + tree->bytes += node_bytes(&node); + if (tree->free_head) { + *index = tree->free_head - 1; + tree->free_head = tree->nodes[*index].size; + --tree->free_count; + tree->nodes[*index] = node; + return true; + } if (tree->count == tree->capacity) { size_t capacity = tree->capacity ? 2 * tree->capacity : 64; Node *nodes = realloc(tree->nodes, capacity * sizeof(*nodes)); - if (!nodes) return false; + if (!nodes) { tree->bytes -= node_bytes(&node); return false; } tree->nodes = nodes; tree->capacity = capacity; } @@ -537,8 +552,9 @@ static uint64_t put_branch(Writer *writer, const Entry *kids, size_t count, unsi } /* Append a subtree's arena nodes, children first, and set entry to the - * stored reference without changing the arena. With relocate, stored nodes - * are copied too; compaction uses this to rewrite reachable nodes. */ + * stored reference. The arena is not changed, so a failed commit leaves a + * draft intact. With relocate, stored nodes are copied too (compaction, or a + * draft whose base file was replaced by compaction). */ static bool write_subtree(const Tree *tree, Writer *writer, Entry *entry, unsigned height, bool relocate) { if (!entry->bytes || (!(entry->ref & MEMORY_BIT) && !relocate)) return true; @@ -918,6 +934,160 @@ PureResult pure_store_keys(const char *path, bool (*visit)(void *, const char *k return result; } +struct PureDraft { + char *path; + char key[KEY + 1]; + Tree tree; /* the base file's mapping plus this draft's node arena */ + dev_t device; /* identity of the base file; compaction replaces it */ + ino_t inode; + Entry root; + int64_t revision; + size_t live; /* arena bytes reachable at the last collection */ +}; + +/* Rebase the draft on the committed version of its key. */ +static PureResult load_draft(PureDraft *draft) { + int directory = open(draft->path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + if (directory < 0) return PURE_STORE_ERROR; + int fd = openat(directory, tree_name, O_RDONLY | O_NOFOLLOW | O_CLOEXEC); + close(directory); + if (fd < 0) return PURE_STORE_ERROR; + Tree tree = {0}; + Head head; + struct stat info; + Entry document; + uint64_t index; + int64_t revision; + bool found; + bool ok = !fstat(fd, &info) && read_head(fd, &head) && map_tree(fd, &head, &tree) && + find(&tree, (Entry){head.catalog, head.catalog_bytes}, draft->key, &index, &found, + &document, &revision); + close(fd); + if (!ok) { release_tree(&tree); return PURE_STORE_ERROR; } + release_tree(&draft->tree); + draft->tree = tree; + draft->device = info.st_dev; + draft->inode = info.st_ino; + draft->root = document; + draft->revision = revision; + draft->live = 0; + return PURE_OK; +} + +PureResult pure_draft_open(const char *path, const char *key, PureDraft **out) { + *out = NULL; + if (!path || !valid_key(key)) return PURE_INVALID; + PureDraft *draft = calloc(1, sizeof(*draft)); + if (!draft || !(draft->path = strdup(path))) { free(draft); return PURE_ALLOCATION; } + memcpy(draft->key, key, strlen(key) + 1); + PureResult result = load_draft(draft); + if (result != PURE_OK) { pure_draft_close(draft); return result; } + *out = draft; + return PURE_OK; +} + +void pure_draft_close(PureDraft *draft) { + if (!draft) return; + release_tree(&draft->tree); + free(draft->path); + free(draft); +} + +size_t pure_draft_size(const PureDraft *draft) { return (size_t)draft->root.bytes; } + +int64_t pure_draft_revision(const PureDraft *draft) { return draft->revision; } + +static size_t mark(const Tree *tree, Entry entry, unsigned char *marks) { + if (!(entry.ref & MEMORY_BIT)) return 0; + size_t at = (size_t)(entry.ref & LOCATION); + if (marks[at]) return 0; + marks[at] = 1; + const Node *node = &tree->nodes[at]; + size_t bytes = node_bytes(node); + if (!node->bytes) + for (size_t i = 0; i < node->size; ++i) bytes += mark(tree, node->children[i], marks); + return bytes; +} + +/* Every edit copies a leaf and path, so reclaim arena nodes the root no + * longer reaches once they outweigh the reachable ones (and 32 KiB). */ +static void collect(PureDraft *draft) { + Tree *tree = &draft->tree; + size_t allowance = draft->live > 32768 ? draft->live : 32768; + if (tree->bytes <= draft->live || tree->bytes - draft->live <= allowance) return; + unsigned char *marks = calloc(tree->count, 1); + if (!marks) return; + draft->live = mark(tree, draft->root, marks); + for (size_t i = 0; i < tree->count; ++i) { + Node *node = &tree->nodes[i]; + if (marks[i] || node->height == FREE) continue; + tree->bytes -= node_bytes(node); + free(node->bytes); + free(node->children); + *node = (Node){.size = tree->free_head, .height = FREE}; + tree->free_head = i + 1; + ++tree->free_count; + } + free(marks); +} + +PureResult pure_draft_splice(PureDraft *draft, size_t offset, size_t remove, PureSpan insert) { + uint64_t size = draft->root.bytes; + if (insert.size && !insert.data) return PURE_INVALID; + if (offset > size || remove > size - offset || insert.size > PURE_OBJECT_LIMIT - (size - remove)) + return PURE_LIMIT; + if (!remove && !insert.size) return PURE_OK; + Entry next; + bool ok = rebuild(&draft->tree, draft->root, offset, remove, insert, &next); + if (ok) draft->root = next; + collect(draft); + return ok ? PURE_OK : PURE_STORE_ERROR; +} + +PureResult pure_draft_read(const PureDraft *draft, size_t offset, size_t length, + bool (*visit)(void *, PureSpan), void *context) { + unsigned height; + if (offset > draft->root.bytes || length > draft->root.bytes - offset) return PURE_LIMIT; + if (!length) return PURE_OK; + if (!root_height(&draft->tree, draft->root, &height)) return PURE_STORE_ERROR; + return visit_range(&draft->tree, draft->root, height, offset, length, visit, context) ? + PURE_OK : PURE_STORE_ERROR; +} + +PureResult pure_draft_commit(PureDraft *draft, int64_t *revision) { + *revision = draft->revision; + Session session; + Writer *writer = NULL; + Entry current, root = draft->root; + uint64_t index; + int64_t stored; + bool found; + PureResult result = open_session(draft->path, &session); + if (result != PURE_OK) goto finish; + result = PURE_STORE_ERROR; + if (!find(&session.tree, (Entry){session.head.catalog, session.head.catalog_bytes}, draft->key, + &index, &found, ¤t, &stored)) goto finish; + if (stored != draft->revision) { *revision = stored; result = PURE_CONFLICT; goto finish; } + if (stored == INT64_MAX) { result = PURE_LIMIT; goto finish; } + /* After compaction the draft's stored references name the old file. */ + bool relocate = session.file.st_dev != draft->device || session.file.st_ino != draft->inode; + writer = malloc(sizeof(*writer)); + if (!writer) goto finish; + *writer = (Writer){.fd = session.fd, .at = session.head.end}; + if (!write_root(&draft->tree, writer, &root, relocate)) { + if (ftruncate(session.fd, (off_t)session.head.end) == 0) sync_fd(session.fd); + goto finish; + } + result = publish(&session, writer, draft->key, index, found, root, revision); + /* Rebase while still holding the writer lock; the arena is no longer needed. + * If remapping fails the draft keeps equal content at the new revision. */ + if (result == PURE_OK && load_draft(draft) != PURE_OK) draft->revision = *revision; +finish: + free(writer); + close_session(&session); + return result; +} + PureResult pure_store_put(const char *path, const char *key, int64_t expected, PureSpan body, int64_t *revision) { return mutate(path, key, expected, body, false, 0, 0, PURE_OBJECT_LIMIT, revision); @@ -946,6 +1116,17 @@ static bool shape(const Tree *tree, Entry entry, unsigned height, bool root, return true; } +/* Test-only: live arena nodes and their payload bytes. */ +void pure_draft_test_arena(const PureDraft *draft, size_t *nodes, size_t *bytes) { + *nodes = *bytes = 0; + for (size_t i = 0; i < draft->tree.count; ++i) { + const Node *node = &draft->tree.nodes[i]; + if (node->height == FREE) continue; + ++*nodes; + *bytes += node->bytes ? node->size : node->size * sizeof(Entry); + } +} + /* Test-only: the stored height, leaf count and number of underfull nodes. */ bool pure_tree_test_shape(const char *path, const char *key, unsigned *height, uint64_t *leaves, uint64_t *small, uint64_t *file_end) { diff --git a/prototypes/connections/test-tree-store.c b/prototypes/connections/test-tree-store.c index 3a1e8f0..8cf1f1e 100644 --- a/prototypes/connections/test-tree-store.c +++ b/prototypes/connections/test-tree-store.c @@ -1,5 +1,6 @@ #define _POSIX_C_SOURCE 200809L #include "store.h" +#include "state.h" #include <assert.h> #include <dirent.h> #include <fcntl.h> @@ -22,6 +23,8 @@ int pure_store_test_fault(PureStoreStage stage) { bool pure_tree_test_shape(const char *path, const char *key, unsigned *height, uint64_t *leaves, uint64_t *small, uint64_t *file_end); +void pure_draft_test_arena(const PureDraft *draft, size_t *nodes, size_t *bytes); + enum { KEYS = 4, CAPACITY = 1 << 20 }; typedef struct { unsigned char *bytes; size_t size; int64_t revision; } Oracle; @@ -366,6 +369,215 @@ static void keys(const char *path) { CHECK(pure_store_keys(path, count_key, &count) == PURE_OK && count == 5); } +typedef struct { unsigned char *out; size_t spans, largest, limit; } Gather; + +static bool gather(void *context, PureSpan span) { + Gather *g = context; + memcpy(g->out, span.data, span.size); + g->out += span.size; + if (span.size > g->largest) g->largest = span.size; + return ++g->spans != g->limit; +} + +static void expect_draft(const PureDraft *draft, const Oracle *o, unsigned char *scratch) { + Gather g = {scratch, 0, 0, 0}; + CHECK(pure_draft_size(draft) == o->size && pure_draft_revision(draft) == o->revision); + CHECK(pure_draft_read(draft, 0, o->size, gather, &g) == PURE_OK); + CHECK((size_t)(g.out - scratch) == o->size && (!o->size || !memcmp(scratch, o->bytes, o->size))); + CHECK(g.largest <= 4096); +} + +static void apply(Oracle *o, size_t offset, size_t remove, const unsigned char *insert, size_t count) { + memmove(o->bytes + offset + count, o->bytes + offset + remove, o->size - offset - remove); + if (count) memcpy(o->bytes + offset, insert, count); + o->size = o->size - remove + count; +} + +/* Edits live only in the draft until commit; each commit continues from the + * published version with an empty arena. */ +static void drafts(const char *path) { + Oracle o = {malloc(CAPACITY), 0, 0}; + unsigned char *scratch = malloc(CAPACITY), *insert = malloc(65536); + CHECK(o.bytes && scratch && insert); + PureDraft *draft; + PureStoredView view; + CHECK(pure_draft_open(path, "draft", &draft) == PURE_OK); + expect_draft(draft, &o, scratch); + uint32_t state = 0x1234567u; + for (unsigned step = 0; step < 20000; ++step) { + uint32_t kind = random_value(&state) % 100; + size_t count = kind < 80 ? random_value(&state) % 8 : + kind < 98 ? random_value(&state) % 4000 : random_value(&state) % 65536; + size_t offset = o.size ? random_value(&state) % (o.size + 1) : 0; + size_t remove = o.size - offset ? random_value(&state) % (o.size - offset + 1) : 0; + if (random_value(&state) % 64) remove %= 16; + if (o.size - remove + count > CAPACITY) count = 0; + fill(insert, count, &state); + CHECK(pure_draft_splice(draft, offset, remove, (PureSpan){insert, count}) == PURE_OK); + apply(&o, offset, remove, insert, count); + if (step % 97 == 0) expect_draft(draft, &o, scratch); + if (step % 1000 == 999) { + if (o.revision) CHECK(pure_store_get(path, "draft", PURE_OBJECT_LIMIT, &view) == PURE_OK && + view.revision == o.revision); + if (o.revision) pure_store_release(&view); + int64_t revision; + CHECK(pure_draft_commit(draft, &revision) == PURE_OK && revision == o.revision + 1); + o.revision = revision; + expect(path, "draft", &o); + expect_draft(draft, &o, scratch); + size_t nodes, bytes; + pure_draft_test_arena(draft, &nodes, &bytes); + CHECK(nodes == 0); + } + } + CHECK(o.size > 100000); + Gather g = {scratch, 0, 0, 3}; + CHECK(pure_draft_read(draft, 0, o.size, gather, &g) == PURE_OK && g.spans == 3); + CHECK(pure_draft_splice(draft, o.size + 1, 0, (PureSpan){0}) == PURE_LIMIT); + CHECK(pure_draft_splice(draft, 0, o.size + 1, (PureSpan){0}) == PURE_LIMIT); + CHECK(pure_draft_read(draft, o.size, 1, gather, &g) == PURE_LIMIT); + expect_draft(draft, &o, scratch); + pure_draft_close(draft); + printf("{\"draft_steps\":20000,\"draft_commits\":%lld,\"draft_bytes\":%zu}\n", + (long long)o.revision, o.size); + free(o.bytes); + free(scratch); + free(insert); +} + +/* Long typing and deleting at one place keeps only the live path in memory, + * and its commit appends the typed bytes and path, not the document. */ +static void typing(const char *path) { + size_t size = 1048576; + Oracle o = {malloc(size + 40000), size, 0}; + unsigned char *scratch = malloc(size + 40000); + uint32_t state = 5; + CHECK(o.bytes && scratch); + fill(o.bytes, size, &state); + CHECK(pure_store_put(path, "typed", 0, (PureSpan){o.bytes, size}, &o.revision) == PURE_OK); + PureDraft *draft; + CHECK(pure_draft_open(path, "typed", &draft) == PURE_OK); + size_t cursor = size / 2, nodes, bytes, peak = 0; + for (unsigned i = 0; i < 30000; ++i) { + unsigned char byte = (unsigned char)('a' + i % 26); + if (i % 3 == 2) { + CHECK(pure_draft_splice(draft, cursor - 1, 1, (PureSpan){0}) == PURE_OK); + apply(&o, --cursor, 1, NULL, 0); + } else { + CHECK(pure_draft_splice(draft, cursor, 0, (PureSpan){&byte, 1}) == PURE_OK); + apply(&o, cursor++, 0, &byte, 1); + } + pure_draft_test_arena(draft, &nodes, &bytes); + if (bytes > peak) peak = bytes; + } + expect_draft(draft, &o, scratch); + /* Reachable: 10,000 net typed bytes, the leaves they landed in and one + * path. Unreachable copies are reclaimed past max(reachable, 32 KiB). */ + CHECK(peak < 2 * (10000 + 4 * 4096 + 2048) + 32768 + 8192); + uint64_t before = file_size(path); + int64_t revision; + CHECK(pure_draft_commit(draft, &revision) == PURE_OK); + o.revision = revision; + CHECK(file_size(path) - before < 10000 + 16384); + expect(path, "typed", &o); + pure_draft_close(draft); + printf("{\"typing_keystrokes\":30000,\"peak_arena_bytes\":%zu,\"commit_appended_bytes\":%llu}\n", + peak, (unsigned long long)(file_size(path) - before)); + free(o.bytes); + free(scratch); +} + +/* Two drafts of one key: the second commit conflicts and keeps its edits. */ +static void draft_conflict(const char *path) { + unsigned char scratch[64]; + int64_t revision; + CHECK(pure_store_put(path, "shared", 0, (PureSpan){(const unsigned char *)"base", 4}, &revision) == PURE_OK); + PureDraft *first, *second; + CHECK(pure_draft_open(path, "shared", &first) == PURE_OK && pure_draft_open(path, "shared", &second) == PURE_OK); + CHECK(pure_draft_splice(first, 4, 0, (PureSpan){(const unsigned char *)"-one", 4}) == PURE_OK); + CHECK(pure_draft_splice(second, 0, 0, (PureSpan){(const unsigned char *)"two-", 4}) == PURE_OK); + CHECK(pure_draft_commit(first, &revision) == PURE_OK && revision == 2); + CHECK(pure_draft_commit(second, &revision) == PURE_CONFLICT && revision == 2); + Oracle kept = {(unsigned char *)"two-base", 8, 1}; + expect_draft(second, &kept, scratch); + Oracle stored = {(unsigned char *)"base-one", 8, 2}; + expect(path, "shared", &stored); + pure_draft_close(second); + CHECK(pure_draft_open(path, "shared", &second) == PURE_OK); + expect_draft(second, &stored, scratch); + CHECK(pure_store_put(path, "shared", 2, (PureSpan){(const unsigned char *)"x", 1}, &revision) == PURE_OK); + CHECK(pure_draft_commit(first, &revision) == PURE_CONFLICT && revision == 3); + pure_draft_close(first); + pure_draft_close(second); +} + +/* Another key's writes compact the file under an open draft; the draft's + * stored extents are copied from its old mapping on commit. */ +static void draft_relocation(const char *path) { + unsigned char *big = malloc(300000), scratch[20000]; + uint32_t state = 11; + CHECK(big); + fill(big, 300000, &state); + Oracle o = {malloc(20000), 12000, 0}; + CHECK(o.bytes); + fill(o.bytes, o.size, &state); + CHECK(pure_store_put(path, "kept", 0, (PureSpan){o.bytes, o.size}, &o.revision) == PURE_OK); + PureDraft *draft; + CHECK(pure_draft_open(path, "kept", &draft) == PURE_OK); + CHECK(pure_draft_splice(draft, 6000, 10, (PureSpan){(const unsigned char *)"relocated", 9}) == PURE_OK); + apply(&o, 6000, 10, (const unsigned char *)"relocated", 9); + char name[512]; + snprintf(name, sizeof(name), "%s/pure.tree", path); + struct stat before, after; + CHECK(stat(name, &before) == 0); + int64_t revision = 0; + for (unsigned i = 0; i < 8; ++i) + CHECK(pure_store_put(path, "other", revision, (PureSpan){big, 300000 - i}, &revision) == PURE_OK); + CHECK(stat(name, &after) == 0 && after.st_ino != before.st_ino); + CHECK(pure_draft_commit(draft, &revision) == PURE_OK && revision == o.revision + 1); + o.revision = revision; + expect(path, "kept", &o); + expect_draft(draft, &o, scratch); + Oracle other = {big, 300000 - 7, 8}; + expect(path, "other", &other); + pure_draft_close(draft); + free(o.bytes); + free(big); +} + +/* A failed commit changes neither the store nor the draft. */ +static void draft_faults(const char *path) { + unsigned char scratch[64]; + int64_t revision; + CHECK(pure_store_put(path, "faulty", 0, (PureSpan){(const unsigned char *)"stable", 6}, &revision) == PURE_OK); + PureDraft *draft; + CHECK(pure_draft_open(path, "faulty", &draft) == PURE_OK); + CHECK(pure_draft_splice(draft, 6, 0, (PureSpan){(const unsigned char *)"+edit", 5}) == PURE_OK); + Oracle stored = {(unsigned char *)"stable", 6, 1}, edited = {(unsigned char *)"stable+edit", 11, 1}; + uint64_t size = file_size(path); + for (fail_stage = PURE_AFTER_HEADER; fail_stage <= PURE_BEFORE_RENAME; ++fail_stage) { + CHECK(pure_draft_commit(draft, &revision) == PURE_STORE_ERROR); + expect(path, "faulty", &stored); + expect_draft(draft, &edited, scratch); + CHECK(file_size(path) == size); + } + fail_stage = PURE_BEFORE_DIR_SYNC; + CHECK(pure_draft_commit(draft, &revision) == PURE_UNCERTAIN && revision == 2); + fail_stage = -1; + expect_draft(draft, &edited, scratch); + CHECK(pure_draft_commit(draft, &revision) == PURE_CONFLICT && revision == 2); + pure_draft_close(draft); + CHECK(pure_draft_open(path, "faulty", &draft) == PURE_OK); + edited.revision = 2; + expect_draft(draft, &edited, scratch); + CHECK(pure_draft_splice(draft, 0, 6, (PureSpan){0}) == PURE_OK); + CHECK(pure_draft_commit(draft, &revision) == PURE_OK && revision == 3); + Oracle final = {(unsigned char *)"+edit", 5, 3}; + expect(path, "faulty", &final); + pure_draft_close(draft); + CHECK(pure_draft_open(path, "bad/key", &draft) == PURE_INVALID && !draft); +} + static void clean(const char *path) { DIR *entries = opendir(path); CHECK(entries); @@ -380,7 +592,8 @@ static void clean(const char *path) { int main(void) { void (*checks[])(const char *) = {oracle, amplification, faults, crashes, torn_head, - isolation, corruption, many_keys, revision_limit, keys}; + isolation, corruption, many_keys, revision_limit, keys, + drafts, typing, draft_conflict, draft_relocation, draft_faults}; for (size_t i = 0; i < sizeof(checks) / sizeof(*checks); ++i) { char path[] = "/tmp/pure-tree-check-XXXXXX"; CHECK(mkdtemp(path) && pure_store_init(path) == PURE_OK); @@ -389,6 +602,7 @@ int main(void) { clean(path); } puts("{\"tree_store\":\"random-oracle-balance-held-views-one-byte-amplification-faults-" - "process-crash-torn-head-lockless-isolation-compaction-corruption-1024-keys-revision-limit-key-listing\"}"); + "process-crash-torn-head-lockless-isolation-compaction-corruption-1024-keys-revision-limit-key-listing-" + "draft-oracle-typing-arena-conflict-relocation-commit-faults\"}"); return 0; } |
