aboutsummaryrefslogtreecommitdiff
path: root/test/core
diff options
context:
space:
mode:
Diffstat (limited to 'test/core')
-rw-r--r--test/core/.gitignore10
-rw-r--r--test/core/Makefile66
-rw-r--r--test/core/README.md9
-rw-r--r--test/core/array-test.c213
-rw-r--r--test/core/buffer-test.c90
-rw-r--r--test/core/ccan-config.c571
l---------test/core/ccan/compiler/LICENSE1
-rw-r--r--test/core/ccan/compiler/_info64
-rw-r--r--test/core/ccan/compiler/compiler.h231
-rw-r--r--test/core/ccan/compiler/test/compile_fail-printf.c22
-rw-r--r--test/core/ccan/compiler/test/run-is_compile_constant.c15
-rw-r--r--test/core/ccan/tap/_info61
-rw-r--r--test/core/ccan/tap/tap.3362
-rw-r--r--test/core/ccan/tap/tap.c459
-rw-r--r--test/core/ccan/tap/tap.h251
-rw-r--r--test/core/ccan/tap/test/run.c133
-rw-r--r--test/core/licenses/CC028
-rw-r--r--test/core/map-test.c115
-rw-r--r--test/core/tap.h58
-rw-r--r--test/core/text-test.c403
20 files changed, 3162 insertions, 0 deletions
diff --git a/test/core/.gitignore b/test/core/.gitignore
new file mode 100644
index 0000000..b5fdb76
--- /dev/null
+++ b/test/core/.gitignore
@@ -0,0 +1,10 @@
+/config.h
+/text-test
+/buffer-test
+/map-test
+/array-test
+/ccan-config
+*.gcda
+*.gcno
+*.gcov
+*.valgrind
diff --git a/test/core/Makefile b/test/core/Makefile
new file mode 100644
index 0000000..19991de
--- /dev/null
+++ b/test/core/Makefile
@@ -0,0 +1,66 @@
+-include ../../config.mk
+
+ALL = buffer-test map-test array-test text-test
+SRC = $(wildcard ccan/*/*.c)
+CFLAGS += -I. -I../.. -DBUFFER_SIZE=4 -DBLOCK_SIZE=4
+
+test: $(ALL)
+ @./buffer-test
+ @./map-test
+ @./array-test
+ @./text-test
+
+config.h:
+ @echo Generating ccan configuration header
+ @${CC} ccan-config.c -o ccan-config && ./ccan-config "${CC}" ${CFLAGS} > config.h
+
+text-test: config.h text-test.c ../../text.c ../../text-common.c ../../text-io.c ../../text-iterator.c ../../text-util.c ../../text-motions.c ../../text-objects.c ../../text-regex.c ../../array.c
+ @echo Compiling $@ binary
+ @${CC} ${CFLAGS} ${CFLAGS_STD} ${CFLAGS_LIBC} ${CFLAGS_EXTRA} ${filter %.c, $^} ${SRC} ${LDFLAGS} -o $@
+
+buffer-test: config.h buffer-test.c ../../buffer.c
+ @echo Compiling $@ binary
+ @${CC} ${CFLAGS} ${CFLAGS_STD} ${CFLAGS_LIBC} ${CFLAGS_EXTRA} ${filter %.c, $^} ${SRC} ${LDFLAGS} -o $@
+
+map-test: config.h map-test.c ../../map.c
+ @echo Compiling $@ binary
+ @${CC} ${CFLAGS} ${CFLAGS_STD} ${CFLAGS_LIBC} ${CFLAGS_EXTRA} ${filter %.c, $^} ${SRC} ${LDFLAGS} -o $@
+
+array-test: config.h array-test.c ../../array.c
+ @echo Compiling $@ binary
+ @${CC} ${CFLAGS} ${CFLAGS_STD} ${CFLAGS_LIBC} ${CFLAGS_EXTRA} ${filter %.c, $^} ${SRC} ${LDFLAGS} -o $@
+
+debug: clean
+ $(MAKE) CFLAGS_EXTRA='${CFLAGS_EXTRA} ${CFLAGS_DEBUG}'
+
+coverage: clean
+ $(MAKE) CFLAGS_EXTRA='--coverage'
+
+asan: clean
+ $(MAKE) CFLAGS_EXTRA='-fsanitize=address'
+
+ubsan: clean
+ $(MAKE) CFLAGS_EXTRA='-fsanitize=undefined'
+
+msan: clean
+ $(MAKE) CFLAGS_EXTRA='-fsanitize=memory -fsanitize-memory-track-origins'
+
+valgrind: clean ${ALL}
+ @for test in ${ALL}; do \
+ valgrind --leak-check=full --log-file="$$test.valgrind" "./$$test"; \
+ cat "$$test.valgrind"; \
+ grep LEAK "$$test.valgrind" >/dev/null && exit 1 || true; \
+ done
+
+tis: clean
+ $(MAKE) CC="tis-interpreter.sh --cc" CFLAGS='"${CFLAGS} ${CFLAGS_STD} -DHAVE_MEMRCHR=0 -DTIS_INTERPRETER=1"' CFLAGS_STD='' CFLAGS_LIBC='' LDFLAGS='#' $(ALL)
+
+clean:
+ @echo cleaning
+ @rm -f ccan-config config.h
+ @rm -f data symlink hardlink
+ @rm -f $(ALL)
+ @rm -f *.gcov *.gcda *.gcno
+ @rm -f *.valgrind
+
+.PHONY: clean debug coverage tis valgrind asan ubsan msan
diff --git a/test/core/README.md b/test/core/README.md
new file mode 100644
index 0000000..849d7f7
--- /dev/null
+++ b/test/core/README.md
@@ -0,0 +1,9 @@
+Unit tests for the low level routines used by vis
+-------------------------------------------------
+
+The testing infrastruce makes use of the tap module from the
+[C Code Archive](http://ccodearchive.net).
+
+To run the tests, execute `make`.
+
+ $ make
diff --git a/test/core/array-test.c b/test/core/array-test.c
new file mode 100644
index 0000000..356e75d
--- /dev/null
+++ b/test/core/array-test.c
@@ -0,0 +1,213 @@
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include "tap.h"
+#include "array.h"
+#include "util.h"
+
+typedef struct {
+ char key[64];
+ int value;
+} Item;
+
+static int values[] = { 2, 3, 5, 7, 11 };
+static const size_t len = LENGTH(values);
+
+static bool item_compare(Item *a, Item *b) {
+ return strcmp(a->key, b->key) == 0 && a->value == b->value;
+}
+
+static void test_small_objects(void) {
+ Array arr;
+ array_init_sized(&arr, sizeof(int));
+ ok(array_length(&arr) == 0, "Initialization");
+ ok(!array_set(&arr, 0, NULL) && errno == EINVAL, "Set with invalid index");
+ ok(array_get(&arr, 0) == NULL && errno == EINVAL, "Get with invalid index");
+ ok(array_peek(&arr) == NULL && array_length(&arr) == 0, "Peek empty array");
+ ok(array_pop(&arr) == NULL && array_length(&arr) == 0, "Pop empty array");
+
+ for (size_t i = 0; i < len; i++) {
+ int *v;
+ ok(array_add(&arr, &values[i]) && array_length(&arr) == i+1,
+ "Add integer: %zu = %d", i, values[i]);
+ ok((v = array_get(&arr, i)) && *v == values[i],
+ "Get integer: %zu = %d", i, *v);
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ ok(array_set(&arr, i, &values[len-i-1]) && array_length(&arr) == len,
+ "Set array element: %zu = %d", i, values[len-i-1]);
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ int *v;
+ ok((v = array_get(&arr, i)) && *v == values[len-i-1],
+ "Get array element: %zu = %d", i, *v);
+ }
+
+ int *v;
+ ok((v = array_peek(&arr)) && *v == values[0] && array_length(&arr) == len, "Peek populated array");
+ ok((v = array_pop(&arr)) && *v == values[0] && array_length(&arr) == len-1, "Pop populated array");
+ ok((v = array_peek(&arr)) && *v == values[1] && array_length(&arr) == len-1, "Peek after pop");
+
+ array_clear(&arr);
+ ok(array_length(&arr) == 0 && array_get(&arr, 0) == NULL && errno == EINVAL, "Clear");
+
+ for (size_t i = 0; i < len; i++) {
+ ok(array_add(&arr, &values[i]) && array_length(&arr) == i+1,
+ "Re-add integer: %zu = %d", i, values[i]);
+ }
+
+ int old, *tmp;
+ ok((tmp = array_get(&arr, 0)) && (old = *tmp) && array_set(&arr, 0, NULL) &&
+ array_get(&arr, 0) == tmp && *tmp == 0 && array_set(&arr, 0, &old) &&
+ array_get(&arr, 0) == tmp && *tmp == old, "Set array element NULL");
+ ok(!array_set(&arr, array_length(&arr), &values[0]) && errno == EINVAL, "Get past end of array");
+ ok(!array_get(&arr, array_length(&arr)) && errno == EINVAL, "Get past end of array");
+
+ ok(!array_remove(&arr, array_length(&arr)) && errno == EINVAL, "Remove past end of array");
+
+ size_t len_before = array_length(&arr);
+ ok(array_remove(&arr, 2) && array_length(&arr) == len_before-1 &&
+ (v = array_get(&arr, 0)) && *v == values[0] &&
+ (v = array_get(&arr, 1)) && *v == values[1] &&
+ (v = array_get(&arr, 2)) && *v == values[3] &&
+ (v = array_get(&arr, 3)) && *v == values[4],
+ "Remove element 2");
+
+ len_before = array_length(&arr);
+ ok(array_remove(&arr, 0) && array_length(&arr) == len_before-1 &&
+ (v = array_get(&arr, 0)) && *v == values[1] &&
+ (v = array_get(&arr, 1)) && *v == values[3] &&
+ (v = array_get(&arr, 2)) && *v == values[4],
+ "Remove first element");
+
+ len_before = array_length(&arr);
+ ok(array_remove(&arr, len_before-1) && array_length(&arr) == len_before-1 &&
+ (v = array_get(&arr, 0)) && *v == values[1] &&
+ (v = array_get(&arr, 1)) && *v == values[3],
+ "Remove last element");
+
+ array_release(&arr);
+}
+
+static void test_large_objects(void) {
+ Array arr;
+ array_init_sized(&arr, sizeof(Item));
+ ok(array_length(&arr) == 0 && array_get(&arr, 0) == NULL && errno == EINVAL,
+ "Initialization");
+
+ Item items[len];
+
+ for (size_t i = 0; i < len; i++) {
+ snprintf(items[i].key, sizeof items[i].key, "key: %zu", i);
+ items[i].value = values[i];
+ Item *item;
+ ok(array_add(&arr, &items[i]) && array_length(&arr) == i+1,
+ "Add item: %zu = { '%s' = %d }", i, items[i].key, items[i].value);
+ ok((item = array_get(&arr, i)) && item != &items[i] && item_compare(item, &items[i]),
+ "Get item: %zu = { '%s' = %d }", i, item->key, item->value);
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ Item *item = &items[len-i-1];
+ ok(array_set(&arr, i, item) && array_length(&arr) == len,
+ "Set array element: %zu = { '%s' = %d }", i, item->key, item->value);
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ Item *item;
+ ok((item = array_get(&arr, i)) && item != &items[len-i-1] && item_compare(item, &items[len-i-1]),
+ "Get item: %zu = { '%s' = %d }", i, item->key, item->value);
+ }
+
+ ok(!array_add_ptr(&arr, &items[0]) && errno == ENOTSUP && array_length(&arr) == len,
+ "Adding pointer to non pointer array");
+ ok(!array_set_ptr(&arr, 0, &items[0]) && errno == ENOTSUP && item_compare(array_get(&arr, 0), &items[len-1]),
+ "Setting pointer in non pointer array");
+
+ array_clear(&arr);
+ ok(array_length(&arr) == 0 && array_get(&arr, 0) == NULL && errno == EINVAL, "Clear");
+
+ array_release(&arr);
+}
+
+static void test_pointers(void) {
+
+ Array arr;
+
+ array_init_sized(&arr, 1);
+ ok(array_length(&arr) == 0 && array_get_ptr(&arr, 0) == NULL && errno == ENOTSUP,
+ "Initialization with size 1");
+
+ ok(!array_add_ptr(&arr, &arr) && errno == ENOTSUP && array_get_ptr(&arr, 0) == NULL,
+ "Add pointer to non-pointer array");
+
+ errno = 0;
+ char byte = '_', *ptr;
+ ok(array_add(&arr, &byte) && (ptr = array_get(&arr, 0)) && *ptr == byte,
+ "Add byte element");
+ ok(!array_get_ptr(&arr, 0) && errno == ENOTSUP, "Get pointer from non-pointer array");
+ array_release(&arr);
+
+ array_init(&arr);
+ ok(array_length(&arr) == 0 && array_get_ptr(&arr, 0) == NULL && errno == EINVAL,
+ "Initialization");
+
+ Item *items[len];
+
+ for (size_t i = 0; i < len; i++) {
+ items[i] = malloc(sizeof(Item));
+ snprintf(items[i]->key, sizeof(items[i]->key), "key: %zu", i);
+ items[i]->value = values[i];
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ Item *item;
+ ok(array_add_ptr(&arr, items[i]) && array_length(&arr) == i+1,
+ "Add item: %zu = %p", i, (void*)items[i]);
+ ok((item = array_get_ptr(&arr, i)) && item == items[i],
+ "Get item: %zu = %p", i, (void*)item);
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ Item *item = items[len-i-1];
+ ok(array_set_ptr(&arr, i, item) && array_length(&arr) == len,
+ "Set item: %zu = %p", i, (void*)item);
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ Item *item;
+ ok((item = array_get_ptr(&arr, i)) && item == items[len-i-1],
+ "Get item: %zu = %p", i, (void*)item);
+ }
+
+ Item *tmp;
+ ok((tmp = array_get_ptr(&arr, 0)) && array_set_ptr(&arr, 0, NULL) &&
+ array_get_ptr(&arr, 0) == NULL && array_set_ptr(&arr, 0, tmp) &&
+ array_get_ptr(&arr, 0) == tmp, "Set pointer NULL");
+ ok(!array_set_ptr(&arr, array_length(&arr), items[0]) && errno == EINVAL, "Set pointer past end of array");
+ ok(!array_get_ptr(&arr, array_length(&arr)) && errno == EINVAL, "Get pointer past end of array");
+
+ array_clear(&arr);
+ ok(array_length(&arr) == 0 && array_get_ptr(&arr, 0) == NULL && errno == EINVAL, "Clear");
+
+ for (size_t i = 0; i < len; i++) {
+ ok(array_add_ptr(&arr, items[i]) && array_length(&arr) == i+1,
+ "Re-add item: %zu = %p", i, (void*)items[i]);
+ }
+ array_release_full(&arr);
+}
+
+int main(int argc, char *argv[]) {
+ plan_no_plan();
+
+ test_small_objects();
+ test_large_objects();
+ test_pointers();
+
+ return exit_status();
+}
diff --git a/test/core/buffer-test.c b/test/core/buffer-test.c
new file mode 100644
index 0000000..dfdb466
--- /dev/null
+++ b/test/core/buffer-test.c
@@ -0,0 +1,90 @@
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "tap.h"
+#include "buffer.h"
+
+static bool compare(Buffer *buf, const char *data, size_t len) {
+ return buf->len == len && (len == 0 || memcmp(buf->data, data, buf->len) == 0);
+}
+
+static bool compare0(Buffer *buf, const char *data) {
+ return buf->len == strlen(data)+1 && memcmp(buf->data, data, buf->len) == 0;
+}
+
+int main(int argc, char *argv[]) {
+ Buffer buf;
+
+ plan_no_plan();
+
+ buffer_init(&buf);
+ ok(buffer_content(&buf) == NULL && buffer_length(&buf) == 0 && buffer_capacity(&buf) == 0, "Initialization");
+ ok(buffer_insert(&buf, 0, "foo", 0) && buffer_content(&buf) == NULL &&
+ buffer_length(&buf) == 0 && buffer_capacity(&buf) == 0, "Insert zero length data");
+ ok(!buffer_insert0(&buf, 1, "foo"), "Insert string at invalid position");
+
+ ok(buffer_insert0(&buf, 0, "") && compare0(&buf, ""), "Insert empty string");
+ ok(buffer_insert0(&buf, 0, "foo") && compare0(&buf, "foo"), "Insert string at start");
+ ok(buffer_insert0(&buf, 1, "l") && compare0(&buf, "floo"), "Insert string in middle");
+ ok(buffer_insert0(&buf, 4, "r") && compare0(&buf, "floor"), "Insert string at end");
+
+ ok(buffer_put0(&buf, "") && compare0(&buf, ""), "Put empty string");
+ ok(buffer_put0(&buf, "bar") && compare0(&buf, "bar"), "Put string");
+
+ ok(buffer_prepend0(&buf, "foo") && compare0(&buf, "foobar"), "Prepend string");
+ ok(buffer_append0(&buf, "baz") && compare0(&buf, "foobarbaz"), "Append string");
+
+ buffer_release(&buf);
+ ok(buf.data == NULL && buffer_length(&buf) == 0 && buffer_capacity(&buf) == 0, "Release");
+
+ ok(buffer_insert(&buf, 0, "foo", 0) && compare(&buf, "", 0), "Insert zero length data");
+ ok(buffer_insert(&buf, 0, "foo", 3) && compare(&buf, "foo", 3), "Insert data at start");
+ ok(buffer_insert(&buf, 1, "l", 1) && compare(&buf, "floo", 4), "Insert data in middle");
+ ok(buffer_insert(&buf, 4, "r", 1) && compare(&buf, "floor", 5), "Insert data at end");
+
+ size_t cap = buffer_capacity(&buf);
+ buffer_clear(&buf);
+ ok(buf.data && buffer_length(&buf) == 0 && buffer_capacity(&buf) == cap, "Clear");
+
+ ok(buffer_put(&buf, "foo", 0) && compare(&buf, "", 0), "Put zero length data");
+ ok(buffer_put(&buf, "bar", 3) && compare(&buf, "bar", 3), "Put data");
+
+ ok(buffer_prepend(&buf, "foo\0", 4) && compare(&buf, "foo\0bar", 7), "Prepend data");
+ ok(buffer_append(&buf, "\0baz", 4) && compare(&buf, "foo\0bar\0baz", 11), "Append data");
+
+ ok(buffer_grow(&buf, cap+1) && compare(&buf, "foo\0bar\0baz", 11) && buffer_capacity(&buf) >= cap+1, "Grow");
+
+ const char *content = buffer_content(&buf);
+ char *data = buffer_move(&buf);
+ ok(data == content && buffer_length(&buf) == 0 && buffer_capacity(&buf) == 0 && buffer_content(&buf) == NULL, "Move");
+ ok(buffer_append0(&buf, "foo") && buffer_content(&buf) != data, "Modify after move");
+ free(data);
+
+ skip_if(TIS_INTERPRETER, 1, "vsnprintf not supported") {
+
+ ok(buffer_printf(&buf, "Test: %d\n", 42) && compare0(&buf, "Test: 42\n"), "Set formatted");
+ ok(buffer_printf(&buf, "%d\n", 42) && compare0(&buf, "42\n"), "Set formatted overwrite");
+ buffer_clear(&buf);
+
+ ok(buffer_printf(&buf, "%s", "") && compare0(&buf, ""), "Set formatted empty string");
+ buffer_clear(&buf);
+
+ bool append = true;
+ for (int i = 1; i <= 10; i++)
+ append &= buffer_appendf(&buf, "%d", i);
+ ok(append && compare0(&buf, "12345678910"), "Append formatted");
+ buffer_clear(&buf);
+
+ append = true;
+ for (int i = 1; i <= 10; i++)
+ append &= buffer_appendf(&buf, "%s", "");
+ ok(append && compare0(&buf, ""), "Append formatted empty string");
+ buffer_clear(&buf);
+ }
+
+ buffer_release(&buf);
+
+ return exit_status();
+}
diff --git a/test/core/ccan-config.c b/test/core/ccan-config.c
new file mode 100644
index 0000000..f358f04
--- /dev/null
+++ b/test/core/ccan-config.c
@@ -0,0 +1,571 @@
+/* Simple tool to create config.h.
+ * Would be much easier with ccan modules, but deliberately standalone.
+ *
+ * Copyright 2011 Rusty Russell <rusty@rustcorp.com.au>. MIT license.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <err.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+
+#define DEFAULT_COMPILER "cc"
+#define DEFAULT_FLAGS "-g3 -ggdb -Wall -Wundef -Wmissing-prototypes -Wmissing-declarations -Wstrict-prototypes -Wold-style-definition"
+
+#define OUTPUT_FILE "configurator.out"
+#define INPUT_FILE "configuratortest.c"
+
+static int verbose;
+
+enum test_style {
+ OUTSIDE_MAIN = 0x1,
+ DEFINES_FUNC = 0x2,
+ INSIDE_MAIN = 0x4,
+ DEFINES_EVERYTHING = 0x8,
+ MAY_NOT_COMPILE = 0x10,
+ EXECUTE = 0x8000
+};
+
+struct test {
+ const char *name;
+ enum test_style style;
+ const char *depends;
+ const char *link;
+ const char *fragment;
+ const char *overrides; /* On success, force this to '1' */
+ bool done;
+ bool answer;
+};
+
+static struct test tests[] = {
+ { "HAVE_32BIT_OFF_T", DEFINES_EVERYTHING|EXECUTE, NULL, NULL,
+ "#include <sys/types.h>\n"
+ "int main(int argc, char *argv[]) {\n"
+ " return sizeof(off_t) == 4 ? 0 : 1;\n"
+ "}\n" },
+ { "HAVE_ALIGNOF", INSIDE_MAIN, NULL, NULL,
+ "return __alignof__(double) > 0 ? 0 : 1;" },
+ { "HAVE_ASPRINTF", DEFINES_FUNC, NULL, NULL,
+ "#define _GNU_SOURCE\n"
+ "#include <stdio.h>\n"
+ "static char *func(int x) {"
+ " char *p;\n"
+ " if (asprintf(&p, \"%u\", x) == -1) p = NULL;"
+ " return p;\n"
+ "}" },
+ { "HAVE_ATTRIBUTE_COLD", DEFINES_FUNC, NULL, NULL,
+ "static int __attribute__((cold)) func(int x) { return x; }" },
+ { "HAVE_ATTRIBUTE_CONST", DEFINES_FUNC, NULL, NULL,
+ "static int __attribute__((const)) func(int x) { return x; }" },
+ { "HAVE_ATTRIBUTE_PURE", DEFINES_FUNC, NULL, NULL,
+ "static int __attribute__((pure)) func(int x) { return x; }" },
+ { "HAVE_ATTRIBUTE_MAY_ALIAS", OUTSIDE_MAIN, NULL, NULL,
+ "typedef short __attribute__((__may_alias__)) short_a;" },
+ { "HAVE_ATTRIBUTE_NORETURN", DEFINES_FUNC, NULL, NULL,
+ "#include <stdlib.h>\n"
+ "static void __attribute__((noreturn)) func(int x) { exit(x); }" },
+ { "HAVE_ATTRIBUTE_PRINTF", DEFINES_FUNC, NULL, NULL,
+ "static void __attribute__((format(__printf__, 1, 2))) func(const char *fmt, ...) { }" },
+ { "HAVE_ATTRIBUTE_UNUSED", OUTSIDE_MAIN, NULL, NULL,
+ "static int __attribute__((unused)) func(int x) { return x; }" },
+ { "HAVE_ATTRIBUTE_USED", OUTSIDE_MAIN, NULL, NULL,
+ "static int __attribute__((used)) func(int x) { return x; }" },
+ { "HAVE_BACKTRACE", DEFINES_FUNC, NULL, NULL,
+ "#include <execinfo.h>\n"
+ "static int func(int x) {"
+ " void *bt[10];\n"
+ " return backtrace(bt, 10) < x;\n"
+ "}" },
+ { "HAVE_BIG_ENDIAN", INSIDE_MAIN|EXECUTE, NULL, NULL,
+ "union { int i; char c[sizeof(int)]; } u;\n"
+ "u.i = 0x01020304;\n"
+ "return u.c[0] == 0x01 && u.c[1] == 0x02 && u.c[2] == 0x03 && u.c[3] == 0x04 ? 0 : 1;" },
+ { "HAVE_BSWAP_64", DEFINES_FUNC, "HAVE_BYTESWAP_H", NULL,
+ "#include <byteswap.h>\n"
+ "static int func(int x) { return bswap_64(x); }" },
+ { "HAVE_BUILTIN_CHOOSE_EXPR", INSIDE_MAIN, NULL, NULL,
+ "return __builtin_choose_expr(1, 0, \"garbage\");" },
+ { "HAVE_BUILTIN_CLZ", INSIDE_MAIN, NULL, NULL,
+ "return __builtin_clz(1) == (sizeof(int)*8 - 1) ? 0 : 1;" },
+ { "HAVE_BUILTIN_CLZL", INSIDE_MAIN, NULL, NULL,
+ "return __builtin_clzl(1) == (sizeof(long)*8 - 1) ? 0 : 1;" },
+ { "HAVE_BUILTIN_CLZLL", INSIDE_MAIN, NULL, NULL,
+ "return __builtin_clzll(1) == (sizeof(long long)*8 - 1) ? 0 : 1;" },
+ { "HAVE_BUILTIN_CTZ", INSIDE_MAIN, NULL, NULL,
+ "return __builtin_ctz(1 << (sizeof(int)*8 - 1)) == (sizeof(int)*8 - 1) ? 0 : 1;" },
+ { "HAVE_BUILTIN_CTZL", INSIDE_MAIN, NULL, NULL,
+ "return __builtin_ctzl(1UL << (sizeof(long)*8 - 1)) == (sizeof(long)*8 - 1) ? 0 : 1;" },
+ { "HAVE_BUILTIN_CTZLL", INSIDE_MAIN, NULL, NULL,
+ "return __builtin_ctzll(1ULL << (sizeof(long long)*8 - 1)) == (sizeof(long long)*8 - 1) ? 0 : 1;" },
+ { "HAVE_BUILTIN_CONSTANT_P", INSIDE_MAIN, NULL, NULL,
+ "return __builtin_constant_p(1) ? 0 : 1;" },
+ { "HAVE_BUILTIN_EXPECT", INSIDE_MAIN, NULL, NULL,
+ "return __builtin_expect(argc == 1, 1) ? 0 : 1;" },
+ { "HAVE_BUILTIN_FFS", INSIDE_MAIN, NULL, NULL,
+ "return __builtin_ffs(0) == 0 ? 0 : 1;" },
+ { "HAVE_BUILTIN_FFSL", INSIDE_MAIN, NULL, NULL,
+ "return __builtin_ffsl(0L) == 0 ? 0 : 1;" },
+ { "HAVE_BUILTIN_FFSLL", INSIDE_MAIN, NULL, NULL,
+ "return __builtin_ffsll(0LL) == 0 ? 0 : 1;" },
+ { "HAVE_BUILTIN_POPCOUNTL", INSIDE_MAIN, NULL, NULL,
+ "return __builtin_popcountl(255L) == 8 ? 0 : 1;" },
+ { "HAVE_BUILTIN_TYPES_COMPATIBLE_P", INSIDE_MAIN, NULL, NULL,
+ "return __builtin_types_compatible_p(char *, int) ? 1 : 0;" },
+ { "HAVE_ICCARM_INTRINSICS", DEFINES_FUNC, NULL, NULL,
+ "#include <intrinsics.h>\n"
+ "int func(int v) {\n"
+ " return __CLZ(__RBIT(v));\n"
+ "}" },
+ { "HAVE_BYTESWAP_H", OUTSIDE_MAIN, NULL, NULL,
+ "#include <byteswap.h>\n" },
+ { "HAVE_CLOCK_GETTIME",
+ DEFINES_FUNC, "HAVE_STRUCT_TIMESPEC", NULL,
+ "#include <time.h>\n"
+ "static struct timespec func(void) {\n"
+ " struct timespec ts;\n"
+ " clock_gettime(CLOCK_REALTIME, &ts);\n"
+ " return ts;\n"
+ "}\n" },
+ { "HAVE_CLOCK_GETTIME_IN_LIBRT",
+ DEFINES_FUNC,
+ "HAVE_STRUCT_TIMESPEC !HAVE_CLOCK_GETTIME",
+ "-lrt",
+ "#include <time.h>\n"
+ "static struct timespec func(void) {\n"
+ " struct timespec ts;\n"
+ " clock_gettime(CLOCK_REALTIME, &ts);\n"
+ " return ts;\n"
+ "}\n",
+ /* This means HAVE_CLOCK_GETTIME, too */
+ "HAVE_CLOCK_GETTIME" },
+ { "HAVE_COMPOUND_LITERALS", INSIDE_MAIN, NULL, NULL,
+ "int *foo = (int[]) { 1, 2, 3, 4 };\n"
+ "return foo[0] ? 0 : 1;" },
+ { "HAVE_FCHDIR", DEFINES_EVERYTHING|EXECUTE, NULL, NULL,
+ "#include <sys/types.h>\n"
+ "#include <sys/stat.h>\n"
+ "#include <fcntl.h>\n"
+ "#include <unistd.h>\n"
+ "int main(void) {\n"
+ " int fd = open(\"..\", O_RDONLY);\n"
+ " return fchdir(fd) == 0 ? 0 : 1;\n"
+ "}\n" },
+ { "HAVE_ERR_H", DEFINES_FUNC, NULL, NULL,
+ "#include <err.h>\n"
+ "static void func(int arg) {\n"
+ " if (arg == 0)\n"
+ " err(1, \"err %u\", arg);\n"
+ " if (arg == 1)\n"
+ " errx(1, \"err %u\", arg);\n"
+ " if (arg == 3)\n"
+ " warn(\"warn %u\", arg);\n"
+ " if (arg == 4)\n"
+ " warnx(\"warn %u\", arg);\n"
+ "}\n" },
+ { "HAVE_FILE_OFFSET_BITS", DEFINES_EVERYTHING|EXECUTE,
+ "HAVE_32BIT_OFF_T", NULL,
+ "#define _FILE_OFFSET_BITS 64\n"
+ "#include <sys/types.h>\n"
+ "int main(int argc, char *argv[]) {\n"
+ " return sizeof(off_t) == 8 ? 0 : 1;\n"
+ "}\n" },
+ { "HAVE_FOR_LOOP_DECLARATION", INSIDE_MAIN, NULL, NULL,
+ "for (int i = 0; i < argc; i++) { return 0; };\n"
+ "return 1;" },
+ { "HAVE_FLEXIBLE_ARRAY_MEMBER", OUTSIDE_MAIN, NULL, NULL,
+ "struct foo { unsigned int x; int arr[]; };" },
+ { "HAVE_GETPAGESIZE", DEFINES_FUNC, NULL, NULL,
+ "#include <unistd.h>\n"
+ "static int func(void) { return getpagesize(); }" },
+ { "HAVE_ISBLANK", DEFINES_FUNC, NULL, NULL,
+ "#define _GNU_SOURCE\n"
+ "#include <ctype.h>\n"
+ "static int func(void) { return isblank(' '); }" },
+ { "HAVE_LITTLE_ENDIAN", INSIDE_MAIN|EXECUTE, NULL, NULL,
+ "union { int i; char c[sizeof(int)]; } u;\n"
+ "u.i = 0x01020304;\n"
+ "return u.c[0] == 0x04 && u.c[1] == 0x03 && u.c[2] == 0x02 && u.c[3] == 0x01 ? 0 : 1;" },
+ { "HAVE_MEMMEM", DEFINES_FUNC, NULL, NULL,
+ "#define _GNU_SOURCE\n"
+ "#include <string.h>\n"
+ "static void *func(void *h, size_t hl, void *n, size_t nl) {\n"
+ "return memmem(h, hl, n, nl);"
+ "}\n", },
+ { "HAVE_MEMRCHR", DEFINES_FUNC, NULL, NULL,
+ "#define _GNU_SOURCE\n"
+ "#include <string.h>\n"
+ "static void *func(void *s, int c, size_t n) {\n"
+ "return memrchr(s, c, n);"
+ "}\n", },
+ { "HAVE_MMAP", DEFINES_FUNC, NULL, NULL,
+ "#include <sys/mman.h>\n"
+ "static void *func(int fd) {\n"
+ " return mmap(0, 65536, PROT_READ, MAP_SHARED, fd, 0);\n"
+ "}" },
+ { "HAVE_PROC_SELF_MAPS", DEFINES_EVERYTHING|EXECUTE, NULL, NULL,
+ "#include <sys/types.h>\n"
+ "#include <sys/stat.h>\n"
+ "#include <fcntl.h>\n"
+ "int main(void) {\n"
+ " return open(\"/proc/self/maps\", O_RDONLY) != -1 ? 0 : 1;\n"
+ "}\n" },
+ { "HAVE_QSORT_R_PRIVATE_LAST",
+ DEFINES_EVERYTHING|EXECUTE|MAY_NOT_COMPILE, NULL, NULL,
+ "#define _GNU_SOURCE 1\n"
+ "#include <stdlib.h>\n"
+ "static int cmp(const void *lp, const void *rp, void *priv) {\n"
+ " *(unsigned int *)priv = 1;\n"
+ " return *(const int *)lp - *(const int *)rp; }\n"
+ "int main(void) {\n"
+ " int array[] = { 9, 2, 5 };\n"
+ " unsigned int called = 0;\n"
+ " qsort_r(array, 3, sizeof(int), cmp, &called);\n"
+ " return called && array[0] == 2 && array[1] == 5 && array[2] == 9 ? 0 : 1;\n"
+ "}\n" },
+ { "HAVE_STRUCT_TIMESPEC",
+ DEFINES_FUNC, NULL, NULL,
+ "#include <time.h>\n"
+ "static void func(void) {\n"
+ " struct timespec ts;\n"
+ " ts.tv_sec = ts.tv_nsec = 1;\n"
+ "}\n" },
+ { "HAVE_SECTION_START_STOP",
+ DEFINES_FUNC, NULL, NULL,
+ "static void *__attribute__((__section__(\"mysec\"))) p = &p;\n"
+ "static int func(void) {\n"
+ " extern void *__start_mysec[], *__stop_mysec[];\n"
+ " return __stop_mysec - __start_mysec;\n"
+ "}\n" },
+ { "HAVE_STACK_GROWS_UPWARDS", DEFINES_EVERYTHING|EXECUTE, NULL, NULL,
+ "static long nest(const void *base, unsigned int i)\n"
+ "{\n"
+ " if (i == 0)\n"
+ " return (const char *)&i - (const char *)base;\n"
+ " return nest(base, i-1);\n"
+ "}\n"
+ "int main(int argc, char *argv[]) {\n"
+ " return (nest(&argc, argc) > 0) ? 0 : 1\n;"
+ "}\n" },
+ { "HAVE_STATEMENT_EXPR", INSIDE_MAIN, NULL, NULL,
+ "return ({ int x = argc; x == argc ? 0 : 1; });" },
+ { "HAVE_SYS_FILIO_H", OUTSIDE_MAIN, NULL, NULL, /* Solaris needs this for FIONREAD */
+ "#include <sys/filio.h>\n" },
+ { "HAVE_SYS_TERMIOS_H", OUTSIDE_MAIN, NULL, NULL,
+ "#include <sys/termios.h>\n" },
+ { "HAVE_TYPEOF", INSIDE_MAIN, NULL, NULL,
+ "__typeof__(argc) i; i = argc; return i == argc ? 0 : 1;" },
+ { "HAVE_UNALIGNED_ACCESS", DEFINES_EVERYTHING|EXECUTE, NULL, NULL,
+ "#include <string.h>\n"
+ "int main(int argc, char *argv[]) {\n"
+ " char pad[sizeof(int *) * 1];\n"
+ " memcpy(pad, argv[0], sizeof(pad));\n"
+ " return *(int *)(pad) == *(int *)(pad + 1);\n"
+ "}\n" },
+ { "HAVE_UTIME", DEFINES_FUNC, NULL, NULL,
+ "#include <sys/types.h>\n"
+ "#include <utime.h>\n"
+ "static int func(const char *filename) {\n"
+ " struct utimbuf times = { 0 };\n"
+ " return utime(filename, &times);\n"
+ "}" },
+ { "HAVE_WARN_UNUSED_RESULT", DEFINES_FUNC, NULL, NULL,
+ "#include <sys/types.h>\n"
+ "#include <utime.h>\n"
+ "static __attribute__((warn_unused_result)) int func(int i) {\n"
+ " return i + 1;\n"
+ "}" },
+};
+
+static char *grab_fd(int fd)
+{
+ int ret;
+ size_t max, size = 0;
+ char *buffer;
+
+ max = 16384;
+ buffer = malloc(max+1);
+ while ((ret = read(fd, buffer + size, max - size)) > 0) {
+ size += ret;
+ if (size == max)
+ buffer = realloc(buffer, max *= 2);
+ }
+ if (ret < 0)
+ err(1, "reading from command");
+ buffer[size] = '\0';
+ return buffer;
+}
+
+static char *run(const char *cmd, int *exitstatus)
+{
+ pid_t pid;
+ int p[2];
+ char *ret;
+ int status;
+
+ if (pipe(p) != 0)
+ err(1, "creating pipe");
+
+ pid = fork();
+ if (pid == -1)
+ err(1, "forking");
+
+ if (pid == 0) {
+ if (dup2(p[1], STDOUT_FILENO) != STDOUT_FILENO
+ || dup2(p[1], STDERR_FILENO) != STDERR_FILENO
+ || close(p[0]) != 0
+ || close(STDIN_FILENO) != 0
+ || open("/dev/null", O_RDONLY) != STDIN_FILENO)
+ exit(128);
+
+ status = system(cmd);
+ if (WIFEXITED(status))
+ exit(WEXITSTATUS(status));
+ /* Here's a hint... */
+ exit(128 + WTERMSIG(status));
+ }
+
+ close(p[1]);
+ ret = grab_fd(p[0]);
+ /* This shouldn't fail... */
+ if (waitpid(pid, &status, 0) != pid)
+ err(1, "Failed to wait for child");
+ close(p[0]);
+ if (WIFEXITED(status))
+ *exitstatus = WEXITSTATUS(status);
+ else
+ *exitstatus = -WTERMSIG(status);
+ return ret;
+}
+
+static char *connect_args(const char *argv[], const char *extra)
+{
+ unsigned int i, len = strlen(extra) + 1;
+ char *ret;
+
+ for (i = 1; argv[i]; i++)
+ len += 1 + strlen(argv[i]);
+
+ ret = malloc(len);
+ len = 0;
+ for (i = 1; argv[i]; i++) {
+ strcpy(ret + len, argv[i]);
+ len += strlen(argv[i]);
+ if (argv[i+1])
+ ret[len++] = ' ';
+ }
+ strcpy(ret + len, extra);
+ return ret;
+}
+
+static struct test *find_test(const char *name)
+{
+ unsigned int i;
+
+ for (i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) {
+ if (strcmp(tests[i].name, name) == 0)
+ return &tests[i];
+ }
+ abort();
+}
+
+#define PRE_BOILERPLATE "/* Test program generated by configurator. */\n"
+#define MAIN_START_BOILERPLATE "int main(int argc, char *argv[]) {\n"
+#define USE_FUNC_BOILERPLATE "(void)func;\n"
+#define MAIN_BODY_BOILERPLATE "return 0;\n"
+#define MAIN_END_BOILERPLATE "}\n"
+
+static bool run_test(const char *cmd, struct test *test)
+{
+ char *output;
+ FILE *outf;
+ int status;
+
+ if (test->done)
+ return test->answer;
+
+ if (test->depends) {
+ size_t len;
+ const char *deps = test->depends;
+ char *dep;
+
+ /* Space-separated dependencies, could be ! for inverse. */
+ while ((len = strcspn(deps, " "))) {
+ bool positive = true;
+ if (deps[len]) {
+ dep = strdup(deps);
+ dep[len] = '\0';
+ } else {
+ dep = (char *)deps;
+ }
+
+ if (dep[0] == '!') {
+ dep++;
+ positive = false;
+ }
+ if (run_test(cmd, find_test(dep)) != positive) {
+ test->answer = false;
+ test->done = true;
+ return test->answer;
+ }
+ deps += len;
+ deps += strspn(deps, " ");
+ }
+ }
+
+ outf = fopen(INPUT_FILE, "w");
+ if (!outf)
+ err(1, "creating %s", INPUT_FILE);
+
+ fprintf(outf, "%s", PRE_BOILERPLATE);
+ switch (test->style & ~(EXECUTE|MAY_NOT_COMPILE)) {
+ case INSIDE_MAIN:
+ fprintf(outf, "%s", MAIN_START_BOILERPLATE);
+ fprintf(outf, "%s", test->fragment);
+ fprintf(outf, "%s", MAIN_END_BOILERPLATE);
+ break;
+ case OUTSIDE_MAIN:
+ fprintf(outf, "%s", test->fragment);
+ fprintf(outf, "%s", MAIN_START_BOILERPLATE);
+ fprintf(outf, "%s", MAIN_BODY_BOILERPLATE);
+ fprintf(outf, "%s", MAIN_END_BOILERPLATE);
+ break;
+ case DEFINES_FUNC:
+ fprintf(outf, "%s", test->fragment);
+ fprintf(outf, "%s", MAIN_START_BOILERPLATE);
+ fprintf(outf, "%s", USE_FUNC_BOILERPLATE);
+ fprintf(outf, "%s", MAIN_BODY_BOILERPLATE);
+ fprintf(outf, "%s", MAIN_END_BOILERPLATE);
+ break;
+ case DEFINES_EVERYTHING:
+ fprintf(outf, "%s", test->fragment);
+ break;
+ default:
+ abort();
+
+ }
+ fclose(outf);
+
+ if (verbose > 1)
+ if (system("cat " INPUT_FILE) == -1)
+ ;
+
+ if (test->link) {
+ char *newcmd;
+ newcmd = malloc(strlen(cmd) + strlen(" ")
+ + strlen(test->link) + 1);
+ sprintf(newcmd, "%s %s", cmd, test->link);
+ if (verbose > 1)
+ printf("Extra link line: %s", newcmd);
+ cmd = newcmd;
+ }
+
+ output = run(cmd, &status);
+ if (status != 0 || strstr(output, "warning")) {
+ if (verbose)
+ printf("Compile %s for %s, status %i: %s\n",
+ status ? "fail" : "warning",
+ test->name, status, output);
+ if ((test->style & EXECUTE) && !(test->style & MAY_NOT_COMPILE))
+ errx(1, "Test for %s did not compile:\n%s",
+ test->name, output);
+ test->answer = false;
+ free(output);
+ } else {
+ /* Compile succeeded. */
+ free(output);
+ /* We run INSIDE_MAIN tests for sanity checking. */
+ if ((test->style & EXECUTE) || (test->style & INSIDE_MAIN)) {
+ output = run("./" OUTPUT_FILE, &status);
+ if (!(test->style & EXECUTE) && status != 0)
+ errx(1, "Test for %s failed with %i:\n%s",
+ test->name, status, output);
+ if (verbose && status)
+ printf("%s exited %i\n", test->name, status);
+ free(output);
+ }
+ test->answer = (status == 0);
+ }
+ test->done = true;
+
+ if (test->answer && test->overrides) {
+ struct test *override = find_test(test->overrides);
+ override->done = true;
+ override->answer = true;
+ }
+ return test->answer;
+}
+
+int main(int argc, const char *argv[])
+{
+ char *cmd;
+ unsigned int i;
+ const char *default_args[]
+ = { "", DEFAULT_COMPILER, DEFAULT_FLAGS, NULL };
+
+ if (argc > 1) {
+ if (strcmp(argv[1], "--help") == 0) {
+ printf("Usage: configurator [-v] [<compiler> <flags>...]\n"
+ " <compiler> <flags> will have \"-o <outfile> <infile.c>\" appended\n"
+ "Default: %s %s\n",
+ DEFAULT_COMPILER, DEFAULT_FLAGS);
+ exit(0);
+ }
+ if (strcmp(argv[1], "-v") == 0) {
+ argc--;
+ argv++;
+ verbose = 1;
+ } else if (strcmp(argv[1], "-vv") == 0) {
+ argc--;
+ argv++;
+ verbose = 2;
+ }
+ }
+
+ if (argc == 1)
+ argv = default_args;
+
+ cmd = connect_args(argv, " -o " OUTPUT_FILE " " INPUT_FILE);
+ for (i = 0; i < sizeof(tests)/sizeof(tests[0]); i++)
+ run_test(cmd, &tests[i]);
+
+ unlink(OUTPUT_FILE);
+ unlink(INPUT_FILE);
+
+ printf("/* Generated by CCAN configurator */\n"
+ "#ifndef CCAN_CONFIG_H\n"
+ "#define CCAN_CONFIG_H\n");
+ printf("#ifndef _GNU_SOURCE\n");
+ printf("#define _GNU_SOURCE /* Always use GNU extensions. */\n");
+ printf("#endif\n");
+ printf("#define CCAN_COMPILER \"%s\"\n", argv[1]);
+ printf("#define CCAN_CFLAGS \"%s\"\n\n", connect_args(argv+1, ""));
+ /* This one implies "#include <ccan/..." works, eg. for tdb2.h */
+ printf("#define HAVE_CCAN 1\n");
+ for (i = 0; i < sizeof(tests)/sizeof(tests[0]); i++)
+ printf("#define %s %u\n", tests[i].name, tests[i].answer);
+ printf("#endif /* CCAN_CONFIG_H */\n");
+ return 0;
+}
diff --git a/test/core/ccan/compiler/LICENSE b/test/core/ccan/compiler/LICENSE
new file mode 120000
index 0000000..b7951da
--- /dev/null
+++ b/test/core/ccan/compiler/LICENSE
@@ -0,0 +1 @@
+../../licenses/CC0 \ No newline at end of file
diff --git a/test/core/ccan/compiler/_info b/test/core/ccan/compiler/_info
new file mode 100644
index 0000000..d60dff4
--- /dev/null
+++ b/test/core/ccan/compiler/_info
@@ -0,0 +1,64 @@
+#include "config.h"
+#include <string.h>
+#include <stdio.h>
+
+/**
+ * compiler - macros for common compiler extensions
+ *
+ * Abstracts away some compiler hints. Currently these include:
+ * - COLD
+ * For functions not called in fast paths (aka. cold functions)
+ * - PRINTF_FMT
+ * For functions which take printf-style parameters.
+ * - CONST_FUNCTION
+ * For functions which return the same value for same parameters.
+ * - NEEDED
+ * For functions and variables which must be emitted even if unused.
+ * - UNNEEDED
+ * For functions and variables which need not be emitted if unused.
+ * - UNUSED
+ * For parameters which are not used.
+ * - IS_COMPILE_CONSTANT()
+ * For using different tradeoffs for compiletime vs runtime evaluation.
+ *
+ * License: CC0 (Public domain)
+ * Author: Rusty Russell <rusty@rustcorp.com.au>
+ *
+ * Example:
+ * #include <ccan/compiler/compiler.h>
+ * #include <stdio.h>
+ * #include <stdarg.h>
+ *
+ * // Example of a (slow-path) logging function.
+ * static int log_threshold = 2;
+ * static void COLD PRINTF_FMT(2,3)
+ * logger(int level, const char *fmt, ...)
+ * {
+ * va_list ap;
+ * va_start(ap, fmt);
+ * if (level >= log_threshold)
+ * vfprintf(stderr, fmt, ap);
+ * va_end(ap);
+ * }
+ *
+ * int main(int argc, char *argv[])
+ * {
+ * if (argc != 1) {
+ * logger(3, "Don't want %i arguments!\n", argc-1);
+ * return 1;
+ * }
+ * return 0;
+ * }
+ */
+int main(int argc, char *argv[])
+{
+ /* Expect exactly one argument */
+ if (argc != 2)
+ return 1;
+
+ if (strcmp(argv[1], "depends") == 0) {
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/test/core/ccan/compiler/compiler.h b/test/core/ccan/compiler/compiler.h
new file mode 100644
index 0000000..bce4f25
--- /dev/null
+++ b/test/core/ccan/compiler/compiler.h
@@ -0,0 +1,231 @@
+/* CC0 (Public domain) - see LICENSE file for details */
+#ifndef CCAN_COMPILER_H
+#define CCAN_COMPILER_H
+#include "config.h"
+
+#ifndef COLD
+#if HAVE_ATTRIBUTE_COLD
+/**
+ * COLD - a function is unlikely to be called.
+ *
+ * Used to mark an unlikely code path and optimize appropriately.
+ * It is usually used on logging or error routines.
+ *
+ * Example:
+ * static void COLD moan(const char *reason)
+ * {
+ * fprintf(stderr, "Error: %s (%s)\n", reason, strerror(errno));
+ * }
+ */
+#define COLD __attribute__((__cold__))
+#else
+#define COLD
+#endif
+#endif
+
+#ifndef NORETURN
+#if HAVE_ATTRIBUTE_NORETURN
+/**
+ * NORETURN - a function does not return
+ *
+ * Used to mark a function which exits; useful for suppressing warnings.
+ *
+ * Example:
+ * static void NORETURN fail(const char *reason)
+ * {
+ * fprintf(stderr, "Error: %s (%s)\n", reason, strerror(errno));
+ * exit(1);
+ * }
+ */
+#define NORETURN __attribute__((__noreturn__))
+#else
+#define NORETURN
+#endif
+#endif
+
+#ifndef PRINTF_FMT
+#if HAVE_ATTRIBUTE_PRINTF
+/**
+ * PRINTF_FMT - a function takes printf-style arguments
+ * @nfmt: the 1-based number of the function's format argument.
+ * @narg: the 1-based number of the function's first variable argument.
+ *
+ * This allows the compiler to check your parameters as it does for printf().
+ *
+ * Example:
+ * void PRINTF_FMT(2,3) my_printf(const char *prefix, const char *fmt, ...);
+ */
+#define PRINTF_FMT(nfmt, narg) \
+ __attribute__((format(__printf__, nfmt, narg)))
+#else
+#define PRINTF_FMT(nfmt, narg)
+#endif
+#endif
+
+#ifndef CONST_FUNCTION
+#if HAVE_ATTRIBUTE_CONST
+/**
+ * CONST_FUNCTION - a function's return depends only on its argument
+ *
+ * This allows the compiler to assume that the function will return the exact
+ * same value for the exact same arguments. This implies that the function
+ * must not use global variables, or dereference pointer arguments.
+ */
+#define CONST_FUNCTION __attribute__((__const__))
+#else
+#define CONST_FUNCTION
+#endif
+
+#ifndef PURE_FUNCTION
+#if HAVE_ATTRIBUTE_PURE
+/**
+ * PURE_FUNCTION - a function is pure
+ *
+ * A pure function is one that has no side effects other than it's return value
+ * and uses no inputs other than it's arguments and global variables.
+ */
+#define PURE_FUNCTION __attribute__((__pure__))
+#else
+#define PURE_FUNCTION
+#endif
+#endif
+#endif
+
+#if HAVE_ATTRIBUTE_UNUSED
+#ifndef UNNEEDED
+/**
+ * UNNEEDED - a variable/function may not be needed
+ *
+ * This suppresses warnings about unused variables or functions, but tells
+ * the compiler that if it is unused it need not emit it into the source code.
+ *
+ * Example:
+ * // With some preprocessor options, this is unnecessary.
+ * static UNNEEDED int counter;
+ *
+ * // With some preprocessor options, this is unnecessary.
+ * static UNNEEDED void add_to_counter(int add)
+ * {
+ * counter += add;
+ * }
+ */
+#define UNNEEDED __attribute__((__unused__))
+#endif
+
+#ifndef NEEDED
+#if HAVE_ATTRIBUTE_USED
+/**
+ * NEEDED - a variable/function is needed
+ *
+ * This suppresses warnings about unused variables or functions, but tells
+ * the compiler that it must exist even if it (seems) unused.
+ *
+ * Example:
+ * // Even if this is unused, these are vital for debugging.
+ * static NEEDED int counter;
+ * static NEEDED void dump_counter(void)
+ * {
+ * printf("Counter is %i\n", counter);
+ * }
+ */
+#define NEEDED __attribute__((__used__))
+#else
+/* Before used, unused functions and vars were always emitted. */
+#define NEEDED __attribute__((__unused__))
+#endif
+#endif
+
+#ifndef UNUSED
+/**
+ * UNUSED - a parameter is unused
+ *
+ * Some compilers (eg. gcc with -W or -Wunused) warn about unused
+ * function parameters. This suppresses such warnings and indicates
+ * to the reader that it's deliberate.
+ *
+ * Example:
+ * // This is used as a callback, so needs to have this prototype.
+ * static int some_callback(void *unused UNUSED)
+ * {
+ * return 0;
+ * }
+ */
+#define UNUSED __attribute__((__unused__))
+#endif
+#else
+#ifndef UNNEEDED
+#define UNNEEDED
+#endif
+#ifndef NEEDED
+#define NEEDED
+#endif
+#ifndef UNUSED
+#define UNUSED
+#endif
+#endif
+
+#ifndef IS_COMPILE_CONSTANT
+#if HAVE_BUILTIN_CONSTANT_P
+/**
+ * IS_COMPILE_CONSTANT - does the compiler know the value of this expression?
+ * @expr: the expression to evaluate
+ *
+ * When an expression manipulation is complicated, it is usually better to
+ * implement it in a function. However, if the expression being manipulated is
+ * known at compile time, it is better to have the compiler see the entire
+ * expression so it can simply substitute the result.
+ *
+ * This can be done using the IS_COMPILE_CONSTANT() macro.
+ *
+ * Example:
+ * enum greek { ALPHA, BETA, GAMMA, DELTA, EPSILON };
+ *
+ * // Out-of-line version.
+ * const char *greek_name(enum greek greek);
+ *
+ * // Inline version.
+ * static inline const char *_greek_name(enum greek greek)
+ * {
+ * switch (greek) {
+ * case ALPHA: return "alpha";
+ * case BETA: return "beta";
+ * case GAMMA: return "gamma";
+ * case DELTA: return "delta";
+ * case EPSILON: return "epsilon";
+ * default: return "**INVALID**";
+ * }
+ * }
+ *
+ * // Use inline if compiler knows answer. Otherwise call function
+ * // to avoid copies of the same code everywhere.
+ * #define greek_name(g) \
+ * (IS_COMPILE_CONSTANT(greek) ? _greek_name(g) : greek_name(g))
+ */
+#define IS_COMPILE_CONSTANT(expr) __builtin_constant_p(expr)
+#else
+/* If we don't know, assume it's not. */
+#define IS_COMPILE_CONSTANT(expr) 0
+#endif
+#endif
+
+#ifndef WARN_UNUSED_RESULT
+#if HAVE_WARN_UNUSED_RESULT
+/**
+ * WARN_UNUSED_RESULT - warn if a function return value is unused.
+ *
+ * Used to mark a function where it is extremely unlikely that the caller
+ * can ignore the result, eg realloc().
+ *
+ * Example:
+ * // buf param may be freed by this; need return value!
+ * static char *WARN_UNUSED_RESULT enlarge(char *buf, unsigned *size)
+ * {
+ * return realloc(buf, (*size) *= 2);
+ * }
+ */
+#define WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))
+#else
+#define WARN_UNUSED_RESULT
+#endif
+#endif
+#endif /* CCAN_COMPILER_H */
diff --git a/test/core/ccan/compiler/test/compile_fail-printf.c b/test/core/ccan/compiler/test/compile_fail-printf.c
new file mode 100644
index 0000000..8f34ae5
--- /dev/null
+++ b/test/core/ccan/compiler/test/compile_fail-printf.c
@@ -0,0 +1,22 @@
+#include <ccan/compiler/compiler.h>
+
+static void PRINTF_FMT(2,3) my_printf(int x, const char *fmt, ...)
+{
+}
+
+int main(int argc, char *argv[])
+{
+ unsigned int i = 0;
+
+ my_printf(1, "Not a pointer "
+#ifdef FAIL
+ "%p",
+#if !HAVE_ATTRIBUTE_PRINTF
+#error "Unfortunately we don't fail if !HAVE_ATTRIBUTE_PRINTF."
+#endif
+#else
+ "%i",
+#endif
+ i);
+ return 0;
+}
diff --git a/test/core/ccan/compiler/test/run-is_compile_constant.c b/test/core/ccan/compiler/test/run-is_compile_constant.c
new file mode 100644
index 0000000..a66f2e1
--- /dev/null
+++ b/test/core/ccan/compiler/test/run-is_compile_constant.c
@@ -0,0 +1,15 @@
+#include <ccan/compiler/compiler.h>
+#include <ccan/tap/tap.h>
+
+int main(int argc, char *argv[])
+{
+ plan_tests(2);
+
+ ok1(!IS_COMPILE_CONSTANT(argc));
+#if HAVE_BUILTIN_CONSTANT_P
+ ok1(IS_COMPILE_CONSTANT(7));
+#else
+ pass("If !HAVE_BUILTIN_CONSTANT_P, IS_COMPILE_CONSTANT always false");
+#endif
+ return exit_status();
+}
diff --git a/test/core/ccan/tap/_info b/test/core/ccan/tap/_info
new file mode 100644
index 0000000..2b116de
--- /dev/null
+++ b/test/core/ccan/tap/_info
@@ -0,0 +1,61 @@
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+
+/**
+ * tap - Test Anything Protocol
+ *
+ * The tap package produces simple-to-parse mainly-human-readable test
+ * output to assist in the writing of test cases. It is based on the
+ * (now-defunct) libtap, which is based on Perl's CPAN TAP module. Its
+ * output can be parsed by a harness such as CPAN's Prove.
+ *
+ * CCAN testcases are expected to output the TAP format, usually using
+ * this package.
+ *
+ * For more information about TAP, see:
+ * http://en.wikipedia.org/wiki/Test_Anything_Protocol
+ *
+ * Based on the original libtap, Copyright (c) 2004 Nik Clayton.
+ *
+ * License: BSD (2 clause)
+ *
+ * Example:
+ * #include <string.h>
+ * #include <ccan/tap/tap.h>
+ *
+ * // Run some simple (but overly chatty) tests on strcmp().
+ * int main(int argc, char *argv[])
+ * {
+ * const char a[] = "a", another_a[] = "a";
+ * const char b[] = "b";
+ * const char ab[] = "ab";
+ *
+ * plan_tests(4);
+ * diag("Testing different pointers (%p/%p) with same contents",
+ * a, another_a);
+ * ok1(strcmp(a, another_a) == 0);
+ *
+ * diag("'a' comes before 'b'");
+ * ok1(strcmp(a, b) < 0);
+ * ok1(strcmp(b, a) > 0);
+ *
+ * diag("'ab' comes after 'a'");
+ * ok1(strcmp(ab, a) > 0);
+ * return exit_status();
+ * }
+ *
+ * Maintainer: Rusty Russell <rusty@rustcorp.com.au>
+ */
+int main(int argc, char *argv[])
+{
+ if (argc != 2)
+ return 1;
+
+ if (strcmp(argv[1], "depends") == 0) {
+ printf("ccan/compiler\n");
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/test/core/ccan/tap/tap.3 b/test/core/ccan/tap/tap.3
new file mode 100644
index 0000000..6fc7c5c
--- /dev/null
+++ b/test/core/ccan/tap/tap.3
@@ -0,0 +1,362 @@
+.Dd December 20, 2004
+.Os
+.Dt TAP 3
+.Sh NAME
+.Nm tap
+.Nd write tests that implement the Test Anything Protocol
+.Sh SYNOPSIS
+.In tap.h
+.Sh DESCRIPTION
+The
+.Nm
+library provides functions for writing test scripts that produce output
+consistent with the Test Anything Protocol. A test harness that parses
+this protocol can run these tests and produce useful reports indicating
+their success or failure.
+.Ss PRINTF STRINGS
+In the descriptions that follow, for any function that takes as the
+last two parameters
+.Dq Fa const char * , Fa ...
+it can be assumed that the
+.Fa const char *
+is a
+.Fn printf
+-like format string, and the optional arguments are values to be placed
+in that string.
+.Ss TEST PLANS
+.Bl -tag -width indent
+.It Xo
+.Ft void
+.Fn plan_tests "unsigned int"
+.Xc
+.It Xo
+.Ft void
+.Fn plan_no_plan "void"
+.Xc
+.It Xo
+.Ft void
+.Fn plan_skip_all "const char *" "..."
+.Xc
+.El
+.Pp
+You must first specify a test plan. This indicates how many tests you
+intend to run, and allows the test harness to notice if any tests were
+missed, or if the test program exited prematurely.
+.Pp
+To do this, use
+.Fn plan_tests .
+The function will cause your program to exit prematurely if you specify
+0 tests.
+.Pp
+In some situations you may not know how many tests you will be running, or
+you are developing your test program, and do not want to update the
+.Fn plan_tests
+parameter every time you make a change. For those situations use
+.Fn plan_no_plan .
+It indicates to the test harness that an indeterminate number
+of tests will be run.
+.Pp
+Both
+.Fn plan_tests
+and
+.Fn plan_no_plan
+will cause your test program to exit prematurely with a diagnostic
+message if they are called more than once.
+.Pp
+If your test program detects at run time that some required functionality
+is missing (for example, it relies on a database connection which is not
+present, or a particular configuration option that has not been included
+in the running kernel) use
+.Fn plan_skip_all ,
+passing as parameters a string to display indicating the reason for skipping
+the tests.
+.Ss SIMPLE TESTS
+.Bl -tag -width indent
+.It Xo
+.Ft unsigned int
+.Fn ok "expression" "const char *" "..."
+.Xc
+.It Xo
+.Ft unsigned int
+.Fn ok1 "expression"
+.Xc
+.It Xo
+.Ft unsigned int
+.Fn pass "const char *" "..."
+.Xc
+.It Xo
+.Ft unsigned int
+.Fn fail "const char *" "..."
+.Xc
+.El
+.Pp
+Tests are implemented as expressions checked by calls to the
+.Fn ok
+and
+.Fn ok1
+macros. In both cases
+.Fa expression
+should evaluate to true if the test succeeded.
+.Pp
+.Fn ok
+allows you to specify a name, or comment, describing the test which will
+be included in the output.
+.Fn ok1
+is for those times when the expression to be tested is self
+explanatory and does not need an associated comment. In those cases
+the test expression becomes the comment.
+.Pp
+These four calls are equivalent:
+.Bd -literal -offset indent
+int i = 5;
+
+ok(i == 5, "i equals 5"); /* Overly verbose */
+ok(i == 5, "i equals %d", i); /* Just to demonstrate printf-like
+ behaviour of the test name */
+ok(i == 5, "i == 5"); /* Needless repetition */
+ok1(i == 5); /* Just right */
+.Ed
+.Pp
+It is good practice to ensure that the test name describes the meaning
+behind the test rather than what you are testing. Viz
+.Bd -literal -offset indent
+ok(db != NULL, "db is not NULL"); /* Not bad, but */
+ok(db != NULL, "Database conn. succeeded"); /* this is better */
+.Ed
+.Pp
+.Fn ok
+and
+.Fn ok1
+return 1 if the expression evaluated to true, and 0 if it evaluated to
+false. This lets you chain calls from
+.Fn ok
+to
+.Fn diag
+to only produce diagnostic output if the test failed. For example, this
+code will include diagnostic information about why the database connection
+failed, but only if the test failed.
+.Bd -literal -offset indent
+if (!ok(db != NULL, "Database conn. succeeded")) {
+ diag("Database error code: %d", dberrno);
+}
+.Ed
+.Pp
+You also have
+.Fn pass
+and
+.Fn fail .
+From the Test::More documentation:
+.Bd -literal -offset indent
+Sometimes you just want to say that the tests have passed.
+Usually the case is you've got some complicated condition
+that is difficult to wedge into an ok(). In this case,
+you can simply use pass() (to declare the test ok) or fail
+(for not ok).
+
+Use these very, very, very sparingly.
+.Ed
+.Pp
+These are synonyms for ok(1, ...) and ok(0, ...).
+.Ss SKIPPING TESTS
+.Bl -tag -width indent
+.It Xo
+.Ft void
+.Fn skip "unsigned int" "const char *" "..."
+.Xc
+.It Xo
+.Fn skip_if "expression" "unsigned int" "const char *" "..."
+.Xc
+.El
+.Pp
+Sets of tests can be skipped. Ordinarily you would do this because
+the test can't be run in this particular testing environment.
+.Pp
+For example, suppose some tests should be run as root. If the test is
+not being run as root then the tests should be skipped. In this
+implementation, skipped tests are flagged as being ok, with a special
+message indicating that they were skipped. It is your responsibility
+to ensure that the number of tests skipped (the first parameter to
+.Fn skip )
+is correct for the number of tests to skip.
+.Pp
+One way of implementing this is with a
+.Dq do { } while(0);
+loop, or an
+.Dq if( ) { } else { }
+construct, to ensure that there are no additional side effects from the
+skipped tests.
+.Bd -literal -offset indent
+if(getuid() != 0) {
+ skip(1, "because test only works as root");
+} else {
+ ok(do_something_as_root() == 0, "Did something as root");
+}
+.Ed
+.Pp
+A convenient macro is provided to assist with this. The previous example could
+be re-written as follows.
+.Bd -literal -offset indent
+skip_if(getuid() != 0, 1, "because test only works as root") {
+ ok(do_something_as_root() == 0, "Did something as root");
+}
+.Ed
+.Ss MARKING TESTS AS Dq TODO
+.Bl -tag -width indent
+.It Xo
+.Ft void
+.Fn todo_start "const char *" "..."
+.Xc
+.It Xo
+.Ft void
+.Fn todo_end "void"
+.Xc
+.El
+.Pp
+Sets of tests can be flagged as being
+.Dq TODO .
+These are tests that you expect to fail, probably because you haven't
+fixed a bug, or finished a new feature yet. These tests will still be
+run, but with additional output that indicates that they are expected
+to fail. Should a test start to succeed unexpectedly, tools like
+.Xr prove 1
+will indicate this, and you can move the test out of the todo
+block. This is much more useful than simply commenting out (or
+.Dq #ifdef 0 ... #endif )
+the tests.
+.Bd -literal -offset indent
+todo_start("dwim() not returning true yet");
+
+ok(dwim(), "Did what the user wanted");
+
+todo_end();
+.Ed
+.Pp
+Should
+.Fn dwim
+ever start succeeding you will know about it as soon as you run the
+tests. Note that
+.Em unlike
+the
+.Fn skip_*
+family, additional code between
+.Fn todo_start
+and
+.Fn todo_end
+.Em is
+executed.
+.Ss SKIP vs. TODO
+From the Test::More documentation;
+.Bd -literal -offset indent
+If it's something the user might not be able to do, use SKIP.
+This includes optional modules that aren't installed, running
+under an OS that doesn't have some feature (like fork() or
+symlinks), or maybe you need an Internet connection and one
+isn't available.
+
+If it's something the programmer hasn't done yet, use TODO.
+This is for any code you haven't written yet, or bugs you have
+yet to fix, but want to put tests in your testing script
+(always a good idea).
+.Ed
+.Ss DIAGNOSTIC OUTPUT
+.Bl -tag -width indent
+.It Xo
+.Fr int
+.Fn diag "const char *" "..."
+.Xc
+.El
+.Pp
+If your tests need to produce diagnostic output, use
+.Fn diag .
+It ensures that the output will not be considered by the TAP test harness.
+.Fn diag
+adds the necessary trailing
+.Dq \en
+for you.
+It returns the number of characters written.
+.Bd -literal -offset indent
+diag("Expected return code 0, got return code %d", rcode);
+.Ed
+.Ss EXIT STATUS
+.Bl -tag -width indent
+.It Xo
+.Fr int
+.Fn exit_status void
+.Xc
+.El
+.Pp
+For maximum compatibility your test program should return a particular
+exit code. This is calculated by
+.Fn exit_status
+so it is sufficient to always return from
+.Fn main
+with either
+.Dq return exit_status();
+or
+.Dq exit(exit_status());
+as appropriate.
+.Sh EXAMPLES
+The
+.Pa tests
+directory in the source distribution contains numerous tests of
+.Nm
+functionality, written using
+.Nm .
+Examine them for examples of how to construct test suites.
+.Sh COMPATIBILITY
+.Nm
+strives to be compatible with the Perl Test::More and Test::Harness
+modules. The test suite verifies that
+.Nm
+is bug-for-bug compatible with their behaviour. This is why some
+functions which would more naturally return nothing return constant
+values.
+.Pp
+If the
+.Lb libpthread
+is found at compile time,
+.Nm
+.Em should
+be thread safe. Indications to the contrary (and test cases that expose
+incorrect behaviour) are very welcome.
+.Sh SEE ALSO
+.Xr Test::More 1 ,
+.Xr Test::Harness 1 ,
+.Xr prove 1
+.Sh STANDARDS
+.Nm
+requires a
+.St -isoC-99
+compiler. Some of the
+.Nm
+functionality is implemented as variadic macros, and that functionality
+was not formally codified until C99. Patches to use
+.Nm
+with earlier compilers that have their own implementation of variadic
+macros will be gratefully received.
+.Sh HISTORY
+.Nm
+was written to help improve the quality and coverage of the FreeBSD
+regression test suite, and released in the hope that others find it
+a useful tool to help improve the quality of their code.
+.Sh AUTHORS
+.An "Nik Clayton" Aq nik@ngo.org.uk ,
+.Aq nik@FreeBSD.org
+.Pp
+.Nm
+would not exist without the efforts of
+.An "Michael G Schwern" Aq schqern@pobox.com ,
+.An "Andy Lester" Aq andy@petdance.com ,
+and the countless others who have worked on the Perl QA programme.
+.Sh BUGS
+Ideally, running the tests would have no side effects on the behaviour
+of the application you are testing. However, it is not always possible
+to avoid them. The following side effects of using
+.Nm
+are known.
+.Bl -bullet -offset indent
+.It
+stdout is set to unbuffered mode after calling any of the
+.Fn plan_*
+functions.
+.El
diff --git a/test/core/ccan/tap/tap.c b/test/core/ccan/tap/tap.c
new file mode 100644
index 0000000..2c465ab
--- /dev/null
+++ b/test/core/ccan/tap/tap.c
@@ -0,0 +1,459 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include "config.h"
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "tap.h"
+
+void (*tap_fail_callback)(void) = NULL;
+
+static int no_plan = 0;
+static int skip_all = 0;
+static int have_plan = 0;
+static unsigned int test_count = 0; /* Number of tests that have been run */
+static unsigned int e_tests = 0; /* Expected number of tests to run */
+static unsigned int failures = 0; /* Number of tests that failed */
+static char *todo_msg = NULL;
+static const char *todo_msg_fixed = "libtap malloc issue";
+static int todo = 0;
+static int test_died = 0;
+static int test_pid;
+
+/* Encapsulate the pthread code in a conditional. In the absence of
+ libpthread the code does nothing.
+
+ If you have multiple threads calling ok() etc. at the same time you would
+ need this, but in that case your test numbers will be random and I'm not
+ sure it makes sense. --RR
+*/
+#ifdef WANT_PTHREAD
+#include <pthread.h>
+static pthread_mutex_t M = PTHREAD_MUTEX_INITIALIZER;
+# define LOCK pthread_mutex_lock(&M)
+# define UNLOCK pthread_mutex_unlock(&M)
+#else
+# define LOCK
+# define UNLOCK
+#endif
+
+static void
+_expected_tests(unsigned int tests)
+{
+
+ printf("1..%d\n", tests);
+ e_tests = tests;
+}
+
+static void
+diagv(const char *fmt, va_list ap)
+{
+ fputs("# ", stdout);
+ vfprintf(stdout, fmt, ap);
+ fputs("\n", stdout);
+}
+
+static void
+_diag(const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ diagv(fmt, ap);
+ va_end(ap);
+}
+
+/*
+ * Generate a test result.
+ *
+ * ok -- boolean, indicates whether or not the test passed.
+ * test_name -- the name of the test, may be NULL
+ * test_comment -- a comment to print afterwards, may be NULL
+ */
+unsigned int
+_gen_result(int ok, const char *func, const char *file, unsigned int line,
+ const char *test_name, ...)
+{
+ va_list ap;
+ char *local_test_name = NULL;
+ char *c;
+ int name_is_digits;
+
+ LOCK;
+
+ test_count++;
+
+ /* Start by taking the test name and performing any printf()
+ expansions on it */
+ if(test_name != NULL) {
+ va_start(ap, test_name);
+ if (vasprintf(&local_test_name, test_name, ap) < 0)
+ local_test_name = NULL;
+ va_end(ap);
+
+ /* Make sure the test name contains more than digits
+ and spaces. Emit an error message and exit if it
+ does */
+ if(local_test_name) {
+ name_is_digits = 1;
+ for(c = local_test_name; *c != '\0'; c++) {
+ if(!isdigit((unsigned char)*c)
+ && !isspace((unsigned char)*c)) {
+ name_is_digits = 0;
+ break;
+ }
+ }
+
+ if(name_is_digits) {
+ _diag(" You named your test '%s'. You shouldn't use numbers for your test names.", local_test_name);
+ _diag(" Very confusing.");
+ }
+ }
+ }
+
+ if(!ok) {
+ printf("not ");
+ failures++;
+ }
+
+ printf("ok %d", test_count);
+
+ if(test_name != NULL) {
+ printf(" - ");
+
+ /* Print the test name, escaping any '#' characters it
+ might contain */
+ if(local_test_name != NULL) {
+ flockfile(stdout);
+ for(c = local_test_name; *c != '\0'; c++) {
+ if(*c == '#')
+ fputc('\\', stdout);
+ fputc((int)*c, stdout);
+ }
+ funlockfile(stdout);
+ } else { /* vasprintf() failed, use a fixed message */
+ printf("%s", todo_msg_fixed);
+ }
+ }
+
+ /* If we're in a todo_start() block then flag the test as being
+ TODO. todo_msg should contain the message to print at this
+ point. If it's NULL then asprintf() failed, and we should
+ use the fixed message.
+
+ This is not counted as a failure, so decrement the counter if
+ the test failed. */
+ if(todo) {
+ printf(" # TODO %s", todo_msg ? todo_msg : todo_msg_fixed);
+ if(!ok)
+ failures--;
+ }
+
+ printf("\n");
+
+ if(!ok)
+ _diag(" Failed %stest (%s:%s() at line %d)",
+ todo ? "(TODO) " : "", file, func, line);
+
+ free(local_test_name);
+
+ UNLOCK;
+
+ if (!ok && tap_fail_callback)
+ tap_fail_callback();
+
+ /* We only care (when testing) that ok is positive, but here we
+ specifically only want to return 1 or 0 */
+ return ok ? 1 : 0;
+}
+
+/*
+ * Cleanup at the end of the run, produce any final output that might be
+ * required.
+ */
+static void
+_cleanup(void)
+{
+ /* If we forked, don't do cleanup in child! */
+ if (getpid() != test_pid)
+ return;
+
+ LOCK;
+
+ /* If plan_no_plan() wasn't called, and we don't have a plan,
+ and we're not skipping everything, then something happened
+ before we could produce any output */
+ if(!no_plan && !have_plan && !skip_all) {
+ _diag("Looks like your test died before it could output anything.");
+ UNLOCK;
+ return;
+ }
+
+ if(test_died) {
+ _diag("Looks like your test died just after %d.", test_count);
+ UNLOCK;
+ return;
+ }
+
+
+ /* No plan provided, but now we know how many tests were run, and can
+ print the header at the end */
+ if(!skip_all && (no_plan || !have_plan)) {
+ printf("1..%d\n", test_count);
+ }
+
+ if((have_plan && !no_plan) && e_tests < test_count) {
+ _diag("Looks like you planned %d tests but ran %d extra.",
+ e_tests, test_count - e_tests);
+ UNLOCK;
+ return;
+ }
+
+ if((have_plan || !no_plan) && e_tests > test_count) {
+ _diag("Looks like you planned %d tests but only ran %d.",
+ e_tests, test_count);
+ if(failures) {
+ _diag("Looks like you failed %d tests of %d run.",
+ failures, test_count);
+ }
+ UNLOCK;
+ return;
+ }
+
+ if(failures)
+ _diag("Looks like you failed %d tests of %d.",
+ failures, test_count);
+
+ UNLOCK;
+}
+
+/*
+ * Initialise the TAP library. Will only do so once, however many times it's
+ * called.
+ */
+static void
+_tap_init(void)
+{
+ static int run_once = 0;
+
+ if(!run_once) {
+ test_pid = getpid();
+ atexit(_cleanup);
+
+ /* stdout needs to be unbuffered so that the output appears
+ in the same place relative to stderr output as it does
+ with Test::Harness */
+// setbuf(stdout, 0);
+ run_once = 1;
+ }
+}
+
+/*
+ * Note that there's no plan.
+ */
+void
+plan_no_plan(void)
+{
+
+ LOCK;
+
+ _tap_init();
+
+ if(have_plan != 0) {
+ fprintf(stderr, "You tried to plan twice!\n");
+ test_died = 1;
+ UNLOCK;
+ exit(255);
+ }
+
+ have_plan = 1;
+ no_plan = 1;
+
+ UNLOCK;
+}
+
+/*
+ * Note that the plan is to skip all tests
+ */
+void
+plan_skip_all(const char *reason)
+{
+
+ LOCK;
+
+ _tap_init();
+
+ skip_all = 1;
+
+ printf("1..0");
+
+ if(reason != NULL)
+ printf(" # Skip %s", reason);
+
+ printf("\n");
+
+ UNLOCK;
+}
+
+/*
+ * Note the number of tests that will be run.
+ */
+void
+plan_tests(unsigned int tests)
+{
+
+ LOCK;
+
+ _tap_init();
+
+ if(have_plan != 0) {
+ fprintf(stderr, "You tried to plan twice!\n");
+ test_died = 1;
+ UNLOCK;
+ exit(255);
+ }
+
+ if(tests == 0) {
+ fprintf(stderr, "You said to run 0 tests! You've got to run something.\n");
+ test_died = 1;
+ UNLOCK;
+ exit(255);
+ }
+
+ have_plan = 1;
+
+ _expected_tests(tests);
+
+ UNLOCK;
+}
+
+void
+diag(const char *fmt, ...)
+{
+ va_list ap;
+
+ LOCK;
+
+ va_start(ap, fmt);
+ diagv(fmt, ap);
+ va_end(ap);
+
+ UNLOCK;
+}
+
+void
+skip(unsigned int n, const char *fmt, ...)
+{
+ va_list ap;
+ char *skip_msg;
+
+ LOCK;
+
+ va_start(ap, fmt);
+ if (vasprintf(&skip_msg, fmt, ap) < 0)
+ skip_msg = NULL;
+ va_end(ap);
+
+ while(n-- > 0) {
+ test_count++;
+ printf("ok %d # skip %s\n", test_count,
+ skip_msg != NULL ?
+ skip_msg : "libtap():malloc() failed");
+ }
+
+ free(skip_msg);
+
+ UNLOCK;
+}
+
+void
+todo_start(const char *fmt, ...)
+{
+ va_list ap;
+
+ LOCK;
+
+ va_start(ap, fmt);
+ if (vasprintf(&todo_msg, fmt, ap) < 0)
+ todo_msg = NULL;
+ va_end(ap);
+
+ todo = 1;
+
+ UNLOCK;
+}
+
+void
+todo_end(void)
+{
+
+ LOCK;
+
+ todo = 0;
+ free(todo_msg);
+
+ UNLOCK;
+}
+
+static int
+exit_status_(void)
+{
+ int r;
+
+ LOCK;
+
+ /* If there's no plan, just return the number of failures */
+ if(no_plan || !have_plan) {
+ UNLOCK;
+ return failures;
+ }
+
+ /* Ran too many tests? Return the number of tests that were run
+ that shouldn't have been */
+ if(e_tests < test_count) {
+ r = test_count - e_tests;
+ UNLOCK;
+ return r;
+ }
+
+ /* Return the number of tests that failed + the number of tests
+ that weren't run */
+ r = failures + e_tests - test_count;
+ UNLOCK;
+
+ return r;
+}
+
+int
+exit_status(void)
+{
+ int r = exit_status_();
+ if (r > 255)
+ r = 255;
+ return r;
+}
diff --git a/test/core/ccan/tap/tap.h b/test/core/ccan/tap/tap.h
new file mode 100644
index 0000000..22c245d
--- /dev/null
+++ b/test/core/ccan/tap/tap.h
@@ -0,0 +1,251 @@
+#ifndef CCAN_TAP_H
+#define CCAN_TAP_H
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <ccan/compiler/compiler.h>
+
+/**
+ * plan_tests - announce the number of tests you plan to run
+ * @tests: the number of tests
+ *
+ * This should be the first call in your test program: it allows tracing
+ * of failures which mean that not all tests are run.
+ *
+ * If you don't know how many tests will actually be run, assume all of them
+ * and use skip() if you don't actually run some tests.
+ *
+ * Example:
+ * plan_tests(13);
+ */
+void plan_tests(unsigned int tests);
+
+/**
+ * ok1 - Simple conditional test
+ * @e: the expression which we expect to be true.
+ *
+ * This is the simplest kind of test: if the expression is true, the
+ * test passes. The name of the test which is printed will simply be
+ * file name, line number, and the expression itself.
+ *
+ * Example:
+ * ok1(somefunc() == 1);
+ */
+# define ok1(e) ((e) ? \
+ _gen_result(1, __func__, __FILE__, __LINE__, "%s", #e) : \
+ _gen_result(0, __func__, __FILE__, __LINE__, "%s", #e))
+
+/**
+ * ok - Conditional test with a name
+ * @e: the expression which we expect to be true.
+ * @...: the printf-style name of the test.
+ *
+ * If the expression is true, the test passes. The name of the test will be
+ * the filename, line number, and the printf-style string. This can be clearer
+ * than simply the expression itself.
+ *
+ * Example:
+ * ok1(somefunc() == 1);
+ * ok(somefunc() == 0, "Second somefunc() should fail");
+ */
+# define ok(e, ...) ((e) ? \
+ _gen_result(1, __func__, __FILE__, __LINE__, \
+ __VA_ARGS__) : \
+ _gen_result(0, __func__, __FILE__, __LINE__, \
+ __VA_ARGS__))
+
+/**
+ * pass - Note that a test passed
+ * @...: the printf-style name of the test.
+ *
+ * For complicated code paths, it can be easiest to simply call pass() in one
+ * branch and fail() in another.
+ *
+ * Example:
+ * int x = somefunc();
+ * if (x > 0)
+ * pass("somefunc() returned a valid value");
+ * else
+ * fail("somefunc() returned an invalid value");
+ */
+# define pass(...) ok(1, __VA_ARGS__)
+
+/**
+ * fail - Note that a test failed
+ * @...: the printf-style name of the test.
+ *
+ * For complicated code paths, it can be easiest to simply call pass() in one
+ * branch and fail() in another.
+ */
+# define fail(...) ok(0, __VA_ARGS__)
+
+/* I don't find these to be useful. */
+# define skip_if(cond, n, ...) \
+ if (cond) skip((n), __VA_ARGS__); \
+ else
+
+# define skip_start(test, n, ...) \
+ do { \
+ if((test)) { \
+ skip(n, __VA_ARGS__); \
+ continue; \
+ }
+
+# define skip_end } while(0)
+
+unsigned int _gen_result(int, const char *, const char *, unsigned int,
+ const char *, ...) PRINTF_FMT(5, 6);
+
+/**
+ * diag - print a diagnostic message (use instead of printf/fprintf)
+ * @fmt: the format of the printf-style message
+ *
+ * diag ensures that the output will not be considered to be a test
+ * result by the TAP test harness. It will append '\n' for you.
+ *
+ * Example:
+ * diag("Now running complex tests");
+ */
+void diag(const char *fmt, ...) PRINTF_FMT(1, 2);
+
+/**
+ * skip - print a diagnostic message (use instead of printf/fprintf)
+ * @n: number of tests you're skipping.
+ * @fmt: the format of the reason you're skipping the tests.
+ *
+ * Sometimes tests cannot be run because the test system lacks some feature:
+ * you should explicitly document that you're skipping tests using skip().
+ *
+ * From the Test::More documentation:
+ * If it's something the user might not be able to do, use SKIP. This
+ * includes optional modules that aren't installed, running under an OS that
+ * doesn't have some feature (like fork() or symlinks), or maybe you need an
+ * Internet connection and one isn't available.
+ *
+ * Example:
+ * #ifdef HAVE_SOME_FEATURE
+ * ok1(somefunc());
+ * #else
+ * skip(1, "Don't have SOME_FEATURE");
+ * #endif
+ */
+void skip(unsigned int n, const char *fmt, ...) PRINTF_FMT(2, 3);
+
+/**
+ * todo_start - mark tests that you expect to fail.
+ * @fmt: the reason they currently fail.
+ *
+ * It's extremely useful to write tests before you implement the matching fix
+ * or features: surround these tests by todo_start()/todo_end(). These tests
+ * will still be run, but with additional output that indicates that they are
+ * expected to fail.
+ *
+ * This way, should a test start to succeed unexpectedly, tools like prove(1)
+ * will indicate this and you can move the test out of the todo block. This
+ * is much more useful than simply commenting out (or '#if 0') the tests.
+ *
+ * From the Test::More documentation:
+ * If it's something the programmer hasn't done yet, use TODO. This is for
+ * any code you haven't written yet, or bugs you have yet to fix, but want to
+ * put tests in your testing script (always a good idea).
+ *
+ * Example:
+ * static bool dwim(void)
+ * {
+ * return false; // NYI
+ * }
+ * ...
+ * todo_start("dwim() not returning true yet");
+ * ok(dwim(), "Did what the user wanted");
+ * todo_end();
+ */
+void todo_start(const char *fmt, ...) PRINTF_FMT(1, 2);
+
+/**
+ * todo_end - end of tests you expect to fail.
+ *
+ * See todo_start().
+ */
+void todo_end(void);
+
+/**
+ * exit_status - the value that main should return.
+ *
+ * For maximum compatibility your test program should return a particular exit
+ * code (ie. 0 if all tests were run, and every test which was expected to
+ * succeed succeeded).
+ *
+ * Example:
+ * exit(exit_status());
+ */
+int exit_status(void);
+
+/**
+ * plan_no_plan - I have no idea how many tests I'm going to run.
+ *
+ * In some situations you may not know how many tests you will be running, or
+ * you are developing your test program, and do not want to update the
+ * plan_tests() call every time you make a change. For those situations use
+ * plan_no_plan() instead of plan_tests(). It indicates to the test harness
+ * that an indeterminate number of tests will be run.
+ *
+ * Remember, if you fail to plan, you plan to fail.
+ *
+ * Example:
+ * plan_no_plan();
+ * while (random() % 2)
+ * ok1(somefunc());
+ * exit(exit_status());
+ */
+void plan_no_plan(void);
+
+/**
+ * plan_skip_all - Indicate that you will skip all tests.
+ * @reason: the string indicating why you can't run any tests.
+ *
+ * If your test program detects at run time that some required functionality
+ * is missing (for example, it relies on a database connection which is not
+ * present, or a particular configuration option that has not been included
+ * in the running kernel) use plan_skip_all() instead of plan_tests().
+ *
+ * Example:
+ * #ifndef HAVE_SOME_FEATURE
+ * plan_skip_all("Need SOME_FEATURE support");
+ * exit(exit_status());
+ * #else
+ * plan_tests(13);
+ * ...
+ * #endif
+ */
+void plan_skip_all(const char *reason);
+
+/**
+ * tap_fail_callback - function to call when we fail
+ *
+ * This can be used to ease debugging, or exit on the first failure.
+ */
+extern void (*tap_fail_callback)(void);
+
+#endif /* CCAN_TAP_H */
diff --git a/test/core/ccan/tap/test/run.c b/test/core/ccan/tap/test/run.c
new file mode 100644
index 0000000..fb039a8
--- /dev/null
+++ b/test/core/ccan/tap/test/run.c
@@ -0,0 +1,133 @@
+/* We use the fact that pipes have a buffer greater than the size of
+ * any output, and change stdout and stderr to use that.
+ *
+ * Since we don't use libtap for output, this looks like one big test. */
+#include <ccan/tap/tap.h>
+#include <ccan/tap/tap.c>
+#include <stdio.h>
+#include <limits.h>
+#include <err.h>
+#include <string.h>
+#include <stdbool.h>
+#include <fnmatch.h>
+
+
+
+/* We dup stderr to here. */
+static int stderrfd;
+
+/* write_all inlined here to avoid circular dependency. */
+static void write_all(int fd, const void *data, size_t size)
+{
+ while (size) {
+ ssize_t done;
+
+ done = write(fd, data, size);
+ if (done <= 0)
+ _exit(1);
+ data = (const char *)data + done;
+ size -= done;
+ }
+}
+
+/* Simple replacement for err() */
+static void failmsg(const char *fmt, ...)
+{
+ char buf[1024];
+ va_list ap;
+
+ /* Write into buffer. */
+ va_start(ap, fmt);
+ vsprintf(buf, fmt, ap);
+ va_end(ap);
+
+ write_all(stderrfd, "# ", 2);
+ write_all(stderrfd, buf, strlen(buf));
+ write_all(stderrfd, "\n", 1);
+ _exit(1);
+}
+
+static void expect(int fd, const char *pattern)
+{
+ char buffer[PIPE_BUF+1];
+ int r;
+
+ r = read(fd, buffer, sizeof(buffer)-1);
+ if (r < 0)
+ failmsg("reading from pipe");
+ buffer[r] = '\0';
+
+ if (fnmatch(pattern, buffer, 0) != 0)
+ failmsg("Expected '%s' got '%s'", pattern, buffer);
+}
+
+int main(int argc, char *argv[])
+{
+ int p[2];
+ int stdoutfd;
+
+ setbuf(stdout, 0);
+ printf("1..1\n");
+ stderrfd = dup(STDERR_FILENO);
+ if (stderrfd < 0)
+ err(1, "dup of stderr failed");
+
+ stdoutfd = dup(STDOUT_FILENO);
+ if (stdoutfd < 0)
+ err(1, "dup of stdout failed");
+
+ if (pipe(p) != 0)
+ failmsg("pipe failed");
+
+ if (dup2(p[1], STDERR_FILENO) < 0 || dup2(p[1], STDOUT_FILENO) < 0)
+ failmsg("Duplicating file descriptor");
+
+ plan_tests(10);
+ expect(p[0], "1..10\n");
+
+ ok(1, "msg1");
+ expect(p[0], "ok 1 - msg1\n");
+
+ ok(0, "msg2");
+ expect(p[0], "not ok 2 - msg2\n"
+ "# Failed test (*test/run.c:main() at line 91)\n");
+
+ ok1(true);
+ expect(p[0], "ok 3 - true\n");
+
+ ok1(false);
+ expect(p[0], "not ok 4 - false\n"
+ "# Failed test (*test/run.c:main() at line 98)\n");
+
+ pass("passed");
+ expect(p[0], "ok 5 - passed\n");
+
+ fail("failed");
+ expect(p[0], "not ok 6 - failed\n"
+ "# Failed test (*test/run.c:main() at line 105)\n");
+
+ skip(2, "skipping %s", "test");
+ expect(p[0], "ok 7 # skip skipping test\n"
+ "ok 8 # skip skipping test\n");
+
+ todo_start("todo");
+ ok1(false);
+ expect(p[0], "not ok 9 - false # TODO todo\n"
+ "# Failed (TODO) test (*test/run.c:main() at line 114)\n");
+ ok1(true);
+ expect(p[0], "ok 10 - true # TODO todo\n");
+ todo_end();
+
+ if (exit_status() != 3)
+ failmsg("Expected exit status 3, not %i", exit_status());
+
+#if 0
+ /* Manually run the atexit command. */
+ _cleanup();
+ expect(p[0], "# Looks like you failed 2 tests of 9.\n");
+#endif
+
+ write_all(stdoutfd, "ok 1 - All passed\n",
+ strlen("ok 1 - All passed\n"));
+ exit(0);
+}
diff --git a/test/core/licenses/CC0 b/test/core/licenses/CC0
new file mode 100644
index 0000000..feb9b11
--- /dev/null
+++ b/test/core/licenses/CC0
@@ -0,0 +1,28 @@
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others.
+
+For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following:
+
+ the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work;
+ moral rights retained by the original author(s) and/or performer(s);
+ publicity and privacy rights pertaining to a person's image or likeness depicted in a Work;
+ rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below;
+ rights protecting the extraction, dissemination, use and reuse of data in a Work;
+ database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and
+ other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document.
+ Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law.
+ Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work.
+ Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.
diff --git a/test/core/map-test.c b/test/core/map-test.c
new file mode 100644
index 0000000..7a23d57
--- /dev/null
+++ b/test/core/map-test.c
@@ -0,0 +1,115 @@
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include "tap.h"
+#include "map.h"
+
+static bool get(Map *map, const char *key, const void *data) {
+ return map_get(map, key) == data && map_closest(map, key) == data;
+}
+
+static bool compare(const char *key, void *value, void *data) {
+ Map *map = data;
+ ok(map_get(map, key) == value, "Compare map content");
+ return true;
+}
+
+static bool once(const char *key, void *value, void *data) {
+ int *counter = data;
+ (*counter)++;
+ return false;
+}
+
+static bool visit(const char *key, void *value, void *data) {
+ int *index = value;
+ int *visited = data;
+ visited[*index]++;
+ return true;
+}
+
+static int order_counter;
+
+static bool order(const char *key, void *value, void *data) {
+ int *index = value;
+ int *order = data;
+ order[*index] = ++order_counter;
+ return true;
+}
+
+int main(int argc, char *argv[]) {
+ const char *key = "404";
+ const int values[3] = { 0, 1, 2 };
+
+ plan_no_plan();
+
+ Map *map = map_new();
+
+ ok(map && map_empty(map), "Creation");
+ ok(map_first(map, &key) == NULL && strcmp(key, "404") == 0, "First on empty map");
+ ok(map_empty(map_prefix(map, "404")), "Empty prefix map");
+
+ ok(!map_get(map, "404"), "Get non-existing key");
+ ok(!map_contains(map, "404"), "Contains non-existing key");
+ ok(!map_closest(map, "404") && errno == ENOENT, "Closest non-existing key");
+
+ ok(!map_put(map, "a", NULL) && errno == EINVAL && map_empty(map) && !map_get(map, "a"), "Put NULL value");
+ ok(map_put(map, "a", &values[0]) && !map_empty(map) && get(map, "a", &values[0]), "Put 1");
+ ok(map_first(map, &key) == &values[0] && strcmp(key, "a") == 0, "First on map with 1 value");
+ key = NULL;
+ ok(map_first(map_prefix(map, "a"), &key) == &values[0] && strcmp(key, "a") == 0, "First on prefix map");
+ ok(map_contains(map, "a"), "Contains existing key");
+ ok(map_closest(map, "a") == &values[0], "Closest match existing key");
+ ok(!map_put(map, "a", &values[1]) && errno == EEXIST && get(map, "a", &values[0]), "Put duplicate");
+ ok(map_put(map, "cafebabe", &values[2]) && get(map, "cafebabe", &values[2]), "Put 2");
+ ok(map_put(map, "cafe", &values[1]) && get(map, "cafe", &values[1]), "Put 3");
+ key = NULL;
+ ok(map_first(map_prefix(map, "cafe"), &key) == &values[1] && strcmp(key, "cafe") == 0, "First on prefix map with multiple suffixes");
+
+ Map *copy = map_new();
+ ok(map_copy(copy, map), "Copy");
+ ok(!map_empty(copy), "Not empty after copying");
+ map_iterate(copy, compare, map);
+ map_iterate(map, compare, copy);
+
+ int counter = 0;
+ map_iterate(copy, once, &counter);
+ ok(counter == 1, "Iterate stop condition");
+
+ ok(!map_get(map, "ca") && !map_closest(map, "ca") && errno == 0, "Closest ambigious");
+
+ int visited[] = { 0, 0, 0 };
+
+ map_iterate(map, visit, &visited);
+ ok(visited[0] == 1 && visited[1] == 1 && visited[2] == 1, "Iterate map");
+
+ memset(visited, 0, sizeof visited);
+ order_counter = 0;
+ map_iterate(map, order, &visited);
+ ok(visited[0] == 1 && visited[1] == 2 && visited[2] == 3, "Ordered iteration");
+
+ memset(visited, 0, sizeof visited);
+ map_iterate(map_prefix(map, "ca"), visit, &visited);
+ ok(visited[0] == 0 && visited[1] == 1 && visited[2] == 1, "Iterate sub map");
+
+ memset(visited, 0, sizeof visited);
+ order_counter = 0;
+ map_iterate(map_prefix(map, "ca"), order, &visited);
+ ok(visited[0] == 0 && visited[1] == 1 && visited[2] == 2, "Ordered sub map iteration");
+
+ ok(map_empty(map_prefix(map, "404")), "Empty map for non-existing prefix");
+
+ ok(!map_delete(map, "404"), "Delete non-existing key");
+ ok(map_delete(map, "cafe") == &values[1] && !map_get(map, "cafe"), "Delete existing key");
+ ok(map_closest(map, "cafe") == &values[2], "Closest unambigious");
+ ok(map_put(map, "cafe", &values[1]) && get(map, "cafe", &values[1]), "Put 3 again");
+
+ map_clear(map);
+ ok(map_empty(map), "Empty after clean");
+
+ map_free(map);
+ map_free(copy);
+
+ return exit_status();
+}
diff --git a/test/core/tap.h b/test/core/tap.h
new file mode 100644
index 0000000..b8a7213
--- /dev/null
+++ b/test/core/tap.h
@@ -0,0 +1,58 @@
+#ifndef TAP_H
+#define TAP_H
+
+#ifdef TIS_INTERPRETER
+static int failures = 0;
+static int test_count = 0;
+static void plan_no_plan(void) { }
+
+static int exit_status() {
+ if (failures > 255)
+ failures = 255;
+ return failures;
+}
+
+#define ok(e, ...) do { \
+ bool _e = (e); \
+ printf("%sok %d - ", _e ? "" : "not ", ++test_count); \
+ printf(__VA_ARGS__); \
+ printf("\n"); \
+ if (!_e) { \
+ failures++; \
+ printf(" Failed test (%s:%s() at line %d)\n", __FILE__, __func__, __LINE__); \
+ } \
+} while (0)
+
+#define skip_if(cond, n, ...) \
+ if (cond) skip((n), __VA_ARGS__); \
+ else
+
+#define skip(n, ...) do { \
+ int _n = (n); \
+ while (_n--) { \
+ printf("ok %d # skip ", ++test_count); \
+ printf(__VA_ARGS__); \
+ printf("\n"); \
+ } \
+} while (0)
+
+#include <time.h>
+time_t time(time_t *p)
+{
+ static time_t value;
+ value++;
+ if (p) *p = value;
+ return value;
+}
+
+long labs(long v)
+{
+ return v > 0 ? v : -v;
+}
+
+#else
+#include <ccan/tap/tap.h>
+#define TIS_INTERPRETER 0
+#endif
+
+#endif
diff --git a/test/core/text-test.c b/test/core/text-test.c
new file mode 100644
index 0000000..efb81ef
--- /dev/null
+++ b/test/core/text-test.c
@@ -0,0 +1,403 @@
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "tap.h"
+#include "text.h"
+#include "text-util.h"
+#include "util.h"
+
+#ifndef BUFSIZ
+#define BUFSIZ 1024
+#endif
+
+static bool insert(Text *txt, size_t pos, const char *data) {
+ return text_insert(txt, pos, data, strlen(data));
+}
+
+static bool isempty(Text *txt) {
+ return text_size(txt) == 0;
+}
+
+static bool compare_iterator_forward(Iterator *it, const char *data) {
+ char buf[BUFSIZ] = "", b;
+ while (text_iterator_byte_get(it, &b)) {
+ buf[it->pos] = b;
+ text_iterator_byte_next(it, NULL);
+ }
+ return strcmp(data, buf) == 0;
+}
+
+static bool compare_iterator_backward(Iterator *it, const char *data) {
+ char buf[BUFSIZ] = "", b;
+ while (text_iterator_byte_get(it, &b)) {
+ buf[it->pos] = b;
+ text_iterator_byte_prev(it, NULL);
+ }
+ return strcmp(data, buf) == 0;
+}
+
+static bool compare_iterator_both(Text *txt, const char *data) {
+ Iterator it = text_iterator_get(txt, 0);
+ bool forward = compare_iterator_forward(&it, data);
+ text_iterator_byte_prev(&it, NULL);
+ bool forward_backward = compare_iterator_backward(&it, data);
+ it = text_iterator_get(txt, text_size(txt));
+ bool backward = compare_iterator_backward(&it, data);
+ text_iterator_byte_next(&it, NULL);
+ bool backward_forward = compare_iterator_forward(&it, data);
+ return forward && backward && forward_backward && backward_forward;
+}
+
+static bool compare(Text *txt, const char *data) {
+ char buf[BUFSIZ];
+ size_t len = text_bytes_get(txt, 0, sizeof(buf)-1, buf);
+ buf[len] = '\0';
+ return len == strlen(data) && strcmp(buf, data) == 0 &&
+ compare_iterator_both(txt, data);
+}
+
+static void iterator_find_everywhere(Text *txt, char *data) {
+ size_t len = strlen(data);
+
+ Iterator it = text_iterator_get(txt, 0);
+
+ for (size_t i = 0; i < len; i++) {
+ ok(text_iterator_byte_find_next(&it, data[i]) && it.pos == i && text_iterator_byte_next(&it, NULL) && it.pos == i+1, "Iterator find byte next at current position");
+ }
+ ok(!text_iterator_byte_find_next(&it, data[len-1]) && it.pos == len, "Iterator find byte next at EOF");
+
+ for (size_t i = len; i-- > 0;) {
+ ok(text_iterator_byte_find_prev(&it, data[i]) && it.pos == i, "Iterator find byte prev at current position");
+ }
+ ok(!text_iterator_byte_find_prev(&it, data[0]) && it.pos == 0, "Iterator find byte prev at BOF");
+
+}
+
+static void iterator_find_next(Text *txt, size_t start, char b, size_t match) {
+ Iterator it = text_iterator_get(txt, start);
+ bool found = text_iterator_byte_find_next(&it, b);
+ ok((found && it.pos == match) || (!found && it.pos == text_size(txt)),"Iterator byte find next (start: %zu, match: %zu)", start, match);
+}
+
+static void iterator_find_prev(Text *txt, size_t start, char b, size_t match) {
+ Iterator it = text_iterator_get(txt, start);
+ bool found = text_iterator_byte_find_prev(&it, b);
+ ok((found && it.pos == match) || (!found && it.pos == 0),"Iterator byte find prev (start: %zu, match: %zu)", start, match);
+}
+
+int main(int argc, char *argv[]) {
+ Text *txt;
+
+ plan_no_plan();
+
+ skip_if(TIS_INTERPRETER, 2, "I/O related") {
+
+ const char *filename = "data";
+ unlink(filename);
+
+ enum TextLoadMethod load_method[] = {
+ TEXT_LOAD_AUTO,
+ TEXT_LOAD_READ,
+ TEXT_LOAD_MMAP,
+ };
+
+ for (size_t i = 0; i < LENGTH(load_method); i++) {
+ txt = text_load_method("/", load_method[i]);
+ ok(txt == NULL && errno == EISDIR, "Opening directory (method %zu)", i);
+
+ if (access("/etc/shadow", F_OK) == 0 && access("/etc/shadow", R_OK) != 0) {
+ txt = text_load_method("/etc/shadow", load_method[i]);
+ ok(txt == NULL && errno == EACCES, "Opening file without sufficient permissions (method %zu)", i);
+ }
+ }
+
+ char buf[BUFSIZ] = "Hello World!\n";
+ txt = text_load(NULL);
+ ok(txt && insert(txt, 0, buf) && compare(txt, buf), "Inserting into empty text");
+ ok(txt && text_save(txt, filename), "Text save");
+ text_free(txt);
+
+ for (size_t i = 0; i < LENGTH(load_method); i++) {
+ txt = text_load_method(filename, load_method[i]);
+ ok(txt && compare(txt, buf), "Load text (method %zu)", i);
+ text_free(txt);
+ }
+
+ enum TextSaveMethod save_method[] = {
+ TEXT_SAVE_AUTO,
+ TEXT_SAVE_ATOMIC,
+ TEXT_SAVE_INPLACE,
+ };
+
+ for (size_t l = 0; l < LENGTH(load_method); l++) {
+ for (size_t s = 0; s < LENGTH(save_method); s++) {
+#ifdef __CYGWIN__
+ if (load_method[l] == TEXT_LOAD_MMAP && save_method[s] == TEXT_SAVE_INPLACE)
+ continue;
+#endif
+ snprintf(buf, sizeof buf, "Hello World: (%zu, %zu)\n", l, s);
+ txt = text_load_method(filename, load_method[l]);
+ ok(txt, "Load (%zu, %zu)", l, s);
+ ok(txt && text_delete(txt, 0, text_size(txt)) && isempty(txt), "Empty (%zu, %zu)", l, s);
+ ok(txt && insert(txt, 0, buf) && compare(txt, buf), "Preparing to save (%zu, %zu)", l, s);
+ ok(txt && text_save_method(txt, filename, save_method[s]), "Text save (%zu, %zu)", l, s);
+ text_free(txt);
+
+ txt = text_load(filename);
+ ok(txt && compare(txt, buf), "Verify save (%zu, %zu)", l, s);
+ text_free(txt);
+ }
+ }
+
+ int (*creation[])(const char*, const char*) = { symlink, link };
+ const char *names[] = { "symlink", "hardlink" };
+
+ for (size_t i = 0; i < LENGTH(names); i++) {
+ const char *linkname = names[i];
+ unlink(linkname);
+ ok(creation[i](filename, linkname) == 0, "%s creation", names[i]);
+
+ snprintf(buf, sizeof buf, "%s\n", names[i]);
+ txt = text_load(NULL);
+ ok(txt && insert(txt, 0, buf) && compare(txt, buf), "Preparing %s content", names[i]);
+ ok(txt && text_save(txt, linkname), "Text save %s", names[i]);
+ text_free(txt);
+
+ txt = text_load(linkname);
+ ok(txt && compare(txt, buf), "Load %s", names[i]);
+
+ ok(txt && !text_save_method(txt, linkname, TEXT_SAVE_ATOMIC), "Text save %s atomic", names[i]);
+ text_free(txt);
+ }
+ }
+
+ txt = text_load(NULL);
+ ok(txt != NULL && isempty(txt), "Opening empty file");
+
+ Iterator it = text_iterator_get(txt, 0);
+ ok(text_iterator_valid(&it) && it.pos == 0, "Iterator on empty file");
+ char b = '_';
+ ok(text_iterator_byte_get(&it, &b) && b == '\0', "Read EOF from iterator of empty file");
+ b = '_';
+ ok(!text_iterator_byte_prev(&it, &b) && b == '_' &&
+ !text_iterator_valid(&it), "Moving iterator beyond start of file");
+ ok(!text_iterator_byte_get(&it, &b) && b == '_' &&
+ !text_iterator_valid(&it), "Access iterator beyond start of file");
+ ok(text_iterator_byte_next(&it, &b) && b == '\0' &&
+ text_iterator_valid(&it), "Moving iterator back from beyond start of file");
+ b = '_';
+ ok(text_iterator_byte_get(&it, &b) && b == '\0' &&
+ text_iterator_valid(&it), "Accessing iterator after moving back from beyond start of file");
+ b = '_';
+ ok(!text_iterator_byte_next(&it, &b) && b == '_' &&
+ !text_iterator_valid(&it), "Moving iterator beyond end of file");
+ ok(!text_iterator_byte_get(&it, &b) && b == '_' &&
+ !text_iterator_valid(&it), "Accessing iterator beyond end of file");
+ ok(text_iterator_byte_prev(&it, &b) && b == '\0' &&
+ text_iterator_valid(&it), "Moving iterator back from beyond end of file");
+ b = '_';
+ ok(text_iterator_byte_get(&it, &b) && b == '\0' &&
+ text_iterator_valid(&it), "Accessing iterator after moving back from beyond start of file");
+
+ ok(text_state(txt) > 0, "State on empty file");
+ ok(text_undo(txt) == EPOS && isempty(txt), "Undo on empty file");
+ ok(text_redo(txt) == EPOS && isempty(txt), "Redo on empty file");
+
+ char data[] = "a\nb\nc\n";
+ size_t data_len = strlen(data);
+ ok(insert(txt, 0, data), "Inserting new lines");
+ iterator_find_everywhere(txt, data);
+ iterator_find_next(txt, 0, 'a', 0);
+ iterator_find_next(txt, 0, 'b', 2);
+ iterator_find_next(txt, 0, 'c', 4);
+ iterator_find_next(txt, 0, 'e', EPOS);
+ iterator_find_prev(txt, data_len, 'a', 0);
+ iterator_find_prev(txt, data_len, 'b', 2);
+ iterator_find_prev(txt, data_len, 'c', 4);
+ iterator_find_prev(txt, data_len, 'e', EPOS);
+ ok(text_undo(txt) == 0 && isempty(txt), "Undo to empty document 1");
+
+ ok(insert(txt, 1, "") && isempty(txt), "Inserting empty data");
+ ok(!insert(txt, 1, " ") && isempty(txt), "Inserting with invalid offset");
+
+ /* test cached insertion (i.e. in-place with only one piece) */
+ ok(insert(txt, 0, "3") && compare(txt, "3"), "Inserting into empty document (cached)");
+ ok(insert(txt, 0, "1") && compare(txt, "13"), "Inserting at begin (cached)");
+ ok(insert(txt, 1, "2") && compare(txt, "123"), "Inserting in middle (cached)");
+ ok(insert(txt, text_size(txt), "4") && compare(txt, "1234"), "Inserting at end (cached)");
+
+ ok(text_delete(txt, text_size(txt), 0) && compare(txt, "1234"), "Deleting empty range");
+ ok(!text_delete(txt, text_size(txt), 1) && compare(txt, "1234"), "Deleting invalid offset");
+ ok(!text_delete(txt, 0, text_size(txt)+5) && compare(txt, "1234"), "Deleting invalid range");
+
+ ok(text_undo(txt) == 0 && compare(txt, ""), "Reverting to empty document");
+ ok(text_redo(txt) != EPOS /* == text_size(txt) */ && compare(txt, "1234"), "Restoring previsous content");
+
+ /* test cached deletion (i.e. in-place with only one piece) */
+ ok(text_delete(txt, text_size(txt)-1, 1) && compare(txt, "123"), "Deleting at end (cached)");
+ ok(text_delete(txt, 1, 1) && compare(txt, "13"), "Deleting in middle (cached)");
+ ok(text_delete(txt, 0, 1) && compare(txt, "3"), "Deleting at begin (cached)");
+ ok(text_delete(txt, 0, 1) && compare(txt, ""), "Deleting to empty document (cached)");
+
+ /* test regular insertion (i.e. with multiple pieces) */
+ text_snapshot(txt);
+ ok(insert(txt, 0, "3") && compare(txt, "3"), "Inserting into empty document");
+ text_snapshot(txt);
+ ok(insert(txt, 0, "1") && compare(txt, "13"), "Inserting at begin");
+ text_snapshot(txt);
+ ok(insert(txt, 1, "2") && compare(txt, "123"), "Inserting in between");
+ text_snapshot(txt);
+ ok(insert(txt, text_size(txt), "46") && compare(txt, "12346"), "Inserting at end");
+ text_snapshot(txt);
+ ok(insert(txt, 4, "5") && compare(txt, "123456"), "Inserting in middle");
+ text_snapshot(txt);
+ ok(insert(txt, text_size(txt), "789") && compare(txt, "123456789"), "Inserting at end");
+ text_snapshot(txt);
+ ok(insert(txt, text_size(txt), "0") && compare(txt, "1234567890"), "Inserting at end");
+
+ /* test simple undo / redo oparations */
+ ok(text_undo(txt) != EPOS && compare(txt, "123456789"), "Undo 1");
+ ok(text_undo(txt) != EPOS && compare(txt, "123456"), "Undo 2");
+ ok(text_undo(txt) != EPOS && compare(txt, "12346"), "Undo 3");
+ ok(text_undo(txt) != EPOS && compare(txt, "123"), "Undo 4");
+ ok(text_undo(txt) != EPOS && compare(txt, "13"), "Undo 5");
+ ok(text_undo(txt) != EPOS && compare(txt, "3"), "Undo 6");
+ ok(text_undo(txt) != EPOS && compare(txt, ""), "Undo 7");
+ ok(text_redo(txt) != EPOS && compare(txt, "3"), "Redo 1");
+ ok(text_redo(txt) != EPOS && compare(txt, "13"), "Redo 2");
+ ok(text_redo(txt) != EPOS && compare(txt, "123"), "Redo 3");
+ ok(text_redo(txt) != EPOS && compare(txt, "12346"), "Redo 4");
+ ok(text_redo(txt) != EPOS && compare(txt, "123456"), "Redo 5");
+ ok(text_redo(txt) != EPOS && compare(txt, "123456789"), "Redo 6");
+ ok(text_redo(txt) != EPOS && compare(txt, "1234567890"), "Redo 7");
+ ok(text_earlier(txt) != EPOS && compare(txt, "123456789"), "Earlier 1");
+ ok(text_earlier(txt) != EPOS && compare(txt, "123456"), "Earlier 2");
+ ok(text_earlier(txt) != EPOS && compare(txt, "12346"), "Earlier 3");
+ ok(text_earlier(txt) != EPOS && compare(txt, "123"), "Earlier 4");
+ ok(text_earlier(txt) != EPOS && compare(txt, "13"), "Earlier 5");
+ ok(text_earlier(txt) != EPOS && compare(txt, "3"), "Earlier 6");
+ ok(text_earlier(txt) != EPOS && compare(txt, ""), "Earlier 7");
+ ok(text_later(txt) != EPOS && compare(txt, "3"), "Later 1");
+ ok(text_later(txt) != EPOS && compare(txt, "13"), "Later 2");
+ ok(text_later(txt) != EPOS && compare(txt, "123"), "Later 3");
+ ok(text_later(txt) != EPOS && compare(txt, "12346"), "Later 4");
+ ok(text_later(txt) != EPOS && compare(txt, "123456"), "Later 5");
+ ok(text_later(txt) != EPOS && compare(txt, "123456789"), "Later 6");
+ ok(text_later(txt) != EPOS && compare(txt, "1234567890"), "Later 7");
+
+ /* test regular deletion (i.e. with multiple pieces) */
+ ok(text_delete(txt, 8, 2) && compare(txt, "12345678"), "Deleting midway start");
+ text_undo(txt);
+ ok(text_delete(txt, 2, 6) && compare(txt, "1290"), "Deleting midway end");
+ text_undo(txt);
+ ok(text_delete(txt, 7, 1) && compare(txt, "123456790"), "Deleting midway both same piece");
+ text_undo(txt);
+ ok(text_delete(txt, 0, 5) && compare(txt, "67890"), "Deleting at begin");
+ text_undo(txt);
+ ok(text_delete(txt, 5, 5) && compare(txt, "12345"), "Deleting at end");
+
+ ok(text_mark_get(txt, text_mark_set(txt, -1)) == EPOS, "Mark invalid 1");
+ ok(text_mark_get(txt, text_mark_set(txt, text_size(txt)+1)) == EPOS, "Mark invalid 2");
+
+ const char *chunk = "new content";
+ const size_t delta = strlen(chunk);
+ size_t positions[] = { 0, 1, text_size(txt)/2, text_size(txt)-1 };
+ text_snapshot(txt);
+ for (size_t i = 0; i < LENGTH(positions); i++) {
+ size_t pos = positions[i];
+ Mark bof = text_mark_set(txt, 0);
+ ok(text_mark_get(txt, bof) == 0, "Mark at beginning of file");
+ Mark mof = text_mark_set(txt, pos);
+ ok(text_mark_get(txt, mof) == pos, "Mark in the middle");
+ Mark eof = text_mark_set(txt, text_size(txt));
+ ok(text_mark_get(txt, eof) == text_size(txt), "Mark at end of file");
+ ok(insert(txt, pos, chunk), "Insert before mark");
+ ok(text_mark_get(txt, bof) == ((pos == 0) ? delta : 0), "Mark at beginning adjusted 1");
+ ok(text_mark_get(txt, mof) == pos+delta, "Mark in the middle adjusted 1");
+ ok(text_mark_get(txt, eof) == text_size(txt), "Mark at end adjusted 1");
+ ok(insert(txt, pos+delta+1, chunk), "Insert after mark");
+ ok(text_mark_get(txt, bof) == ((pos == 0) ? delta : 0), "Mark at beginning adjusted 2");
+ ok(text_mark_get(txt, mof) == pos+delta, "Mark in the middle adjusted 2");
+ ok(text_mark_get(txt, eof) == text_size(txt), "Mark at end adjusted 2");
+ text_snapshot(txt);
+ ok(text_delete(txt, pos+delta, 1), "Deleting mark");
+ ok(text_mark_get(txt, mof) == EPOS, "Mark in the middle deleted");
+ text_undo(txt);
+ ok(text_mark_get(txt, mof) == pos+delta, "Mark restored");
+ text_undo(txt);
+ }
+
+ text_snapshot(txt);
+
+ /* Test branching of the revision tree:
+ *
+ * 0 -- 1 -- 2 -- 3
+ * \
+ * `-- 4 -- 5 -- 6 -- 7
+ */
+ typedef struct {
+ time_t state;
+ char data[8];
+ } Rev;
+
+ Rev revs[8];
+ size_t rev = 0;
+
+ for (size_t i = 0; i < LENGTH(revs)/2; i++) {
+ snprintf(revs[i].data, sizeof revs[i].data, "%zu", i);
+ ok(text_delete(txt, 0, text_size(txt)) && text_size(txt) == 0, "Delete everything %zu", i);
+ ok(insert(txt, 0, revs[i].data) && compare(txt, revs[i].data), "Creating state %zu", i);
+ revs[i].state = text_state(txt);
+ text_snapshot(txt);
+ rev = i;
+ }
+
+ for (size_t i = 0; i < LENGTH(revs)/4; i++) {
+ rev--;
+ ok(text_undo(txt) != EPOS && compare(txt, revs[rev].data), "Undo to state %zu", rev);
+ }
+
+ for (size_t i = LENGTH(revs)/2; i < LENGTH(revs); i++) {
+ snprintf(revs[i].data, sizeof revs[i].data, "%zu", i);
+ ok(text_delete(txt, 0, text_size(txt)) && text_size(txt) == 0, "Delete everything %zu", i);
+ ok(insert(txt, 0, revs[i].data) && compare(txt, revs[i].data), "Creating state %zu", i);
+ revs[i].state = text_state(txt);
+ text_snapshot(txt);
+ rev++;
+ }
+
+ while (rev > 0) {
+ text_undo(txt);
+ rev--;
+ }
+
+ ok(compare(txt, revs[0].data), "Undo along main branch to state 0");
+
+ for (size_t i = 1; i < LENGTH(revs); i++) {
+ ok(text_later(txt) != EPOS && compare(txt, revs[i].data), "Advance to state %zu", i);
+ }
+
+ for (size_t i = 0; i < LENGTH(revs); i++) {
+ time_t state = revs[i].state;
+ ok(text_restore(txt, state) != EPOS && text_state(txt) == state, "Restore state %zu", i);
+ }
+
+ for (size_t i = LENGTH(revs)-1; i > 0; i--) {
+ ok(text_earlier(txt) != EPOS && compare(txt, revs[i-1].data), "Revert to state %zu", i-1);
+ }
+
+ for (size_t i = 1; i < LENGTH(revs)/2; i++) {
+ text_redo(txt);
+ }
+
+ rev = LENGTH(revs)/2-1;
+ ok(compare(txt, revs[rev].data), "Redo along main branch to state %zu", rev);
+ ok(text_redo(txt) == EPOS, "End of main branch");
+
+ text_free(txt);
+
+ return exit_status();
+}