summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.gitmodules3
-rw-r--r--Makefile54
m---------deps/covsrv0
-rw-r--r--include/conts/conts.h12
-rw-r--r--include/conts/map.h278
-rw-r--r--include/conts/sptree.h56
-rw-r--r--include/conts/spvec.h232
-rw-r--r--include/conts/vec.h88
-rwxr-xr-xscripts/coverage34
-rwxr-xr-xscripts/run-test40
-rw-r--r--tests/map.c60
-rw-r--r--tests/sptree.c68
-rw-r--r--tests/spvec.c84
-rw-r--r--tests/test.h16
-rw-r--r--tests/vec.c73
16 files changed, 905 insertions, 195 deletions
diff --git a/.gitignore b/.gitignore
index 378eac2..6314f54 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
build
+reports
+coverage
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..c4f7f20
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "deps/covsrv"]
+ path = deps/covsrv
+ url = https://metanimi.dy.fi/cgit/covsrv
diff --git a/Makefile b/Makefile
index f7934d8..716cd5c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,36 +1,66 @@
-CFLAGS = -g -Wall -Wextra
-check: check-vec check-sptree check-map
+CFLAGS = -g -Wall -Wextra -O2
+check: check-vec check-spvec check-sptree check-map
+
+# see scripts/coverage for coverage testing
+TESTITER = 1000
+BENCHITER = 10000000
check-vec:
mkdir -p build
- $(CC) $(CFLAGS) -Iinclude tests/vec.c -o build/vec
- valgrind -q --error-exitcode=1 ./build/vec
+ $(CC) $(CFLAGS) $(COVERAGEFLAGS) -DITER=$(TESTITER) \
+ -Iinclude -Ideps/covsrv/include \
+ deps/covsrv/src/client.c tests/vec.c -o build/vec
+ ./scripts/run-test ./build/vec
+
+check-spvec:
+ mkdir -p build
+ $(CC) $(CFLAGS) $(COVERAGEFLAGS) -DITER=$(TESTITER) \
+ -Iinclude -Ideps/covsrv/include \
+ deps/covsrv/src/client.c tests/spvec.c -o build/spvec
+ ./scripts/run-test ./build/spvec
check-sptree:
mkdir -p build
- $(CC) $(CFLAGS) -Iinclude tests/sptree.c -o build/sptree
- valgrind -q --error-exitcode=1 ./build/sptree
+ $(CC) $(CFLAGS) $(COVERAGEFLAGS) -DITER=$(TESTITER) \
+ -Iinclude -Ideps/covsrv/include \
+ deps/covsrv/src/client.c tests/sptree.c -o build/sptree
+ ./scripts/run-test ./build/sptree
check-map:
mkdir -p build
- $(CC) $(CFLAGS) -Iinclude tests/map.c -o build/map
- valgrind -q --error-exitcode=1 ./build/map
+ $(CC) $(CFLAGS) $(COVERAGEFLAGS) -DITER=$(TESTITER) \
+ -Iinclude -Ideps/covsrv/include \
+ deps/covsrv/src/client.c tests/map.c -o build/map
+ ./scripts/run-test ./build/map
-bench: bench-vec bench-sptree bench-map
+bench: bench-vec bench-spvec bench-sptree bench-map
bench-vec:
mkdir -p build
- $(CC) $(CFLAGS) -O2 -Iinclude tests/vec.c -o build/vec_opt
+ $(CC) $(CFLAGS) -DITER=$(BENCHITER) \
+ -Iinclude -Ideps/covsrv/include \
+ tests/vec.c -o build/vec_opt
time ./build/vec_opt 2> build/vec_bench.txt
+bench-spvec:
+ mkdir -p build
+ $(CC) $(CFLAGS) -DITER=$(BENCHITER)\
+ -Iinclude -Ideps/covsrv/include \
+ tests/spvec.c -o build/spvec_opt
+ time ./build/spvec_opt 2> build/spvec_bench.txt
+
bench-sptree:
mkdir -p build
- $(CC) $(CFLAGS) -O2 -Iinclude tests/sptree.c -o build/sptree_opt
+ $(CC) $(CFLAGS) -DITER=$(BENCHITER) \
+ -Iinclude -Ideps/covsrv/include \
+ tests/sptree.c -o build/sptree_opt
time ./build/sptree_opt 2> build/sptree_bench.txt
bench-map:
mkdir -p build
- $(CC) $(CFLAGS) -O2 -Iinclude tests/map.c -o build/map_opt
+ $(CC) $(CFLAGS) -DITER=$(BENCHITER) \
+ -Iinclude -Ideps/covsrv/include \
+ tests/map.c -o build/map_opt
time ./build/map_opt 2> build/map_bench.txt
clean:
diff --git a/deps/covsrv b/deps/covsrv
new file mode 160000
+Subproject 5ff642c98760e0aec4de6b334a187cb9b4484aa
diff --git a/include/conts/conts.h b/include/conts/conts.h
index d175d30..7f30873 100644
--- a/include/conts/conts.h
+++ b/include/conts/conts.h
@@ -7,16 +7,8 @@
#define CONTAINER_OF(ptr, type, member) \
(type *)((char *)(ptr) - offsetof(type, member))
-#if __STDC_VERSION__ >= 202311UL
-# define CONTS_AUTO auto
-#elif defined(__GNUC__)
-# define CONTS_AUTO __auto_type
-#else
-# warning "iteration won't work with this compiler"
-#endif
-
#define foreach(name, i, s)\
- for (CONTS_AUTO i = CONTS_JOIN(name, begin)(s);\
+ for (CONTS_JOIN(name, iter) i = CONTS_JOIN(name, begin)(s);\
!CONTS_JOIN(name, end)(s, i);\
- i = CONTS_JOIN(name, next)(i))
+ i = CONTS_JOIN(name, next)(s, i))
#endif /* CONTS_H */
diff --git a/include/conts/map.h b/include/conts/map.h
index 6be0694..2060658 100644
--- a/include/conts/map.h
+++ b/include/conts/map.h
@@ -18,11 +18,21 @@
#error "Need map name"
#endif
-#include <stddef.h>
-#include <stdlib.h>
-#include <stdbool.h>
+#ifndef MAP_MALLOC
+#define MAP_MALLOC malloc
+#endif
-#include "conts.h"
+#ifndef MAP_CALLOC
+#define MAP_CALLOC calloc
+#endif
+
+#ifndef MAP_REALLOC
+#define MAP_REALLOC realloc
+#endif
+
+#ifndef MAP_FREE
+#define MAP_FREE free
+#endif
#define MAP(a) CONTS_JOIN(MAP_NAME, a)
@@ -34,6 +44,13 @@
#ifndef CONTS_MAP_H
#define CONTS_MAP_H
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include "conts.h"
+
static inline size_t conts_map_generic_hash(const char *s, size_t l)
{
/* djb2 */
@@ -47,9 +64,11 @@ static inline size_t conts_map_generic_hash(const char *s, size_t l)
#define CONTS_MAP_NO_HASH(a) (a)
#define CONTS_MAP_STR_HASH(a) conts_map_generic_hash(a, strlen(a))
-/* fast modulo pow2 */
-#define CONTS_BUCKET_IDX(hash, pow2, bucket) \
- ((hash) & (((pow2) << bucket) - 1))
+enum conts_map_state {
+ CONTS_MAP_FREE = 0,
+ CONTS_MAP_USED = 1,
+ CONTS_MAP_GRAVE = 2
+};
#endif /* CONTS_MAP_H */
@@ -63,116 +82,140 @@ struct MAP_TUPLE {
MAP_TYPE data;
};
+#define MAP_SPVEC(x) CONTS_JOIN(MAP(spvec), x)
+#define MAP_ITER MAP(iter)
+
+typedef struct {
+ size_t i;
+ struct MAP_TUPLE *t;
+} MAP_ITER;
+
struct MAP_NODE {
- struct MAP_BUCKET *bucket;
- size_t hash;
+ uint32_t used : 2;
+ uint32_t hash : 30;
struct MAP_TUPLE t;
};
-struct MAP_BUCKET {
- size_t size; /* bucket size */
- struct MAP_BUCKET *next;
- struct MAP_NODE nodes[];
-};
+/* use spvec as internal buffer representation */
+#define SPVEC_TYPE struct MAP_NODE
+#define SPVEC_NAME MAP(spvec)
+
+#define SPVEC_MALLOC MAP_MALLOC
+#define SPVEC_CALLOC MAP_CALLOC
+#define SPVEC_REALLOC MAP_REALLOC
+#define SPVEC_FREE MAP_FREE
+
+#include "spvec.h"
struct MAP_ROOT {
- size_t len; /* number of items */
- size_t count; /* bucket count */
- size_t pow2; /* how many nodes in smallest bucket */
- struct MAP_BUCKET **buckets;
+ size_t n; /* number of items */
+ struct MAP(spvec) buf;
};
static inline struct MAP_ROOT MAP(create)(size_t count)
{
- size_t pow2 = 1;
- while (pow2 < count)
- pow2 <<= 1;
-
+ /* ignore count for now */
+ (void)count;
return (struct MAP_ROOT){
- .len = 0,
- .count = 0,
- .pow2 = pow2,
- .buckets = NULL
+ .n = 0,
+ .buf = MAP_SPVEC(create)()
};
}
static inline void MAP(destroy)(struct MAP_ROOT *root)
{
- for (size_t i = 0; i < root->count; ++i)
- free(root->buckets[i]);
-
- free(root->buckets);
+ MAP_SPVEC(destroy)(&root->buf);
}
-static inline MAP_TYPE *MAP(insert)(struct MAP_ROOT *root, MAP_KEY key, MAP_TYPE data)
+static inline MAP_TYPE *MAP(find_hash)(struct MAP_ROOT *root, MAP_KEY key, uint32_t hash)
{
- size_t hash = MAP_HASH(key);
- /* look through buckets in order */
- for (size_t b = 0; b < root->count; ++b) {
- struct MAP_BUCKET *bucket = root->buckets[b];
- size_t idx = CONTS_BUCKET_IDX(hash, root->pow2, b);
-
- struct MAP_NODE *node = &bucket->nodes[idx];
- /* free to use this slot */
- if (!node->bucket) {
- node->bucket = bucket;
- node->hash = hash;
- node->t.data = data;
- node->t.key = key;
- root->len++;
- return &node->t.data;
- }
- /* there already exists a node like this */
- if (node->hash == hash && MAP_CMP(node->t.key, key) == 0)
- return &node->t.data;
+ for (size_t s = MAP_SPVEC(len)(&root->buf); s >= conts_spvec_size(0); s /= 2) {
+ bool base = s == conts_spvec_size(0);
+ size_t range = base ? s : s / 2;
+ size_t offst = base ? 0 : s / 2;
+
+ /* linear probing for now */
+ for (size_t i = 0; i < range; ++i) {
+ assert(__builtin_popcountll(range) == 1);
+ size_t idx = (hash + i) & (range - 1);
+
+ struct MAP_NODE *n = MAP_SPVEC(at)(&root->buf, idx + offst);
+
+ /* unused node means chain must not exist in this range */
+ if (n->used == CONTS_MAP_FREE)
+ break;
+
+ /* not the node we're looking for but we should
+ * keep following the chain*/
+ if (n->hash != hash)
+ continue;
+
+ /* found a match */
+ if (MAP_CMP(n->t.key, key) == 0)
+ return &n->t.data;
+ }
}
- /* no bucket available, create new one */
- size_t size = root->pow2 << root->count;
- size_t bytes = sizeof(struct MAP_BUCKET) + sizeof(struct MAP_NODE) * size;
- struct MAP_BUCKET *bucket = calloc(1, bytes);
- bucket->size = size;
-
- size_t buckets_bytes = sizeof(struct MAP_BUCKET *) * (root->count + 1);
- root->buckets = realloc(root->buckets, buckets_bytes);
- if (root->count != 0)
- root->buckets[root->count - 1]->next = bucket;
-
- root->buckets[root->count] = bucket;
-
- /* populate node */
- size_t idx = CONTS_BUCKET_IDX(hash, root->pow2, root->count);
- struct MAP_NODE *node = &bucket->nodes[idx];
- node->bucket = bucket;
- node->hash = hash;
- node->t.key = key;
- node->t.data = data;
- root->count++;
- root->len++;
- return &node->t.data;
+ return NULL;
}
static inline MAP_TYPE *MAP(find)(struct MAP_ROOT *root, MAP_KEY key)
{
- if (root->len == 0)
- return NULL;
+ uint32_t hash = MAP_HASH(key) & 0x3fffffff;
+ return MAP(find_hash)(root, key, hash);
+}
- size_t hash = MAP_HASH(key);
- for (size_t b = 0; b < root->count; ++b) {
- struct MAP_BUCKET *bucket = root->buckets[b];
- size_t idx = CONTS_BUCKET_IDX(hash, root->pow2, b);
+static inline MAP_TYPE *MAP(insert)(struct MAP_ROOT *root, MAP_KEY key, MAP_TYPE data)
+{
+ /* mask top bit away to get 31 bit hash value as used by the nodes */
+ uint32_t hash = MAP_HASH(key) & 0x3fffffff;
+ MAP_TYPE *exists = MAP(find_hash)(root, key, hash);
+ if (exists)
+ return exists;
+
+ /* initialize */
+ if (root->n == 0) {
+ if (!MAP_SPVEC(reserve)(&root->buf, conts_spvec_size(0)))
+ return NULL;
+ }
- struct MAP_NODE *node = &bucket->nodes[idx];
- if (node->hash != hash)
- continue;
+ /* grow as needed */
+ size_t s = MAP_SPVEC(len)(&root->buf);
+ if (2 * root->n >= s) {
+ s *= 2;
+ if (!MAP_SPVEC(reserve)(&root->buf, s))
+ return NULL;
- if (MAP_CMP(node->t.key, key) != 0)
+ /* reserve should zero out all memory so no need to initialize
+ * any elements */
+ }
+
+ /* there must always be at least one free spot */
+ root->n++;
+ bool base = s == conts_spvec_size(0);
+ size_t range = base ? s : s / 2;
+ size_t offst = base ? 0 : s / 2;
+
+ /* linear probing for now */
+ for (size_t i = 0; i < range; ++i) {
+ assert(__builtin_popcountll(range) == 1);
+ size_t idx = (hash + i) & (range - 1);
+
+ struct MAP_NODE *n = MAP_SPVEC(at)(&root->buf, idx + offst);
+ if (n->used == CONTS_MAP_USED)
continue;
- return &node->t.data;
+ /* grave or free, either way we're taking it */
+ n->used = CONTS_MAP_USED;
+ n->hash = hash;
+ n->t.key = key;
+ n->t.data = data;
+ return &n->t.data;
}
+ /** @todo use __builtin_unreachable or C attributes? */
+ assert(false && "should be unreachable");
return NULL;
}
@@ -180,8 +223,8 @@ static inline void MAP(remove_found)(struct MAP_ROOT *root, MAP_TYPE *data)
{
struct MAP_TUPLE *tuple = CONTAINER_OF(data, struct MAP_TUPLE, data);
struct MAP_NODE *node = CONTAINER_OF(tuple, struct MAP_NODE, t);
- node->bucket = NULL;
- root->len--;
+ node->used = CONTS_MAP_GRAVE;
+ root->n--;
}
static inline void MAP(remove)(struct MAP_ROOT *root, MAP_KEY key)
@@ -193,47 +236,52 @@ static inline void MAP(remove)(struct MAP_ROOT *root, MAP_KEY key)
MAP(remove_found)(root, found);
}
-static inline struct MAP_TUPLE *MAP(find_next)(struct MAP_BUCKET *bucket, struct MAP_TUPLE *tuple)
+static inline MAP_ITER MAP(next)(struct MAP_ROOT *root, MAP_ITER t)
{
- struct MAP_NODE *node = CONTAINER_OF(tuple, struct MAP_NODE, t);
- size_t idx = node - bucket->nodes;
- for (; idx < bucket->size; ++idx) {
- struct MAP_NODE *candidate = &bucket->nodes[idx];
- if (candidate->bucket)
- return &candidate->t;
+ for (size_t i = t.i + 1; i < MAP_SPVEC(len)(&root->buf); ++i) {
+ struct MAP_NODE *n = MAP_SPVEC(at)(&root->buf, i);
+ if (n->used == CONTS_MAP_USED) {
+ return (MAP_ITER){
+ .i = i,
+ .t = &n->t
+ };
+ }
}
- struct MAP_BUCKET *next = bucket->next;
- if (!next)
- return NULL;
-
- return MAP(find_next)(next, &next->nodes[0].t);
+ /* no more elements to search through */
+ return (MAP_ITER){
+ .i = MAP_SPVEC(len)(&root->buf),
+ .t = NULL
+ };
}
-static inline struct MAP_TUPLE *MAP(begin)(struct MAP_ROOT *root)
+static inline MAP_ITER MAP(begin)(struct MAP_ROOT *root)
{
- if (root->len == 0)
- return NULL;
+ if (root->n == 0) {
+ return (MAP_ITER){
+ .i = MAP_SPVEC(len)(&root->buf),
+ .t = NULL
+ };
+ }
- struct MAP_BUCKET *bucket = root->buckets[0];
- return MAP(find_next)(bucket, &bucket->nodes[0].t);
-}
+ struct MAP_NODE *n = MAP_SPVEC(at)(&root->buf, 0);
+ assert(n);
-static inline struct MAP_TUPLE *MAP(next)(struct MAP_TUPLE *t)
-{
- struct MAP_NODE *node = CONTAINER_OF(t, struct MAP_NODE, t);
- return MAP(find_next)(node->bucket, &(node + 1)->t);
+ MAP_ITER t = {.i = 0, .t = &n->t};
+ if (n->used == CONTS_MAP_USED)
+ return t;
+
+ return MAP(next)(root, t);
}
-static inline bool MAP(end)(struct MAP_ROOT *root, struct MAP_TUPLE *t)
+static inline bool MAP(end)(struct MAP_ROOT *root, MAP_ITER t)
{
- (void)root;
- return t == NULL;
+ return t.i >= MAP_SPVEC(len)(&root->buf);
}
static inline size_t MAP(len)(struct MAP_ROOT *root)
{
- return root->len;
+ return root->n;
}
#undef MAP
@@ -246,3 +294,9 @@ static inline size_t MAP(len)(struct MAP_ROOT *root)
#undef MAP_CMP
#undef MAP_HASH
#undef MAP_NAME
+#undef MAP_ITER
+
+#undef MAP_MALLOC
+#undef MAP_CALLOC
+#undef MAP_REALLOC
+#undef MAP_FREE
diff --git a/include/conts/sptree.h b/include/conts/sptree.h
index 0e2c7b1..765dffc 100644
--- a/include/conts/sptree.h
+++ b/include/conts/sptree.h
@@ -1,9 +1,3 @@
-#include <stdint.h>
-#include <assert.h>
-#include <stdlib.h>
-#include <stddef.h>
-#include <stdbool.h>
-
#ifndef SPTREE_TYPE
#error "Need sptree type"
#endif
@@ -16,15 +10,37 @@
#error "Need sptree name"
#endif
-#include "conts.h"
+#ifndef SPTREE_MALLOC
+#define SPTREE_MALLOC malloc
+#endif
+
+#ifndef SPTREE_CALLOC
+#define SPTREE_CALLOC calloc
+#endif
+
+#ifndef SPTREE_REALLOC
+#define SPTREE_REALLOC realloc
+#endif
+
+#ifndef SPTREE_FREE
+#define SPTREE_FREE free
+#endif
#define SPTREE(a) CONTS_JOIN(SPTREE_NAME, a)
#define SPNODE SPTREE(node)
#define SPROOT SPTREE_NAME
-#ifndef SPTREE_H
-#define SPTREE_H
+#ifndef CONTS_SPTREE_H
+#define CONTS_SPTREE_H
+
+#include <stdint.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdbool.h>
+
+#include "conts.h"
#define sp_left(n) ((n)->left)
#define sp_right(n) ((n)->right)
@@ -32,7 +48,7 @@
#define sp_lparen(n) (sp_left(n)->parent)
#define sp_rparen(n) (sp_right(n)->parent)
-#endif /* SPTREE_H */
+#endif /* CONTS_SPTREE_H */
struct SPNODE {
int_fast16_t hint;
@@ -45,6 +61,8 @@ struct SPROOT {
struct SPNODE *root;
};
+typedef SPTREE_TYPE *SPTREE(iter);
+
static inline struct SPROOT SPTREE(create)()
{
return (struct SPROOT){.n = 0, .root = NULL};
@@ -79,8 +97,9 @@ static inline SPTREE_TYPE *SPTREE(begin)(struct SPROOT *s)
return &SPTREE(first)(s->root)->data;
}
-static inline SPTREE_TYPE *SPTREE(next)(SPTREE_TYPE *prev)
+static inline SPTREE_TYPE *SPTREE(next)(struct SPROOT *s, SPTREE_TYPE *prev)
{
+ (void)s;
struct SPNODE *n = CONTAINER_OF(prev, struct SPNODE, data);
if (sp_right(n)) {
n = sp_right(n);
@@ -101,6 +120,7 @@ static inline SPTREE_TYPE *SPTREE(next)(SPTREE_TYPE *prev)
n = p;
}
+ /* god damn it, stop ruining my coverage you bastard */
return NULL;
}
@@ -218,7 +238,7 @@ static inline SPTREE_TYPE *SPTREE(insert)(struct SPROOT *s, SPTREE_TYPE data)
{
if (!s->root) {
assert(s->n == 0);
- struct SPNODE *new = malloc(sizeof(struct SPNODE));
+ struct SPNODE *new = SPTREE_MALLOC(sizeof(struct SPNODE));
if (!new)
return NULL;
@@ -253,7 +273,7 @@ static inline SPTREE_TYPE *SPTREE(insert)(struct SPROOT *s, SPTREE_TYPE data)
return &n->data;
}
- struct SPNODE *new = malloc(sizeof(struct SPNODE));
+ struct SPNODE *new = SPTREE_MALLOC(sizeof(struct SPNODE));
if (!new)
return NULL;
@@ -401,7 +421,7 @@ static inline void SPTREE(free_found)(struct SPROOT *s, SPTREE_TYPE *found)
{
(void)s; /* unused */
struct SPNODE *del = CONTAINER_OF(found, struct SPNODE, data);
- free(del);
+ SPTREE_FREE(del);
}
static inline void SPTREE(remove)(struct SPROOT *s, SPTREE_TYPE data)
@@ -424,9 +444,11 @@ static inline void SPTREE(destroy)(struct SPROOT *s)
}
}
-#undef SPTREE
-#undef SPNODE
-#undef SPROOT
#undef SPTREE_TYPE
#undef SPTREE_NAME
#undef SPTREE_CMP
+
+#undef SPTREE_MALLOC
+#undef SPTREE_CALLOC
+#undef SPTREE_REALLOC
+#undef SPTREE_FREE
diff --git a/include/conts/spvec.h b/include/conts/spvec.h
new file mode 100644
index 0000000..92105b3
--- /dev/null
+++ b/include/conts/spvec.h
@@ -0,0 +1,232 @@
+#ifndef SPVEC_TYPE
+#error "Need vector type"
+#endif
+
+#ifndef SPVEC_NAME
+#error "Need vector name"
+#endif
+
+#ifndef SPVEC_MALLOC
+#define SPVEC_MALLOC malloc
+#endif
+
+#ifndef SPVEC_CALLOC
+#define SPVEC_CALLOC calloc
+#endif
+
+#ifndef SPVEC_REALLOC
+#define SPVEC_REALLOC realloc
+#endif
+
+#ifndef SPVEC_FREE
+#define SPVEC_FREE free
+#endif
+
+#ifndef CONTS_SPVEC_H
+#define CONTS_SPVEC_H
+
+/* common part */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "conts.h"
+
+static inline size_t conts_spvec_bucket(size_t i)
+{
+ if (i < 64)
+ return 0;
+
+ /** @todo fallback for __builtin_clzll, C23 has stdc_leading_zeroes
+ * but might want to go for a simple loop for maximum compatibility? */
+ return (8 * sizeof(unsigned long long)) - __builtin_clzll(i) - 6;
+}
+
+static inline size_t conts_spvec_size(size_t b)
+{
+ /* all buckets are pow2 in size, skipping the lowest 64 */
+ return 1 << (b + 6);
+}
+
+static inline size_t conts_spvec_elemnt(size_t b, size_t i)
+{
+ if (b == 0)
+ return i;
+
+ /* the adjusted index within a bucket is just the size of all previous
+ * buckets subtracted away, and since each bucket is twice the size of
+ * all previous buckets, just check the size of the previous bucket for
+ * the index */
+ return i - conts_spvec_size(b - 1);
+}
+
+#endif /* SPVEC_H */
+
+#define SPVEC(a) CONTS_JOIN(SPVEC_NAME, a)
+
+#define SPVEC_STRUCT SPVEC_NAME
+struct SPVEC_STRUCT {
+ size_t n;
+ /* smallest bucket is of size 64, so ignore 2^6 */
+ SPVEC_TYPE *(buckets[sizeof(size_t) * 8 - 6]);
+};
+
+/* unfortunately I couldn't really think of a better way to implement foreach,
+ * as it's fairly slow to calculate the index from a pointer to an element and a
+ * the foreach wrapper can't really be modified to use separate element pointer
+ * and iterator, they have to be combined. Sorry :( */
+#define SPVEC_ITER SPVEC(iter)
+
+typedef struct {
+ size_t i;
+ SPVEC_TYPE *v;
+} SPVEC_ITER;
+
+
+static inline struct SPVEC_STRUCT SPVEC(create)()
+{
+ /** @todo a bit of a heavy structure to pass by value */
+ struct SPVEC_STRUCT s;
+ memset(&s, 0, sizeof(s));
+ return s;
+}
+
+static inline SPVEC_TYPE *SPVEC(reserve)(struct SPVEC_STRUCT *v, size_t n)
+{
+ if (v->n >= n) {
+ v->n = n;
+ return &v->buckets[0][0];
+ }
+
+ size_t b = conts_spvec_bucket(n);
+ for (size_t i = 0; i <= b; ++i) {
+ if (v->buckets[i])
+ continue;
+
+ v->buckets[i] = SPVEC_CALLOC(
+ conts_spvec_size(i == 0 ? 0 : b - 1),
+ sizeof(SPVEC_TYPE)
+ );
+
+ if (!v->buckets[i])
+ return NULL;
+ }
+
+ v->n = n;
+ return &v->buckets[0][0];
+}
+
+static inline size_t SPVEC(len)(struct SPVEC_STRUCT *v)
+{
+ return v->n;
+}
+
+static inline SPVEC_TYPE *SPVEC(at)(struct SPVEC_STRUCT *v, size_t i)
+{
+ assert(i < v->n && "out of vector bounds");
+ size_t b = conts_spvec_bucket(i);
+ size_t e = conts_spvec_elemnt(b, i);
+ return &v->buckets[b][e];
+}
+
+static inline SPVEC_TYPE *SPVEC(back)(struct SPVEC_STRUCT *v)
+{
+ assert(v->n);
+ return SPVEC(at)(v, v->n - 1);
+}
+
+static inline SPVEC_TYPE *SPVEC(pop)(struct SPVEC_STRUCT *v)
+{
+ assert(v->n && "attempting to pop empty vector");
+ v->n--;
+ return SPVEC(at)(v, v->n);
+}
+
+static inline SPVEC_TYPE *SPVEC(append)(struct SPVEC_STRUCT *v, SPVEC_TYPE n)
+{
+ /* don't update our size until after we've run reserve */
+ size_t ns = v->n + 1;
+ if (!v->buckets[conts_spvec_bucket(ns)]) {
+ /* try to allocate new bucket */
+ if (!SPVEC(reserve)(v, ns))
+ return NULL;
+ }
+
+ /* reserve might've already written our new size so this is kind of
+ * duplicate work, but the code is a bit more readable like this */
+ v->n = ns;
+
+ SPVEC_TYPE *p = SPVEC(at)(v, v->n - 1);
+ *p = n;
+ return p;
+}
+
+static inline void SPVEC(remove)(struct SPVEC_STRUCT *v, size_t i)
+{
+ assert(v->n > i);
+
+ /* very slow :( */
+ for (size_t e = i; e < v->n - 1; ++e)
+ *SPVEC(at)(v, e) = *SPVEC(at)(v, e + 1);
+
+ v->n--;
+}
+
+static inline void SPVEC(reset)(struct SPVEC_STRUCT *v)
+{
+ v->n = 0;
+}
+
+static inline void SPVEC(destroy)(struct SPVEC_STRUCT *v) {
+ for (size_t i = 0; i < 8 * sizeof(size_t) - 6; ++i)
+ SPVEC_FREE(v->buckets[i]);
+}
+
+static inline void SPVEC(shrink)(struct SPVEC_STRUCT *v, size_t n)
+{
+ assert(v->n >= n);
+ v->n = n;
+}
+
+static inline SPVEC_ITER SPVEC(begin)(struct SPVEC_STRUCT *v)
+{
+ if (v->n == 0) {
+ return (SPVEC_ITER) {
+ .i = 0,
+ .v = NULL
+ };
+ }
+
+ return (SPVEC_ITER){
+ .i = 0,
+ .v = SPVEC(at)(v, 0)
+ };
+}
+
+static inline bool SPVEC(end)(struct SPVEC_STRUCT *v, SPVEC_ITER i)
+{
+ (void)v;
+ return i.v == NULL;
+}
+
+static inline SPVEC_ITER SPVEC(next)(struct SPVEC_STRUCT *v, SPVEC_ITER i)
+{
+ return (SPVEC_ITER){
+ .i = i.i + 1,
+ .v = i.i + 1 == v->n ? NULL : SPVEC(at)(v, i.i + 1)
+ };
+}
+
+#undef SPVEC
+#undef SPVEC_TYPE
+#undef SPVEC_NAME
+#undef SPVEC_STRUCT
+#undef SPVEC_ITER
+
+#undef SPVEC_MALLOC
+#undef SPVEC_CALLOC
+#undef SPVEC_REALLOC
+#undef SPVEC_FREE
diff --git a/include/conts/vec.h b/include/conts/vec.h
index 19fd18f..2e93805 100644
--- a/include/conts/vec.h
+++ b/include/conts/vec.h
@@ -6,13 +6,35 @@
#error "Need vector name"
#endif
+#ifndef VEC_MALLOC
+#define VEC_MALLOC malloc
+#endif
+
+#ifndef VEC_CALLOC
+#define VEC_CALLOC calloc
+#endif
+
+#ifndef VEC_REALLOC
+#define VEC_REALLOC realloc
+#endif
+
+#ifndef VEC_FREE
+#define VEC_FREE free
+#endif
+
+#ifndef CONTS_VEC_H
+#define CONTS_VEC_H
+
#include <stdbool.h>
+#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "conts.h"
+#endif /* CONTS_VEC_H */
+
#define VEC(a) CONTS_JOIN(VEC_NAME, a)
#define VEC_STRUCT VEC_NAME
@@ -22,23 +44,19 @@ struct VEC_STRUCT {
VEC_TYPE *buf;
};
+typedef VEC_TYPE *VEC(iter);
+
static inline struct VEC_STRUCT VEC(create)(size_t reserve)
{
- if (reserve == 0)
- return (struct VEC_STRUCT) {.n = 0, .s = 0, .buf = NULL};
-
+ /* first alloc doubles size of s, so divide by two to get wanted
+ * reservation size. Add 1 for odd cases. */
return (struct VEC_STRUCT) {
.n = 0,
- .s = reserve,
- .buf = malloc(reserve * sizeof(VEC_TYPE)),
+ .s = (reserve + 1) / 2,
+ .buf = NULL,
};
}
-static inline bool VEC(uninit)(struct VEC_STRUCT *v)
-{
- return v->buf == NULL;
-}
-
static inline size_t VEC(len)(struct VEC_STRUCT *v)
{
return v->n;
@@ -63,15 +81,22 @@ static inline VEC_TYPE *VEC(pop)(struct VEC_STRUCT *v)
return &v->buf[v->n];
}
-static inline void VEC(append)(struct VEC_STRUCT *v, VEC_TYPE n)
+static inline VEC_TYPE *VEC(append)(struct VEC_STRUCT *v, VEC_TYPE n)
{
v->n++;
- if (v->n >= v->s) {
+ if (v->n >= v->s || !v->buf) {
v->s = v->s == 0 ? 1 : 2 * v->s;
- v->buf = realloc(v->buf, v->s * sizeof(VEC_TYPE));
+ VEC_TYPE *new_buf = VEC_REALLOC(v->buf, v->s * sizeof(VEC_TYPE));
+ if (!new_buf) {
+ v->n--;
+ return NULL;
+ }
+
+ v->buf = new_buf;
}
v->buf[v->n - 1] = n;
+ return &v->buf[v->n - 1];
}
static inline void VEC(reset)(struct VEC_STRUCT *v)
@@ -80,28 +105,35 @@ static inline void VEC(reset)(struct VEC_STRUCT *v)
}
static inline void VEC(destroy)(struct VEC_STRUCT *v) {
- free(v->buf);
+ VEC_FREE(v->buf);
}
typedef int (*VEC(comp_t))(VEC_TYPE *a, VEC_TYPE *b);
static inline void VEC(sort)(struct VEC_STRUCT *v, VEC(comp_t) comp)
{
- qsort(v->buf, v->n, sizeof(VEC_TYPE), (__compar_fn_t)comp);
+ qsort(v->buf, v->n, sizeof(VEC_TYPE),
+ (int(*)(const void *, const void *))comp);
}
-static inline void VEC(reserve)(struct VEC_STRUCT *v, size_t n)
+static inline VEC_TYPE *VEC(reserve)(struct VEC_STRUCT *v, size_t n)
{
- if (v->n >= n)
- return;
+ if (v->s >= n) {
+ v->n = n;
+ return v->buf;
+ }
- v->n = n;
- if (v->s >= v->n)
- return;
+ size_t s = v->s;
+ while (s <= n)
+ s = s == 0 ? 1 : 2 * s;
- while (v->s < v->n)
- v->s = v->s == 0 ? 1 : 2 * v->s;
+ VEC_TYPE *new_buf = VEC_REALLOC(v->buf, s * sizeof(VEC_TYPE));
+ if (!new_buf)
+ return NULL;
- v->buf = realloc(v->buf, v->s * sizeof(VEC_TYPE));
+ v->n = n;
+ v->s = s;
+ v->buf = new_buf;
+ return v->buf;
}
static inline void VEC(shrink)(struct VEC_STRUCT *v, size_t n)
@@ -128,8 +160,9 @@ static inline bool VEC(end)(struct VEC_STRUCT *v, VEC_TYPE *i)
return &v->buf[v->n] == i;
}
-static inline VEC_TYPE *VEC(next)(VEC_TYPE *i)
+static inline VEC_TYPE *VEC(next)(struct VEC_STRUCT *v, VEC_TYPE *i)
{
+ (void)v;
return i + 1;
}
@@ -137,3 +170,8 @@ static inline VEC_TYPE *VEC(next)(VEC_TYPE *i)
#undef VEC_TYPE
#undef VEC_NAME
#undef VEC_STRUCT
+
+#undef VEC_MALLOC
+#undef VEC_CALLOC
+#undef VEC_REALLOC
+#undef VEC_FREE
diff --git a/scripts/coverage b/scripts/coverage
new file mode 100755
index 0000000..6afc069
--- /dev/null
+++ b/scripts/coverage
@@ -0,0 +1,34 @@
+#!/bin/sh
+set -eu
+
+# build covsrv binary
+make -C deps/covsrv
+
+# not super fantastic but most likely good enough
+export COVSRV_SOCKET=$(mktemp -u)
+
+# server program should always be killed at the end of a test run
+cleanup() {
+ ./deps/covsrv/build/covsrv quit
+}
+
+# kill server program even if user interrupted us or something else exceptional
+# happened
+trap interrupt INT HUP TERM QUIT EXIT
+interrupt () {
+ cleanup
+ exit 1
+}
+
+# start coverage server, should create a unix socket at COVSRV_SOCKET that test
+# programs can connect to
+./deps/covsrv/build/covsrv &
+
+# run tests, pass any flags like -j to make
+make COVERAGEFLAGS="--coverage -DCOVERAGE=1" check "$@"
+
+mkdir -p coverage
+lcov --capture --directory . --out coverage/covsrv.info
+genhtml coverage/covsrv.info --out coverage
+
+exit 0
diff --git a/scripts/run-test b/scripts/run-test
new file mode 100755
index 0000000..8f09c45
--- /dev/null
+++ b/scripts/run-test
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+TEST="$1"
+NAME=$(basename "$TEST")
+
+mkdir -p "reports/$NAME"
+
+try_vg() {
+ if which valgrind > /dev/null 2>&1; then
+ valgrind -q --leak-check=full --error-exitcode=1 "$1"
+ else
+ eval "$1"
+ fi
+}
+
+I=0
+while :; do
+ # if no error happened, consider it a pass
+ if try_vg "./${TEST}" > "reports/$NAME/run-$I" 2>&1
+ then
+ echo "$NAME: PASSED"
+ exit 0
+ fi
+
+ if grep 'loss record' "reports/$NAME/run-$I" >/dev/null; then
+ echo "$NAME: MEM FAILED"
+ cat "reports/$NAME/run-$I"
+ exit 1
+ fi
+
+ # an error occured, was it handled properly?
+ if grep 'COVSRV: EXIT' "reports/$NAME/run-$I" >/dev/null; then
+ I=$((I+1))
+ continue
+ fi
+
+ echo "$NAME: FAILED"
+ cat "reports/$NAME/run-$I"
+ exit 1
+done
diff --git a/tests/map.c b/tests/map.c
index 6c42787..9af0b01 100644
--- a/tests/map.c
+++ b/tests/map.c
@@ -1,37 +1,79 @@
#include <assert.h>
#include <stdio.h>
+#include "test.h"
+/* required defs */
#define MAP_KEY int
#define MAP_TYPE int
-#define MAP_HASH(a) CONTS_MAP_NO_HASH(a)
+#define MAP_HASH(a) ints_generic_hash(&(a))
#define MAP_CMP(a, b) ((a) - (b))
#define MAP_NAME ints
+
+/* optional defs */
+#define MAP_MALLOC mallocc
+#define MAP_CALLOC callocc
+#define MAP_REALLOC reallocc
+#define MAP_FREE free
+
#include <conts/map.h>
int main()
{
+#if defined(COVERAGE)
+ assert(!covsrv_init());
+ atexit(covsrv_destroy);
+#endif
+
/* heuristic, but if we know how many elements we'll need, we should
* give it to the create function. */
- struct ints ints = ints_create(0);
- for (int i = 0; i < 1000000; ++i) {
- ints_insert(&ints, i, i);
+ struct ints ints = ints_create(10);
+
+ /* check that trying to search for something in an empty map doesn*t
+ * crash */
+ assert(ints_find(&ints, 0) == NULL);
+
+ /* similarly, iterating an empty map doesn't do anything */
+ foreach(ints, iter, &ints) {
+ assert(false && "iterating empty map");
}
- assert(ints_len(&ints) == 1000000);
- for (int i = 0; i < 1000000; ++i) {
+ for (int i = 0; i < ITER; ++i) {
+ if (!ints_insert(&ints, i, i)) {
+ fprintf(stderr, "failed inserting %d\n", i);
+ ints_destroy(&ints);
+ return -1;
+ }
+ }
+ assert(ints_len(&ints) == ITER);
+
+
+ for (int i = 0; i < ITER; ++i) {
int *v = ints_find(&ints, i);
assert(v && *v == i);
}
+ /* check that trying to find something that doesn't exist doesn't crash */
+ assert(ints_find(&ints, 123456789) == NULL);
+
+ /* check that trying to remove something that doesn't exist is a noop */
+ size_t len = ints_len(&ints);
+ ints_remove(&ints, 123456789);
+ assert(ints_len(&ints) == len);
+
+ /* check that trying to insert 0 again gets us the existing 0 */
+ int *orig = ints_find(&ints, 0);
+ int *new = ints_insert(&ints, 0, 0);
+ assert(orig == new);
+
size_t count = 0;
foreach(ints, iter, &ints) {
- assert(iter->key == iter->data);
+ assert(iter.t->key == iter.t->data);
count++;
}
- assert(count == 1000000);
+ assert(count == ITER);
- for (int i = 0; i < 1000000; ++i) {
+ for (int i = 0; i < ITER; ++i) {
ints_remove(&ints, i);
}
diff --git a/tests/sptree.c b/tests/sptree.c
index b8d1e5a..3499d61 100644
--- a/tests/sptree.c
+++ b/tests/sptree.c
@@ -1,24 +1,57 @@
#include <assert.h>
+#include <stdlib.h>
#include <stdio.h>
+#include "test.h"
+/* required defs */
#define SPTREE_TYPE int
#define SPTREE_CMP(a, b) ((b) - (a))
#define SPTREE_NAME ints
+
+/* optional defs */
+#define SPTREE_MALLOC mallocc
+#define SPTREE_CALLOC callocc
+#define SPTREE_REALLOC reallocc
+#define SPTREE_FREE free
+
#include <conts/sptree.h>
+#define RANDITER (ITER / 100)
+
+int inserted[RANDITER];
+
int main()
{
+#if defined(COVERAGE)
+ assert(!covsrv_init());
+ atexit(covsrv_destroy);
+#endif
+
struct ints ints = ints_create();
- for (int i = 0; i < 1000000; ++i) {
- ints_insert(&ints, i);
+ /* check that iterating an empty tree doesn't do anything */
+ foreach(ints, iter, &ints) {
+ assert(false && "iterating empty tree");
}
- assert(ints_len(&ints) == 1000000);
- for (int i = 0; i < 1000000; ++i) {
+ for (int i = 0; i < ITER; ++i) {
+ if (!ints_insert(&ints, i)) {
+ fprintf(stderr, "failed inserting %d\n", i);
+ ints_destroy(&ints);
+ return -1;
+ }
+ }
+ assert(ints_len(&ints) == ITER);
+
+ for (int i = 0; i < ITER; ++i) {
int *v = ints_find(&ints, i);
assert(v && *v == i);
}
+ /* check that inserting duplicate returns the original */
+ int *orig = ints_find(&ints, 0);
+ ints_insert(&ints, 0);
+ assert(ints_find(&ints, 0) == orig);
+
int i = 0;
foreach(ints, iter, &ints) {
/* since my trees are ordered, this must hold, although you
@@ -28,10 +61,35 @@ int main()
i++;
}
- for (int i = 0; i < 1000000; ++i) {
+ for (int i = 0; i < ITER; ++i) {
ints_remove(&ints, i);
}
assert(ints_len(&ints) == 0);
+
+ /* check that removing nonexistant item (or empty tree) doesn't crash */
+ ints_remove(&ints, 0);
+
+ /* insert random integers to hopefully exercise the code a bit more */
+ srand(0);
+
+ for (int i = 0; i < RANDITER; ++i) {
+ inserted[i] = rand();
+
+ /* covsrv shouldn't fail anymore */
+ assert(ints_insert(&ints, inserted[i]));
+ }
+
+ for (int i = 0; i < RANDITER; ++i) {
+ int *v = ints_find(&ints, inserted[i]);
+ assert(v && *v == inserted[i]);
+ }
+
+ for (int i = 0; i < RANDITER; ++i) {
+ ints_remove(&ints, inserted[i]);
+ }
+
+ assert(ints_len(&ints) == 0);
+
ints_destroy(&ints);
}
diff --git a/tests/spvec.c b/tests/spvec.c
new file mode 100644
index 0000000..b169884
--- /dev/null
+++ b/tests/spvec.c
@@ -0,0 +1,84 @@
+#include <stdio.h>
+#include <assert.h>
+#include "test.h"
+
+/* required defs */
+#define SPVEC_TYPE int
+#define SPVEC_NAME ints
+
+/* optional defs */
+#define SPVEC_MALLOC mallocc
+#define SPVEC_CALLOC callocc
+#define SPVEC_REALLOC reallocc
+#define SPVEC_FREE free
+
+#include <conts/spvec.h>
+
+int main()
+{
+#if defined(COVERAGE)
+ assert(!covsrv_init());
+ atexit(covsrv_destroy);
+#endif
+ struct ints ints = ints_create();
+ foreach(ints, iter, &ints) {
+ assert(false && "iterating empty spvec");
+ }
+
+ /* ensure stability before we do anything else */
+ assert(ints_reserve(&ints, 1));
+ int *p = ints_at(&ints, 0);
+
+ /* should allocate at least a few extra buckets */
+ assert(ints_reserve(&ints, 256));
+ assert(p == ints_at(&ints, 0));
+
+ /* test out resetting as well while we're at it*/
+ assert(ints_len(&ints) == 256);
+ ints_reset(&ints);
+ assert(ints_len(&ints) == 0);
+
+ for (int i = 0; i < ITER; ++i) {
+ if (!ints_append(&ints, i)) {
+ fprintf(stderr, "failed appending %d to vec\n", i);
+ ints_destroy(&ints);
+ return -1;
+ }
+ }
+ assert(ints_len(&ints) == ITER);
+
+ for (int i = 0; i < ITER; ++i) {
+ int *v = ints_at(&ints, i);
+ assert(v && *v == i);
+ }
+
+ int i = 0;
+ foreach(ints, iter, &ints) {
+ assert(iter.v && *iter.v == i);
+ i++;
+ }
+
+ /* TEN million !!1! */
+ if (!ints_reserve(&ints, 10 * ITER)) {
+ fprintf(stderr, "failed reserving vec\n");
+ ints_destroy(&ints);
+ return -1;
+ }
+
+ /* set size back to keep test runtime reasonable
+ * (is shrink necessary when we already have reserve? I
+ * guess it shows intention a bit better and just asserts instead of
+ * maybe fails?) */
+ ints_shrink(&ints, ITER);
+
+ /* so the above is equivalent to */
+ assert(ints_reserve(&ints, ITER));
+ assert(ints_len(&ints) == ITER);
+
+ for (int i = ITER - 1; i >= 0; --i) {
+ ints_remove(&ints, i);
+ }
+ assert(ints_len(&ints) == 0);
+
+ ints_destroy(&ints);
+}
diff --git a/tests/test.h b/tests/test.h
new file mode 100644
index 0000000..ea50db0
--- /dev/null
+++ b/tests/test.h
@@ -0,0 +1,16 @@
+#ifndef TEST_H
+#define TEST_H
+
+#include <covsrv/covsrv.h>
+
+#define cover_ptr(name, ...) ({covsrv_die() ? NULL : name (__VA_ARGS__);})
+
+#define mallocc(...) cover_ptr(malloc, __VA_ARGS__)
+#define callocc(...) cover_ptr(calloc, __VA_ARGS__)
+#define reallocc(...) cover_ptr(realloc, __VA_ARGS__)
+
+#ifndef ITER
+#define ITER 1000000
+#endif
+
+#endif /* TEST_H */
diff --git a/tests/vec.c b/tests/vec.c
index a84096c..09a8deb 100644
--- a/tests/vec.c
+++ b/tests/vec.c
@@ -1,18 +1,47 @@
+#include <stdio.h>
#include <assert.h>
+#include "test.h"
+/* required defs */
#define VEC_TYPE int
#define VEC_NAME ints
+
+/* optional defs */
+#define VEC_MALLOC mallocc
+#define VEC_CALLOC callocc
+#define VEC_REALLOC reallocc
+#define VEC_FREE free
+
#include <conts/vec.h>
+/* used for sorting testing */
+static int int_comp(int *a, int *b)
+{
+ return *a - *b;
+}
+
int main()
{
+#if defined(COVERAGE)
+ assert(!covsrv_init());
+ atexit(covsrv_destroy);
+#endif
+
struct ints ints = ints_create(0);
- for (int i = 0; i < 1000000; ++i) {
- ints_append(&ints, i);
+ foreach(ints, iter, &ints) {
+ assert(false && "iterating empty vec");
}
- assert(ints_len(&ints) == 1000000);
- for (int i = 0; i < 1000000; ++i) {
+ for (int i = 0; i < ITER; ++i) {
+ if (!ints_append(&ints, i)) {
+ fprintf(stderr, "failed appending %d to vec\n", i);
+ ints_destroy(&ints);
+ return -1;
+ }
+ }
+ assert(ints_len(&ints) == ITER);
+
+ for (int i = 0; i < ITER; ++i) {
int *v = ints_at(&ints, i);
assert(v && *v == i);
}
@@ -23,10 +52,44 @@ int main()
i++;
}
- for (int i = 1000000 - 1; i >= 0; --i) {
+ if (!ints_reserve(&ints, 10 * ITER)) {
+ fprintf(stderr, "failed reserving vec\n");
+ ints_destroy(&ints);
+ return -1;
+ }
+
+ /* set size back to keep test runtime reasonable
+ * (is shrink necessary when we already have reserve? I
+ * guess it shows intention a bit better and just asserts instead of
+ * maybe fails?) */
+ ints_shrink(&ints, ITER);
+
+ /* so the above is equivalent to */
+ assert(ints_reserve(&ints, ITER));
+ assert(ints_len(&ints) == ITER);
+
+ for (int i = ITER - 1; i >= 0; --i) {
ints_remove(&ints, i);
}
assert(ints_len(&ints) == 0);
+ /* test out resetting as well */
+ assert(ints_reserve(&ints, 10));
+ assert(ints_len(&ints) == 10);
+
+ ints_reset(&ints);
+ assert(ints_len(&ints) == 0);
+
+ /* try out sorting and special accesses */
+ ints_append(&ints, 3);
+ ints_append(&ints, 2);
+ ints_append(&ints, 1);
+
+ ints_sort(&ints, int_comp);
+
+ assert(*ints_back(&ints) == 3);
+ assert(*ints_pop(&ints) == 3);
+ assert(*ints_back(&ints) == 2);
+
ints_destroy(&ints);
}