include platform.mk
# Targets
-all: info libkresolve kresolved
-install: libkresolve-install kresolved-install
+all: info libkresolve modules kresolved
+install: libkresolve-install modules-install kresolved-install
check: all tests-check
-clean: libkresolve-clean kresolved-clean tests-clean
+clean: libkresolve-clean modules-clean kresolved-clean tests-clean
.PHONY: all install check clean
# Options
include help.mk
include lib/libkresolve.mk
include daemon/kresolved.mk
+include modules/modules.mk
include tests/tests.mk
PREFIX := /usr/local
BINDIR := /bin
LIBDIR := /lib
-INCLUDEDIR = /include
+INCLUDEDIR := /include
+MODULEDIR := $(LIBDIR)/kdns_modules
# Tools
ifndef CC
CC := cc
endif
-CFLAGS += -std=c99 -D_GNU_SOURCE -Wall -fPIC -I$(abspath .) -DPACKAGE_VERSION="\"$(MAJOR).$(MINOR)\""
+CFLAGS += -std=c99 -D_GNU_SOURCE -Wall -fPIC -I$(abspath .)
+CFLAGS += -DPACKAGE_VERSION="\"$(MAJOR).$(MINOR)\"" -DPREFIX="\"$(PREFIX)\"" -DMODULEDIR="\"$(MODULEDIR)\""
RM := rm -f
LN := ln -s
INSTALL := install
/* Open resolution context cache */
worker->resolve.cache = kr_cache_open("/tmp/kresolved", mm, CACHE_DEFAULT_SIZE);
if (worker->resolve.cache == NULL) {
- fprintf(stderr, "Cache directory '/tmp/kresolved' not exists, exitting.\n");
+ fprintf(stderr, "Cache directory '/tmp/kresolved' not exists, exiting.\n");
kr_context_deinit(&worker->resolve);
return KNOT_ERROR;
}
+ /* Load basic modules */
+ kr_context_register(&worker->resolve, "iterate");
+ kr_context_register(&worker->resolve, "itercache");
+ kr_context_register(&worker->resolve, "hints");
+
return KNOT_EOK;
}
$(info BINDIR: $(BINDIR))
$(info LIBDIR: $(LIBDIR))
$(info INCLUDEDIR: $(INCLUDEDIR))
+ $(info MODULEDIR: $(MODULEDIR))
$(info )
$(info Features)
$(info --------)
*/
#include <string.h>
+#include <limits.h>
#include <libknot/errcode.h>
#include <libknot/internal/sockaddr.h>
+#include <libknot/internal/mem.h>
+
#include "lib/context.h"
+#include "lib/defines.h"
#include "lib/rplan.h"
int kr_context_init(struct kr_context *ctx, mm_ctx_t *mm)
kr_cache_close(ctx->cache);
}
+ for (size_t i = 0; i < ctx->mod_loaded; ++i) {
+ kr_module_unload(&ctx->modules[i]);
+ }
+
return KNOT_EOK;
}
+
+int kr_context_register(struct kr_context *ctx, const char *module_name)
+{
+ size_t last = ctx->mod_loaded;
+ int ret = mreserve((char **) &ctx->modules, sizeof(struct kr_module),
+ last + 1, 0, &ctx->mod_reserved);
+ if (ret < 0) {
+ return kr_error(ENOMEM);
+ }
+
+ struct kr_module *mod = &ctx->modules[last];
+ ret = kr_module_load(mod, module_name, NULL);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ctx->mod_loaded += 1;
+ return kr_ok();
+}
#include <libknot/internal/mempattern.h>
#include <libknot/internal/lists.h>
+#include "lib/module.h"
#include "lib/cache.h"
/*!
*/
struct kr_context
{
- struct kr_cache *cache;
- list_t layers;
- unsigned options;
- mm_ctx_t *pool;
+ mm_ctx_t *pool;
+ struct kr_cache *cache;
+ struct kr_module *modules;
+ size_t mod_loaded;
+ size_t mod_reserved;
+ uint32_t options;
};
/*!
* \return KNOT_E*
*/
int kr_context_deinit(struct kr_context *ctx);
+
+/*!
+ * \brief Register module to context.
+ * \param ctx context
+ * \param module_name
+ * \return KNOT_E*
+ */
+int kr_context_register(struct kr_context *ctx, const char *module_name);
#pragma once
+#include <errno.h>
#include <libknot/errcode.h>
#include <libknot/dname.h>
#include <libknot/rrset.h>
+/*
+ * Error codes.
+ */
+#define kr_ok() 0
+#define kr_error(x) -abs(x)
+#define kr_strerror(x) strerror(abs(x))
+
/*
* Connection limits.
*/
#define KR_DNS_PORT 53
#define KR_DNAME_ROOT ((const knot_dname_t*)"")
#define KR_EDNS_VERSION 0
-#define KR_EDNS_PAYLOAD 4096
+#define KR_EDNS_PAYLOAD 4096
\ No newline at end of file
#include "lib/rplan.h"
#include "lib/defines.h"
#include "lib/nsrep.h"
+#include "lib/module.h"
#define DEBUG_MSG(fmt...) QRDEBUG(kr_rplan_current(param->rplan), "iter", fmt)
}
/*! \brief Module implementation. */
-static const knot_layer_api_t LAYER_ITERATE_MODULE = {
- &begin,
- &reset,
- &finish,
- &resolve,
- &prepare_query,
- NULL
-};
-
-const knot_layer_api_t *layer_iterate_module(void)
+const knot_layer_api_t *iterate_layer(void)
{
- return &LAYER_ITERATE_MODULE;
-}
+ static const knot_layer_api_t _layer = {
+ .begin = &begin,
+ .reset = &reset,
+ .finish = &finish,
+ .in = &resolve,
+ .out = &prepare_query
+ };
+ return &_layer;
+}
\ No newline at end of file
#include "lib/layer.h"
/* Processing module implementation. */
-const knot_layer_api_t *layer_iterate_module(void);
-#define LAYER_ITERATE layer_iterate_module()
+extern const knot_layer_api_t *iterate_layer(void);
/*!
* \brief Result updates the query parent.
#include <libknot/internal/mempool.h>
#include <libknot/rrtype/rdname.h>
-#include "lib/layer/static.h"
#include "lib/layer/iterate.h"
+#include "lib/module.h"
#define DEBUG_MSG(fmt...) QRDEBUG(kr_rplan_current(param->rplan), " cc ", fmt)
}
/*! \brief Module implementation. */
-static const knot_layer_api_t LAYER_ITERCACHE_MODULE = {
- &begin,
- NULL,
- NULL,
- &write_cache,
- &read_cache,
- NULL
-};
-
-const knot_layer_api_t *layer_itercache_module(void)
+const knot_layer_api_t *itercache_layer(void)
{
- return &LAYER_ITERCACHE_MODULE;
+ static const knot_layer_api_t _layer = {
+ .begin = &begin,
+ .in = &write_cache,
+ .out = &read_cache
+ };
+
+ return &_layer;
}
#include "lib/layer.h"
/* Processing module implementation. */
-const knot_layer_api_t *layer_itercache_module(void);
-#define LAYER_ITERCACHE layer_itercache_module()
+extern const knot_layer_api_t *itercache_layer(void);
+++ /dev/null
-/* Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <libknot/internal/print.h>
-
-#include "lib/layer/stats.h"
-#include "lib/rplan.h"
-
-#define DEBUG_MSG(fmt...) QRDEBUG(NULL, "stat", fmt)
-
-//static void update_stats(struct kr_ns *ns, double rtt)
-//{
-// /* Knuth, TAOCP, p.232 (Welford running variance/mean). */
-// double d_mean = (rtt - ns->stat.M);
-// ns->stat.n += 1;
-// ns->stat.M += d_mean / ns->stat.n;
-// ns->stat.S += d_mean * (rtt - ns->stat.M);
-//}
-
-static int begin(knot_layer_t *ctx, void *param)
-{
- ctx->data = param;
- return ctx->state;
-}
-
-static int finish(knot_layer_t *ctx)
-{
-#ifndef NDEBUG
- struct kr_layer_param *param = ctx->data;
- struct kr_rplan *rplan = param->rplan;
- const knot_pkt_t *answer = param->answer;
-
- /* Calculate total RTT and number of queries. */
- double total_rtt = 0.0;
- size_t nr_queries = list_size(&rplan->resolved);
- if (nr_queries > 0) {
- struct kr_query *query_first = HEAD(rplan->resolved);
- struct timeval t_end;
- gettimeofday(&t_end, NULL);
- total_rtt = time_diff(&query_first->timestamp, &t_end);
- }
-
- lookup_table_t *rcode = lookup_by_id(knot_rcode_names, knot_wire_get_rcode(answer->wire));
- DEBUG_MSG("result => %s [%u records]\n", rcode ? rcode->name : "??", answer->rrset_count);
- DEBUG_MSG("rtt => %.02lf [ms]\n", total_rtt);
-#endif
-
- return ctx->state;
-}
-
-/*! \brief Module implementation. */
-static const knot_layer_api_t LAYER_STATS_MODULE = {
- &begin,
- NULL,
- &finish,
- NULL,
- NULL,
- NULL
-};
-
-const knot_layer_api_t *layer_stats_module(void)
-{
- return &LAYER_STATS_MODULE;
-}
libkresolve_SOURCES := \
lib/layer/iterate.c \
lib/layer/itercache.c \
- lib/layer/static.c \
- lib/layer/stats.c \
+ lib/utils.c \
lib/nsrep.c \
+ lib/module.c \
lib/context.c \
lib/resolve.c \
lib/zonecut.c \
lib/cache.c
libkresolve_HEADERS := \
- lib/layer/iterate.h \
- lib/layer/itercache.h \
- lib/layer/static.h \
- lib/layer/stats.h \
lib/layer.h \
+ lib/utils.h \
lib/nsrep.h \
+ lib/module.h \
lib/context.h \
lib/resolve.h \
lib/zonecut.h \
--- /dev/null
+#include <dlfcn.h>
+
+#include "lib/defines.h"
+#include "lib/utils.h"
+#include "lib/module.h"
+
+/*! \brief Library extension. */
+static inline const char *library_ext(void)
+{
+#if defined(__APPLE__)
+ return ".dylib";
+#elif _WIN32
+ return ".lib";
+#else
+ return ".so";
+#endif
+}
+
+static void *load_symbol(void *lib, const char *prefix, const char *name)
+{
+ auto_free char *symbol = kr_strcatdup(3, prefix, "_", name);
+ return dlsym(lib, symbol);
+}
+
+static int load_library(struct kr_module *module, const char *name, const char *path)
+{
+ if (path == NULL) {
+ return kr_error(EINVAL);
+ }
+
+ auto_free char *lib_path = kr_strcatdup(4, path, "/", name, library_ext());
+ if (lib_path == NULL) {
+ return kr_error(ENOMEM);
+ }
+
+ module->lib = dlopen(lib_path, RTLD_LAZY);
+ if (module->lib) {
+ return kr_ok();
+ }
+
+ return kr_error(ENOENT);
+}
+
+int kr_module_load(struct kr_module *module, const char *name, const char *path)
+{
+ if (load_library(module, name, path) != 0) {
+ if (load_library(module, name, "~/.local/" MODULEDIR) != 0) {
+ if (load_library(module, name, PREFIX MODULEDIR) != 0) {
+ }
+ }
+ }
+
+ /* It's okay if it fails, then current exec space is searched. */
+ if (module->lib == NULL) {
+ module->lib = RTLD_DEFAULT;
+ }
+
+ /* Load all symbols. */
+ module_api_cb *module_api = NULL;
+ *(void **) (&module_api) = load_symbol(module->lib, name, "api");
+ *(void **) (&module->init) = load_symbol(module->lib, name, "init");
+ *(void **) (&module->deinit) = load_symbol(module->lib, name, "deinit");
+ *(void **) (&module->config) = load_symbol(module->lib, name, "config");
+ *(void **) (&module->layer) = load_symbol(module->lib, name, "layer");
+
+ /* Check module API version (if declared). */
+ if (module_api && module_api() > KR_MODULE_API) {
+ return kr_error(ENOTSUP);
+ }
+
+ /* Initialize module */
+ if (module->init) {
+ return module->init(module);
+ }
+
+ return kr_ok();
+}
+
+void kr_module_unload(struct kr_module *module)
+{
+ if (module->deinit) {
+ module->deinit(module);
+ }
+
+ if (module->lib && module->lib != RTLD_DEFAULT) {
+ dlclose(module->lib);
+ }
+}
\ No newline at end of file
--- /dev/null
+/* Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <libknot/processing/layer.h>
+
+/*
+ * Forward decls
+ */
+struct kr_module;
+
+/*
+ * API definition.
+ */
+#define KR_MODULE_API 0x20150401 /*!< API version */
+typedef uint32_t (module_api_cb)(void);
+typedef int (module_init_cb)(struct kr_module *);
+typedef int (module_deinit_cb)(struct kr_module *);
+typedef int (module_config_cb)(struct kr_module *, void *);
+typedef const knot_layer_api_t* (module_layer_cb)(void);
+
+/*! Loaded module representation. */
+struct kr_module {
+ module_init_cb *init; /*!< Constructor */
+ module_deinit_cb *deinit; /*!< Destructor */
+ module_config_cb *config; /*!< Configuration */
+ module_layer_cb *layer; /*!< Layer getter */
+ void *lib; /*!< Shared library handle or RTLD_DEFAULT */
+ void *data; /*!< Custom data context. */
+};
+
+/*! Load module instance into memory.
+ * @param module module structure
+ * @param name module name
+ * @param path module search path
+ * @return 0 or an error
+ */
+int kr_module_load(struct kr_module *module, const char *name, const char *path);
+
+/*! Unload module instance.
+ * @param module module structure
+ */
+void kr_module_unload(struct kr_module *module);
+
+/*! Export module API version (place this at the end of your module).
+ * @param module module name (f.e. hints)
+ */
+#define KR_MODULE_EXPORT(module) \
+ uint32_t module ## _api() { return KR_MODULE_API; }
\ No newline at end of file
#include "lib/defines.h"
#include "lib/layer/itercache.h"
#include "lib/layer/iterate.h"
-#include "lib/layer/static.h"
-#include "lib/layer/stats.h"
#define DEBUG_MSG(fmt...) QRDEBUG(kr_rplan_current(param->rplan), "resl", fmt)
return ret;
}
+static void prepare_layers(struct knot_requestor *req, struct kr_layer_param *param)
+{
+ struct kr_context *ctx = param->ctx;
+ for (size_t i = 0; i < ctx->mod_loaded; ++i) {
+ struct kr_module *mod = &ctx->modules[i];
+ if (mod->layer) {
+ knot_requestor_overlay(req, mod->layer(), param);
+ }
+ }
+}
+
static int resolve_iterative(struct kr_layer_param *param, mm_ctx_t *pool)
{
- /* Initialize requestor and overlay. */
+ /* Initialize requestor. */
struct knot_requestor requestor;
knot_requestor_init(&requestor, pool);
- knot_requestor_overlay(&requestor, LAYER_STATIC, param);
- knot_requestor_overlay(&requestor, LAYER_ITERCACHE, param);
- knot_requestor_overlay(&requestor, LAYER_ITERATE, param);
- knot_requestor_overlay(&requestor, LAYER_STATS, param);
+ prepare_layers(&requestor, param);
/* Iteratively solve the query. */
int ret = KNOT_EOK;
--- /dev/null
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "lib/defines.h"
+#include "lib/utils.h"
+
+/*
+ * Macros.
+ */
+#define strlen_safe(x) ((x) ? strlen(x) : 0)
+
+/*
+ * Cleanup callbacks.
+ */
+void _cleanup_free(char **p)
+{
+ free(*p);
+}
+
+void _cleanup_close(int *p)
+{
+ if (*p > 0) close(*p);
+}
+
+char* kr_strcatdup(unsigned n, ...)
+{
+ /* Calculate total length */
+ size_t total_len = 0;
+ va_list vl;
+ va_start(vl, n);
+ for (unsigned i = 0; i < n; ++i) {
+ char *item = va_arg(vl, char *);
+ total_len += strlen_safe(item);
+ }
+ va_end(vl);
+
+ /* Allocate result and fill */
+ char *result = NULL;
+ if (total_len > 0) {
+ result = malloc(total_len + 1);
+ }
+ if (result) {
+ char *stream = result;
+ va_start(vl, n);
+ for (unsigned i = 0; i < n; ++i) {
+ char *item = va_arg(vl, char *);
+ if (item) {
+ size_t len = strlen(item);
+ memcpy(stream, item, len + 1);
+ stream += len;
+ }
+ }
+ va_end(vl);
+ }
+
+ return result;
+}
\ No newline at end of file
#pragma once
-#include "lib/layer.h"
+/*
+ * General-purpose attributes.
+ */
+#define auto_free __attribute__((cleanup(_cleanup_free)))
+extern void _cleanup_free(char **p);
+#define auto_close __attribute__((cleanup(_cleanup_close)))
+extern void _cleanup_close(int *p);
+
+/*
+ * Defines.
+ */
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(*(x)))
-/* Processing module implementation. */
-const knot_layer_api_t *layer_static_module(void);
-#define LAYER_STATIC layer_static_module()
+/*! \brief Concatenate N strings. */
+char* kr_strcatdup(unsigned n, ...);
--- /dev/null
+package gostats
+
+/*
+#include "lib/layer.h"
+#include "lib/module.h"
+extern int gostats_begin(knot_layer_t *, void *);
+extern int gostats_finish(knot_layer_t *);
+static inline const knot_layer_api_t *_layer(void)
+{
+ static const knot_layer_api_t _module = {
+ .begin = &gostats_begin,
+ .finish = &gostats_finish
+ };
+ return &_module;
+}
+*/
+import "C"
+import "unsafe"
+import "fmt"
+
+//export gostats_begin
+func gostats_begin(ctx *C.knot_layer_t, param unsafe.Pointer) C.int {
+ fmt.Println("go_begin()")
+ return 0
+}
+
+//export gostats_finish
+func gostats_finish(ctx *C.knot_layer_t) C.int {
+ fmt.Println("go_finish()")
+ return 0
+}
+
+//export gostats_layer
+func gostats_layer() *C.knot_layer_api_t {
+ return C._layer()
+}
\ No newline at end of file
--- /dev/null
+hints_SOURCES := modules/gostats/gostats.g
+gostats_DEPEND := libkresolve
+gostats_LIBS := $(libkresolve_TARGET) $(libkresolve_LIBS)
+$(call make_go_module,gostats)
\ No newline at end of file
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include "lib/layer/static.h"
+#include "lib/module.h"
+#include "lib/layer.h"
#define DEBUG_MSG(fmt...) QRDEBUG(NULL, "hint", fmt)
-static int reset(knot_layer_t *ctx)
+static int begin(knot_layer_t *ctx, void *module_param)
{
- /* TODO: sync, cleanup after resolution */
+ ctx->data = module_param;
return ctx->state;
}
-static int begin(knot_layer_t *ctx, void *param)
+static int query(knot_layer_t *ctx, knot_pkt_t *pkt)
{
- ctx->data = param;
+ return ctx->state;
+}
- /* TODO: read static hosts file */
+/*
+ * Module implementation.
+ */
- return ctx->state;
+const knot_layer_api_t *hints_layer(void)
+{
+ static const knot_layer_api_t _layer = {
+ .begin = &begin,
+ .out = &query
+ };
+ return &_layer;
}
-/*! \brief Module implementation. */
-static const knot_layer_api_t LAYER_STATIC_MODULE = {
- &begin,
- &reset,
- NULL,
- NULL,
- NULL,
- NULL
-};
-
-const knot_layer_api_t *layer_static_module(void)
+int hints_init(struct kr_module *module)
{
- return &LAYER_STATIC_MODULE;
+ return 0;
}
+
+int hints_deinit(struct kr_module *module)
+{
+ return 0;
+}
+
+KR_MODULE_EXPORT(hints);
--- /dev/null
+hints_SOURCES := modules/hints/hints.c
+hints_DEPEND := libkresolve
+hints_LIBS := $(libkresolve_TARGET) $(libkresolve_LIBS)
+$(call make_c_module,hints)
\ No newline at end of file
--- /dev/null
+# List of built-in modules
+modules_TARGETS := hints
+
+# Make C module
+define make_c_module
+$(eval $(call make_module,$(1),modules/$(1)))
+endef
+
+# Make Go module
+define make_go_module
+# TODO: compilable only with gccgo -shared
+# go tool cgo -- $(CFLAGS) $$($(1)_SOURCES)
+endef
+
+# Build rules
+modules: $(modules_TARGETS)
+modules-clean: $(addsuffix -clean,$(modules_TARGETS))
+modules-install: $(addsuffix -install,$(modules_TARGETS))
+$(foreach module,$(modules_TARGETS),$(eval include modules/$(module)/$(module).mk))
\ No newline at end of file
$(1)-clean:
$(RM) $$($(1)_OBJ) $$($(1)_DEP) $(2)/$(1)$(3)
$(1)-install: $(2)/$(1)$(3)
- $(INSTALL) $$^ $(PREFIX)/$(5)
+ $(INSTALL) -d $(PREFIX)/$(5)
+ $(INSTALL) $$^ $(PREFIX)/$(5)
ifneq ($$(strip $$($(1)_HEADERS)),)
$(INSTALL) -d $(PREFIX)/$(INCLUDEDIR)/$(1)
$(INSTALL) $$($(1)_HEADERS) $(PREFIX)/$(INCLUDEDIR)/$(1)
# Make targets (name,path)
make_bin = $(call make_target,$(1),$(2),$(BINEXT),,$(BINDIR))
make_lib = $(call make_target,$(1),$(2),$(LIBEXT),-$(LIBTYPE),$(LIBDIR))
-make_module = $(call make_target,$(1),$(2),$(MODEXT),-$(MODTYPE),$(LIBDIR))
+make_module = $(call make_target,$(1),$(2),$(LIBEXT),-$(LIBTYPE),$(MODULEDIR))
+make_shared = $(call make_target,$(1),$(2),$(MODEXT),-$(MODTYPE),$(LIBDIR))
# Evaluate library
define have_lib
#define CACHE_SIZE 100*1024
test_mm_ctx_init(&global_mm);
kr_context_init(&global_context, &global_mm);
+ kr_context_register(&global_context, "iterate");
+ kr_context_register(&global_context, "itercache");
global_tmpdir = test_tmpdir_create();
assert(global_tmpdir);
global_context.cache = kr_cache_open(global_tmpdir, &global_mm, CACHE_SIZE);
-/* Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+/* Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#pragma once
+#include "tests/test.h"
+#include <cmocka.h>
-#include "lib/layer.h"
+#include "lib/utils.h"
-/* Processing module implementation. */
-const knot_layer_api_t *layer_stats_module(void);
-#define LAYER_STATS layer_stats_module()
+static void test_strcatdup(void **state)
+{
+ auto_free char *empty_res = kr_strcatdup(0);
+ assert_null(empty_res);
+
+ auto_free char *null_res = kr_strcatdup(1, NULL);
+ assert_null(null_res);
+
+ auto_free char *nullcat_res = kr_strcatdup(2, NULL, "beef");
+ assert_string_equal(nullcat_res, "beef");
+
+ auto_free char *multi_res = kr_strcatdup(3, "need", "beef", "dead");
+ assert_string_equal(multi_res, "needbeefdead");
+}
+
+int main(void)
+{
+ const UnitTest tests[] = {
+ unit_test(test_strcatdup),
+ };
+
+ return run_tests(tests);
+}
#
tests_BIN := \
+ test_utils \
test_context \
test_rplan \
test_cache \
_test_integration_SOURCES := tests/test_integration.c
_test_integration_LIBS := -Wl,-rpath,tests -Ltests -lmock_calls $(libmock_calls_LIBS)
_test_integration_DEPEND := libmock_calls
-$(eval $(call make_module,_test_integration,tests))
+$(eval $(call make_shared,_test_integration,tests))
# Preload mock library
ifeq ($(PLATFORM),Darwin)