From 2314c24e8986cd6ecbb418547a83652e4300ac94 Mon Sep 17 00:00:00 2001 From: Michal Bloch Date: Wed, 27 Nov 2019 11:52:01 +0100 Subject: [PATCH 01/16] Release 6.0.2 Adds some product related functionality. Change-Id: Ib967cc2f76454576a87714f3b9335a7778a3b345 --- packaging/resourced.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/resourced.spec b/packaging/resourced.spec index 010e148..ea2f1e1 100644 --- a/packaging/resourced.spec +++ b/packaging/resourced.spec @@ -14,7 +14,7 @@ Name: resourced Summary: Resource management daemon -Version: 6.0.1 +Version: 6.0.2 Release: 1 Group: System/Libraries License: Apache-2.0 -- 2.7.4 From 4bdc7c0d5e2e622f7f48276f55478ce2f70d9b54 Mon Sep 17 00:00:00 2001 From: "sanghyeok.oh" Date: Tue, 3 Dec 2019 15:26:54 +0900 Subject: [PATCH 02/16] svace fix Change-Id: Ifc22558c277143c9620b38c9b2eb1552d37b652b Signed-off-by: sanghyeok.oh --- src/memory/vmpressure-lowmem-handler.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/memory/vmpressure-lowmem-handler.c b/src/memory/vmpressure-lowmem-handler.c index 9a7cfec..c7678f0 100644 --- a/src/memory/vmpressure-lowmem-handler.c +++ b/src/memory/vmpressure-lowmem-handler.c @@ -428,10 +428,17 @@ static char *strrstr(const char *str, const char *token) static int timesort(const struct dirent **a, const struct dirent **b) { - long long time1, time2; + long long time1 = 0; + long long time2 = 0; + char *ptr; - time1 = atoll(strtok(strrstr((*a)->d_name, "_"), "_")); - time2 = atoll(strtok(strrstr((*b)->d_name, "_"), "_")); + ptr = strrstr((*a)->d_name, "_"); + if (ptr && *++ptr) + time1 = atoll(ptr); + + ptr = strrstr((*b)->d_name, "_"); + if (ptr && *++ptr) + time2 = atoll(ptr); return (time1 - time2); } @@ -1850,13 +1857,11 @@ static int create_memcgs(void) return ret; } -static unsigned int lowmem_press_eventfd_read(int fd) +static int lowmem_press_eventfd_read(int fd) { - unsigned int ret; uint64_t dummy_state; - ret = read(fd, &dummy_state, sizeof(dummy_state)); - return ret; + return read(fd, &dummy_state, sizeof(dummy_state)); } static void lowmem_press_root_cgroup_handler(void) @@ -1902,7 +1907,8 @@ static bool lowmem_press_eventfd_handler(int fd, void *data) enum lmk_type lmk_type = LMK_MEMORY; // FIXME: probably shouldn't get ignored - (void)lowmem_press_eventfd_read(fd); + if (lowmem_press_eventfd_read(fd) < 0) + _E("Failed to read lowmem press event, %m\n"); for (i = 0; i < MEMCG_MAX; i++) { if (!memcg_tree[i] || !memcg_tree[i]->info) -- 2.7.4 From a37ca4514a124f02b00b7b597f65d13de26070fb Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Stelmach?= Date: Wed, 28 Feb 2018 15:27:17 +0100 Subject: [PATCH 03/16] vip : rearrange logging to provide more debugging information MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Change-Id: I7ece8e8d7b83ff288457f8607eaa39f24b1bfa4a Signed-off-by: Łukasz Stelmach --- src/vip-agent/vip-release-agent.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/vip-agent/vip-release-agent.c b/src/vip-agent/vip-release-agent.c index 50b642b..d233562 100644 --- a/src/vip-agent/vip-release-agent.c +++ b/src/vip-agent/vip-release-agent.c @@ -70,19 +70,21 @@ int main(int argc, char *argv[]) char *rebootargv[4] = {REBOOT_PATH, "silent", NULL, NULL}; DIR *dir = 0; + _I("Starting vip-release-agent: [%d:%s]", argc, argv[1]); + dir = opendir(VIP_CGROUP); if (!dir) { - _E("doesn't support cgroup release agent"); + _E("doesn't support cgroup release agent: %m"); return 0; } closedir(dir); - _E("call release agent : [%d:%s]\n", argc, argv[1]); - /* check previous process */ if (access(CHECK_RELEASE_PROGRESS, F_OK) == 0) return 0; + _D("No other vip-release-agent is running."); + /* make tmp file */ checkfd = creat(CHECK_RELEASE_PROGRESS, 0640); if (checkfd < 0) { @@ -91,7 +93,7 @@ int main(int argc, char *argv[]) } /* unmount cgroup for preventing launching another release-agent */ - _E("systemd service stop"); + _E("unmounting /sys/fs/cgroup"); umount2("/sys/fs/cgroup", MNT_FORCE |MNT_DETACH); /* check debug level */ @@ -107,6 +109,7 @@ int main(int argc, char *argv[]) sync(); + _E("rebooting"); run_exec(rebootargv); return 0; } -- 2.7.4 From db1efcddd46a10944e3dd9aa062deb133802f6dc Mon Sep 17 00:00:00 2001 From: Maciej Slodczyk Date: Tue, 3 Dec 2019 16:27:45 +0100 Subject: [PATCH 04/16] fix file-helper return value on premature EOF Change-Id: I022f22746db4a9285f50208fe53ec7c49063cb0d Signed-off-by: Maciej Slodczyk --- src/common/file-helper.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/common/file-helper.c b/src/common/file-helper.c index 15e3eac..e82a88b 100644 --- a/src/common/file-helper.c +++ b/src/common/file-helper.c @@ -46,8 +46,9 @@ int fwrite_str(const char *path, const char *str) ret_value_errno_msg_if(!f, -errno, "Fail to open file %s", path); + errno = 0; ret = fputs(str, f); - ret_value_errno_msg_if(ret == EOF, errno ? -errno : -EIO, + ret_value_errno_msg_if(ret == EOF, -(errno ?: EIO), "Fail to write file"); return RESOURCED_ERROR_NONE; @@ -115,9 +116,10 @@ int fread_nth_int(const char *path, size_t n, int32_t *number) ret_value_errno_msg_if(!f, -errno, "Fail to open %s file.", path); + errno = 0; for (i = 0; i <= n; i++) { ret = fscanf(f, "%d", &t); - ret_value_errno_msg_if(ret == EOF, -errno, + ret_value_errno_msg_if(ret == EOF, -(errno ?: ENOENT), "Fail to read file\n"); } @@ -136,9 +138,10 @@ int fread_nth_uint(const char *path, size_t n, u_int32_t *number) ret_value_errno_msg_if(!f, -errno, "Fail to open %s file.", path); + errno = 0; for (i = 0; i <= n; i++) { ret = fscanf(f, "%u", &t); - ret_value_errno_msg_if(ret == EOF, -errno, + ret_value_errno_msg_if(ret == EOF, -(errno ?: ENOENT), "Fail to read file\n"); } @@ -157,9 +160,10 @@ int fread_nth_ulong(const char *path, size_t n, unsigned long *number) ret_value_errno_msg_if(!f, -errno, "Fail to open %s file.", path); + errno = 0; for (i = 0; i <= n; i++) { ret = fscanf(f, "%lu", &t); - ret_value_errno_msg_if(ret == EOF, -errno, + ret_value_errno_msg_if(ret == EOF, -(errno ?: ENOENT), "Fail to read file\n"); } -- 2.7.4 From a53e41e31e3b0d4d007a321fd41b3ac0d0eee271 Mon Sep 17 00:00:00 2001 From: Maciej Slodczyk Date: Tue, 12 Nov 2019 14:16:41 +0100 Subject: [PATCH 05/16] add cmocka based unit testing framework to the build process Contains a notifier test as a working example. Change-Id: I545406edad50763d9687abf4c418c2e6b0f94b0d Signed-off-by: Maciej Slodczyk --- CMakeLists.txt | 1 + packaging/resourced.spec | 1 + src/CMakeLists.txt | 24 ++++++++ src/common/macro.h | 11 ++++ tests/CMakeLists.txt | 28 +++++++++ tests/cmocka-core.c | 146 +++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 211 insertions(+) create mode 100644 tests/cmocka-core.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 94e406a..3555cdd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,5 +55,6 @@ INSTALL(FILES ${CMAKE_SOURCE_DIR}/resourced.conf DESTINATION /etc/dbus-1/system. ADD_SUBDIRECTORY(src) IF(DEFINED RD_TESTS_PATH) + ENABLE_TESTING() ADD_SUBDIRECTORY(tests) ENDIF(DEFINED RD_TESTS_PATH) diff --git a/packaging/resourced.spec b/packaging/resourced.spec index ea2f1e1..7d9342d 100644 --- a/packaging/resourced.spec +++ b/packaging/resourced.spec @@ -43,6 +43,7 @@ BuildRequires: pkgconfig(libtzplatform-config) BuildRequires: pkgconfig(storage) BuildRequires: pkgconfig(libgum) BuildRequires: pkgconfig(capi-system-device) +BuildRequires: pkgconfig(cmocka) #only for data types BuildRequires: pkgconfig(tapi) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4c54ad6..07d0828 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -153,6 +153,30 @@ ADD_EXECUTABLE(${RD_BINARY_NAME} ${RESOURCED_SOURCE_DIR}/init.c ${RESOURCED_SOURCE_DIR}/main.c ${SOURCES}) + +SET(SAVE_EXTRA_CFLAGS ${EXTRA_CFLAGS}) +SET(SAVE_CMAKE_C_FLAGS ${CMAKE_C_FLAGS}) +SET(SAVE_CMAKE_C_FLAGS_RELEASE ${CMAKE_C_FLAGS_RELEASE}) +SET(EXTRA_CFLAGS "") +SET(EXTRA_CFLAGS_RELEASE "") +SET(CMAKE_C_FLAGS "") +SET(CMAKE_CXX_FLAGS "") +SET(CMAKE_CXX_FLAGS_RELEASE "") +SET(CMAKE_C_FLAGS_RELEASE "-D_UNIT_TEST -Wp,-U_FORTIFY_SOURCE -U_FORTIFY_SOURCE -O0 -g") +ADD_LIBRARY(resourced_shared_test STATIC + ${RESOURCED_INCLUDE_HEADERS} + ${RESOURCED_SHARED_SOURCES} + ${RESOURCED_SHARED_HEADERS} + ) + +SET_TARGET_PROPERTIES(resourced_shared_test + PROPERTIES COMPILE_FLAGS ${CMAKE_C_FLAGS_RELEASE}) +TARGET_LINK_LIBRARIES(resourced_shared_test ${RESOURCED_REQUIRE_PKGS_LDFLAGS}) + +SET(EXTRA_CFLAGS ${SAVE_EXTRA_CFLAGS}) +SET(CMAKE_C_FLAGS ${SAVE_CMAKE_C_FLAGS}) +SET(CMAKE_C_FLAGS_RELEASE ${SAVE_CMAKE_C_FLAGS_RELEASE}) + TARGET_LINK_LIBRARIES(${RD_BINARY_NAME} resourced-private-api ${RESOURCED_REQUIRE_PKGS_LDFLAGS} "-pie -ldl -lm -Wl,-rpath=${RD_PLUGIN_PATH}") SET_TARGET_PROPERTIES(${RD_BINARY_NAME} PROPERTIES COMPILE_FLAGS "-fvisibility=default") diff --git a/src/common/macro.h b/src/common/macro.h index f67a94d..c8e9a51 100644 --- a/src/common/macro.h +++ b/src/common/macro.h @@ -138,6 +138,7 @@ elem && ((node = elem->data) != NULL); \ elem = elem_next, elem_next = g_slist_next(elem), node = NULL) +#ifndef _UNIT_TEST #define MODULE_REGISTER(module) \ static void __attribute__ ((constructor)) module_init(void) \ { \ @@ -147,6 +148,16 @@ { \ remove_module(module); \ } +#else +/* module is declared in all modules as static struct and the resourced + * codebase is compiled with -Wall so we need to use 'module' pionter + * somehow to prevent from breaking the build process */ +#define MODULE_REGISTER(module) \ + static void __attribute__ ((constructor)) placeholder(void) \ + { \ + ((void)(module)); \ + } +#endif /* Overlap definition to consider three strings as a string */ #define STRING_FORMAT_SPECIFIER_WITH_MACRO(macro) "%"#macro"s" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f44c951..2576482 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,6 +18,34 @@ TARGET_LINK_LIBRARIES(watchdog-test "${GLIB2_LDFLAGS}" "${GIO2_LDFLAGS}") +# run unit tests autimatically whenever building resourced +ADD_CUSTOM_TARGET(do-test ALL make test + WORKING_DIRECTORY ./ + USES_TERMINAL) + +# build unit test +ADD_EXECUTABLE(cmocka-core cmocka-core.c) + +PKG_CHECK_MODULES(CMOCKA REQUIRED cmocka) + +SET(EXTRA_CFLAGS "") +SET(RESOURCED_REQUIRE_PKGS_LDFLAGS "") +SET(CMAKE_C_FLAGS "") +SET(CMAKE_C_FLAGS_RELEASE "") +SET(CMAKE_LDFLAGS "") +SET(CMAKE_LDFLAGS_RELEASE "") + +SET(UNIT_TESTS_CFLAGS "-O0 -D_UNIT_TEST -D_GNU_SOURCE") + +SET_TARGET_PROPERTIES(cmocka-core PROPERTIES COMPILE_FLAGS + "-I${COMMON_SOURCE_DIR} -I/usr/include/dlog -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include ${UNIT_TESTS_CFLAGS}") +TARGET_LINK_LIBRARIES(cmocka-core resourced_shared_test cmocka dlog gio-2.0 gobject-2.0 glib-2.0 + "-Wl,--wrap=malloc,--wrap=free,--wrap=g_slist_append,--wrap=g_slist_remove,--wrap=strdup,--wrap=strndup -O0") + +# add unit test to test target +ADD_TEST(core cmocka-core) +ADD_DEPENDENCIES(do-test cmocka-core) + INSTALL(TARGETS watchdog-test DESTINATION ${RD_TESTS_PATH}) INSTALL(FILES run_tests.sh diff --git a/tests/cmocka-core.c b/tests/cmocka-core.c new file mode 100644 index 0000000..0793cae --- /dev/null +++ b/tests/cmocka-core.c @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "notifier.h" +#include "config-parser.h" + +typedef int (*noti_cb)(void *data); + +void *__real_malloc(size_t size); +void __real_free(void *ptr); +GSList *__real_g_slist_append(GSList *list, gpointer data); +GSList *__real_g_slist_remove(GSList *list, gconstpointer data); + +struct resourced_notifier { + enum notifier_type status; + noti_cb func; +}; + +struct resourced_notifier *notifier; + +int test_notifier_cb(void *data) +{ + check_expected_ptr(data); + return 0; +} + +void __wrap_free(void *ptr) +{ + bool check = mock_type(bool); + if (check) + check_expected_ptr(ptr); + __real_free(ptr); +} + +void *__wrap_malloc(size_t size) +{ + bool fake = mock_type(bool); + if (fake) + return NULL; + return __real_malloc(size); +} + +GSList *__wrap_g_slist_append(GSList *list, gpointer data) +{ + assert(data); + bool wrap_append = mock_type(bool); + if (!wrap_append) + return __real_g_slist_append(list, data); + + notifier = (struct resourced_notifier *)data; + + if (!notifier->func) + return NULL; + int t = mock_type(int); + void *f = mock_ptr_type(void *); + + assert(t == (int) notifier->status); + assert(f == (void*) notifier->func); + + (void) t; + (void) f; + + GSList *l = (GSList *)calloc(1, sizeof (GSList)); + assert(l); + l->data = data; + + return l; +} + +GSList *__wrap_g_slist_remove(GSList *list, gconstpointer data) +{ + bool wrap_remove = mock_type(bool); + if (!wrap_remove) + return __real_g_slist_remove(list, data); + + if (!list) + return NULL; + + struct resourced_notifier *n = (struct resourced_notifier *)data; + assert(n == notifier); + __real_free(list); + + (void) n; + return NULL; +} + +static void test_register_notifier(void **state) +{ + (void) state; /* unused */ + + void *fptr = test_notifier_cb; + assert_int_equal(register_notifier(1, NULL), -EINVAL); + + will_return(__wrap_malloc, true); + assert_int_equal(register_notifier(1, fptr), -ENOMEM); + will_return_maybe(__wrap_malloc, false); + + will_return(__wrap_g_slist_append, true); + will_return(__wrap_g_slist_append, 1); + will_return(__wrap_g_slist_append, cast_ptr_to_largest_integral_type(fptr)); + assert_int_equal(register_notifier(1, fptr), 0); + assert_int_equal(register_notifier(1, fptr), -EINVAL); +} + +static void test_notify(void **state) +{ + (void) state; /* unused */ + const char *d = "xoxo"; + expect_string(test_notifier_cb, data, d); + resourced_notify(1, (void *)d); +} + +static void test_unregister_notifier(void **state) +{ + (void) state; /* unused */ + void *fptr = test_notifier_cb; + assert_int_equal(unregister_notifier(1, NULL), -EINVAL); + + will_return(__wrap_free, true); + expect_value(__wrap_free, ptr, cast_ptr_to_largest_integral_type(notifier)); + will_return(__wrap_g_slist_remove, true); + assert_int_equal(unregister_notifier(1, fptr), 0); +} + +static int test_setup(void **state) +{ + will_return_maybe(__wrap_malloc, false); + return 0; +} + +int main(int argc, char* argv[]) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_register_notifier), + cmocka_unit_test(test_notify), + cmocka_unit_test(test_unregister_notifier), + + }; + return cmocka_run_group_tests(tests, test_setup, NULL); +} -- 2.7.4 From a0a8a25bd80090e54ce8bb94d7492710e8be8a63 Mon Sep 17 00:00:00 2001 From: Maciej Slodczyk Date: Tue, 5 Nov 2019 17:58:21 +0100 Subject: [PATCH 06/16] unit tests: config parser Change-Id: If103e1b6a8a9d8ae647ea081fc5a9681c5767881 Signed-off-by: Maciej Slodczyk --- tests/cmocka-core.c | 152 +++++++++++++++++++++++++++++++++++++++- tests/config_parser_test_1.conf | 1 + tests/config_parser_test_2.conf | 2 + tests/config_parser_test_3.conf | 2 + tests/config_parser_test_4.conf | 140 ++++++++++++++++++++++++++++++++++++ tests/config_parser_test_5.conf | 8 +++ 6 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 tests/config_parser_test_1.conf create mode 100644 tests/config_parser_test_2.conf create mode 100644 tests/config_parser_test_3.conf create mode 100644 tests/config_parser_test_4.conf create mode 100644 tests/config_parser_test_5.conf diff --git a/tests/cmocka-core.c b/tests/cmocka-core.c index 0793cae..01750ed 100644 --- a/tests/cmocka-core.c +++ b/tests/cmocka-core.c @@ -1,12 +1,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include "notifier.h" #include "config-parser.h" @@ -14,6 +16,8 @@ typedef int (*noti_cb)(void *data); void *__real_malloc(size_t size); void __real_free(void *ptr); +char *__real_strdup(const char *s); +char *__real_strndup(const char *s, size_t n); GSList *__real_g_slist_append(GSList *list, gpointer data); GSList *__real_g_slist_remove(GSList *list, gconstpointer data); @@ -30,6 +34,45 @@ int test_notifier_cb(void *data) return 0; } +int config_parser_cb(struct parse_result *result, void *user_data) +{ + int t = mock_type(int); + if (t == 1) + return -1; + check_expected_ptr(user_data); + + char *s = mock_ptr_type(char *); + char *k = mock_ptr_type(char *); + char *v = mock_ptr_type(char *); + + assert(!strcmp(s, result->section)); + assert(!strcmp(k, result->name)); + assert(!strcmp(v, result->value)); + + (void) s; + (void) k; + (void) v; + return 0; +} + +char *__wrap_strdup(const char *s) +{ + bool fake = mock_type(bool); + if (fake) + return NULL; + + return __real_strdup(s); +} + +char *__wrap_strndup(const char *s, size_t n) +{ + bool fake = mock_type(bool); + if (fake) + return NULL; + + return __real_strndup(s, n); +} + void __wrap_free(void *ptr) { bool check = mock_type(bool); @@ -128,6 +171,112 @@ static void test_unregister_notifier(void **state) assert_int_equal(unregister_notifier(1, fptr), 0); } +static void test_config_parse(void **state) +{ + (void) state; /* unused */ + const char *section = "section"; + const char *key = "key"; + const char *value = "value"; + void *ptr = (void*)0xD00D1E; + + /* null pointer test */ + assert_int_equal(config_parse(NULL, config_parser_cb, NULL), -EINVAL); + + /* null pointer test */ + assert_int_equal(config_parse("/dev/null", NULL, NULL), -EINVAL); + + /* non-existing file test */ + assert_int_equal(config_parse("the_x_files", config_parser_cb, NULL), -EIO); + + /* invalid section header test */ + assert_int_equal(config_parse("../../tests/config_parser_test_1.conf", config_parser_cb, NULL), -EBADMSG); + + /* key with no value test */ + assert_int_equal(config_parse("../../tests/config_parser_test_2.conf", config_parser_cb, NULL), -EBADMSG); + + /* callback return value test */ + will_return(config_parser_cb, 1); + assert_int_equal(config_parse("../../tests/config_parser_test_3.conf", config_parser_cb, NULL), -EBADMSG); + + /* positive test */ + will_return(config_parser_cb, 0); + expect_value(config_parser_cb, user_data, cast_ptr_to_largest_integral_type(ptr)); + will_return(config_parser_cb, cast_ptr_to_largest_integral_type(section)); + will_return(config_parser_cb, cast_ptr_to_largest_integral_type(key)); + will_return(config_parser_cb, cast_ptr_to_largest_integral_type(value)); + assert_int_equal(config_parse("../../tests/config_parser_test_3.conf", config_parser_cb, ptr), 0); +} + +static void test_config_parse_new(void **state) +{ + (void) state; /* unused */ + ConfigTableItem dummy_items[] = { + { NULL, NULL, NULL, 0, NULL } + }; + + char *str = NULL; + bool b = false; + int i = 0; + float f = 100.0; + int bytes = 1; + ConfigTableItem items[] = { + { "section", "key_s", config_parse_string, 0, &str }, + { "section", "key_b", config_parse_bool, 0, &b }, + { "section", "key_i", config_parse_int, 0, &i }, + { "section", "key_f", config_parse_float, 0, &f }, + { "section", "key_bytes", config_parse_bytes, 0, &bytes }, + { NULL, NULL, NULL, 0, NULL } + }; + + will_return_maybe(__wrap_free, false); + /* non-existing file test */ + assert_int_equal(config_parse_new("the_x_files", NULL), -ENOENT); + + /* invalid section header test */ + assert_int_equal(config_parse_new("../../tests/config_parser_test_1.conf", NULL), -EBADMSG); + + /* oom test 1 */ + will_return(__wrap_strndup, true); + assert_int_equal(config_parse_new("../../tests/config_parser_test_2.conf", NULL), -ENOMEM); + + /* oom test 2 */ + will_return(__wrap_strndup, false); + will_return(__wrap_strndup, true); + assert_int_equal(config_parse_new("../../tests/config_parser_test_3.conf", NULL), -ENOMEM); + + /* oom test 2 */ + will_return(__wrap_strndup, false); + will_return(__wrap_strndup, false); + will_return(__wrap_strdup, true); + assert_int_equal(config_parse_new("../../tests/config_parser_test_3.conf", NULL), -ENOMEM); + + /* too much data in a file test */ + will_return_count(__wrap_strndup, false, 131); + will_return_count(__wrap_strdup, false, 65); + assert_int_equal(config_parse_new("../../tests/config_parser_test_4.conf", dummy_items), -EOVERFLOW); + + /* lookup keys not present in config */ + will_return_count(__wrap_strndup, false, 2); + will_return_count(__wrap_strdup, false, 1); + assert_int_equal(config_parse_new("../../tests/config_parser_test_3.conf", items), 0); + assert(str == NULL); + assert(b == false); + assert (i == 0); + assert(f == 100.0); + assert(bytes = 1); + + /* positive test */ + will_return_count(__wrap_strndup, false, 8); + will_return_count(__wrap_strdup, false, 7); + assert_int_equal(config_parse_new("../../tests/config_parser_test_5.conf", items), 0); + assert(str); + assert(!strcmp(str, "xD")); + assert(b == true); + assert(i == 123); + assert(fabsf(f - 1.23) < 1e-6); + assert(bytes == 10485760); +} + static int test_setup(void **state) { will_return_maybe(__wrap_malloc, false); @@ -140,7 +289,8 @@ int main(int argc, char* argv[]) cmocka_unit_test(test_register_notifier), cmocka_unit_test(test_notify), cmocka_unit_test(test_unregister_notifier), - + cmocka_unit_test(test_config_parse), + cmocka_unit_test(test_config_parse_new), }; return cmocka_run_group_tests(tests, test_setup, NULL); } diff --git a/tests/config_parser_test_1.conf b/tests/config_parser_test_1.conf new file mode 100644 index 0000000..70a8368 --- /dev/null +++ b/tests/config_parser_test_1.conf @@ -0,0 +1 @@ +[section diff --git a/tests/config_parser_test_2.conf b/tests/config_parser_test_2.conf new file mode 100644 index 0000000..36fad3b --- /dev/null +++ b/tests/config_parser_test_2.conf @@ -0,0 +1,2 @@ +[section] +key diff --git a/tests/config_parser_test_3.conf b/tests/config_parser_test_3.conf new file mode 100644 index 0000000..877008f --- /dev/null +++ b/tests/config_parser_test_3.conf @@ -0,0 +1,2 @@ +[section] +key=value diff --git a/tests/config_parser_test_4.conf b/tests/config_parser_test_4.conf new file mode 100644 index 0000000..d422abc --- /dev/null +++ b/tests/config_parser_test_4.conf @@ -0,0 +1,140 @@ +[section0] +key=value +[section1] +key=value +[section2] +key=value +[section3] +key=value +[section4] +key=value +[section5] +key=value +[section6] +key=value +[section7] +key=value +[section8] +key=value +[section9] +key=value +[section10] +key=value +[section11] +key=value +[section12] +key=value +[section13] +key=value +[section14] +key=value +[section15] +key=value +[section16] +key=value +[section17] +key=value +[section18] +key=value +[section19] +key=value +[section20] +key=value +[section21] +key=value +[section22] +key=value +[section23] +key=value +[section24] +key=value +[section25] +key=value +[section26] +key=value +[section27] +key=value +[section28] +key=value +[section29] +key=value +[section30] +key=value +[section31] +key=value +[section32] +key=value +[section33] +key=value +[section34] +key=value +[section35] +key=value +[section36] +key=value +[section37] +key=value +[section38] +key=value +[section39] +key=value +[section30] +key=value +[section41] +key=value +[section42] +key=value +[section43] +key=value +[section44] +key=value +[section45] +key=value +[section46] +key=value +[section47] +key=value +[section48] +key=value +[section49] +key=value +[section50] +key=value +[section51] +key=value +[section52] +key=value +[section53] +key=value +[section54] +key=value +[section55] +key=value +[section56] +key=value +[section57] +key=value +[section58] +key=value +[section59] +key=value +[section60] +key=value +[section61] +key=value +[section62] +key=value +[section63] +key=value +[section64] +key=value +[section65] +key=value +[section66] +key=value +[section67] +key=value +[section68] +key=value +[section69] +key=value diff --git a/tests/config_parser_test_5.conf b/tests/config_parser_test_5.conf new file mode 100644 index 0000000..90671bb --- /dev/null +++ b/tests/config_parser_test_5.conf @@ -0,0 +1,8 @@ +[section] +key_s=xD +key_b=y +key_i=123 +key_f=1.23 +key_l=1230000 +key_bytes=10M + -- 2.7.4 From f7955964dc21665de08d12df651ef0e8f41095f0 Mon Sep 17 00:00:00 2001 From: Maciej Slodczyk Date: Thu, 14 Nov 2019 17:20:55 +0100 Subject: [PATCH 07/16] unit tests: fd-handler Change-Id: Id52044e532cc1873c6838f06e24c5f72229e107f Signed-off-by: Maciej Slodczyk --- tests/cmocka-core.c | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 162 insertions(+), 4 deletions(-) diff --git a/tests/cmocka-core.c b/tests/cmocka-core.c index 01750ed..9cc2852 100644 --- a/tests/cmocka-core.c +++ b/tests/cmocka-core.c @@ -11,6 +11,7 @@ #include #include "notifier.h" #include "config-parser.h" +#include "fd-handler.h" typedef int (*noti_cb)(void *data); @@ -283,14 +284,171 @@ static int test_setup(void **state) return 0; } +struct test_fd_read_handler_ctx { + fd_handler_h h; + int fd[2]; + int flag; + GMainLoop *loop; +}; + +bool test_fd_read_handler_cb(int fd, void *data) +{ + struct test_fd_read_handler_ctx *c = (struct test_fd_read_handler_ctx *) data; + int check = mock_type(int); + + if (check == 1) { + check_expected(fd); + check_expected_ptr(data); + } + g_main_loop_quit(c->loop); + return TRUE; + +} + +static void test_fd_read_handler_release_cb(void *data) +{ + struct test_fd_read_handler_ctx *c = (struct test_fd_read_handler_ctx *) data; + int check = mock_type(int); + + if (check == 1) + check_expected_ptr(data); + c->flag++; +} + +static int test_fd_read_handler_setup(void **state) +{ + struct test_fd_read_handler_ctx *c = calloc(1, sizeof (struct test_fd_read_handler_ctx)); + + if (c == NULL) + return -1; + + if (pipe(c->fd) < 0) + return -1; + + c->loop = g_main_loop_new(NULL, FALSE); + + will_return_maybe(__wrap_malloc, false); + *state = c; + + return 0; +} + +static int test_fd_read_handler_teardown(void **state) +{ + struct test_fd_read_handler_ctx *c = (struct test_fd_read_handler_ctx *) *state; + assert(c); + assert(c->fd >= 0); + + close(c->fd[0]); + close(c->fd[1]); + + if (c->loop) + g_main_loop_unref(c->loop); + + will_return(__wrap_free, false); + free(c); + return 0; +} + +static void test_add_fd_read_handler(void **state) +{ + struct test_fd_read_handler_ctx *c = (struct test_fd_read_handler_ctx *) *state; + assert(c != NULL); + assert(c->fd >= 0); + + assert_int_equal(add_fd_read_handler(-1, NULL, NULL, NULL, &(c->h)), -EINVAL); + assert_int_equal(add_fd_read_handler(231, NULL, NULL, NULL, &(c->h)), -EINVAL); + assert_int_equal(add_fd_read_handler(c->fd[0], test_fd_read_handler_cb, c, test_fd_read_handler_release_cb, &(c->h)), 0); +} + +static gboolean test_fd_read_handler_start(gpointer data) +{ + struct test_fd_read_handler_ctx *c = (struct test_fd_read_handler_ctx *) data; + int r = write(c->fd[1], "hi", 2); + assert(r > 0); + + return FALSE; +} + +static gboolean test_fd_read_handler_terminate(gpointer data) +{ + struct test_fd_read_handler_ctx *c = (struct test_fd_read_handler_ctx *) data; + c->flag = 1; + g_main_loop_quit(c->loop); + return FALSE; +} + + +static void test_fd_read_handler(void **state) +{ + struct test_fd_read_handler_ctx *c = (struct test_fd_read_handler_ctx *) *state; + + assert(c != NULL); + assert(c->fd >= 0); + + will_return(test_fd_read_handler_cb, 1); + expect_value(test_fd_read_handler_cb, fd, c->fd[0]); + expect_value(test_fd_read_handler_cb, data, cast_ptr_to_largest_integral_type(c)); + + /* defer write to a pipe and run mainloop */ + g_timeout_add_seconds(0, test_fd_read_handler_start, c); + g_main_loop_run(c->loop); +} + + +static void test_remove_fd_read_handler(void **state) +{ + struct test_fd_read_handler_ctx *c = (struct test_fd_read_handler_ctx *) *state; + assert(c != NULL); + assert(c->fd >= 0); + + c->flag = 0; + assert_int_equal(remove_fd_read_handler(NULL), -EINVAL); + will_return(__wrap_free, false); + will_return(test_fd_read_handler_release_cb, 1); + expect_value(test_fd_read_handler_release_cb, data, cast_ptr_to_largest_integral_type(c)); + assert_int_equal(remove_fd_read_handler(&(c->h)), 0); + assert_int_equal(c->flag, 1); + + /* defer write to a pipe and run mainloop, the callback should not be called */ + g_timeout_add_seconds(0, test_fd_read_handler_start, c); + + /* flag should be changed in test_fd_read_handler_terminate() */ + c->flag = 0; + g_timeout_add_seconds(2, test_fd_read_handler_terminate, c); + g_main_loop_run(c->loop); + assert_int_equal(c->flag, 1); +} + int main(int argc, char* argv[]) { - const struct CMUnitTest tests[] = { + int r; + const struct CMUnitTest notifier_tests[] = { cmocka_unit_test(test_register_notifier), cmocka_unit_test(test_notify), - cmocka_unit_test(test_unregister_notifier), + cmocka_unit_test(test_unregister_notifier) + }; + const struct CMUnitTest config_parser_tests[] = { cmocka_unit_test(test_config_parse), - cmocka_unit_test(test_config_parse_new), + cmocka_unit_test(test_config_parse_new) }; - return cmocka_run_group_tests(tests, test_setup, NULL); + const struct CMUnitTest fd_read_handler_tests[] = { + cmocka_unit_test(test_add_fd_read_handler), + cmocka_unit_test(test_fd_read_handler), + cmocka_unit_test(test_remove_fd_read_handler), + }; + + r = cmocka_run_group_tests(notifier_tests, test_setup, NULL); + if (r != 0) + return r; + + r = cmocka_run_group_tests(config_parser_tests, test_setup, NULL); + if (r != 0) + return r; + + r = cmocka_run_group_tests(fd_read_handler_tests, test_fd_read_handler_setup, test_fd_read_handler_teardown); + if (r != 0) + return r; + + return r; } -- 2.7.4 From ffdd80579b4c686100e1b9546a8db99408cf0292 Mon Sep 17 00:00:00 2001 From: Michal Bloch Date: Wed, 4 Dec 2019 15:27:19 +0100 Subject: [PATCH 08/16] Fix `aarch64` builds Change-Id: I0636a683b342bd8e2460737260b7e6934d977d7e Signed-off-by: Michal Bloch --- tests/CMakeLists.txt | 2 +- tests/cmocka-core.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2576482..aa27d09 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -38,7 +38,7 @@ SET(CMAKE_LDFLAGS_RELEASE "") SET(UNIT_TESTS_CFLAGS "-O0 -D_UNIT_TEST -D_GNU_SOURCE") SET_TARGET_PROPERTIES(cmocka-core PROPERTIES COMPILE_FLAGS - "-I${COMMON_SOURCE_DIR} -I/usr/include/dlog -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include ${UNIT_TESTS_CFLAGS}") + "-I${COMMON_SOURCE_DIR} -I/usr/include/dlog ${GLIB2_CFLAGS} ${UNIT_TESTS_CFLAGS}") TARGET_LINK_LIBRARIES(cmocka-core resourced_shared_test cmocka dlog gio-2.0 gobject-2.0 glib-2.0 "-Wl,--wrap=malloc,--wrap=free,--wrap=g_slist_append,--wrap=g_slist_remove,--wrap=strdup,--wrap=strndup -O0") diff --git a/tests/cmocka-core.c b/tests/cmocka-core.c index 9cc2852..3d3f132 100644 --- a/tests/cmocka-core.c +++ b/tests/cmocka-core.c @@ -219,7 +219,7 @@ static void test_config_parse_new(void **state) bool b = false; int i = 0; float f = 100.0; - int bytes = 1; + size_t bytes = 1; ConfigTableItem items[] = { { "section", "key_s", config_parse_string, 0, &str }, { "section", "key_b", config_parse_bool, 0, &b }, -- 2.7.4 From a5c04cc1c000d3c5030979f0db8b1ab8bd00a06d Mon Sep 17 00:00:00 2001 From: Maciej Slodczyk Date: Tue, 10 Dec 2019 11:08:46 +0100 Subject: [PATCH 09/16] remove duplicated code Change-Id: I0a9dfa4534eb87c8e665268ff08b2060762a99f4 Signed-off-by: Maciej Slodczyk --- src/common/config-parser.c | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/common/config-parser.c b/src/common/config-parser.c index 0e4e8fa..627fc0a 100644 --- a/src/common/config-parser.c +++ b/src/common/config-parser.c @@ -28,20 +28,6 @@ #define MAX_SECTION 64 -static inline char *trim_str(char *s) -{ - char *t; - /* left trim */ - s += strspn(s, WHITESPACE); - - /* right trim */ - for (t = strchr(s, 0); t > s; t--) - if (!strchr(WHITESPACE, t[-1])) - break; - *t = 0; - return s; -} - int config_parse(const char *file_name, int cb(struct parse_result *result, void *user_data), void *user_data) { @@ -72,7 +58,7 @@ int config_parse(const char *file_name, int cb(struct parse_result *result, start = line; truncate_nl(start); - start = trim_str(start); + start = strstrip(start); if (*start == COMMENT) { continue; @@ -95,12 +81,12 @@ int config_parse(const char *file_name, int cb(struct parse_result *result, goto error; } *end = '\0'; - name = trim_str(start); - value = trim_str(end + 1); + name = strstrip(start); + value = strstrip(end + 1); end = strchr(value, COMMENT); if (end && *end == COMMENT) { *end = '\0'; - value = trim_str(value); + value = strstrip(value); } result.section = section; -- 2.7.4 From 02301e35945abbdd31ffe8655de5364473172f2f Mon Sep 17 00:00:00 2001 From: Adrian Szyndela Date: Mon, 16 Dec 2019 17:16:49 +0100 Subject: [PATCH 10/16] lowmem: fix memleak on error Change-Id: I10a70086f0302335c9023b4892388b2b3209674c --- src/memory/lowmem-limit.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/memory/lowmem-limit.c b/src/memory/lowmem-limit.c index 35e8dd7..a289659 100644 --- a/src/memory/lowmem-limit.c +++ b/src/memory/lowmem-limit.c @@ -178,9 +178,15 @@ static void memlimit_finish_cb(void *data, int ret) { struct memory_limit_log *mlog = (struct memory_limit_log *)data; - if (!mlog || !mlog->cgdir) + if (!mlog) return; + if (!mlog->cgdir) { + free(mlog->appname); + free(mlog); + return; + } + /* * send sigabt signal * If debug is enabled, it makes crash popup with log files. @@ -196,10 +202,8 @@ static void memlimit_finish_cb(void *data, int ret) else if (mem_limit == MEM_LIMIT_OOM) cgroup_write_node_uint32(mlog->cgdir, MEMCG_OOM_CONTROL_PATH, 0); - if (mlog->appname) - free(mlog->appname); - if (mlog->cgdir) - free(mlog->cgdir); + free(mlog->appname); + free(mlog->cgdir); free(mlog); } @@ -356,8 +360,7 @@ static bool lowmem_limit_cb(int fd, void *data) remove_mle: g_hash_table_remove(memory_limit_hash, cg_dir); - if (mlog) - free(mlog); + free(mlog); return false; } -- 2.7.4 From e84f2cf17d3d9502df11be91e89decbb6e0354b1 Mon Sep 17 00:00:00 2001 From: Adrian Szyndela Date: Tue, 17 Dec 2019 13:40:36 +0100 Subject: [PATCH 11/16] memory: allow lowmem_limit to reinit for tests This sets memory_limit_hash to NULL after freeing memory associated with it. This way we can initialize the module again. It helps with performing consecutive tests within a single binary. Change-Id: Icfe2755c1d7689a314d04d0056c9cac05bdafcd7 --- src/memory/lowmem-limit.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/memory/lowmem-limit.c b/src/memory/lowmem-limit.c index a289659..bca5834 100644 --- a/src/memory/lowmem-limit.c +++ b/src/memory/lowmem-limit.c @@ -651,8 +651,12 @@ void lowmem_limit_init(void) void lowmem_limit_exit(void) { - if (memory_limit_hash) + if (memory_limit_hash) { g_hash_table_destroy(memory_limit_hash); +#ifdef _UNIT_TEST + memory_limit_hash = NULL; +#endif + } if (mem_service_limit) unregister_notifier(RESOURCED_NOTIFIER_SERVICE_LAUNCH, lowmem_limit_service); -- 2.7.4 From 023b7512f07136cb38da1e49cbc57d95b523d5df Mon Sep 17 00:00:00 2001 From: Adrian Szyndela Date: Tue, 17 Dec 2019 15:13:02 +0100 Subject: [PATCH 12/16] spec: allow incremental builds This makes 'mkdir -p' from 'mkdir' to silence mkdir from complaining when created directory already exists. This is needed for incremental builds, as by their very nature they build over something that was already built. That is, 'build' directory was created with previous build, and now, while building we don't want to bail out with error, because it already exists. Enough said. This is good stuff. Really. Change-Id: Iaba6fa9d6ecb0d1a9bc4040465c4d979df377695 --- packaging/resourced.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/resourced.spec b/packaging/resourced.spec index 7d9342d..2041f94 100644 --- a/packaging/resourced.spec +++ b/packaging/resourced.spec @@ -116,7 +116,7 @@ Summary: vip-release-agent module for resourced %setup -q %build -mkdir build +mkdir -p build pushd build %cmake .. -DFULLVER=%{version} \ -DCMAKE_BUILD_TYPE=Release \ -- 2.7.4 From deb78de7c63bf6d06dad268d2c259fd8a3a60af8 Mon Sep 17 00:00:00 2001 From: Adrian Szyndela Date: Mon, 16 Dec 2019 13:32:02 +0100 Subject: [PATCH 13/16] tests: lowmem-limit This adds tests framework for lowmem_limit module. The framework consists of: - mocking layer (lowmem-limit-mock); - mocked environment (lowmem-limit-env) - mocked system functions and implemented other functions used by lowmem_limit (lowmem-limit-env); - test driver (lowmem-limit-test) - contains test cases and runs them. Change-Id: Id70b8d755bbde4accb7c45d193e7f7c77523d007 --- tests/CMakeLists.txt | 22 +++ tests/lowmem-limit-env.cpp | 389 ++++++++++++++++++++++++++++++++++++++++++++ tests/lowmem-limit-env.hpp | 119 ++++++++++++++ tests/lowmem-limit-mock.cpp | 109 +++++++++++++ tests/lowmem-limit-test.cpp | 221 +++++++++++++++++++++++++ 5 files changed, 860 insertions(+) create mode 100644 tests/lowmem-limit-env.cpp create mode 100644 tests/lowmem-limit-env.hpp create mode 100644 tests/lowmem-limit-mock.cpp create mode 100644 tests/lowmem-limit-test.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index aa27d09..5727692 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -46,6 +46,28 @@ 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) +# 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) + INSTALL(TARGETS watchdog-test DESTINATION ${RD_TESTS_PATH}) INSTALL(FILES run_tests.sh diff --git a/tests/lowmem-limit-env.cpp b/tests/lowmem-limit-env.cpp new file mode 100644 index 0000000..7bc1a1e --- /dev/null +++ b/tests/lowmem-limit-env.cpp @@ -0,0 +1,389 @@ +#include +#include +#include + +#include + +extern "C" { +#include +#include "notifier.h" +} + +#include "lowmem-limit-env.hpp" + +LowmemLimitEnv *global_test_lowmem_limit_env{nullptr}; + +LowmemLimitEnv::LowmemLimitEnv(MemLimitType limit_type, unsigned long limit_value, unsigned long ram) +{ + assert_null(global_test_lowmem_limit_env); + + configure_mem_limit_type(limit_type); + configure_mem_limit_value(limit_value); + configure_ktotalram(ram); + + global_test_lowmem_limit_env = this; +} + +LowmemLimitEnv::~LowmemLimitEnv() +{ + assert_non_null(global_test_lowmem_limit_env); + global_test_lowmem_limit_env = nullptr; +} + +void LowmemLimitEnv::reinit() +{ + mem_limit_type = "none"; + mem_limit_value = "0"; + ktotalram = 0; + notifiers.clear(); + apps.clear(); + fd_handlers.clear(); + cgroup_memory.clear(); + cgroup_memory[default_cgroup_memory] = CGroupData{{default_cgroup_usage}}; + eventfds.clear(); + last_eventfd = INITIAL_FD; +} + +void LowmemLimitEnv::configure_mem_limit_type(MemLimitType type) +{ + switch (type) { + case MemLimitType::OOM: + mem_limit_type = "oom"; + break; + case MemLimitType::THRESHOLD: + mem_limit_type = "threshold"; + break; + default: + mem_limit_type = "none"; + } +} + +void LowmemLimitEnv::configure_mem_limit_value(unsigned long value) +{ + mem_limit_value = std::to_string(value); +} + +void LowmemLimitEnv::configure_ktotalram(unsigned long value) +{ + ktotalram = value; +} + +LowmemLimitEnv::AppTestValuesHolder::~AppTestValuesHolder() +{ + g_slist_free(app_info.childs); +} + +void LowmemLimitEnv::configure_add_app(pid_t pid, const char *appid, Usages &&u) +{ + pid_list pid_list = nullptr; + for (const auto &e: u) { + if (e.first != pid) + pid_list = g_slist_prepend(pid_list, PID_TO_GPOINTER(e.first)); + } + pid_list = g_slist_reverse(pid_list); + + auto it = apps.insert(std::make_pair(pid, AppTestValuesHolder{{.appid = const_cast(appid), .main_pid = pid}, u})).first; + it->second.app_info.childs = pid_list; +} + +void LowmemLimitEnv::configure_remove_app(pid_t pid) +{ + apps.erase(pid); +} + +void LowmemLimitEnv::configure_cgroup_usages(const char *cgroup, unsigned long sw_usage, unsigned long usage) +{ + auto cg_name = std::string("/sys/fs/cgroup/memory/MemLimit/") + cgroup; + auto &cg = cgroup_memory[cg_name]; + cg.files["memory.memsw.usage_in_bytes"] = std::to_string(sw_usage); + cg.files["memory.usage_in_bytes"] = std::to_string(usage); + cg.memory_stat = {0, usage, 0, 0, 0, 0, sw_usage}; +} + +int LowmemLimitEnv::config_parse(const char *, ConfigParseCallbackFun cb, void *user_data) { + + auto callback = [&cb, &user_data] (const char *section, const char *name, const char *value) { + struct parse_result result; + result.section = const_cast(section); + result.name = const_cast(name); + result.value = const_cast(value); + return cb(&result, user_data); + }; + + if (callback("MemLimit", "MemLimitTrigger", mem_limit_type.c_str()) < 0) + return -EBADMSG; + + if (callback("MemLimit", "MemLimitService", mem_limit_value.c_str()) < 0) + return -EBADMSG; + + 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); +} + +unsigned long LowmemLimitEnv::lowmem_get_ktotalram() +{ + return ktotalram; // in bytes +} + +struct proc_app_info *LowmemLimitEnv::find_app_info(const pid_t pid) +{ + // We have two options here - either we result NULL, then the client looks up the app by cmdline + // or we return here proc_app_info with appid set. + // proc_app_info may contains 'childs' which are processed, too. + auto it = apps.find(pid); + std::cerr << " -> app " << pid << (it == apps.end() ? " not" : "") << " found\n"; + return it != apps.end() ? &it->second.app_info : NULL; +} + +int LowmemLimitEnv::memcg_set_eventfd(const char *memcg, const char *event, char *value) +{ + std::cerr << " memcg=" << memcg << ", event=" << event << ", value=" << value << std::endl; + + eventfds[std::string(memcg) + "/" + event] = ++last_eventfd; + + return last_eventfd; +} + +int LowmemLimitEnv::add_fd_read_handler(int fd, fd_changed_cb callback, + void *data, release_cb free_func, fd_handler_h *handler) +{ + fd_handlers.insert(std::make_pair(fd, FDReadHandler{callback, data, free_func, handler})); + return 0; +} + +void LowmemLimitEnv::event_cgroup_eventfd(const std::string &cgroup_event) +{ + const auto &p = eventfds.find(std::string("/sys/fs/cgroup/memory/MemLimit/") + cgroup_event); + if (p != eventfds.end()) { + event_fd_handler(p->second); + } else { + std::cerr << " -> event " << cgroup_event << " not found\n"; + } +} + +void LowmemLimitEnv::trigger_oom_event(const std::string &cgroup_name) +{ + event_cgroup_eventfd(std::string(cgroup_name) + "/memory.oom_control"); +} + +void LowmemLimitEnv::trigger_threshold_event(const std::string &cgroup_name) +{ + event_cgroup_eventfd(std::string(cgroup_name) + "/memory.memsw.usage_in_bytes"); +} + +void LowmemLimitEnv::event_fd_handler(int fd) +{ + auto fdh = fd_handlers.find(fd); + + assert_true(fdh != fd_handlers.end()); + + fdh->second.callback(fd, fdh->second.data); +} + +std::pair LowmemLimitEnv::kill(pid_t pid, int sig) +{ + std::cerr << " pid=" << pid << " sig=" << sig << std::endl; + configure_remove_app(pid); + check_expected(pid); + return std::make_pair(true, 0); +} + +std::pair LowmemLimitEnv::read(int fd, void *buf, size_t count) +{ + std::cerr << " fake read from fd=" << fd << std::endl; + if (fd >= INITIAL_FD) { + return std::make_pair(true, count); + } + return std::make_pair(false, 0); +} + +std::pair LowmemLimitEnv::access(const char *pathname, int mode) +{ + std::cerr << " pathname=" << pathname << std::endl; + return std::make_pair(cgroup_memory.find(pathname) != cgroup_memory.end(), 0); +} + +int LowmemLimitEnv::proc_get_mem_usage(pid_t pid, unsigned int *usage) +{ + std::cerr << " pid=" << pid << std::endl << " -> "; + *usage = 0; + + auto it = apps.find(pid); + if (it != apps.end()) { + for (auto &u: it->second.usages) { + *usage += u.second; + } + } else { + std::cerr << "not found\n"; + return RESOURCED_ERROR_FAIL; + } + std::cerr << *usage << std::endl; + return RESOURCED_ERROR_NONE; +} + +int LowmemLimitEnv::cgroup_get_pids(const char *name, GArray **pids) +{ + // name is passed with an additional slash... + std::cerr << " name=" << name << std::endl; + + std::string cgname = name; + if (cgname[cgname.size()-1] == '/') + cgname.resize(cgname.size()-1); + + std::cerr << " -> "; + const auto &cg = cgroup_memory.find(cgname); + if (cg != cgroup_memory.end()) { + *pids = g_array_sized_new(false, false, sizeof(pid_t), cg->second.pids.size()); + for (auto p: cg->second.pids) { + g_array_append_val(*pids, p); + std::cerr << p << " "; + } + std::cerr << std::endl; + } else { + std::cerr << "cgroup " << cgname << " pids not found\n"; + } + return 0; +} + +int LowmemLimitEnv::exec_cmd(int argc, const char *argv[]) +{ + std::cerr << " argv[0]=" << argv[0] << ", argc=" << argc << std::endl; + /* FIXME It might be wrong to mock it here */ + return 0; +} + +void LowmemLimitEnv::make_memps_log(char *file, pid_t pid, char *victim_name) +{ + // IGNORE THIS CALL +} + +int LowmemLimitEnv::cgroup_write_node_uint32(const char *cgroup_name, const char *file_name, uint32_t value) +{ + std::cerr << " cgroup_name=" << cgroup_name << ", file_name=" << file_name << ", value=" << value << std::endl; + check_expected(cgroup_name); + check_expected(file_name); + check_expected(value); + + const std::string fname = file_name; + + if (fname == "/cgroup.procs") { + cgroup_memory[cgroup_name].pids.push_back(value); + } else { + cgroup_memory[cgroup_name].files[fname] = std::to_string(value); + } + return 0; +} + +int LowmemLimitEnv::cgroup_read_node_uint32(const char *cgroup_name, const char *file_name, uint32_t *value) +{ + std::cerr << " cgroup_name=" << cgroup_name << ", file_name=" << file_name << std::endl; + std::cerr << " -> "; + auto it = cgroup_memory.find(cgroup_name); + if (it == cgroup_memory.end()) { + std::cerr << "cgroup " << cgroup_name << " not found\n"; + return -1; + } + + auto it_file = it->second.files.find(file_name); + if (it_file == it->second.files.end()) { + std::cerr << "file " << file_name << " not found\n"; + return -1; + } + + *value = std::stoi(it_file->second); + std::cerr << *value << std::endl; + + return 0; +} + +int LowmemLimitEnv::cgroup_get_memory_stat(const char *name, struct cgroup_memory_stat **mem_stat) +{ + std::cerr << " name=" << name << std::endl; + auto &cg = cgroup_memory[name]; + auto *ms = reinterpret_cast(malloc(sizeof(cgroup_memory_stat))); + for (int i = 0; i < CGROUP_MEMORY_STAT_MAX; i++) + ms->value[i] = cg.memory_stat.value[i]; + *mem_stat = ms; + return 0; +} + +int LowmemLimitEnv::cgroup_make_subdir(const char* parentdir, const char* cgroup_name, bool *already) +{ + check_expected(parentdir); + check_expected(cgroup_name); + + std::cerr << " parentdir=" << parentdir << " cgroup_name=" << cgroup_name << std::endl; + std::string cgroup = parentdir; + cgroup += "/"; + cgroup += cgroup_name; + + auto it = cgroup_memory.find(cgroup_name); + if (it == cgroup_memory.end()) { + cgroup_memory[cgroup] = + CGroupData{{{"memory.limit_in_bytes", "1"}, + {"memory.oom_control", "0"}, + {"memory.memsw.limit_in_bytes", "0"}, + {"memory.limit_in_bytes", "0"}, + {"/cgroup.procs", ""}, + {"memory.usage_in_bytes", "0"}, + {"memory.memsw.usage_in_bytes", "0"} + }}; + } + + if (already) + *already = it != cgroup_memory.end(); + + return 0; +} + +int LowmemLimitEnv::proc_get_cmdline(pid_t pid, char *cmdline) +{ + std::cerr << " -> "; + const auto *info = find_app_info(pid); + if (info) { + strncpy(cmdline, info->appid, strlen(info->appid)+1); + std::cerr << info->appid << std::endl; + return RESOURCED_ERROR_NONE; + } + std::cerr << "not found\n"; + return RESOURCED_ERROR_FAIL; +} + +int LowmemLimitEnv::d_bus_broadcast_signal_gvariant(const char *path, const char *interface, + const char *name, GVariant *gv) +{ + std::cerr << " path=" << path << ", interface=" << interface << ", name=" << name << std::endl; + check_expected(path); + check_expected(interface); + check_expected(name); + return 0; +} + +int LowmemLimitEnv::request_helper_worker(enum helper_worker_operation op, + void *data, int (*function) (void *data), void (*callback) (void *data, int ret)) +{ + callback(data, function(data)); + return 0; +} diff --git a/tests/lowmem-limit-env.hpp b/tests/lowmem-limit-env.hpp new file mode 100644 index 0000000..f690979 --- /dev/null +++ b/tests/lowmem-limit-env.hpp @@ -0,0 +1,119 @@ +#pragma once + +#include + +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" +} + +#include +#include + +typedef int (*ConfigParseCallbackFun)(struct parse_result *result, void *user_data); +typedef int (*NotifierCallbackFun)(void *data); + +class LowmemLimitEnv { +public: + enum class MemLimitType { NONE, OOM, THRESHOLD }; + typedef std::vector> Usages; +private: + + struct FDReadHandler { + FDReadHandler(fd_changed_cb _cb, void *_data, release_cb _free_fn, fd_handler_h *_handler) + : callback{_cb}, data{_data}, free_func{_free_fn}, handler{_handler} + {} + fd_changed_cb callback; + void *data; + release_cb free_func; + fd_handler_h *handler; + }; + struct AppTestValuesHolder { + proc_app_info app_info; + Usages usages; + + ~AppTestValuesHolder(); + }; + std::map notifiers; + std::map apps; + std::map fd_handlers; + + const std::string default_cgroup_memory{"/sys/fs/cgroup/memory"}; + const std::pair default_cgroup_usage{"memory.memsw.usage_in_bytes", "33554432"}; + + typedef std::map CGroupFiles; + struct CGroupData { + CGroupFiles files; + std::vector pids; + cgroup_memory_stat memory_stat; + }; + std::map cgroup_memory{{default_cgroup_memory, {.files = CGroupFiles{default_cgroup_usage}}}}; + + std::map eventfds; + static const int INITIAL_FD{1024}; + int last_eventfd{INITIAL_FD}; + + std::string mem_limit_type{"none"}; + std::string mem_limit_value{"0"}; // in MBs, string + unsigned long ktotalram{0}; // in bytes +public: + LowmemLimitEnv(MemLimitType limit_type, unsigned long limit_value, unsigned long ram); + ~LowmemLimitEnv(); + // test configuration + void configure_mem_limit_type(MemLimitType type); + void configure_mem_limit_value(unsigned long value); + void configure_ktotalram(unsigned long value); + void configure_add_app(pid_t pid, const char *appid, Usages &&u); + // app: pid, appid, mem_usage, sub_pids (each of them has its own usage) + void configure_remove_app(pid_t pid); + void configure_cgroup_usages(const char *cgroup, unsigned long sw_usage, unsigned long usage); + void reinit(); + + // 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, + void *data, int (*function) (void *data), void (*callback)(void *data, int ret)); + + struct proc_app_info *find_app_info(const pid_t pid); + int proc_get_mem_usage(pid_t, unsigned int *usage); + int proc_get_cmdline(pid_t pid, char *cmdline); + + int memcg_set_eventfd(const char *memcg, const char *event, char *value); + int add_fd_read_handler(int fd, fd_changed_cb callback, + void *data, release_cb free_func, fd_handler_h *handler); + + void make_memps_log(char *file, pid_t pid, char *victim_name); + + int cgroup_get_pids(const char *name, GArray **pids); + int cgroup_write_node_uint32(const char *cgroup_name, const char *file_name, uint32_t value); + int cgroup_read_node_uint32(const char *cgroup_name, const char *file_name, uint32_t *value); + int cgroup_get_memory_stat(const char *name, struct cgroup_memory_stat **mem_stat); + int cgroup_make_subdir(const char* parentdir, const char* cgroup_name, bool *already); + + int d_bus_broadcast_signal_gvariant(const char *path, const char *interface, + const char *name, GVariant *gv); + + // mock system functions + std::pair kill(pid_t pid, int sig); + std::pair read(int fd, void *buf, size_t count); + std::pair access(const char *pathname, int mode); + + // events + void event_service_launch(pid_t pid); + void event_cgroup_eventfd(const std::string &cgroup_event); + void trigger_oom_event(const std::string &cgroup_name); + void trigger_threshold_event(const std::string &cgroup_name); + void event_fd_handler(int fd); +}; + +extern LowmemLimitEnv *global_test_lowmem_limit_env; diff --git a/tests/lowmem-limit-mock.cpp b/tests/lowmem-limit-mock.cpp new file mode 100644 index 0000000..28ee3d6 --- /dev/null +++ b/tests/lowmem-limit-mock.cpp @@ -0,0 +1,109 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "lowmem-limit-env.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; \ + } + +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)) + +/* + * 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; \ + } + +/* 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(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(int, cgroup_get_pids, (const char *name, GArray **pids), (name, pids)) +MOCK(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, + (const char *cgroup_name, const char *file_name, uint32_t *value), + (cgroup_name, file_name, value)) +MOCK(int, cgroup_get_memory_stat, + (const char *name, struct cgroup_memory_stat **mem_stat), + (name, mem_stat)) +MOCK(int, cgroup_make_subdir, + (const char* parentdir, const char* cgroup_name, bool *already), + (parentdir, cgroup_name, already)) +MOCK(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, + (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, + (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, + (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 new file mode 100644 index 0000000..12f36e2 --- /dev/null +++ b/tests/lowmem-limit-test.cpp @@ -0,0 +1,221 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +extern "C" { +#include + +#include "lowmem-handler.h" +} + +#include "lowmem-limit-env.hpp" + +void expect_cgroup_write_uint32(const char *cgroup_name, const char *file_name, uint32_t val) +{ + std::cerr << "expecting cgroup write: " << cgroup_name << " " << file_name << " " << val << std::endl; + expect_string(cgroup_write_node_uint32, cgroup_name, cgroup_name); + expect_string(cgroup_write_node_uint32, file_name, file_name); + expect_value(cgroup_write_node_uint32, value, val); +} + +void expect_d_bus_emit(const char *path, const char *interface, const char *name) +{ + std::cerr << "expecting D-Bus signal emit: " << path << " " << interface << " " << name << std::endl; + expect_string(d_bus_broadcast_signal_gvariant, path, path); + expect_string(d_bus_broadcast_signal_gvariant, interface, interface); + expect_string(d_bus_broadcast_signal_gvariant, name, name); +} + +class CgPath { +public: + CgPath(const char *name) : app_name{name}, cg_name{std::string{"/sys/fs/cgroup/memory/MemLimit/"}+name} {} + CgPath(CgPath &&) = delete; + + const char *get_app_name() const { return app_name.c_str(); } + const char *get_cg_name() const { return cg_name.c_str(); } + +private: + std::string app_name; + std::string cg_name; +}; + +constexpr unsigned long long operator""_MB (unsigned long long x) { return x*1024*1024; } + +void app_test_prepare( + LowmemLimitEnv &test_lowmem_limit_env, + unsigned long MEM_LIMIT, + LowmemLimitEnv::MemLimitType limit_type, + pid_t APP_PID, + CgPath &cg_path, + LowmemLimitEnv::Usages &&usages, + unsigned long cgroup_sw_usage, + unsigned long cgroup_usage) +{ + MEM_LIMIT = MEM_LIMIT*1_MB; + + const char *app_name = cg_path.get_app_name(); + const char *cg_name = cg_path.get_cg_name(); + + test_lowmem_limit_env.configure_add_app(APP_PID, app_name, std::move(usages)); + + expect_string(cgroup_make_subdir, parentdir, "/sys/fs/cgroup/memory/MemLimit"); + expect_string(cgroup_make_subdir, cgroup_name, app_name); + + if (limit_type == LowmemLimitEnv::MemLimitType::OOM) { + expect_cgroup_write_uint32(cg_name, "memory.oom_control", + limit_type == LowmemLimitEnv::MemLimitType::OOM); + + expect_cgroup_write_uint32(cg_name, "memory.memsw.limit_in_bytes", MEM_LIMIT); + expect_cgroup_write_uint32(cg_name, "memory.limit_in_bytes", MEM_LIMIT); + } else { + expect_cgroup_write_uint32(cg_name, "memory.limit_in_bytes", MEM_LIMIT*1.2); // increased by resourced + } + expect_cgroup_write_uint32(cg_name, "/cgroup.procs", APP_PID); + for (const auto &u: usages) { + if (u.first != APP_PID) + expect_cgroup_write_uint32(cg_name, "/cgroup.procs", u.first); + } + + test_lowmem_limit_env.event_service_launch(APP_PID); + test_lowmem_limit_env.configure_cgroup_usages(app_name, cgroup_sw_usage, cgroup_usage); +} + +void trigger_app_event_oom(LowmemLimitEnv &test_lowmem_limit_env, CgPath &cg_path, bool expect_kill) +{ + if (expect_kill) { + expect_cgroup_write_uint32(cg_path.get_cg_name(), "memory.oom_control", 0); + expect_d_bus_emit("/Org/Tizen/ResourceD/Oom", "org.tizen.resourced.oom", "MemLimitEvent"); + } + // trigger oom event + test_lowmem_limit_env.trigger_oom_event(cg_path.get_app_name()); +} + +void trigger_app_event_threshold(LowmemLimitEnv &test_lowmem_limit_env, CgPath &cg_path, pid_t APP_PID) +{ + if (APP_PID != 0) { + expect_value(kill, pid, APP_PID); + expect_d_bus_emit("/Org/Tizen/ResourceD/Oom", "org.tizen.resourced.oom", "MemLimitEvent"); + } + // trigger oom event + test_lowmem_limit_env.trigger_threshold_event(cg_path.get_app_name()); +} + +void simple_oom_test(void **) +{ + unsigned long MEM_LIMIT_MB = 32; + LowmemLimitEnv::MemLimitType LIMIT_TYPE = LowmemLimitEnv::MemLimitType::OOM; + + LowmemLimitEnv test_lowmem_limit_env(LIMIT_TYPE, MEM_LIMIT_MB, 64_MB); + + lowmem_limit_init(); + + // configure an app + CgPath cg_path{"my.great.app"}; + app_test_prepare(test_lowmem_limit_env, MEM_LIMIT_MB, LIMIT_TYPE, 1780, cg_path, + {{1780,33_MB}}, 33_MB, 33_MB); + + // trigger oom event + trigger_app_event_oom(test_lowmem_limit_env, cg_path, true); + + lowmem_limit_exit(); +} + +void simple_threshold_test(void **) +{ + unsigned long MEM_LIMIT_MB = 32; + LowmemLimitEnv::MemLimitType LIMIT_TYPE = LowmemLimitEnv::MemLimitType::THRESHOLD; + + LowmemLimitEnv test_lowmem_limit_env(LIMIT_TYPE, MEM_LIMIT_MB, 64_MB); + + lowmem_limit_init(); + + // configure an app + CgPath cg_path1{"my.greater.app"}; + app_test_prepare(test_lowmem_limit_env, MEM_LIMIT_MB, LIMIT_TYPE, 1781, cg_path1, + LowmemLimitEnv::Usages{{1781, 33_MB}, {1782, 1}, {1783, 1000000}}, + 33_MB, 33_MB); + + // add another app + CgPath cg_path2{"my.greatest.app"}; + app_test_prepare(test_lowmem_limit_env, MEM_LIMIT_MB, LIMIT_TYPE, 1801, cg_path2, + LowmemLimitEnv::Usages{{1801, 31_MB}, {1802, 31_MB}, {1803, 31_MB}}, + (31+31+31)*1_MB, 0); + + // trigger threshold event + trigger_app_event_threshold(test_lowmem_limit_env, cg_path1, 1781); + + // trigger threshold event + trigger_app_event_threshold(test_lowmem_limit_env, cg_path2, 1801); + + lowmem_limit_exit(); +} + +void test_oom_two_apps_one_killed(void **) +{ + unsigned long MEM_LIMIT_MB = 32; + LowmemLimitEnv::MemLimitType LIMIT_TYPE = LowmemLimitEnv::MemLimitType::OOM; + + LowmemLimitEnv test_lowmem_limit_env(LIMIT_TYPE, MEM_LIMIT_MB, 64_MB); + + lowmem_limit_init(); + + // configure an app + CgPath cg_path_kill{"app.to.kill"}; + app_test_prepare(test_lowmem_limit_env, MEM_LIMIT_MB, LIMIT_TYPE, 32760, cg_path_kill, + {{32762,33_MB}, {32760, 1}, {32761,2}}, 33_MB, 33_MB); + + // configure a second app + CgPath cg_path_survive{"app.to.survive"}; + app_test_prepare(test_lowmem_limit_env, MEM_LIMIT_MB, LIMIT_TYPE, 32770, cg_path_survive, + {{32772,1_MB}, {32770, 1}, {32771,2}}, 2_MB, 0); + + // trigger oom event: only app.to.kill should be killed + trigger_app_event_oom(test_lowmem_limit_env, cg_path_kill, true); + trigger_app_event_oom(test_lowmem_limit_env, cg_path_survive, false); + + lowmem_limit_exit(); +} + +void test_threshold_two_apps_one_killed(void **) +{ + unsigned long MEM_LIMIT_MB = 32; + LowmemLimitEnv::MemLimitType LIMIT_TYPE = LowmemLimitEnv::MemLimitType::THRESHOLD; + + LowmemLimitEnv test_lowmem_limit_env(LIMIT_TYPE, MEM_LIMIT_MB, 64_MB); + + lowmem_limit_init(); + + // configure an app + CgPath cg_path_survive{"app.to.survive"}; + app_test_prepare(test_lowmem_limit_env, MEM_LIMIT_MB, LIMIT_TYPE, 32750, cg_path_survive, + {{32752,1_MB}, {32750, 1}, {32751,2}}, 2_MB, 0); + + // configure a second app + CgPath cg_path_kill{"app.to.kill"}; + pid_t PID_TO_KILL = 32740; + app_test_prepare(test_lowmem_limit_env, MEM_LIMIT_MB, LIMIT_TYPE, PID_TO_KILL, cg_path_kill, + {{32742,33_MB}, {32740, 1}, {32741,2}}, 33_MB, 33_MB); + + // trigger oom event: only app.to.kill should be killed + trigger_app_event_threshold(test_lowmem_limit_env, cg_path_kill, PID_TO_KILL); + trigger_app_event_threshold(test_lowmem_limit_env, cg_path_survive, 0); + + lowmem_limit_exit(); +} + +int main(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(simple_oom_test), + cmocka_unit_test(simple_threshold_test), + cmocka_unit_test(test_oom_two_apps_one_killed), + cmocka_unit_test(test_threshold_two_apps_one_killed), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} -- 2.7.4 From a73c9089d11e16058e5d3ed8df0db3677a4bdf2e Mon Sep 17 00:00:00 2001 From: Michal Bloch Date: Mon, 30 Dec 2019 12:40:40 +0100 Subject: [PATCH 14/16] Explicit "(null)" for %s (-Werror=format-overflow) Change-Id: I4e9fa414b3bbb1196491bb9ba5258f83610770ec Signed-off-by: Michal Bloch --- src/common/appinfo-list.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/appinfo-list.c b/src/common/appinfo-list.c index d7fdedb..5a48c2e 100644 --- a/src/common/appinfo-list.c +++ b/src/common/appinfo-list.c @@ -44,7 +44,7 @@ static struct resourced_appinfo *resourced_appinfo_create(const char *appid, assert(pkgname != NULL); if (!appid || !pkgname) { - _E("appid or pkgname was null, values: %s, %s", appid, pkgname); + _E("appid or pkgname was null, values: %s, %s", appid ?: "(null)", pkgname ?: "(null)"); return NULL; } -- 2.7.4 From 66f640f91d709328e68131817a05e50b6c918d7c Mon Sep 17 00:00:00 2001 From: Michal Bloch Date: Mon, 30 Dec 2019 17:34:38 +0100 Subject: [PATCH 15/16] Fix CMake flag restoration Change-Id: Ib098276c83720d88ac8cf1809065f1355067e1f4 Signed-off-by: Michal Bloch --- src/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 07d0828..e9b1375 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -155,8 +155,11 @@ ADD_EXECUTABLE(${RD_BINARY_NAME} ${SOURCES}) SET(SAVE_EXTRA_CFLAGS ${EXTRA_CFLAGS}) +SET(SAVE_EXTRA_CFLAGS_RELEASE ${EXTRA_CFLAGS_RELEASE}) SET(SAVE_CMAKE_C_FLAGS ${CMAKE_C_FLAGS}) SET(SAVE_CMAKE_C_FLAGS_RELEASE ${CMAKE_C_FLAGS_RELEASE}) +SET(SAVE_CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) +SET(SAVE_CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE}) SET(EXTRA_CFLAGS "") SET(EXTRA_CFLAGS_RELEASE "") SET(CMAKE_C_FLAGS "") @@ -174,8 +177,11 @@ SET_TARGET_PROPERTIES(resourced_shared_test TARGET_LINK_LIBRARIES(resourced_shared_test ${RESOURCED_REQUIRE_PKGS_LDFLAGS}) SET(EXTRA_CFLAGS ${SAVE_EXTRA_CFLAGS}) +SET(EXTRA_CFLAGS_RELEASE ${SAVE_EXTRA_CFLAGS_RELEASE}) SET(CMAKE_C_FLAGS ${SAVE_CMAKE_C_FLAGS}) SET(CMAKE_C_FLAGS_RELEASE ${SAVE_CMAKE_C_FLAGS_RELEASE}) +SET(CMAKE_CXX_FLAGS ${SAVE_CMAKE_CXX_FLAGS}) +SET(CMAKE_CXX_FLAGS_RELEASE ${SAVE_CMAKE_CXX_FLAGS_RELEASE}) TARGET_LINK_LIBRARIES(${RD_BINARY_NAME} resourced-private-api ${RESOURCED_REQUIRE_PKGS_LDFLAGS} "-pie -ldl -lm -Wl,-rpath=${RD_PLUGIN_PATH}") -- 2.7.4 From 4240b515a8c0099bd8c69405761239e1748250c8 Mon Sep 17 00:00:00 2001 From: Michal Bloch Date: Mon, 30 Dec 2019 19:15:30 +0100 Subject: [PATCH 16/16] Fix an instance of errno pollution Change-Id: Iecfb9e9639a6ddf81ea038e844da66e46cfc0287 Signed-off-by: Michal Bloch --- src/common/config-parser.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/common/config-parser.c b/src/common/config-parser.c index 627fc0a..a89af9e 100644 --- a/src/common/config-parser.c +++ b/src/common/config-parser.c @@ -196,8 +196,9 @@ int config_parse_new(const char *filename, void *table) f = fopen(filename, "r"); if (!f) { - _E("Failed to open file %s", filename); - return -errno; + const int saved_errno = errno; + _E("Failed to open file %s", filename); // can modify errno + return -saved_errno; } while (!feof(f)) { -- 2.7.4