1/*
2 * Copyright (C) 2014 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32#include "platform/PurgeableVector.h"
33
34#include "platform/TestingPlatformSupport.h"
35#include "public/platform/WebDiscardableMemory.h"
36#include "wtf/Vector.h"
37
38#include <algorithm>
39#include <cstdlib>
40
41#include <gtest/gtest.h>
42
43using namespace blink;
44
45namespace {
46
47const size_t kTestSize = 32 * 1024;
48
49enum DiscardableMemorySupport {
50    DontSupportDiscardableMemory,
51    SupportDiscardableMemory,
52};
53
54class PurgeableVectorTestWithPlatformSupport : public testing::TestWithParam<DiscardableMemorySupport> {
55public:
56    PurgeableVectorTestWithPlatformSupport() : m_testingPlatformSupport(makeTestingPlatformSupportConfig()) { }
57
58protected:
59    bool isDiscardableMemorySupported() const { return GetParam() == SupportDiscardableMemory; }
60
61    TestingPlatformSupport::Config makeTestingPlatformSupportConfig() const
62    {
63        TestingPlatformSupport::Config config;
64        config.hasDiscardableMemorySupport = isDiscardableMemorySupported();
65        return config;
66    }
67
68    PurgeableVector::PurgeableOption makePurgeableOption() const
69    {
70        return isDiscardableMemorySupported() ? PurgeableVector::Purgeable : PurgeableVector::NotPurgeable;
71    }
72
73private:
74    TestingPlatformSupport m_testingPlatformSupport;
75};
76
77TEST_P(PurgeableVectorTestWithPlatformSupport, grow)
78{
79    PurgeableVector purgeableVector(makePurgeableOption());
80    purgeableVector.grow(kTestSize);
81    ASSERT_EQ(kTestSize, purgeableVector.size());
82    // Make sure the underlying buffer was actually (re)allocated.
83    memset(purgeableVector.data(), 0, purgeableVector.size());
84}
85
86TEST_P(PurgeableVectorTestWithPlatformSupport, clear)
87{
88    Vector<char> testData(kTestSize);
89    std::generate(testData.begin(), testData.end(), &std::rand);
90
91    PurgeableVector purgeableVector(makePurgeableOption());
92    purgeableVector.append(testData.data(), testData.size());
93    EXPECT_EQ(testData.size(), purgeableVector.size());
94
95    purgeableVector.clear();
96    EXPECT_EQ(0U, purgeableVector.size());
97    EXPECT_EQ(0, purgeableVector.data());
98}
99
100TEST_P(PurgeableVectorTestWithPlatformSupport, clearDoesNotResetLockCounter)
101{
102    PurgeableVector purgeableVector(makePurgeableOption());
103    purgeableVector.clear();
104    EXPECT_TRUE(purgeableVector.isLocked());
105    purgeableVector.unlock();
106    EXPECT_FALSE(purgeableVector.isLocked());
107}
108
109TEST_P(PurgeableVectorTestWithPlatformSupport, reserveCapacityDoesNotChangeSize)
110{
111    PurgeableVector purgeableVector(makePurgeableOption());
112    EXPECT_EQ(0U, purgeableVector.size());
113    purgeableVector.reserveCapacity(kTestSize);
114    EXPECT_EQ(0U, purgeableVector.size());
115}
116
117TEST_P(PurgeableVectorTestWithPlatformSupport, multipleAppends)
118{
119    Vector<char> testData(kTestSize);
120    std::generate(testData.begin(), testData.end(), &std::rand);
121
122    PurgeableVector purgeableVector(makePurgeableOption());
123    // Force an allocation.
124    const char kSmallString[] = "hello";
125    purgeableVector.append(kSmallString, sizeof(kSmallString));
126    const char* const data = purgeableVector.data();
127
128    // Append all the testing data in 4 iterations. The |data| pointer should
129    // have been changed at the end of the unit test due to reallocations.
130    const size_t kIterationCount = 4;
131    ASSERT_EQ(0U, testData.size() % kIterationCount);
132    for (size_t i = 0; i < kIterationCount; ++i) {
133        const char* const testDataStart = testData.data() + i * (testData.size() / kIterationCount);
134        purgeableVector.append(testDataStart, testData.size() / kIterationCount);
135        ASSERT_EQ((i + 1) * testData.size() / kIterationCount, purgeableVector.size() - sizeof(kSmallString));
136    }
137
138    ASSERT_EQ(sizeof(kSmallString) + testData.size(), purgeableVector.size());
139    EXPECT_NE(data, purgeableVector.data());
140    EXPECT_EQ(0, memcmp(purgeableVector.data() + sizeof(kSmallString), testData.data(), testData.size()));
141}
142
143TEST_P(PurgeableVectorTestWithPlatformSupport, multipleAppendsAfterReserveCapacity)
144{
145    Vector<char> testData(kTestSize);
146    std::generate(testData.begin(), testData.end(), &std::rand);
147
148    PurgeableVector purgeableVector(makePurgeableOption());
149    purgeableVector.reserveCapacity(testData.size());
150    const char* const data = purgeableVector.data();
151
152    // The |data| pointer should be unchanged at the end of the unit test
153    // meaning that there should not have been any reallocation.
154    const size_t kIterationCount = 4;
155    ASSERT_EQ(0U, testData.size() % kIterationCount);
156    for (size_t i = 0; i < kIterationCount; ++i) {
157        const char* const testDataStart = testData.data() + i * (testData.size() / kIterationCount);
158        purgeableVector.append(testDataStart, testData.size() / kIterationCount);
159        ASSERT_EQ((i + 1) * testData.size() / kIterationCount, purgeableVector.size());
160    }
161
162    ASSERT_EQ(testData.size(), purgeableVector.size());
163    EXPECT_EQ(data, purgeableVector.data());
164    EXPECT_EQ(0, memcmp(purgeableVector.data(), testData.data(), testData.size()));
165}
166
167TEST_P(PurgeableVectorTestWithPlatformSupport, reserveCapacityUsesExactCapacityWhenVectorIsEmpty)
168{
169    Vector<char> testData(kTestSize);
170    std::generate(testData.begin(), testData.end(), &std::rand);
171
172    PurgeableVector purgeableVector(makePurgeableOption());
173    purgeableVector.reserveCapacity(kTestSize);
174    const char* const data = purgeableVector.data();
175
176    purgeableVector.append(testData.data(), testData.size());
177    EXPECT_EQ(data, purgeableVector.data());
178    EXPECT_EQ(0, memcmp(purgeableVector.data(), testData.data(), testData.size()));
179
180    // This test is not reliable if the PurgeableVector uses a plain WTF::Vector
181    // for storage, as it does if discardable memory is not supported; the vectors
182    // capacity will always be expanded to fill the PartitionAlloc bucket.
183    if (isDiscardableMemorySupported()) {
184        // Appending one extra byte should cause a reallocation since the first
185        // allocation happened while the purgeable vector was empty. This behavior
186        // helps us guarantee that there is no memory waste on very small vectors
187        // (which SharedBuffer requires).
188        purgeableVector.append(testData.data(), 1);
189        EXPECT_NE(data, purgeableVector.data());
190    }
191}
192
193TEST_P(PurgeableVectorTestWithPlatformSupport, appendReservesCapacityIfNeeded)
194{
195    Vector<char> testData(kTestSize);
196    std::generate(testData.begin(), testData.end(), &std::rand);
197
198    PurgeableVector purgeableVector(makePurgeableOption());
199    // No reserveCapacity().
200    ASSERT_FALSE(purgeableVector.data());
201
202    purgeableVector.append(testData.data(), testData.size());
203    ASSERT_EQ(testData.size(), purgeableVector.size());
204    ASSERT_EQ(0, memcmp(purgeableVector.data(), testData.data(), testData.size()));
205}
206
207TEST_P(PurgeableVectorTestWithPlatformSupport, adopt)
208{
209    Vector<char> testData(kTestSize);
210    std::generate(testData.begin(), testData.end(), &std::rand);
211    const Vector<char> testDataCopy(testData);
212    const char* const testDataPtr = testData.data();
213
214    PurgeableVector purgeableVector(makePurgeableOption());
215    purgeableVector.adopt(testData);
216    EXPECT_TRUE(testData.isEmpty());
217    EXPECT_EQ(kTestSize, purgeableVector.size());
218    ASSERT_EQ(0, memcmp(purgeableVector.data(), testDataCopy.data(), testDataCopy.size()));
219
220    if (isDiscardableMemorySupported()) {
221        // An extra discardable memory allocation + memcpy() should have happened.
222        EXPECT_NE(testDataPtr, purgeableVector.data());
223    } else {
224        // Vector::swap() should have been used.
225        EXPECT_EQ(testDataPtr, purgeableVector.data());
226    }
227}
228
229TEST_P(PurgeableVectorTestWithPlatformSupport, adoptEmptyVector)
230{
231    Vector<char> testData;
232    PurgeableVector purgeableVector(makePurgeableOption());
233    purgeableVector.adopt(testData);
234}
235
236TEST(PurgeableVectorTestWithPlatformSupport, adoptDiscardsPreviousData)
237{
238    Vector<char> testData;
239    std::generate(testData.begin(), testData.end(), &std::rand);
240
241    PurgeableVector purgeableVector(PurgeableVector::NotPurgeable);
242    static const char smallString[] = "hello";
243    purgeableVector.append(smallString, sizeof(smallString));
244    ASSERT_EQ(0, memcmp(purgeableVector.data(), smallString, sizeof(smallString)));
245
246    purgeableVector.adopt(testData);
247    EXPECT_EQ(testData.size(), purgeableVector.size());
248    ASSERT_EQ(0, memcmp(purgeableVector.data(), testData.data(), testData.size()));
249}
250
251TEST_P(PurgeableVectorTestWithPlatformSupport, unlockWithoutHintAtConstruction)
252{
253    Vector<char> testData(30000);
254    std::generate(testData.begin(), testData.end(), &std::rand);
255
256    unsigned length = testData.size();
257    PurgeableVector purgeableVector(PurgeableVector::NotPurgeable);
258    purgeableVector.append(testData.data(), length);
259    ASSERT_EQ(length, purgeableVector.size());
260    const char* data = purgeableVector.data();
261
262    purgeableVector.unlock();
263
264    // Note that the purgeable vector must be locked before calling data().
265    const bool wasPurged = !purgeableVector.lock();
266    if (isDiscardableMemorySupported()) {
267        // The implementation of purgeable memory used for testing always purges data upon unlock().
268        EXPECT_TRUE(wasPurged);
269    }
270
271    if (isDiscardableMemorySupported()) {
272        // The data should have been moved from the heap-allocated vector to a purgeable buffer.
273        ASSERT_NE(data, purgeableVector.data());
274    } else {
275        ASSERT_EQ(data, purgeableVector.data());
276    }
277
278    if (!wasPurged)
279        ASSERT_EQ(0, memcmp(purgeableVector.data(), testData.data(), length));
280}
281
282TEST(PurgeableVectorTest, unlockOnEmptyPurgeableVector)
283{
284    PurgeableVector purgeableVector;
285    ASSERT_EQ(0U, purgeableVector.size());
286    purgeableVector.unlock();
287    ASSERT_FALSE(purgeableVector.isLocked());
288}
289
290TEST_P(PurgeableVectorTestWithPlatformSupport, unlockOnPurgeableVectorWithPurgeableHint)
291{
292    Vector<char> testData(kTestSize);
293    std::generate(testData.begin(), testData.end(), &std::rand);
294
295    PurgeableVector purgeableVector;
296    purgeableVector.append(testData.data(), kTestSize);
297    const char* const data = purgeableVector.data();
298
299    // unlock() should happen in place, i.e. without causing any reallocation.
300    // Note that the instance must be locked when data() is called.
301    purgeableVector.unlock();
302    EXPECT_FALSE(purgeableVector.isLocked());
303    purgeableVector.lock();
304    EXPECT_TRUE(purgeableVector.isLocked());
305    EXPECT_EQ(data, purgeableVector.data());
306}
307
308TEST_P(PurgeableVectorTestWithPlatformSupport, lockingUsesACounter)
309{
310    Vector<char> testData(kTestSize);
311    std::generate(testData.begin(), testData.end(), &std::rand);
312
313    PurgeableVector purgeableVector(PurgeableVector::NotPurgeable);
314    purgeableVector.append(testData.data(), testData.size());
315    ASSERT_EQ(testData.size(), purgeableVector.size());
316
317    ASSERT_TRUE(purgeableVector.isLocked()); // SharedBuffer is locked at creation.
318    ASSERT_TRUE(purgeableVector.lock()); // Add an extra lock.
319    ASSERT_TRUE(purgeableVector.isLocked());
320
321    purgeableVector.unlock();
322    ASSERT_TRUE(purgeableVector.isLocked());
323
324    purgeableVector.unlock();
325    ASSERT_FALSE(purgeableVector.isLocked());
326
327    if (purgeableVector.lock())
328        ASSERT_EQ(0, memcmp(purgeableVector.data(), testData.data(), testData.size()));
329}
330
331// Instantiates all the unit tests using the SharedBufferTestWithPlatformSupport fixture both with
332// and without discardable memory support.
333INSTANTIATE_TEST_CASE_P(testsWithPlatformSetUp, PurgeableVectorTestWithPlatformSupport,
334    ::testing::Values(DontSupportDiscardableMemory, SupportDiscardableMemory));
335
336} // namespace
337
338