/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* 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 <folly/container/tape.h>
#include <forward_list>
#include <iterator>
#include <vector>
#include <folly/portability/GMock.h>
#include <folly/portability/GTest.h>
#include <folly/small_vector.h>
namespace {
template <typename I>
struct InputIter {
I wrapped;
using value_type = typename std::iterator_traits<I>::value_type;
using reference = typename std::iterator_traits<I>::reference;
using difference_type = typename std::iterator_traits<I>::difference_type;
using pointer = typename std::iterator_traits<I>::pointer;
using iterator_category = std::input_iterator_tag;
InputIter& operator++() {
++wrapped;
return *this;
}
reference operator*() const { return *wrapped; }
auto operator++(int) { operator++(); }
friend bool operator==(const InputIter& x, const InputIter& y) {
return x.wrapped == y.wrapped;
}
friend bool operator!=(const InputIter& x, const InputIter& y) {
return !(x == y);
}
};
template <typename T>
struct InputRange {
std::vector<T> c_;
using iterator = InputIter<typename std::vector<T>::const_iterator>;
iterator begin() const { return iterator{c_.begin()}; }
iterator end() const { return iterator{c_.end()}; }
};
#if defined(__cpp_lib_ranges)
static_assert(!std::forward_iterator<InputIter<int*>>);
static_assert(std::input_iterator<InputIter<int*>>);
#endif
template <typename T>
auto asRange(const std::vector<T>& c, std::input_iterator_tag) {
return InputRange<T>{c};
}
template <typename T>
auto asRange(const std::vector<T>& c, std::forward_iterator_tag) {
return std::forward_list<T>{c.begin(), c.end()};
}
template <typename T>
auto asRange(const std::vector<T>& c, std::random_access_iterator_tag) {
return c;
}
} // namespace
// tape types
static_assert(
std::is_same_v<folly::StringPiece, folly::string_tape::reference>, "");
static_assert(
std::is_same_v<folly::StringPiece, folly::string_tape::value_type>, "");
static_assert(
std::is_same_v<
folly::Range<const int*>,
folly::tape<std::vector<int>>::reference>,
"");
static_assert(
std::is_same_v<
folly::Range<const int*>,
folly::tape<std::vector<int>>::value_type>,
"");
static_assert(
std::is_same_v<
folly::Range<const int*>,
folly::tape<folly::small_vector<int, 4>>::reference>,
"");
#if defined(__cpp_lib_ranges)
static_assert(std::ranges::random_access_range<folly::string_tape>);
#endif
TEST(Tape, SmokeTest) {
folly::string_tape st;
st.push_back("abc");
st.push_back("def");
ASSERT_EQ("abc", st[0]);
ASSERT_EQ("def", st[1]);
ASSERT_EQ("abc", st.front());
ASSERT_EQ("def", st.back());
}
TEST(Tape, IntTape) {
folly::tape<std::vector<int>> t;
t.push_back({1, 2});
t.emplace_back(std::vector{3});
ASSERT_EQ(1, t[0][0]);
ASSERT_EQ(2, t[0][1]);
ASSERT_EQ(3, t[1][0]);
}
TEST(Tape, Count) {
folly::string_tape st{"abc", "def", "bla", "abc"};
ASSERT_EQ(2U, (std::count(st.begin(), st.end(), "abc")));
ASSERT_EQ(2U, (std::count(st.cbegin(), st.cend(), "abc")));
ASSERT_EQ(2U, (std::count(st.rbegin(), st.rend(), "abc")));
ASSERT_EQ(2U, (std::count(st.crbegin(), st.crend(), "abc")));
}
TEST(Tape, At) {
folly::string_tape st{"ab", "cde", "f"};
ASSERT_EQ("ab", st.at(0));
ASSERT_EQ("cde", st.at(1));
ASSERT_EQ("f", st.at(2));
ASSERT_THROW((void)st.at(3), std::out_of_range);
}
TEST(Tape, Move) {
folly::string_tape st0{"ab", "cde", "f"};
folly::string_tape st1{st0};
folly::string_tape move_constuctor{std::move(st0)};
folly::string_tape move_assign;
move_assign = std::move(st1);
ASSERT_EQ(move_constuctor, move_assign);
ASSERT_THAT(move_constuctor, testing::ElementsAre("ab", "cde", "f"));
// moved out should be valid but unspecified.
// we had a bug where 0s marker was missing.
// The test is over specified.
ASSERT_EQ(0U, st0.size());
ASSERT_EQ(0U, st1.size());
ASSERT_TRUE(st0.begin() == st0.end());
ASSERT_TRUE(st1.begin() == st1.end());
st0.push_back("ab");
st1.push_back("ab");
ASSERT_THAT(st0, testing::ElementsAre("ab"));
ASSERT_THAT(st1, testing::ElementsAre("ab"));
// self-move should be valid but unspecified.
// not many generic choices, though: either no-op or clear or reset
// std::vector<int> self-move is:
// reset under libstdc++ and libc++
// no-op under microsoft-stl
// the current implementation is reset
move_assign = static_cast<folly::string_tape&&>(move_assign);
ASSERT_EQ(0U, move_assign.size());
}
TEST(Tape, TwoItersConstructor) {
auto tst = [&](auto tag) {
{
const std::vector<std::vector<int>> a{{1, 2}, {3, 4, 5}};
auto input = asRange(a, tag);
folly::tape<std::vector<int>> t{input.begin(), input.end()};
ASSERT_EQ(2, t.size());
ASSERT_EQ(folly::crange(a[0]), t[0]);
ASSERT_EQ(folly::crange(a[1]), t[1]);
}
{
const std::vector<std::vector<int>> empty;
auto input = asRange(empty, tag);
folly::tape<std::vector<int>> t{input.begin(), input.end()};
ASSERT_EQ(0, t.size());
ASSERT_TRUE(t.empty());
}
{
const std::vector<std::vector<int>> emptyRow{{1, 2}, {}, {3, 4, 5}};
auto input = asRange(emptyRow, tag);
folly::tape<std::vector<int>> t{input.begin(), input.end()};
ASSERT_EQ(3, t.size());
ASSERT_EQ(folly::crange(emptyRow[0]), t[0]);
ASSERT_EQ(folly::crange(emptyRow[1]), t[1]);
ASSERT_EQ(folly::crange(emptyRow[2]), t[2]);
}
};
tst(std::input_iterator_tag{});
tst(std::forward_iterator_tag{});
tst(std::random_access_iterator_tag{});
}
TEST(Tape, InitListNotStrings) {
std::vector<int> a[] = {{1, 2}, {3, 4, 5}, {6, 7}};
folly::tape<std::vector<int>> t{a[0], a[1], a[2]};
ASSERT_EQ(3, t.size());
ASSERT_EQ(folly::crange(a[0]), t[0]);
ASSERT_EQ(folly::crange(a[1]), t[1]);
ASSERT_EQ(folly::crange(a[2]), t[2]);
}
TEST(Tape, Ordering) {
const folly::string_tape st1{"abc", "def"};
const folly::string_tape st2{"abcd", "ef"};
ASSERT_EQ(st1, st1);
ASSERT_NE(st1, st2);
ASSERT_LT(st1, st2);
ASSERT_LE(st1, st2);
ASSERT_LE(st1, st1);
ASSERT_GT(st2, st1);
ASSERT_GE(st2, st1);
ASSERT_GE(st1, st1);
}
TEST(Tape, RecordBuilder) {
folly::string_tape st;
{
auto builder = st.new_record_builder();
ASSERT_TRUE(builder.empty());
ASSERT_EQ(0U, builder.size());
builder.push_back('a');
builder.push_back('b');
ASSERT_FALSE(builder.empty());
ASSERT_EQ(2U, builder.size());
ASSERT_EQ('b', builder.back());
ASSERT_EQ('b', std::as_const(builder).back());
ASSERT_EQ(std::string_view(builder.begin(), builder.end()), "ab");
ASSERT_EQ(std::string_view(builder.cbegin(), builder.cend()), "ab");
*builder.begin() = 'c';
builder.commit();
}
{
auto builder = st.new_record_builder();
ASSERT_EQ(builder.emplace_back('d'), 'd');
builder[0] = 'e';
ASSERT_EQ(std::as_const(builder)[0], 'e');
builder.commit();
}
{
auto builder = st.new_record_builder();
builder.emplace_back(); // test emplace with default constructor
builder.at(0) = 'b';
ASSERT_EQ(std::as_const(builder).at(0), 'b');
builder[0] = 'a';
ASSERT_EQ(std::as_const(builder)[0], 'a');
builder.commit();
}
{
auto builder = st.new_record_builder();
constexpr std::string_view s = "abc";
std::copy(s.begin(), s.end(), builder.back_inserter());
builder.commit();
}
{
auto builder = st.new_record_builder();
builder.push_back('0');
builder.push_back('1');
// Default abort - not committed
}
{
// calling abort
auto builder = st.new_record_builder();
ASSERT_EQ(0U, builder.size());
builder.push_back('a');
builder.push_back('b');
ASSERT_EQ(2U, builder.size());
builder.abort();
ASSERT_EQ(0U, builder.size());
builder.push_back('a');
builder.push_back('b');
builder.commit();
}
{
// amending record
ASSERT_EQ(st.back(), "ab");
auto builder = st.last_record_builder();
builder.push_back('c');
builder[0] = '0';
builder.commit();
ASSERT_EQ(st.back(), "0bc");
}
{
// not committing last record
ASSERT_EQ(st.size(), 5);
{
auto builder = st.new_record_builder();
builder.push_back('a');
builder.push_back('b');
builder.commit();
}
ASSERT_EQ(st.size(), 6);
(void)st.last_record_builder(); // destructor will auto abort.
ASSERT_EQ(st.size(), 5);
}
ASSERT_EQ("cb", st[0]);
ASSERT_EQ("e", st[1]);
ASSERT_EQ("a", st[2]);
ASSERT_EQ("abc", st[3]);
ASSERT_EQ("0bc", st[4]);
ASSERT_EQ(10U, st.size_flat());
ASSERT_EQ(5U, st.size());
// checking that pop back is still ok.
st.pop_back();
ASSERT_EQ("cb", st[0]);
ASSERT_EQ("e", st[1]);
ASSERT_EQ("a", st[2]);
ASSERT_EQ("abc", st[3]);
ASSERT_EQ(7U, st.size_flat());
ASSERT_EQ(4U, st.size());
st.pop_back();
ASSERT_EQ("cb", st[0]);
ASSERT_EQ("e", st[1]);
ASSERT_EQ("a", st[2]);
ASSERT_EQ(4U, st.size_flat());
ASSERT_EQ(3U, st.size());
st.pop_back();
ASSERT_EQ("cb", st[0]);
ASSERT_EQ("e", st[1]);
ASSERT_EQ(2U, st.size());
ASSERT_EQ(3U, st.size_flat());
st.pop_back();
ASSERT_EQ("cb", st[0]);
ASSERT_EQ(1U, st.size());
ASSERT_EQ(2U, st.size_flat());
st.pop_back();
ASSERT_EQ(0U, st.size());
ASSERT_EQ(0U, st.size_flat());
}
TEST(Tape, PushBack) {
folly::tape<std::vector<int>> t;
auto tst = [&](auto tag) mutable {
t.clear();
ASSERT_EQ(0, t.size());
std::vector<int> a{1, 2, 3}, b{4, 5}, c;
t.push_back(asRange(a, tag));
ASSERT_EQ(1, t.size());
ASSERT_EQ(folly::crange(a), t[0]);
t.push_back(asRange(b, tag));
ASSERT_EQ(2, t.size());
ASSERT_EQ(folly::crange(a), t[0]);
ASSERT_EQ(folly::crange(b), t[1]);
t.push_back(c);
ASSERT_EQ(3, t.size());
ASSERT_EQ(folly::crange(a), t[0]);
ASSERT_EQ(folly::crange(b), t[1]);
ASSERT_EQ(folly::crange(c), t[2]);
};
tst(std::input_iterator_tag{});
tst(std::forward_iterator_tag{});
tst(std::random_access_iterator_tag{});
}
TEST(Tape, PushBackStr) {
folly::string_tape st;
st.push_back("abc");
st.push_back({'d', 'e'});
constexpr std::string_view c = "fgh";
st.push_back(c);
constexpr std::string_view d = "ijklm";
st.push_back(d.begin(), d.end());
ASSERT_THAT(st, testing::ElementsAre("abc", "de", "fgh", "ijklm"));
}
TEST(Tape, EmptyRecord) {
folly::tape<std::vector<int>> t;
t.push_back({});
t.push_back({});
ASSERT_EQ(2U, t.size());
ASSERT_TRUE(t[0].empty());
ASSERT_TRUE(t[1].empty());
}
TEST(Tape, Resize) {
folly::string_tape st{"ab", "abc", "abcd", "de"};
st.resize(6U);
ASSERT_THAT(st, testing::ElementsAre("ab", "abc", "abcd", "de", "", ""));
st.resize(2U);
ASSERT_THAT(st, testing::ElementsAre("ab", "abc"));
st.resize(2U);
ASSERT_THAT(st, testing::ElementsAre("ab", "abc"));
st.push_back("abcd");
ASSERT_THAT(st, testing::ElementsAre("ab", "abc", "abcd"));
st.resize(0U);
ASSERT_TRUE(st.empty());
st.push_back("ab");
ASSERT_THAT(st, testing::ElementsAre("ab"));
}
TEST(Tape, EmptyStrings) {
folly::string_tape st{"a"};
st.push_back("");
ASSERT_THAT(st, testing::ElementsAre("a", ""));
st.emplace_back();
ASSERT_THAT(st, testing::ElementsAre("a", "", ""));
st.erase(st.begin() + 1, st.end());
ASSERT_THAT(st, testing::ElementsAre("a"));
st.emplace_back();
st.emplace_back("bd");
ASSERT_THAT(st, testing::ElementsAre("a", "", "bd"));
}
TEST(Tape, InsertOne) {
auto tst = [](auto tag) {
folly::tape<std::vector<int>> t;
std::vector<int> a{1, 2, 3}, b{4, 5}, c{6, 7, 8}, d{};
{
auto pos = t.insert(t.end(), asRange(a, tag));
ASSERT_EQ(pos - t.begin(), 0);
ASSERT_THAT(t, testing::ElementsAre(a));
}
{
auto pos = t.insert(t.end(), asRange(c, tag));
ASSERT_EQ(pos - t.begin(), 1);
ASSERT_THAT(t, testing::ElementsAre(a, c));
}
{
auto pos = t.insert(t.begin() + 1, asRange(b, tag));
ASSERT_EQ(pos - t.begin(), 1);
ASSERT_THAT(t, testing::ElementsAre(a, b, c));
}
{
auto pos = t.insert(t.begin() + 2, asRange(d, tag));
ASSERT_EQ(pos - t.begin(), 2);
ASSERT_THAT(t, testing::ElementsAre(a, b, d, c));
}
};
tst(std::input_iterator_tag{});
tst(std::forward_iterator_tag{});
tst(std::random_access_iterator_tag{});
}
TEST(Tape, InsertOneStr) {
folly::string_tape st;
auto pos = st.insert(st.end(), "ab");
ASSERT_EQ(pos - st.begin(), 0);
ASSERT_THAT(st, testing::ElementsAre("ab"));
{
std::string s("cde");
pos = st.insert(st.end(), s.begin(), s.end());
ASSERT_EQ(pos - st.begin(), 1);
ASSERT_THAT(st, testing::ElementsAre("ab", "cde"));
}
{
pos = st.insert(st.begin() + 1, {'0', '1'});
ASSERT_EQ(pos - st.begin(), 1);
ASSERT_THAT(st, testing::ElementsAre("ab", "01", "cde"));
}
{
std::forward_list<char> in{'1', '2', '3'};
pos = st.insert(st.begin(), in);
ASSERT_EQ(pos - st.begin(), 0);
ASSERT_THAT(st, testing::ElementsAre("123", "ab", "01", "cde"));
}
}
TEST(Tape, EraseOne) {
folly::string_tape st{"ab", "abc", "abcd", "de"};
folly::string_tape::iterator pos = st.erase(st.cbegin() + 1);
ASSERT_EQ(1, pos - st.begin());
ASSERT_THAT(st, testing::ElementsAre("ab", "abcd", "de"));
// tape is still functional
st.push_back("ef");
ASSERT_THAT(st, testing::ElementsAre("ab", "abcd", "de", "ef"));
ASSERT_EQ(st.erase(st.cbegin()) - st.cbegin(), 0);
ASSERT_THAT(st, testing::ElementsAre("abcd", "de", "ef"));
ASSERT_EQ(st.erase(st.begin() + 1) - st.cbegin(), 1);
ASSERT_THAT(st, testing::ElementsAre("abcd", "ef"));
ASSERT_EQ(st.erase(st.cend() - 1) - st.cbegin(), 1);
ASSERT_THAT(st, testing::ElementsAre("abcd"));
ASSERT_EQ(st.erase(st.cbegin()) - st.cbegin(), 0);
ASSERT_TRUE(st.empty());
}
TEST(Tape, EraseRange) {
folly::string_tape st{"ab", "abc", "abcd", "de"};
folly::string_tape::iterator pos = st.erase(st.cbegin() + 1, st.cend() - 1);
ASSERT_THAT(st, testing::ElementsAre("ab", "de"));
ASSERT_EQ(pos - st.begin(), 1);
pos = st.erase(st.end(), st.end());
ASSERT_THAT(st, testing::ElementsAre("ab", "de"));
ASSERT_EQ(pos - st.begin(), 2);
}
TEST(Tape, Iteration) {
folly::string_tape st{"0", "10", "100", "1000"};
ASSERT_THAT(st, testing::ElementsAre("0", "10", "100", "1000"));
folly::string_tape st2(st.rbegin(), st.rend());
ASSERT_THAT(st2, testing::ElementsAre("1000", "100", "10", "0"));
}
TEST(Tape, TapeOfTapes) {
folly::tape<folly::string_tape> strings;
auto builder = strings.new_record_builder();
builder.push_back("ab");
builder.push_back("abc");
builder.commit();
builder.push_back("bc");
builder.push_back("bcd");
builder.push_back("bcde");
builder.commit();
ASSERT_EQ(strings.size(), 2);
ASSERT_THAT(strings[0], testing::ElementsAre("ab", "abc"));
ASSERT_THAT(strings[1], testing::ElementsAre("bc", "bcd", "bcde"));
}