From 724e45d217661ca8426b9ae636debc246acb8299 Mon Sep 17 00:00:00 2001 From: Adrian Szyndela Date: Wed, 8 Jan 2020 16:34:01 +0100 Subject: [PATCH] tests: extract common code for lowmem modules This extracts the code which is common for testing lowmem-limit and lowmem-system modules to separate files. Add minor improvements: * extract function for adding test modules in CMakeLists.txt * add cmocka_wrap.hpp which aims to make including cmocka.h easier. Change-Id: I67c34e3dbd84316fbd86d075e72333f2d0d14625 --- tests/CMakeLists.txt | 45 ++++++++++-------- tests/cmocka_wrap.hpp | 14 ++++++ tests/lowmem-env-mock.cpp | 9 ++++ tests/lowmem-env.cpp | 48 ++++++++++++++++++++ tests/lowmem-env.hpp | 26 +++++++++++ tests/lowmem-limit-env.cpp | 26 ++--------- tests/lowmem-limit-env.hpp | 9 ++-- tests/lowmem-limit-mock.cpp | 108 ++++++++++---------------------------------- tests/lowmem-limit-test.cpp | 6 +-- tests/lowmem-mock.hpp | 58 ++++++++++++++++++++++++ 10 files changed, 213 insertions(+), 136 deletions(-) create mode 100644 tests/cmocka_wrap.hpp create mode 100644 tests/lowmem-env-mock.cpp create mode 100644 tests/lowmem-env.cpp create mode 100644 tests/lowmem-env.hpp create mode 100644 tests/lowmem-mock.hpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5727692..f511742 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -46,27 +46,32 @@ TARGET_LINK_LIBRARIES(cmocka-core resourced_shared_test cmocka dlog gio-2.0 gobj ADD_TEST(core cmocka-core) ADD_DEPENDENCIES(do-test cmocka-core) +function(ADD_MEMORY_TESTS name libs wraps sources) + ADD_EXECUTABLE(${name} ${sources} ${ARGN}) + SET_TARGET_PROPERTIES(${name} PROPERTIES COMPILE_FLAGS "-fprofile-arcs -ftest-coverage") + if(NOT ${wraps} EQUAL "") + SET_TARGET_PROPERTIES(${name} PROPERTIES LINK_FLAGS ${wraps}) + endif() + TARGET_COMPILE_DEFINITIONS(${name} + PRIVATE RD_CONFIG_PATH="." SYSTEM_LIB_PATH="/usr/lib" SWAP_SUPPORT RD_SYS_DB="." RD_SYS_DATA="." _UNIT_TEST) + TARGET_INCLUDE_DIRECTORIES(${name} + PRIVATE ${COMMON_SOURCE_DIR} + ../include + ../src/memory + ../src/resourced + ../src/common + PUBLIC ${RESOURCED_REQUIRE_PKGS_INCLUDE_DIRS}) + TARGET_LINK_LIBRARIES(${name} cmocka pthread dl m gcov dlog ${libs}) + ADD_TEST(${name} ${name}) + ADD_DEPENDENCIES(do-test ${name}) +endfunction() + # lowmem-limit unit test -ADD_EXECUTABLE(lowmem-limit-test - lowmem-limit-test.cpp lowmem-limit-mock.cpp lowmem-limit-env.cpp - ../src/memory/lowmem-limit.c - ) -SET_TARGET_PROPERTIES(lowmem-limit-test PROPERTIES - COMPILE_FLAGS "-fprofile-arcs -ftest-coverage" - LINK_FLAGS "-Wl,--wrap=kill,--wrap=read,--wrap=access") -TARGET_COMPILE_DEFINITIONS(lowmem-limit-test - PRIVATE RD_CONFIG_PATH="." SYSTEM_LIB_PATH="/usr/lib" SWAP_SUPPORT RD_SYS_DB="." RD_SYS_DATA="." _UNIT_TEST) -TARGET_INCLUDE_DIRECTORIES(lowmem-limit-test - PRIVATE ${COMMON_SOURCE_DIR} - ../include - ../src/memory - ../src/resourced - ../src/common - PUBLIC ${RESOURCED_REQUIRE_PKGS_INCLUDE_DIRS}) -TARGET_LINK_LIBRARIES(lowmem-limit-test cmocka pthread dl m gcov - ${RESOURCED_REQUIRE_PKGS_LIBRARIES}) -ADD_TEST(lowmem-limit-test lowmem-limit-test) -ADD_DEPENDENCIES(do-test lowmem-limit-test) +ADD_MEMORY_TESTS(lowmem-limit-test "glib-2.0" + "-Wl,--wrap=kill,--wrap=read,--wrap=access" + lowmem-limit-test.cpp lowmem-limit-mock.cpp lowmem-limit-env.cpp lowmem-env.cpp lowmem-env-mock.cpp + ../src/memory/lowmem-limit.c) +# lowmem-system unit test INSTALL(TARGETS watchdog-test DESTINATION ${RD_TESTS_PATH}) diff --git a/tests/cmocka_wrap.hpp b/tests/cmocka_wrap.hpp new file mode 100644 index 0000000..4ca9803 --- /dev/null +++ b/tests/cmocka_wrap.hpp @@ -0,0 +1,14 @@ +#pragma once + +// This is a wrapper file for including the below set of header files with a single #include + +#include +#include +#include + +// iostream has some conflicts with below cmocka.h, it needs to be first +#include + +extern "C" { +#include +} diff --git a/tests/lowmem-env-mock.cpp b/tests/lowmem-env-mock.cpp new file mode 100644 index 0000000..e0433a2 --- /dev/null +++ b/tests/lowmem-env-mock.cpp @@ -0,0 +1,9 @@ +#include "lowmem-mock.hpp" +#include "lowmem-env.hpp" + +#define MOCK_LOWMEM(rettype, name, def_args, call_args) \ + MOCK(global_test_lowmem_env, rettype, name, def_args, call_args) + +MOCK_LOWMEM(int, register_notifier, (enum notifier_type status, int (*func)(void *data)), (status, func)) +MOCK_LOWMEM(int, unregister_notifier, (enum notifier_type status, int (*func)(void *data)), (status, func)) + diff --git a/tests/lowmem-env.cpp b/tests/lowmem-env.cpp new file mode 100644 index 0000000..904d875 --- /dev/null +++ b/tests/lowmem-env.cpp @@ -0,0 +1,48 @@ +#include "lowmem-env.hpp" +#include "cmocka_wrap.hpp" + +LowmemEnv *global_test_lowmem_env{nullptr}; + +LowmemEnv::LowmemEnv() +{ + assert_null(global_test_lowmem_env); + global_test_lowmem_env = this; +} + +LowmemEnv::~LowmemEnv() +{ + assert_non_null(global_test_lowmem_env); + global_test_lowmem_env = nullptr; +} + +void LowmemEnv::reinit() +{ + notifiers.clear(); +} + +int LowmemEnv::register_notifier(enum notifier_type status, NotifierCallbackFun func) +{ + notifiers[status] = func; + return 0; +} + +int LowmemEnv::unregister_notifier(enum notifier_type status, NotifierCallbackFun func) +{ + auto notit = notifiers.find(status); + if (notit != notifiers.end()) { + // If this assert ever fires, then it means that there are two functions + // registered for the status. It is acceptable by the resourced. + // However, for this test code I simplified the mechanism, because the code + // I wanted to test never registered more than one function to a status. + assert_true(notit->second == func); + notifiers.erase(notit); + } + return 0; +} + +void LowmemEnv::trigger_notifier(enum notifier_type status, void *data) +{ + auto fun = notifiers[status]; + assert_non_null(fun); + fun(data); +} diff --git a/tests/lowmem-env.hpp b/tests/lowmem-env.hpp new file mode 100644 index 0000000..7b4c4aa --- /dev/null +++ b/tests/lowmem-env.hpp @@ -0,0 +1,26 @@ +#pragma once + +extern "C" { +#include "notifier.h" +} + +#include + +typedef int (*NotifierCallbackFun)(void *data); + +class LowmemEnv { +public: + LowmemEnv(); + ~LowmemEnv(); + + void reinit(); + + int register_notifier(enum notifier_type status, NotifierCallbackFun func); + int unregister_notifier(enum notifier_type status, NotifierCallbackFun func); + + void trigger_notifier(enum notifier_type status, void *data); +private: + std::map notifiers; +}; + +extern LowmemEnv *global_test_lowmem_env; diff --git a/tests/lowmem-limit-env.cpp b/tests/lowmem-limit-env.cpp index 4ca4e55..2a1fa70 100644 --- a/tests/lowmem-limit-env.cpp +++ b/tests/lowmem-limit-env.cpp @@ -1,11 +1,9 @@ -#include -#include -#include #include +#include "cmocka_wrap.hpp" + extern "C" { -#include #include "notifier.h" } @@ -32,10 +30,10 @@ LowmemLimitEnv::~LowmemLimitEnv() void LowmemLimitEnv::reinit() { + lowmem_env.reinit(); mem_limit_type = "none"; mem_limit_value = "0"; ktotalram = 0; - notifiers.clear(); apps.clear(); fd_handlers.clear(); cgroup_memory.clear(); @@ -119,27 +117,11 @@ int LowmemLimitEnv::config_parse(const char *, ConfigParseCallbackFun cb, void * return 0; } -int LowmemLimitEnv::register_notifier(enum notifier_type status, NotifierCallbackFun func) -{ - notifiers[status] = func; - return 0; -} - -int LowmemLimitEnv::unregister_notifier(enum notifier_type status, NotifierCallbackFun func) -{ - auto notit = notifiers.find(status); - if (notit != notifiers.end() && notit->second == func) - notifiers.erase(notit); - return 0; -} - void LowmemLimitEnv::event_service_launch(pid_t pid) { struct proc_status ps; ps.pid = pid; - auto fun = notifiers[RESOURCED_NOTIFIER_SERVICE_LAUNCH]; - assert_non_null(fun); - fun(&ps); + lowmem_env.trigger_notifier(RESOURCED_NOTIFIER_SERVICE_LAUNCH, &ps); } unsigned long LowmemLimitEnv::lowmem_get_ktotalram() diff --git a/tests/lowmem-limit-env.hpp b/tests/lowmem-limit-env.hpp index f690979..f57fbd4 100644 --- a/tests/lowmem-limit-env.hpp +++ b/tests/lowmem-limit-env.hpp @@ -2,11 +2,12 @@ #include +#include "lowmem-env.hpp" + extern "C" { #include "cgroup.h" #include "config-parser.h" #include "fd-handler.h" -#include "notifier.h" #include "proc-common.h" #include "resourced.h" #include "resourced-helper-worker.h" @@ -16,7 +17,6 @@ extern "C" { #include typedef int (*ConfigParseCallbackFun)(struct parse_result *result, void *user_data); -typedef int (*NotifierCallbackFun)(void *data); class LowmemLimitEnv { public: @@ -39,7 +39,6 @@ private: ~AppTestValuesHolder(); }; - std::map notifiers; std::map apps; std::map fd_handlers; @@ -61,6 +60,8 @@ private: std::string mem_limit_type{"none"}; std::string mem_limit_value{"0"}; // in MBs, string unsigned long ktotalram{0}; // in bytes + + LowmemEnv lowmem_env; public: LowmemLimitEnv(MemLimitType limit_type, unsigned long limit_value, unsigned long ram); ~LowmemLimitEnv(); @@ -77,8 +78,6 @@ public: // mock functions int config_parse(const char *file_name, ConfigParseCallbackFun cb, void *user_data); - int register_notifier(enum notifier_type status, NotifierCallbackFun func); - int unregister_notifier(enum notifier_type status, NotifierCallbackFun func); unsigned long lowmem_get_ktotalram(); int exec_cmd(int argc, const char *argv[]); int request_helper_worker(enum helper_worker_operation op, diff --git a/tests/lowmem-limit-mock.cpp b/tests/lowmem-limit-mock.cpp index 28ee3d6..81a96e9 100644 --- a/tests/lowmem-limit-mock.cpp +++ b/tests/lowmem-limit-mock.cpp @@ -1,109 +1,49 @@ -#include -#include -#include -#include -#include -#include -#include - -#include -#include #include "lowmem-limit-env.hpp" +#include "lowmem-mock.hpp" -extern "C" { -#include -} - -/* Macro WRAP is used for wrapping and mocking functions from libraries, e.g. from glibc or glib. - * - * Its params are: - * - rettype - return type of the wrapped function; - * - name - the name of the wrapped function; - * - def_args - function arguments as defined in function declaration, e.g. (pid_t pid, int sig) - * - call_args - function arguments as used in calling, e.g. (pid, sig) - * - * For example, writing WRAP(int, kill, (pid_t pid, int sig), (pid, sig)) does two things: - * - declares a function: - * extern "C" int __real_kill(pid_t pid, int sig); - * - defines a function: - * extern "C" int __wrap_kill(pid_t pid, int sig) { - * // ... - * auto ret = global_test_lowmem_limit_env->kill(pid, sig); - * if (ret.first) - * return ret.second; - * return __real_kill(pid, sig); - * } - * - * global_test_lowmem_limit_env can handle the wrapped function or pass it to the real version, by manipulating - * its return value. - */ -#define WRAP(rettype, name, def_args, call_args) \ - extern "C" rettype __real_##name def_args; \ - extern "C" rettype __wrap_##name def_args { \ - std::cerr << #name"\n"; \ - assert_non_null(global_test_lowmem_limit_env); \ - auto ret = global_test_lowmem_limit_env->name call_args; \ - if (ret.first) \ - return ret.second; \ - return __real_##name call_args; \ - } +#define WRAP_LIMIT(rettype, name, def_args, call_args) \ + WRAP(global_test_lowmem_limit_env, rettype, name, def_args, call_args) -WRAP(int, kill, (pid_t pid, int sig), (pid, sig)) -WRAP(ssize_t, read, (int fd, void *buf, size_t count), (fd, buf, count)) -WRAP(int, access, (const char *pathname, int mode), (pathname, mode)) +WRAP_LIMIT(int, kill, (pid_t pid, int sig), (pid, sig)) +WRAP_LIMIT(ssize_t, read, (int fd, void *buf, size_t count), (fd, buf, count)) +WRAP_LIMIT(int, access, (const char *pathname, int mode), (pathname, mode)) -/* - * Macro MOCK is used for redirecting function handlers for mocked functions from resourced - * to global_test_lowmem_limit_env static object. - * Its params are: - * - rettype - return type of the wrapped function; - * - name - the name of the wrapped function; - * - def_args - function arguments as defined in function declaration, e.g. (pid_t pid) - * - call_args - function arguments as used in calling, e.g. (pid) - * - */ -#define MOCK(rettype, name, def_args, call_args) \ - extern "C" rettype name def_args { \ - std::cerr << #name"\n"; \ - assert_non_null(global_test_lowmem_limit_env); \ - return global_test_lowmem_limit_env->name call_args; \ - } +#define MOCK_LIMIT(rettype, name, def_args, call_args) \ + MOCK(global_test_lowmem_limit_env, rettype, name, def_args, call_args) /* mocking other module dependencies for lowmem-limit.c */ -MOCK(int, proc_get_mem_usage, (pid_t pid, unsigned int *usage), (pid, usage)) -MOCK(int, proc_get_cmdline, (pid_t pid, char *cmdline), (pid, cmdline)) -MOCK(proc_app_info *, find_app_info, (const pid_t pid), (pid)) +MOCK_LIMIT(int, proc_get_mem_usage, (pid_t pid, unsigned int *usage), (pid, usage)) +MOCK_LIMIT(int, proc_get_cmdline, (pid_t pid, char *cmdline), (pid, cmdline)) +MOCK_LIMIT(proc_app_info *, find_app_info, (const pid_t pid), (pid)) -MOCK(int, exec_cmd, (int argc, const char *argv[]), (argc, argv)) -MOCK(void, make_memps_log, (char *file, pid_t pid, char *victim_name), (file, pid, victim_name)) +MOCK_LIMIT(int, exec_cmd, (int argc, const char *argv[]), (argc, argv)) +MOCK_LIMIT(void, make_memps_log, (char *file, pid_t pid, char *victim_name), (file, pid, victim_name)) -MOCK(int, cgroup_get_pids, (const char *name, GArray **pids), (name, pids)) -MOCK(int, cgroup_write_node_uint32, +MOCK_LIMIT(int, cgroup_get_pids, (const char *name, GArray **pids), (name, pids)) +MOCK_LIMIT(int, cgroup_write_node_uint32, (const char *cgroup_name, const char *file_name, uint32_t value), (cgroup_name, file_name, value)) -MOCK(int, cgroup_read_node_uint32, +MOCK_LIMIT(int, cgroup_read_node_uint32, (const char *cgroup_name, const char *file_name, uint32_t *value), (cgroup_name, file_name, value)) -MOCK(int, cgroup_get_memory_stat, +MOCK_LIMIT(int, cgroup_get_memory_stat, (const char *name, struct cgroup_memory_stat **mem_stat), (name, mem_stat)) -MOCK(int, cgroup_make_subdir, +MOCK_LIMIT(int, cgroup_make_subdir, (const char* parentdir, const char* cgroup_name, bool *already), (parentdir, cgroup_name, already)) -MOCK(int, d_bus_broadcast_signal_gvariant, +MOCK_LIMIT(int, d_bus_broadcast_signal_gvariant, (const char *path, const char *interface, const char *name, GVariant *gv), (path, interface, name, gv)) -MOCK(int, request_helper_worker, +MOCK_LIMIT(int, request_helper_worker, (enum helper_worker_operation op, void *data, int (*function) (void *data), void *callback), (op, data, function, reinterpret_cast(callback))) -MOCK(int, memcg_set_eventfd, (const char *memcg, const char *event, char *value), (memcg, event, value)) -MOCK(int, add_fd_read_handler, +MOCK_LIMIT(int, memcg_set_eventfd, (const char *memcg, const char *event, char *value), (memcg, event, value)) +MOCK_LIMIT(int, add_fd_read_handler, (int fd, fd_changed_cb callback, void *data, release_cb free_func, fd_handler_h *handler), (fd, callback, data, free_func, handler)) -MOCK(unsigned long, lowmem_get_ktotalram, (void), ()) -MOCK(int, config_parse, +MOCK_LIMIT(unsigned long, lowmem_get_ktotalram, (void), ()) +MOCK_LIMIT(int, config_parse, (const char *file_name, int cb(struct parse_result *result, void *user_data), void *user_data), (file_name, cb, user_data)) -MOCK(int, register_notifier, (enum notifier_type status, int (*func)(void *data)), (status, func)) -MOCK(int, unregister_notifier, (enum notifier_type status, int (*func)(void *data)), (status, func)) diff --git a/tests/lowmem-limit-test.cpp b/tests/lowmem-limit-test.cpp index 12f36e2..66ef637 100644 --- a/tests/lowmem-limit-test.cpp +++ b/tests/lowmem-limit-test.cpp @@ -1,16 +1,12 @@ -#include -#include -#include #include #include #include #include #include +#include "cmocka_wrap.hpp" extern "C" { -#include - #include "lowmem-handler.h" } diff --git a/tests/lowmem-mock.hpp b/tests/lowmem-mock.hpp new file mode 100644 index 0000000..ffec288 --- /dev/null +++ b/tests/lowmem-mock.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include +#include "cmocka_wrap.hpp" + +/* Macro WRAP is used for wrapping and mocking functions from libraries, e.g. from glibc or glib. + * + * Its params are: + * - global_object - an object that actually handles the wrapped function; + * - rettype - return type of the wrapped function; + * - name - the name of the wrapped function; + * - def_args - function arguments as defined in function declaration, e.g. (pid_t pid, int sig) + * - call_args - function arguments as used in calling, e.g. (pid, sig) + * + * For example, writing WRAP(global_object, int, kill, (pid_t pid, int sig), (pid, sig)) does two things: + * - declares a function: + * extern "C" int __real_kill(pid_t pid, int sig); + * - defines a function: + * extern "C" int __wrap_kill(pid_t pid, int sig) { + * // ... + * auto ret = global_object->kill(pid, sig); + * if (ret.first) + * return ret.second; + * return __real_kill(pid, sig); + * } + * + * global_object can handle the wrapped function or pass it to the real version, by manipulating + * its return value. + */ +#define WRAP(global_object, rettype, name, def_args, call_args) \ + extern "C" rettype __real_##name def_args; \ + extern "C" rettype __wrap_##name def_args { \ + std::cerr << #name"\n"; \ + assert_non_null(global_object); \ + auto ret = global_object->name call_args; \ + if (ret.first) \ + return ret.second; \ + return __real_##name call_args; \ + } + +/* + * Macro MOCK is used for redirecting function handlers for mocked functions from resourced + * to global_test_lowmem_limit_env static object. + * Its params are: + * - global_object - an object that actually handles the mocked function; + * - rettype - return type of the wrapped function; + * - name - the name of the wrapped function; + * - def_args - function arguments as defined in function declaration, e.g. (pid_t pid) + * - call_args - function arguments as used in calling, e.g. (pid) + * + */ +#define MOCK(global_object, rettype, name, def_args, call_args) \ + extern "C" rettype name def_args { \ + std::cerr << #name"\n"; \ + if (!global_object) std::cerr << "no global_object!\n"; \ + assert_non_null(global_object); \ + return global_object->name call_args; \ + } -- 2.7.4