| /* | 
 |  * Copyright (C) 2019 The Android Open Source Project | 
 |  * | 
 |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
 |  * you may not use this file except in compliance with the License. | 
 |  * You may obtain a copy of the License at | 
 |  * | 
 |  *      http://www.apache.org/licenses/LICENSE-2.0 | 
 |  * | 
 |  * Unless required by applicable law or agreed to in writing, software | 
 |  * distributed under the License is distributed on an "AS IS" BASIS, | 
 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |  * See the License for the specific language governing permissions and | 
 |  * limitations under the License. | 
 |  */ | 
 |  | 
 | #include "src/trace_processor/containers/string_pool.h" | 
 |  | 
 | #include <array> | 
 | #include <random> | 
 |  | 
 | #include "test/gtest_and_gmock.h" | 
 |  | 
 | namespace perfetto { | 
 | namespace trace_processor { | 
 |  | 
 | class StringPoolTest : public testing::Test { | 
 |  protected: | 
 |   static constexpr size_t kNumBlockOffsetBits = StringPool::kNumBlockOffsetBits; | 
 |   static constexpr size_t kBlockIndexBitMask = StringPool::kBlockIndexBitMask; | 
 |   static constexpr size_t kBlockSizeBytes = StringPool::kBlockSizeBytes; | 
 |   static constexpr size_t kMinLargeStringSizeBytes = | 
 |       StringPool::kMinLargeStringSizeBytes; | 
 |  | 
 |   StringPool pool_; | 
 | }; | 
 |  | 
 | namespace { | 
 |  | 
 | TEST_F(StringPoolTest, EmptyPool) { | 
 |   ASSERT_EQ(pool_.Get(StringPool::Id::Null()).c_str(), nullptr); | 
 |  | 
 |   auto it = pool_.CreateIterator(); | 
 |   ASSERT_TRUE(it); | 
 |   ASSERT_EQ(it.StringView().c_str(), nullptr); | 
 |   ASSERT_FALSE(++it); | 
 | } | 
 |  | 
 | TEST_F(StringPoolTest, InternAndRetrieve) { | 
 |   static char kString[] = "Test String"; | 
 |   auto id = pool_.InternString(kString); | 
 |   ASSERT_STREQ(pool_.Get(id).c_str(), kString); | 
 |   ASSERT_EQ(pool_.Get(id), kString); | 
 |   ASSERT_EQ(id, pool_.InternString(kString)); | 
 | } | 
 |  | 
 | TEST_F(StringPoolTest, NullPointerHandling) { | 
 |   auto id = pool_.InternString(NullTermStringView()); | 
 |   ASSERT_TRUE(id.is_null()); | 
 |   ASSERT_EQ(pool_.Get(id).c_str(), nullptr); | 
 | } | 
 |  | 
 | TEST_F(StringPoolTest, Iterator) { | 
 |   auto it = pool_.CreateIterator(); | 
 |   ASSERT_TRUE(it); | 
 |   ASSERT_EQ(it.StringView().c_str(), nullptr); | 
 |   ASSERT_FALSE(++it); | 
 |  | 
 |   static char kString[] = "Test String"; | 
 |   pool_.InternString(kString); | 
 |  | 
 |   it = pool_.CreateIterator(); | 
 |   ASSERT_TRUE(++it); | 
 |   ASSERT_STREQ(it.StringView().c_str(), kString); | 
 |   ASSERT_FALSE(++it); | 
 | } | 
 |  | 
 | TEST_F(StringPoolTest, ConstIterator) { | 
 |   static char kString[] = "Test String"; | 
 |   pool_.InternString(kString); | 
 |  | 
 |   const StringPool& const_pool = pool_; | 
 |  | 
 |   auto it = const_pool.CreateIterator(); | 
 |   ASSERT_TRUE(it); | 
 |   ASSERT_TRUE(++it); | 
 |   ASSERT_STREQ(it.StringView().c_str(), kString); | 
 |   ASSERT_FALSE(++it); | 
 | } | 
 |  | 
 | TEST_F(StringPoolTest, StressTest) { | 
 |   // First create a buffer with 33MB of random characters, so that we insert | 
 |   // into at least two chunks. | 
 |   constexpr size_t kBufferSize = 33 * 1024 * 1024; | 
 |   std::minstd_rand0 rnd_engine(0); | 
 |   std::unique_ptr<char[]> buffer(new char[kBufferSize]); | 
 |   for (size_t i = 0; i < kBufferSize; i++) | 
 |     buffer.get()[i] = 'A' + (rnd_engine() % 26); | 
 |  | 
 |   // Next create strings of length 0 to 16k in length from this buffer and | 
 |   // intern them, storing their ids. | 
 |   std::multimap<StringPool::Id, base::StringView> string_map; | 
 |   constexpr uint16_t kMaxStrSize = 16u * 1024u - 1; | 
 |   for (size_t i = 0;;) { | 
 |     size_t length = static_cast<uint64_t>(rnd_engine()) % (kMaxStrSize + 1); | 
 |     if (i + length > kBufferSize) | 
 |       break; | 
 |  | 
 |     auto str = base::StringView(&buffer.get()[i], length); | 
 |     string_map.emplace(pool_.InternString(str), str); | 
 |     i += length; | 
 |   } | 
 |  | 
 |   // Finally, iterate through each string in the string pool, check that all ids | 
 |   // that match in the multimap are equal, and finish by checking we've removed | 
 |   // every item in the multimap. | 
 |   for (auto it = pool_.CreateIterator(); it; ++it) { | 
 |     ASSERT_EQ(it.StringView(), pool_.Get(it.StringId())); | 
 |  | 
 |     auto it_pair = string_map.equal_range(it.StringId()); | 
 |     for (auto in_it = it_pair.first; in_it != it_pair.second; ++in_it) { | 
 |       ASSERT_EQ(it.StringView(), in_it->second) | 
 |           << it.StringId().raw_id() << ": " << it.StringView().Hash() << " vs " | 
 |           << in_it->second.Hash(); | 
 |     } | 
 |     string_map.erase(it_pair.first, it_pair.second); | 
 |   } | 
 |   ASSERT_EQ(string_map.size(), 0u); | 
 | } | 
 |  | 
 | TEST_F(StringPoolTest, BigString) { | 
 |   // Two of these should fit into one block, but the third one should go into | 
 |   // the |large_strings_| list. | 
 |   constexpr size_t kBigStringSize = 15 * 1024 * 1024; | 
 |   // Will fit into block 1 after two kBigStringSize strings. | 
 |   constexpr size_t kSmallStringSize = 16 * 1024; | 
 |   // Will not fit into block 1 anymore after 2*kBigStringSize and | 
 |   // 2*kSmallStringSize, but is smaller than kMinLargeStringSizeBytes, so will | 
 |   // start a new block. | 
 |   constexpr size_t kMediumStringSize = 2 * 1024 * 1024; | 
 |   // Would not fit into a block at all, so ahs to go into |large_strings_|. | 
 |   constexpr size_t kEnormousStringSize = 33 * 1024 * 1024; | 
 |  | 
 |   constexpr std::array<size_t, 8> kStringSizes = { | 
 |       kBigStringSize,       // block 1 | 
 |       kBigStringSize,       // block 1 | 
 |       kBigStringSize,       // large strings | 
 |       kSmallStringSize,     // block 1 | 
 |       kSmallStringSize,     // block 1 | 
 |       kMediumStringSize,    // block 2 | 
 |       kEnormousStringSize,  // large strings | 
 |       kBigStringSize        // block 2 | 
 |   }; | 
 |  | 
 |   static_assert(kBigStringSize * 2 + kSmallStringSize * 2 + kMediumStringSize > | 
 |                     kBlockSizeBytes, | 
 |                 "medium string shouldn't fit into block 1 for this test"); | 
 |   static_assert(kMediumStringSize < kMinLargeStringSizeBytes, | 
 |                 "medium string should cause a new block for this test"); | 
 |  | 
 |   std::array<std::unique_ptr<char[]>, kStringSizes.size()> big_strings; | 
 |   for (size_t i = 0; i < big_strings.size(); i++) { | 
 |     big_strings[i].reset(new char[kStringSizes[i] + 1]); | 
 |     for (size_t j = 0; j < kStringSizes[i]; j++) { | 
 |       big_strings[i].get()[j] = 'A' + static_cast<char>((j + i) % 26); | 
 |     } | 
 |     big_strings[i].get()[kStringSizes[i]] = '\0'; | 
 |   } | 
 |  | 
 |   std::array<StringPool::Id, kStringSizes.size()> string_ids; | 
 |   for (size_t i = 0; i < big_strings.size(); i++) { | 
 |     string_ids[i] = pool_.InternString( | 
 |         base::StringView(big_strings[i].get(), kStringSizes[i])); | 
 |     // Interning it a second time should return the original id. | 
 |     ASSERT_EQ(string_ids[i], pool_.InternString(base::StringView( | 
 |                                  big_strings[i].get(), kStringSizes[i]))); | 
 |   } | 
 |  | 
 |   ASSERT_FALSE(string_ids[0].is_large_string()); | 
 |   ASSERT_FALSE(string_ids[1].is_large_string()); | 
 |   ASSERT_TRUE(string_ids[2].is_large_string()); | 
 |   ASSERT_FALSE(string_ids[3].is_large_string()); | 
 |   ASSERT_FALSE(string_ids[4].is_large_string()); | 
 |   ASSERT_FALSE(string_ids[5].is_large_string()); | 
 |   ASSERT_TRUE(string_ids[6].is_large_string()); | 
 |   ASSERT_FALSE(string_ids[7].is_large_string()); | 
 |  | 
 |   ASSERT_EQ(string_ids[0].block_index(), 0u); | 
 |   ASSERT_EQ(string_ids[1].block_index(), 0u); | 
 |   ASSERT_EQ(string_ids[3].block_index(), 0u); | 
 |   ASSERT_EQ(string_ids[4].block_index(), 0u); | 
 |   ASSERT_EQ(string_ids[5].block_index(), 1u); | 
 |   ASSERT_EQ(string_ids[7].block_index(), 1u); | 
 |  | 
 |   for (size_t i = 0; i < big_strings.size(); i++) { | 
 |     ASSERT_EQ(big_strings[i].get(), pool_.Get(string_ids[i])); | 
 |   } | 
 | } | 
 |  | 
 | }  // namespace | 
 | }  // namespace trace_processor | 
 | }  // namespace perfetto |