210 lines
7.5 KiB
C++
210 lines
7.5 KiB
C++
/*
|
|
* Copyright (C) 2022 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "task_profiles.h"
|
|
#include <android-base/logging.h>
|
|
#include <gtest/gtest.h>
|
|
#include <mntent.h>
|
|
#include <processgroup/processgroup.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
|
|
#include <fstream>
|
|
|
|
using ::android::base::ERROR;
|
|
using ::android::base::LogFunction;
|
|
using ::android::base::LogId;
|
|
using ::android::base::LogSeverity;
|
|
using ::android::base::SetLogger;
|
|
using ::android::base::VERBOSE;
|
|
using ::testing::TestWithParam;
|
|
using ::testing::Values;
|
|
|
|
namespace {
|
|
|
|
bool IsCgroupV2Mounted() {
|
|
std::unique_ptr<FILE, int (*)(FILE*)> mnts(setmntent("/proc/mounts", "re"), endmntent);
|
|
if (!mnts) {
|
|
LOG(ERROR) << "Failed to open /proc/mounts";
|
|
return false;
|
|
}
|
|
struct mntent* mnt;
|
|
while ((mnt = getmntent(mnts.get()))) {
|
|
if (strcmp(mnt->mnt_fsname, "cgroup2") == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
class ScopedLogCapturer {
|
|
public:
|
|
struct log_args {
|
|
LogId log_buffer_id;
|
|
LogSeverity severity;
|
|
std::string tag;
|
|
std::string file;
|
|
unsigned int line;
|
|
std::string message;
|
|
};
|
|
|
|
// Constructor. Installs a new logger and saves the currently active logger.
|
|
ScopedLogCapturer() {
|
|
saved_severity_ = SetMinimumLogSeverity(android::base::VERBOSE);
|
|
saved_logger_ = SetLogger([this](LogId log_buffer_id, LogSeverity severity, const char* tag,
|
|
const char* file, unsigned int line, const char* message) {
|
|
if (saved_logger_) {
|
|
saved_logger_(log_buffer_id, severity, tag, file, line, message);
|
|
}
|
|
log_.emplace_back(log_args{.log_buffer_id = log_buffer_id,
|
|
.severity = severity,
|
|
.tag = tag,
|
|
.file = file,
|
|
.line = line,
|
|
.message = message});
|
|
});
|
|
}
|
|
// Destructor. Restores the original logger and log level.
|
|
~ScopedLogCapturer() {
|
|
SetLogger(std::move(saved_logger_));
|
|
SetMinimumLogSeverity(saved_severity_);
|
|
}
|
|
ScopedLogCapturer(const ScopedLogCapturer&) = delete;
|
|
ScopedLogCapturer& operator=(const ScopedLogCapturer&) = delete;
|
|
// Returns the logged lines.
|
|
const std::vector<log_args>& Log() const { return log_; }
|
|
|
|
private:
|
|
LogSeverity saved_severity_;
|
|
LogFunction saved_logger_;
|
|
std::vector<log_args> log_;
|
|
};
|
|
|
|
// cgroup attribute at the top level of the cgroup hierarchy.
|
|
class ProfileAttributeMock : public IProfileAttribute {
|
|
public:
|
|
ProfileAttributeMock(const std::string& file_name) : file_name_(file_name) {}
|
|
~ProfileAttributeMock() override = default;
|
|
void Reset(const CgroupController& controller, const std::string& file_name) override {
|
|
CHECK(false);
|
|
}
|
|
const CgroupController* controller() const override {
|
|
CHECK(false);
|
|
return {};
|
|
}
|
|
const std::string& file_name() const override { return file_name_; }
|
|
bool GetPathForTask(int tid, std::string* path) const override {
|
|
#ifdef __ANDROID__
|
|
CHECK(CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, path));
|
|
CHECK_GT(path->length(), 0);
|
|
if (path->rbegin()[0] != '/') {
|
|
*path += "/";
|
|
}
|
|
#else
|
|
// Not Android.
|
|
*path = "/sys/fs/cgroup/";
|
|
#endif
|
|
*path += file_name_;
|
|
return true;
|
|
};
|
|
|
|
private:
|
|
const std::string file_name_;
|
|
};
|
|
|
|
struct TestParam {
|
|
const char* attr_name;
|
|
const char* attr_value;
|
|
bool optional_attr;
|
|
bool result;
|
|
LogSeverity log_severity;
|
|
const char* log_prefix;
|
|
const char* log_suffix;
|
|
};
|
|
|
|
class SetAttributeFixture : public TestWithParam<TestParam> {
|
|
public:
|
|
~SetAttributeFixture() = default;
|
|
};
|
|
|
|
TEST_P(SetAttributeFixture, SetAttribute) {
|
|
// Treehugger runs host tests inside a container without cgroupv2 support.
|
|
if (!IsCgroupV2Mounted()) {
|
|
GTEST_SKIP();
|
|
return;
|
|
}
|
|
const TestParam params = GetParam();
|
|
ScopedLogCapturer captured_log;
|
|
ProfileAttributeMock pa(params.attr_name);
|
|
SetAttributeAction a(&pa, params.attr_value, params.optional_attr);
|
|
EXPECT_EQ(a.ExecuteForProcess(getuid(), getpid()), params.result);
|
|
auto log = captured_log.Log();
|
|
if (params.log_prefix || params.log_suffix) {
|
|
ASSERT_EQ(log.size(), 1);
|
|
EXPECT_EQ(log[0].severity, params.log_severity);
|
|
if (params.log_prefix) {
|
|
EXPECT_EQ(log[0].message.find(params.log_prefix), 0);
|
|
}
|
|
if (params.log_suffix) {
|
|
EXPECT_NE(log[0].message.find(params.log_suffix), std::string::npos);
|
|
}
|
|
} else {
|
|
ASSERT_EQ(log.size(), 0);
|
|
}
|
|
}
|
|
|
|
// Test the four combinations of optional_attr {false, true} and cgroup attribute { does not exist,
|
|
// exists }.
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
SetAttributeTestSuite, SetAttributeFixture,
|
|
Values(
|
|
// Test that attempting to write into a non-existing cgroup attribute fails and also
|
|
// that an error message is logged.
|
|
TestParam{.attr_name = "no-such-attribute",
|
|
.attr_value = ".",
|
|
.optional_attr = false,
|
|
.result = false,
|
|
.log_severity = ERROR,
|
|
.log_prefix = "No such cgroup attribute"},
|
|
// Test that attempting to write into an optional non-existing cgroup attribute
|
|
// results in the return value 'true' and also that no messages are logged.
|
|
TestParam{.attr_name = "no-such-attribute",
|
|
.attr_value = ".",
|
|
.optional_attr = true,
|
|
.result = true},
|
|
// Test that attempting to write an invalid value into an existing optional cgroup
|
|
// attribute fails and also that it causes an error
|
|
// message to be logged.
|
|
TestParam{.attr_name = "cgroup.procs",
|
|
.attr_value = "-1",
|
|
.optional_attr = true,
|
|
.result = false,
|
|
.log_severity = ERROR,
|
|
.log_prefix = "Failed to write",
|
|
.log_suffix = geteuid() == 0 ? "Invalid argument" : "Permission denied"},
|
|
// Test that attempting to write into an existing optional read-only cgroup
|
|
// attribute fails and also that it causes an error message to be logged.
|
|
TestParam{
|
|
.attr_name = "cgroup.controllers",
|
|
.attr_value = ".",
|
|
.optional_attr = false,
|
|
.result = false,
|
|
.log_severity = ERROR,
|
|
.log_prefix = "Failed to write",
|
|
.log_suffix = geteuid() == 0 ? "Invalid argument" : "Permission denied"}));
|
|
|
|
} // namespace
|