text: // Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
  #include <cstdint>
  
  #include "gtest/gtest.h"
  
  #include "byteslike.h"
  #include "runtime.h"
  #include "str-builtins.h"
  #include "test-utils.h"
  
  namespace py {
  namespace testing {
  
  using BytearrayTest = RuntimeFixture;
  using LargeBytesTest = RuntimeFixture;
  using SmallBytesTest = RuntimeFixture;
  using CodeTest = RuntimeFixture;
  using ComplexTest = RuntimeFixture;
  using DequeTest = RuntimeFixture;
  using DoubleTest = RuntimeFixture;
  using IntTest = RuntimeFixture;
  using LargeStrTest = RuntimeFixture;
  using ListTest = RuntimeFixture;
  using MmapTest = RuntimeFixture;
  using ModulesTest = RuntimeFixture;
  using MutableBytesTest = RuntimeFixture;
  using MutableTupleTest = RuntimeFixture;
  using SliceTest = RuntimeFixture;
  using SmallStrTest = RuntimeFixture;
  using StrArrayTest = RuntimeFixture;
  using StrTest = RuntimeFixture;
  using StringTest = RuntimeFixture;
  using ValueCellTest = RuntimeFixture;
  using WeakRefTest = RuntimeFixture;
  
  TEST_F(BytearrayTest, DownsizeMaintainsCapacity) {
    HandleScope scope(thread_);
    Bytearray array(&scope, runtime_->newBytearray());
    const byte byte_array[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
    runtime_->bytearrayExtend(thread_, array, byte_array);
    ASSERT_EQ(array.numItems(), 9);
    word capacity = array.capacity();
    array.downsize(5);
    EXPECT_EQ(array.numItems(), 5);
    EXPECT_EQ(array.capacity(), capacity);
  }
  
  TEST_F(DequeTest, DequeClearRemovesElements) {
    HandleScope scope(thread_);
    Deque self(&scope, runtime_->newDeque());
    MutableTuple underlying_tuple(&scope, runtime_->newMutableTuple(5));
    underlying_tuple.atPut(0, SmallInt::fromWord(0));
    underlying_tuple.atPut(1, SmallInt::fromWord(1));
    underlying_tuple.atPut(2, SmallInt::fromWord(2));
  
    self.setItems(*underlying_tuple);
    self.setNumItems(3);
    self.clear();
    ASSERT_EQ(self.numItems(), 0);
    ASSERT_EQ(self.left(), 0);
    EXPECT_EQ(self.at(0), NoneType::object());
    EXPECT_EQ(self.at(1), NoneType::object());
    EXPECT_EQ(self.at(2), NoneType::object());
  }
  
  TEST_F(SmallBytesTest, CopyToStartAtCopiesToDestinationStartingAtIndex) {
    byte src_bytes[] = "hello";
    HandleScope scope(thread_);
    Bytes src(&scope, runtime_->newBytesWithAll(src_bytes));
    byte result[5] = {0};
    src.copyToStartAt(result, 4, 1);
    EXPECT_STREQ(reinterpret_cast<char*>(result), "ello");
  }
  
  TEST_F(SmallBytesTest, FindByteWithZeroLengthReturnsNegativeOne) {
    byte src_bytes[] = "hello";
    HandleScope scope(thread_);
    SmallBytes bytes(&scope, runtime_->newBytesWithAll(src_bytes));
    EXPECT_EQ(bytes.findByte('h', 0, 0), -1);
  }
  
  TEST_F(SmallBytesTest, FindByteWithEndBeforeByteReturnsNegativeOne) {
    byte src_bytes[] = "hello";
    HandleScope scope(thread_);
    SmallBytes bytes(&scope, runtime_->newBytesWithAll(src_bytes));
    EXPECT_EQ(bytes.findByte('o', 1, 2), -1);
  }
  
  TEST_F(SmallBytesTest, FindByteWithByteNotInBytesReturnsNegativeOne) {
    byte src_bytes[] = "hello";
    HandleScope scope(thread_);
    SmallBytes bytes(&scope, runtime_->newBytesWithAll(src_bytes));
    EXPECT_EQ(bytes.findByte('x', 0, bytes.length()), -1);
  }
  
  TEST_F(SmallBytesTest, FindByteWithByteInBytesReturnsIndex) {
    byte src_bytes[] = "hello";
    HandleScope scope(thread_);
    SmallBytes bytes(&scope, runtime_->newBytesWithAll(src_bytes));
    EXPECT_EQ(bytes.findByte('o', 0, bytes.length()), 4);
  }
  
  TEST_F(LargeBytesTest, FindByteWithZerolengthReturnsNegativeOne) {
    byte src_bytes[] = "hello world this is patrick";
    HandleScope scope(thread_);
    LargeBytes bytes(&scope, runtime_->newBytesWithAll(src_bytes));
    EXPECT_EQ(bytes.findByte('h', 0, 0), -1);
  }
  
  TEST_F(LargeBytesTest, FindByteWithEndBeforeByteReturnsNegativeOne) {
    byte src_bytes[] = "hello world";
    HandleScope scope(thread_);
    LargeBytes bytes(&scope, runtime_->newBytesWithAll(src_bytes));
    EXPECT_EQ(bytes.findByte('d', 3, 5), -1);
  }
  
  TEST_F(LargeBytesTest, FindByteWithByteNotInBytesReturnsNegativeOne) {
    byte src_bytes[] = "hello world";
    HandleScope scope(thread_);
    LargeBytes bytes(&scope, runtime_->newBytesWithAll(src_bytes));
    EXPECT_EQ(bytes.findByte('x', 0, bytes.length()), -1);
  }
  
  TEST_F(LargeBytesTest, FindByteWithByteInBytesReturnsIndex) {
    byte src_bytes[] = "hello world";
    HandleScope scope(thread_);
    LargeBytes bytes(&scope, runtime_->newBytesWithAll(src_bytes));
    EXPECT_EQ(bytes.findByte('o', 0, bytes.length()), 4);
  }
  
  TEST_F(LargeBytesTest, FindByteNonZeroStartReturnsIndex) {
    byte src_bytes[] = "hello world";
    HandleScope scope(thread_);
    LargeBytes bytes(&scope, runtime_->newBytesWithAll(src_bytes));
    EXPECT_EQ(bytes.findByte('o', 5, bytes.length() - 5), 7);
  }
  
  TEST_F(LargeBytesTest, CopyToStartAtCopiesToDestinationStartingAtIndex) {
    byte src_bytes[] = "hello world this is patrick";
    HandleScope scope(thread_);
    LargeBytes src(&scope, runtime_->newBytesWithAll(src_bytes));
    byte result[8] = {0};
    src.copyToStartAt(result, 7, 20);
    EXPECT_STREQ(reinterpret_cast<char*>(result), "patrick");
  }
  
  TEST_F(MutableBytesTest,
         IndexOfAnyWithDifferentStartReturnsFirstMatchIndexAfterStart) {
    HandleScope scope(thread_);
  
    const byte src_bytes[] = {'h', 'e', 'l', 'l', 'o', ' ',
                              'w', 'o', 'r', 'l', 'd'};
    word src_length = ARRAYSIZE(src_bytes);
    MutableBytes src(&scope, runtime_->newMutableBytesUninitialized(src_length));
    for (word i = 0; i < src_length; i++) {
      src.byteAtPut(i, src_bytes[i]);
    }
    const byte needle[] = "eor";
    EXPECT_EQ(src.indexOfAny(needle, 0), 1);
    EXPECT_EQ(src.indexOfAny(needle, 2), 4);
    EXPECT_EQ(src.indexOfAny(needle, 5), 7);
  }
  
  TEST_F(MutableBytesTest, IndexOfAnyWithNeedleNoMatchReturnsHaystackLength) {
    HandleScope scope(thread_);
  
    const byte src_bytes[] = {'h', 'e', 'l', 'l', 'o', ' ',
                              'w', 'o', 'r', 'l', 'd'};
    word src_length = ARRAYSIZE(src_bytes);
    MutableBytes src(&scope, runtime_->newMutableBytesUninitialized(src_length));
    for (word i = 0; i < src_length; i++) {
      src.byteAtPut(i, src_bytes[i]);
    }
    const byte needle[] = "abc";
    EXPECT_EQ(src.indexOfAny(needle, 0), src_length);
    EXPECT_EQ(src.indexOfAny(needle, 5), src_length);
    EXPECT_EQ(src.indexOfAny(needle, 9), src_length);
  }
  
  TEST_F(MutableBytesTest, ReplaceFromWithByteslikeReplacesBytes) {
    HandleScope scope(thread_);
    const byte bytes[] = "hello world";
    Object value(&scope, runtime_->newBytesWithAll(bytes));
    Byteslike byteslike(&scope, thread_, *value);
    ASSERT_TRUE(byteslike.isValid());
    MutableBytes mutable_bytes(&scope,
                               runtime_->newMutableBytesUninitialized(11));
    mutable_bytes.replaceFromWithByteslike(0, byteslike, 5);
    mutable_bytes.replaceFromWithByteslike(5, byteslike, 2);
    mutable_bytes.replaceFromWithByteslike(11, byteslike, 0);
    mutable_bytes.replaceFromWithByteslike(7, byteslike, 3);
    mutable_bytes.byteAtPut(10, '\0');
    const byte expected[] = "hellohehel";
    EXPECT_TRUE(isMutableBytesEqualsBytes(mutable_bytes, expected));
  }
  
  TEST_F(MutableBytesTest, ReplaceFromWithByteslikeStartAtReplacesBytes) {
    HandleScope scope(thread_);
    const byte bytes[] = "hello world";
    Object value(&scope, runtime_->newBytesWithAll(bytes));
    Byteslike byteslike(&scope, thread_, *value);
    ASSERT_TRUE(byteslike.isValid());
    MutableBytes mutable_bytes(&scope,
                               runtime_->newMutableBytesUninitialized(10));
    mutable_bytes.replaceFromWithByteslikeStartAt(0, byteslike, 5, 0);
    mutable_bytes.replaceFromWithByteslikeStartAt(2, byteslike, 4, 1);
    mutable_bytes.replaceFromWithByteslikeStartAt(6, byteslike, 4, 8);
    mutable_bytes.replaceFromWithByteslikeStartAt(7, byteslike, 0, 11);
    mutable_bytes.replaceFromWithByteslikeStartAt(10, byteslike, 0, 0);
    const byte expected[] = "heellorld";
    EXPECT_TRUE(isMutableBytesEqualsBytes(mutable_bytes, expected));
  }
  
  TEST_F(MutableBytesTest, ReplaceFromWithStartAtSelfNoop) {
    HandleScope scope(thread_);
    const byte src_bytes[] = "patrick";
    word src_length = ARRAYSIZE(src_bytes);
    MutableBytes src(&scope, runtime_->newMutableBytesUninitialized(src_length));
    for (word i = 0; i < src_length; i++) {
      src.byteAtPut(i, src_bytes[i]);
    }
    ASSERT_TRUE(isMutableBytesEqualsBytes(src, src_bytes));
    src.replaceFromWithStartAt(0, *src, 3, 0);
    EXPECT_TRUE(isMutableBytesEqualsBytes(src, src_bytes));
  }
  
  TEST_F(MutableBytesTest, ReplaceFromWithStartAtSelfBackward) {
    HandleScope scope(thread_);
    const byte src_bytes[] = "patrick";
    word src_length = ARRAYSIZE(src_bytes);
    MutableBytes src(&scope, runtime_->newMutableBytesUninitialized(src_length));
    for (word i = 0; i < src_length; i++) {
      src.byteAtPut(i, src_bytes[i]);
    }
    ASSERT_TRUE(isMutableBytesEqualsBytes(src, src_bytes));
    src.replaceFromWithStartAt(0, *src, 3, 4);
    const byte expected[] = "ickrick";
    EXPECT_TRUE(isMutableBytesEqualsBytes(src, expected));
  }
  
  TEST_F(MutableBytesTest, ReplaceFromWithStartAtSelfForward) {
    HandleScope scope(thread_);
    const byte src_bytes[] = "patrick";
    word src_length = ARRAYSIZE(src_bytes);
    MutableBytes src(&scope, runtime_->newMutableBytesUninitialized(src_length));
    for (word i = 0; i < src_length; i++) {
      src.byteAtPut(i, src_bytes[i]);
    }
    ASSERT_TRUE(isMutableBytesEqualsBytes(src, src_bytes));
    src.replaceFromWithStartAt(4, *src, 3, 0);
    const byte expected[] = "patrpat";
    EXPECT_TRUE(isMutableBytesEqualsBytes(src, expected));
  }
  
  TEST_F(MutableBytesTest, ReplaceFromWithStartAtReplacesStartingAtSrcIndex) {
    byte src_bytes[] = "hello world this is patrick";
    HandleScope scope(thread_);
    LargeBytes src(&scope, runtime_->newBytesWithAll(src_bytes));
    MutableBytes dst(&scope, runtime_->newMutableBytesUninitialized(8));
    dst.replaceFromWithStartAt(0, *src, 7, 20);
    const byte expected[] = "patrick";
    EXPECT_TRUE(isMutableBytesEqualsBytes(dst, expected));
  }
  
  TEST_F(CodeTest, OffsetToLineNumReturnsLineNumber) {
    const char* src = R"(
  def func():
    a = 1
    b = 2
    print(a, b)
  )";
    ASSERT_FALSE(runFromCStr(runtime_, src).isError());
    HandleScope scope(thread_);
  
    // The bytecode for func is roughly:
    // LOAD_CONST     # a = 1
    // STORE_FAST
    //
    // LOAD_CONST     # b = 2
    // STORE_FAST
    //
    // LOAD_GLOBAL    # print(a, b)
    // LOAD_FAST
    // LOAD_FAST
    // CALL_FUNCTION
  
    Function func(&scope, mainModuleAt(runtime_, "func"));
    Code code(&scope, func.code());
    ASSERT_EQ(code.firstlineno(), 2);
  
    // a = 1
    EXPECT_EQ(code.offsetToLineNum(0), 3);
    EXPECT_EQ(code.offsetToLineNum(2 * kCodeUnitScale), 3);
  
    // b = 2
    EXPECT_EQ(code.offsetToLineNum(4 * kCodeUnitScale), 4);
    EXPECT_EQ(code.offsetToLineNum(6 * kCodeUnitScale), 4);
  
    // print(a, b)
    for (word i = 8; i < Bytes::cast(code.code()).length(); i++) {
      EXPECT_EQ(code.offsetToLineNum(i * kCodeUnitScale), 5);
    }
  }
  
  TEST_F(DoubleTest, DoubleTest) {
    RawObject o = runtime_->newFloat(3.14);
    ASSERT_TRUE(o.isFloat());
    RawFloat d = Float::cast(o);
    EXPECT_EQ(d.value(), 3.14);
  }
  
  TEST_F(ComplexTest, ComplexTest) {
    RawObject o = runtime_->newComplex(1.0, 2.0);
    ASSERT_TRUE(o.isComplex());
    RawComplex c = Complex::cast(o);
    EXPECT_EQ(c.real(), 1.0);
    EXPECT_EQ(c.imag(), 2.0);
  }
  
  TEST_F(IntTest, IntTest) {
    HandleScope scope(thread_);
  
    Object o1(&scope, runtime_->newInt(42));
    EXPECT_TRUE(isIntEqualsWord(*o1, 42));
  
    Object o2(&scope, runtime_->newInt(9223372036854775807L));
    EXPECT_TRUE(isIntEqualsWord(*o2, 9223372036854775807L));
  
    int stack_val = 123;
    Int o3(&scope, runtime_->newIntFromCPtr(&stack_val));
    EXPECT_EQ(*static_cast<int*>(o3.asCPtr()), 123);
  
    Object o4(&scope, runtime_->newInt(kMinWord));
    EXPECT_TRUE(isIntEqualsWord(*o4, kMinWord));
  
    uword digits[] = {kMaxUword, 0};
    Int o5(&scope, runtime_->newLargeIntWithDigits(digits));
    EXPECT_TRUE(o5.isLargeInt());
    EXPECT_EQ(o5.bitLength(), kBitsPerWord);
  
    uword digits2[] = {kMaxUword, 1};
    Int o6(&scope, runtime_->newLargeIntWithDigits(digits2));
    EXPECT_TRUE(o6.isLargeInt());
    EXPECT_EQ(o6.bitLength(), kBitsPerWord + 1);
  }
  
  TEST_F(IntTest, LargeIntValid) {
    HandleScope scope(thread_);
  
    uword digits[] = {static_cast<uword>(-1234), static_cast<uword>(-1)};
    LargeInt i(&scope, newLargeIntWithDigits(digits));
    // Redundant sign-extension
    EXPECT_FALSE(i.isValid());
  
    i.digitAtPut(1, -2);
    EXPECT_TRUE(i.isValid());
  
    i.digitAtPut(0, 1234);
    i.digitAtPut(1, 0);
    // Redundant zero-extension
    EXPECT_FALSE(i.isValid());
  
    i.digitAtPut(1, 1);
    EXPECT_TRUE(i.isValid());
  }
  
  TEST_F(IntTest, IsPositive) {
    HandleScope scope(thread_);
  
    Int zero(&scope, runtime_->newInt(0));
    EXPECT_FALSE(zero.isPositive());
  
    Int one(&scope, runtime_->newInt(1));
    EXPECT_TRUE(one.isPositive());
  
    Int neg_one(&scope, runtime_->newInt(-1));
    EXPECT_FALSE(neg_one.isPositive());
  
    Int max_small_int(&scope, runtime_->newInt(RawSmallInt::kMaxValue));
    EXPECT_TRUE(max_small_int.isPositive());
  
    Int min_small_int(&scope, runtime_->newInt(RawSmallInt::kMinValue));
    EXPECT_FALSE(min_small_int.isPositive());
  
    Int max_word(&scope, runtime_->newInt(kMaxWord));
    EXPECT_TRUE(max_word.isPositive());
  
    Int min_word(&scope, runtime_->newInt(kMinWord));
    EXPECT_FALSE(min_word.isPositive());
  }
  
  TEST_F(IntTest, IsNegative) {
    HandleScope scope(thread_);
  
    Int zero(&scope, runtime_->newInt(0));
    EXPECT_FALSE(zero.isNegative());
  
    Int one(&scope, runtime_->newInt(1));
    EXPECT_FALSE(one.isNegative());
  
    Int neg_one(&scope, runtime_->newInt(-1));
    EXPECT_TRUE(neg_one.isNegative());
  
    Int max_small_int(&scope, runtime_->newInt(RawSmallInt::kMaxValue));
    EXPECT_FALSE(max_small_int.isNegative());
  
    Int min_small_int(&scope, runtime_->newInt(RawSmallInt::kMinValue));
    EXPECT_TRUE(min_small_int.isNegative());
  
    Int max_word(&scope, runtime_->newInt(kMaxWord));
    EXPECT_FALSE(max_word.isNegative());
  
    Int min_word(&scope, runtime_->newInt(kMinWord));
    EXPECT_TRUE(min_word.isNegative());
  }
  
  TEST_F(IntTest, IsZero) {
    HandleScope scope(thread_);
  
    Int zero(&scope, runtime_->newInt(0));
    EXPECT_TRUE(zero.isZero());
  
    Int one(&scope, runtime_->newInt(1));
    EXPECT_FALSE(one.isZero());
  
    Int neg_one(&scope, runtime_->newInt(-1));
    EXPECT_FALSE(neg_one.isZero());
  
    Int max_small_int(&scope, runtime_->newInt(RawSmallInt::kMaxValue));
    EXPECT_FALSE(max_small_int.isZero());
  
    Int min_small_int(&scope, runtime_->newInt(RawSmallInt::kMinValue));
    EXPECT_FALSE(min_small_int.isZero());
  
    Int max_word(&scope, runtime_->newInt(kMaxWord));
    EXPECT_FALSE(max_word.isZero());
  
    Int min_word(&scope, runtime_->newInt(kMinWord));
    EXPECT_FALSE(min_word.isZero());
  }
  
  TEST_F(IntTest, Compare) {
    HandleScope scope(thread_);
  
    Int zero(&scope, runtime_->newInt(0));
    Int one(&scope, runtime_->newInt(1));
    Int neg_one(&scope, runtime_->newInt(-1));
  
    EXPECT_EQ(zero.compare(*zero), 0);
    EXPECT_GE(one.compare(*neg_one), 1);
    EXPECT_LE(neg_one.compare(*one), -1);
  
    Int min_small_int(&scope, runtime_->newInt(RawSmallInt::kMinValue));
    Int max_small_int(&scope, runtime_->newInt(RawSmallInt::kMaxValue));
  
    EXPECT_GE(max_small_int.compare(*min_small_int), 1);
    EXPECT_LE(min_small_int.compare(*max_small_int), -1);
    EXPECT_EQ(min_small_int.compare(*min_small_int), 0);
    EXPECT_EQ(max_small_int.compare(*max_small_int), 0);
  
    Int min_word(&scope, runtime_->newInt(kMinWord));
    Int max_word(&scope, runtime_->newInt(kMaxWord));
  
    EXPECT_GE(max_word.compare(*min_word), 1);
    EXPECT_LE(min_word.compare(*max_word), -1);
    EXPECT_EQ(min_word.compare(*min_word), 0);
    EXPECT_EQ(max_word.compare(*max_word), 0);
  
    EXPECT_GE(max_word.compare(*max_small_int), 1);
    EXPECT_LE(min_word.compare(*min_small_int), -1);
  }
  
  TEST_F(IntTest, LargeIntCompare) {
    HandleScope scope(thread_);
    const uword digits_great[] = {1, 1};
    Int great(&scope, runtime_->newLargeIntWithDigits(digits_great));
    const uword digits_small[] = {0, 0, kMaxUword};
    Int small(&scope, runtime_->newLargeIntWithDigits(digits_small));
    EXPECT_EQ(great.compare(*small), 1);
    EXPECT_EQ(small.compare(*great), -1);
  
    const uword digits_great2[] = {1, 1, 1};
    const uword digits_small2[] = {1, 1};
    great = runtime_->newLargeIntWithDigits(digits_great2);
    small = runtime_->newLargeIntWithDigits(digits_small2);
    EXPECT_EQ(great.compare(*small), 1);
    EXPECT_EQ(small.compare(*great), -1);
  
    const uword digits_great3[] = {kMaxUword - 1, 1};
    const uword digits_small3[] = {2, 1};
    great = runtime_->newLargeIntWithDigits(digits_great3);
    small = runtime_->newLargeIntWithDigits(digits_small3);
    EXPECT_EQ(great.compare(*small), 1);
    EXPECT_EQ(small.compare(*great), -1);
  
    const uword digits_great4[] = {kMaxUword - 1, kMaxUword - 1};
    const uword digits_small4[] = {2, kMaxUword - 1};
    great = runtime_->newLargeIntWithDigits(digits_great4);
    small = runtime_->newLargeIntWithDigits(digits_small4);
    EXPECT_EQ(great.compare(*small), 1);
    EXPECT_EQ(small.compare(*great), -1);
  }
  
  #define EXPECT_VALID(expr, expected_value)                                     \
    {                                                                            \
      auto const result = (expr);                                                \
      EXPECT_EQ(result.error, CastError::None);                                  \
      EXPECT_EQ(result.value, expected_value);                                   \
    }
  
  TEST_F(IntTest, AsIntWithZeroReturnsZero) {
    HandleScope scope(thread_);
    Int zero(&scope, runtime_->newInt(0));
    EXPECT_VALID(zero.asInt<int>(), 0);
    EXPECT_VALID(zero.asInt<unsigned>(), 0U);
    EXPECT_VALID(zero.asInt<unsigned long>(), 0UL);
    EXPECT_VALID(zero.asInt<unsigned long long>(), 0ULL);
  }
  
  TEST_F(IntTest, AsIntReturnsInt) {
    HandleScope scope(thread_);
    Int num(&scope, runtime_->newInt(1234));
    EXPECT_VALID(num.asInt<int>(), 1234);
    EXPECT_VALID(num.asInt<long>(), 1234);
    EXPECT_VALID(num.asInt<unsigned>(), 1234U);
    EXPECT_VALID(num.asInt<unsigned long>(), 1234UL);
  }
  
  TEST_F(IntTest, AsIntReturnsOverflow) {
    HandleScope scope(thread_);
    Int num(&scope, runtime_->newInt(1234));
    EXPECT_EQ(num.asInt<byte>().error, CastError::Overflow);
    EXPECT_EQ(num.asInt<int8_t>().error, CastError::Overflow);
    Int word_max(&scope, runtime_->newInt(kMaxWord));
    EXPECT_EQ(word_max.asInt<int32_t>().error, CastError::Overflow);
    Int word_min(&scope, runtime_->newInt(kMinWord));
    EXPECT_EQ(word_min.asInt<int32_t>().error, CastError::Overflow);
  }
  
  TEST_F(IntTest, AsIntWithNegativeIntReturnsInt) {
    HandleScope scope(thread_);
    Int neg_num(&scope, runtime_->newInt(-4567));
    EXPECT_VALID(neg_num.asInt<int16_t>(), -4567);
    Int neg_one(&scope, runtime_->newInt(-1));
    EXPECT_VALID(neg_one.asInt<int>(), -1);
  }
  
  TEST_F(IntTest, AsIntReturnsUnderflow) {
    HandleScope scope(thread_);
    Int neg_num(&scope, runtime_->newInt(-4567));
    EXPECT_EQ(neg_num.asInt<unsigned>().error, CastError::Underflow);
    EXPECT_EQ(neg_num.asInt<int8_t>().error, CastError::Underflow);
    Int neg_one(&scope, runtime_->newInt(-1));
    EXPECT_EQ(neg_one.asInt<unsigned>().error, CastError::Underflow);
    Int word_min(&scope, runtime_->newInt(kMinWord));
    EXPECT_EQ(word_min.asInt<uword>().error, CastError::Underflow);
  }
  
  TEST_F(IntTest, AsIntWithMaxInt32ReturnsInt) {
    HandleScope scope(thread_);
    Int int32_max(&scope, runtime_->newInt(kMaxInt32));
    EXPECT_VALID(int32_max.asInt<int32_t>(), kMaxInt32);
    EXPECT_EQ(int32_max.asInt<int16_t>().error, CastError::Overflow);
  }
  
  TEST_F(IntTest, AsIntWithMaxUwordReturnsInt) {
    HandleScope scope(thread_);
    Int uword_max(&scope, runtime_->newIntFromUnsigned(kMaxUword));
    EXPECT_VALID(uword_max.asInt<uword>(), kMaxUword);
    EXPECT_EQ(uword_max.asInt<word>().error, CastError::Overflow);
  }
  
  TEST_F(IntTest, AsIntWithMaxWordReturnsInt) {
    HandleScope scope(thread_);
    Int word_max(&scope, runtime_->newInt(kMaxWord));
    EXPECT_VALID(word_max.asInt<word>(), kMaxWord);
    EXPECT_VALID(word_max.asInt<uword>(), uword{kMaxWord});
  }
  
  TEST_F(IntTest, AsIntWithMinWordReturnsInt) {
    HandleScope scope(thread_);
    Int word_min(&scope, runtime_->newInt(kMinWord));
    EXPECT_VALID(word_min.asInt<word>(), kMinWord);
  }
  
  TEST_F(IntTest, AsIntWithNegativeLargeIntReturnsUnderflow) {
    HandleScope scope(thread_);
    uword digits[] = {0, kMaxUword};
    Int negative(&scope, runtime_->newLargeIntWithDigits(digits));
    EXPECT_EQ(negative.asInt<word>().error, CastError::Underflow);
    EXPECT_EQ(negative.asInt<uword>().error, CastError::Underflow);
  }
  
  TEST_F(IntTest, AsIntWithTrueReturnsOne) {
    HandleScope scope(thread_);
    Int value(&scope, Bool::trueObj());
    EXPECT_VALID(value.asInt<word>(), 1);
    EXPECT_VALID(value.asInt<uint8_t>(), 1);
  }
  
  TEST_F(IntTest, AsIntWithFalseReturnsZero) {
    HandleScope scope(thread_);
    Int value(&scope, Bool::falseObj());
    EXPECT_VALID(value.asInt<uword>(), uword{0});
    EXPECT_VALID(value.asInt<int32_t>(), 0);
  }
  
  #undef EXPECT_VALID
  
  TEST_F(IntTest, SmallIntFromWordTruncatedWithSmallNegativeNumberReturnsSelf) {
    EXPECT_EQ(SmallInt::fromWord(-1), SmallInt::fromWordTruncated(-1));
  }
  
  TEST_F(MmapTest, AccessSettersSetValues) {
    HandleScope scope(thread_);
    Object obj(&scope, runtime_->newMmap());
    ASSERT_TRUE(obj.isMmap());
    Mmap mmap_obj(&scope, *obj);
    mmap_obj.setReadable();
    mmap_obj.setWritable();
    mmap_obj.setCopyOnWrite();
    EXPECT_EQ(mmap_obj.isReadable(), true);
    EXPECT_EQ(mmap_obj.isWritable(), true);
    EXPECT_EQ(mmap_obj.isCopyOnWrite(), true);
  }
  
  TEST_F(ModulesTest, TestCreate) {
    HandleScope scope(thread_);
    Object name(&scope, runtime_->newStrFromCStr("mymodule"));
    Module module(&scope, runtime_->newModule(name));
    EXPECT_EQ(module.name(), *name);
  }
  
  TEST_F(MutableBytesTest, BecomeStrTurnsObjectIntoSmallStr) {
    HandleScope scope(thread_);
    Object test_0(&scope, runtime_->emptyMutableBytes());
    ASSERT_TRUE(test_0.isMutableBytes());
    Object as_str_0(&scope, MutableBytes::cast(*test_0).becomeStr());
    EXPECT_TRUE(test_0.isMutableBytes());
    EXPECT_TRUE(as_str_0.isSmallStr());
    EXPECT_TRUE(isStrEqualsCStr(*as_str_0, ""));
  
    Str str(&scope, runtime_->newStrFromCStr("abcdefghi"));
  
    Object test_1(&scope, runtime_->newMutableBytesUninitialized(1));
    ASSERT_TRUE(test_1.isMutableBytes());
    MutableBytes::cast(*test_1).replaceFromWithStr(0, *str, 1);
    Object as_str_1(&scope, MutableBytes::cast(*test_1).becomeStr());
    EXPECT_TRUE(test_1.isMutableBytes());
    EXPECT_TRUE(as_str_1.isSmallStr());
    EXPECT_TRUE(isStrEqualsCStr(*as_str_1, "a"));
  
    Object test_m(&scope,
                  runtime_->newMutableBytesUninitialized(SmallStr::kMaxLength));
    ASSERT_TRUE(test_m.isMutableBytes());
    MutableBytes::cast(*test_m).replaceFromWithStr(0, *str, SmallStr::kMaxLength);
    Object as_str_m(&scope, MutableBytes::cast(*test_m).becomeStr());
    EXPECT_TRUE(test_m.isMutableBytes());
    EXPECT_TRUE(as_str_m.isSmallStr());
    EXPECT_TRUE(isStrEqualsCStr(*as_str_m, "abcdefg"));
  }
  
  TEST_F(MutableBytesTest, BecomeStrTurnsObjectIntoLargeStr) {
    HandleScope scope(thread_);
    Str str(&scope, runtime_->newStrFromCStr("hello world!"));
  
    Object test(&scope, runtime_->newMutableBytesUninitialized(str.length()));
    ASSERT_TRUE(test.isMutableBytes());
    MutableBytes::cast(*test).replaceFromWithStr(0, *str, str.length());
    MutableBytes::cast(*test).becomeStr();
    EXPECT_TRUE(test.isLargeStr());
    EXPECT_TRUE(isStrEqualsCStr(*test, "hello world!"));
  }
  
  TEST_F(SliceTest, AdjustIndices) {
    // Test: 0:10:1 on len: 10
    word length = 10;
    word start = 0;
    word stop = 10;
    word step = 1;
    word new_length = Slice::adjustIndices(length, &start, &stop, step);
    ASSERT_EQ(new_length, 10);
    ASSERT_EQ(start, 0);
    ASSERT_EQ(stop, 10);
  
    // Test: 2:10:1 on len: 10
    start = 2;
    new_length = Slice::adjustIndices(length, &start, &stop, step);
    ASSERT_EQ(new_length, 8);
    ASSERT_EQ(start, 2);
    ASSERT_EQ(stop, 10);
  
    // Test: -4:10:1 on len: 10
    start = -4;
    new_length = Slice::adjustIndices(length, &start, &stop, step);
    ASSERT_EQ(new_length, 4);
    ASSERT_EQ(start, 6);
    ASSERT_EQ(stop, 10);
  
    // Test: 0:2:1 on len: 10
    start = 0;
    stop = 2;
    new_length = Slice::adjustIndices(length, &start, &stop, step);
    ASSERT_EQ(new_length, 2);
    ASSERT_EQ(start, 0);
    ASSERT_EQ(stop, 2);
  
    // Test: 0:-2:1 on len: 10
    start = 0;
    stop = -2;
    new_length = Slice::adjustIndices(length, &start, &stop, step);
    ASSERT_EQ(new_length, 8);
    ASSERT_EQ(start, 0);
    ASSERT_EQ(stop, 8);
  
    // Test: 0:10:2 on len: 10
    start = 0;
    stop = 10;
    step = 2;
    new_length = Slice::adjustIndices(length, &start, &stop, step);
    ASSERT_EQ(new_length, 5);
    ASSERT_EQ(start, 0);
    ASSERT_EQ(stop, 10);
  
    // Test: 0:10:-2 on len: 10
    start = 0;
    stop = 10;
    step = -2;
    new_length = Slice::adjustIndices(length, &start, &stop, step);
    ASSERT_EQ(new_length, 0);
    ASSERT_EQ(start, 0);
    ASSERT_EQ(stop, 9);
  }
  
  TEST_F(SliceTest, AdjustIndicesOutOfBounds) {
    // Test: 10:5:1 on len: 5
    word length = 5;
    word start = 10;
    word stop = 5;
    word step = 1;
    word new_length = Slice::adjustIndices(length, &start, &stop, step);
    ASSERT_EQ(new_length, 0);
    ASSERT_EQ(start, 5);
    ASSERT_EQ(stop, 5);
  
    // Test: -10:5:1 on len: 5
    start = -10;
    new_length = Slice::adjustIndices(length, &start, &stop, step);
    ASSERT_EQ(new_length, 5);
    ASSERT_EQ(start, 0);
    ASSERT_EQ(stop, 5);
  
    // Test: 0:10:1 on len: 5
    start = 0;
    stop = 10;
    new_length = Slice::adjustIndices(length, &start, &stop, step);
    ASSERT_EQ(new_length, 5);
    ASSERT_EQ(start, 0);
    ASSERT_EQ(stop, 5);
  
    // Test: 0:-10:1 on len: 5
    stop = -10;
    new_length = Slice::adjustIndices(length, &start, &stop, step);
    ASSERT_EQ(new_length, 0);
    ASSERT_EQ(start, 0);
    ASSERT_EQ(stop, 0);
  
    // Test: 0:5:10 on len: 5
    stop = 5;
    step = 10;
    new_length = Slice::adjustIndices(length, &start, &stop, step);
    ASSERT_EQ(new_length, 1);
    ASSERT_EQ(start, 0);
    ASSERT_EQ(stop, 5);
  
    // Test: 0:5:-10 on len: 5
    step = -10;
    new_length = Slice::adjustIndices(length, &start, &stop, step);
    ASSERT_EQ(new_length, 0);
    ASSERT_EQ(start, 0);
    ASSERT_EQ(stop, 4);
  }
  
  TEST_F(SliceTest, LengthWithNegativeStepAndStopLessThanStartReturnsLength) {
    EXPECT_EQ(Slice::length(5, 2, -1), 3);
  }
  
  TEST_F(SliceTest, LengthWithNegativeStepAndStartLessThanStopReturnsZero) {
    EXPECT_EQ(Slice::length(2, 5, -1), 0);
  }
  
  TEST_F(SliceTest, LengthWithNegativeStepAndStartEqualsStopReturnsZero) {
    EXPECT_EQ(Slice::length(2, 2, -1), 0);
  }
  
  TEST_F(SliceTest, LengthWithPositiveStepAndStartLessThanStopReturnsLength) {
    EXPECT_EQ(Slice::length(2, 5, 1), 3);
  }
  
  TEST_F(SliceTest, LengthWithPositiveStepAndStopLessThanStartReturnsZero) {
    EXPECT_EQ(Slice::length(5, 2, 1), 0);
  }
  
  TEST_F(SliceTest, LengthWithPositiveStepAndStartEqualsStopReturnsZero) {
    EXPECT_EQ(Slice::length(2, 2, 1), 0);
  }
  
  TEST_F(StrArrayTest, OffsetByCodePoints) {
    HandleScope scope(thread_);
  
    StrArray empty(&scope, runtime_->newStrArray());
    EXPECT_EQ(empty.numItems(), 0);
    EXPECT_EQ(empty.offsetByCodePoints(0, 1), 0);
    EXPECT_EQ(empty.offsetByCodePoints(2, 0), 0);
    EXPECT_EQ(empty.offsetByCodePoints(2, 1), 0);
  
    StrArray ascii(&scope, runtime_->newStrArray());
    Str ascii_str(&scope, runtime_->newStrFromCStr("abcd"));
    runtime_->strArrayAddStr(thread_, ascii, ascii_str);
    EXPECT_EQ(ascii.numItems(), 4);
  
    // for ASCII, each code point is one byte wide
    EXPECT_EQ(ascii.offsetByCodePoints(0, 0), 0);
    EXPECT_EQ(ascii.offsetByCodePoints(0, 3), 3);
    EXPECT_EQ(ascii.offsetByCodePoints(1, 0), 1);
    EXPECT_EQ(ascii.offsetByCodePoints(2, 0), 2);
    EXPECT_EQ(ascii.offsetByCodePoints(2, 1), 3);
    EXPECT_EQ(ascii.offsetByCodePoints(3, 0), 3);
  
    // return the length once we reach the end of the string
    EXPECT_EQ(ascii.offsetByCodePoints(0, 4), 4);
    EXPECT_EQ(ascii.offsetByCodePoints(0, 5), 4);
    EXPECT_EQ(ascii.offsetByCodePoints(1, 3), 4);
    EXPECT_EQ(ascii.offsetByCodePoints(1, 4), 4);
    EXPECT_EQ(ascii.offsetByCodePoints(2, 2), 4);
    EXPECT_EQ(ascii.offsetByCodePoints(2, 3), 4);
    EXPECT_EQ(ascii.offsetByCodePoints(3, 1), 4);
    EXPECT_EQ(ascii.offsetByCodePoints(3, 2), 4);
    EXPECT_EQ(ascii.offsetByCodePoints(4, 0), 4);
    EXPECT_EQ(ascii.offsetByCodePoints(6, 0), 4);
  
    StrArray unicode(&scope, runtime_->newStrArray());
    Str unicode_str(
        &scope, runtime_->newStrFromCStr("\xd7\x90pq\xd7\x91\xd7\x92-\xd7\x93"));
    runtime_->strArrayAddStr(thread_, unicode, unicode_str);
    EXPECT_EQ(unicode.numItems(), 11);
  
    // for Unicode, code points may be more than one byte wide
    EXPECT_EQ(unicode.offsetByCodePoints(0, 0), 0);
    EXPECT_EQ(unicode.offsetByCodePoints(0, 1), 2);
    EXPECT_EQ(unicode.offsetByCodePoints(0, 2), 3);
    EXPECT_EQ(unicode.offsetByCodePoints(0, 3), 4);
    EXPECT_EQ(unicode.offsetByCodePoints(0, 4), 6);
    EXPECT_EQ(unicode.offsetByCodePoints(0, 5), 8);
    EXPECT_EQ(unicode.offsetByCodePoints(0, 6), 9);
    EXPECT_EQ(unicode.offsetByCodePoints(2, 0), 2);
    EXPECT_EQ(unicode.offsetByCodePoints(2, 1), 3);
    EXPECT_EQ(unicode.offsetByCodePoints(2, 2), 4);
    EXPECT_EQ(unicode.offsetByCodePoints(2, 3), 6);
    EXPECT_EQ(unicode.offsetByCodePoints(2, 4), 8);
    EXPECT_EQ(unicode.offsetByCodePoints(2, 5), 9);
    EXPECT_EQ(unicode.offsetByCodePoints(2, 6), 11);
    EXPECT_EQ(unicode.offsetByCodePoints(4, 0), 4);
    EXPECT_EQ(unicode.offsetByCodePoints(4, 1), 6);
    EXPECT_EQ(unicode.offsetByCodePoints(6, 0), 6);
  
    // return the length once we reach the end of the string
    EXPECT_EQ(unicode.offsetByCodePoints(0, 7), 11);
    EXPECT_EQ(unicode.offsetByCodePoints(0, 9), 11);
    EXPECT_EQ(unicode.offsetByCodePoints(2, 7), 11);
    EXPECT_EQ(unicode.offsetByCodePoints(3, 6), 11);
    EXPECT_EQ(unicode.offsetByCodePoints(4, 5), 11);
    EXPECT_EQ(unicode.offsetByCodePoints(8, 3), 11);
    EXPECT_EQ(unicode.offsetByCodePoints(12, 0), 11);
  }
  
  TEST_F(StrArrayTest, RotateCodePointWithSameFirstAndLastIsNoop) {
    HandleScope scope(thread_);
    StrArray array(&scope, runtime_->newStrArray());
    runtime_->strArrayAddASCII(thread_, array, 'H');
    runtime_->strArrayAddASCII(thread_, array, 'i');
    runtime_->strArrayAddASCII(thread_, array, '!');
    EXPECT_TRUE(isStrEqualsCStr(runtime_->strFromStrArray(array), "Hi!"));
  
    array.rotateCodePoint(1, 1);
    EXPECT_TRUE(isStrEqualsCStr(runtime_->strFromStrArray(array), "Hi!"));
  }
  
  TEST_F(StrArrayTest, RotateCodePointWithFirstBeforeLastRotatesCodePoint) {
    HandleScope scope(thread_);
    StrArray array(&scope, runtime_->newStrArray());
    runtime_->strArrayAddASCII(thread_, array, 'a');
    runtime_->strArrayAddASCII(thread_, array, 'b');
    runtime_->strArrayAddASCII(thread_, array, 'c');
    EXPECT_TRUE(isStrEqualsCStr(runtime_->strFromStrArray(array), "abc"));
  
    array.rotateCodePoint(0, 2);
    EXPECT_TRUE(isStrEqualsCStr(runtime_->strFromStrArray(array), "cab"));
  }
  
  TEST_F(StrArrayTest, RotateCodePointWithNonASCIIRotatesEntireCodePoint) {
    HandleScope scope(thread_);
    StrArray array(&scope, runtime_->newStrArray());
    Str str(&scope, runtime_->newStrFromCStr("Chopin \u00e9tude"));
    runtime_->strArrayAddStr(thread_, array, str);
  
    array.rotateCodePoint(1, 7);
    EXPECT_TRUE(
        isStrEqualsCStr(runtime_->strFromStrArray(array), "C\u00e9hopin tude"));
  }
  
  TEST_F(StrArrayTest, RotateCodePointWithNonASCIIUsesCharIndex) {
    HandleScope scope(thread_);
    StrArray array(&scope, runtime_->newStrArray());
    Str str(&scope, runtime_->newStrFromCStr("Chopin \u00e9tude"));
    runtime_->strArrayAddStr(thread_, array, str);
  
    array.rotateCodePoint(1, 10);
    EXPECT_TRUE(
        isStrEqualsCStr(runtime_->strFromStrArray(array), "Cuhopin \u00e9tde"));
  }
  
  TEST_F(LargeStrTest, CopyTo) {
    RawObject obj = runtime_->newStrFromCStr("hello world!");
    ASSERT_TRUE(obj.isLargeStr());
    RawStr str = Str::cast(obj);
  
    byte array[5];
    memset(array, 'a', ARRAYSIZE(array));
    str.copyTo(array, 0);
    EXPECT_EQ(array[0], 'a');
    EXPECT_EQ(array[1], 'a');
    EXPECT_EQ(array[2], 'a');
    EXPECT_EQ(array[3], 'a');
    EXPECT_EQ(array[4], 'a');
  
    memset(array, 'b', ARRAYSIZE(array));
    str.copyTo(array, 1);
    EXPECT_EQ(array[0], 'h');
    EXPECT_EQ(array[1], 'b');
    EXPECT_EQ(array[2], 'b');
    EXPECT_EQ(array[3], 'b');
    EXPECT_EQ(array[4], 'b');
  
    memset(array, 'c', ARRAYSIZE(array));
    str.copyTo(array, 5);
    EXPECT_EQ(array[0], 'h');
    EXPECT_EQ(array[1], 'e');
    EXPECT_EQ(array[2], 'l');
    EXPECT_EQ(array[3], 'l');
    EXPECT_EQ(array[4], 'o');
  }
  
  TEST_F(StringTest, CompareSmallStrCStrASCII) {
    HandleScope scope(thread_);
  
    Str small_ascii(&scope, runtime_->newStrFromCStr("sm"));
    ASSERT_TRUE(small_ascii.isSmallStr());
  
    // Equal
    EXPECT_EQ(small_ascii.compareCStr("sm"), 0);
  
    // Less
    EXPECT_EQ(small_ascii.compareCStr("sma"), -1);
    EXPECT_EQ(small_ascii.compareCStr("sn"), -1);
  
    // Greater
    EXPECT_EQ(small_ascii.compareCStr("s"), 1);
    EXPECT_EQ(small_ascii.compareCStr("sl"), 1);
  }
  
  TEST_F(StringTest, CompareSmallStrWithNulCStrASCII) {
    HandleScope scope(thread_);
  
    const byte data[] = {'s', '\0', 'm'};
    Str small_ascii(&scope, runtime_->newStrWithAll(data));
    ASSERT_TRUE(small_ascii.isSmallStr());
  
    // Less
    EXPECT_EQ(small_ascii.compareCStr("t"), -1);
  
    // Greater
    EXPECT_EQ(small_ascii.compareCStr("s"), 1);
    EXPECT_EQ(small_ascii.compareCStr("a\0m"), 1);
  }
  
  TEST_F(StringTest, CompareLargeStrWithNulCStrASCII) {
    HandleScope scope(thread_);
  
    const byte data[] = {'l', 'a', 'r', 'g', 'e', '\0', 's', 't'};
    Str large_ascii(&scope, runtime_->newStrWithAll(data));
    ASSERT_TRUE(large_ascii.isLargeStr());
  
    // Less
    EXPECT_EQ(large_ascii.compareCStr("largz"), -1);
  
    // Greater
    EXPECT_EQ(large_ascii.compareCStr("large"), 1);
    EXPECT_EQ(large_ascii.compareCStr("larga\0st"), 1);
  }
  
  TEST_F(StringTest, CompareLargeStrCStrASCII) {
    HandleScope scope(thread_);
  
    Str large_ascii(&scope, runtime_->newStrFromCStr("large string"));
    ASSERT_TRUE(large_ascii.isLargeStr());
  
    // Equal
    EXPECT_EQ(large_ascii.compareCStr("large string"), 0);
  
    // Less
    EXPECT_EQ(large_ascii.compareCStr("large strings"), -1);
    EXPECT_EQ(large_ascii.compareCStr("large tbigger"), -1);
  
    // Greater
    EXPECT_EQ(large_ascii.compareCStr("large strin"), 1);
    EXPECT_EQ(large_ascii.compareCStr("large smaller"), 1);
  }
  
  TEST_F(StringTest, CompareSmallStrCStrUTF8) {
    HandleScope scope(thread_);
  
    Str small_utf8(&scope, runtime_->newStrFromCStr("\xC3\x87"));
    ASSERT_TRUE(small_utf8.isSmallStr());
  
    // Equal
    EXPECT_EQ(small_utf8.compareCStr("\xC3\x87"), 0);
  
    // Less
    EXPECT_EQ(small_utf8.compareCStr("\xC3\x87s"), -1);
    EXPECT_EQ(small_utf8.compareCStr("\xC3\x88"), -1);
    EXPECT_EQ(small_utf8.compareCStr("\xC3\xA7"), -1);
  
    // Greater
    EXPECT_EQ(small_utf8.compareCStr(""), 1);
    EXPECT_EQ(small_utf8.compareCStr("\xC3\x86"), 1);
    EXPECT_EQ(small_utf8.compareCStr("\xC3\x67"), 1);
  }
  
  TEST_F(StringTest, CompareLargeStrCStrUTF8) {
    HandleScope scope(thread_);
  
    Str large_utf8(&scope, runtime_->newStrFromCStr("\xC3\x87 large"));
    ASSERT_TRUE(large_utf8.isLargeStr());
  
    // Equal
    EXPECT_EQ(large_utf8.compareCStr("\xC3\x87 large"), 0);
  
    // Less
    EXPECT_EQ(large_utf8.compareCStr("\xC3\x87 larges"), -1);
    EXPECT_EQ(large_utf8.compareCStr("\xC3\x88 large"), -1);
    EXPECT_EQ(large_utf8.compareCStr("\xC3\xA7 large"), -1);
  
    // Greater
    EXPECT_EQ(large_utf8.compareCStr("\xC3\x87"), 1);
    EXPECT_EQ(large_utf8.compareCStr("\xC3\x86 large"), 1);
    EXPECT_EQ(large_utf8.compareCStr("g large"), 1);
  }
  
  TEST_F(StringTest, CompareSmallStrCStrLatin1) {
    HandleScope scope(thread_);
  
    Str small_latin1(&scope, runtime_->newStrFromCStr("\xDC"));
    ASSERT_TRUE(small_latin1.isSmallStr());
  
    // Equal
    EXPECT_EQ(small_latin1.compareCStr("\xDC"), 0);
  
    // Less
    EXPECT_EQ(small_latin1.compareCStr("\xDCs"), -1);
    EXPECT_EQ(small_latin1.compareCStr("\xDD"), -1);
    EXPECT_EQ(small_latin1.compareCStr("\xEC"), -1);
  
    // Greater
    EXPECT_EQ(small_latin1.compareCStr(""), 1);
    EXPECT_EQ(small_latin1.compareCStr("\xDB"), 1);
    EXPECT_EQ(small_latin1.compareCStr("\xAC"), 1);
  }
  
  TEST_F(StringTest, CompareLargeStrCStrLatin1) {
    HandleScope scope(thread_);
  
    Str large_latin1(&scope, runtime_->newStrFromCStr("\xDClarge str"));
    ASSERT_TRUE(large_latin1.isLargeStr());
  
    // Equal
    EXPECT_EQ(large_latin1.compareCStr("\xDClarge str"), 0);
  
    // Less
    EXPECT_EQ(large_latin1.compareCStr("\xDClarge strs"), -1);
    EXPECT_EQ(large_latin1.compareCStr("\xDDlarge str"), -1);
    EXPECT_EQ(large_latin1.compareCStr("\xEClarge str"), -1);
  
    // Greater
    EXPECT_EQ(large_latin1.compareCStr("\xDC"), 1);
    EXPECT_EQ(large_latin1.compareCStr("\xDBlarge str"), 1);
    EXPECT_EQ(large_latin1.compareCStr("\xBClarge str"), 1);
  }
  
  TEST_F(StringTest, CopyToStartAtWithLargeStrCopiesBytes) {
    HandleScope scope(thread_);
    Str str(&scope, runtime_->newStrFromCStr("Hello world!"));
  
    byte actual0[5];
    LargeStr::cast(*str).copyToStartAt(actual0, 5, 3);
    EXPECT_EQ(std::memcmp(actual0, "lo", 2), 0);
  
    byte actual1[3];
    LargeStr::cast(*str).copyToStartAt(actual1, 3, 4);
    EXPECT_EQ(std::memcmp(actual1, "o w", 3), 0);
  
    // zero-sized copies should do nothing.
    str.copyToStartAt(nullptr, 0, 0);
    LargeStr::cast(*str).copyToStartAt(nullptr, 0, 12);
  }
  
  TEST_F(StringTest, CopyToStartAtWithSmallStrCopiesBytes) {
    HandleScope scope(thread_);
    Str str(&scope, SmallStr::fromCStr("bar"));
  
    byte actual0[3];
    str.copyToStartAt(actual0, 3, 0);
    EXPECT_EQ(std::memcmp(actual0, "bar", 3), 0);
  
    byte actual1[2];
    str.copyToStartAt(actual1, 2, 1);
    EXPECT_EQ(std::memcmp(actual1, "ar", 2), 0);
  
    // zero-sized copies should do nothing.
    str.copyToStartAt(nullptr, 0, 0);
    str.copyToStartAt(nullptr, 0, 3);
  }
  
  TEST_F(SmallStrTest, CodePointLengthWithAsciiReturnsLength) {
    HandleScope scope(thread_);
    SmallStr len0(&scope, SmallStr::fromCStr(""));
    EXPECT_EQ(len0.length(), 0);
    EXPECT_EQ(len0.codePointLength(), 0);
  
    SmallStr len1(&scope, SmallStr::fromCStr("1"));
    EXPECT_EQ(len1.length(), 1);
    EXPECT_EQ(len1.codePointLength(), 1);
  
    SmallStr len2(&scope, SmallStr::fromCStr("12"));
    EXPECT_EQ(len2.length(), 2);
    EXPECT_EQ(len2.codePointLength(), 2);
  
    SmallStr len3(&scope, SmallStr::fromCStr("123"));
    EXPECT_EQ(len3.length(), 3);
    EXPECT_EQ(len3.codePointLength(), 3);
  }
  
  TEST_F(SmallStrTest, CodePointLengthWithOneCodePoint) {
    HandleScope scope(thread_);
    SmallStr len1(&scope, SmallStr::fromCStr("\x24"));
    EXPECT_EQ(len1.length(), 1);
    EXPECT_EQ(len1.codePointLength(), 1);
  
    SmallStr len2(&scope, SmallStr::fromCStr("\xC2\xA2"));
    EXPECT_EQ(len2.length(), 2);
    EXPECT_EQ(len2.codePointLength(), 1);
  
    SmallStr len3(&scope, SmallStr::fromCStr("\xE0\xA4\xB9"));
    EXPECT_EQ(len3.length(), 3);
    EXPECT_EQ(len3.codePointLength(), 1);
  
    SmallStr len4(&scope, SmallStr::fromCStr("\xF0\x90\x8D\x88"));
    EXPECT_EQ(len4.length(), 4);
    EXPECT_EQ(len4.codePointLength(), 1);
  }
  
  TEST_F(SmallStrTest, CodePointLengthWithTwoCodePoints) {
    HandleScope scope(thread_);
    SmallStr len1(&scope, SmallStr::fromCStr("\x24\x65"));
    EXPECT_EQ(len1.length(), 2);
    EXPECT_EQ(len1.codePointLength(), 2);
  
    SmallStr len2(&scope, SmallStr::fromCStr("\xC2\xA2\xC2\xA3"));
    EXPECT_EQ(len2.length(), 4);
    EXPECT_EQ(len2.codePointLength(), 2);
  
    SmallStr len3(&scope, SmallStr::fromCStr("\xE0\xA4\xB9\xC2\xA3"));
    EXPECT_EQ(len3.length(), 5);
    EXPECT_EQ(len3.codePointLength(), 2);
  
    SmallStr len4(&scope, SmallStr::fromCStr("\xF0\x90\x8D\x88\xC2\xA3"));
    EXPECT_EQ(len4.length(), 6);
    EXPECT_EQ(len4.codePointLength(), 2);
  }
  
  TEST_F(SmallStrTest, CodePointLengthWithThreeCodePoints) {
    HandleScope scope(thread_);
    SmallStr len1(&scope, SmallStr::fromCStr("\x24\x65\x66"));
    EXPECT_EQ(len1.length(), 3);
    EXPECT_EQ(len1.codePointLength(), 3);
  
    SmallStr len2(&scope, SmallStr::fromCStr("\xC2\xA2\xC2\xA3\xC2\xA4"));
    EXPECT_EQ(len2.length(), 6);
    EXPECT_EQ(len2.codePointLength(), 3);
  
    SmallStr len3(&scope, SmallStr::fromCStr("\xE0\xA4\xB9\xC2\xA3\xC2\xA4"));
    EXPECT_EQ(len3.length(), 7);
    EXPECT_EQ(len3.codePointLength(), 3);
  
    SmallStr len4(&scope, SmallStr::fromCStr("\xF0\x90\x8D\x88\x65\xC2\xA3"));
    EXPECT_EQ(len4.length(), 7);
    EXPECT_EQ(len4.codePointLength(), 3);
  }
  
  TEST_F(SmallStrTest, CopyToCopiesBytes) {
    HandleScope scope(thread_);
    SmallStr str(&scope, SmallStr::fromCStr("AB"));
    EXPECT_EQ(str.length(), 2);
    EXPECT_EQ(str.byteAt(0), 'A');
    EXPECT_EQ(str.byteAt(1), 'B');
  
    byte array[3]{0, 0, 0};
    str.copyTo(array, 2);
    EXPECT_EQ(array[0], 'A');
    EXPECT_EQ(array[1], 'B');
    EXPECT_EQ(array[2], 0);
  }
  
  TEST_F(SmallStrTest, FromCodePointOneByte) {
    HandleScope scope(thread_);
    SmallStr str(&scope, SmallStr::fromCodePoint(0x24));
    ASSERT_EQ(str.length(), 1);
    EXPECT_EQ(str.byteAt(0), 0x24);
  }
  
  TEST_F(SmallStrTest, FromCodePointTwoByte) {
    HandleScope scope(thread_);
    SmallStr str(&scope, SmallStr::fromCodePoint(0xA2));
    ASSERT_EQ(str.length(), 2);
    EXPECT_EQ(str.byteAt(0), 0xC2);
    EXPECT_EQ(str.byteAt(1), 0xA2);
  }
  
  TEST_F(SmallStrTest, FromCodePointThreeByte) {
    HandleScope scope(thread_);
    SmallStr str1(&scope, SmallStr::fromCodePoint(0x0939));
    ASSERT_EQ(str1.length(), 3);
    EXPECT_EQ(str1.byteAt(0), 0xE0);
    EXPECT_EQ(str1.byteAt(1), 0xA4);
    EXPECT_EQ(str1.byteAt(2), 0xB9);
  
    SmallStr str2(&scope, SmallStr::fromCodePoint(0x20AC));
    ASSERT_EQ(str2.length(), 3);
    EXPECT_EQ(str2.byteAt(0), 0xE2);
    EXPECT_EQ(str2.byteAt(1), 0x82);
    EXPECT_EQ(str2.byteAt(2), 0xAC);
  }
  
  TEST_F(SmallStrTest, FromCodePointFourByte) {
    HandleScope scope(thread_);
    SmallStr str(&scope, SmallStr::fromCodePoint(0x10348));
    ASSERT_EQ(str.length(), 4);
    EXPECT_EQ(str.byteAt(0), 0xF0);
    EXPECT_EQ(str.byteAt(1), 0x90);
    EXPECT_EQ(str.byteAt(2), 0x8D);
    EXPECT_EQ(str.byteAt(3), 0x88);
  }
  
  TEST_F(SmallStrTest, IncludesLargeStrReturnsFalse) {
    HandleScope scope(thread_);
    SmallStr haystack(&scope, SmallStr::fromCStr("abcd"));
    LargeStr needle(&scope, runtime_->newStrFromCStr("abcdefgh"));
    EXPECT_FALSE(haystack.includes(*needle));
  }
  
  TEST_F(SmallStrTest, IncludesSmallStrChecksSubstr) {
    HandleScope scope(thread_);
    SmallStr haystack(&scope, SmallStr::fromCStr("abcd"));
    SmallStr empty(&scope, Str::empty());
    SmallStr needle(&scope, SmallStr::fromCStr("bc"));
    SmallStr not_needle(&scope, SmallStr::fromCStr("abcde"));
  
    EXPECT_TRUE(haystack.includes(*empty));
    EXPECT_TRUE(haystack.includes(*needle));
    EXPECT_TRUE(haystack.includes(*haystack));
    EXPECT_FALSE(haystack.includes(*not_needle));
  }
  
  TEST_F(SmallStrTest, IsASCIIReturnsTrueIfAndOnlyIfAllASCII) {
    HandleScope scope(thread_);
    SmallStr all_ascii(&scope, SmallStr::fromCStr("abc"));
    EXPECT_TRUE(all_ascii.isASCII());
  
    SmallStr some_non_ascii(&scope, SmallStr::fromCStr("a\xC2\xA3g"));
    EXPECT_FALSE(some_non_ascii.isASCII());
  }
  
  TEST_F(SmallStrTest, OccurrencesOfWithEmptyStringReturnsZero) {
    HandleScope scope(thread_);
    Str haystack(&scope, SmallStr::fromCStr("hello"));
    Str needle(&scope, runtime_->newStrFromCStr(""));
    EXPECT_EQ(haystack.occurrencesOf(*needle), 0);
  }
  
  TEST_F(SmallStrTest, OccurrencesOfWithLargeStrReturnsZero) {
    HandleScope scope(thread_);
    Str haystack(&scope, SmallStr::fromCStr("abab"));
    Str needle(&scope, runtime_->newStrFromCStr("ababababab"));
    EXPECT_EQ(haystack.occurrencesOf(*needle), 0);
  }
  
  TEST_F(SmallStrTest, OccurrencesOfWithNoOccurenceReturnsZero) {
    HandleScope scope(thread_);
    Str haystack(&scope, SmallStr::fromCStr("abab"));
    Str needle(&scope, SmallStr::fromCStr("cd"));
    EXPECT_EQ(haystack.occurrencesOf(*needle), 0);
  }
  
  TEST_F(SmallStrTest, OccurrencesOfWithSmallStrFindsOccurrenceAtSecondChar) {
    HandleScope scope(thread_);
    Str haystack(&scope, SmallStr::fromCStr("abab"));
    Str needle(&scope, SmallStr::fromCStr("ba"));
    EXPECT_EQ(haystack.occurrencesOf(*needle), 1);
  }
  
  TEST_F(SmallStrTest, OccurrencesOfWithSmallStrMultipleOccurrencesFindsAll) {
    HandleScope scope(thread_);
    Str haystack(&scope, SmallStr::fromCStr("hello"));
    Str needle(&scope, SmallStr::fromCStr("l"));
    EXPECT_EQ(haystack.occurrencesOf(*needle), 2);
  }
  
  TEST_F(StrTest, OffsetByCodePoints) {
    HandleScope scope(thread_);
  
    Str empty(&scope, Str::empty());
    EXPECT_EQ(empty.length(), 0);
    EXPECT_EQ(empty.codePointLength(), 0);
    EXPECT_EQ(empty.offsetByCodePoints(0, 1), 0);
    EXPECT_EQ(empty.offsetByCodePoints(2, 0), 0);
    EXPECT_EQ(empty.offsetByCodePoints(2, 1), 0);
  
    Str ascii(&scope, runtime_->newStrFromCStr("abcd"));
    EXPECT_EQ(ascii.length(), 4);
    EXPECT_EQ(ascii.codePointLength(), 4);
  
    // for ASCII, each code point is one byte wide
    EXPECT_EQ(ascii.offsetByCodePoints(0, 0), 0);
    EXPECT_EQ(ascii.offsetByCodePoints(0, 3), 3);
    EXPECT_EQ(ascii.offsetByCodePoints(1, 0), 1);
    EXPECT_EQ(ascii.offsetByCodePoints(2, 0), 2);
    EXPECT_EQ(ascii.offsetByCodePoints(2, 1), 3);
    EXPECT_EQ(ascii.offsetByCodePoints(3, 0), 3);
  
    // return the length once we reach the end of the string
    EXPECT_EQ(ascii.offsetByCodePoints(0, 4), 4);
    EXPECT_EQ(ascii.offsetByCodePoints(0, 5), 4);
    EXPECT_EQ(ascii.offsetByCodePoints(1, 3), 4);
    EXPECT_EQ(ascii.offsetByCodePoints(1, 4), 4);
    EXPECT_EQ(ascii.offsetByCodePoints(2, 2), 4);
    EXPECT_EQ(ascii.offsetByCodePoints(2, 3), 4);
    EXPECT_EQ(ascii.offsetByCodePoints(3, 1), 4);
    EXPECT_EQ(ascii.offsetByCodePoints(3, 2), 4);
    EXPECT_EQ(ascii.offsetByCodePoints(4, 0), 4);
    EXPECT_EQ(ascii.offsetByCodePoints(6, 0), 4);
  
    Str unicode(&scope,
                runtime_->newStrFromCStr("\xd7\x90pq\xd7\x91\xd7\x92-\xd7\x93"));
    EXPECT_EQ(unicode.length(), 11);
    EXPECT_EQ(unicode.codePointLength(), 7);
  
    // for Unicode, code points may be more than one byte wide
    EXPECT_EQ(unicode.offsetByCodePoints(0, 0), 0);
    EXPECT_EQ(unicode.offsetByCodePoints(0, 1), 2);
    EXPECT_EQ(unicode.offsetByCodePoints(0, 2), 3);
    EXPECT_EQ(unicode.offsetByCodePoints(0, 3), 4);
    EXPECT_EQ(unicode.offsetByCodePoints(0, 4), 6);
    EXPECT_EQ(unicode.offsetByCodePoints(0, 5), 8);
    EXPECT_EQ(unicode.offsetByCodePoints(0, 6), 9);
    EXPECT_EQ(unicode.offsetByCodePoints(2, 0), 2);
    EXPECT_EQ(unicode.offsetByCodePoints(2, 1), 3);
    EXPECT_EQ(unicode.offsetByCodePoints(2, 2), 4);
    EXPECT_EQ(unicode.offsetByCodePoints(2, 3), 6);
    EXPECT_EQ(unicode.offsetByCodePoints(2, 4), 8);
    EXPECT_EQ(unicode.offsetByCodePoints(2, 5), 9);
    EXPECT_EQ(unicode.offsetByCodePoints(2, 6), 11);
    EXPECT_EQ(unicode.offsetByCodePoints(4, 0), 4);
    EXPECT_EQ(unicode.offsetByCodePoints(4, 1), 6);
    EXPECT_EQ(unicode.offsetByCodePoints(6, 0), 6);
  
    // return the length once we reach the end of the string
    EXPECT_EQ(unicode.offsetByCodePoints(0, 7), 11);
    EXPECT_EQ(unicode.offsetByCodePoints(0, 9), 11);
    EXPECT_EQ(unicode.offsetByCodePoints(2, 7), 11);
    EXPECT_EQ(unicode.offsetByCodePoints(3, 6), 11);
    EXPECT_EQ(unicode.offsetByCodePoints(4, 5), 11);
    EXPECT_EQ(unicode.offsetByCodePoints(8, 3), 11);
    EXPECT_EQ(unicode.offsetByCodePoints(12, 0), 11);
  }
  
  TEST_F(LargeStrTest, CodePointLengthAscii) {
    HandleScope scope(thread_);
  
    const char* code_units = "01234567012345670";
  
    Str str(&scope, runtime_->newStrFromCStr(code_units));
    EXPECT_TRUE(str.isLargeStr());
    EXPECT_EQ(str.length(), static_cast<word>(std::strlen(code_units)));
    EXPECT_EQ(str.codePointLength(), 17);
  }
  
  TEST_F(LargeStrTest, CodePointLength) {
    HandleScope scope(thread_);
  
    const char* code_units =
        "\xd7\x99\xd7\xa9 \xd7\x9c\xd7\x99 \xd7\x94\xd7\xa8\xd7\x91\xd7\x94 "
        "\xd7\x90\xd7\x95\xd7\xaa\xd7\x99\xd7\x95\xd7\xaa "
        "\xd7\xa2\xd7\x9b\xd7\xa9\xd7\x99\xd7\x95";
  
    Str str(&scope, runtime_->newStrFromCStr(code_units));
    EXPECT_TRUE(str.isLargeStr());
    EXPECT_EQ(str.length(), static_cast<word>(std::strlen(code_units)));
    EXPECT_EQ(str.codePointLength(), 23);
  }
  
  TEST_F(LargeStrTest, IncludesLargeStrChecksSubstr) {
    HandleScope scope(thread_);
    LargeStr haystack(&scope, runtime_->newStrFromCStr("abcdefghijk"));
    LargeStr needle(&scope, runtime_->newStrFromCStr("bcdefghi"));
    LargeStr not_needle(&scope, runtime_->newStrFromCStr("aaaaaaaa"));
  
    EXPECT_TRUE(haystack.includes(*haystack));
    EXPECT_TRUE(haystack.includes(*needle));
    EXPECT_FALSE(haystack.includes(*not_needle));
  }
  
  TEST_F(LargeStrTest, IncludesSmallStrChecksSubstr) {
    HandleScope scope(thread_);
    LargeStr haystack(&scope, runtime_->newStrFromCStr("abcdefghijkl"));
    SmallStr empty(&scope, Str::empty());
    SmallStr needle(&scope, SmallStr::fromCStr("fgh"));
    SmallStr not_needle(&scope, SmallStr::fromCStr("bb"));
  
    EXPECT_TRUE(haystack.includes(*empty));
    EXPECT_TRUE(haystack.includes(*needle));
    EXPECT_FALSE(haystack.includes(*not_needle));
  
    Str chars(&scope, runtime_->newStrFromCStr(".\\[{()*+?^$|"));
    Str hash(&scope, SmallStr::fromCStr("#"));
    EXPECT_FALSE(chars.includes(*hash));
  }
  
  TEST_F(LargeStrTest, IsASCIIReturnsTrueIfAndOnlyIfAllASCII) {
    HandleScope scope(thread_);
  
    Str ascii(&scope, runtime_->newStrFromCStr("01234567012345670"));
    EXPECT_TRUE(ascii.isLargeStr());
    EXPECT_TRUE(ascii.isASCII());
  
    Str unicode(&scope, runtime_->newStrFromCStr("ascii \xd7\x99\xd7\xa9 pad"));
    EXPECT_TRUE(unicode.isLargeStr());
    EXPECT_FALSE(unicode.isASCII());
  }
  
  TEST_F(LargeStrTest, OccurrencesOfWithEmptyStringReturnsZero) {
    HandleScope scope(thread_);
    Str haystack(&scope, runtime_->newStrFromCStr("hello world"));
    Str needle(&scope, runtime_->newStrFromCStr(""));
    EXPECT_EQ(haystack.occurrencesOf(*needle), 0);
  }
  
  TEST_F(LargeStrTest, OccurrencesOfWithLargeStrFindsOccurrenceAtFirstChar) {
    HandleScope scope(thread_);
    Str haystack(&scope, runtime_->newStrFromCStr("hello world"));
    Str needle(&scope, runtime_->newStrFromCStr("hello wor"));
    EXPECT_EQ(haystack.occurrencesOf(*needle), 1);
  }
  
  TEST_F(LargeStrTest, OccurrencesOfWithLargeStrMultipleOccurrencesFindsAll) {
    HandleScope scope(thread_);
    Str haystack(&scope, runtime_->newStrFromCStr("hello world hello world"));
    Str needle(&scope, runtime_->newStrFromCStr("hello world"));
    EXPECT_EQ(haystack.occurrencesOf(*needle), 2);
  }
  
  TEST_F(LargeStrTest, OccurrencesOfWithNoOccurenceReturnsZero) {
    HandleScope scope(thread_);
    Str haystack(&scope, runtime_->newStrFromCStr("hello world"));
    Str needle(&scope, runtime_->newStrFromCStr("is not here"));
    EXPECT_EQ(haystack.occurrencesOf(*needle), 0);
  }
  
  TEST_F(LargeStrTest, OccurrencesOfWithSmallStrFindsOccurrenceAtThirdChar) {
    HandleScope scope(thread_);
    Str haystack(&scope, runtime_->newStrFromCStr("hello world"));
    Str needle(&scope, SmallStr::fromCStr("llo"));
    EXPECT_EQ(haystack.occurrencesOf(*needle), 1);
  }
  
  TEST_F(LargeStrTest, OccurrencesOfWithSmallStrMultipleOccurrencesFindsAll) {
    HandleScope scope(thread_);
    Str haystack(&scope, runtime_->newStrFromCStr("hello hello"));
    Str needle(&scope, SmallStr::fromCStr("llo"));
    EXPECT_EQ(haystack.occurrencesOf(*needle), 2);
  }
  
  TEST_F(LargeStrTest, OccurrencesOfWithSmallStrUsesCorrectEndian) {
    HandleScope scope(thread_);
    Str haystack(&scope, runtime_->newStrFromCStr("ababababab"));
    Str needle(&scope, SmallStr::fromCStr("ab"));
    EXPECT_EQ(haystack.occurrencesOf(*needle), 5);
  }
  
  TEST_F(StringTest, ReverseOffsetByCodePointsEmptyString) {
    HandleScope scope(thread_);
    Str empty(&scope, Str::empty());
  
    word i = empty.length();
    ASSERT_EQ(0, i);
  
    EXPECT_EQ(-1, empty.offsetByCodePoints(i, -1));
  }
  
  TEST_F(StringTest, ReverseOffsetByCodePointsStringLength1) {
    HandleScope scope(thread_);
  
    Str str1(&scope, runtime_->newStrFromCStr("1"));
    word len = str1.length();
    ASSERT_EQ(1, len);
  
    EXPECT_EQ(1, str1.offsetByCodePoints(len, 0));
    EXPECT_EQ(0, str1.offsetByCodePoints(len, -1));
    EXPECT_EQ(-1, str1.offsetByCodePoints(len, -2));
  }
  
  TEST_F(StringTest, ReverseOffsetByCodePointsStringLength3) {
    HandleScope scope(thread_);
  
    Str str3(&scope, runtime_->newStrFromCStr("123"));
    word len = str3.length();
    ASSERT_EQ(3, len);
  
    EXPECT_EQ(2, str3.offsetByCodePoints(len, -1));
    EXPECT_EQ(1, str3.offsetByCodePoints(len, -2));
    EXPECT_EQ(0, str3.offsetByCodePoints(len, -3));
    EXPECT_EQ(-1, str3.offsetByCodePoints(len, -4));
  
    EXPECT_EQ(1, str3.offsetByCodePoints(len - 1, -1));
    EXPECT_EQ(0, str3.offsetByCodePoints(len - 1, -2));
    EXPECT_EQ(-1, str3.offsetByCodePoints(len - 1, -3));
    EXPECT_EQ(-1, str3.offsetByCodePoints(len - 1, -4));
  
    EXPECT_EQ(0, str3.offsetByCodePoints(len - 2, -1));
    EXPECT_EQ(-1, str3.offsetByCodePoints(len - 2, -2));
    EXPECT_EQ(-1, str3.offsetByCodePoints(len - 2, -3));
    EXPECT_EQ(-1, str3.offsetByCodePoints(len - 2, -4));
  }
  
  TEST_F(StringTest, ReverseOffsetByCodePointsUnicodeStringLength5) {
    HandleScope scope(thread_);
  
    Str str5(&scope, runtime_->newStrFromCStr("\x41\xD7\x91\xD7\x92"));
    word len = str5.length();
    ASSERT_EQ(5, len);
  
    EXPECT_EQ(3, str5.offsetByCodePoints(len, -1));
    EXPECT_EQ(1, str5.offsetByCodePoints(len, -2));
    EXPECT_EQ(0, str5.offsetByCodePoints(len, -3));
    EXPECT_EQ(-1, str5.offsetByCodePoints(len, -4));
  
    EXPECT_EQ(1, str5.offsetByCodePoints(len - 2, -1));
    EXPECT_EQ(0, str5.offsetByCodePoints(len - 2, -2));
    EXPECT_EQ(-1, str5.offsetByCodePoints(len - 2, -3));
    EXPECT_EQ(-1, str5.offsetByCodePoints(len - 2, -4));
  
    EXPECT_EQ(0, str5.offsetByCodePoints(len - 4, -1));
    EXPECT_EQ(-1, str5.offsetByCodePoints(len - 4, -2));
    EXPECT_EQ(-1, str5.offsetByCodePoints(len - 4, -3));
    EXPECT_EQ(-1, str5.offsetByCodePoints(len - 4, -4));
  }
  
  TEST_F(StringTest, ToCString) {
    HandleScope scope(thread_);
  
    Str empty(&scope, Str::empty());
    char* c_empty = empty.toCStr();
    ASSERT_NE(c_empty, nullptr);
    EXPECT_STREQ(c_empty, "");
    std::free(c_empty);
  
    Str length1(&scope, runtime_->newStrFromCStr("a"));
    char* c_length1 = length1.toCStr();
    ASSERT_NE(c_length1, nullptr);
    EXPECT_STREQ(c_length1, "a");
    std::free(c_length1);
  
    Str length2(&scope, runtime_->newStrFromCStr("ab"));
    char* c_length2 = length2.toCStr();
    ASSERT_NE(c_length2, nullptr);
    EXPECT_STREQ(c_length2, "ab");
    std::free(c_length2);
  
    Str length10(&scope, runtime_->newStrFromCStr("1234567890"));
    char* c_length10 = length10.toCStr();
    ASSERT_NE(c_length10, nullptr);
    EXPECT_STREQ(c_length10, "1234567890");
    std::free(c_length10);
  
    Str nulchar(&scope, runtime_->newStrFromCStr("wx\0yz"));
    char* c_nulchar = nulchar.toCStr();
    ASSERT_NE(c_nulchar, nullptr);
    EXPECT_STREQ(c_nulchar, "wx");
    std::free(c_nulchar);
  }
  
  TEST_F(StringTest, CompareSmallStr) {
    HandleScope scope(thread_);
  
    Str small(&scope, runtime_->newStrFromCStr("foo"));
    EXPECT_TRUE(small.isSmallStr());
  
    EXPECT_TRUE(small.equalsCStr("foo"));
    // This apparently stupid test is in response to a bug where we assumed
    // that the c-string passed to SmallStr::equalsCStr would always
    // be small itself.
    EXPECT_FALSE(small.equalsCStr("123456789"));
  }
  
  TEST_F(StringTest, CompareWithUnicode) {
    HandleScope scope(thread_);
    Str small(&scope, runtime_->newStrFromCStr(u8"hello\u2028"));
    EXPECT_TRUE(small.equalsCStr("hello\u2028"));
  }
  
  TEST_F(ValueCellTest, SetPlaceholderRendersIsPlaceholderToReturnTrue) {
    HandleScope scope(thread_);
    ValueCell value_cell(&scope, runtime_->newValueCell());
    ASSERT_FALSE(value_cell.isPlaceholder());
    value_cell.makePlaceholder();
    EXPECT_TRUE(value_cell.isPlaceholder());
  }
  
  TEST_F(WeakRefTest, EnqueueAndDequeue) {
    HandleScope scope(thread_);
    RawObject list = NoneType::object();
    Object o0(&scope, runtime_->newList());
    Object o1(&scope, runtime_->newList());
    Object o2(&scope, runtime_->newList());
  
    WeakRef::enqueue(runtime_->newWeakRef(thread_, o0), &list);
    WeakRef::enqueue(runtime_->newWeakRef(thread_, o1), &list);
    WeakRef::enqueue(runtime_->newWeakRef(thread_, o2), &list);
  
    WeakRef weak(&scope, WeakRef::dequeue(&list));
    EXPECT_EQ(weak.referent(), o0);
  
    weak = WeakRef::dequeue(&list);
    EXPECT_EQ(weak.referent(), o1);
  
    weak = WeakRef::dequeue(&list);
    EXPECT_EQ(weak.referent(), o2);
  
    EXPECT_EQ(list, NoneType::object());
  }
  
  TEST_F(WeakRefTest, SpliceQueue) {
    HandleScope scope(thread_);
    RawObject list1 = NoneType::object();
    RawObject list2 = NoneType::object();
    EXPECT_EQ(WeakRef::spliceQueue(list1, list2), NoneType::object());
  
    Object none(&scope, NoneType::object());
    RawObject list3 = runtime_->newWeakRef(thread_, none);
    WeakRef::cast(list3).setLink(list3);
    EXPECT_EQ(WeakRef::spliceQueue(list1, list3), list3);
    EXPECT_EQ(WeakRef::spliceQueue(list3, list2), list3);
  
    Object o0(&scope, runtime_->newDict());
    Object o1(&scope, runtime_->newDict());
    Object o2(&scope, runtime_->newDict());
    Object o3(&scope, runtime_->newDict());
  
    WeakRef::enqueue(runtime_->newWeakRef(thread_, o0), &list1);
    WeakRef::enqueue(runtime_->newWeakRef(thread_, o2), &list2);
    WeakRef::enqueue(runtime_->newWeakRef(thread_, o1), &list1);
    WeakRef::enqueue(runtime_->newWeakRef(thread_, o3), &list2);
  
    RawObject list = WeakRef::spliceQueue(list1, list2);
    WeakRef weak(&scope, WeakRef::dequeue(&list));
    EXPECT_EQ(weak.referent(), o0);
  
    weak = WeakRef::dequeue(&list);
    EXPECT_EQ(weak.referent(), o1);
  
    weak = WeakRef::dequeue(&list);
    EXPECT_EQ(weak.referent(), o2);
  
    weak = WeakRef::dequeue(&list);
    EXPECT_EQ(weak.referent(), o3);
  
    EXPECT_EQ(list, NoneType::object());
  }
  
  TEST_F(ListTest, ReplaceFromWithReplacesElementsStartingAtZero) {
    HandleScope scope(thread_);
    List dst(&scope, runtime_->newList());
    Tuple dst_tuple(&scope, runtime_->newMutableTuple(5));
    dst.setItems(*dst_tuple);
    dst.setNumItems(5);
    List src(&scope, listFromRange(0, 5));
    dst.replaceFromWith(0, *src, 2);
    ASSERT_EQ(dst.numItems(), 5);
    EXPECT_TRUE(isIntEqualsWord(dst.at(0), 0));
    EXPECT_TRUE(isIntEqualsWord(dst.at(1), 1));
    EXPECT_EQ(dst.at(2), SmallInt::fromWord(0));
    EXPECT_EQ(dst.at(3), SmallInt::fromWord(0));
    EXPECT_EQ(dst.at(4), SmallInt::fromWord(0));
  }
  
  TEST_F(ListTest, ReplaceFromWithReplacesElementsStartingInMiddle) {
    HandleScope scope(thread_);
    List dst(&scope, runtime_->newList());
    Tuple dst_tuple(&scope, runtime_->newMutableTuple(5));
    dst.setItems(*dst_tuple);
    dst.setNumItems(5);
    List src(&scope, listFromRange(0, 5));
    dst.replaceFromWith(1, *src, 2);
    ASSERT_EQ(dst.numItems(), 5);
    EXPECT_EQ(dst.at(0), SmallInt::fromWord(0));
    EXPECT_TRUE(isIntEqualsWord(dst.at(1), 0));
    EXPECT_TRUE(isIntEqualsWord(dst.at(2), 1));
    EXPECT_EQ(dst.at(3), SmallInt::fromWord(0));
    EXPECT_EQ(dst.at(4), SmallInt::fromWord(0));
  }
  
  TEST_F(ListTest, ReplaceFromWithCopiesZeroElements) {
    HandleScope scope(thread_);
    List dst(&scope, runtime_->newList());
    Tuple dst_tuple(&scope, runtime_->newMutableTuple(5));
    dst.setItems(*dst_tuple);
    dst.setNumItems(5);
    List src(&scope, listFromRange(0, 5));
    dst.replaceFromWith(0, *src, 0);
    ASSERT_EQ(dst.numItems(), 5);
    EXPECT_EQ(dst.at(0), SmallInt::fromWord(0));
    EXPECT_EQ(dst.at(1), SmallInt::fromWord(0));
    EXPECT_EQ(dst.at(2), SmallInt::fromWord(0));
    EXPECT_EQ(dst.at(3), SmallInt::fromWord(0));
    EXPECT_EQ(dst.at(4), SmallInt::fromWord(0));
  }
  
  TEST_F(ListTest, ReplaceFromWithCopiesEveryElementFromSrc) {
    HandleScope scope(thread_);
    List dst(&scope, runtime_->newList());
    Tuple dst_tuple(&scope, runtime_->newMutableTuple(5));
    dst.setItems(*dst_tuple);
    dst.setNumItems(5);
    List src(&scope, listFromRange(0, 5));
    dst.replaceFromWith(0, *src, 5);
    ASSERT_EQ(dst.numItems(), 5);
    EXPECT_TRUE(isIntEqualsWord(dst.at(0), 0));
    EXPECT_TRUE(isIntEqualsWord(dst.at(1), 1));
    EXPECT_TRUE(isIntEqualsWord(dst.at(2), 2));
    EXPECT_TRUE(isIntEqualsWord(dst.at(3), 3));
    EXPECT_TRUE(isIntEqualsWord(dst.at(4), 4));
  }
  
  TEST_F(ListTest, ReplaceFromWithStartAtReplacesElementsStartingAtSrcStart) {
    HandleScope scope(thread_);
    List dst(&scope, runtime_->newList());
    Tuple dst_tuple(&scope, runtime_->newMutableTuple(5));
    dst.setItems(*dst_tuple);
    dst.setNumItems(5);
    List src(&scope, listFromRange(0, 5));
    dst.replaceFromWithStartAt(0, *src, 2, 2);
    ASSERT_EQ(dst.numItems(), 5);
    EXPECT_TRUE(isIntEqualsWord(dst.at(0), 2));
    EXPECT_TRUE(isIntEqualsWord(dst.at(1), 3));
    EXPECT_EQ(dst.at(2), SmallInt::fromWord(0));
    EXPECT_EQ(dst.at(3), SmallInt::fromWord(0));
    EXPECT_EQ(dst.at(4), SmallInt::fromWord(0));
  }
  
  TEST_F(ListTest, ReplaceFromWithStartAtWithSelfNoop) {
    HandleScope scope(thread_);
    List dst(&scope, listFromRange(0, 5));
    dst.replaceFromWithStartAt(0, *dst, 2, 0);
    EXPECT_PYLIST_EQ(dst, {0, 1, 2, 3, 4});
  }
  
  TEST_F(ListTest, ReplaceFromWithStartAtWithSelfBackward) {
    HandleScope scope(thread_);
    List dst(&scope, listFromRange(0, 5));
    dst.replaceFromWithStartAt(0, *dst, 2, 2);
    EXPECT_PYLIST_EQ(dst, {2, 3, 2, 3, 4});
  }
  
  TEST_F(ListTest, ReplaceFromWithStartAtWithSelfForward) {
    HandleScope scope(thread_);
    List dst(&scope, listFromRange(0, 5));
    dst.replaceFromWithStartAt(2, *dst, 2, 0);
    EXPECT_PYLIST_EQ(dst, {0, 1, 0, 1, 4});
  }
  
  TEST_F(ListTest, SwapSwapsElementsAtIndices) {
    HandleScope scope(thread_);
    List src(&scope, listFromRange(0, 5));
    EXPECT_PYLIST_EQ(src, {0, 1, 2, 3, 4});
    src.swap(1, 3);
    EXPECT_PYLIST_EQ(src, {0, 3, 2, 1, 4});
  }
  
  TEST_F(MutableTupleTest, NoneFillTupleFillsTupleWithNone) {
    HandleScope scope(thread_);
    MutableTuple tuple(&scope, runtime_->newMutableTuple(3));
    tuple.atPut(0, SmallInt::fromWord(0));
    tuple.atPut(1, SmallInt::fromWord(1));
    tuple.atPut(2, SmallInt::fromWord(2));
    tuple.fill(NoneType::object());
    EXPECT_EQ(tuple.at(0), NoneType::object());
    EXPECT_EQ(tuple.at(1), NoneType::object());
    EXPECT_EQ(tuple.at(2), NoneType::object());
  }
  
  TEST_F(MutableTupleTest, ReplaceFromWithReplacesElementsStartingAtZero) {
    HandleScope scope(thread_);
    MutableTuple dst(&scope, runtime_->newMutableTuple(5));
    List src(&scope, listFromRange(0, 5));
    Tuple src_items(&scope, src.items());
    dst.replaceFromWith(0, *src_items, 2);
    EXPECT_TRUE(isIntEqualsWord(dst.at(0), 0));
    EXPECT_TRUE(isIntEqualsWord(dst.at(1), 1));
    EXPECT_EQ(dst.at(2), SmallInt::fromWord(0));
    EXPECT_EQ(dst.at(3), SmallInt::fromWord(0));
    EXPECT_EQ(dst.at(4), SmallInt::fromWord(0));
  }
  
  TEST_F(MutableTupleTest, ReplaceFromWithReplacesElementsStartingInMiddle) {
    HandleScope scope(thread_);
    MutableTuple dst(&scope, runtime_->newMutableTuple(5));
    List src(&scope, listFromRange(0, 5));
    Tuple src_items(&scope, src.items());
    dst.replaceFromWith(1, *src_items, 2);
    EXPECT_EQ(dst.at(0), SmallInt::fromWord(0));
    EXPECT_TRUE(isIntEqualsWord(dst.at(1), 0));
    EXPECT_TRUE(isIntEqualsWord(dst.at(2), 1));
    EXPECT_EQ(dst.at(3), SmallInt::fromWord(0));
    EXPECT_EQ(dst.at(4), SmallInt::fromWord(0));
  }
  
  TEST_F(MutableTupleTest, ReplaceFromWithCopiesZeroElements) {
    HandleScope scope(thread_);
    MutableTuple dst(&scope, runtime_->newMutableTuple(5));
    List src(&scope, listFromRange(0, 5));
    Tuple src_items(&scope, src.items());
    dst.replaceFromWith(0, *src_items, 0);
    EXPECT_EQ(dst.at(0), SmallInt::fromWord(0));
    EXPECT_EQ(dst.at(1), SmallInt::fromWord(0));
    EXPECT_EQ(dst.at(2), SmallInt::fromWord(0));
    EXPECT_EQ(dst.at(3), SmallInt::fromWord(0));
    EXPECT_EQ(dst.at(4), SmallInt::fromWord(0));
  }
  
  TEST_F(MutableTupleTest, ReplaceFromWithCopiesEveryElementFromSrc) {
    HandleScope scope(thread_);
    MutableTuple dst(&scope, runtime_->newMutableTuple(5));
    List src(&scope, listFromRange(0, 5));
    Tuple src_items(&scope, src.items());
    dst.replaceFromWith(0, *src_items, 5);
    EXPECT_TRUE(isIntEqualsWord(dst.at(0), 0));
    EXPECT_TRUE(isIntEqualsWord(dst.at(1), 1));
    EXPECT_TRUE(isIntEqualsWord(dst.at(2), 2));
    EXPECT_TRUE(isIntEqualsWord(dst.at(3), 3));
    EXPECT_TRUE(isIntEqualsWord(dst.at(4), 4));
  }
  
  TEST_F(MutableTupleTest, ReplaceFromWithStartAtWithSelfNoop) {
    HandleScope scope(thread_);
    List dst_list(&scope, listFromRange(0, 5));
    MutableTuple dst(&scope, dst_list.items());
    dst.replaceFromWithStartAt(0, *dst, 2, 0);
    EXPECT_PYLIST_EQ(dst_list, {0, 1, 2, 3, 4});
  }
  
  TEST_F(MutableTupleTest, ReplaceFromWithStartAtWithSelfBackward) {
    HandleScope scope(thread_);
    List dst_list(&scope, listFromRange(0, 5));
    MutableTuple dst(&scope, dst_list.items());
    dst.replaceFromWithStartAt(0, *dst, 2, 2);
    EXPECT_PYLIST_EQ(dst_list, {2, 3, 2, 3, 4});
  }
  
  TEST_F(MutableTupleTest, ReplaceFromWithStartAtWithSelfForward) {
    HandleScope scope(thread_);
    List dst_list(&scope, listFromRange(0, 5));
    MutableTuple dst(&scope, dst_list.items());
    dst.replaceFromWithStartAt(2, *dst, 2, 0);
    EXPECT_PYLIST_EQ(dst_list, {0, 1, 0, 1, 4});
  }
  
  TEST_F(MutableTupleTest,
         ReplaceFromWithStartAtReplacesElementsStartingAtSrcStart) {
    HandleScope scope(thread_);
    MutableTuple dst(&scope, runtime_->newMutableTuple(5));
    List src_list(&scope, listFromRange(0, 5));
    Tuple src(&scope, src_list.items());
    dst.replaceFromWithStartAt(0, *src, 2, 2);
    EXPECT_TRUE(isIntEqualsWord(dst.at(0), 2));
    EXPECT_TRUE(isIntEqualsWord(dst.at(1), 3));
    EXPECT_EQ(dst.at(2), SmallInt::fromWord(0));
    EXPECT_EQ(dst.at(3), SmallInt::fromWord(0));
    EXPECT_EQ(dst.at(4), SmallInt::fromWord(0));
  }
  
  TEST_F(MutableTupleTest, SwapSwapsElementsAtIndices) {
    HandleScope scope(thread_);
    List src_list(&scope, listFromRange(0, 5));
    MutableTuple src(&scope, src_list.items());
    EXPECT_PYLIST_EQ(src_list, {0, 1, 2, 3, 4});
    src.swap(1, 3);
    EXPECT_PYLIST_EQ(src_list, {0, 3, 2, 1, 4});
  }
  
  TEST(ErrorTest, ErrorIsError) {
    EXPECT_TRUE(Error::error().isError());
  
    EXPECT_TRUE(Error::exception().isError());
    EXPECT_TRUE(Error::exception().isErrorException());
  
    EXPECT_TRUE(Error::notFound().isError());
    EXPECT_TRUE(Error::notFound().isErrorNotFound());
  
    EXPECT_TRUE(Error::noMoreItems().isError());
    EXPECT_TRUE(Error::noMoreItems().isErrorNoMoreItems());
  
    EXPECT_TRUE(Error::outOfMemory().isError());
    EXPECT_TRUE(Error::outOfMemory().isErrorOutOfMemory());
  
    EXPECT_TRUE(Error::outOfBounds().isError());
    EXPECT_TRUE(Error::outOfBounds().isErrorOutOfBounds());
  }
  
  TEST(ErrorTest, ErrorHasCorrectKind) {
    EXPECT_EQ(Error::error().kind(), ErrorKind::kNone);
    EXPECT_EQ(Error::exception().kind(), ErrorKind::kException);
    EXPECT_EQ(Error::notFound().kind(), ErrorKind::kNotFound);
    EXPECT_EQ(Error::noMoreItems().kind(), ErrorKind::kNoMoreItems);
    EXPECT_EQ(Error::outOfMemory().kind(), ErrorKind::kOutOfMemory);
    EXPECT_EQ(Error::outOfBounds().kind(), ErrorKind::kOutOfBounds);
  }
  
  }  // namespace testing
  }  // namespace py
  