| // Copyright 2014 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 "base/basictypes.h" |
| #include "base/memory/discardable_shared_memory.h" |
| #include "base/process/process_metrics.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace base { |
| namespace { |
| |
| class TestDiscardableSharedMemory : public DiscardableSharedMemory { |
| public: |
| TestDiscardableSharedMemory() {} |
| |
| explicit TestDiscardableSharedMemory(SharedMemoryHandle handle) |
| : DiscardableSharedMemory(handle) {} |
| |
| void SetNow(Time now) { now_ = now; } |
| |
| private: |
| // Overriden from DiscardableSharedMemory: |
| Time Now() const override { return now_; } |
| |
| Time now_; |
| }; |
| |
| TEST(DiscardableSharedMemoryTest, CreateAndMap) { |
| const uint32 kDataSize = 1024; |
| |
| TestDiscardableSharedMemory memory; |
| bool rv = memory.CreateAndMap(kDataSize); |
| ASSERT_TRUE(rv); |
| EXPECT_GE(memory.mapped_size(), kDataSize); |
| } |
| |
| TEST(DiscardableSharedMemoryTest, CreateFromHandle) { |
| const uint32 kDataSize = 1024; |
| |
| TestDiscardableSharedMemory memory1; |
| bool rv = memory1.CreateAndMap(kDataSize); |
| ASSERT_TRUE(rv); |
| |
| SharedMemoryHandle shared_handle; |
| ASSERT_TRUE( |
| memory1.ShareToProcess(GetCurrentProcessHandle(), &shared_handle)); |
| ASSERT_TRUE(SharedMemory::IsHandleValid(shared_handle)); |
| |
| TestDiscardableSharedMemory memory2(shared_handle); |
| rv = memory2.Map(kDataSize); |
| ASSERT_TRUE(rv); |
| } |
| |
| TEST(DiscardableSharedMemoryTest, LockAndUnlock) { |
| const uint32 kDataSize = 1024; |
| |
| TestDiscardableSharedMemory memory1; |
| bool rv = memory1.CreateAndMap(kDataSize); |
| ASSERT_TRUE(rv); |
| |
| // Memory is initially locked. Unlock it. |
| memory1.SetNow(Time::FromDoubleT(1)); |
| memory1.Unlock(0, 0); |
| |
| // Lock and unlock memory. |
| auto lock_rv = memory1.Lock(0, 0); |
| EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv); |
| memory1.SetNow(Time::FromDoubleT(2)); |
| memory1.Unlock(0, 0); |
| |
| // Lock again before duplicating and passing ownership to new instance. |
| lock_rv = memory1.Lock(0, 0); |
| EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv); |
| |
| SharedMemoryHandle shared_handle; |
| ASSERT_TRUE( |
| memory1.ShareToProcess(GetCurrentProcessHandle(), &shared_handle)); |
| ASSERT_TRUE(SharedMemory::IsHandleValid(shared_handle)); |
| |
| TestDiscardableSharedMemory memory2(shared_handle); |
| rv = memory2.Map(kDataSize); |
| ASSERT_TRUE(rv); |
| |
| // Unlock second instance. |
| memory2.SetNow(Time::FromDoubleT(3)); |
| memory2.Unlock(0, 0); |
| |
| // Lock second instance before passing ownership back to first instance. |
| lock_rv = memory2.Lock(0, 0); |
| EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv); |
| |
| // Memory should still be resident. |
| rv = memory1.IsMemoryResident(); |
| EXPECT_TRUE(rv); |
| |
| // Unlock first instance. |
| memory1.SetNow(Time::FromDoubleT(4)); |
| memory1.Unlock(0, 0); |
| } |
| |
| TEST(DiscardableSharedMemoryTest, Purge) { |
| const uint32 kDataSize = 1024; |
| |
| TestDiscardableSharedMemory memory1; |
| bool rv = memory1.CreateAndMap(kDataSize); |
| ASSERT_TRUE(rv); |
| |
| SharedMemoryHandle shared_handle; |
| ASSERT_TRUE( |
| memory1.ShareToProcess(GetCurrentProcessHandle(), &shared_handle)); |
| ASSERT_TRUE(SharedMemory::IsHandleValid(shared_handle)); |
| |
| TestDiscardableSharedMemory memory2(shared_handle); |
| rv = memory2.Map(kDataSize); |
| ASSERT_TRUE(rv); |
| |
| // This should fail as memory is locked. |
| rv = memory1.Purge(Time::FromDoubleT(1)); |
| EXPECT_FALSE(rv); |
| |
| memory2.SetNow(Time::FromDoubleT(2)); |
| memory2.Unlock(0, 0); |
| |
| ASSERT_TRUE(memory2.IsMemoryResident()); |
| |
| // Memory is unlocked, but our usage timestamp is incorrect. |
| rv = memory1.Purge(Time::FromDoubleT(3)); |
| EXPECT_FALSE(rv); |
| |
| ASSERT_TRUE(memory2.IsMemoryResident()); |
| |
| // Memory is unlocked and our usage timestamp should be correct. |
| rv = memory1.Purge(Time::FromDoubleT(4)); |
| EXPECT_TRUE(rv); |
| |
| // Lock should fail as memory has been purged. |
| auto lock_rv = memory2.Lock(0, 0); |
| EXPECT_EQ(DiscardableSharedMemory::FAILED, lock_rv); |
| |
| ASSERT_FALSE(memory2.IsMemoryResident()); |
| } |
| |
| TEST(DiscardableSharedMemoryTest, LastUsed) { |
| const uint32 kDataSize = 1024; |
| |
| TestDiscardableSharedMemory memory1; |
| bool rv = memory1.CreateAndMap(kDataSize); |
| ASSERT_TRUE(rv); |
| |
| SharedMemoryHandle shared_handle; |
| ASSERT_TRUE( |
| memory1.ShareToProcess(GetCurrentProcessHandle(), &shared_handle)); |
| ASSERT_TRUE(SharedMemory::IsHandleValid(shared_handle)); |
| |
| TestDiscardableSharedMemory memory2(shared_handle); |
| rv = memory2.Map(kDataSize); |
| ASSERT_TRUE(rv); |
| |
| memory2.SetNow(Time::FromDoubleT(1)); |
| memory2.Unlock(0, 0); |
| |
| EXPECT_EQ(memory2.last_known_usage(), Time::FromDoubleT(1)); |
| |
| auto lock_rv = memory2.Lock(0, 0); |
| EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv); |
| |
| // This should fail as memory is locked. |
| rv = memory1.Purge(Time::FromDoubleT(2)); |
| ASSERT_FALSE(rv); |
| |
| // Last usage should have been updated to timestamp passed to Purge above. |
| EXPECT_EQ(memory1.last_known_usage(), Time::FromDoubleT(2)); |
| |
| memory2.SetNow(Time::FromDoubleT(3)); |
| memory2.Unlock(0, 0); |
| |
| // Usage time should be correct for |memory2| instance. |
| EXPECT_EQ(memory2.last_known_usage(), Time::FromDoubleT(3)); |
| |
| // However, usage time has not changed as far as |memory1| instance knows. |
| EXPECT_EQ(memory1.last_known_usage(), Time::FromDoubleT(2)); |
| |
| // Memory is unlocked, but our usage timestamp is incorrect. |
| rv = memory1.Purge(Time::FromDoubleT(4)); |
| EXPECT_FALSE(rv); |
| |
| // The failed purge attempt should have updated usage time to the correct |
| // value. |
| EXPECT_EQ(memory1.last_known_usage(), Time::FromDoubleT(3)); |
| |
| // Purge memory through |memory2| instance. The last usage time should be |
| // set to 0 as a result of this. |
| rv = memory2.Purge(Time::FromDoubleT(5)); |
| EXPECT_TRUE(rv); |
| EXPECT_TRUE(memory2.last_known_usage().is_null()); |
| |
| // This should fail as memory has already been purged and |memory1|'s usage |
| // time is incorrect as a result. |
| rv = memory1.Purge(Time::FromDoubleT(6)); |
| EXPECT_FALSE(rv); |
| |
| // The failed purge attempt should have updated usage time to the correct |
| // value. |
| EXPECT_TRUE(memory1.last_known_usage().is_null()); |
| |
| // Purge should succeed now that usage time is correct. |
| rv = memory1.Purge(Time::FromDoubleT(7)); |
| EXPECT_TRUE(rv); |
| } |
| |
| TEST(DiscardableSharedMemoryTest, LockShouldAlwaysFailAfterSuccessfulPurge) { |
| const uint32 kDataSize = 1024; |
| |
| TestDiscardableSharedMemory memory1; |
| bool rv = memory1.CreateAndMap(kDataSize); |
| ASSERT_TRUE(rv); |
| |
| SharedMemoryHandle shared_handle; |
| ASSERT_TRUE( |
| memory1.ShareToProcess(GetCurrentProcessHandle(), &shared_handle)); |
| ASSERT_TRUE(SharedMemory::IsHandleValid(shared_handle)); |
| |
| TestDiscardableSharedMemory memory2(shared_handle); |
| rv = memory2.Map(kDataSize); |
| ASSERT_TRUE(rv); |
| |
| memory2.SetNow(Time::FromDoubleT(1)); |
| memory2.Unlock(0, 0); |
| |
| rv = memory2.Purge(Time::FromDoubleT(2)); |
| EXPECT_TRUE(rv); |
| |
| // Lock should fail as memory has been purged. |
| auto lock_rv = memory2.Lock(0, 0); |
| EXPECT_EQ(DiscardableSharedMemory::FAILED, lock_rv); |
| } |
| |
| TEST(DiscardableSharedMemoryTest, LockAndUnlockRange) { |
| const uint32 kDataSize = 32; |
| |
| uint32 data_size_in_bytes = kDataSize * base::GetPageSize(); |
| |
| TestDiscardableSharedMemory memory1; |
| bool rv = memory1.CreateAndMap(data_size_in_bytes); |
| ASSERT_TRUE(rv); |
| |
| SharedMemoryHandle shared_handle; |
| ASSERT_TRUE( |
| memory1.ShareToProcess(GetCurrentProcessHandle(), &shared_handle)); |
| ASSERT_TRUE(SharedMemory::IsHandleValid(shared_handle)); |
| |
| TestDiscardableSharedMemory memory2(shared_handle); |
| rv = memory2.Map(data_size_in_bytes); |
| ASSERT_TRUE(rv); |
| |
| // Unlock first page. |
| memory2.SetNow(Time::FromDoubleT(1)); |
| memory2.Unlock(0, base::GetPageSize()); |
| |
| rv = memory1.Purge(Time::FromDoubleT(2)); |
| EXPECT_FALSE(rv); |
| |
| // Lock first page again. |
| memory2.SetNow(Time::FromDoubleT(3)); |
| auto lock_rv = memory2.Lock(0, base::GetPageSize()); |
| EXPECT_NE(DiscardableSharedMemory::FAILED, lock_rv); |
| |
| // Unlock first page. |
| memory2.SetNow(Time::FromDoubleT(4)); |
| memory2.Unlock(0, base::GetPageSize()); |
| |
| rv = memory1.Purge(Time::FromDoubleT(5)); |
| EXPECT_FALSE(rv); |
| |
| // Unlock second page. |
| memory2.SetNow(Time::FromDoubleT(6)); |
| memory2.Unlock(base::GetPageSize(), base::GetPageSize()); |
| |
| rv = memory1.Purge(Time::FromDoubleT(7)); |
| EXPECT_FALSE(rv); |
| |
| // Unlock anything onwards. |
| memory2.SetNow(Time::FromDoubleT(8)); |
| memory2.Unlock(2 * base::GetPageSize(), 0); |
| |
| // Memory is unlocked, but our usage timestamp is incorrect. |
| rv = memory1.Purge(Time::FromDoubleT(9)); |
| EXPECT_FALSE(rv); |
| |
| // The failed purge attempt should have updated usage time to the correct |
| // value. |
| EXPECT_EQ(Time::FromDoubleT(8), memory1.last_known_usage()); |
| |
| // Purge should now succeed. |
| rv = memory1.Purge(Time::FromDoubleT(10)); |
| EXPECT_TRUE(rv); |
| } |
| |
| TEST(DiscardableSharedMemoryTest, MappedSize) { |
| const uint32 kDataSize = 1024; |
| |
| TestDiscardableSharedMemory memory; |
| bool rv = memory.CreateAndMap(kDataSize); |
| ASSERT_TRUE(rv); |
| |
| EXPECT_LE(kDataSize, memory.mapped_size()); |
| |
| // Mapped size should be 0 after memory segment has been unmapped. |
| rv = memory.Unmap(); |
| EXPECT_TRUE(rv); |
| EXPECT_EQ(0u, memory.mapped_size()); |
| } |
| |
| TEST(DiscardableSharedMemoryTest, Close) { |
| const uint32 kDataSize = 1024; |
| |
| TestDiscardableSharedMemory memory; |
| bool rv = memory.CreateAndMap(kDataSize); |
| ASSERT_TRUE(rv); |
| |
| // Mapped size should be unchanged after memory segment has been closed. |
| memory.Close(); |
| EXPECT_LE(kDataSize, memory.mapped_size()); |
| |
| // Memory is initially locked. Unlock it. |
| memory.SetNow(Time::FromDoubleT(1)); |
| memory.Unlock(0, 0); |
| |
| // Lock and unlock memory. |
| auto lock_rv = memory.Lock(0, 0); |
| EXPECT_EQ(DiscardableSharedMemory::SUCCESS, lock_rv); |
| memory.SetNow(Time::FromDoubleT(2)); |
| memory.Unlock(0, 0); |
| } |
| |
| #if defined(DISCARDABLE_SHARED_MEMORY_SHRINKING) |
| TEST(DiscardableSharedMemoryTest, Shrink) { |
| const uint32 kDataSize = 1024; |
| |
| TestDiscardableSharedMemory memory; |
| bool rv = memory.CreateAndMap(kDataSize); |
| ASSERT_TRUE(rv); |
| |
| EXPECT_NE(0u, memory.mapped_size()); |
| |
| // Mapped size should be 0 after shrinking memory segment. |
| memory.Shrink(); |
| EXPECT_EQ(0u, memory.mapped_size()); |
| } |
| #endif |
| |
| } // namespace |
| } // namespace base |