diff options
Diffstat (limited to 'prototypes/connections/test-flow.c')
| -rw-r--r-- | prototypes/connections/test-flow.c | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/prototypes/connections/test-flow.c b/prototypes/connections/test-flow.c new file mode 100644 index 0000000..5ff028d --- /dev/null +++ b/prototypes/connections/test-flow.c @@ -0,0 +1,183 @@ +#define _POSIX_C_SOURCE 200809L +#include "store.h" +#include <dirent.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> + +#define CHECK(expr) do { if (!(expr)) { fprintf(stderr, "line %d: %s\n", __LINE__, #expr); exit(1); } } while (0) +static int failed_stage = -1; +static bool crash_at_stage; +int pure_store_test_fault(PureStoreStage stage) { + if ((int)stage != failed_stage) return 0; + if (crash_at_stage) _exit(77); + return 1; +} +typedef struct { unsigned char *bytes; unsigned releases; } Owner; +static void release(void *raw) { + Owner *owner = raw; + free(owner->bytes); + owner->bytes = NULL; + ++owner->releases; +} + +static void transfers(void) { + Owner owner = {.bytes = malloc(5)}; + CHECK(owner.bytes); + memcpy(owner.bytes, "a\0bcd", 5); + PureTransfer transfer = {0}; + pure_transfer_init(&transfer, (PureSpan){(const unsigned char *)"head", 4}, + (PureSpan){owner.bytes, 5}, &owner, release); + CHECK(!pure_transfer_consume(&transfer, 5)); + CHECK(transfer.active && !owner.releases); + CHECK(pure_transfer_consume(&transfer, 0)); + unsigned char received[9]; + size_t used = 0; + while (transfer.active) { + PureSpan span = pure_transfer_next(&transfer, 2); + CHECK(span.size && span.size <= 2); + memcpy(received + used, span.data, span.size); + used += span.size; + CHECK(pure_transfer_consume(&transfer, span.size)); + CHECK((used == 9) == (owner.releases == 1)); + } + CHECK(used == 9 && !memcmp(received, "heada\0bcd", 9)); + pure_transfer_abort(&transfer); + CHECK(owner.releases == 1 && !pure_transfer_consume(&transfer, 0)); + owner.bytes = malloc(3); + CHECK(owner.bytes); + pure_transfer_init(&transfer, (PureSpan){0}, (PureSpan){owner.bytes, 3}, &owner, release); + CHECK(pure_transfer_consume(&transfer, 1)); + pure_transfer_abort(&transfer); + pure_transfer_abort(&transfer); + CHECK(owner.releases == 2 && !transfer.active); + pure_transfer_init(&transfer, (PureSpan){0}, (PureSpan){0}, &owner, release); + CHECK(owner.releases == 3 && !transfer.active); +} + +static void storage(const char *path) { + CHECK(pure_store_init(path) == PURE_OK); + PureStoredView first, second; + CHECK(pure_store_get(path, "missing", 100, &first) == PURE_NOT_FOUND); + CHECK(!first.mapping && !first.mapped_size); + static const unsigned char original[] = {0, 1, 2, 255, 17}; + static const unsigned char updated[] = {9, 8, 7}; + int64_t revision; + CHECK(pure_store_put(path, "work", 0, (PureSpan){original, sizeof(original)}, &revision) == PURE_OK); + CHECK(revision == 1); + CHECK(pure_store_get(path, "work", sizeof(original) - 1, &first) == PURE_BUDGET); + CHECK(!first.mapping && !first.mapped_size); + CHECK(pure_store_get_range(path, "work", 1, 3, 3, &first) == PURE_OK); + CHECK(first.object_size == sizeof(original) && first.body.size == 3 && !memcmp(first.body.data, original + 1, 3)); + pure_store_release(&first); + CHECK(pure_store_get_range(path, "work", SIZE_MAX, 1, 10, &first) == PURE_LIMIT); + CHECK(pure_store_get_range(path, "work", 1, SIZE_MAX, 10, &first) == PURE_LIMIT); + CHECK(pure_store_get_range(path, "work", 1, 3, 2, &first) == PURE_BUDGET); + CHECK(pure_store_get_range(path, "work", sizeof(original), 0, 0, &first) == PURE_OK); + CHECK(!first.mapped_size && !first.body.size); + pure_store_release(&first); + CHECK(pure_store_get(path, "work", sizeof(original), &first) == PURE_OK); + CHECK(first.body.size == sizeof(original) && first.revision == 1); + CHECK(pure_store_put(path, "work", 1, (PureSpan){updated, sizeof(updated)}, &revision) == PURE_OK); + CHECK(revision == 2); + CHECK(!memcmp(first.body.data, original, sizeof(original))); + CHECK(pure_store_get(path, "work", 100, &second) == PURE_OK); + CHECK(second.revision == 2 && second.body.size == sizeof(updated)); + CHECK(!memcmp(second.body.data, updated, sizeof(updated))); + pure_store_release(&first); + pure_store_release(&first); + pure_store_release(&second); + CHECK(pure_store_put(path, "work", 1, (PureSpan){original, sizeof(original)}, &revision) == PURE_CONFLICT); + CHECK(revision == 2); + + for (failed_stage = PURE_AFTER_HEADER; failed_stage <= PURE_BEFORE_RENAME; ++failed_stage) { + CHECK(pure_store_put(path, "work", 2, (PureSpan){original, sizeof(original)}, &revision) == PURE_STORE_ERROR); + CHECK(pure_store_get(path, "work", 100, &first) == PURE_OK); + CHECK(first.revision == 2 && !memcmp(first.body.data, updated, sizeof(updated))); + pure_store_release(&first); + } + failed_stage = PURE_BEFORE_DIR_SYNC; + CHECK(pure_store_put(path, "work", 2, (PureSpan){original, sizeof(original)}, &revision) == PURE_UNCERTAIN); + CHECK(revision == 3); + failed_stage = -1; + CHECK(pure_store_get(path, "work", 100, &first) == PURE_OK); + CHECK(first.revision == 3 && !memcmp(first.body.data, original, sizeof(original))); + pure_store_release(&first); + CHECK(pure_store_put(path, "work", 2, (PureSpan){updated, sizeof(updated)}, &revision) == PURE_CONFLICT); + + /* A crashed unpublished writer leaves only a private file. A new publisher + * must still use the previous committed version, never an incomplete body. */ + pid_t child = fork(); + CHECK(child >= 0); + if (!child) { + failed_stage = PURE_AFTER_HEADER; + crash_at_stage = true; + pure_store_put(path, "work", 3, (PureSpan){updated, sizeof(updated)}, &revision); + _exit(78); + } + int status; + CHECK(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 77); + CHECK(pure_store_init(path) == PURE_OK); + CHECK(pure_store_get(path, "work", 100, &first) == PURE_OK); + CHECK(first.revision == 3 && !memcmp(first.body.data, original, sizeof(original))); + pure_store_release(&first); + CHECK(pure_store_put(path, "work", 3, (PureSpan){updated, sizeof(updated)}, &revision) == PURE_OK); + CHECK(revision == 4); + + char file[320]; + snprintf(file, sizeof(file), "%s/obj-work", path); + int fd = open(file, O_WRONLY); + CHECK(fd >= 0); + const unsigned char maximum[] = {255,255,255,255,255,255,255,127}; + CHECK(pwrite(fd, maximum, 8, 8) == 8 && close(fd) == 0); + CHECK(pure_store_put(path, "work", INT64_MAX, (PureSpan){original, sizeof(original)}, &revision) == PURE_LIMIT); + CHECK(pure_store_put(path, "empty", 0, (PureSpan){0}, &revision) == PURE_OK); + CHECK(pure_store_get(path, "empty", 0, &first) == PURE_OK && first.body.size == 0); + pure_store_release(&first); + + /* Range mapping crosses page boundaries without copying a whole object. */ + long page = sysconf(_SC_PAGESIZE); + CHECK(page >= 128 && (size_t)page <= PURE_OBJECT_LIMIT / 3); + size_t size = (size_t)page * 3, offset = (size_t)page + 13, length = (size_t)page + 105; + unsigned char *bytes = malloc(size); + CHECK(bytes); + for (size_t i = 0; i < size; ++i) bytes[i] = (unsigned char)((i * 17u) ^ (i >> 8) ^ (i >> 16)); + CHECK(pure_store_put(path, "range", 0, (PureSpan){bytes, size}, &revision) == PURE_OK); + CHECK(pure_store_get_range(path, "range", offset, length, length, &first) == PURE_OK); + CHECK(first.body.size == length && !memcmp(first.body.data, bytes + offset, length)); + pure_store_release(&first); + free(bytes); + snprintf(file, sizeof(file), "%s/obj-broken", path); + fd = open(file, O_CREAT | O_WRONLY | O_EXCL, 0600); + CHECK(fd >= 0 && write(fd, "incomplete", 10) == 10 && close(fd) == 0); + CHECK(pure_store_get(path, "broken", 100, &first) == PURE_STORE_ERROR && !first.mapped_size); + CHECK(pure_store_put(path, "broken", 0, (PureSpan){updated, sizeof(updated)}, &revision) == PURE_STORE_ERROR); + CHECK(pure_store_init(file) == PURE_STORE_ERROR); + struct stat info; + CHECK(stat(file, &info) == 0 && info.st_size == 10); +} + +int main(void) { + transfers(); + char directory[] = "/tmp/pure-connection-unit-XXXXXX"; + CHECK(mkdtemp(directory)); + char path[256]; + snprintf(path, sizeof(path), "%s/store", directory); + storage(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(unlinkat(dirfd(entries), entry->d_name, 0) == 0); + } + CHECK(closedir(entries) == 0 && rmdir(path) == 0); + CHECK(rmdir(directory) == 0); + puts("{\"transfer\":\"partial-consumption-and-single-release\"," + "\"storage\":\"mapped-ranges-snapshot-budget-conflict-failure-uncertain-crash-overflow-empty\"}"); + return 0; +} |
