root/base/memory/discardable_memory_manager_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. is_locked_
  2. Unlock
  3. Memory
  4. IsRegistered
  5. CanBePurged
  6. BytesAllocated
  7. Memory
  8. SetDiscardableMemoryLimit
  9. SetBytesToKeepUnderModeratePressure
  10. CreateLockedMemory
  11. TEST_F
  12. TEST_F
  13. TEST_F
  14. TEST_F
  15. TEST_F
  16. TEST_F
  17. ordering
  18. CreateAndUseDiscardableMemory
  19. discardable
  20. TEST_P
  21. TEST_P
  22. TEST_P
  23. TEST_P
  24. TEST_F
  25. TEST_F
  26. TEST_F
  27. thread_sync_
  28. SetUp
  29. TearDown
  30. UseMemoryHelper
  31. SignalHelper
  32. TEST_F

// 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/memory/discardable_memory_manager.h"

#include "base/bind.h"
#include "base/memory/discardable_memory.h"
#include "base/run_loop.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {

class DiscardableMemoryManagerTestBase {
 public:
  class TestDiscardableMemory : public DiscardableMemory {
   public:
    TestDiscardableMemory(
        internal::DiscardableMemoryManager* manager, size_t size)
        : manager_(manager),
          is_locked_(false) {
      manager_->Register(this, size);
    }

    virtual ~TestDiscardableMemory() {
      if (is_locked_)
        Unlock();
      manager_->Unregister(this);
    }

    // Overridden from DiscardableMemory:
    virtual DiscardableMemoryLockStatus Lock() OVERRIDE {
      DCHECK(!is_locked_);

      bool purged = false;
      memory_ = manager_->Acquire(this, &purged);
      if (!memory_)
        return DISCARDABLE_MEMORY_LOCK_STATUS_FAILED;

      is_locked_ = true;
      return purged ? DISCARDABLE_MEMORY_LOCK_STATUS_PURGED
                    : DISCARDABLE_MEMORY_LOCK_STATUS_SUCCESS;
    }
    virtual void Unlock() OVERRIDE {
      DCHECK(is_locked_);
      manager_->Release(this, memory_.Pass());
      is_locked_ = false;
    }
    virtual void* Memory() const OVERRIDE {
      DCHECK(memory_);
      return memory_.get();
    }

   private:
    internal::DiscardableMemoryManager* manager_;
    scoped_ptr<uint8, FreeDeleter> memory_;
    bool is_locked_;

    DISALLOW_COPY_AND_ASSIGN(TestDiscardableMemory);
  };

  DiscardableMemoryManagerTestBase()
      : manager_(new internal::DiscardableMemoryManager) {
    manager_->RegisterMemoryPressureListener();
  }

 protected:
  bool IsRegistered(const DiscardableMemory* discardable) {
    return manager_->IsRegisteredForTest(discardable);
  }

  bool CanBePurged(const DiscardableMemory* discardable) {
    return manager_->CanBePurgedForTest(discardable);
  }

  size_t BytesAllocated() const {
    return manager_->GetBytesAllocatedForTest();
  }

  void* Memory(const DiscardableMemory* discardable) const {
    return discardable->Memory();
  }

  void SetDiscardableMemoryLimit(size_t bytes) {
    manager_->SetDiscardableMemoryLimit(bytes);
  }

  void SetBytesToKeepUnderModeratePressure(size_t bytes) {
    manager_->SetBytesToKeepUnderModeratePressure(bytes);
  }

  scoped_ptr<DiscardableMemory> CreateLockedMemory(size_t size) {
    scoped_ptr<TestDiscardableMemory> memory(
        new TestDiscardableMemory(manager_.get(), size));
    if (memory->Lock() != DISCARDABLE_MEMORY_LOCK_STATUS_PURGED)
      return scoped_ptr<DiscardableMemory>();
    return memory.PassAs<DiscardableMemory>();
  }

 private:
  MessageLoopForIO message_loop_;
  scoped_ptr<internal::DiscardableMemoryManager> manager_;
};

class DiscardableMemoryManagerTest
    : public DiscardableMemoryManagerTestBase,
      public testing::Test {
 public:
  DiscardableMemoryManagerTest() {}
};

TEST_F(DiscardableMemoryManagerTest, CreateLockedMemory) {
  size_t size = 1024;
  const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size));
  EXPECT_TRUE(IsRegistered(discardable.get()));
  EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get()));
  EXPECT_EQ(1024u, BytesAllocated());
  EXPECT_FALSE(CanBePurged(discardable.get()));
}

TEST_F(DiscardableMemoryManagerTest, CreateLockedMemoryZeroSize) {
  size_t size = 0;
  const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size));
  EXPECT_FALSE(discardable);
  EXPECT_FALSE(IsRegistered(discardable.get()));
  EXPECT_EQ(0u, BytesAllocated());
}

TEST_F(DiscardableMemoryManagerTest, LockAfterUnlock) {
  size_t size = 1024;
  const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size));
  EXPECT_TRUE(IsRegistered(discardable.get()));
  EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get()));
  EXPECT_EQ(1024u, BytesAllocated());
  EXPECT_FALSE(CanBePurged(discardable.get()));

  // Now unlock so we can lock later.
  discardable->Unlock();
  EXPECT_TRUE(CanBePurged(discardable.get()));

  EXPECT_EQ(DISCARDABLE_MEMORY_LOCK_STATUS_SUCCESS, discardable->Lock());
  EXPECT_FALSE(CanBePurged(discardable.get()));
}

TEST_F(DiscardableMemoryManagerTest, LockAfterPurge) {
  size_t size = 1024;
  const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size));
  EXPECT_TRUE(IsRegistered(discardable.get()));
  EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get()));
  EXPECT_EQ(1024u, BytesAllocated());
  EXPECT_FALSE(CanBePurged(discardable.get()));

  // Now unlock so we can lock later.
  discardable->Unlock();
  EXPECT_TRUE(CanBePurged(discardable.get()));

  // Force the system to purge.
  MemoryPressureListener::NotifyMemoryPressure(
      MemoryPressureListener::MEMORY_PRESSURE_CRITICAL);

  // Required because ObserverListThreadSafe notifies via PostTask.
  RunLoop().RunUntilIdle();

  EXPECT_EQ(DISCARDABLE_MEMORY_LOCK_STATUS_PURGED, discardable->Lock());
  EXPECT_FALSE(CanBePurged(discardable.get()));
}

TEST_F(DiscardableMemoryManagerTest, LockAfterPurgeAndCannotReallocate) {
  size_t size = 1024;
  const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size));
  EXPECT_TRUE(IsRegistered(discardable.get()));
  EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get()));
  EXPECT_EQ(1024u, BytesAllocated());
  EXPECT_FALSE(CanBePurged(discardable.get()));

  // Now unlock so we can lock later.
  discardable->Unlock();
  EXPECT_TRUE(CanBePurged(discardable.get()));

  // Set max allowed allocation to 1 byte. This will make cause the memory
  // to be purged.
  SetDiscardableMemoryLimit(1);

  EXPECT_EQ(DISCARDABLE_MEMORY_LOCK_STATUS_PURGED, discardable->Lock());
  EXPECT_FALSE(CanBePurged(discardable.get()));
}

TEST_F(DiscardableMemoryManagerTest, Overflow) {
  {
    size_t size = 1024;
    const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size));
    EXPECT_TRUE(IsRegistered(discardable.get()));
    EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get()));
    EXPECT_EQ(1024u, BytesAllocated());

    size_t massive_size = std::numeric_limits<size_t>::max();
    const scoped_ptr<DiscardableMemory> massive_discardable(
        CreateLockedMemory(massive_size));
    EXPECT_FALSE(massive_discardable);
    EXPECT_EQ(1024u, BytesAllocated());
  }
  EXPECT_EQ(0u, BytesAllocated());
}

class PermutationTestData {
 public:
  PermutationTestData(unsigned d0, unsigned d1, unsigned d2) {
    ordering_[0] = d0;
    ordering_[1] = d1;
    ordering_[2] = d2;
  }

  const unsigned* ordering() const { return ordering_; }

 private:
  unsigned ordering_[3];
};

class DiscardableMemoryManagerPermutationTest
    : public DiscardableMemoryManagerTestBase,
      public testing::TestWithParam<PermutationTestData> {
 public:
  DiscardableMemoryManagerPermutationTest() {}

 protected:
  // Use discardable memory in order specified by ordering parameter.
  void CreateAndUseDiscardableMemory() {
    for (int i = 0; i < 3; ++i) {
      discardables_[i] = CreateLockedMemory(1024);
      EXPECT_TRUE(discardables_[i]);
      EXPECT_NE(static_cast<void*>(NULL), Memory(discardables_[i].get()));
      discardables_[i]->Unlock();
    }
    for (int i = 0; i < 3; ++i) {
      int index = GetParam().ordering()[i];
      EXPECT_NE(DISCARDABLE_MEMORY_LOCK_STATUS_FAILED,
                discardables_[index]->Lock());
      // Leave i == 0 locked.
      if (i > 0)
        discardables_[index]->Unlock();
    }
  }

  DiscardableMemory* discardable(unsigned position) {
    return discardables_[GetParam().ordering()[position]].get();
  }

 private:
  scoped_ptr<DiscardableMemory> discardables_[3];
};

// Verify that memory was discarded in the correct order after applying
// memory pressure.
TEST_P(DiscardableMemoryManagerPermutationTest, LRUDiscardedModeratePressure) {
  CreateAndUseDiscardableMemory();

  SetBytesToKeepUnderModeratePressure(1024);
  SetDiscardableMemoryLimit(2048);

  MemoryPressureListener::NotifyMemoryPressure(
      MemoryPressureListener::MEMORY_PRESSURE_MODERATE);
  RunLoop().RunUntilIdle();

  EXPECT_NE(DISCARDABLE_MEMORY_LOCK_STATUS_FAILED, discardable(2)->Lock());
  EXPECT_NE(DISCARDABLE_MEMORY_LOCK_STATUS_SUCCESS, discardable(1)->Lock());
  // 0 should still be locked.
  EXPECT_NE(static_cast<void*>(NULL), Memory(discardable(0)));
}

// Verify that memory was discarded in the correct order after changing
// memory limit.
TEST_P(DiscardableMemoryManagerPermutationTest, LRUDiscardedExceedLimit) {
  CreateAndUseDiscardableMemory();

  SetBytesToKeepUnderModeratePressure(1024);
  SetDiscardableMemoryLimit(2048);

  EXPECT_NE(DISCARDABLE_MEMORY_LOCK_STATUS_FAILED, discardable(2)->Lock());
  EXPECT_NE(DISCARDABLE_MEMORY_LOCK_STATUS_SUCCESS, discardable(1)->Lock());
  // 0 should still be locked.
  EXPECT_NE(static_cast<void*>(NULL), Memory(discardable(0)));
}

// Verify that no more memory than necessary was discarded after changing
// memory limit.
TEST_P(DiscardableMemoryManagerPermutationTest, LRUDiscardedAmount) {
  SetBytesToKeepUnderModeratePressure(2048);
  SetDiscardableMemoryLimit(4096);

  CreateAndUseDiscardableMemory();

  SetDiscardableMemoryLimit(2048);

  EXPECT_EQ(DISCARDABLE_MEMORY_LOCK_STATUS_SUCCESS, discardable(2)->Lock());
  EXPECT_EQ(DISCARDABLE_MEMORY_LOCK_STATUS_PURGED, discardable(1)->Lock());
  // 0 should still be locked.
  EXPECT_NE(static_cast<void*>(NULL), Memory(discardable(0)));
}

TEST_P(DiscardableMemoryManagerPermutationTest,
       CriticalPressureFreesAllUnlocked) {
  CreateAndUseDiscardableMemory();

  MemoryPressureListener::NotifyMemoryPressure(
      MemoryPressureListener::MEMORY_PRESSURE_CRITICAL);
  RunLoop().RunUntilIdle();

  for (int i = 0; i < 3; ++i) {
    if (i == 0)
      EXPECT_NE(static_cast<void*>(NULL), Memory(discardable(i)));
    else
      EXPECT_EQ(DISCARDABLE_MEMORY_LOCK_STATUS_PURGED, discardable(i)->Lock());
  }
}

INSTANTIATE_TEST_CASE_P(DiscardableMemoryManagerPermutationTests,
                        DiscardableMemoryManagerPermutationTest,
                        ::testing::Values(PermutationTestData(0, 1, 2),
                                          PermutationTestData(0, 2, 1),
                                          PermutationTestData(1, 0, 2),
                                          PermutationTestData(1, 2, 0),
                                          PermutationTestData(2, 0, 1),
                                          PermutationTestData(2, 1, 0)));

TEST_F(DiscardableMemoryManagerTest, NormalDestruction) {
  {
    size_t size = 1024;
    const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size));
    EXPECT_TRUE(IsRegistered(discardable.get()));
    EXPECT_EQ(1024u, BytesAllocated());
  }
  EXPECT_EQ(0u, BytesAllocated());
}

TEST_F(DiscardableMemoryManagerTest, DestructionWhileLocked) {
  {
    size_t size = 1024;
    const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size));
    EXPECT_TRUE(IsRegistered(discardable.get()));
    EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get()));
    EXPECT_EQ(1024u, BytesAllocated());
    EXPECT_FALSE(CanBePurged(discardable.get()));
  }
  // Should have ignored the "locked" status and freed the discardable memory.
  EXPECT_EQ(0u, BytesAllocated());
}

#if !defined(NDEBUG) && !defined(OS_ANDROID) && !defined(OS_IOS)
// Death tests are not supported with Android APKs.
TEST_F(DiscardableMemoryManagerTest, UnlockedMemoryAccessCrashesInDebugMode) {
  size_t size = 1024;
  const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size));
  EXPECT_TRUE(IsRegistered(discardable.get()));
  EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get()));
  EXPECT_EQ(1024u, BytesAllocated());
  EXPECT_FALSE(CanBePurged(discardable.get()));
  discardable->Unlock();
  EXPECT_TRUE(CanBePurged(discardable.get()));
  // We *must* die if we are asked to vend a pointer to unlocked memory.
  EXPECT_DEATH(discardable->Memory(), ".*Check failed.*");
}
#endif

class ThreadedDiscardableMemoryManagerTest
    : public DiscardableMemoryManagerTest {
 public:
  ThreadedDiscardableMemoryManagerTest()
      : memory_usage_thread_("memory_usage_thread"),
        thread_sync_(true, false) {
  }

  virtual void SetUp() OVERRIDE {
    memory_usage_thread_.Start();
  }

  virtual void TearDown() OVERRIDE {
    memory_usage_thread_.Stop();
  }

  void UseMemoryHelper() {
    size_t size = 1024;
    const scoped_ptr<DiscardableMemory> discardable(CreateLockedMemory(size));
    EXPECT_TRUE(IsRegistered(discardable.get()));
    EXPECT_NE(static_cast<void*>(NULL), Memory(discardable.get()));
    discardable->Unlock();
  }

  void SignalHelper() {
    thread_sync_.Signal();
  }

  Thread memory_usage_thread_;
  WaitableEvent thread_sync_;
};

TEST_F(ThreadedDiscardableMemoryManagerTest, UseMemoryOnThread) {
  memory_usage_thread_.message_loop()->PostTask(
      FROM_HERE,
      Bind(&ThreadedDiscardableMemoryManagerTest::UseMemoryHelper,
           Unretained(this)));
  memory_usage_thread_.message_loop()->PostTask(
      FROM_HERE,
      Bind(&ThreadedDiscardableMemoryManagerTest::SignalHelper,
           Unretained(this)));
  thread_sync_.Wait();
}

}  // namespace base

/* [<][>][^][v][top][bottom][index][help] */