Index: test/unittests/value-serializer-unittest.cc |
diff --git a/test/unittests/value-serializer-unittest.cc b/test/unittests/value-serializer-unittest.cc |
index 696f90466a6e383abb3571b23a08ce3ca21a7706..c48961fb6cddbb61ac4c2fa464af29ab925e0062 100644 |
--- a/test/unittests/value-serializer-unittest.cc |
+++ b/test/unittests/value-serializer-unittest.cc |
@@ -4,6 +4,9 @@ |
#include "src/value-serializer.h" |
+#include <algorithm> |
+#include <string> |
+ |
#include "include/v8.h" |
#include "src/api.h" |
#include "src/base/build_config.h" |
@@ -29,22 +32,27 @@ class ValueSerializerTest : public TestWithIsolate { |
template <typename InputFunctor, typename OutputFunctor> |
void RoundTripTest(const InputFunctor& input_functor, |
const OutputFunctor& output_functor) { |
- std::vector<uint8_t> data; |
- { |
- Context::Scope scope(serialization_context()); |
- TryCatch try_catch(isolate()); |
- // TODO(jbroman): Use the public API once it exists. |
- Local<Value> input_value = input_functor(); |
- i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate()); |
- i::HandleScope handle_scope(internal_isolate); |
- i::ValueSerializer serializer; |
- serializer.WriteHeader(); |
- ASSERT_TRUE(serializer.WriteObject(Utils::OpenHandle(*input_value)) |
- .FromMaybe(false)); |
- ASSERT_FALSE(try_catch.HasCaught()); |
- data = serializer.ReleaseBuffer(); |
- } |
- DecodeTest(data, output_functor); |
+ EncodeTest(input_functor, |
+ [this, &output_functor](const std::vector<uint8_t>& data) { |
+ DecodeTest(data, output_functor); |
+ }); |
+ } |
+ |
+ template <typename InputFunctor, typename EncodedDataFunctor> |
+ void EncodeTest(const InputFunctor& input_functor, |
+ const EncodedDataFunctor& encoded_data_functor) { |
+ Context::Scope scope(serialization_context()); |
+ TryCatch try_catch(isolate()); |
+ // TODO(jbroman): Use the public API once it exists. |
+ Local<Value> input_value = input_functor(); |
+ i::Isolate* internal_isolate = reinterpret_cast<i::Isolate*>(isolate()); |
+ i::HandleScope handle_scope(internal_isolate); |
+ i::ValueSerializer serializer; |
+ serializer.WriteHeader(); |
+ ASSERT_TRUE(serializer.WriteObject(Utils::OpenHandle(*input_value)) |
+ .FromMaybe(false)); |
+ ASSERT_FALSE(try_catch.HasCaught()); |
+ encoded_data_functor(serializer.ReleaseBuffer()); |
} |
template <typename OutputFunctor> |
@@ -106,6 +114,11 @@ class ValueSerializerTest : public TestWithIsolate { |
.ToLocalChecked(); |
} |
+ static std::string Utf8Value(Local<Value> value) { |
+ String::Utf8Value utf8(value); |
+ return std::string(*utf8, utf8.length()); |
+ } |
+ |
private: |
Local<Context> serialization_context_; |
Local<Context> deserialization_context_; |
@@ -248,5 +261,132 @@ TEST_F(ValueSerializerTest, DecodeNumber) { |
// TODO(jbroman): Equivalent test for big-endian machines. |
} |
+// String constants (in UTF-8) used for string encoding tests. |
+static const char kHelloString[] = "Hello"; |
+static const char kQuebecString[] = "\x51\x75\xC3\xA9\x62\x65\x63"; |
+static const char kEmojiString[] = "\xF0\x9F\x91\x8A"; |
+ |
+TEST_F(ValueSerializerTest, RoundTripString) { |
+ RoundTripTest([this]() { return String::Empty(isolate()); }, |
+ [](Local<Value> value) { |
+ ASSERT_TRUE(value->IsString()); |
+ EXPECT_EQ(0, String::Cast(*value)->Length()); |
+ }); |
+ // Inside ASCII. |
+ RoundTripTest([this]() { return StringFromUtf8(kHelloString); }, |
+ [](Local<Value> value) { |
+ ASSERT_TRUE(value->IsString()); |
+ EXPECT_EQ(5, String::Cast(*value)->Length()); |
+ EXPECT_EQ(kHelloString, Utf8Value(value)); |
+ }); |
+ // Inside Latin-1 (i.e. one-byte string), but not ASCII. |
+ RoundTripTest([this]() { return StringFromUtf8(kQuebecString); }, |
+ [](Local<Value> value) { |
+ ASSERT_TRUE(value->IsString()); |
+ EXPECT_EQ(6, String::Cast(*value)->Length()); |
+ EXPECT_EQ(kQuebecString, Utf8Value(value)); |
+ }); |
+ // An emoji (decodes to two 16-bit chars). |
+ RoundTripTest([this]() { return StringFromUtf8(kEmojiString); }, |
+ [](Local<Value> value) { |
+ ASSERT_TRUE(value->IsString()); |
+ EXPECT_EQ(2, String::Cast(*value)->Length()); |
+ EXPECT_EQ(kEmojiString, Utf8Value(value)); |
+ }); |
+} |
+ |
+TEST_F(ValueSerializerTest, DecodeString) { |
+ // Decoding the strings above from UTF-8. |
+ DecodeTest({0xff, 0x09, 0x53, 0x00}, |
+ [](Local<Value> value) { |
+ ASSERT_TRUE(value->IsString()); |
+ EXPECT_EQ(0, String::Cast(*value)->Length()); |
+ }); |
+ DecodeTest({0xff, 0x09, 0x53, 0x05, 'H', 'e', 'l', 'l', 'o'}, |
+ [](Local<Value> value) { |
+ ASSERT_TRUE(value->IsString()); |
+ EXPECT_EQ(5, String::Cast(*value)->Length()); |
+ EXPECT_EQ(kHelloString, Utf8Value(value)); |
+ }); |
+ DecodeTest({0xff, 0x09, 0x53, 0x07, 'Q', 'u', 0xc3, 0xa9, 'b', 'e', 'c'}, |
+ [](Local<Value> value) { |
+ ASSERT_TRUE(value->IsString()); |
+ EXPECT_EQ(6, String::Cast(*value)->Length()); |
+ EXPECT_EQ(kQuebecString, Utf8Value(value)); |
+ }); |
+ DecodeTest({0xff, 0x09, 0x53, 0x04, 0xf0, 0x9f, 0x91, 0x8a}, |
+ [](Local<Value> value) { |
+ ASSERT_TRUE(value->IsString()); |
+ EXPECT_EQ(2, String::Cast(*value)->Length()); |
+ EXPECT_EQ(kEmojiString, Utf8Value(value)); |
+ }); |
+ |
+// And from two-byte strings (endianness dependent). |
+#if defined(V8_TARGET_LITTLE_ENDIAN) |
+ DecodeTest({0xff, 0x09, 0x63, 0x00}, |
+ [](Local<Value> value) { |
+ ASSERT_TRUE(value->IsString()); |
+ EXPECT_EQ(0, String::Cast(*value)->Length()); |
+ }); |
+ DecodeTest({0xff, 0x09, 0x63, 0x0a, 'H', '\0', 'e', '\0', 'l', '\0', 'l', |
+ '\0', 'o', '\0'}, |
+ [](Local<Value> value) { |
+ ASSERT_TRUE(value->IsString()); |
+ EXPECT_EQ(5, String::Cast(*value)->Length()); |
+ EXPECT_EQ(kHelloString, Utf8Value(value)); |
+ }); |
+ DecodeTest({0xff, 0x09, 0x63, 0x0c, 'Q', '\0', 'u', '\0', 0xe9, '\0', 'b', |
+ '\0', 'e', '\0', 'c', '\0'}, |
+ [](Local<Value> value) { |
+ ASSERT_TRUE(value->IsString()); |
+ EXPECT_EQ(6, String::Cast(*value)->Length()); |
+ EXPECT_EQ(kQuebecString, Utf8Value(value)); |
+ }); |
+ DecodeTest({0xff, 0x09, 0x63, 0x04, 0x3d, 0xd8, 0x4a, 0xdc}, |
+ [](Local<Value> value) { |
+ ASSERT_TRUE(value->IsString()); |
+ EXPECT_EQ(2, String::Cast(*value)->Length()); |
+ EXPECT_EQ(kEmojiString, Utf8Value(value)); |
+ }); |
+#endif |
+ // TODO(jbroman): The same for big-endian systems. |
+} |
+ |
+TEST_F(ValueSerializerTest, DecodeInvalidString) { |
+ // UTF-8 string with too few bytes available. |
+ InvalidDecodeTest({0xff, 0x09, 0x53, 0x10, 'v', '8'}); |
+#if defined(V8_TARGET_LITTLE_ENDIAN) |
+ // Two-byte string with too few bytes available. |
+ InvalidDecodeTest({0xff, 0x09, 0x63, 0x10, 'v', '\0', '8', '\0'}); |
+ // Two-byte string with an odd byte length. |
+ InvalidDecodeTest({0xff, 0x09, 0x63, 0x03, 'v', '\0', '8'}); |
+#endif |
+ // TODO(jbroman): The same for big-endian systems. |
+} |
+ |
+TEST_F(ValueSerializerTest, EncodeTwoByteStringUsesPadding) { |
+ // As long as the output has a version that Blink expects to be able to read, |
+ // we must respect its alignment requirements. It requires that two-byte |
+ // characters be aligned. |
+ EncodeTest( |
+ [this]() { |
+ // We need a string whose length will take two bytes to encode, so that |
+ // a padding byte is needed to keep the characters aligned. The string |
+ // must also have a two-byte character, so that it gets the two-byte |
+ // encoding. |
+ std::string string(200, ' '); |
+ string += kEmojiString; |
+ return StringFromUtf8(string.c_str()); |
+ }, |
+ [](const std::vector<uint8_t>& data) { |
+ // This is a sufficient but not necessary condition to be aligned. |
+ // Note that the third byte (0x00) is padding. |
+ const uint8_t expected_prefix[] = {0xff, 0x09, 0x00, 0x63, 0x94, 0x03}; |
+ ASSERT_GT(data.size(), sizeof(expected_prefix) / sizeof(uint8_t)); |
+ EXPECT_TRUE(std::equal(std::begin(expected_prefix), |
+ std::end(expected_prefix), data.begin())); |
+ }); |
+} |
+ |
} // namespace |
} // namespace v8 |