TestKey.cpp (14236B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "gtest/gtest.h" 8 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject, JS::NewArrayObject 9 #include "js/ArrayBuffer.h" 10 #include "js/PropertyAndElement.h" // JS_GetElement, JS_SetElement 11 #include "js/RootingAPI.h" 12 #include "js/String.h" 13 #include "js/TypeDecls.h" 14 #include "js/Value.h" 15 #include "mozilla/IntegerRange.h" 16 #include "mozilla/dom/ScriptSettings.h" 17 #include "mozilla/dom/SimpleGlobalObject.h" 18 #include "mozilla/dom/indexedDB/Key.h" 19 20 // TODO: This PrintTo overload is defined in dom/media/gtest/TestGroupId.cpp. 21 // However, it is not used, probably because of 22 // https://stackoverflow.com/a/36941270 23 void PrintTo(const nsString& value, std::ostream* os); 24 25 using namespace mozilla; 26 using namespace mozilla::dom::indexedDB; 27 using JS::Rooted; 28 29 // DOM_IndexedDB_Key_Ctor tests test the construction of a Key, and check the 30 // properties of the constructed key with the const methods afterwards. The 31 // tested ctors include the default ctor, which constructs an unset key, and the 32 // ctors that accepts an encoded buffer, which is then decoded using the 33 // Key::To* method corresponding to its type. 34 // 35 // So far, only some cases are tested: 36 // - scalar binary 37 // -- empty 38 // -- with 1-byte encoded representation 39 // - scalar string 40 // -- empty 41 // -- with 1-byte encoded representation 42 // 43 // TODO More test cases should be added, including 44 // - empty (?) 45 // - scalar binary 46 // -- containing 0 byte(s) 47 // -- with 2-byte encoded representation 48 // - scalar string 49 // -- with 2-byte and 3-byte encoded representation 50 // - scalar number 51 // - scalar date 52 // - arrays, incl. nested arrays, with various combinations of contained types 53 54 TEST(DOM_IndexedDB_Key, Ctor_Default) 55 { 56 auto key = Key{}; 57 58 EXPECT_TRUE(key.IsUnset()); 59 } 60 61 // TODO does such a helper function already exist? 62 template <size_t N> 63 static auto BufferAsCString(const uint8_t (&aBuffer)[N]) { 64 return nsCString{reinterpret_cast<const char*>( 65 static_cast<std::decay_t<const uint8_t[]>>(aBuffer)), 66 N}; 67 } 68 69 static void ExpectKeyIsBinary(const Key& aKey) { 70 EXPECT_FALSE(aKey.IsUnset()); 71 72 EXPECT_FALSE(aKey.IsArray()); 73 EXPECT_TRUE(aKey.IsBinary()); 74 EXPECT_FALSE(aKey.IsDate()); 75 EXPECT_FALSE(aKey.IsFloat()); 76 EXPECT_FALSE(aKey.IsString()); 77 } 78 79 static void ExpectKeyIsString(const Key& aKey) { 80 EXPECT_FALSE(aKey.IsUnset()); 81 82 EXPECT_FALSE(aKey.IsArray()); 83 EXPECT_FALSE(aKey.IsBinary()); 84 EXPECT_FALSE(aKey.IsDate()); 85 EXPECT_FALSE(aKey.IsFloat()); 86 EXPECT_TRUE(aKey.IsString()); 87 } 88 89 static void ExpectKeyIsArray(const Key& aKey) { 90 EXPECT_FALSE(aKey.IsUnset()); 91 92 EXPECT_TRUE(aKey.IsArray()); 93 EXPECT_FALSE(aKey.IsBinary()); 94 EXPECT_FALSE(aKey.IsDate()); 95 EXPECT_FALSE(aKey.IsFloat()); 96 EXPECT_FALSE(aKey.IsString()); 97 } 98 99 static JSObject* ExpectArrayBufferObject(const JS::Value& aValue) { 100 EXPECT_TRUE(aValue.isObject()); 101 auto& object = aValue.toObject(); 102 EXPECT_TRUE(JS::IsArrayBufferObject(&object)); 103 return &object; 104 } 105 106 static JSObject* ExpectArrayObject(JSContext* const aContext, 107 JS::Handle<JS::Value> aValue) { 108 EXPECT_TRUE(aValue.isObject()); 109 bool rv; 110 EXPECT_TRUE(JS::IsArrayObject(aContext, aValue, &rv)); 111 EXPECT_TRUE(rv); 112 return &aValue.toObject(); 113 } 114 115 static void CheckArrayBuffer(const nsCString& aExpected, 116 const JS::Value& aActual) { 117 auto obj = ExpectArrayBufferObject(aActual); 118 size_t length; 119 bool isSharedMemory; 120 uint8_t* data; 121 JS::GetArrayBufferLengthAndData(obj, &length, &isSharedMemory, &data); 122 123 EXPECT_EQ(aExpected.Length(), length); 124 EXPECT_EQ(0, memcmp(aExpected.get(), data, length)); 125 } 126 127 static void CheckString(JSContext* const aContext, const nsString& aExpected, 128 JS::Handle<JS::Value> aActual) { 129 EXPECT_TRUE(aActual.isString()); 130 int32_t rv; 131 EXPECT_TRUE(JS_CompareStrings(aContext, 132 JS_NewUCStringCopyZ(aContext, aExpected.get()), 133 aActual.toString(), &rv)); 134 EXPECT_EQ(0, rv); 135 } 136 137 namespace { 138 // This is modeled after dom/base/test/gtest/TestContentUtils.cpp 139 struct AutoTestJSContext { 140 AutoTestJSContext() 141 : mGlobalObject( 142 mozilla::dom::RootingCx(), 143 mozilla::dom::SimpleGlobalObject::Create( 144 mozilla::dom::SimpleGlobalObject::GlobalType::BindingDetail)) { 145 EXPECT_TRUE(mJsAPI.Init(mGlobalObject)); 146 mContext = mJsAPI.cx(); 147 } 148 149 operator JSContext*() const { return mContext; } 150 151 private: 152 Rooted<JSObject*> mGlobalObject; 153 mozilla::dom::AutoJSAPI mJsAPI; 154 JSContext* mContext; 155 }; 156 157 // The following classes serve as base classes for the parametrized tests below. 158 // The name of each class reflects the parameter type. 159 160 class TestWithParam_CString_ArrayBuffer_Pair 161 : public ::testing::TestWithParam<std::pair<nsCString, nsLiteralCString>> { 162 }; 163 164 class TestWithParam_CString_String_Pair 165 : public ::testing::TestWithParam<std::pair<nsCString, nsLiteralString>> {}; 166 167 class TestWithParam_LiteralString 168 : public ::testing::TestWithParam<nsLiteralString> {}; 169 170 class TestWithParam_StringArray 171 : public ::testing::TestWithParam<std::vector<nsString>> {}; 172 173 class TestWithParam_ArrayBufferArray 174 : public ::testing::TestWithParam<std::vector<nsCString>> {}; 175 176 } // namespace 177 178 TEST_P(TestWithParam_CString_ArrayBuffer_Pair, Ctor_EncodedBinary) { 179 const auto key = Key{GetParam().first}; 180 181 ExpectKeyIsBinary(key); 182 183 AutoTestJSContext context; 184 185 Rooted<JS::Value> rv(context); 186 EXPECT_EQ(NS_OK, key.ToJSVal(context, &rv)); 187 188 CheckArrayBuffer(GetParam().second, rv); 189 } 190 191 static const uint8_t zeroLengthBinaryEncodedBuffer[] = {Key::eBinary}; 192 static const uint8_t nonZeroLengthBinaryEncodedBuffer[] = {Key::eBinary, 193 'a' + 1, 'b' + 1}; 194 INSTANTIATE_TEST_SUITE_P( 195 DOM_IndexedDB_Key, TestWithParam_CString_ArrayBuffer_Pair, 196 ::testing::Values( 197 std::make_pair(BufferAsCString(zeroLengthBinaryEncodedBuffer), ""_ns), 198 std::make_pair(BufferAsCString(nonZeroLengthBinaryEncodedBuffer), 199 "ab"_ns))); 200 201 TEST_P(TestWithParam_CString_String_Pair, Ctor_EncodedString) { 202 const auto key = Key{GetParam().first}; 203 204 ExpectKeyIsString(key); 205 206 EXPECT_EQ(GetParam().second, key.ToString()); 207 } 208 209 static const uint8_t zeroLengthStringEncodedBuffer[] = {Key::eString}; 210 static const uint8_t nonZeroLengthStringEncodedBuffer[] = {Key::eString, 211 'a' + 1, 'b' + 1}; 212 213 INSTANTIATE_TEST_SUITE_P( 214 DOM_IndexedDB_Key, TestWithParam_CString_String_Pair, 215 ::testing::Values( 216 std::make_pair(BufferAsCString(zeroLengthStringEncodedBuffer), u""_ns), 217 std::make_pair(BufferAsCString(nonZeroLengthStringEncodedBuffer), 218 u"ab"_ns))); 219 220 TEST_P(TestWithParam_LiteralString, SetFromString) { 221 auto key = Key{}; 222 const auto result = key.SetFromString(GetParam()); 223 EXPECT_TRUE(result.isOk()); 224 225 ExpectKeyIsString(key); 226 227 EXPECT_EQ(GetParam(), key.ToString()); 228 } 229 230 INSTANTIATE_TEST_SUITE_P(DOM_IndexedDB_Key, TestWithParam_LiteralString, 231 ::testing::Values(u""_ns, u"abc"_ns, u"\u007f"_ns, 232 u"\u0080"_ns, u"\u1fff"_ns, 233 u"\u7fff"_ns, u"\u8000"_ns, 234 u"\uffff"_ns)); 235 236 static JS::Value CreateArrayBufferValue(JSContext* const aContext, 237 const size_t aSize, char* const aData) { 238 mozilla::UniquePtr<void, JS::FreePolicy> ptr{aData}; 239 Rooted<JSObject*> arrayBuffer{aContext, JS::NewArrayBufferWithContents( 240 aContext, aSize, std::move(ptr))}; 241 EXPECT_TRUE(arrayBuffer); 242 return JS::ObjectValue(*arrayBuffer); 243 } 244 245 // This tests calling SetFromJSVal with an ArrayBuffer scalar of length 0. 246 // TODO Probably there should be more test cases for SetFromJSVal with other 247 // ArrayBuffer scalars, which convert this into a parametrized test as well. 248 TEST(DOM_IndexedDB_Key, SetFromJSVal_ZeroLengthArrayBuffer) 249 { 250 AutoTestJSContext context; 251 252 auto key = Key{}; 253 Rooted<JS::Value> arrayBuffer(context, 254 CreateArrayBufferValue(context, 0, nullptr)); 255 const auto result = key.SetFromJSVal(context, arrayBuffer); 256 EXPECT_TRUE(result.isOk()); 257 258 ExpectKeyIsBinary(key); 259 260 Rooted<JS::Value> rv2(context); 261 EXPECT_EQ(NS_OK, key.ToJSVal(context, &rv2)); 262 263 CheckArrayBuffer(""_ns, rv2); 264 } 265 266 template <typename CheckElement> 267 static void CheckArray(JSContext* const context, 268 JS::Handle<JS::Value> arrayValue, 269 const size_t expectedLength, 270 const CheckElement& checkElement) { 271 Rooted<JSObject*> actualArray(context, 272 ExpectArrayObject(context, arrayValue)); 273 274 uint32_t actualLength; 275 EXPECT_TRUE(JS::GetArrayLength(context, actualArray, &actualLength)); 276 EXPECT_EQ(expectedLength, actualLength); 277 for (size_t i = 0; i < expectedLength; ++i) { 278 Rooted<JS::Value> element(static_cast<JSContext*>(context)); 279 EXPECT_TRUE(JS_GetElement(context, actualArray, i, &element)); 280 281 checkElement(i, element); 282 } 283 } 284 285 static JS::Value CreateArrayBufferArray( 286 JSContext* const context, const std::vector<nsCString>& elements) { 287 Rooted<JSObject*> arrayObject(context, 288 JS::NewArrayObject(context, elements.size())); 289 EXPECT_TRUE(arrayObject); 290 291 Rooted<JS::Value> arrayBuffer(context); 292 for (size_t i = 0; i < elements.size(); ++i) { 293 // TODO strdup only works if the element is actually 0-terminated 294 arrayBuffer = CreateArrayBufferValue( 295 context, elements[i].Length(), 296 elements[i].Length() ? strdup(elements[i].get()) : nullptr); 297 EXPECT_TRUE(JS_SetElement(context, arrayObject, i, arrayBuffer)); 298 } 299 300 return JS::ObjectValue(*arrayObject); 301 } 302 303 TEST_P(TestWithParam_ArrayBufferArray, SetFromJSVal) { 304 const auto& elements = GetParam(); 305 306 AutoTestJSContext context; 307 Rooted<JS::Value> arrayValue(context); 308 arrayValue = CreateArrayBufferArray(context, elements); 309 310 auto key = Key{}; 311 const auto result = key.SetFromJSVal(context, arrayValue); 312 EXPECT_TRUE(result.isOk()); 313 314 ExpectKeyIsArray(key); 315 316 Rooted<JS::Value> rv2(context); 317 EXPECT_EQ(NS_OK, key.ToJSVal(context, &rv2)); 318 319 CheckArray(context, rv2, elements.size(), 320 [&elements](const size_t i, const JS::HandleValue& element) { 321 CheckArrayBuffer(elements[i], element); 322 }); 323 } 324 325 const uint8_t element2[] = "foo"; 326 INSTANTIATE_TEST_SUITE_P( 327 DOM_IndexedDB_Key, TestWithParam_ArrayBufferArray, 328 testing::Values(std::vector<nsCString>{}, std::vector<nsCString>{""_ns}, 329 std::vector<nsCString>{""_ns, BufferAsCString(element2)})); 330 331 static JS::Value CreateStringValue(JSContext* const context, 332 const nsString& string) { 333 JSString* str = JS_NewUCStringCopyZ(context, string.get()); 334 EXPECT_TRUE(str); 335 return JS::StringValue(str); 336 } 337 338 static JS::Value CreateStringArray(JSContext* const context, 339 const std::vector<nsString>& elements) { 340 Rooted<JSObject*> array(context, 341 JS::NewArrayObject(context, elements.size())); 342 EXPECT_TRUE(array); 343 344 for (size_t i = 0; i < elements.size(); ++i) { 345 Rooted<JS::Value> string(context, CreateStringValue(context, elements[i])); 346 EXPECT_TRUE(JS_SetElement(context, array, i, string)); 347 } 348 349 return JS::ObjectValue(*array); 350 } 351 352 TEST_P(TestWithParam_StringArray, SetFromJSVal) { 353 const auto& elements = GetParam(); 354 355 AutoTestJSContext context; 356 Rooted<JS::Value> arrayValue(context, CreateStringArray(context, elements)); 357 358 auto key = Key{}; 359 const auto result = key.SetFromJSVal(context, arrayValue); 360 EXPECT_TRUE(result.isOk()); 361 362 ExpectKeyIsArray(key); 363 364 Rooted<JS::Value> rv2(context); 365 EXPECT_EQ(NS_OK, key.ToJSVal(context, &rv2)); 366 367 CheckArray( 368 context, rv2, elements.size(), 369 [&elements, &context](const size_t i, JS::Handle<JS::Value> element) { 370 CheckString(context, elements[i], element); 371 }); 372 } 373 374 INSTANTIATE_TEST_SUITE_P( 375 DOM_IndexedDB_Key, TestWithParam_StringArray, 376 testing::Values(std::vector<nsString>{u""_ns, u"abc\u0080\u1fff"_ns}, 377 std::vector<nsString>{u"abc\u0080\u1fff"_ns, 378 u"abc\u0080\u1fff"_ns})); 379 380 TEST(DOM_IndexedDB_Key, CompareKeys_NonZeroLengthArrayBuffer) 381 { 382 AutoTestJSContext context; 383 const char buf[] = "abc\x80"; 384 385 auto first = Key{}; 386 Rooted<JS::Value> arrayBuffer1( 387 context, CreateArrayBufferValue(context, sizeof buf, strdup(buf))); 388 const auto result1 = first.SetFromJSVal(context, arrayBuffer1); 389 EXPECT_TRUE(result1.isOk()); 390 391 auto second = Key{}; 392 Rooted<JS::Value> arrayBuffer2( 393 context, CreateArrayBufferValue(context, sizeof buf, strdup(buf))); 394 const auto result2 = second.SetFromJSVal(context, arrayBuffer2); 395 EXPECT_TRUE(result2.isOk()); 396 397 EXPECT_EQ(0, Key::CompareKeys(first, second)); 398 } 399 400 constexpr auto kTestLocale = "e"_ns; 401 402 TEST(DOM_IndexedDB_Key, ToLocaleAwareKey_Empty) 403 { 404 const auto input = Key{}; 405 406 auto res = input.ToLocaleAwareKey(kTestLocale); 407 EXPECT_TRUE(res.isOk()); 408 409 EXPECT_TRUE(res.inspect().IsUnset()); 410 } 411 412 TEST(DOM_IndexedDB_Key, ToLocaleAwareKey_Bug_1641598) 413 { 414 const auto buffer = [] { 415 nsCString res; 416 // This is the encoded representation produced by the test case from bug 417 // 1641598. 418 res.AppendLiteral("\x90\x01\x01\x01\x01\x00\x40"); 419 for (const size_t unused : IntegerRange<size_t>(256)) { 420 (void)unused; 421 res.AppendLiteral("\x01\x01\x80\x03\x43"); 422 } 423 return res; 424 }(); 425 const auto input = Key{buffer}; 426 427 auto res = input.ToLocaleAwareKey(kTestLocale); 428 EXPECT_TRUE(res.isOk()); 429 430 EXPECT_EQ(input, res.inspect()); 431 }