diff options
author | Kimplul <kimi.h.kuparinen@gmail.com> | 2025-08-22 15:27:17 +0300 |
---|---|---|
committer | Kimplul <kimi.h.kuparinen@gmail.com> | 2025-08-22 15:27:17 +0300 |
commit | b0d619e2c9595f4ec05463e87be9d0d3423c0a70 (patch) | |
tree | dfcda5da356b660fadb8b133772c926c08908cbb | |
parent | 7774ae2f8c2dca9ab2d93082856f031e78a1b5f0 (diff) | |
download | conts-b0d619e2c9595f4ec05463e87be9d0d3423c0a70.tar.gz conts-b0d619e2c9595f4ec05463e87be9d0d3423c0a70.zip |
use covsrv for coverage testing
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | .gitmodules | 3 | ||||
-rw-r--r-- | Makefile | 20 | ||||
m--------- | deps/covsrv | 0 | ||||
-rw-r--r-- | include/conts/map.h | 38 | ||||
-rw-r--r-- | include/conts/sptree.h | 30 | ||||
-rw-r--r-- | include/conts/vec.h | 75 | ||||
-rwxr-xr-x | scripts/coverage | 34 | ||||
-rwxr-xr-x | scripts/run-test | 31 | ||||
-rw-r--r-- | tests/map.c | 20 | ||||
-rw-r--r-- | tests/sptree.c | 20 | ||||
-rw-r--r-- | tests/test.h | 12 | ||||
-rw-r--r-- | tests/vec.c | 34 |
13 files changed, 277 insertions, 42 deletions
@@ -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 @@ -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); } |