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
+ADD_MEMORY_TESTS(lowmem-system-test ""
+ "-Wl,--wrap=opendir,--wrap=readdir,--wrap=closedir"
+ lowmem-system-test.cpp lowmem-system-mock.cpp lowmem-system-env.cpp lowmem-env.cpp lowmem-env-mock.cpp
+ ../src/memory/lowmem-system.c)
INSTALL(TARGETS watchdog-test
DESTINATION ${RD_TESTS_PATH})
--- /dev/null
+#include "lowmem-system-env.hpp"
+
+#include "cmocka_wrap.hpp"
+
+#include "resourced.h"
+
+#include <iostream>
+#include <cstring>
+
+LowmemSystemEnv *global_test_lowmem_system_env{nullptr};
+
+LowmemSystemEnv::LowmemSystemEnv()
+{
+ assert_null(global_test_lowmem_system_env);
+ global_test_lowmem_system_env = this;
+ local_dirent.d_type = DT_DIR;
+}
+
+LowmemSystemEnv::~LowmemSystemEnv()
+{
+ assert_non_null(global_test_lowmem_system_env);
+ global_test_lowmem_system_env = nullptr;
+}
+
+void LowmemSystemEnv::lowmem_reassign_limit(const char *dir, unsigned int limit)
+{
+ check_expected(dir);
+ check_expected(limit);
+}
+
+int LowmemSystemEnv::cgroup_read_node_int32(const char *cgroup_name, const char *file_name, int32_t *value)
+{
+ std::cerr << " cgroup_name=" << cgroup_name << ", file_name=" << file_name << std::endl;
+ check_expected(cgroup_name);
+ check_expected(file_name);
+ *value = static_cast<int32_t>(mock());
+ std::cerr << " -> " << *value << std::endl;
+ return RESOURCED_ERROR_NONE;
+}
+
+int LowmemSystemEnv::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;
+ std::cerr << ", value=" << value << std::endl;
+ check_expected(cgroup_name);
+ check_expected(file_name);
+ check_expected(value);
+ return RESOURCED_ERROR_NONE;
+}
+
+int LowmemSystemEnv::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;
+ check_expected(cgroup_name);
+ check_expected(file_name);
+ *value = static_cast<uint32_t>(mock());
+ std::cerr << " -> " << *value << std::endl;
+ return RESOURCED_ERROR_NONE;
+}
+
+void LowmemSystemEnv::g_source_destroy(GSource *source)
+{
+ delete source;
+}
+
+GSource *LowmemSystemEnv::g_timeout_source_new_seconds(guint interval)
+{
+ return new GSource;
+}
+
+void LowmemSystemEnv::g_source_set_callback(GSource *source, GSourceFunc fun, gpointer data, GDestroyNotify notify)
+{
+ timeout_callback = fun;
+ assert_null(notify);
+}
+
+guint LowmemSystemEnv::g_source_attach(GSource *source, GMainContext *context)
+{
+ return 1;
+}
+
+void LowmemSystemEnv::trigger_booting_done()
+{
+ lowmem_env.trigger_notifier(RESOURCED_NOTIFIER_BOOTING_DONE, nullptr);
+}
+
+void LowmemSystemEnv::trigger_timeout()
+{
+ assert_non_null(timeout_callback);
+ timeout_callback(NULL);
+}
+
+std::pair<bool,DIR *> LowmemSystemEnv::opendir(const char *name)
+{
+ std::cerr << " name=" << name << std::endl;
+ return std::make_pair(true, reinterpret_cast<DIR*>(1));
+}
+
+std::pair<bool,int> LowmemSystemEnv::closedir(DIR *dirp)
+{
+ return std::make_pair(true, 0);
+}
+
+std::pair<bool,struct dirent *> LowmemSystemEnv::readdir(DIR *dirp)
+{
+ const char *dirname = mock_ptr_type(const char *);
+ if (dirname) {
+ strncpy(local_dirent.d_name, dirname, sizeof(local_dirent.d_name));
+ } else {
+ local_dirent.d_name[0] = 0;
+ }
+ return std::make_pair(true, dirname ? &local_dirent : nullptr);
+}
--- /dev/null
+#pragma once
+
+#include "lowmem-env.hpp"
+
+#include <glib.h>
+#include <dirent.h>
+
+#include <utility>
+
+class LowmemSystemEnv {
+public:
+ LowmemSystemEnv();
+ ~LowmemSystemEnv();
+
+ int cgroup_read_node_int32(const char *cgroup_name, const char *file_name, int32_t *value);
+ 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);
+
+ void g_source_destroy(GSource *source);
+ GSource *g_timeout_source_new_seconds(guint interval);
+ void g_source_set_callback(GSource *source, GSourceFunc fun, gpointer data, GDestroyNotify notify);
+ guint g_source_attach(GSource *source, GMainContext *context);
+
+ void lowmem_reassign_limit(const char *dir, unsigned int limit);
+
+ void trigger_booting_done();
+ void trigger_timeout();
+
+ std::pair<bool,DIR *> opendir(const char *name);
+ std::pair<bool,int> closedir(DIR *dirp);
+ std::pair<bool,struct dirent *> readdir(DIR *dirp);
+private:
+ LowmemEnv lowmem_env;
+ GSourceFunc timeout_callback{nullptr};
+ dirent local_dirent;
+};
+
+extern LowmemSystemEnv *global_test_lowmem_system_env;
--- /dev/null
+#include "lowmem-mock.hpp"
+#include "lowmem-system-env.hpp"
+
+#define MOCK_SYSTEM(rettype, name, def_args, call_args) \
+ MOCK(global_test_lowmem_system_env, rettype, name, def_args, call_args)
+
+MOCK_SYSTEM(void, lowmem_reassign_limit, (const char *dir, unsigned int limit), (dir, limit))
+MOCK_SYSTEM(int, cgroup_read_node_int32,
+ (const char *cgroup_name, const char *file_name, int32_t *value),
+ (cgroup_name, file_name, value))
+MOCK_SYSTEM(int, cgroup_write_node_uint32,
+ (const char *cgroup_name, const char *file_name, uint32_t value),
+ (cgroup_name, file_name, value))
+MOCK_SYSTEM(int, cgroup_read_node_uint32,
+ (const char *cgroup_name, const char *file_name, uint32_t *value),
+ (cgroup_name, file_name, value))
+
+MOCK_SYSTEM(void, g_source_destroy, (GSource *source), (source))
+MOCK_SYSTEM(GSource *, g_timeout_source_new_seconds, (guint interval), (interval))
+MOCK_SYSTEM(void, g_source_set_callback,
+ (GSource *source, GSourceFunc fun, gpointer data, GDestroyNotify notify),
+ (source, fun, data, notify))
+MOCK_SYSTEM(guint, g_source_attach, (GSource *source, GMainContext *context), (source, context))
+
+#define WRAP_SYSTEM(rettype, name, def_args, call_args) \
+ WRAP(global_test_lowmem_system_env, rettype, name, def_args, call_args)
+
+WRAP_SYSTEM(DIR *, opendir, (const char *name), (name))
+WRAP_SYSTEM(int, closedir, (DIR *dirp), (dirp))
+WRAP_SYSTEM(struct dirent *, readdir, (DIR *dirp), (dirp))
+
--- /dev/null
+#include "lowmem-system-env.hpp"
+#include "cmocka_wrap.hpp"
+
+extern "C" {
+#include "lowmem-handler.h"
+#include "memory-common.h"
+}
+
+#include <vector>
+
+#define MEMORY_SYSTEMD_SYSTEM "/sys/fs/cgroup/memory/system.slice"
+#define MEMORY_SYSTEMD_USER "/sys/fs/cgroup/memory/user.slice"
+
+class Subdir {
+public:
+ Subdir(const char *subdir, const char *dir, uint32_t l)
+ : subdir_name{subdir}, full_name{std::string{dir}+"/"+subdir}, limit{l} {}
+
+ const char *get_subdir() const { return subdir_name.c_str(); }
+ const char *get_subdir_full() const { return full_name.c_str(); }
+ uint32_t get_limit() const { return limit; }
+
+private:
+ std::string subdir_name;
+ std::string full_name;
+ uint32_t limit;
+};
+
+typedef std::vector<Subdir> Subdirs;
+
+void mock_cgroup_read_node_int32(const char *cgroup_name, const char *file_name, int32_t value)
+{
+ std::cerr << "mocking cgroup read: " << cgroup_name << " " << file_name << " " << value << std::endl;
+ expect_string(cgroup_read_node_int32, cgroup_name, cgroup_name);
+ expect_string(cgroup_read_node_int32, file_name, file_name);
+ will_return(cgroup_read_node_int32, value);
+}
+
+void mock_cgroup_read_node_uint32(const char *cgroup_name, const char *file_name, uint32_t value)
+{
+ std::cerr << "mocking cgroup read: " << cgroup_name << " " << file_name << " " << value << std::endl;
+ expect_string(cgroup_read_node_uint32, cgroup_name, cgroup_name);
+ expect_string(cgroup_read_node_uint32, file_name, file_name);
+ will_return(cgroup_read_node_uint32, value);
+}
+
+void expect_cgroup_write_node_uint32(const char *cgroup_name, const char *file_name, uint32_t value)
+{
+ std::cerr << "expecting cgroup write: " << cgroup_name << " " << file_name << " " << value << 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, value);
+}
+
+void mock_search_systemd_cgroup(const char *dir, int32_t root_swappiness, int32_t dir_swappiness,
+ const Subdirs &subdirs = Subdirs())
+{
+ mock_cgroup_read_node_int32(LOWMEM_ROOT_CGROUP, MEMCG_SWAPNESS_PATH, root_swappiness);
+ mock_cgroup_read_node_int32(dir, MEMCG_SWAPNESS_PATH, dir_swappiness);
+
+ if (root_swappiness >= 0 && root_swappiness != dir_swappiness) {
+ expect_cgroup_write_node_uint32(dir, MEMCG_SWAPNESS_PATH, root_swappiness);
+ }
+
+ // subdirs
+ // LINE: FOREACH_DIRENT(de, d, return -errno)
+ for (const auto &subdir: subdirs) {
+ // mock subdir
+ will_return(readdir, subdir.get_subdir());
+
+ mock_cgroup_read_node_uint32(subdir.get_subdir_full(), MEMCG_LIMIT_PATH, subdir.get_limit());
+ if (subdir.get_limit() > 0) {
+ expect_cgroup_write_node_uint32(subdir.get_subdir_full(), MEMCG_SWAPNESS_PATH, root_swappiness);
+
+ expect_string(lowmem_reassign_limit, dir, subdir.get_subdir_full());
+ expect_value(lowmem_reassign_limit, limit, subdir.get_limit());
+ }
+ }
+
+ // finish subdirs
+ will_return(readdir, nullptr);
+}
+
+void simple_system_test(void **)
+{
+ LowmemSystemEnv env;
+
+ lowmem_system_init();
+ env.trigger_booting_done();
+
+ const int32_t SWAPPINESS_NO_CHANGE = 1000;
+ mock_search_systemd_cgroup(MEMORY_SYSTEMD_SYSTEM, SWAPPINESS_NO_CHANGE, SWAPPINESS_NO_CHANGE);
+
+ const int32_t SWAPPINESS_CHANGE_TO = 0;
+ mock_search_systemd_cgroup(MEMORY_SYSTEMD_USER, SWAPPINESS_CHANGE_TO, 100000);
+
+ env.trigger_timeout();
+ lowmem_system_exit();
+}
+
+void simple_system_with_subdirs_test(void **)
+{
+ LowmemSystemEnv env;
+
+ lowmem_system_init();
+ env.trigger_booting_done();
+
+ const int32_t SWAPPINESS_CHANGE_TO = 1000;
+ const Subdirs subdirs_system{
+ {"my.sweet.dir", MEMORY_SYSTEMD_SYSTEM, 0},
+ {"my.sweeter.dir", MEMORY_SYSTEMD_SYSTEM, SWAPPINESS_CHANGE_TO},
+ {"my sweetest.dir", MEMORY_SYSTEMD_SYSTEM, SWAPPINESS_CHANGE_TO*SWAPPINESS_CHANGE_TO}
+ };
+
+ mock_search_systemd_cgroup(MEMORY_SYSTEMD_SYSTEM,
+ SWAPPINESS_CHANGE_TO, SWAPPINESS_CHANGE_TO*SWAPPINESS_CHANGE_TO,
+ subdirs_system);
+
+ const Subdirs subdirs_user{
+ {"my.user.dir", MEMORY_SYSTEMD_USER, 0},
+ {"my.userer.dir", MEMORY_SYSTEMD_USER, SWAPPINESS_CHANGE_TO},
+ {"my userest.dir", MEMORY_SYSTEMD_USER, SWAPPINESS_CHANGE_TO*SWAPPINESS_CHANGE_TO}
+ };
+ mock_search_systemd_cgroup(MEMORY_SYSTEMD_USER,
+ SWAPPINESS_CHANGE_TO, SWAPPINESS_CHANGE_TO-SWAPPINESS_CHANGE_TO,
+ subdirs_user);
+
+ env.trigger_timeout();
+ lowmem_system_exit();
+}
+
+int main(void) {
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(simple_system_test),
+ cmocka_unit_test(simple_system_with_subdirs_test),
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}