diff --git a/gitlab-ci/functional-test.sh b/gitlab-ci/functional-test.sh
new file mode 100755
index 0000000000000000000000000000000000000000..7b7ead977bafb9b1de62821cd5724a68757e696f
--- /dev/null
+++ b/gitlab-ci/functional-test.sh
@@ -0,0 +1,74 @@
+#!/bin/bash
+
+. /usr/share/libubox/jshn.sh
+. /usr/bin/tap.sh
+
+cntlrlog="/tmp/cntlr.test.log"
+agentlog="/tmp/agent.test.log"
+
+echo "preparation script"
+pwd
+
+echo "Cleaning..."
+make clean
+make -C test/cmocka clean
+
+make -C src
+
+supervisorctl status all
+supervisorctl update
+supervisorctl restart ubusd wifimngr ieee1905d topologyd mapagent mapcontroller
+sleep 2
+supervisorctl status all
+
+echo "Running the unit test cases, pwd ${LIB_DIR}"
+#ret=$?
+
+supervisorctl stop ubusd ieee1905d wifimngr topologyd mapagent mapcontroller
+supervisorctl status all
+
+tap_validate_md5sum() {
+    sha1=$(md5sum "$1" | cut -d' ' -f1)
+    sha2=$(md5sum "$2" | cut -d' ' -f1)
+
+    tap_is_str "$sha1" "$sha2" "ubus call $3 $4 $5"
+}
+
+ubus_invoke() {
+    object=$1
+    method=$2
+    args=$3
+
+    ubus call $object $method $args
+}
+
+json_load "$(cat test/api/json/mapcontroller.validation.json)"
+json_dump
+json_get_var object object
+json_get_keys methods methods
+json_select methods
+
+for i in $methods; do
+    json_select "$i"
+    json_get_var method method
+    json_get_var args args
+
+    ubus_invoke $object $method $args
+    tap_validate_md5sum "$agentlog" "$cntlrlog" "$object" "$method" "$args"
+
+    echo "" > "$cntlrlog"
+    echo "" > "$agentlog"
+    json_select ..
+done
+
+tap_done_testing
+tap_finish
+
+#report part
+#GitLab-CI output
+gcovr -r .
+# Artefact
+gcovr -r . --xml -o ./unit-test-coverage.xml
+date +%s > timestamp.log
+
+#echo "$0 Return status ${ret}"
diff --git a/gitlab-ci/install-dependencies.sh b/gitlab-ci/install-dependencies.sh
new file mode 100755
index 0000000000000000000000000000000000000000..74e46d7f46dbf2daec6b66003c87395b2204873d
--- /dev/null
+++ b/gitlab-ci/install-dependencies.sh
@@ -0,0 +1,165 @@
+#!/bin/bash
+
+echo "install dependencies"
+
+home=$(pwd)
+
+function exec_cmd()
+{
+	echo "executing $@"
+	$@ >/dev/null 2>&1
+	local ret=$?
+
+	if [ "${ret}" -ne 0 ]; then
+		echo "Failed to execute $@ ret (${ret})"
+		exit 1
+	fi
+}
+
+exec_cmd apt update
+exec_cmd apt install -y iproute2
+exec_cmd apt install -y libavahi-core-dev
+
+# install easy-soc-libs (libwifi-6)
+cd /opt/dev
+rm -fr easy-soc-libs
+mkdir -p /usr/include/easy
+exec_cmd git clone -b devel https://dev.iopsys.eu/iopsys/easy-soc-libs.git
+cd easy-soc-libs/libeasy
+exec_cmd make CFLAGS+="-I/usr/include/libnl3"
+cp libeasy*.so* /usr/lib
+cp easy.h event.h utils.h debug.h /usr/include/easy/
+cd ../libwifi
+exec_cmd make PLATFORM="test"
+cp wifi.h /usr/include/ &&
+cp libwifi-6*.so* /usr/lib
+
+# install wifimngr
+cd /opt/dev
+rm -fr wifimngr
+exec_cmd git clone -b devel https://dev.iopsys.eu/iopsys/wifimngr.git
+cd wifimngr
+exec_cmd ./gitlab-ci/install-dependencies.sh
+exec_cmd ./gitlab-ci/setup.sh
+exec_cmd make
+exec_cmd cp wifimngr /usr/sbin/
+
+# install ieee1905
+cd /opt/dev
+export CFLAGS="${CFLAGS} -g -Wall -g -O0"
+rm -fr ieee1905
+exec_cmd git clone --depth 1 -b devel https://dev.iopsys.eu/iopsys/ieee1905.git
+cd ieee1905
+exec_cmd ./gitlab-ci/install-dependencies.sh
+exec_cmd ./gitlab-ci/setup.sh
+exec_cmd make "CONFIG_IEEE1905_ALME_OVER_UBUS=y"
+exec_cmd make install
+mkdir -p /usr/include/ieee1905
+exec_cmd cp -a include/plugin.h /usr/include/ieee1905
+exec_cmd cp -a libieee1905/include/1905_cmdus.h /usr/include/ieee1905
+exec_cmd cp -a libieee1905/include/1905_tlvs.h /usr/include/ieee1905
+exec_cmd cp -a ieee1905d /usr/sbin/
+
+# install map-1905 (libmaputils)
+cd /opt/dev
+rm -fr map-1905
+git clone -b devel https://dev.iopsys.eu/iopsys/map-1905.git
+cd map-1905
+exec_cmd make
+cp -r libmaputils.so /usr/lib
+mkdir -p /usr/include/map1905
+cp include/*.h /usr/include/map1905/
+mkdir -p /usr/lib/ieee1905
+cp -r libwifimap2.so /usr/lib/ieee1905
+#cd ${home}
+#mkdir -p /usr/lib/ieee1905
+#mkdir -p /usr/include/map1905
+#ls ./test/map/libs/
+#cp ./test/map/libs/libmaputils.so /usr/lib/
+#cp ./test/map/libs/libwifimap2.so /usr/lib/ieee1905/
+#cp ./test/map/include/*.h /usr/include/map1905/
+
+# install topology
+cd /opt/dev
+rm -fr map-topology
+exec_cmd git clone -b devel https://dev.iopsys.eu/iopsys/map-topology.git
+cd map-topology/src
+exec_cmd make
+exec_cmd cp -a topologyd /usr/sbin/
+
+ldconfig
+
+# install mapagent
+cd /opt/dev
+rm -fr map-agent
+exec_cmd git clone -b devel https://dev.iopsys.eu/iopsys/map-agent.git
+cd map-agent/src
+git checkout ap_capabilities
+exec_cmd make
+exec_cmd cp mapagent /usr/sbin/
+
+# install mapagent
+cd /opt/dev
+rm -fr tap
+exec_cmd git clone https://github.com/andrewgregory/tap.sh.git tap
+cd tap
+exec_cmd cp tap.sh /usr/bin/
+
+
+echo "
+config wifiagent
+	option enabled '1'
+	option debug '6'
+
+config fh-iface
+	option ifname 'test5'
+	option steer 'rssi bssload'
+	list exclude '00:11:22:33:44:55'
+	list exclude_btm '00:aa:bb:cc:dd:ee'
+	list assoc_ctrl '00:10:20:30:40:50'
+	option btm_retry '3'
+	option btm_retry_secs '180'
+	option fallback_legacy '1'
+	option steer_legacy_reassoc_secs '30'
+	option steer_legacy_retry_secs '3600'
+	option assoc_ctrl_secs '30'
+
+config fh-iface
+	option ifname 'test2'
+	option steer 'rssi bssload'
+	list exclude '00:11:22:33:44:55'
+	list exclude_btm '00:aa:bb:cc:dd:ee'
+
+#config bk-iface
+#	option ifname 'apclii0'
+#	option enabled '1'
+#	option onboarded '0'
+
+config steer-param 'rssi'
+	option priority '0'
+	option rssi_threshold '-68'
+	option hysteresis '5'
+	option margin '3'
+	option diffsnr '8'
+
+config steer-param 'bssload'
+	option priority '0'
+	option bssload_threshold '80'
+
+### custom rules follows ###
+config rule-custom
+	option action steer
+	option sta 'd8:32:e3:4d:35:d2'
+	option bss '00:22:07:11:22:33'
+
+config rule-custom
+	option action restrict
+	option sta 'd8:32:e3:4d:35:d2'
+	option bss '00:22:07:11:22:33'
+" > /etc/config/agent
+
+echo "
+config wificntlr
+	option enabled '1'
+	option debug '6'
+" > /etc/config/controller
\ No newline at end of file
diff --git a/gitlab-ci/iopsys-supervisord.conf b/gitlab-ci/iopsys-supervisord.conf
new file mode 100644
index 0000000000000000000000000000000000000000..bed0b42d0100dde9d797c41bd033ac08f8ab89c3
--- /dev/null
+++ b/gitlab-ci/iopsys-supervisord.conf
@@ -0,0 +1,17 @@
+[program:ubusd]
+command=/bin/bash -c "/usr/sbin/ubusd"
+
+[program:wifimngr]
+command=/bin/bash -c "/usr/sbin/wifimngr"
+
+[program:ieee1905d]
+command=/bin/bash -c "/usr/sbin/ieee1905d"
+
+[program:topologyd]
+command=/bin/bash -c "/usr/sbin/topologyd"
+
+[program:mapagent]
+command=/bin/bash -c "/usr/sbin/mapagent -d"
+
+[program:mapcontroller]
+command=/bin/bash -c "/usr/bin/valgrind --xml=yes --xml-file=memory-report.xml --leak-check=full --show-reachable=yes --show-leak-kinds=all --errors-for-leak-kinds=all --error-exitcode=1 --track-origins=yes /builds/iopsys/map-controller/src/mapcontroller -d"
diff --git a/gitlab-ci/setup.sh b/gitlab-ci/setup.sh
new file mode 100755
index 0000000000000000000000000000000000000000..3fd902b17d80dc571e6a8b25811d849fd528b0c5
--- /dev/null
+++ b/gitlab-ci/setup.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+echo "preparation script"
+
+pwd
+
+cp -r ./test/files/etc/* /etc/
+cp -r ./schemas/ubus/* /usr/share/rpcd/schemas
+cp ./gitlab-ci/iopsys-supervisord.conf /etc/supervisor/conf.d/
+
+ls /etc/config/
+ls /usr/share/rpcd/schemas/
+ls /etc/supervisor/conf.d/
diff --git a/src/Makefile b/src/Makefile
index 2b0381448fa1fee33849ac21770e79d3832ce161..6e85c299c74ec25996d39d7379564b012f5be8d1 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -59,6 +59,19 @@ check: FORCE
 		-I. -Iutils -Icore -Iipc -Iinclude \
 		. 2> cppcheck.out
 
+test: CFLAGS += -fPIC
+test: ${OBJS} $(IPC_OBJS) $(CNTLR_OBJS)
+	${CC} ${LDFLAGS} -shared -o libmapcontroller.so ${OBJS} $(IPC_OBJS) $(CNTLR_OBJS) ${LIBS}
+
+unit-test: coverage
+	make -C ../test/cmocka unit-test MAPCNTLR_LIB_DIR=$(PWD)
+
+coverage: CFLAGS  += -g -O0 -fprofile-arcs -ftest-coverage -fPIC
+coverage: LDFLAGS += --coverage
+coverage: test mapcontroller
+	$(foreach testprog, $(CODECOVERAGE_SRC), $(GCOV) $(testprog);)
+
+
 clean:
 	rm -f *.o *.so utils/*.o utils/*.so ipc/*.o core/*.o $(EXECS)
 	for i in $(plugin_subdirs); do [ -d $$i ] && $(MAKE) -C $$i clean; done
diff --git a/src/controller.conf b/src/controller.conf
index c7fe7b4e36c1f6cb62a42533e86fc5537ba170d9..452caf3e5219f738916659df50215f0b6a0bffc7 100644
--- a/src/controller.conf
+++ b/src/controller.conf
@@ -1,6 +1,7 @@
 config wificntlr
 	option enabled '1'
 	option registrar '5 2'	    #bands on which wps registrar supported
+	option debug '6'
 
 config fh-credentials
 	option band '5'
diff --git a/src/core/cntlr_map.c b/src/core/cntlr_map.c
index 21cd943033dec63252b9cc0641f4e9840284109f..2619ddab36fa5cc7309678d2ac118639aba9a905 100644
--- a/src/core/cntlr_map.c
+++ b/src/core/cntlr_map.c
@@ -57,7 +57,7 @@ struct tlv {
 	uint8_t value[];
 } __attribute__ ((packed));
 
-
+int verbose;
 typedef int (*map_cmdu_handler_t)(void *cntlr, struct cmdu_cstruct *cmdu);
 
 struct map_cmdu_calltable_t {
@@ -368,10 +368,12 @@ int cntlr_handle_map_event(void *module, uint16_t cmdutype, uint16_t mid,
 		return -1;
 	}
 
+	test_cmdu(cmdu);
+
 	if (f[idx].handle)
 		ret = f[idx].handle(c, cmdu);
 
-	if (c->cfg.debug_level >= 3 && f[idx].debug)
+	if (verbose >= 4 && f[idx].debug)
 		f[idx].debug(c, cmdu);
 
 
diff --git a/src/core/cntlr_ubus.c b/src/core/cntlr_ubus.c
index 7af67ba559b3e3f0d0e907476c0652a87ba7ca7b..6e2b29d21c60b3f15133f601f3cfabb6ae469ead 100644
--- a/src/core/cntlr_ubus.c
+++ b/src/core/cntlr_ubus.c
@@ -106,7 +106,7 @@ static int send_cmdu(struct controller *c,
 	uint8_t *ss = NULL;
 	uint16_t msgid = 0;
 	uint16_t len;
-	int ret = 0;
+	int ret = -1;
 	size_t i;
 	uint32_t id;
 	struct ieee1905_cmdu_msg *cmsg = NULL;
@@ -170,20 +170,7 @@ static int send_cmdu(struct controller *c,
 					__func__, __LINE__);
 		goto out;
 	}
-
-	//TODO: improve /////////////////////////
-//	if (is_store_mid) {
-//		cmsg = &priv->cmsg;
-//		for (i = 0; i < MAX_CMDU_MSG; i++) {
-//			if (cmsg->msg_id[i] == -1) {
-//				cmsg->msg_id[i] = msgid;
-//				cmsg->msg_ts[i] = time(NULL);
-//				break;
-//			}
-//		}
-//	}
-	/////////////////////////////////////////
-
+	test_cmdu(cmdu_data);
 out:
 	blob_buf_free(&b);
 
diff --git a/src/core/config.c b/src/core/config.c
index 407cf1406c94b6abb664876052acf5ce89016624..b1e736c4ccb83083b3beff4e5411a2f63700fcb0 100644
--- a/src/core/config.c
+++ b/src/core/config.c
@@ -39,6 +39,8 @@
 #include "cntlr.h"
 #include "config.h"
 
+int verbose;
+
 void cntlr_config_dump(struct controller_config *c)
 {
 	int i;
@@ -107,6 +109,7 @@ static int cntlr_config_get_base(struct controller_config *c,
 		{ .name = "enabled", .type = UCI_TYPE_STRING },
 		{ .name = "registrar", .type = UCI_TYPE_STRING },
 		{ .name = "debug", .type = UCI_TYPE_STRING },
+		{ .name = "debug", .type = UCI_TYPE_STRING }
 	};
 	struct uci_option *tb[NUM_CNTLR_ATTRS];
 
@@ -126,10 +129,12 @@ static int cntlr_config_get_base(struct controller_config *c,
 		c->has_registrar_2g = !strstr(val, "2") ? false : true;
 	}
 
-	if (tb[CNTLR_ENABLED]) {
+	if (tb[CNTLR_DEBUG]) {
 		const char *debug = tb[CNTLR_DEBUG]->v.string;
 
 		c->debug_level = atoi(debug);
+		if (c->debug_level > verbose)
+			verbose = c->debug_level;
 	}
 
 	return 0;
diff --git a/src/core/main.c b/src/core/main.c
index 75576343a91c5ffb57015dfde33e4f5c0c20b584..1085ad9338bdf3d5f43fcb7e3c0e1fcb3329ac59 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -95,10 +95,12 @@ int main(int argc, char **argv)
 		do_daemonize(pidfile);
 
 	start_logging();
+	start_test_logging();
 	init_alloctrace("wificntlr");
 
 	start_controller();
 
+	stop_test_logging();
 	stop_logging();
 
 	return 0;
diff --git a/src/utils/debug.c b/src/utils/debug.c
index d4b3dca5ec39c0e79829d3cb4d19953155ebe388..1412ad852043b265e7af310e092bbff585d8e3d0 100644
--- a/src/utils/debug.c
+++ b/src/utils/debug.c
@@ -7,6 +7,7 @@
  *
  */
 
+#include <stdlib.h>
 #include <stdio.h>
 #include <stdbool.h>
 #include <stdarg.h>
@@ -17,11 +18,17 @@
 #include <unistd.h>
 #include <time.h>
 
+#include "utils.h"
 #include "debug.h"
 
+#include <map1905/map2.h>
+#include <map1905/maputils.h>
+
+static FILE *testfile = NULL;
 static FILE *outfile = NULL;
-static int ffd;
+static int ffd, tfd;
 extern const char *outfile_path;
+extern const char *testfile_path = "/tmp/cntlr.test.log";
 
 extern const char *PROG_NAME;
 extern int verbose;
@@ -130,3 +137,86 @@ void dump(unsigned char *buf, int len, char *label)
 	if (label)
 		printf("\n--------------\n");
 }
+
+// TODO: duplicate of start_logging
+void start_test_logging(void)
+{
+	if (!testfile) {
+		if (testfile_path) {
+			if (usefifo) {
+				struct stat st;
+				int rfd;
+
+				if (stat(testfile_path, &st))
+					unlink(testfile_path);
+
+				mkfifo(testfile_path, 0600);
+				if (stat(testfile_path, &st) == -1 ||
+						!S_ISFIFO(st.st_mode))
+					return;
+
+				rfd = open(testfile_path, O_RDONLY | O_NONBLOCK);
+				if (rfd) {
+					tfd = open(testfile_path, O_WRONLY | O_NONBLOCK);
+					close(rfd);
+				}
+			} else {
+				testfile = fopen(testfile_path, "w+");
+			}
+		} else {
+			testfile = stderr;
+		}
+	}
+}
+
+void stop_test_logging(void)
+{
+	if (testfile)
+		fclose(testfile);
+
+	if (tfd) {
+		close(tfd);
+		unlink(testfile_path);
+	}
+}
+
+void log_test(int level, void *var, int len)
+{
+	char *bstr;
+
+	if (level > verbose)
+		return;
+
+	bstr = calloc(1, (len * 2) + 1);
+	if (!bstr)
+		return;
+
+	btostr(var, len, bstr);
+
+	printf("%s %d bstr = %s\n", __func__, __LINE__, bstr);
+
+	fprintf(testfile, "%s\n", bstr);
+	fflush(testfile);
+out:
+	free(bstr);
+}
+
+void log_cmdu(int level, void *var)
+{
+	struct cmdu_cstruct *cmdu = (struct cmdu_cstruct *) var;
+	int i;
+
+	if (level > verbose)
+		return;
+
+	for (i = 0; i < cmdu->num_tlvs; i++) {
+		uint16_t len;
+		uint8_t *btlv;
+
+		btlv = map_put_tlv_cstruct(cmdu->tlvs[i], &len);
+		if (!btlv)
+			continue;
+
+		log_test(level, btlv, len);
+	}
+}
diff --git a/src/utils/debug.h b/src/utils/debug.h
index 9a4a0e2d7ee80f16b2df07e575f035301bc52a23..2bf8d5d91ff3b03071c5474c8076845bbe42d4e6 100644
--- a/src/utils/debug.h
+++ b/src/utils/debug.h
@@ -14,7 +14,11 @@
 
 void start_logging(void);
 void stop_logging(void);
+void start_test_logging(void);
+void stop_test_logging(void);
 void log_message(int level, const char *fmt, ...);
+void log_test(int level, void *var, int len);
+void log_cmdu(int level, void *var);
 
 #define DEBUG_COLOR	1
 
@@ -39,6 +43,8 @@ void log_message(int level, const char *fmt, ...);
 #define trace(fmt, ...)       log_message(4, fmt, ## __VA_ARGS__)
 #define trace_cmd(fmt, ...)   log_message(4, brown fmt nocl, ## __VA_ARGS__)
 #define loud(fmt, ...)        log_message(5, fmt, ## __VA_ARGS__)
+#define test(var, len)        log_test(6, var, len)
+#define test_cmdu(var)        log_cmdu(6, var)
 #else
 
 #define logrec(...)	log_message(-1, __VA_ARGS__)
@@ -49,6 +55,8 @@ void log_message(int level, const char *fmt, ...);
 #define trace(...)	log_message(4, __VA_ARGS__)
 #define trace_cmd(...)	log_message(4, __VA_ARGS__)
 #define loud(...)	log_message(5, __VA_ARGS__)
+#define test(var, len)        log_test(6, var, len)
+#define test_cmdu(var)   log_cmdu(6, var)
 #endif /* DEBUG_COLOR */
 
 
diff --git a/test/api/json/mapcontroller.validation.json b/test/api/json/mapcontroller.validation.json
new file mode 100644
index 0000000000000000000000000000000000000000..91584e837f2f3b94713ab0fd5e23d3cb92743062
--- /dev/null
+++ b/test/api/json/mapcontroller.validation.json
@@ -0,0 +1,13 @@
+{
+    "object": "map.controller",
+    "methods": [
+        {
+            "method": "ap_caps",
+            "rc": 0
+        },
+        {
+            "method": "sta_caps",
+            "rc": 0
+        }
+    ]
+}
diff --git a/test/cmocka/Makefile b/test/cmocka/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..7bc33ed365b9133e138a4d02a9c28345f321952e
--- /dev/null
+++ b/test/cmocka/Makefile
@@ -0,0 +1,32 @@
+CC		= gcc
+MAPCNTLR_LIB_DIR	?= $(shell dirname $(PWD))
+MAPCNTLR_LIB	= -L$(MAPCNTLR_LIB_DIR) -lmapcontroller
+CMOCKA_LIB	= -l cmocka
+LIBS		= $(MAPCNTLR_LIB) $(CMOCKA_LIB) -lwifi-6 -pthread -luci -lubus -lubox -ljson-c -lblobmsg_json -lnl-genl-3 -lnl-3 -ljson-validator -ljson-schema-validator -ljson-editor
+LIBS 		+= -rdynamic -ldl
+LIBS 		+= -lmaputils
+CFLAGS		= -g -Wall -I../../src/core -I../../src/utils
+LDFLAGS		= $(LIBS) -Wl,-rpath=$(MAPCNTLR_LIB_DIR) -I$(MAPCNTLR_LIB_DIR)
+UNIT_TESTS	= unit_test_cntlr
+FUNCTIONAL_TESTS	= functional_test_cntlr
+UTILS 		= test_utils.o
+
+VALGRIND	= valgrind --leak-check=full --show-reachable=no \
+	--show-leak-kinds=all --errors-for-leak-kinds=all \
+	--error-exitcode=1 --track-origins=yes
+
+unit_test_cntlr: $(UTILS) unit_test_cntlr.o
+	$(CC) -o $@ $^ $(LDFLAGS)
+
+functional_test_cntlr: $(UTILS) functional_test_cntlr.o
+	$(CC) -o $@ $^ $(LDFLAGS)
+
+unit-test: $(UNIT_TESTS)
+	$(foreach testprog, $(UNIT_TESTS), sudo $(VALGRIND) ./$(testprog);)
+
+functional-test: $(FUNCTIONAL_TESTS)
+	$(foreach testprog, $(FUNCTIONAL_TESTS), sudo $(VALGRIND) ./$(testprog);)
+
+.PHONY: clean
+clean:
+	rm $(UNIT_TESTS) $(FUNCTIONAL_TESTS) *.o -fv
diff --git a/test/cmocka/test_utils.c b/test/cmocka/test_utils.c
new file mode 100644
index 0000000000000000000000000000000000000000..cf41e5926e0eda497464ef722dbe40a980698d02
--- /dev/null
+++ b/test/cmocka/test_utils.c
@@ -0,0 +1,43 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "test_utils.h"
+
+int compare_files(char *file1, char *file2)
+{
+    char ch1, ch2;
+    int error = 0, pos = 0, line = 1;
+    FILE *fp1;
+    FILE *fp2;
+
+    fp1 = fopen(file1, "r");
+    if (!fp1)
+        return -1;
+
+    fp2 = fopen(file2, "r");
+    if (!fp2)
+        return -1;
+
+    ch1 = getc(fp1);
+    ch2 = getc(fp2);
+
+    while (ch1 != EOF && ch2 != EOF) {
+        pos++;
+
+        if (ch1 == '\n' && ch2 == '\n') {
+            line++;
+            pos = 0;
+        }
+
+        if (ch1 != ch2)
+            return -1;
+
+        ch1 = getc(fp1);
+        ch2 = getc(fp2);
+    }
+
+    fclose(file1);
+    fclose(file2);
+    return 0;
+}
diff --git a/test/cmocka/test_utils.h b/test/cmocka/test_utils.h
new file mode 100644
index 0000000000000000000000000000000000000000..dc3a992492808d9762ab56127310e82809596a92
--- /dev/null
+++ b/test/cmocka/test_utils.h
@@ -0,0 +1,6 @@
+#ifndef TEST_UTILS_H
+#define TEST_UTILS_H
+
+int compare_files(char *file1, char *file2);
+
+#endif
diff --git a/test/cmocka/unit_test_cntlr.c b/test/cmocka/unit_test_cntlr.c
new file mode 100644
index 0000000000000000000000000000000000000000..ced0be1038375a14e053644f0d227f4cf4a593e8
--- /dev/null
+++ b/test/cmocka/unit_test_cntlr.c
@@ -0,0 +1,131 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+#include <libubus.h>
+#include <libubox/blobmsg_json.h>
+#include <libubox/blobmsg.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <json-c/json.h>
+
+#include <easy/easy.h>
+#include <wifi.h>	// FIXME: should not be included
+
+#include "debug.h"
+#include "utils.h"
+#include "config.h"
+#include "test_utils.h"
+#include "cntlr.h"
+
+#define CNTLR_FILE "/tmp/cntlr.test.log"
+#define AGENT_FILE "/tmp/cntlr.test.log"
+
+int cntlr_ap_caps(struct ubus_context *ctx, struct ubus_object *obj,
+		struct ubus_request_data *req, const char *method,
+		struct blob_attr *msg);
+
+struct test_ctx {
+	struct blob_buf bb;
+	struct ubus_object radio;
+	struct ubus_object ap;
+	FILE *fp;
+};
+
+static int group_setup(void **state)
+{
+	struct test_ctx *ctx;// = malloc(sizeof(struct test_ctx));
+
+	ctx = calloc(1, sizeof(*ctx));
+
+	printf("%s %d\n", __func__, __LINE__);
+
+	start_test_logging();
+
+
+
+//	if (!ctx)
+//		return -1;
+//
+//	printf("%s %d\n", __func__, __LINE__);
+//
+//	remove(CNTLR_FILE);
+//	remove(AGENT_FILE);
+//
+//	printf("%s %d\n", __func__, __LINE__);
+//
+//	memset(&ctx->bb, 0, sizeof(struct blob_buf));
+//
+//	printf("%s %d\n", __func__, __LINE__);
+//
+//	*state = ctx;
+	return 0;
+}
+
+static int setup(void **state)
+{
+	printf("%s %d\n", __func__, __LINE__);
+	return 0;
+}
+
+static int teardown(void **state)
+{
+	stop_test_logging();
+
+	printf("%s %d\n", __func__, __LINE__);
+	return 0;
+}
+
+/* overload ubus_send_reply to prevent segfault*/
+int ubus_send_reply(struct ubus_context *ctx, struct ubus_request_data *req,
+		    struct blob_attr *msg)
+{
+	return 0;
+}
+
+static int group_teardown(void **state)
+{
+//	struct test_ctx *ctx = (struct test_ctx *) *state;
+
+	printf("%s %d\n", __func__, __LINE__);
+
+//	blob_buf_free(&ctx->bb);
+//	free(ctx);
+//	remove(CNTLR_FILE);
+//	remove(AGENT_FILE);
+	return 0;
+}
+
+static void test_cmdu_comm(void **state)
+{
+	//struct test_ctx *ctx = (struct test_ctx *) *state;
+	//struct blob_buf *bb = &ctx->bb;
+	//struct json_object *jobj, *tmp;
+	int rv;
+	struct blob_buf bb = {0};
+	struct controller c;
+
+	c.ubus_ctx = ubus_connect(NULL);
+	blob_buf_init(&bb, 0);
+	printf("%s %d\n", __func__, __LINE__);
+
+	rv = cntlr_ap_caps(NULL, &c.obj, NULL, NULL, bb.head);
+
+	dbg("test\n");
+printf("%s %d\n", __func__, __LINE__);
+	assert_int_equal(rv, 0);
+printf("%s %d\n", __func__, __LINE__);
+	assert_true(!compare_files(CNTLR_FILE, AGENT_FILE));
+}
+
+int main(void)
+{
+	const struct CMUnitTest tests[] = {
+		cmocka_unit_test_setup_teardown(test_cmdu_comm, setup, teardown),
+	};
+
+	return cmocka_run_group_tests(tests, group_setup, group_teardown);
+}