/* * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ #ifdef LINUX #include "runtime/os.hpp" #include "cgroupSubsystem_linux.hpp" #include "cgroupUtil_linux.hpp" #include "cgroupV1Subsystem_linux.hpp" #include "cgroupV2Subsystem_linux.hpp" #include "unittest.hpp" #include "utilities/globalDefinitions.hpp" #include // for basename #include typedef struct { const char* mount_path; const char* root_path; const char* cgroup_path; const char* expected_path; } TestCase; // Utilities static bool file_exists(const char* filename) { struct stat st; return os::stat(filename, &st) == 0; } // we rely on temp_file returning modifiable memory in resource area. static char* temp_file(const char* prefix) { const testing::TestInfo* test_info = ::testing::UnitTest::GetInstance()->current_test_info(); stringStream path; path.print_raw(os::get_temp_directory()); path.print_raw(os::file_separator()); path.print("%s-test-jdk.pid%d.%s.%s", prefix, os::current_process_id(), test_info->test_case_name(), test_info->name()); return path.as_string(true); } static void delete_file(const char* filename) { if (!file_exists(filename)) { return; } int ret = remove(filename); EXPECT_TRUE(ret == 0 || errno == ENOENT) << "failed to remove file '" << filename << "': " << os::strerror(errno) << " (" << errno << ")"; } class TestController : public CgroupController { private: char* _path; public: TestController(char* p): _path(p) {} const char* subsystem_path() override { return _path; }; bool is_read_only() override { return true; // doesn't matter } }; static void fill_file(const char* path, const char* content) { delete_file(path); FILE* fp = os::fopen(path, "w"); if (fp == nullptr) { return; } if (content != nullptr) { fprintf(fp, "%s", content); } fclose(fp); } TEST(cgroupTest, read_numerical_key_value_failure_cases) { char* test_file = temp_file("cgroups"); const char* b = basename(test_file); EXPECT_TRUE(b != nullptr) << "basename was null"; stringStream path; path.print_raw(os::file_separator()); path.print_raw(b); const char* base_with_slash = path.as_string(true); TestController* controller = new TestController((char*)os::get_temp_directory()); constexpr uint64_t bad = 0xBAD; uint64_t x = bad; fill_file(test_file, "foo "); bool is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_FALSE(is_ok) << "Value is missing in key/value case, expecting false"; EXPECT_EQ(bad, x) << "x must be unchanged"; x = bad; fill_file(test_file, "faulty_start foo 101"); is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_FALSE(is_ok) << "key must be at the start"; EXPECT_EQ(bad, x) << "x must be unchanged"; x = bad; fill_file(test_file, nullptr); is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_FALSE(is_ok) << "key not in empty file"; EXPECT_EQ(bad, x) << "x must be unchanged"; x = bad; fill_file(test_file, "foo\n"); is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_FALSE(is_ok) << "key must have a value"; EXPECT_EQ(bad, x) << "x must be unchanged"; x = bad; fill_file(test_file, "foof 1002"); is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_FALSE(is_ok) << "key must be exact match"; EXPECT_EQ(bad, x) << "x must be unchanged"; // Cleanup delete_file(test_file); } TEST(cgroupTest, read_numerical_key_value_success_cases) { char* test_file = temp_file("cgroups"); const char* b = basename(test_file); EXPECT_TRUE(b != nullptr) << "basename was null"; stringStream path; path.print_raw(os::file_separator()); path.print_raw(b); const char* base_with_slash = path.as_string(true); TestController* controller = new TestController((char*)os::get_temp_directory()); constexpr uint64_t bad = 0xBAD; uint64_t x = bad; fill_file(test_file, "foo 100"); bool is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_TRUE(is_ok); EXPECT_EQ((uint64_t)100, x); x = bad; fill_file(test_file, "foo\t111"); is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_TRUE(is_ok); EXPECT_EQ((uint64_t)111, x); x = bad; fill_file(test_file, "foo\nbar 333\nfoo\t111"); is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_TRUE(is_ok); EXPECT_EQ((uint64_t)111, x); x = bad; fill_file(test_file, "foof 100\nfoo 133"); is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_TRUE(is_ok); EXPECT_EQ((uint64_t)133, x); x = bad; fill_file(test_file, "foo\t333\nfoot 999"); is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_TRUE(is_ok); EXPECT_EQ((uint64_t)333, x); x = bad; fill_file(test_file, "foo 1\nfoo car"); is_ok = controller->read_numerical_key_value(base_with_slash, "foo", x); EXPECT_TRUE(is_ok); EXPECT_EQ((uint64_t)1, x); // Cleanup delete_file(test_file); } TEST(cgroupTest, read_number_null) { TestController* null_path_controller = new TestController((char*)nullptr); const char* test_file_path = "/not-used"; constexpr uint64_t bad = 0xBAD; uint64_t a = bad; // null subsystem_path() case bool is_ok = null_path_controller->read_number(test_file_path, a); EXPECT_FALSE(is_ok) << "Null subsystem path should be an error"; EXPECT_EQ(bad, a) << "Expected untouched scan value"; } TEST(cgroupTest, read_string_beyond_max_path) { char larger_than_max[MAXPATHLEN + 1]; for (int i = 0; i < (MAXPATHLEN); i++) { larger_than_max[i] = 'A' + (i % 26); } larger_than_max[MAXPATHLEN] = '\0'; TestController* too_large_path_controller = new TestController(larger_than_max); const char* test_file_path = "/file-not-found"; char foo[1024]; foo[0] = '\0'; bool is_ok = too_large_path_controller->read_string(test_file_path, foo, 1024); EXPECT_FALSE(is_ok) << "Too long path should be an error"; EXPECT_STREQ("", foo) << "Expected untouched scan value"; } TEST(cgroupTest, read_number_file_not_exist) { TestController* unknown_path_ctrl = new TestController((char*)"/do/not/exist"); const char* test_file_path = "/file-not-found"; constexpr uint64_t bad = 0xBAD; uint64_t result = bad; bool is_ok = unknown_path_ctrl->read_number(test_file_path, result); EXPECT_FALSE(is_ok) << "File not found should be an error"; EXPECT_EQ(bad, result) << "Expected untouched scan value"; } TEST(cgroupTest, read_numerical_key_value_null) { TestController* null_path_controller = new TestController((char*)nullptr); const char* test_file_path = "/not-used"; const char* key = "something"; constexpr uint64_t bad = 0xBAD; uint64_t a = bad; // null subsystem_path() case bool is_ok = null_path_controller->read_numerical_key_value(test_file_path, key, a); EXPECT_FALSE(is_ok) << "Null subsystem path should be an error"; EXPECT_EQ(bad, a) << "Expected untouched scan value"; } TEST(cgroupTest, read_number_tests) { char* test_file = temp_file("cgroups"); const char* b = basename(test_file); constexpr uint64_t bad = 0xBAD; EXPECT_TRUE(b != nullptr) << "basename was null"; stringStream path; path.print_raw(os::file_separator()); path.print_raw(b); const char* base_with_slash = path.as_string(true); fill_file(test_file, "8888"); TestController* controller = new TestController((char*)os::get_temp_directory()); uint64_t foo = bad; bool ok = controller->read_number(base_with_slash, foo); EXPECT_TRUE(ok) << "Number parsing should have been successful"; EXPECT_EQ((uint64_t)8888, foo) << "Wrong value for 'foo' (NOTE: 0xBAD == " << 0xBAD << ")"; // Some interface files might have negative values, ensure we can read // them and manually cast them as needed. For example, on cgv1, the cpu.cfs_quota_us // file might be set to -1 to indicate no cpu quota setup. fill_file(test_file, "-1"); foo = bad; ok = controller->read_number(base_with_slash, foo); EXPECT_TRUE(ok) << "Number parsing should have been successful"; EXPECT_EQ((int)-1, (int)foo) << "Wrong value for 'foo' (NOTE: 0xBAD == " << 0xBAD << ")"; foo = bad; fill_file(test_file, nullptr); ok = controller->read_number(base_with_slash, foo); EXPECT_FALSE(ok) << "Empty file should have failed"; EXPECT_EQ(bad, foo) << "foo was altered"; // Some interface files have numbers as well as the string // 'max', which means unlimited. uint64_t result = 0; uint64_t unlimited = std::numeric_limits::max(); fill_file(test_file, "max\n"); ok = controller->read_number_handle_max(base_with_slash, result); EXPECT_TRUE(ok) << "Number parsing for 'max' string should have been successful"; EXPECT_EQ(unlimited, result) << "'max' means unlimited (-1)"; result = 0; fill_file(test_file, "11114\n"); ok = controller->read_number_handle_max(base_with_slash, result); EXPECT_TRUE(ok) << "Number parsing for should have been successful"; EXPECT_EQ((uint64_t)11114, result) << "Incorrect result"; result = 0; // This is a contrived test case not matching cgroup interface files // in the wild where numbers are positive. The value is deliberately // negative. Yet it should work fill_file(test_file, "-51114\n"); ok = controller->read_number_handle_max(base_with_slash, result); EXPECT_TRUE(ok) << "Number parsing for should have been successful"; EXPECT_EQ((int)-51114, (int)result) << "Incorrect result"; delete_file(test_file); } TEST(cgroupTest, read_string_tests) { char* test_file = temp_file("cgroups"); const char* b = basename(test_file); EXPECT_TRUE(b != nullptr) << "basename was null"; stringStream path; path.print_raw(os::file_separator()); path.print_raw(b); const char* base_with_slash = path.as_string(true); fill_file(test_file, "foo-bar"); TestController* controller = new TestController((char*)os::get_temp_directory()); char result[1024]; bool ok = controller->read_string(base_with_slash, result, 1024); EXPECT_TRUE(ok) << "String parsing should have been successful"; EXPECT_STREQ("foo-bar", result); result[0] = '\0'; fill_file(test_file, "1234"); ok = controller->read_string(base_with_slash, result, 1024); EXPECT_TRUE(ok) << "String parsing should have been successful"; EXPECT_STREQ("1234", result); // values with a space result[0] = '\0'; fill_file(test_file, "abc def"); ok = controller->read_string(base_with_slash, result, 1024); EXPECT_TRUE(ok) << "String parsing should have been successful"; EXPECT_STREQ("abc def", result); result[0] = '\0'; fill_file(test_file, " \na"); ok = controller->read_string(base_with_slash, result, 1024); EXPECT_TRUE(ok) << "String parsing should have been successful"; EXPECT_STREQ(" ", result); // only the first line are being returned result[0] = '\0'; fill_file(test_file, "test\nabc"); ok = controller->read_string(base_with_slash, result, 1024); EXPECT_TRUE(ok) << "String parsing should have been successful"; EXPECT_STREQ("test", result); result[0] = '\0'; fill_file(test_file, nullptr); ok = controller->read_string(base_with_slash, result, 1024); EXPECT_FALSE(ok) << "Empty file should have failed"; EXPECT_STREQ("", result) << "Expected untouched result"; // File contents larger than 1K // We only read in the first 1K - 1 bytes const size_t large_len = 2 * 1024; char too_large[large_len]; for (size_t i = 0; i < large_len; i++) { too_large[i] = 'A' + (i % 26); } too_large[large_len - 1] = '\0'; result[0] = '\0'; fill_file(test_file, too_large); ok = controller->read_string(base_with_slash, result, 1024); EXPECT_TRUE(ok) << "String parsing should have been successful"; EXPECT_TRUE(1023 == strlen(result)) << "Expected only the first 1023 chars to be read in"; EXPECT_EQ(0, strncmp(too_large, result, 1023)); EXPECT_EQ(result[1023], '\0') << "The last character must be the null character"; delete_file(test_file); } TEST(cgroupTest, read_number_tuple_test) { char* test_file = temp_file("cgroups"); const char* b = basename(test_file); EXPECT_TRUE(b != nullptr) << "basename was null"; stringStream path; path.print_raw(os::file_separator()); path.print_raw(b); const char* base_with_slash = path.as_string(true); fill_file(test_file, "max 10000"); TestController* controller = new TestController((char*)os::get_temp_directory()); uint64_t result = 0; bool ok = controller->read_numerical_tuple_value(base_with_slash, true /* use_first */, result); EXPECT_TRUE(ok) << "Should be OK to read value"; EXPECT_EQ(value_unlimited, result) << "max should be unlimited (-1)"; result = 0; ok = controller->read_numerical_tuple_value(base_with_slash, false /* use_first */, result); EXPECT_TRUE(ok) << "Should be OK to read the value"; EXPECT_EQ((uint64_t)10000, result); // non-max strings fill_file(test_file, "abc 10000"); result = 0; ok = controller->read_numerical_tuple_value(base_with_slash, true /* use_first */, result); EXPECT_FALSE(ok) << "abc should not be parsable"; EXPECT_EQ((uint64_t)0, result) << "result value should be unchanged"; fill_file(test_file, nullptr); result = 0; ok = controller->read_numerical_tuple_value(base_with_slash, true /* use_first */, result); EXPECT_FALSE(ok) << "Empty file should be an error"; EXPECT_EQ((uint64_t)0, result) << "result value should be unchanged"; delete_file(test_file); } TEST(cgroupTest, read_numerical_key_beyond_max_path) { char larger_than_max[MAXPATHLEN + 1]; for (int i = 0; i < (MAXPATHLEN); i++) { larger_than_max[i] = 'A' + (i % 26); } larger_than_max[MAXPATHLEN] = '\0'; TestController* too_large_path_controller = new TestController(larger_than_max); const char* test_file_path = "/file-not-found"; const char* key = "something"; uint64_t a = 0xBAD; bool is_ok = too_large_path_controller->read_numerical_key_value(test_file_path, key, a); EXPECT_FALSE(is_ok) << "Too long path should be an error"; EXPECT_EQ((uint64_t)0xBAD, a) << "Expected untouched scan value"; } TEST(cgroupTest, read_numerical_key_file_not_exist) { TestController* unknown_path_ctrl = new TestController((char*)"/do/not/exist"); const char* test_file_path = "/file-not-found"; const char* key = "something"; uint64_t a = 0xBAD; bool is_ok = unknown_path_ctrl->read_numerical_key_value(test_file_path, key, a); EXPECT_FALSE(is_ok) << "File not found should be an error"; EXPECT_EQ((uint64_t)0xBAD, a) << "Expected untouched scan value"; } TEST(cgroupTest, set_cgroupv1_subsystem_path) { TestCase host = { "/sys/fs/cgroup/memory", // mount_path "/", // root_path "/user.slice/user-1000.slice/user@1000.service", // cgroup_path "/sys/fs/cgroup/memory/user.slice/user-1000.slice/user@1000.service" // expected_path }; TestCase container_engine = { "/sys/fs/cgroup/mem", // mount_path "/user.slice/user-1000.slice/user@1000.service", // root_path "/user.slice/user-1000.slice/user@1000.service", // cgroup_path "/sys/fs/cgroup/mem" // expected_path }; TestCase container_moving_cgroup = { "/sys/fs/cgroup/cpu,cpuacct", // mount_path "/system.slice/garden.service/garden/good/2f57368b-0eda-4e52-64d8-af5c", // root_path "/system.slice/garden.service/garden/bad/2f57368b-0eda-4e52-64d8-af5c", // cgroup_path "/sys/fs/cgroup/cpu,cpuacct" // expected_path }; int length = 3; TestCase* testCases[] = { &host, &container_engine, &container_moving_cgroup }; for (int i = 0; i < length; i++) { CgroupV1Controller* ctrl = new CgroupV1Controller( (char*)testCases[i]->root_path, (char*)testCases[i]->mount_path, true /* read-only mount */); ctrl->set_subsystem_path((char*)testCases[i]->cgroup_path); ASSERT_STREQ(testCases[i]->expected_path, ctrl->subsystem_path()); } } TEST(cgroupTest, set_cgroupv1_subsystem_path_adjusted) { TestCase memory = { "/sys/fs/cgroup/memory", // mount_path "/", // root_path "../test1", // cgroup_path "/sys/fs/cgroup/memory" // expected_path }; TestCase cpu = { "/sys/fs/cgroup/cpu", // mount_path "/", // root_path "../../test2", // cgroup_path "/sys/fs/cgroup/cpu" // expected_path }; CgroupCpuController* ccc = new CgroupV1CpuController(CgroupV1Controller((char*)cpu.root_path, (char*)cpu.mount_path, true /* read-only mount */)); ccc->set_subsystem_path((char*)cpu.cgroup_path); EXPECT_TRUE(ccc->needs_hierarchy_adjustment()); CgroupUtil::adjust_controller(ccc); ASSERT_STREQ(cpu.expected_path, ccc->subsystem_path()); EXPECT_FALSE(ccc->needs_hierarchy_adjustment()); CgroupMemoryController* cmc = new CgroupV1MemoryController(CgroupV1Controller((char*)memory.root_path, (char*)memory.mount_path, true /* read-only mount */)); cmc->set_subsystem_path((char*)memory.cgroup_path); EXPECT_TRUE(cmc->needs_hierarchy_adjustment()); CgroupUtil::adjust_controller(cmc); ASSERT_STREQ(memory.expected_path, cmc->subsystem_path()); EXPECT_FALSE(cmc->needs_hierarchy_adjustment()); } TEST(cgroupTest, set_cgroupv2_subsystem_path_adjusted) { TestCase memory = { "/sys/fs/cgroup", // mount_path "/", // root_path "../test1", // cgroup_path "/sys/fs/cgroup" // expected_path }; TestCase cpu = { "/sys/fs/cgroup", // mount_path "/", // root_path "../../test2", // cgroup_path "/sys/fs/cgroup" // expected_path }; CgroupCpuController* ccc = new CgroupV2CpuController(CgroupV2Controller((char*)cpu.mount_path, (char*)cpu.cgroup_path, true /* read-only mount */)); EXPECT_TRUE(ccc->needs_hierarchy_adjustment()); CgroupUtil::adjust_controller(ccc); ASSERT_STREQ(cpu.expected_path, ccc->subsystem_path()); EXPECT_FALSE(ccc->needs_hierarchy_adjustment()); CgroupMemoryController* cmc = new CgroupV2MemoryController(CgroupV2Controller((char*)memory.mount_path, (char*)memory.cgroup_path, true /* read-only mount */)); EXPECT_TRUE(cmc->needs_hierarchy_adjustment()); CgroupUtil::adjust_controller(cmc); ASSERT_STREQ(memory.expected_path, cmc->subsystem_path()); EXPECT_FALSE(cmc->needs_hierarchy_adjustment()); } TEST(cgroupTest, set_cgroupv2_subsystem_path) { TestCase at_mount_root = { "/sys/fs/cgroup", // mount_path nullptr, // root_path, ignored "/", // cgroup_path "/sys/fs/cgroup" // expected_path }; TestCase sub_path = { "/sys/fs/cgroup", // mount_path nullptr, // root_path, ignored "/foobar", // cgroup_path "/sys/fs/cgroup/foobar" // expected_path }; int length = 2; TestCase* testCases[] = { &at_mount_root, &sub_path }; for (int i = 0; i < length; i++) { CgroupV2Controller* ctrl = new CgroupV2Controller( (char*)testCases[i]->mount_path, (char*)testCases[i]->cgroup_path, true /* read-only mount */); ASSERT_STREQ(testCases[i]->expected_path, ctrl->subsystem_path()); } } TEST(cgroupTest, cgroupv2_is_hierarchy_walk_needed) { bool controller_read_only = false; // value irrelevant; CgroupV2Controller* test = new CgroupV2Controller((char*)"/sys/fs/cgroup", (char*)"/" /* cgroup_path */, controller_read_only); EXPECT_FALSE(test->needs_hierarchy_adjustment()); test = new CgroupV2Controller((char*)"/sys/fs/cgroup", (char*)"/bar" /* cgroup_path */, controller_read_only); EXPECT_TRUE(test->needs_hierarchy_adjustment()); test = new CgroupV2Controller((char*)"/sys/fs/cgroup/b", (char*)"/a/b" /* cgroup_path */, controller_read_only); EXPECT_TRUE(test->needs_hierarchy_adjustment()); CgroupCpuController* test2 = new CgroupV2CpuController(CgroupV2Controller((char*)"/sys/fs/cgroup", (char*)"/" /* cgroup_path */, controller_read_only)); EXPECT_FALSE(test2->needs_hierarchy_adjustment()); test2 = new CgroupV2CpuController(CgroupV2Controller((char*)"/sys/fs/cgroup", (char*)"/bar" /* cgroup_path */, controller_read_only)); EXPECT_TRUE(test2->needs_hierarchy_adjustment()); test2 = new CgroupV2CpuController(CgroupV2Controller((char*)"/sys/fs/cgroup/b", (char*)"/a/b" /* cgroup_path */, controller_read_only)); EXPECT_TRUE(test2->needs_hierarchy_adjustment()); CgroupMemoryController* test3 = new CgroupV2MemoryController(CgroupV2Controller((char*)"/sys/fs/cgroup", (char*)"/" /* cgroup_path */, controller_read_only)); EXPECT_FALSE(test3->needs_hierarchy_adjustment()); test3 = new CgroupV2MemoryController(CgroupV2Controller((char*)"/sys/fs/cgroup", (char*)"/bar" /* cgroup_path */, controller_read_only)); EXPECT_TRUE(test3->needs_hierarchy_adjustment()); test3 = new CgroupV2MemoryController(CgroupV2Controller((char*)"/sys/fs/cgroup/b", (char*)"/a/b" /* cgroup_path */, controller_read_only)); EXPECT_TRUE(test3->needs_hierarchy_adjustment()); } TEST(cgroupTest, cgroupv1_is_hierarchy_walk_needed) { bool controller_read_only = true; // shouldn't matter; CgroupV1Controller* test = new CgroupV1Controller((char*)"/a/b/c" /* root */, (char*)"/sys/fs/cgroup/memory" /* mount_path */, controller_read_only); test->set_subsystem_path((char*)"/a/b/c"); EXPECT_FALSE(test->needs_hierarchy_adjustment()); test->set_subsystem_path((char*)"/"); EXPECT_TRUE(test->needs_hierarchy_adjustment()); test = new CgroupV1Controller((char*)"/a/b/c" /* root */, (char*)"/"/* mount_path */, controller_read_only); test->set_subsystem_path((char*)"/"); EXPECT_TRUE(test->needs_hierarchy_adjustment()); CgroupCpuController* test2 = new CgroupV1CpuController(CgroupV1Controller((char*)"/a/b/c" /* root */, (char*)"/sys/fs/cgroup/memory" /* mount_path */, controller_read_only)); static_cast(test2)->set_subsystem_path((char*)"/a/b/c"); EXPECT_FALSE(test2->needs_hierarchy_adjustment()); static_cast(test2)->set_subsystem_path((char*)"/"); EXPECT_TRUE(test2->needs_hierarchy_adjustment()); test2 = new CgroupV1CpuController(CgroupV1Controller((char*)"/a/b/c" /* root */, (char*)"/"/* mount_path */, controller_read_only)); static_cast(test2)->set_subsystem_path((char*)"/"); EXPECT_TRUE(test2->needs_hierarchy_adjustment()); CgroupMemoryController* test3 = new CgroupV1MemoryController(CgroupV1Controller((char*)"/a/b/c" /* root */, (char*)"/sys/fs/cgroup/memory" /* mount_path */, controller_read_only)); static_cast(test3)->set_subsystem_path((char*)"/a/b/c"); EXPECT_FALSE(test3->needs_hierarchy_adjustment()); static_cast(test3)->set_subsystem_path((char*)"/"); EXPECT_TRUE(test3->needs_hierarchy_adjustment()); test3 = new CgroupV1MemoryController(CgroupV1Controller((char*)"/a/b/c" /* root */, (char*)"/"/* mount_path */, controller_read_only)); static_cast(test3)->set_subsystem_path((char*)"/"); EXPECT_TRUE(test3->needs_hierarchy_adjustment()); } #endif // LINUX