folly/folly/test/FixedStringTest.cpp

/*
 * 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/FixedString.h>

#include <string_view>

#include <folly/Range.h>
#include <folly/portability/GTest.h>

#define FS(x) ::folly::makeFixedString(x)
using namespace folly::string_literals;

TEST(FixedStringExamples, Examples) {
  // Example from the docs:
  using namespace folly;
  constexpr auto hello = makeFixedString("hello"); // a FixedString<5>
  constexpr auto world = makeFixedString("world"); // another FixedString<5>
  constexpr auto hello_world = hello + ' ' + world + '!';
  static_assert(hello_world == "hello world!", "w00t");
  EXPECT_STREQ("hello world!", hello_world.c_str());

  FixedString<10> test{"****"};
  test.replace(1, 2, "!!!!");
  EXPECT_STREQ("*!!!!*", test.c_str());
  static_assert(makeFixedString("****").creplace(1, 2, "!!!!") == "*!!!!*", "");
}

TEST(FixedStringCtorTest, Default) {
  constexpr folly::FixedString<42> s{};
  static_assert(s[0] == '\0', "");
  static_assert(s.size() == 0u, "");

  constexpr auto s2 = s;
  static_assert(s2[0] == '\0', "");
  static_assert(s2.size() == 0u, "");
}

TEST(FixedStringCtorTest, FromLiterals) {
  constexpr folly::FixedString<42> s{"hello world"};
  static_assert(s[0] == 'h', "");
  constexpr folly::FixedString<11> s2{"hello world"};
  static_assert(s2[0] == 'h', "");
  static_assert(s2[10] == 'd', "");
  static_assert(s2[11] == '\0', "");

  // Does not compile, hurray! :-)
  // constexpr char a[1] = {'a'};
  // constexpr folly::FixedString<10> s3(a);
}

TEST(FixedStringCtorTest, FromPtrAndLength) {
  constexpr folly::FixedString<11> s{"hello world", 11};
  static_assert(s[0] == 'h', "");
  static_assert(s[10] == 'd', "");
  static_assert(s[11] == '\0', "");
  static_assert(s.size() == 11u, "");

  constexpr folly::FixedString<5> s2{"hello world", 5};
  static_assert(s2[0] == 'h', "");
  static_assert(s2[4] == 'o', "");
  static_assert(s2[5] == '\0', "");
  static_assert(s2.size() == 5u, "");

  constexpr folly::FixedString<20> s3{"hello world", 5};
  static_assert(s2[0] == 'h', "");
  static_assert(s2[4] == 'o', "");
  static_assert(s2[5] == '\0', "");
  static_assert(s2.size() == 5u, "");

  static_assert("hello" == s3, "");
  static_assert(s3 == "hello", "");
  static_assert(s3 == s2, "");
  static_assert("hell" != s3, "");
  static_assert(s3 != "helloooo", "");
  static_assert(!(s3 != s2), "");
}

TEST(FixedStringCtorTest, FromStringAndOffset) {
  constexpr folly::FixedString<11> s{"hello world"};
  constexpr folly::FixedString<5> s2{s, 6u, npos};
  static_assert(s2 == "world", "");
  constexpr folly::FixedString<0> s3{s, 11u, npos};
  static_assert(s3 == "", "");
  // Out of bounds offset, does not compile
  // constexpr folly::FixedString<0> s4{s, 12};
}

TEST(FixedStringCtorTest, FromStringOffsetAndCount) {
  constexpr folly::FixedString<11> s{"hello world"};
  constexpr folly::FixedString<4> s2{s, 6u, 4u};
  static_assert(s2 == "worl", "");
  constexpr folly::FixedString<5> s3{s, 6u, 5u};
  static_assert(s3 == "world", "");
  // Out of bounds count, does not compile:
  // constexpr folly::FixedString<5> s4{s, 6, 6};
}

TEST(FixedStringCtorTest, FromStringView) {
  constexpr folly::FixedString<11> s{
      std::string_view{"hello world"},
  };
  static_assert(s == "hello world", "");
  static_assert(s.size() == 11u, "");
}

TEST(FixedStringCtorTest, FromInitializerList) {
  constexpr folly::FixedString<11> s{
      'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
  static_assert(s == "hello world", "");
  // Out of bounds count, does not compile:
  // constexpr folly::FixedString<10> s{
  //     {'h','e','l','l','o',' ','w','o','r','l','d'}};
}

TEST(FixedStringCtorTest, FromUDL) {
  using namespace folly::literals;
#if defined(__GNUC__)
  constexpr auto x = "hello"_fs;
  static_assert(
      std::is_same<decltype(x), const folly::FixedString<5>>::value, "");
  static_assert(x[0] == 'h', "");
  static_assert(x[1] == 'e', "");
  static_assert(x[2] == 'l', "");
  static_assert(x[3] == 'l', "");
  static_assert(x[4] == 'o', "");
  static_assert(x[5] == '\0', "");
  static_assert(x.size() == 5u, "");
#endif

  constexpr auto y = "goodbye"_fs8;
  static_assert(
      std::is_same<decltype(y), const folly::FixedString<8>>::value, "");
  static_assert(y.size() == 7u, "");
  static_assert(y == "goodbye", "");

  constexpr auto z = "now is the time for all good llamas"_fs64;
  static_assert(
      std::is_same<decltype(z), const folly::FixedString<64>>::value, "");
  static_assert(z.size() == 35u, "");
  static_assert(z == "now is the time for all good llamas", "");
}

TEST(FixedStringConcatTest, FromStringAndLiteral) {
  constexpr folly::FixedString<42> s{"hello world"};
  constexpr auto res = s + "!!!";
  static_assert(res.size() == 14u, "");
  static_assert(res == "hello world!!!", "");
}

TEST(FixedStringConcatTest, FromTwoStrings) {
  constexpr folly::FixedString<42> s{"hello world"};
  constexpr auto res = s + "!!!";
  static_assert(res.size() == 14u, "");
  static_assert(res == "hello world!!!", "");
}

constexpr folly::FixedString<20> constexpr_swap_test() {
  folly::FixedString<10> tmp1{"hello"}, tmp2{"world!"};
  tmp2.swap(tmp1);
  return tmp1 + tmp2;
}

TEST(FixedStringSwapTest, ConstexprSwap) {
  static_assert(constexpr_swap_test() == "world!hello", "");
}

TEST(FixedStringSwapTest, RuntimeSwap) {
  folly::FixedString<10> tmp1{"hello"}, tmp2{"world!"};
  tmp2.swap(tmp1);
  EXPECT_STREQ((tmp1 + tmp2).c_str(), "world!hello");
}

constexpr folly::FixedString<10> constexpr_assign_string_test_1() {
  folly::FixedString<10> tmp1, tmp2{"world!"};
  tmp1 = tmp2;
  return tmp1;
}
constexpr folly::FixedString<10> constexpr_assign_string_test_2() {
  folly::FixedString<10> tmp{"aaaaaaaaaa"};
  tmp.assign("hello"_fs8);
  return tmp;
}
constexpr folly::FixedString<10> constexpr_assign_string_test_3() {
  folly::FixedString<10> tmp{"aaaaaaaaaa"};
  tmp.assign("goodbye"_fs8, 3u, 2u);
  return tmp;
}
constexpr folly::FixedString<10> constexpr_assign_string_test_4() {
  folly::FixedString<10> tmp{"aaaaaaaaaa"};
  tmp.assign("goodbye"_fs8, 3u, npos);
  return tmp;
}

TEST(FixedStringAssignTest, ConstexprAssignString) {
  static_assert(constexpr_assign_string_test_1() == "world!", "");
  static_assert(constexpr_assign_string_test_2() == "hello", "");
  static_assert(constexpr_assign_string_test_3() == "db", "");
  static_assert(constexpr_assign_string_test_4() == "dbye", "");
}

TEST(FixedStringAssignTest, RuntimeAssignString) {
  folly::FixedString<10> tmp1, tmp2{"world!"};
  tmp1 = tmp2;
  EXPECT_STREQ(tmp1.c_str(), "world!");
  tmp1.assign("goodbye"_fs8);
  EXPECT_STREQ("goodbye", tmp1.c_str());
  tmp1.assign("goodbye"_fs8, 3u, npos);
  EXPECT_STREQ("dbye", tmp1.c_str());
  tmp1.assign("goodbye"_fs8, 3u, 3u);
  EXPECT_STREQ("dby", tmp1.c_str());
}

constexpr folly::FixedString<10> constexpr_assign_literal_test_1() {
  folly::FixedString<10> tmp{"aaaaaaaaaa"};
  tmp = "hello";
  // Not null-terminated, does not compile:
  // using C = const char[1];
  // tmp = C{'a'};
  return tmp;
}
constexpr folly::FixedString<10> constexpr_assign_literal_test_2() {
  folly::FixedString<10> tmp{"aaaaaaaaaa"};
  tmp.assign("hello");
  return tmp;
}
constexpr folly::FixedString<10> constexpr_assign_literal_test_3() {
  folly::FixedString<10> tmp{"aaaaaaaaaa"};
  tmp.assign("goodbye", 4u);
  return tmp;
}

TEST(FixedStringAssignTest, ConstexprAssignLiteral) {
  static_assert(constexpr_assign_literal_test_1() == "hello", "");
  static_assert(constexpr_assign_literal_test_2() == "hello", "");
  static_assert(constexpr_assign_literal_test_3() == "good", "");
}

TEST(FixedStringAssignTest, RuntimeAssignLiteral) {
  folly::FixedString<10> tmp{"aaaaaaaaaa"};
  tmp = "hello";
  EXPECT_STREQ("hello", tmp.c_str());
  tmp.assign("goodbye");
  EXPECT_STREQ("goodbye", tmp.c_str());
  tmp.assign("goodbye", 4u);
  EXPECT_STREQ("good", tmp.c_str());
}

TEST(FixedStringIndexTest, Index) {
  constexpr folly::FixedString<11> digits{"0123456789"};
  static_assert(digits[0] == '0', "");
  static_assert(digits[1] == '1', "");
  static_assert(digits[2] == '2', "");
  static_assert(digits[9] == '9', "");
  static_assert(digits[10] == '\0', "");
#ifdef NDEBUG
  // This should be allowed and work in constexpr mode since the internal array
  // is actually big enough and op[] does no parameter validation:
  static_assert(digits[11] == '\0', "");
#endif

  static_assert(digits.at(0) == '0', "");
  static_assert(digits.at(1) == '1', "");
  static_assert(digits.at(2) == '2', "");
  static_assert(digits.at(9) == '9', "");
  static_assert(digits.at(10) == '\0', "");
  EXPECT_THROW(digits.at(11), std::out_of_range);
}

TEST(FixedStringCompareTest, Compare) {
  constexpr folly::FixedString<10> tmp1{"aaaaaaaaaa"};
  constexpr folly::FixedString<12> tmp2{"aaaaaaaaaba"};
  static_assert(-1 == tmp1.compare(tmp2), "");
  static_assert(1 == tmp2.compare(tmp1), "");
  static_assert(0 == tmp2.compare(tmp2), "");
  static_assert(tmp1 < tmp2, "");
  static_assert(tmp1 <= tmp2, "");
  static_assert(tmp2 > tmp1, "");
  static_assert(tmp2 >= tmp1, "");
  static_assert(tmp2 == tmp2, "");
  static_assert(tmp2 <= tmp2, "");
  static_assert(tmp2 >= tmp2, "");
  static_assert(!(tmp2 < tmp2), "");
  static_assert(!(tmp2 > tmp2), "");

  constexpr folly::FixedString<10> tmp3{"aaa"};
  constexpr folly::FixedString<12> tmp4{"aaaa"};
  static_assert(-1 == tmp3.compare(tmp4), "");
  static_assert(1 == tmp4.compare(tmp3), "");
  static_assert(tmp3 < tmp4, "");
  static_assert(tmp3 <= tmp4, "");
  static_assert(tmp4 > tmp3, "");
  static_assert(tmp4 >= tmp3, "");
  static_assert(tmp3 < "aaaa", "");
  static_assert(tmp3 <= "aaaa", "");
  static_assert(!(tmp3 == tmp4), "");
  static_assert(tmp3 != tmp4, "");
  static_assert("aaaa" > tmp3, "");
  static_assert("aaaa" >= tmp3, "");
  static_assert("aaaa" != tmp3, "");
  static_assert("aaa" == tmp3, "");
  static_assert(tmp3 != "aaaa", "");
  static_assert(tmp3 == "aaa", "");
}

TEST(FixedStringCompareTest, CompareStdString) {
  constexpr folly::FixedString<10> tmp1{"aaaaaaaaaa"};
  std::string const tmp2{"aaaaaaaaaba"};
  EXPECT_EQ(-1, tmp1.compare(tmp2));
  // These are specifically testing the operators, and so we can't rely
  // on whever the implementation details of EXPECT_<OP> might be.
  EXPECT_FALSE(tmp1 == tmp2);
  EXPECT_FALSE(tmp2 == tmp1);
  EXPECT_TRUE(tmp1 != tmp2);
  EXPECT_TRUE(tmp2 != tmp1);
  EXPECT_TRUE(tmp1 < tmp2);
  EXPECT_FALSE(tmp2 < tmp1);
  EXPECT_TRUE(tmp1 <= tmp2);
  EXPECT_FALSE(tmp2 <= tmp1);
  EXPECT_FALSE(tmp1 > tmp2);
  EXPECT_TRUE(tmp2 > tmp1);
  EXPECT_FALSE(tmp1 >= tmp2);
  EXPECT_TRUE(tmp2 >= tmp1);
}

constexpr folly::FixedString<20> constexpr_append_string_test() {
  folly::FixedString<20> a{"hello"}, b{"X world!"};
  a.append(1u, ' ');
  a.append(b, 2u, 5u);
  a.append(b, 7u, 1u);
  return a;
}

TEST(FixedStringAssignTest, ConstexprAppendString) {
  static_assert(constexpr_append_string_test() == "hello world!", "");
}

TEST(FixedStringAssignTest, RuntimeAppendString) {
  folly::FixedString<20> a{"hello"}, b{"X world!"};
  a.append(1u, ' ');
  a.append(b, 2u, 5u);
  a.append(b, 7u, 1u);
  EXPECT_STREQ("hello world!", a.c_str());
}

constexpr folly::FixedString<20> constexpr_append_literal_test() {
  folly::FixedString<20> a{"hello"};
  a.append(1u, ' ');
  a.append("world foo bar baz", 5u);
  a.append("!");
  return a;
}

TEST(FixedStringAssignTest, ConstexprAppendLiteral) {
  static_assert(constexpr_append_literal_test() == "hello world!", "");
}

TEST(FixedStringAssignTest, RuntimeAppendLiteral) {
  folly::FixedString<20> a{"hello"};
  a.append(1u, ' ');
  constexpr char s[] = "X world!";
  a.append(&s[2u], 5u);
  a.append(&s[7u]);
  EXPECT_STREQ("hello world!", a.c_str());
}

TEST(FixedStringCAppendTest, CAppendString) {
  constexpr folly::FixedString<10> a{"hello"}, b{"X world!"};
  constexpr auto tmp1 = a.cappend(' ');
  constexpr auto tmp2 = tmp1.cappend(b, 2u, 5u);
  constexpr auto tmp3 = tmp2.cappend(b, 7u, 1u);
  static_assert(tmp3 == "hello world!", "");
}

TEST(FixedStringCAppendTest, CAppendLiteral) {
  constexpr folly::FixedString<10> a{"hello"};
  constexpr auto tmp1 = a.cappend(' ');
  constexpr auto tmp2 = tmp1.cappend("X world!", 2u, 5u);
  constexpr auto tmp3 = tmp2.cappend("X world!", 7u, 1u);
  static_assert(tmp3 == "hello world!", "");
}

constexpr folly::FixedString<10> constexpr_replace_string_test() {
  folly::FixedString<10> tmp{"abcdefghij"};
  tmp.replace(1, 5, FS("XX"));
  return tmp;
}

TEST(FixedStringReplaceTest, ConstexprReplaceString) {
  static_assert(constexpr_replace_string_test().size() == 7u, "");
  static_assert(constexpr_replace_string_test() == "aXXghij", "");
}

TEST(FixedStringReplaceTest, RuntimeReplaceString) {
  folly::FixedString<10> tmp{"abcdefghij"};
  tmp.replace(1, 5, FS("XX"));
  EXPECT_EQ(7u, tmp.size());
  EXPECT_STREQ("aXXghij", tmp.c_str());
}

TEST(FixedStringEraseTest, RuntimeEraseTest) {
  auto x = FS("abcdefghijklmnopqrstuvwxyz"), y = x;
  x.erase(x.size());
  EXPECT_EQ(26u, x.size());
  EXPECT_STREQ(y.c_str(), x.c_str());
  x.erase(25u).erase(24u);
  EXPECT_EQ(24u, x.size());
  EXPECT_STREQ("abcdefghijklmnopqrstuvwx", x.c_str());
  x.erase(1u, x.size() - 2u);
  EXPECT_EQ(2u, x.size());
  EXPECT_STREQ("ax", x.c_str());
}

TEST(FixedStringEraseTest, CEraseTest) {
  constexpr auto x = FS("abcdefghijklmnopqrstuvwxyz"), y = x;
  constexpr auto tmp0 = x.cerase(x.size());
  static_assert(26u == tmp0.size(), "");
  static_assert(y == tmp0, "");
  constexpr auto tmp1 = tmp0.cerase(25u).cerase(24u);
  static_assert(24u == tmp1.size(), "");
  static_assert("abcdefghijklmnopqrstuvwx" == tmp1, "");
  constexpr auto tmp2 = tmp1.cerase(1u, tmp1.size() - 2u);
  static_assert(2u == tmp2.size(), "");
  static_assert("ax" == tmp2, "");
  constexpr auto tmp3 = tmp2.cerase();
  static_assert("" == tmp3, "");
}

TEST(FixedStringFindTest, FindString) {
  constexpr folly::FixedString<10> tmp{"hijdefghij"};
  static_assert(tmp.find(FS("hij")) == 0u, "");
  static_assert(tmp.find(FS("hij"), 1u) == 7u, "");
  static_assert(tmp.find(FS("hijdefghij")) == 0u, "");
  static_assert(tmp.find(FS("")) == 0u, "");
}

TEST(FixedStringFindTest, FindLiteral) {
  constexpr folly::FixedString<10> tmp{"hijdefghij"};
  static_assert(tmp.find("hij") == 0u, "");
  static_assert(tmp.find("hij", 1u) == 7u, "");
  static_assert(tmp.find("hijdefghij") == 0u, "");
}

TEST(FixedStringReverseFindTest, FindChar) {
  constexpr folly::FixedString<16> tmp{"This is a string"};
  static_assert(tmp.find('s') == 3u, "");
  static_assert(tmp.find('s', 9u) == 10u, "");
  static_assert(tmp.find('s', 10u) == 10u, "");
  static_assert(tmp.find('s', 11u) == tmp.npos, "");
}

TEST(FixedStringReverseFindTest, ReverseFindString) {
  constexpr folly::FixedString<16> tmp{"This is a string"};
  static_assert(tmp.rfind(FS("is")) == 5u, "");
  static_assert(tmp.rfind(FS("is"), 4u) == 2u, "");
  static_assert(tmp.rfind(FS("This is a string")) == 0u, "");
  static_assert(tmp.rfind(FS("This is a string!")) == tmp.npos, "");
  static_assert(tmp.rfind(FS("")) == 16u, "");
}

TEST(FixedStringReverseFindTest, ReverseFindLiteral) {
  constexpr folly::FixedString<16> tmp{"This is a string"};
  static_assert(tmp.rfind("is") == 5u, "");
  static_assert(tmp.rfind("is", 4u) == 2u, "");
  static_assert(tmp.rfind("This is a string") == 0u, "");
  static_assert(tmp.rfind("This is a string!") == tmp.npos, "");
  static_assert(tmp.rfind("") == 16u, "");
}

TEST(FixedStringReverseFindTest, ReverseFindChar) {
  constexpr folly::FixedString<16> tmp{"This is a string"};
  static_assert(tmp.rfind('s') == 10u, "");
  static_assert(tmp.rfind('s', 5u) == 3u, "");
  static_assert(tmp.rfind('s', 3u) == 3u, "");
  static_assert(tmp.rfind('s', 2u) == tmp.npos, "");
}

TEST(FixedStringFindFirstOfTest, FindFirstOfString) {
  constexpr folly::FixedString<16> tmp{"This is a string"};
  static_assert(tmp.find_first_of(FS("hi")) == 1u, "");
  static_assert(tmp.find_first_of(FS("xi")) == 2u, "");
  static_assert(tmp.find_first_of(FS("xi"), 6u) == 13u, "");
  static_assert(tmp.find_first_of(FS("xz")) == tmp.npos, "");
  static_assert(FS("a").find_first_of(FS("cba")) == 0u, "");
  static_assert(FS("").find_first_of(FS("cba")) == tmp.npos, "");
  static_assert(FS("a").find_first_of(FS("")) == tmp.npos, "");
  static_assert(FS("").find_first_of(FS("")) == tmp.npos, "");
}

TEST(FixedStringFindFirstOfTest, FindFirstOfLiteral) {
  constexpr folly::FixedString<16> tmp{"This is a string"};
  static_assert(tmp.find_first_of("hi") == 1u, "");
  static_assert(tmp.find_first_of("xi") == 2u, "");
  static_assert(tmp.find_first_of("xi", 6u) == 13u, "");
  static_assert(tmp.find_first_of("xis", 6u, 2u) == 13u, "");
  static_assert(tmp.find_first_of("xz") == tmp.npos, "");
  static_assert(FS("a").find_first_of("cba") == 0u, "");
  static_assert(FS("").find_first_of("cba") == tmp.npos, "");
  static_assert(FS("a").find_first_of("") == tmp.npos, "");
  static_assert(FS("").find_first_of("") == tmp.npos, "");
}

TEST(FixedStringFindFirstOfTest, FindFirstOfChar) {
  constexpr folly::FixedString<16> tmp{"This is a string"};
  static_assert(tmp.find_first_of('h') == 1u, "");
  static_assert(tmp.find_first_of('i') == 2u, "");
  static_assert(tmp.find_first_of('i', 6u) == 13u, "");
  static_assert(tmp.find_first_of('x') == tmp.npos, "");
  static_assert(FS("a").find_first_of('a') == 0u, "");
  static_assert(FS("").find_first_of('a') == tmp.npos, "");
}

TEST(FixedStringFindFirstNotOfTest, FindFirstNotOfString) {
  constexpr folly::FixedString<16> tmp{"This is a string"};
  static_assert(tmp.find_first_not_of(FS("Ti")) == 1u, "");
  static_assert(tmp.find_first_not_of(FS("hT")) == 2u, "");
  static_assert(tmp.find_first_not_of(FS("s atr"), 6u) == 13u, "");
  static_assert(tmp.find_first_not_of(FS("This atrng")) == tmp.npos, "");
  static_assert(FS("a").find_first_not_of(FS("X")) == 0u, "");
  static_assert(FS("").find_first_not_of(FS("cba")) == tmp.npos, "");
  static_assert(FS("a").find_first_not_of(FS("")) == 0u, "");
  static_assert(FS("").find_first_not_of(FS("")) == tmp.npos, "");
}

TEST(FixedStringFindFirstNotOfTest, FindFirstNotOfLiteral) {
  constexpr folly::FixedString<16> tmp{"This is a string"};
  static_assert(tmp.find_first_not_of("Ti") == 1u, "");
  static_assert(tmp.find_first_not_of("hT") == 2u, "");
  static_assert(tmp.find_first_not_of("s atr", 6u) == 13u, "");
  static_assert(tmp.find_first_not_of("This atrng") == tmp.npos, "");
  static_assert(FS("a").find_first_not_of("X") == 0u, "");
  static_assert(FS("").find_first_not_of("cba") == tmp.npos, "");
  static_assert(FS("a").find_first_not_of("") == 0u, "");
  static_assert(FS("").find_first_not_of("") == tmp.npos, "");
}

TEST(FixedStringFindFirstNotOfTest, FindFirstNotOfChar) {
  constexpr folly::FixedString<16> tmp{"This is a string"};
  static_assert(tmp.find_first_not_of('T') == 1u, "");
  static_assert(tmp.find_first_not_of('i') == 0u, "");
  static_assert(tmp.find_first_not_of('x', 6u) == 6u, "");
  static_assert(tmp.find_first_not_of('s', 6u) == 7u, "");
  static_assert(FS("a").find_first_not_of('a') == tmp.npos, "");
  static_assert(FS("").find_first_not_of('a') == tmp.npos, "");
}

TEST(FixedStringFindLastOfTest, FindLastOfString) {
  constexpr folly::FixedString<16> tmp{"This is a string"};
  static_assert(tmp.find_last_of(FS("hi")) == 13u, "");
  static_assert(tmp.find_last_of(FS("xh")) == 1u, "");
  static_assert(tmp.find_last_of(FS("xi"), 6u) == 5u, "");
  static_assert(tmp.find_last_of(FS("xz")) == tmp.npos, "");
  static_assert(FS("a").find_last_of(FS("cba")) == 0u, "");
  static_assert(FS("").find_last_of(FS("cba")) == tmp.npos, "");
  static_assert(FS("a").find_last_of(FS("")) == tmp.npos, "");
  static_assert(FS("").find_last_of(FS("")) == tmp.npos, "");
}

TEST(FixedStringFindLastOfTest, FindLastOfLiteral) {
  constexpr folly::FixedString<16> tmp{"This is a string"};
  static_assert(tmp.find_last_of("hi") == 13u, "");
  static_assert(tmp.find_last_of("xh") == 1u, "");
  static_assert(tmp.find_last_of("xi", 6u) == 5u, "");
  static_assert(tmp.find_last_of("xis", 6u, 2u) == 5u, "");
  static_assert(tmp.find_last_of("xz") == tmp.npos, "");
  static_assert(FS("a").find_last_of("cba") == 0u, "");
  static_assert(FS("").find_last_of("cba") == tmp.npos, "");
  static_assert(FS("a").find_last_of("") == tmp.npos, "");
  static_assert(FS("").find_last_of("") == tmp.npos, "");
}

TEST(FixedStringFindLastOfTest, FindLastOfChar) {
  constexpr folly::FixedString<16> tmp{"This is a string"};
  static_assert(tmp.find_last_of('h') == 1u, "");
  static_assert(tmp.find_last_of('i') == 13u, "");
  static_assert(tmp.find_last_of('i', 6u) == 5u, "");
  static_assert(tmp.find_last_of('x') == tmp.npos, "");
  static_assert(FS("a").find_last_of('a') == 0u, "");
  static_assert(FS("").find_last_of('a') == tmp.npos, "");
}

TEST(FixedStringFindLastNotOfTest, FindLastNotOfString) {
  constexpr folly::FixedString<16> tmp{"This is a string"};
  static_assert(tmp.find_last_not_of(FS("gstrin")) == 9u, "");
  static_assert(tmp.find_last_not_of(FS("hT")) == 15u, "");
  static_assert(tmp.find_last_not_of(FS("s atr"), 6u) == 5u, "");
  static_assert(tmp.find_last_not_of(FS("This atrng")) == tmp.npos, "");
  static_assert(FS("a").find_last_not_of(FS("X")) == 0u, "");
  static_assert(FS("").find_last_not_of(FS("cba")) == tmp.npos, "");
  static_assert(FS("a").find_last_not_of(FS("")) == 0u, "");
  static_assert(FS("").find_last_not_of(FS("")) == tmp.npos, "");
}

TEST(FixedStringFindLastNotOfTest, FindLastNotOfLiteral) {
  constexpr folly::FixedString<16> tmp{"This is a string"};
  static_assert(tmp.find_last_not_of("gstrin") == 9u, "");
  static_assert(tmp.find_last_not_of("hT") == 15u, "");
  static_assert(tmp.find_last_not_of("s atr", 6u) == 5u, "");
  static_assert(tmp.find_last_not_of(" atrs", 6u, 4u) == 6u, "");
  static_assert(tmp.find_last_not_of("This atrng") == tmp.npos, "");
  static_assert(FS("a").find_last_not_of("X") == 0u, "");
  static_assert(FS("").find_last_not_of("cba") == tmp.npos, "");
  static_assert(FS("a").find_last_not_of("") == 0u, "");
  static_assert(FS("").find_last_not_of("") == tmp.npos, "");
}

TEST(FixedStringFindLastNotOfTest, FindLastNotOfChar) {
  constexpr folly::FixedString<16> tmp{"This is a string"};
  static_assert(tmp.find_last_not_of('g') == 14u, "");
  static_assert(tmp.find_last_not_of('i') == 15u, "");
  static_assert(tmp.find_last_not_of('x', 6u) == 6u, "");
  static_assert(tmp.find_last_not_of('s', 6u) == 5u, "");
  static_assert(FS("a").find_last_not_of('a') == tmp.npos, "");
  static_assert(FS("").find_last_not_of('a') == tmp.npos, "");
}

TEST(FixedStringConversionTest, ConversionToStdString) {
  constexpr folly::FixedString<16> tmp{"This is a string"};
  std::string str = tmp;
  EXPECT_STREQ("This is a string", str.c_str());
  str = "another string"_fs16;
  EXPECT_STREQ("another string", str.c_str());
}

constexpr std::size_t countSpacesReverse(folly::FixedString<50> s) {
  std::size_t count = 0u;
  auto i = s.rbegin();
  for (; i != s.rend(); ++i, --i, i++, i--, i += 1, i -= 1, i += 1) {
    if (' ' == *i) {
      ++count;
    }
  }
  return count;
}

TEST(FixedStringReverseIteratorTest, Cpp14ConstexprReverseIteration) {
  static_assert(3 == countSpacesReverse("This is a string"), "");
}

TEST(FixedStringReverseIteratorTest, ConstexprReverseIteration) {
  static constexpr auto alpha = FS("abcdefghijklmnopqrstuvwxyz");
  static_assert('a' == alpha.rbegin()[25], "");
  static_assert('a' == *(alpha.rbegin() + 25), "");
  static_assert('c' == *(alpha.rbegin() + 25 - 2), "");
  static_assert((alpha.rend() - 2) == (alpha.rbegin() + 24), "");
}

TEST(FixedStringConversionTest, ConversionToFollyRange) {
  // The following declaration is static for compilers that haven't implemented
  // the resolution of:
  // http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1454
  static constexpr folly::FixedString<16> tmp{"This is a string"};
  constexpr folly::StringPiece piece = tmp;
  static_assert(tmp.begin() == piece.begin(), "");
  static_assert(tmp.end() == piece.end(), "");
}

TEST(FixedStringConversionTest, ConversionToStringView) {
  static constexpr folly::FixedString<16> tmp{"This is a string"};
  constexpr std::string_view view = tmp;
  static_assert(tmp.data() == view.data(), "");
  static_assert(tmp.size() == view.size(), "");
}