mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
346 lines
13 KiB
C++
346 lines
13 KiB
C++
/*
|
|
* Copyright (C) 2014 Google Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following disclaimer
|
|
* in the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* * Neither the name of Google Inc. nor the names of its
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "flutter/sky/engine/platform/PurgeableVector.h"
|
|
|
|
#include "flutter/sky/engine/platform/TestingPlatformSupport.h"
|
|
#include "flutter/sky/engine/public/platform/WebDiscardableMemory.h"
|
|
#include "flutter/sky/engine/wtf/Vector.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstdlib>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
using namespace blink;
|
|
|
|
namespace {
|
|
|
|
const size_t kTestSize = 32 * 1024;
|
|
|
|
enum DiscardableMemorySupport {
|
|
DontSupportDiscardableMemory,
|
|
SupportDiscardableMemory,
|
|
};
|
|
|
|
class PurgeableVectorTestWithPlatformSupport
|
|
: public testing::TestWithParam<DiscardableMemorySupport> {
|
|
public:
|
|
PurgeableVectorTestWithPlatformSupport()
|
|
: m_testingPlatformSupport(makeTestingPlatformSupportConfig()) {}
|
|
|
|
protected:
|
|
bool isDiscardableMemorySupported() const {
|
|
return GetParam() == SupportDiscardableMemory;
|
|
}
|
|
|
|
TestingPlatformSupport::Config makeTestingPlatformSupportConfig() const {
|
|
TestingPlatformSupport::Config config;
|
|
config.hasDiscardableMemorySupport = isDiscardableMemorySupported();
|
|
return config;
|
|
}
|
|
|
|
PurgeableVector::PurgeableOption makePurgeableOption() const {
|
|
return isDiscardableMemorySupported() ? PurgeableVector::Purgeable
|
|
: PurgeableVector::NotPurgeable;
|
|
}
|
|
|
|
private:
|
|
TestingPlatformSupport m_testingPlatformSupport;
|
|
};
|
|
|
|
TEST_P(PurgeableVectorTestWithPlatformSupport, grow) {
|
|
PurgeableVector purgeableVector(makePurgeableOption());
|
|
purgeableVector.grow(kTestSize);
|
|
ASSERT_EQ(kTestSize, purgeableVector.size());
|
|
// Make sure the underlying buffer was actually (re)allocated.
|
|
memset(purgeableVector.data(), 0, purgeableVector.size());
|
|
}
|
|
|
|
TEST_P(PurgeableVectorTestWithPlatformSupport, clear) {
|
|
Vector<char> testData(kTestSize);
|
|
std::generate(testData.begin(), testData.end(), &std::rand);
|
|
|
|
PurgeableVector purgeableVector(makePurgeableOption());
|
|
purgeableVector.append(testData.data(), testData.size());
|
|
EXPECT_EQ(testData.size(), purgeableVector.size());
|
|
|
|
purgeableVector.clear();
|
|
EXPECT_EQ(0U, purgeableVector.size());
|
|
EXPECT_EQ(0, purgeableVector.data());
|
|
}
|
|
|
|
TEST_P(PurgeableVectorTestWithPlatformSupport, clearDoesNotResetLockCounter) {
|
|
PurgeableVector purgeableVector(makePurgeableOption());
|
|
purgeableVector.clear();
|
|
EXPECT_TRUE(purgeableVector.isLocked());
|
|
purgeableVector.unlock();
|
|
EXPECT_FALSE(purgeableVector.isLocked());
|
|
}
|
|
|
|
TEST_P(PurgeableVectorTestWithPlatformSupport,
|
|
reserveCapacityDoesNotChangeSize) {
|
|
PurgeableVector purgeableVector(makePurgeableOption());
|
|
EXPECT_EQ(0U, purgeableVector.size());
|
|
purgeableVector.reserveCapacity(kTestSize);
|
|
EXPECT_EQ(0U, purgeableVector.size());
|
|
}
|
|
|
|
TEST_P(PurgeableVectorTestWithPlatformSupport, multipleAppends) {
|
|
Vector<char> testData(kTestSize);
|
|
std::generate(testData.begin(), testData.end(), &std::rand);
|
|
|
|
PurgeableVector purgeableVector(makePurgeableOption());
|
|
// Force an allocation.
|
|
const char kSmallString[] = "hello";
|
|
purgeableVector.append(kSmallString, sizeof(kSmallString));
|
|
const char* const data = purgeableVector.data();
|
|
|
|
// Append all the testing data in 4 iterations. The |data| pointer should
|
|
// have been changed at the end of the unit test due to reallocations.
|
|
const size_t kIterationCount = 4;
|
|
ASSERT_EQ(0U, testData.size() % kIterationCount);
|
|
for (size_t i = 0; i < kIterationCount; ++i) {
|
|
const char* const testDataStart =
|
|
testData.data() + i * (testData.size() / kIterationCount);
|
|
purgeableVector.append(testDataStart, testData.size() / kIterationCount);
|
|
ASSERT_EQ((i + 1) * testData.size() / kIterationCount,
|
|
purgeableVector.size() - sizeof(kSmallString));
|
|
}
|
|
|
|
ASSERT_EQ(sizeof(kSmallString) + testData.size(), purgeableVector.size());
|
|
EXPECT_NE(data, purgeableVector.data());
|
|
EXPECT_EQ(0, memcmp(purgeableVector.data() + sizeof(kSmallString),
|
|
testData.data(), testData.size()));
|
|
}
|
|
|
|
TEST_P(PurgeableVectorTestWithPlatformSupport,
|
|
multipleAppendsAfterReserveCapacity) {
|
|
Vector<char> testData(kTestSize);
|
|
std::generate(testData.begin(), testData.end(), &std::rand);
|
|
|
|
PurgeableVector purgeableVector(makePurgeableOption());
|
|
purgeableVector.reserveCapacity(testData.size());
|
|
const char* const data = purgeableVector.data();
|
|
|
|
// The |data| pointer should be unchanged at the end of the unit test
|
|
// meaning that there should not have been any reallocation.
|
|
const size_t kIterationCount = 4;
|
|
ASSERT_EQ(0U, testData.size() % kIterationCount);
|
|
for (size_t i = 0; i < kIterationCount; ++i) {
|
|
const char* const testDataStart =
|
|
testData.data() + i * (testData.size() / kIterationCount);
|
|
purgeableVector.append(testDataStart, testData.size() / kIterationCount);
|
|
ASSERT_EQ((i + 1) * testData.size() / kIterationCount,
|
|
purgeableVector.size());
|
|
}
|
|
|
|
ASSERT_EQ(testData.size(), purgeableVector.size());
|
|
EXPECT_EQ(data, purgeableVector.data());
|
|
EXPECT_EQ(0,
|
|
memcmp(purgeableVector.data(), testData.data(), testData.size()));
|
|
}
|
|
|
|
TEST_P(PurgeableVectorTestWithPlatformSupport,
|
|
reserveCapacityUsesExactCapacityWhenVectorIsEmpty) {
|
|
Vector<char> testData(kTestSize);
|
|
std::generate(testData.begin(), testData.end(), &std::rand);
|
|
|
|
PurgeableVector purgeableVector(makePurgeableOption());
|
|
purgeableVector.reserveCapacity(kTestSize);
|
|
const char* const data = purgeableVector.data();
|
|
|
|
purgeableVector.append(testData.data(), testData.size());
|
|
EXPECT_EQ(data, purgeableVector.data());
|
|
EXPECT_EQ(0,
|
|
memcmp(purgeableVector.data(), testData.data(), testData.size()));
|
|
|
|
// This test is not reliable if the PurgeableVector uses a plain WTF::Vector
|
|
// for storage, as it does if discardable memory is not supported; the vectors
|
|
// capacity will always be expanded to fill the PartitionAlloc bucket.
|
|
if (isDiscardableMemorySupported()) {
|
|
// Appending one extra byte should cause a reallocation since the first
|
|
// allocation happened while the purgeable vector was empty. This behavior
|
|
// helps us guarantee that there is no memory waste on very small vectors
|
|
// (which SharedBuffer requires).
|
|
purgeableVector.append(testData.data(), 1);
|
|
EXPECT_NE(data, purgeableVector.data());
|
|
}
|
|
}
|
|
|
|
TEST_P(PurgeableVectorTestWithPlatformSupport, appendReservesCapacityIfNeeded) {
|
|
Vector<char> testData(kTestSize);
|
|
std::generate(testData.begin(), testData.end(), &std::rand);
|
|
|
|
PurgeableVector purgeableVector(makePurgeableOption());
|
|
// No reserveCapacity().
|
|
ASSERT_FALSE(purgeableVector.data());
|
|
|
|
purgeableVector.append(testData.data(), testData.size());
|
|
ASSERT_EQ(testData.size(), purgeableVector.size());
|
|
ASSERT_EQ(0,
|
|
memcmp(purgeableVector.data(), testData.data(), testData.size()));
|
|
}
|
|
|
|
TEST_P(PurgeableVectorTestWithPlatformSupport, adopt) {
|
|
Vector<char> testData(kTestSize);
|
|
std::generate(testData.begin(), testData.end(), &std::rand);
|
|
const Vector<char> testDataCopy(testData);
|
|
const char* const testDataPtr = testData.data();
|
|
|
|
PurgeableVector purgeableVector(makePurgeableOption());
|
|
purgeableVector.adopt(testData);
|
|
EXPECT_TRUE(testData.isEmpty());
|
|
EXPECT_EQ(kTestSize, purgeableVector.size());
|
|
ASSERT_EQ(0, memcmp(purgeableVector.data(), testDataCopy.data(),
|
|
testDataCopy.size()));
|
|
|
|
if (isDiscardableMemorySupported()) {
|
|
// An extra discardable memory allocation + memcpy() should have happened.
|
|
EXPECT_NE(testDataPtr, purgeableVector.data());
|
|
} else {
|
|
// Vector::swap() should have been used.
|
|
EXPECT_EQ(testDataPtr, purgeableVector.data());
|
|
}
|
|
}
|
|
|
|
TEST_P(PurgeableVectorTestWithPlatformSupport, adoptEmptyVector) {
|
|
Vector<char> testData;
|
|
PurgeableVector purgeableVector(makePurgeableOption());
|
|
purgeableVector.adopt(testData);
|
|
}
|
|
|
|
TEST(PurgeableVectorTestWithPlatformSupport, adoptDiscardsPreviousData) {
|
|
Vector<char> testData;
|
|
std::generate(testData.begin(), testData.end(), &std::rand);
|
|
|
|
PurgeableVector purgeableVector(PurgeableVector::NotPurgeable);
|
|
static const char smallString[] = "hello";
|
|
purgeableVector.append(smallString, sizeof(smallString));
|
|
ASSERT_EQ(0,
|
|
memcmp(purgeableVector.data(), smallString, sizeof(smallString)));
|
|
|
|
purgeableVector.adopt(testData);
|
|
EXPECT_EQ(testData.size(), purgeableVector.size());
|
|
ASSERT_EQ(0,
|
|
memcmp(purgeableVector.data(), testData.data(), testData.size()));
|
|
}
|
|
|
|
TEST_P(PurgeableVectorTestWithPlatformSupport,
|
|
unlockWithoutHintAtConstruction) {
|
|
Vector<char> testData(30000);
|
|
std::generate(testData.begin(), testData.end(), &std::rand);
|
|
|
|
unsigned length = testData.size();
|
|
PurgeableVector purgeableVector(PurgeableVector::NotPurgeable);
|
|
purgeableVector.append(testData.data(), length);
|
|
ASSERT_EQ(length, purgeableVector.size());
|
|
const char* data = purgeableVector.data();
|
|
|
|
purgeableVector.unlock();
|
|
|
|
// Note that the purgeable vector must be locked before calling data().
|
|
const bool wasPurged = !purgeableVector.lock();
|
|
if (isDiscardableMemorySupported()) {
|
|
// The implementation of purgeable memory used for testing always purges
|
|
// data upon unlock().
|
|
EXPECT_TRUE(wasPurged);
|
|
}
|
|
|
|
if (isDiscardableMemorySupported()) {
|
|
// The data should have been moved from the heap-allocated vector to a
|
|
// purgeable buffer.
|
|
ASSERT_NE(data, purgeableVector.data());
|
|
} else {
|
|
ASSERT_EQ(data, purgeableVector.data());
|
|
}
|
|
|
|
if (!wasPurged)
|
|
ASSERT_EQ(0, memcmp(purgeableVector.data(), testData.data(), length));
|
|
}
|
|
|
|
TEST(PurgeableVectorTest, unlockOnEmptyPurgeableVector) {
|
|
PurgeableVector purgeableVector;
|
|
ASSERT_EQ(0U, purgeableVector.size());
|
|
purgeableVector.unlock();
|
|
ASSERT_FALSE(purgeableVector.isLocked());
|
|
}
|
|
|
|
TEST_P(PurgeableVectorTestWithPlatformSupport,
|
|
unlockOnPurgeableVectorWithPurgeableHint) {
|
|
Vector<char> testData(kTestSize);
|
|
std::generate(testData.begin(), testData.end(), &std::rand);
|
|
|
|
PurgeableVector purgeableVector;
|
|
purgeableVector.append(testData.data(), kTestSize);
|
|
const char* const data = purgeableVector.data();
|
|
|
|
// unlock() should happen in place, i.e. without causing any reallocation.
|
|
// Note that the instance must be locked when data() is called.
|
|
purgeableVector.unlock();
|
|
EXPECT_FALSE(purgeableVector.isLocked());
|
|
purgeableVector.lock();
|
|
EXPECT_TRUE(purgeableVector.isLocked());
|
|
EXPECT_EQ(data, purgeableVector.data());
|
|
}
|
|
|
|
TEST_P(PurgeableVectorTestWithPlatformSupport, lockingUsesACounter) {
|
|
Vector<char> testData(kTestSize);
|
|
std::generate(testData.begin(), testData.end(), &std::rand);
|
|
|
|
PurgeableVector purgeableVector(PurgeableVector::NotPurgeable);
|
|
purgeableVector.append(testData.data(), testData.size());
|
|
ASSERT_EQ(testData.size(), purgeableVector.size());
|
|
|
|
ASSERT_TRUE(
|
|
purgeableVector.isLocked()); // SharedBuffer is locked at creation.
|
|
ASSERT_TRUE(purgeableVector.lock()); // Add an extra lock.
|
|
ASSERT_TRUE(purgeableVector.isLocked());
|
|
|
|
purgeableVector.unlock();
|
|
ASSERT_TRUE(purgeableVector.isLocked());
|
|
|
|
purgeableVector.unlock();
|
|
ASSERT_FALSE(purgeableVector.isLocked());
|
|
|
|
if (purgeableVector.lock())
|
|
ASSERT_EQ(0,
|
|
memcmp(purgeableVector.data(), testData.data(), testData.size()));
|
|
}
|
|
|
|
// Instantiates all the unit tests using the SharedBufferTestWithPlatformSupport
|
|
// fixture both with and without discardable memory support.
|
|
INSTANTIATE_TEST_CASE_P(testsWithPlatformSetUp,
|
|
PurgeableVectorTestWithPlatformSupport,
|
|
::testing::Values(DontSupportDiscardableMemory,
|
|
SupportDiscardableMemory));
|
|
|
|
} // namespace
|