blob: 40811d01332bd9ec85f0577ea496b52359a71a8a [file] [log] [blame] [edit]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "swap_management/swap_tool.h"
#include <limits>
#include <utility>
#include <absl/random/random.h>
#include <absl/strings/str_cat.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
using testing::_;
using testing::DoAll;
using testing::ElementsAre;
using testing::Return;
using testing::SetArgPointee;
using testing::StrictMock;
namespace swap_management {
namespace {
const char kSwapsNoZram[] =
"Filename "
" Type Size "
"Used Priority\n";
const char kMeminfoMemTotal8G[] = R"(MemTotal: 8144424 kB
MemFree: 5712260 kB
MemAvailable: 6368308 kB
Buffers: 64092 kB
Cached: 1045820 kB
SwapCached: 0 kB
Active: 1437424 kB
Inactive: 493512 kB
Active(anon): 848464 kB
Inactive(anon): 63600 kB
Active(file): 588960 kB
Inactive(file): 429912 kB
Unevictable: 68676 kB
Mlocked: 40996 kB
SwapTotal: 16288844 kB
SwapFree: 16288844 kB
Dirty: 380 kB
Writeback: 0 kB
AnonPages: 889736 kB
Mapped: 470060 kB
Shmem: 91040 kB
KReclaimable: 87488 kB
Slab: 174412 kB
SReclaimable: 87488 kB
SUnreclaim: 86924 kB
KernelStack: 9056 kB
PageTables: 18120 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 20361056 kB
Committed_AS: 5581424 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 145016 kB
VmallocChunk: 0 kB
Percpu: 2976 kB
AnonHugePages: 40960 kB
ShmemHugePages: 0 kB
ShmemPmdMapped: 0 kB
FileHugePages: 0 kB
FilePmdMapped: 0 kB
DirectMap4k: 188268 kB
DirectMap2M: 8200192 kB)";
const char kZramDisksize8G[] = "16679780352";
const char kChromeosLowMemMargin8G[] = "413 3181";
} // namespace
class MockSwapToolUtil : public swap_management::SwapToolUtil {
public:
MockSwapToolUtil() = default;
MockSwapToolUtil& operator=(const MockSwapToolUtil&) = delete;
MockSwapToolUtil(const MockSwapToolUtil&) = delete;
MOCK_METHOD(absl::Status,
RunProcessHelper,
(const std::vector<std::string>& commands),
(override));
MOCK_METHOD(absl::Status,
RunProcessHelper,
(const std::vector<std::string>& commands, std::string* output),
(override));
MOCK_METHOD(absl::Status,
WriteFile,
(const base::FilePath& path, const std::string& data),
(override));
MOCK_METHOD(absl::Status,
ReadFileToStringWithMaxSize,
(const base::FilePath& path,
std::string* contents,
size_t max_size),
(override));
MOCK_METHOD(absl::Status,
ReadFileToString,
(const base::FilePath& path, std::string* contents),
(override));
MOCK_METHOD(absl::Status,
DeleteFile,
(const base::FilePath& path),
(override));
MOCK_METHOD(absl::Status,
PathExists,
(const base::FilePath& path),
(override));
MOCK_METHOD(absl::Status,
Fallocate,
(const base::FilePath& path, size_t size),
(override));
MOCK_METHOD(absl::Status,
CreateDirectory,
(const base::FilePath& path),
(override));
MOCK_METHOD(absl::Status,
SetPosixFilePermissions,
(const base::FilePath& path, int mode),
(override));
MOCK_METHOD(absl::Status,
Mount,
(const std::string& source,
const std::string& target,
const std::string& fs_type,
uint64_t mount_flags,
const std::string& data),
(override));
MOCK_METHOD(absl::Status, Umount, (const std::string& target), (override));
MOCK_METHOD(absl::StatusOr<struct statfs>,
GetStatfs,
(const std::string& path),
(override));
MOCK_METHOD(absl::StatusOr<std::string>,
GenerateRandHex,
(size_t size),
(override));
};
class SwapToolTest : public ::testing::Test {
public:
void SetUp() override {
swap_tool_ = std::make_unique<SwapTool>();
// Init SwapToolUtil and then replace with mocked one.
SwapToolUtil::OverrideForTesting(&mock_util_);
}
protected:
std::unique_ptr<SwapTool> swap_tool_;
MockSwapToolUtil mock_util_;
};
TEST_F(SwapToolTest, SwapIsAlreadyOnOrOff) {
EXPECT_CALL(mock_util_, ReadFileToString(base::FilePath("/proc/swaps"), _))
.WillOnce(DoAll(SetArgPointee<1>(
absl::StrCat(kSwapsNoZram,
"/dev/zram0 "
" partition 16288844 "
"0 -2\n")),
Return(absl::OkStatus())));
EXPECT_THAT(swap_tool_->SwapStart(), absl::OkStatus());
EXPECT_CALL(mock_util_, ReadFileToString(base::FilePath("/proc/swaps"), _))
.WillOnce(DoAll(
SetArgPointee<1>(absl::StrCat(kSwapsNoZram,
"/zram0 "
"partition 16288844 "
"0 -2\n")),
Return(absl::OkStatus())));
EXPECT_THAT(swap_tool_->SwapStart(), absl::OkStatus());
EXPECT_CALL(mock_util_, ReadFileToString(base::FilePath("/proc/swaps"), _))
.WillOnce(
DoAll(SetArgPointee<1>(kSwapsNoZram), Return(absl::OkStatus())));
EXPECT_THAT(swap_tool_->SwapStop(), absl::OkStatus());
}
TEST_F(SwapToolTest, SwapStartWithoutSwapEnabled) {
// IsZramSwapOn
EXPECT_CALL(mock_util_, ReadFileToString(base::FilePath("/proc/swaps"), _))
.WillOnce(
DoAll(SetArgPointee<1>(kSwapsNoZram), Return(absl::OkStatus())));
// GetMemTotal
EXPECT_CALL(mock_util_, ReadFileToString(base::FilePath("/proc/meminfo"), _))
.WillOnce(DoAll(SetArgPointee<1>(kMeminfoMemTotal8G),
Return(absl::OkStatus())));
// InitializeMMTunables
EXPECT_CALL(
mock_util_,
WriteFile(base::FilePath("/sys/kernel/mm/chromeos-low_mem/margin"),
kChromeosLowMemMargin8G))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(
mock_util_,
WriteFile(base::FilePath("/proc/sys/vm/min_filelist_kbytes"), "1000000"))
.WillOnce(Return(absl::OkStatus()));
// GetZramSize
EXPECT_CALL(mock_util_, ReadFileToStringWithMaxSize(
base::FilePath("/var/lib/swap/swap_size"), _, _))
.WillOnce(Return(
absl::NotFoundError("Failed to read /var/lib/swap/swap_size")));
EXPECT_CALL(mock_util_,
RunProcessHelper(ElementsAre("/sbin/modprobe", "zram")))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_util_, WriteFile(base::FilePath("/sys/block/zram0/disksize"),
kZramDisksize8G))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_util_,
RunProcessHelper(ElementsAre("/sbin/mkswap", "/dev/zram0")))
.WillOnce(Return(absl::OkStatus()));
// EnableZramSwapping
EXPECT_CALL(mock_util_,
RunProcessHelper(ElementsAre("/sbin/swapon", "/dev/zram0")))
.WillOnce(Return(absl::OkStatus()));
EXPECT_THAT(swap_tool_->SwapStart(), absl::OkStatus());
}
TEST_F(SwapToolTest, SwapStartButSwapIsDisabled) {
// IsZramSwapOn
EXPECT_CALL(mock_util_, ReadFileToString(base::FilePath("/proc/swaps"), _))
.WillOnce(
DoAll(SetArgPointee<1>(kSwapsNoZram), Return(absl::OkStatus())));
// GetMemTotal
EXPECT_CALL(mock_util_, ReadFileToString(base::FilePath("/proc/meminfo"), _))
.WillOnce(DoAll(SetArgPointee<1>(kMeminfoMemTotal8G),
Return(absl::OkStatus())));
// InitializeMMTunables
EXPECT_CALL(
mock_util_,
WriteFile(base::FilePath("/sys/kernel/mm/chromeos-low_mem/margin"),
kChromeosLowMemMargin8G))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(
mock_util_,
WriteFile(base::FilePath("/proc/sys/vm/min_filelist_kbytes"), "1000000"))
.WillOnce(Return(absl::OkStatus()));
// GetZramSize
EXPECT_CALL(mock_util_, ReadFileToStringWithMaxSize(
base::FilePath("/var/lib/swap/swap_size"), _, _))
.WillOnce(DoAll(SetArgPointee<1>("0"), Return(absl::OkStatus())));
absl::Status status = swap_tool_->SwapStart();
EXPECT_TRUE(absl::IsInvalidArgument(status));
EXPECT_EQ(status.ToString(),
"INVALID_ARGUMENT: Swap is not turned on since "
"/var/lib/swap/swap_size contains 0.");
}
TEST_F(SwapToolTest, SwapStartWithEmptySwapZramSize) {
// IsZramSwapOn
EXPECT_CALL(mock_util_, ReadFileToString(base::FilePath("/proc/swaps"), _))
.WillOnce(
DoAll(SetArgPointee<1>(kSwapsNoZram), Return(absl::OkStatus())));
// GetMemTotal
EXPECT_CALL(mock_util_, ReadFileToString(base::FilePath("/proc/meminfo"), _))
.WillOnce(DoAll(SetArgPointee<1>(kMeminfoMemTotal8G),
Return(absl::OkStatus())));
// InitializeMMTunables
EXPECT_CALL(
mock_util_,
WriteFile(base::FilePath("/sys/kernel/mm/chromeos-low_mem/margin"),
kChromeosLowMemMargin8G))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(
mock_util_,
WriteFile(base::FilePath("/proc/sys/vm/min_filelist_kbytes"), "1000000"))
.WillOnce(Return(absl::OkStatus()));
// GetZramSize
EXPECT_CALL(mock_util_, ReadFileToStringWithMaxSize(
base::FilePath("/var/lib/swap/swap_size"), _, _))
.WillOnce(DoAll(SetArgPointee<1>(""), Return(absl::OkStatus())));
EXPECT_CALL(mock_util_,
RunProcessHelper(ElementsAre("/sbin/modprobe", "zram")))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_util_, WriteFile(base::FilePath("/sys/block/zram0/disksize"),
kZramDisksize8G))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_util_,
RunProcessHelper(ElementsAre("/sbin/mkswap", "/dev/zram0")))
.WillOnce(Return(absl::OkStatus()));
// EnableZramSwapping
EXPECT_CALL(mock_util_,
RunProcessHelper(ElementsAre("/sbin/swapon", "/dev/zram0")))
.WillOnce(Return(absl::OkStatus()));
EXPECT_THAT(swap_tool_->SwapStart(), absl::OkStatus());
}
TEST_F(SwapToolTest, SwapStop) {
// IsZramSwapOn
EXPECT_CALL(mock_util_, ReadFileToString(base::FilePath("/proc/swaps"), _))
.WillOnce(DoAll(
SetArgPointee<1>(absl::StrCat(std::string(kSwapsNoZram),
"/zram0 "
"partition 16288844 "
"0 -2\n")),
Return(absl::OkStatus())));
EXPECT_CALL(mock_util_, RunProcessHelper(
ElementsAre("/sbin/swapoff", "-v", "/dev/zram0")))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_util_, WriteFile(base::FilePath("/sys/block/zram0/reset"),
std::to_string(1)))
.WillOnce(Return(absl::OkStatus()));
EXPECT_THAT(swap_tool_->SwapStop(), absl::OkStatus());
}
TEST_F(SwapToolTest, SwapSetSize) {
// If size is 0.
EXPECT_CALL(mock_util_, DeleteFile(base::FilePath("/var/lib/swap/swap_size")))
.WillOnce(Return(absl::OkStatus()));
EXPECT_THAT(swap_tool_->SwapSetSize(0), absl::OkStatus());
// If size is larger than 20000.
absl::BitGen gen;
uint32_t size = absl::uniform_int_distribution<uint32_t>(
20001, std::numeric_limits<uint32_t>::max())(gen);
absl::Status status = swap_tool_->SwapSetSize(size);
EXPECT_TRUE(absl::IsInvalidArgument(status));
EXPECT_EQ(status.ToString(),
"INVALID_ARGUMENT: Size is not between 100 and 20000 MiB.");
// If size is smaller than 100, but not 0.
size = absl::uniform_int_distribution<uint32_t>(1, 99)(gen);
status = swap_tool_->SwapSetSize(size);
EXPECT_TRUE(absl::IsInvalidArgument(status));
EXPECT_EQ(status.ToString(),
"INVALID_ARGUMENT: Size is not between 100 and 20000 MiB.");
// If size is between 100 and 20000.
size = absl::uniform_int_distribution<uint32_t>(100, 20000)(gen);
EXPECT_CALL(mock_util_, WriteFile(base::FilePath("/var/lib/swap/swap_size"),
absl::StrCat(size)))
.WillOnce(Return(absl::OkStatus()));
EXPECT_THAT(swap_tool_->SwapSetSize(size), absl::OkStatus());
}
TEST_F(SwapToolTest, SwapZramEnableWriteback) {
// ZramWritebackPrerequisiteCheck
EXPECT_CALL(
mock_util_,
ReadFileToString(base::FilePath("/sys/block/zram0/backing_dev"), _))
.WillOnce(DoAll(SetArgPointee<1>("none\n"), Return(absl::OkStatus())));
EXPECT_CALL(mock_util_, DeleteFile(base::FilePath("/run/zram-integrity")))
.WillOnce(Return(absl::OkStatus()));
// GetZramWritebackInfo
struct statfs sf = {
.f_bsize = 4096,
.f_blocks = 2038647,
.f_bfree = 1159962,
};
EXPECT_CALL(
mock_util_,
GetStatfs("/mnt/stateful_partition/unencrypted/userspace_swap.tmp"))
.WillOnce(Return(std::move(sf)));
// CreateDmDevicesAndEnableWriteback
EXPECT_CALL(
mock_util_,
WriteFile(base::FilePath("/mnt/stateful_partition/unencrypted/"
"userspace_swap.tmp/zram_writeback.swap"),
std::string()))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(
mock_util_,
Fallocate(base::FilePath("/mnt/stateful_partition/unencrypted/"
"userspace_swap.tmp/zram_writeback.swap"),
134217728))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(
mock_util_,
RunProcessHelper(ElementsAre("/sbin/losetup", "--show", "--direct-io=on",
"--sector-size=4096", "-f",
"/mnt/stateful_partition/unencrypted/"
"userspace_swap.tmp/zram_writeback.swap"),
_))
.WillOnce(
DoAll(SetArgPointee<1>("/dev/loop10\n"), Return(absl::OkStatus())));
EXPECT_CALL(mock_util_,
CreateDirectory(base::FilePath("/run/zram-integrity")))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_util_, SetPosixFilePermissions(
base::FilePath("/run/zram-integrity"), 0700))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_util_, Mount("none", "/run/zram-integrity", "ramfs", 0,
"noexec,nosuid,noatime,mode=0700"))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(
mock_util_,
WriteFile(base::FilePath("/run/zram-integrity/zram_integrity.swap"),
std::string(1048576, 0)))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(
mock_util_,
RunProcessHelper(ElementsAre("/sbin/losetup", "--show", "-f",
"/run/zram-integrity/zram_integrity.swap"),
_))
.WillOnce(
DoAll(SetArgPointee<1>("/dev/loop11\n"), Return(absl::OkStatus())));
EXPECT_CALL(
mock_util_,
RunProcessHelper(ElementsAre(
"/sbin/dmsetup", "create", "zram-integrity", "--table",
"0 262144 integrity /dev/loop10 0 24 D 4 block_size:4096 "
"meta_device:/dev/loop11 journal_sectors:1 buffer_sectors:128")))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_util_,
PathExists(base::FilePath("/dev/mapper/zram-integrity")))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_util_, GenerateRandHex(32))
.WillOnce(Return(std::move(
"31EDB364E004FA99CFDBA21D726284A810421F7466F892ED2306DB7FB917084E")));
EXPECT_CALL(mock_util_,
RunProcessHelper(ElementsAre(
"/sbin/dmsetup", "create", "zram-writeback", "--table",
"0 262144 crypt capi:gcm(aes)-random "
"31EDB364E004FA99CFDBA21D726284A810421F7466F892ED2306DB7FB917"
"084E 0 /dev/mapper/zram-integrity 0 4 allow_discards "
"submit_from_crypt_cpus sector_size:4096 integrity:24:aead")))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_util_,
PathExists(base::FilePath("/dev/mapper/zram-writeback")))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_util_,
WriteFile(base::FilePath("/sys/block/zram0/backing_dev"),
"/dev/mapper/zram-writeback"))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_util_, DeleteFile(base::FilePath(
"/mnt/stateful_partition/unencrypted/"
"userspace_swap.tmp/zram_writeback.swap")))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(
mock_util_,
DeleteFile(base::FilePath("/run/zram-integrity/zram_integrity.swap")))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_util_, RunProcessHelper(ElementsAre("/sbin/losetup", "-d",
"/dev/loop10")))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_util_, RunProcessHelper(ElementsAre("/sbin/losetup", "-d",
"/dev/loop11")))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_util_,
RunProcessHelper(ElementsAre("/sbin/dmsetup", "remove",
"--deferred", "zram-writeback")))
.WillOnce(Return(absl::OkStatus()));
EXPECT_CALL(mock_util_,
RunProcessHelper(ElementsAre("/sbin/dmsetup", "remove",
"--deferred", "zram-integrity")))
.WillOnce(Return(absl::OkStatus()));
EXPECT_THAT(swap_tool_->SwapZramEnableWriteback(128), absl::OkStatus());
}
} // namespace swap_management