// Copyright 2017 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifdef V8_INTL_SUPPORT #include "src/objects/intl-objects.h" #include "src/objects/js-break-iterator.h" #include "src/objects/js-collator-inl.h" #include "src/objects/js-date-time-format.h" #include "src/objects/js-list-format.h" #include "src/objects/js-number-format.h" #include "src/objects/js-plural-rules.h" #include "src/objects/js-relative-time-format.h" #include "src/objects/js-segmenter.h" #include "src/objects/lookup.h" #include "src/objects/objects-inl.h" #include "src/objects/option-utils.h" #include "test/cctest/cctest.h" #include "unicode/coll.h" namespace v8 { namespace internal { // This operator overloading enables CHECK_EQ to be used with // std::vector bool operator==(const NumberFormatSpan& lhs, const NumberFormatSpan& rhs) { return memcmp(&lhs, &rhs, sizeof(lhs)) == 0; } template std::basic_ostream<_CharT, _Traits>& operator<<( std::basic_ostream<_CharT, _Traits>& self, const NumberFormatSpan& part) { return self << "{" << part.field_id << "," << part.begin_pos << "," << part.end_pos << "}"; } void test_flatten_regions_to_parts( const std::vector& regions, const std::vector& expected_parts) { std::vector mutable_regions = regions; std::vector parts = FlattenRegionsToParts(&mutable_regions); CHECK_EQ(expected_parts, parts); } TEST(FlattenRegionsToParts) { test_flatten_regions_to_parts( std::vector{ NumberFormatSpan(-1, 0, 10), NumberFormatSpan(1, 2, 8), NumberFormatSpan(2, 2, 4), NumberFormatSpan(3, 6, 8), }, std::vector{ NumberFormatSpan(-1, 0, 2), NumberFormatSpan(2, 2, 4), NumberFormatSpan(1, 4, 6), NumberFormatSpan(3, 6, 8), NumberFormatSpan(-1, 8, 10), }); test_flatten_regions_to_parts( std::vector{ NumberFormatSpan(0, 0, 1), }, std::vector{ NumberFormatSpan(0, 0, 1), }); test_flatten_regions_to_parts( std::vector{ NumberFormatSpan(-1, 0, 1), NumberFormatSpan(0, 0, 1), }, std::vector{ NumberFormatSpan(0, 0, 1), }); test_flatten_regions_to_parts( std::vector{ NumberFormatSpan(0, 0, 1), NumberFormatSpan(-1, 0, 1), }, std::vector{ NumberFormatSpan(0, 0, 1), }); test_flatten_regions_to_parts( std::vector{ NumberFormatSpan(-1, 0, 10), NumberFormatSpan(1, 0, 1), NumberFormatSpan(2, 0, 2), NumberFormatSpan(3, 0, 3), NumberFormatSpan(4, 0, 4), NumberFormatSpan(5, 0, 5), NumberFormatSpan(15, 5, 10), NumberFormatSpan(16, 6, 10), NumberFormatSpan(17, 7, 10), NumberFormatSpan(18, 8, 10), NumberFormatSpan(19, 9, 10), }, std::vector{ NumberFormatSpan(1, 0, 1), NumberFormatSpan(2, 1, 2), NumberFormatSpan(3, 2, 3), NumberFormatSpan(4, 3, 4), NumberFormatSpan(5, 4, 5), NumberFormatSpan(15, 5, 6), NumberFormatSpan(16, 6, 7), NumberFormatSpan(17, 7, 8), NumberFormatSpan(18, 8, 9), NumberFormatSpan(19, 9, 10), }); // : 4 // : 22 33 3 // : 11111 22 // input regions: 0000000 111 // : ------------ // output parts: 0221340--231 test_flatten_regions_to_parts( std::vector{ NumberFormatSpan(-1, 0, 12), NumberFormatSpan(0, 0, 7), NumberFormatSpan(1, 9, 12), NumberFormatSpan(1, 1, 6), NumberFormatSpan(2, 9, 11), NumberFormatSpan(2, 1, 3), NumberFormatSpan(3, 10, 11), NumberFormatSpan(3, 4, 6), NumberFormatSpan(4, 5, 6), }, std::vector{ NumberFormatSpan(0, 0, 1), NumberFormatSpan(2, 1, 3), NumberFormatSpan(1, 3, 4), NumberFormatSpan(3, 4, 5), NumberFormatSpan(4, 5, 6), NumberFormatSpan(0, 6, 7), NumberFormatSpan(-1, 7, 9), NumberFormatSpan(2, 9, 10), NumberFormatSpan(3, 10, 11), NumberFormatSpan(1, 11, 12), }); } TEST(GetStringOption) { LocalContext env; Isolate* isolate = CcTest::i_isolate(); v8::Isolate* v8_isolate = env->GetIsolate(); v8::HandleScope handle_scope(v8_isolate); Handle options = isolate->factory()->NewJSObjectWithNullProto(); { // No value found std::unique_ptr result = nullptr; Maybe found = GetStringOption(isolate, options, "foo", std::vector{}, "service", &result); CHECK(!found.FromJust()); CHECK_NULL(result); } Handle key = isolate->factory()->NewStringFromAsciiChecked("foo"); LookupIterator it(isolate, options, key); CHECK(Object::SetProperty(&it, Handle(Smi::FromInt(42), isolate), StoreOrigin::kMaybeKeyed, Just(ShouldThrow::kThrowOnError)) .FromJust()); { // Value found std::unique_ptr result = nullptr; Maybe found = GetStringOption(isolate, options, "foo", std::vector{}, "service", &result); CHECK(found.FromJust()); CHECK_NOT_NULL(result); CHECK_EQ(0, strcmp("42", result.get())); } { // No expected value in values array std::unique_ptr result = nullptr; Maybe found = GetStringOption(isolate, options, "foo", std::vector{"bar"}, "service", &result); CHECK(isolate->has_pending_exception()); CHECK(found.IsNothing()); CHECK_NULL(result); isolate->clear_pending_exception(); } { // Expected value in values array std::unique_ptr result = nullptr; Maybe found = GetStringOption(isolate, options, "foo", std::vector{"42"}, "service", &result); CHECK(found.FromJust()); CHECK_NOT_NULL(result); CHECK_EQ(0, strcmp("42", result.get())); } } TEST(GetBoolOption) { LocalContext env; Isolate* isolate = CcTest::i_isolate(); v8::Isolate* v8_isolate = env->GetIsolate(); v8::HandleScope handle_scope(v8_isolate); Handle options = isolate->factory()->NewJSObjectWithNullProto(); { bool result = false; Maybe found = GetBoolOption(isolate, options, "foo", "service", &result); CHECK(!found.FromJust()); CHECK(!result); } Handle key = isolate->factory()->NewStringFromAsciiChecked("foo"); { LookupIterator it(isolate, options, key); Handle false_value = handle(i::ReadOnlyRoots(isolate).false_value(), isolate); Object::SetProperty(isolate, options, key, false_value, StoreOrigin::kMaybeKeyed, Just(ShouldThrow::kThrowOnError)) .Assert(); bool result = false; Maybe found = GetBoolOption(isolate, options, "foo", "service", &result); CHECK(found.FromJust()); CHECK(!result); } { LookupIterator it(isolate, options, key); Handle true_value = handle(i::ReadOnlyRoots(isolate).true_value(), isolate); Object::SetProperty(isolate, options, key, true_value, StoreOrigin::kMaybeKeyed, Just(ShouldThrow::kThrowOnError)) .Assert(); bool result = false; Maybe found = GetBoolOption(isolate, options, "foo", "service", &result); CHECK(found.FromJust()); CHECK(result); } } TEST(GetAvailableLocales) { std::set locales; locales = JSV8BreakIterator::GetAvailableLocales(); CHECK(locales.count("en-US")); CHECK(!locales.count("abcdefg")); locales = JSCollator::GetAvailableLocales(); CHECK(locales.count("en-US")); locales = JSDateTimeFormat::GetAvailableLocales(); CHECK(locales.count("en-US")); locales = JSListFormat::GetAvailableLocales(); CHECK(locales.count("en-US")); locales = JSNumberFormat::GetAvailableLocales(); CHECK(locales.count("en-US")); locales = JSPluralRules::GetAvailableLocales(); CHECK(locales.count("en")); locales = JSRelativeTimeFormat::GetAvailableLocales(); CHECK(locales.count("en-US")); locales = JSSegmenter::GetAvailableLocales(); CHECK(locales.count("en-US")); CHECK(!locales.count("abcdefg")); } // Tests that the LocaleCompare fast path and generic path return the same // comparison results for all ASCII strings. TEST(StringLocaleCompareFastPath) { LocalContext env; Isolate* isolate = CcTest::i_isolate(); HandleScope handle_scope(isolate); // We compare all single-char strings of printable ASCII characters. std::vector> ascii_strings; for (int c = 0; c <= 0x7F; c++) { if (!std::isprint(c)) continue; ascii_strings.push_back( isolate->factory()->LookupSingleCharacterStringFromCode(c)); } Handle collator_constructor = Handle( JSFunction::cast( isolate->context().native_context().intl_collator_function()), isolate); Handle constructor_map = JSFunction::GetDerivedMap(isolate, collator_constructor, collator_constructor) .ToHandleChecked(); Handle options(ReadOnlyRoots(isolate).undefined_value(), isolate); static const char* const kMethodName = "StringLocaleCompareFastPath"; // For all fast locales, exhaustively compare within the printable ASCII // range. const std::set& locales = JSCollator::GetAvailableLocales(); for (const std::string& locale : locales) { Handle locale_string = isolate->factory()->NewStringFromAsciiChecked(locale.c_str()); if (Intl::CompareStringsOptionsFor(isolate->AsLocalIsolate(), locale_string, options) != Intl::CompareStringsOptions::kTryFastPath) { continue; } Handle collator = JSCollator::New(isolate, constructor_map, locale_string, options, kMethodName) .ToHandleChecked(); for (size_t i = 0; i < ascii_strings.size(); i++) { Handle lhs = ascii_strings[i]; for (size_t j = i + 1; j < ascii_strings.size(); j++) { Handle rhs = ascii_strings[j]; CHECK_EQ( Intl::CompareStrings(isolate, *collator->icu_collator().raw(), lhs, rhs, Intl::CompareStringsOptions::kNone), Intl::CompareStrings(isolate, *collator->icu_collator().raw(), lhs, rhs, Intl::CompareStringsOptions::kTryFastPath)); } } } } } // namespace internal } // namespace v8 #endif // V8_INTL_SUPPORT