blob: 8f215afcda842e8d4013bec014a62607dde252e1 [file] [log] [blame]
/*
* 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 <cstddef>
#include <cstdint>
#include <map>
#include <memory>
#include <random>
#include "perfetto/ext/base/string_view.h"
#include "src/trace_processor/containers/null_term_string_view.h"
#include "test/gtest_and_gmock.h"
namespace perfetto::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_.CreateSmallStringIterator();
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_.CreateSmallStringIterator();
ASSERT_TRUE(it);
ASSERT_EQ(it.StringView().c_str(), nullptr);
ASSERT_FALSE(++it);
static char kString[] = "Test String";
pool_.InternString(kString);
it = pool_.CreateSmallStringIterator();
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.CreateSmallStringIterator();
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_.CreateSmallStringIterator(); 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, LargeString) {
// Would not fit into a block at all, so has to go into |large_strings_|.
constexpr size_t kEnormousStringSize = 33 * 1024 * 1024;
constexpr std::array<size_t, 1> kStringSizes = {
kEnormousStringSize, // large strings
};
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_TRUE(string_ids[0].is_large_string());
for (size_t i = 0; i < big_strings.size(); i++) {
ASSERT_EQ(big_strings[i].get(), pool_.Get(string_ids[i]));
}
}
} // namespace
} // namespace perfetto::trace_processor