summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKimplul <kimi.h.kuparinen@gmail.com>2025-08-22 15:27:17 +0300
committerKimplul <kimi.h.kuparinen@gmail.com>2025-08-22 15:27:17 +0300
commitb0d619e2c9595f4ec05463e87be9d0d3423c0a70 (patch)
treedfcda5da356b660fadb8b133772c926c08908cbb
parent7774ae2f8c2dca9ab2d93082856f031e78a1b5f0 (diff)
downloadconts-b0d619e2c9595f4ec05463e87be9d0d3423c0a70.tar.gz
conts-b0d619e2c9595f4ec05463e87be9d0d3423c0a70.zip
use covsrv for coverage testing
-rw-r--r--.gitignore2
-rw-r--r--.gitmodules3
-rw-r--r--Makefile20
m---------deps/covsrv0
-rw-r--r--include/conts/map.h38
-rw-r--r--include/conts/sptree.h30
-rw-r--r--include/conts/vec.h75
-rwxr-xr-xscripts/coverage34
-rwxr-xr-xscripts/run-test31
-rw-r--r--tests/map.c20
-rw-r--r--tests/sptree.c20
-rw-r--r--tests/test.h12
-rw-r--r--tests/vec.c34
13 files changed, 277 insertions, 42 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..dae78e5 100644
--- a/Makefile
+++ b/Makefile
@@ -1,20 +1,28 @@
CFLAGS = -g -Wall -Wextra
check: check-vec check-sptree check-map
+# see scripts/coverage for coverage testing
+
check-vec:
mkdir -p build
- $(CC) $(CFLAGS) -Iinclude tests/vec.c -o build/vec
- valgrind -q --error-exitcode=1 ./build/vec
+ $(CC) $(CFLAGS) $(COVERAGEFLAGS) \
+ -Iinclude -Ideps/covsrv/include \
+ deps/covsrv/src/client.c tests/vec.c -o build/vec
+ ./scripts/run-test ./build/vec
check-sptree:
mkdir -p build
- $(CC) $(CFLAGS) -Iinclude tests/sptree.c -o build/sptree
- valgrind -q --error-exitcode=1 ./build/sptree
+ $(CC) $(CFLAGS) $(COVERAGEFLAGS) \
+ -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) \
+ -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
diff --git a/deps/covsrv b/deps/covsrv
new file mode 160000
+Subproject 5ff642c98760e0aec4de6b334a187cb9b4484aa
diff --git a/include/conts/map.h b/include/conts/map.h
index db1e386..e3f0b2f 100644
--- a/include/conts/map.h
+++ b/include/conts/map.h
@@ -18,6 +18,22 @@
#error "Need map name"
#endif
+#ifndef MAP_MALLOC
+#define MAP_MALLOC malloc
+#endif
+
+#ifndef MAP_CALLOC
+#define MAP_CALLOC calloc
+#endif
+
+#ifndef MAP_REALLOC
+#define MAP_REALLOC realloc
+#endif
+
+#ifndef MAP_FREE
+#define MAP_FREE free
+#endif
+
#include <stddef.h>
#include <stdlib.h>
#include <stdbool.h>
@@ -101,9 +117,9 @@ static inline struct MAP_ROOT MAP(create)(size_t count)
static inline void MAP(destroy)(struct MAP_ROOT *root)
{
for (size_t i = 0; i < root->count; ++i)
- free(root->buckets[i]);
+ MAP_FREE(root->buckets[i]);
- free(root->buckets);
+ MAP_FREE(root->buckets);
}
static inline MAP_TYPE *MAP(insert)(struct MAP_ROOT *root, MAP_KEY key, MAP_TYPE data)
@@ -133,11 +149,20 @@ static inline MAP_TYPE *MAP(insert)(struct MAP_ROOT *root, MAP_KEY key, MAP_TYPE
/* 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);
+ struct MAP_BUCKET *bucket = MAP_CALLOC(1, bytes);
+ if (!bucket)
+ return NULL;
+
bucket->size = size;
size_t buckets_bytes = sizeof(struct MAP_BUCKET *) * (root->count + 1);
- root->buckets = realloc(root->buckets, buckets_bytes);
+ struct MAP_BUCKET **new_buckets = MAP_REALLOC(root->buckets, buckets_bytes);
+ if (!new_buckets) {
+ free(bucket);
+ return NULL;
+ }
+
+ root->buckets = new_buckets;
if (root->count != 0)
root->buckets[root->count - 1]->next = bucket;
@@ -248,3 +273,8 @@ static inline size_t MAP(len)(struct MAP_ROOT *root)
#undef MAP_CMP
#undef MAP_HASH
#undef MAP_NAME
+
+#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 8c74604..1464d0f 100644
--- a/include/conts/sptree.h
+++ b/include/conts/sptree.h
@@ -16,6 +16,22 @@
#error "Need sptree name"
#endif
+#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
+
#include "conts.h"
#define SPTREE(a) CONTS_JOIN(SPTREE_NAME, a)
@@ -220,7 +236,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;
@@ -255,7 +271,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;
@@ -403,7 +419,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)
@@ -426,9 +442,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/vec.h b/include/conts/vec.h
index c08fd49..b8dfde9 100644
--- a/include/conts/vec.h
+++ b/include/conts/vec.h
@@ -6,7 +6,24 @@
#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
+
#include <stdbool.h>
+#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
@@ -26,21 +43,15 @@ 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;
@@ -65,15 +76,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)
@@ -82,7 +100,7 @@ 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);
@@ -91,19 +109,25 @@ 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);
}
-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)
@@ -139,3 +163,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..1c4a279
--- /dev/null
+++ b/scripts/coverage
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+# 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
+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
+
+cleanup
+exit 0
diff --git a/scripts/run-test b/scripts/run-test
new file mode 100755
index 0000000..05ddfe1
--- /dev/null
+++ b/scripts/run-test
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+TEST="$1"
+NAME=$(basename "$TEST")
+
+mkdir -p "reports/$NAME"
+
+I=0
+while :; do
+ # if no error happened, consider it a pass
+ if valgrind -q --leak-check=full --error-exitcode=1 \
+ "./$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"
+ 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"
+ exit 1
+done
diff --git a/tests/map.c b/tests/map.c
index 6c42787..a370413 100644
--- a/tests/map.c
+++ b/tests/map.c
@@ -1,20 +1,38 @@
#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_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);
+ if (!ints_insert(&ints, i, i)) {
+ fprintf(stderr, "failed inserting %d\n", i);
+ ints_destroy(&ints);
+ return -1;
+ }
}
assert(ints_len(&ints) == 1000000);
diff --git a/tests/sptree.c b/tests/sptree.c
index b8d1e5a..6ab3d6a 100644
--- a/tests/sptree.c
+++ b/tests/sptree.c
@@ -1,16 +1,34 @@
#include <assert.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>
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);
+ if (!ints_insert(&ints, i)) {
+ fprintf(stderr, "failed inserting %d\n", i);
+ ints_destroy(&ints);
+ return -1;
+ }
}
assert(ints_len(&ints) == 1000000);
diff --git a/tests/test.h b/tests/test.h
new file mode 100644
index 0000000..2fa0879
--- /dev/null
+++ b/tests/test.h
@@ -0,0 +1,12 @@
+#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__)
+
+#endif /* TEST_H */
diff --git a/tests/vec.c b/tests/vec.c
index a84096c..8531b14 100644
--- a/tests/vec.c
+++ b/tests/vec.c
@@ -1,14 +1,33 @@
+#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>
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);
+ if (!ints_append(&ints, i)) {
+ fprintf(stderr, "failed appending %d to vec\n", i);
+ ints_destroy(&ints);
+ return -1;
+ }
}
assert(ints_len(&ints) == 1000000);
@@ -23,6 +42,19 @@ int main()
i++;
}
+ /* TEN million !!1! */
+ if (!ints_reserve(&ints, 10000000)) {
+ 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, 1000000);
+
for (int i = 1000000 - 1; i >= 0; --i) {
ints_remove(&ints, i);
}