| // Copyright (c) 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/memory/userspace_swap/swap_storage.h" |
| |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <chrono> |
| #include <random> |
| |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_file.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "chromeos/memory/userspace_swap/region.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace chromeos { |
| namespace memory { |
| namespace userspace_swap { |
| |
| class SwapStorageTest : public testing::Test, |
| public testing::WithParamInterface<SwapFile::Type> { |
| public: |
| SwapStorageTest() |
| : random_(/* seen prng */ |
| std::chrono::system_clock::now().time_since_epoch().count()) {} |
| |
| protected: |
| void SetUp() override { |
| // Start by creating a temporary file we will use for this test and then |
| // wrapping it in the swap type we're testing. |
| base::FilePath temp_file; |
| ASSERT_TRUE(base::CreateTemporaryFile(&temp_file)); |
| |
| base::ScopedFD swap_fd; |
| swap_fd.reset(HANDLE_EINTR(open(temp_file.MaybeAsASCII().c_str(), |
| O_RDWR | O_CLOEXEC, S_IRUSR | S_IWUSR))); |
| |
| ASSERT_NE(unlink(temp_file.MaybeAsASCII().c_str()), -1); |
| |
| ASSERT_TRUE(swap_fd.is_valid()); |
| |
| // Wrap this FD in the swap file type for this test. |
| swap_ = SwapFile::WrapFD(std::move(swap_fd), GetParam()); |
| ASSERT_NE(swap_, nullptr); |
| } |
| |
| void FillRandom(std::string* str, int size) { |
| for (int j = 0; j < size; ++j) { |
| str->append(1, static_cast<char>(random_() % 255)); |
| } |
| |
| ASSERT_EQ(str->size(), static_cast<std::string::size_type>(size)); |
| } |
| |
| std::default_random_engine random_; |
| std::unique_ptr<SwapFile> swap_; |
| }; |
| |
| // We test all the different variations of a swap file to make sure all |
| // functionality is properly implemented, this will test: |
| // - Standard swap file with no compression or encryption. |
| // - A compressed swap file. |
| // - An encrypted swap file. |
| // - A compressed and encrypted swap file. |
| INSTANTIATE_TEST_SUITE_P( |
| SwapStorageTest, |
| SwapStorageTest, |
| testing::Values( |
| SwapFile::Type::kStandard /* no compression or encryption */, |
| SwapFile::Type::kCompressed, |
| SwapFile::Type::kEncrypted, |
| SwapFile::Type::kEncrypted | SwapFile::Type::kCompressed)); |
| |
| TEST_P(SwapStorageTest, SimpleWriteRead) { |
| std::string buffer = "hello world"; |
| |
| // Swap address can be at index 0 so we use UINT64_MAX to differentiate. |
| Region swap_region(std::numeric_limits<uint64_t>::max(), 0); |
| |
| // Write it to swap and validate we also got back sane values for swap pos and |
| // length. |
| ASSERT_TRUE(swap_->WriteToSwap(Region(buffer.c_str(), buffer.length()), |
| &swap_region)); |
| ASSERT_NE(swap_region.address, std::numeric_limits<uint64_t>::max()); |
| ASSERT_NE(swap_region.length, 0u); |
| |
| // Read the region from swap in [swap_pos, swap_pos + swap_len] |
| char read_buf[buffer.length()]; |
| memset(read_buf, 0, sizeof(read_buf)); |
| ASSERT_EQ( |
| swap_->ReadFromSwap(swap_region, Region(read_buf, sizeof(read_buf))), |
| static_cast<ssize_t>(sizeof(read_buf))); |
| |
| // We should have correctly read back what we wrote. |
| ASSERT_EQ(memcmp(read_buf, buffer.c_str(), sizeof(read_buf)), 0); |
| } |
| |
| TEST_P(SwapStorageTest, ManyWriteRead) { |
| // Write 1000 random length buffers and then read them back in a random order |
| // and make sure they are as expected. |
| std::vector<std::pair<std::string, Region>> buffers; |
| |
| constexpr int kNumBuffers = 1000; |
| buffers.reserve(kNumBuffers); |
| for (int i = 0; i < kNumBuffers; ++i) { |
| // Choose a random length between 1 byte and 10KB |
| int buffer_len = (random_() % (10 << 10)) + 1; |
| std::string buf; |
| buf.reserve(buffer_len); |
| FillRandom(&buf, buffer_len); |
| |
| // Swap pos can be at index 0 so we use UINT64_MAX to differentiate. |
| Region swap_region(std::numeric_limits<uint64_t>::max(), 0); |
| |
| // Write it to swap and validate we also got back sane values for swap pos |
| // and length. |
| ASSERT_TRUE( |
| swap_->WriteToSwap(Region(buf.c_str(), buf.size()), &swap_region)); |
| ASSERT_NE(swap_region.address, std::numeric_limits<uint64_t>::max()); |
| ASSERT_NE(swap_region.length, 0u); |
| |
| // Save where this buffer was written. |
| buffers.emplace_back(std::move(buf), swap_region); |
| } |
| |
| // Shuffle the ordering of the buffers so we read them back in a random order. |
| std::shuffle(buffers.begin(), buffers.end(), random_); |
| |
| // Read back all the regions and verify. |
| for (const auto& buf : buffers) { |
| char read_buf[buf.first.size()]; |
| memset(read_buf, 0, sizeof(read_buf)); |
| ASSERT_EQ(swap_->ReadFromSwap(/* Region */ buf.second, |
| Region(read_buf, sizeof(read_buf))), |
| static_cast<ssize_t>(sizeof(read_buf))); |
| |
| // We should have correctly read back what we wrote. |
| ASSERT_EQ(memcmp(read_buf, buf.first.c_str(), sizeof(read_buf)), 0); |
| |
| // Now drop it from the swap. |
| ASSERT_TRUE(swap_->DropFromSwap(/* Region */ buf.second)); |
| } |
| } |
| |
| TEST_P(SwapStorageTest, DropFromSwap) { |
| // This test validates that we can drop what we wrote from the swap file and |
| // the block size will return to the original size. |
| uint64_t block_size_kb_before = swap_->GetUsageKB(); |
| std::string buffer; |
| |
| // We want to fill buffer with what should be many blocks of random data so we |
| // can fully observe the growing and shrinking size. |
| FillRandom(&buffer, 32 * (4 << 10)); |
| |
| // Swap pos can be at index 0 so we use UINT64_MAX to differentiate. |
| Region swap_region(std::numeric_limits<uint64_t>::max(), 0); |
| |
| // Write it to swap and validate we also got back sane values for swap pos and |
| // length. |
| ASSERT_TRUE( |
| swap_->WriteToSwap(Region(buffer.c_str(), buffer.size()), &swap_region)); |
| ASSERT_NE(swap_region.address, std::numeric_limits<uint64_t>::max()); |
| ASSERT_NE(swap_region.length, 0u); |
| |
| uint64_t block_size_kb = swap_->GetUsageKB(); |
| ASSERT_GT(block_size_kb, block_size_kb_before); |
| |
| // Read the region from swap in [swap_pos, swap_pos + swap_len] |
| char read_buf[buffer.length()]; |
| memset(read_buf, 0, sizeof(read_buf)); |
| ASSERT_EQ( |
| swap_->ReadFromSwap(swap_region, Region(read_buf, sizeof(read_buf))), |
| static_cast<ssize_t>(sizeof(read_buf))); |
| |
| // We should have correctly read back what we wrote. |
| ASSERT_EQ(memcmp(read_buf, buffer.c_str(), sizeof(read_buf)), 0); |
| |
| // Now we will drop it. |
| ASSERT_TRUE(swap_->DropFromSwap(swap_region)); |
| |
| // Finally check the size. |
| uint64_t block_size_kb_end = swap_->GetUsageKB(); |
| ASSERT_LT(block_size_kb_end, block_size_kb); |
| } |
| |
| } // namespace userspace_swap |
| } // namespace memory |
| } // namespace chromeos |