/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ResourceParser.h" #include #include #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" #include "io/StringStream.h" #include "test/Test.h" #include "xml/XmlPullParser.h" using ::aapt::io::StringInputStream; using ::aapt::test::StrValueEq; using ::aapt::test::ValueEq; using ::android::ConfigDescription; using ::android::Res_value; using ::android::ResTable_map; using ::android::StringPiece; using ::testing::Eq; using ::testing::IsEmpty; using ::testing::IsNull; using ::testing::NotNull; using ::testing::Pointee; using ::testing::SizeIs; using ::testing::StrEq; namespace aapt { constexpr const char* kXmlPreamble = "\n"; TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) { std::unique_ptr context = test::ContextBuilder().Build(); ResourceTable table; ResourceParser parser(context->GetDiagnostics(), &table, Source{"test"}, {}); std::string input = kXmlPreamble; input += R"()"; StringInputStream in(input); xml::XmlPullParser xml_parser(&in); ASSERT_FALSE(parser.Parse(&xml_parser)); } class ResourceParserTest : public ::testing::Test { public: void SetUp() override { context_ = test::ContextBuilder().Build(); } ::testing::AssertionResult TestParse(const StringPiece& str) { return TestParse(str, ConfigDescription{}); } ::testing::AssertionResult TestParse(const StringPiece& str, const ConfigDescription& config) { ResourceParserOptions parserOptions; ResourceParser parser(context_->GetDiagnostics(), &table_, Source{"test"}, config, parserOptions); std::string input = kXmlPreamble; input += "\n"; input.append(str.data(), str.size()); input += "\n"; StringInputStream in(input); xml::XmlPullParser xmlParser(&in); if (parser.Parse(&xmlParser)) { return ::testing::AssertionSuccess(); } return ::testing::AssertionFailure(); } protected: ResourceTable table_; std::unique_ptr context_; }; TEST_F(ResourceParserTest, ParseQuotedString) { ASSERT_TRUE(TestParse(R"( " hey there " )")); String* str = test::GetValue(&table_, "string/foo"); ASSERT_THAT(str, NotNull()); EXPECT_THAT(*str, StrValueEq(" hey there ")); EXPECT_THAT(str->untranslatable_sections, IsEmpty()); ASSERT_TRUE(TestParse(R"(Isn\'t it cool?)")); str = test::GetValue(&table_, "string/bar"); ASSERT_THAT(str, NotNull()); EXPECT_THAT(*str, StrValueEq("Isn't it cool?")); ASSERT_TRUE(TestParse(R"("Isn't it cool?")")); str = test::GetValue(&table_, "string/baz"); ASSERT_THAT(str, NotNull()); EXPECT_THAT(*str, StrValueEq("Isn't it cool?")); } TEST_F(ResourceParserTest, ParseEscapedString) { ASSERT_TRUE(TestParse(R"(\?123)")); String* str = test::GetValue(&table_, "string/foo"); ASSERT_THAT(str, NotNull()); EXPECT_THAT(*str, StrValueEq("?123")); EXPECT_THAT(str->untranslatable_sections, IsEmpty()); ASSERT_TRUE(TestParse(R"(This isn\’t a bad string)")); str = test::GetValue(&table_, "string/bar"); ASSERT_THAT(str, NotNull()); EXPECT_THAT(*str, StrValueEq("This isn’t a bad string")); } TEST_F(ResourceParserTest, ParseFormattedString) { ASSERT_FALSE(TestParse(R"(%d %s)")); ASSERT_TRUE(TestParse(R"(%1$d %2$s)")); } TEST_F(ResourceParserTest, ParseStyledString) { // Use a surrogate pair unicode point so that we can verify that the span // indices use UTF-16 length and not UTF-8 length. std::string input = "This is my aunt\u2019s fickle string"; ASSERT_TRUE(TestParse(input)); StyledString* str = test::GetValue(&table_, "string/foo"); ASSERT_THAT(str, NotNull()); EXPECT_THAT(str->value->value, StrEq("This is my aunt\u2019s fickle string")); EXPECT_THAT(str->value->spans, SizeIs(2)); EXPECT_THAT(str->untranslatable_sections, IsEmpty()); EXPECT_THAT(*str->value->spans[0].name, StrEq("b")); EXPECT_THAT(str->value->spans[0].first_char, Eq(18u)); EXPECT_THAT(str->value->spans[0].last_char, Eq(30u)); EXPECT_THAT(*str->value->spans[1].name, StrEq("small")); EXPECT_THAT(str->value->spans[1].first_char, Eq(25u)); EXPECT_THAT(str->value->spans[1].last_char, Eq(30u)); } TEST_F(ResourceParserTest, ParseStringWithWhitespace) { ASSERT_TRUE(TestParse(R"( This is what I think )")); String* str = test::GetValue(&table_, "string/foo"); ASSERT_THAT(str, NotNull()); EXPECT_THAT(*str->value, StrEq("This is what I think")); EXPECT_THAT(str->untranslatable_sections, IsEmpty()); ASSERT_TRUE(TestParse(R"(" This is what I think ")")); str = test::GetValue(&table_, "string/foo2"); ASSERT_THAT(str, NotNull()); EXPECT_THAT(*str, StrValueEq(" This is what I think ")); } TEST_F(ResourceParserTest, ParseStringTruncateASCII) { // Tuncate leading and trailing whitespace EXPECT_TRUE(TestParse(R"( Hello )")); String* str = test::GetValue(&table_, "string/foo"); ASSERT_THAT(str, NotNull()); EXPECT_THAT(*str->value, StrEq("Hello")); EXPECT_THAT(str->untranslatable_sections, IsEmpty()); // AAPT does not truncate unicode whitespace EXPECT_TRUE(TestParse(R"(\u0020\Hello\u0020)")); str = test::GetValue(&table_, "string/foo2"); ASSERT_THAT(str, NotNull()); EXPECT_THAT(*str->value, StrEq(" Hello ")); EXPECT_THAT(str->untranslatable_sections, IsEmpty()); // Preserve non-ASCII whitespace including extended ASCII characters EXPECT_TRUE(TestParse(R"( Hello World )")); str = test::GetValue(&table_, "string/foo3"); ASSERT_THAT(str, NotNull()); EXPECT_THAT(*str->value, StrEq("\xC2\xA0Hello\xE2\x80\xAFWorld\xC2\xA0")); EXPECT_THAT(str->untranslatable_sections, IsEmpty()); EXPECT_TRUE(TestParse(R"(2005年6月1日)")); str = test::GetValue(&table_, "string/foo4"); ASSERT_THAT(str, NotNull()); EXPECT_THAT(*str->value, StrEq("2005年6月1日")); EXPECT_THAT(str->untranslatable_sections, IsEmpty()); } TEST_F(ResourceParserTest, ParseStyledStringWithWhitespace) { std::string input = R"( My favorite string )"; ASSERT_TRUE(TestParse(input)); StyledString* str = test::GetValue(&table_, "string/foo"); ASSERT_THAT(str, NotNull()); EXPECT_THAT(str->value->value, StrEq(" My favorite string ")); EXPECT_THAT(str->untranslatable_sections, IsEmpty()); ASSERT_THAT(str->value->spans, SizeIs(2u)); EXPECT_THAT(*str->value->spans[0].name, StrEq("b")); EXPECT_THAT(str->value->spans[0].first_char, Eq(1u)); EXPECT_THAT(str->value->spans[0].last_char, Eq(21u)); EXPECT_THAT(*str->value->spans[1].name, StrEq("i")); EXPECT_THAT(str->value->spans[1].first_char, Eq(5u)); EXPECT_THAT(str->value->spans[1].last_char, Eq(13u)); } TEST_F(ResourceParserTest, ParseStringTranslatableAttribute) { // If there is no translate attribute the default is 'true' EXPECT_TRUE(TestParse(R"(Translate)")); String* str = test::GetValue(&table_, "string/foo1"); ASSERT_THAT(str, NotNull()); ASSERT_TRUE(str->IsTranslatable()); // Explicit 'true' translate attribute EXPECT_TRUE(TestParse(R"(Translate)")); str = test::GetValue(&table_, "string/foo2"); ASSERT_THAT(str, NotNull()); ASSERT_TRUE(str->IsTranslatable()); // Explicit 'false' translate attribute EXPECT_TRUE(TestParse(R"(Do not translate)")); str = test::GetValue(&table_, "string/foo3"); ASSERT_THAT(str, NotNull()); ASSERT_FALSE(str->IsTranslatable()); // Invalid value for the translate attribute, should be boolean ('true' or 'false') EXPECT_FALSE(TestParse(R"(Translate)")); } TEST_F(ResourceParserTest, IgnoreXliffTagsOtherThanG) { std::string input = R"( There are no apples)"; ASSERT_TRUE(TestParse(input)); String* str = test::GetValue(&table_, "string/foo"); ASSERT_THAT(str, NotNull()); EXPECT_THAT(*str, StrValueEq("There are no apples")); EXPECT_THAT(str->untranslatable_sections, IsEmpty()); } TEST_F(ResourceParserTest, NestedXliffGTagsAreIllegal) { std::string input = R"( Do not translate this)"; EXPECT_FALSE(TestParse(input)); } TEST_F(ResourceParserTest, RecordUntranslateableXliffSectionsInString) { std::string input = R"( There are %1$d apples)"; ASSERT_TRUE(TestParse(input)); String* str = test::GetValue(&table_, "string/foo"); ASSERT_THAT(str, NotNull()); EXPECT_THAT(*str, StrValueEq("There are %1$d apples")); ASSERT_THAT(str->untranslatable_sections, SizeIs(1)); EXPECT_THAT(str->untranslatable_sections[0].start, Eq(10u)); EXPECT_THAT(str->untranslatable_sections[0].end, Eq(14u)); } TEST_F(ResourceParserTest, RecordUntranslateableXliffSectionsInStyledString) { std::string input = R"( There are %1$d apples)"; ASSERT_TRUE(TestParse(input)); StyledString* str = test::GetValue(&table_, "string/foo"); ASSERT_THAT(str, NotNull()); EXPECT_THAT(str->value->value, Eq(" There are %1$d apples")); ASSERT_THAT(str->untranslatable_sections, SizeIs(1)); EXPECT_THAT(str->untranslatable_sections[0].start, Eq(11u)); EXPECT_THAT(str->untranslatable_sections[0].end, Eq(15u)); ASSERT_THAT(str->value->spans, SizeIs(1u)); EXPECT_THAT(*str->value->spans[0].name, StrEq("b")); EXPECT_THAT(str->value->spans[0].first_char, Eq(11u)); EXPECT_THAT(str->value->spans[0].last_char, Eq(14u)); } TEST_F(ResourceParserTest, ParseNull) { std::string input = R"(@null)"; ASSERT_TRUE(TestParse(input)); // The Android runtime treats a value of android::Res_value::TYPE_NULL as // a non-existing value, and this causes problems in styles when trying to // resolve an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE // with a data value of 0. Reference* null_ref = test::GetValue(&table_, "integer/foo"); ASSERT_THAT(null_ref, NotNull()); EXPECT_FALSE(null_ref->name); EXPECT_FALSE(null_ref->id); EXPECT_THAT(null_ref->reference_type, Eq(Reference::Type::kResource)); } TEST_F(ResourceParserTest, ParseEmpty) { std::string input = R"(@empty)"; ASSERT_TRUE(TestParse(input)); BinaryPrimitive* integer = test::GetValue(&table_, "integer/foo"); ASSERT_THAT(integer, NotNull()); EXPECT_THAT(integer->value.dataType, Eq(Res_value::TYPE_NULL)); EXPECT_THAT(integer->value.data, Eq(Res_value::DATA_NULL_EMPTY)); } TEST_F(ResourceParserTest, ParseAttr) { std::string input = R"( )"; ASSERT_TRUE(TestParse(input)); Attribute* attr = test::GetValue(&table_, "attr/foo"); ASSERT_THAT(attr, NotNull()); EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_STRING)); attr = test::GetValue(&table_, "attr/bar"); ASSERT_THAT(attr, NotNull()); EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_ANY)); } // Old AAPT allowed attributes to be defined under different configurations, but ultimately // stored them with the default configuration. Check that we have the same behavior. TEST_F(ResourceParserTest, ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) { const ConfigDescription watch_config = test::ParseConfigOrDie("watch"); std::string input = R"( )"; ASSERT_TRUE(TestParse(input, watch_config)); EXPECT_THAT(test::GetValueForConfig(&table_, "attr/foo", watch_config), IsNull()); EXPECT_THAT(test::GetValueForConfig(&table_, "attr/baz", watch_config), IsNull()); EXPECT_THAT(test::GetValueForConfig(&table_, "styleable/bar", watch_config), IsNull()); EXPECT_THAT(test::GetValue(&table_, "attr/foo"), NotNull()); EXPECT_THAT(test::GetValue(&table_, "attr/baz"), NotNull()); EXPECT_THAT(test::GetValue(&table_, "styleable/bar"), NotNull()); } TEST_F(ResourceParserTest, ParseAttrWithMinMax) { std::string input = R"()"; ASSERT_TRUE(TestParse(input)); Attribute* attr = test::GetValue(&table_, "attr/foo"); ASSERT_THAT(attr, NotNull()); EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_INTEGER)); EXPECT_THAT(attr->min_int, Eq(10)); EXPECT_THAT(attr->max_int, Eq(23)); } TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) { ASSERT_FALSE(TestParse(R"()")); } TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) { std::string input = R"( )"; ASSERT_TRUE(TestParse(input)); Attribute* attr = test::GetValue(&table_, "attr/foo"); ASSERT_THAT(attr, NotNull()); EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_STRING)); } TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) { std::string input = R"( )"; ASSERT_TRUE(TestParse(input)); Attribute* attr = test::GetValue(&table_, "attr/foo"); ASSERT_THAT(attr, NotNull()); EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_BOOLEAN)); } TEST_F(ResourceParserTest, ParseEnumAttr) { std::string input = R"( )"; ASSERT_TRUE(TestParse(input)); Attribute* enum_attr = test::GetValue(&table_, "attr/foo"); ASSERT_THAT(enum_attr, NotNull()); EXPECT_THAT(enum_attr->type_mask, Eq(ResTable_map::TYPE_ENUM)); ASSERT_THAT(enum_attr->symbols, SizeIs(3)); ASSERT_TRUE(enum_attr->symbols[0].symbol.name); EXPECT_THAT(enum_attr->symbols[0].symbol.name.value().entry, Eq("bar")); EXPECT_THAT(enum_attr->symbols[0].value, Eq(0u)); ASSERT_TRUE(enum_attr->symbols[1].symbol.name); EXPECT_THAT(enum_attr->symbols[1].symbol.name.value().entry, Eq("bat")); EXPECT_THAT(enum_attr->symbols[1].value, Eq(1u)); ASSERT_TRUE(enum_attr->symbols[2].symbol.name); EXPECT_THAT(enum_attr->symbols[2].symbol.name.value().entry, Eq("baz")); EXPECT_THAT(enum_attr->symbols[2].value, Eq(2u)); } TEST_F(ResourceParserTest, ParseFlagAttr) { std::string input = R"( )"; ASSERT_TRUE(TestParse(input)); Attribute* flag_attr = test::GetValue(&table_, "attr/foo"); ASSERT_THAT(flag_attr, NotNull()); EXPECT_THAT(flag_attr->type_mask, Eq(ResTable_map::TYPE_FLAGS)); ASSERT_THAT(flag_attr->symbols, SizeIs(3)); ASSERT_TRUE(flag_attr->symbols[0].symbol.name); EXPECT_THAT(flag_attr->symbols[0].symbol.name.value().entry, Eq("bar")); EXPECT_THAT(flag_attr->symbols[0].value, Eq(0u)); ASSERT_TRUE(flag_attr->symbols[1].symbol.name); EXPECT_THAT(flag_attr->symbols[1].symbol.name.value().entry, Eq("bat")); EXPECT_THAT(flag_attr->symbols[1].value, Eq(1u)); ASSERT_TRUE(flag_attr->symbols[2].symbol.name); EXPECT_THAT(flag_attr->symbols[2].symbol.name.value().entry, Eq("baz")); EXPECT_THAT(flag_attr->symbols[2].value, Eq(2u)); std::unique_ptr flag_value = ResourceUtils::TryParseFlagSymbol(flag_attr, "baz|bat"); ASSERT_THAT(flag_value, NotNull()); EXPECT_THAT(flag_value->value.data, Eq(1u | 2u)); } TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) { std::string input = R"( )"; ASSERT_FALSE(TestParse(input)); } TEST_F(ResourceParserTest, ParseStyle) { std::string input = R"( )"; ASSERT_TRUE(TestParse(input)); Style* style = test::GetValue)"; ASSERT_TRUE(TestParse(input)); Style* style = test::GetValue)"; ASSERT_TRUE(TestParse(input)); Style* style = test::GetValue