diff options
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/aapt2/ConfigDescription.cpp | 11 | ||||
| -rw-r--r-- | tools/aapt2/DominatorTree_test.cpp | 61 | ||||
| -rw-r--r-- | tools/aapt2/flatten/XmlFlattener.cpp | 6 | ||||
| -rw-r--r-- | tools/aapt2/flatten/XmlFlattener_test.cpp | 225 | ||||
| -rw-r--r-- | tools/aapt2/optimize/ResourceDeduper_test.cpp | 91 | ||||
| -rw-r--r-- | tools/aapt2/test/Common.h | 23 | ||||
| -rw-r--r-- | tools/aapt2/util/Maybe.h | 32 | ||||
| -rw-r--r-- | tools/aapt2/util/Util.cpp | 15 | ||||
| -rw-r--r-- | tools/aapt2/util/Util.h | 3 | ||||
| -rw-r--r-- | tools/aapt2/util/Util_test.cpp | 88 | ||||
| -rw-r--r-- | tools/aapt2/xml/XmlDom_test.cpp | 37 |
11 files changed, 319 insertions, 273 deletions
diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp index 46098cbc3aa4..7ff0c7227c9c 100644 --- a/tools/aapt2/ConfigDescription.cpp +++ b/tools/aapt2/ConfigDescription.cpp @@ -877,7 +877,16 @@ ConfigDescription ConfigDescription::CopyWithoutSdkVersion() const { } bool ConfigDescription::Dominates(const ConfigDescription& o) const { - if (*this == DefaultConfig() || *this == o) { + if (*this == o) { + return true; + } + + // Locale de-duping is not-trivial, disable for now (b/62409213). + if (diff(o) & CONFIG_LOCALE) { + return false; + } + + if (*this == DefaultConfig()) { return true; } return MatchWithDensity(o) && !o.MatchWithDensity(*this) && diff --git a/tools/aapt2/DominatorTree_test.cpp b/tools/aapt2/DominatorTree_test.cpp index e89c6beb0c57..efc523fcf59b 100644 --- a/tools/aapt2/DominatorTree_test.cpp +++ b/tools/aapt2/DominatorTree_test.cpp @@ -69,14 +69,12 @@ class PrettyPrinter : public DominatorTree::Visitor { TEST(DominatorTreeTest, DefaultDominatesEverything) { const ConfigDescription default_config = {}; const ConfigDescription land_config = test::ParseConfigOrDie("land"); - const ConfigDescription sw600dp_land_config = - test::ParseConfigOrDie("sw600dp-land-v13"); + const ConfigDescription sw600dp_land_config = test::ParseConfigOrDie("sw600dp-land-v13"); std::vector<std::unique_ptr<ResourceConfigValue>> configs; configs.push_back(util::make_unique<ResourceConfigValue>(default_config, "")); configs.push_back(util::make_unique<ResourceConfigValue>(land_config, "")); - configs.push_back( - util::make_unique<ResourceConfigValue>(sw600dp_land_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(sw600dp_land_config, "")); DominatorTree tree(configs); PrettyPrinter printer; @@ -91,16 +89,13 @@ TEST(DominatorTreeTest, DefaultDominatesEverything) { TEST(DominatorTreeTest, ProductsAreDominatedSeparately) { const ConfigDescription default_config = {}; const ConfigDescription land_config = test::ParseConfigOrDie("land"); - const ConfigDescription sw600dp_land_config = - test::ParseConfigOrDie("sw600dp-land-v13"); + const ConfigDescription sw600dp_land_config = test::ParseConfigOrDie("sw600dp-land-v13"); std::vector<std::unique_ptr<ResourceConfigValue>> configs; configs.push_back(util::make_unique<ResourceConfigValue>(default_config, "")); configs.push_back(util::make_unique<ResourceConfigValue>(land_config, "")); - configs.push_back( - util::make_unique<ResourceConfigValue>(default_config, "phablet")); - configs.push_back( - util::make_unique<ResourceConfigValue>(sw600dp_land_config, "phablet")); + configs.push_back(util::make_unique<ResourceConfigValue>(default_config, "phablet")); + configs.push_back(util::make_unique<ResourceConfigValue>(sw600dp_land_config, "phablet")); DominatorTree tree(configs); PrettyPrinter printer; @@ -118,16 +113,11 @@ TEST(DominatorTreeTest, MoreSpecificConfigurationsAreDominated) { const ConfigDescription en_config = test::ParseConfigOrDie("en"); const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21"); const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl-v4"); - const ConfigDescription ldrtl_xhdpi_config = - test::ParseConfigOrDie("ldrtl-xhdpi-v4"); - const ConfigDescription sw300dp_config = - test::ParseConfigOrDie("sw300dp-v13"); - const ConfigDescription sw540dp_config = - test::ParseConfigOrDie("sw540dp-v14"); - const ConfigDescription sw600dp_config = - test::ParseConfigOrDie("sw600dp-v14"); - const ConfigDescription sw720dp_config = - test::ParseConfigOrDie("sw720dp-v13"); + const ConfigDescription ldrtl_xhdpi_config = test::ParseConfigOrDie("ldrtl-xhdpi-v4"); + const ConfigDescription sw300dp_config = test::ParseConfigOrDie("sw300dp-v13"); + const ConfigDescription sw540dp_config = test::ParseConfigOrDie("sw540dp-v14"); + const ConfigDescription sw600dp_config = test::ParseConfigOrDie("sw600dp-v14"); + const ConfigDescription sw720dp_config = test::ParseConfigOrDie("sw720dp-v13"); const ConfigDescription v20_config = test::ParseConfigOrDie("v20"); std::vector<std::unique_ptr<ResourceConfigValue>> configs; @@ -135,8 +125,7 @@ TEST(DominatorTreeTest, MoreSpecificConfigurationsAreDominated) { configs.push_back(util::make_unique<ResourceConfigValue>(en_config, "")); configs.push_back(util::make_unique<ResourceConfigValue>(en_v21_config, "")); configs.push_back(util::make_unique<ResourceConfigValue>(ldrtl_config, "")); - configs.push_back( - util::make_unique<ResourceConfigValue>(ldrtl_xhdpi_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(ldrtl_xhdpi_config, "")); configs.push_back(util::make_unique<ResourceConfigValue>(sw300dp_config, "")); configs.push_back(util::make_unique<ResourceConfigValue>(sw540dp_config, "")); configs.push_back(util::make_unique<ResourceConfigValue>(sw600dp_config, "")); @@ -148,15 +137,37 @@ TEST(DominatorTreeTest, MoreSpecificConfigurationsAreDominated) { std::string expected = "<default>\n" - " en\n" - " en-v21\n" " ldrtl-v4\n" " ldrtl-xhdpi-v4\n" " sw300dp-v13\n" " sw540dp-v14\n" " sw600dp-v14\n" " sw720dp-v13\n" - " v20\n"; + " v20\n" + "en\n" + " en-v21\n"; + EXPECT_EQ(expected, printer.ToString(&tree)); +} + +TEST(DominatorTreeTest, LocalesAreNeverDominated) { + const ConfigDescription fr_config = test::ParseConfigOrDie("fr"); + const ConfigDescription fr_rCA_config = test::ParseConfigOrDie("fr-rCA"); + const ConfigDescription fr_rFR_config = test::ParseConfigOrDie("fr-rFR"); + + std::vector<std::unique_ptr<ResourceConfigValue>> configs; + configs.push_back(util::make_unique<ResourceConfigValue>(ConfigDescription::DefaultConfig(), "")); + configs.push_back(util::make_unique<ResourceConfigValue>(fr_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(fr_rCA_config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(fr_rFR_config, "")); + + DominatorTree tree(configs); + PrettyPrinter printer; + + std::string expected = + "<default>\n" + "fr\n" + "fr-rCA\n" + "fr-rFR\n"; EXPECT_EQ(expected, printer.ToString(&tree)); } diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp index 0711749d0378..bfebedef2a1e 100644 --- a/tools/aapt2/flatten/XmlFlattener.cpp +++ b/tools/aapt2/flatten/XmlFlattener.cpp @@ -257,9 +257,11 @@ class XmlFlattenerVisitor : public xml::Visitor { // Process plain strings to make sure they get properly escaped. StringPiece raw_value = xml_attr->value; - util::StringBuilder str_builder; + + util::StringBuilder str_builder(true /*preserve_spaces*/); + str_builder.Append(xml_attr->value); + if (!options_.keep_raw_values) { - str_builder.Append(xml_attr->value); raw_value = str_builder.ToString(); } diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp index f1e903f2151e..a57e3178accd 100644 --- a/tools/aapt2/flatten/XmlFlattener_test.cpp +++ b/tools/aapt2/flatten/XmlFlattener_test.cpp @@ -23,7 +23,13 @@ #include "util/BigBuffer.h" #include "util/Util.h" -using android::StringPiece16; +using ::aapt::test::StrEq; +using ::android::StringPiece16; +using ::testing::Eq; +using ::testing::Ge; +using ::testing::IsNull; +using ::testing::Ne; +using ::testing::NotNull; namespace aapt { @@ -72,163 +78,138 @@ class XmlFlattenerTest : public ::testing::Test { }; TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( - <View xmlns:test="http://com.test" - attr="hey"> - <Layout test:hello="hi" /> - <Layout>Some text\\</Layout> - </View>)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( + <View xmlns:test="http://com.test" attr="hey"> + <Layout test:hello="hi" /> + <Layout>Some text\\</Layout> + </View>)"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); - - ASSERT_EQ(android::ResXMLTree::START_NAMESPACE, tree.next()); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_NAMESPACE)); size_t len; - const char16_t* namespace_prefix = tree.getNamespacePrefix(&len); - EXPECT_EQ(StringPiece16(u"test"), StringPiece16(namespace_prefix, len)); - - const char16_t* namespace_uri = tree.getNamespaceUri(&len); - ASSERT_EQ(StringPiece16(u"http://com.test"), StringPiece16(namespace_uri, len)); - - ASSERT_EQ(android::ResXMLTree::START_TAG, tree.next()); - - ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); - const char16_t* tag_name = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(u"View"), StringPiece16(tag_name, len)); - - ASSERT_EQ(1u, tree.getAttributeCount()); - ASSERT_EQ(nullptr, tree.getAttributeNamespace(0, &len)); - const char16_t* attr_name = tree.getAttributeName(0, &len); - EXPECT_EQ(StringPiece16(u"attr"), StringPiece16(attr_name, len)); - - EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr", StringPiece16(u"attr").size())); + EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"test")); + EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://com.test")); - ASSERT_EQ(android::ResXMLTree::START_TAG, tree.next()); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementNamespace(&len), IsNull()); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"View")); - ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); - tag_name = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(u"Layout"), StringPiece16(tag_name, len)); + ASSERT_THAT(tree.getAttributeCount(), Eq(1u)); + EXPECT_THAT(tree.getAttributeNamespace(0, &len), IsNull()); + EXPECT_THAT(tree.getAttributeName(0, &len), StrEq(u"attr")); - ASSERT_EQ(1u, tree.getAttributeCount()); - const char16_t* attr_namespace = tree.getAttributeNamespace(0, &len); - EXPECT_EQ(StringPiece16(u"http://com.test"), StringPiece16(attr_namespace, len)); + const StringPiece16 kAttr(u"attr"); + EXPECT_THAT(tree.indexOfAttribute(nullptr, 0, kAttr.data(), kAttr.size()), Eq(0)); - attr_name = tree.getAttributeName(0, &len); - EXPECT_EQ(StringPiece16(u"hello"), StringPiece16(attr_name, len)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); + EXPECT_THAT(tree.getElementNamespace(&len), IsNull()); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout")); - ASSERT_EQ(android::ResXMLTree::END_TAG, tree.next()); - ASSERT_EQ(android::ResXMLTree::START_TAG, tree.next()); + ASSERT_THAT(tree.getAttributeCount(), Eq(1u)); + EXPECT_THAT(tree.getAttributeNamespace(0, &len), StrEq(u"http://com.test")); + EXPECT_THAT(tree.getAttributeName(0, &len), StrEq(u"hello")); - ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); - tag_name = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(u"Layout"), StringPiece16(tag_name, len)); - ASSERT_EQ(0u, tree.getAttributeCount()); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); - ASSERT_EQ(android::ResXMLTree::TEXT, tree.next()); - const char16_t* text = tree.getText(&len); - EXPECT_EQ(StringPiece16(u"Some text\\"), StringPiece16(text, len)); + EXPECT_THAT(tree.getElementNamespace(&len), IsNull()); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout")); + ASSERT_THAT(tree.getAttributeCount(), Eq(0u)); - ASSERT_EQ(android::ResXMLTree::END_TAG, tree.next()); - ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); - tag_name = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(u"Layout"), StringPiece16(tag_name, len)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u"Some text\\")); - ASSERT_EQ(android::ResXMLTree::END_TAG, tree.next()); - ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); - tag_name = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(u"View"), StringPiece16(tag_name, len)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + EXPECT_THAT(tree.getElementNamespace(&len), IsNull()); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout")); - ASSERT_EQ(android::ResXMLTree::END_NAMESPACE, tree.next()); - namespace_prefix = tree.getNamespacePrefix(&len); - EXPECT_EQ(StringPiece16(u"test"), StringPiece16(namespace_prefix, len)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG)); + EXPECT_THAT(tree.getElementNamespace(&len), IsNull()); + EXPECT_THAT(tree.getElementName(&len), StrEq(u"View")); - namespace_uri = tree.getNamespaceUri(&len); - ASSERT_EQ(StringPiece16(u"http://com.test"), StringPiece16(namespace_uri, len)); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_NAMESPACE)); + EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"test")); + EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://com.test")); - ASSERT_EQ(android::ResXMLTree::END_DOCUMENT, tree.next()); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_DOCUMENT)); } TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripOnlyTools) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( - <View xmlns:tools="http://schemas.android.com/tools" - xmlns:foo="http://schemas.android.com/foo" - foo:bar="Foo" - tools:ignore="MissingTranslation"/>)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( + <View xmlns:tools="http://schemas.android.com/tools" + xmlns:foo="http://schemas.android.com/foo" + foo:bar="Foo" + tools:ignore="MissingTranslation"/>)"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); - - ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_NAMESPACE)); size_t len; - const char16_t* namespace_prefix = tree.getNamespacePrefix(&len); - EXPECT_EQ(StringPiece16(namespace_prefix, len), u"foo"); - - const char16_t* namespace_uri = tree.getNamespaceUri(&len); - ASSERT_EQ(StringPiece16(namespace_uri, len), - u"http://schemas.android.com/foo"); - - ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); + EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"foo")); + EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://schemas.android.com/foo")); + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG)); - EXPECT_EQ(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"), - android::NAME_NOT_FOUND); - EXPECT_GE(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), 0); + EXPECT_THAT(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"), + Eq(android::NAME_NOT_FOUND)); + EXPECT_THAT(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), Ge(0)); } TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( - <View xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@id/id" - class="str" - style="@id/id"/>)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@id/id" + class="str" + style="@id/id"/>)"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT)); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT)); } - EXPECT_EQ(tree.indexOfClass(), 0); - EXPECT_EQ(tree.indexOfStyle(), 1); + EXPECT_THAT(tree.indexOfClass(), Eq(0)); + EXPECT_THAT(tree.indexOfStyle(), Eq(1)); } // The device ResXMLParser in libandroidfw differentiates between empty namespace and null // namespace. TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom("<View package=\"android\"/>"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<View package="android"/>)"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT)); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT)); } const StringPiece16 kPackage = u"package"; - EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0); + EXPECT_THAT(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), Ge(0)); } TEST_F(XmlFlattenerTest, EmptyStringValueInAttributeIsNotNull) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom("<View package=\"\"/>"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<View package=""/>)"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT)); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT)); } const StringPiece16 kPackage = u"package"; ssize_t idx = tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()); - ASSERT_GE(idx, 0); + ASSERT_THAT(idx, Ge(0)); size_t len; - EXPECT_NE(nullptr, tree.getAttributeStringValue(idx, &len)); + EXPECT_THAT(tree.getAttributeStringValue(idx, &len), NotNull()); } TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) { @@ -236,11 +217,11 @@ TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) { context_->SetPackageId(0x80); context_->SetNameManglerPolicy({"com.app.test.feature"}); - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( <View xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@id/foo" - app:foo="@id/foo" />)EOF"); + app:foo="@id/foo" />)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); @@ -253,59 +234,57 @@ TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) { ASSERT_TRUE(Flatten(doc.get(), &tree)); while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(android::ResXMLTree::BAD_DOCUMENT, tree.getEventType()); - ASSERT_NE(android::ResXMLTree::END_DOCUMENT, tree.getEventType()); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT)); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT)); } ssize_t idx; idx = tree.indexOfAttribute(xml::kSchemaAndroid, "id"); - ASSERT_GE(idx, 0); - EXPECT_EQ(idx, tree.indexOfID()); - EXPECT_EQ(ResourceId(0x010100d0), ResourceId(tree.getAttributeNameResID(idx))); + ASSERT_THAT(idx, Ge(0)); + EXPECT_THAT(tree.indexOfID(), Eq(idx)); + EXPECT_THAT(tree.getAttributeNameResID(idx), Eq(0x010100d0u)); idx = tree.indexOfAttribute(xml::kSchemaAuto, "foo"); - ASSERT_GE(idx, 0); - EXPECT_EQ(ResourceId(0x80010000), ResourceId(tree.getAttributeNameResID(idx))); - EXPECT_EQ(android::Res_value::TYPE_REFERENCE, tree.getAttributeDataType(idx)); - EXPECT_EQ(ResourceId(0x80020000), tree.getAttributeData(idx)); + ASSERT_THAT(idx, Ge(0)); + EXPECT_THAT(tree.getAttributeNameResID(idx), Eq(0x80010000u)); + EXPECT_THAT(tree.getAttributeDataType(idx), Eq(android::Res_value::TYPE_REFERENCE)); + EXPECT_THAT(tree.getAttributeData(idx), Eq(int32_t(0x80020000))); } TEST_F(XmlFlattenerTest, ProcessEscapedStrings) { std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom( - R"EOF(<element value="\?hello" pattern="\\d{5}">\\d{5}</element>)EOF"); + R"(<element value="\?hello" pattern="\\d{5}" other=""">\\d{5}</element>)"); android::ResXMLTree tree; ASSERT_TRUE(Flatten(doc.get(), &tree)); while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT)); + ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT)); } const StringPiece16 kValue = u"value"; const StringPiece16 kPattern = u"pattern"; + const StringPiece16 kOther = u"other"; size_t len; ssize_t idx; - const char16_t* str16; idx = tree.indexOfAttribute(nullptr, 0, kValue.data(), kValue.size()); - ASSERT_GE(idx, 0); - str16 = tree.getAttributeStringValue(idx, &len); - ASSERT_NE(nullptr, str16); - EXPECT_EQ(StringPiece16(u"?hello"), StringPiece16(str16, len)); + ASSERT_THAT(idx, Ge(0)); + EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"?hello")); idx = tree.indexOfAttribute(nullptr, 0, kPattern.data(), kPattern.size()); - ASSERT_GE(idx, 0); - str16 = tree.getAttributeStringValue(idx, &len); - ASSERT_NE(nullptr, str16); - EXPECT_EQ(StringPiece16(u"\\d{5}"), StringPiece16(str16, len)); - - ASSERT_EQ(android::ResXMLTree::TEXT, tree.next()); - str16 = tree.getText(&len); - ASSERT_NE(nullptr, str16); - EXPECT_EQ(StringPiece16(u"\\d{5}"), StringPiece16(str16, len)); + ASSERT_THAT(idx, Ge(0)); + EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"\\d{5}")); + + idx = tree.indexOfAttribute(nullptr, 0, kOther.data(), kOther.size()); + ASSERT_THAT(idx, Ge(0)); + EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"\"")); + + ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT)); + EXPECT_THAT(tree.getText(&len), StrEq(u"\\d{5}")); } } // namespace aapt diff --git a/tools/aapt2/optimize/ResourceDeduper_test.cpp b/tools/aapt2/optimize/ResourceDeduper_test.cpp index 4d00fa6efe37..d9f384c095ca 100644 --- a/tools/aapt2/optimize/ResourceDeduper_test.cpp +++ b/tools/aapt2/optimize/ResourceDeduper_test.cpp @@ -19,69 +19,88 @@ #include "ResourceTable.h" #include "test/Test.h" +using ::aapt::test::HasValue; +using ::testing::Not; + namespace aapt { TEST(ResourceDeduperTest, SameValuesAreDeduped) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); const ConfigDescription default_config = {}; + const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl"); + const ConfigDescription ldrtl_v21_config = test::ParseConfigOrDie("ldrtl-v21"); const ConfigDescription en_config = test::ParseConfigOrDie("en"); const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21"); - // Chosen because this configuration is compatible with en. + // Chosen because this configuration is compatible with ldrtl/en. const ConfigDescription land_config = test::ParseConfigOrDie("land"); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .AddString("android:string/dedupe", ResourceId{}, default_config, - "dedupe") - .AddString("android:string/dedupe", ResourceId{}, en_config, "dedupe") - .AddString("android:string/dedupe", ResourceId{}, land_config, - "dedupe") - .AddString("android:string/dedupe2", ResourceId{}, default_config, - "dedupe") - .AddString("android:string/dedupe2", ResourceId{}, en_config, - "dedupe") - .AddString("android:string/dedupe2", ResourceId{}, en_v21_config, - "keep") - .AddString("android:string/dedupe2", ResourceId{}, land_config, - "dedupe") + .AddString("android:string/dedupe", ResourceId{}, default_config, "dedupe") + .AddString("android:string/dedupe", ResourceId{}, ldrtl_config, "dedupe") + .AddString("android:string/dedupe", ResourceId{}, land_config, "dedupe") + + .AddString("android:string/dedupe2", ResourceId{}, default_config, "dedupe") + .AddString("android:string/dedupe2", ResourceId{}, ldrtl_config, "dedupe") + .AddString("android:string/dedupe2", ResourceId{}, ldrtl_v21_config, "keep") + .AddString("android:string/dedupe2", ResourceId{}, land_config, "dedupe") + + .AddString("android:string/dedupe3", ResourceId{}, default_config, "dedupe") + .AddString("android:string/dedupe3", ResourceId{}, en_config, "dedupe") + .AddString("android:string/dedupe3", ResourceId{}, en_v21_config, "dedupe") .Build(); ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); - EXPECT_EQ(nullptr, test::GetValueForConfig<String>( - table.get(), "android:string/dedupe", en_config)); - EXPECT_EQ(nullptr, test::GetValueForConfig<String>( - table.get(), "android:string/dedupe", land_config)); - EXPECT_EQ(nullptr, test::GetValueForConfig<String>( - table.get(), "android:string/dedupe2", en_config)); - EXPECT_NE(nullptr, test::GetValueForConfig<String>( - table.get(), "android:string/dedupe2", en_v21_config)); + EXPECT_THAT(table, Not(HasValue("android:string/dedupe", ldrtl_config))); + EXPECT_THAT(table, Not(HasValue("android:string/dedupe", land_config))); + + EXPECT_THAT(table, HasValue("android:string/dedupe2", ldrtl_v21_config)); + EXPECT_THAT(table, Not(HasValue("android:string/dedupe2", ldrtl_config))); + + EXPECT_THAT(table, HasValue("android:string/dedupe3", default_config)); + EXPECT_THAT(table, HasValue("android:string/dedupe3", en_config)); + EXPECT_THAT(table, Not(HasValue("android:string/dedupe3", en_v21_config))); } TEST(ResourceDeduperTest, DifferentValuesAreKept) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); const ConfigDescription default_config = {}; - const ConfigDescription en_config = test::ParseConfigOrDie("en"); - const ConfigDescription en_v21_config = test::ParseConfigOrDie("en-v21"); - // Chosen because this configuration is compatible with en. + const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl"); + const ConfigDescription ldrtl_v21_config = test::ParseConfigOrDie("ldrtl-v21"); + // Chosen because this configuration is compatible with ldrtl. const ConfigDescription land_config = test::ParseConfigOrDie("land"); std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .AddString("android:string/keep", ResourceId{}, default_config, - "keep") - .AddString("android:string/keep", ResourceId{}, en_config, "keep") - .AddString("android:string/keep", ResourceId{}, en_v21_config, - "keep2") + .AddString("android:string/keep", ResourceId{}, default_config, "keep") + .AddString("android:string/keep", ResourceId{}, ldrtl_config, "keep") + .AddString("android:string/keep", ResourceId{}, ldrtl_v21_config, "keep2") .AddString("android:string/keep", ResourceId{}, land_config, "keep2") .Build(); ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); - EXPECT_NE(nullptr, test::GetValueForConfig<String>( - table.get(), "android:string/keep", en_config)); - EXPECT_NE(nullptr, test::GetValueForConfig<String>( - table.get(), "android:string/keep", en_v21_config)); - EXPECT_NE(nullptr, test::GetValueForConfig<String>( - table.get(), "android:string/keep", land_config)); + EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_config)); + EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_v21_config)); + EXPECT_THAT(table, HasValue("android:string/keep", land_config)); +} + +TEST(ResourceDeduperTest, LocalesValuesAreKept) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + const ConfigDescription default_config = {}; + const ConfigDescription fr_config = test::ParseConfigOrDie("fr"); + const ConfigDescription fr_rCA_config = test::ParseConfigOrDie("fr-rCA"); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddString("android:string/keep", ResourceId{}, default_config, "keep") + .AddString("android:string/keep", ResourceId{}, fr_config, "keep") + .AddString("android:string/keep", ResourceId{}, fr_rCA_config, "keep") + .Build(); + + ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); + EXPECT_THAT(table, HasValue("android:string/keep", default_config)); + EXPECT_THAT(table, HasValue("android:string/keep", fr_config)); + EXPECT_THAT(table, HasValue("android:string/keep", fr_rCA_config)); } } // namespace aapt diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index 05851485e216..8efd56a7514b 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -145,11 +145,34 @@ void PrintTo(const Maybe<T>& value, std::ostream* out) { namespace test { +MATCHER_P(StrEq, a, + std::string(negation ? "isn't" : "is") + " equal to " + + ::testing::PrintToString(android::StringPiece16(a))) { + return android::StringPiece16(arg) == a; +} + MATCHER_P(ValueEq, a, std::string(negation ? "isn't" : "is") + " equal to " + ::testing::PrintToString(a)) { return arg.Equals(&a); } +MATCHER_P(StrValueEq, a, + std::string(negation ? "isn't" : "is") + " equal to " + ::testing::PrintToString(a)) { + return *(arg.value) == a; +} + +MATCHER_P(HasValue, name, + std::string(negation ? "does not have" : "has") + " value " + + ::testing::PrintToString(name)) { + return GetValueForConfig<Value>(&(*arg), name, {}) != nullptr; +} + +MATCHER_P2(HasValue, name, config, + std::string(negation ? "does not have" : "has") + " value " + + ::testing::PrintToString(name) + " for config " + ::testing::PrintToString(config)) { + return GetValueForConfig<Value>(&(*arg), name, config) != nullptr; +} + } // namespace test } // namespace aapt diff --git a/tools/aapt2/util/Maybe.h b/tools/aapt2/util/Maybe.h index b43f8e87fd68..9a82418e0a5a 100644 --- a/tools/aapt2/util/Maybe.h +++ b/tools/aapt2/util/Maybe.h @@ -281,16 +281,12 @@ inline Maybe<T> make_nothing() { return Maybe<T>(); } -/** - * Define the == operator between Maybe<T> and Maybe<U> only if the operator T - * == U is defined. - * That way the compiler will show an error at the callsite when comparing two - * Maybe<> objects - * whose inner types can't be compared. - */ +// Define the == operator between Maybe<T> and Maybe<U> only if the operator T == U is defined. +// That way the compiler will show an error at the callsite when comparing two Maybe<> objects +// whose inner types can't be compared. template <typename T, typename U> -typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==( - const Maybe<T>& a, const Maybe<U>& b) { +typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==(const Maybe<T>& a, + const Maybe<U>& b) { if (a && b) { return a.value() == b.value(); } else if (!a && !b) { @@ -299,18 +295,22 @@ typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==( return false; } -/** - * Same as operator== but negated. - */ template <typename T, typename U> -typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator!=( - const Maybe<T>& a, const Maybe<U>& b) { +typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==(const Maybe<T>& a, + const U& b) { + return a ? a.value() == b : false; +} + +// Same as operator== but negated. +template <typename T, typename U> +typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator!=(const Maybe<T>& a, + const Maybe<U>& b) { return !(a == b); } template <typename T, typename U> -typename std::enable_if<has_lt_op<T, U>::value, bool>::type operator<( - const Maybe<T>& a, const Maybe<U>& b) { +typename std::enable_if<has_lt_op<T, U>::value, bool>::type operator<(const Maybe<T>& a, + const Maybe<U>& b) { if (a && b) { return a.value() < b.value(); } else if (!a && !b) { diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp index 28e952e25a67..8a8be858cb4c 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -312,6 +312,9 @@ static Maybe<std::string> ParseUnicodeCodepoint(const char** start, return result_utf8; } +StringBuilder::StringBuilder(bool preserve_spaces) : preserve_spaces_(preserve_spaces) { +} + StringBuilder& StringBuilder::Append(const StringPiece& str) { if (!error_.empty()) { return *this; @@ -368,14 +371,12 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) { } last_char_was_escape_ = false; start = current + 1; - } else if (*current == '"') { + } else if (!preserve_spaces_ && *current == '"') { if (!quote_ && trailing_space_) { - // We found an opening quote, and we have - // trailing space, so we should append that + // We found an opening quote, and we have trailing space, so we should append that // space now. if (trailing_space_) { - // We had trailing whitespace, so - // replace with a single space. + // We had trailing whitespace, so replace with a single space. if (!str_.empty()) { str_ += ' '; } @@ -385,7 +386,7 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) { quote_ = !quote_; str_.append(start, current - start); start = current + 1; - } else if (*current == '\'' && !quote_) { + } else if (!preserve_spaces_ && *current == '\'' && !quote_) { // This should be escaped. error_ = "unescaped apostrophe"; return *this; @@ -402,7 +403,7 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) { str_.append(start, current - start); start = current + 1; last_char_was_escape_ = true; - } else if (!quote_) { + } else if (!preserve_spaces_ && !quote_) { // This is not quoted text, so look for whitespace. if (isspace(*current)) { // We found whitespace, see if we have seen some diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index 386f74b0301a..b9ada7704a26 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -166,6 +166,8 @@ bool VerifyJavaStringFormat(const android::StringPiece& str); class StringBuilder { public: + explicit StringBuilder(bool preserve_spaces = false); + StringBuilder& Append(const android::StringPiece& str); const std::string& ToString() const; const std::string& Error() const; @@ -179,6 +181,7 @@ class StringBuilder { explicit operator bool() const; private: + bool preserve_spaces_; std::string str_; size_t utf16_len_ = 0; bool quote_ = false; diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp index e49aee5d50ed..5cced3e9acab 100644 --- a/tools/aapt2/util/Util_test.cpp +++ b/tools/aapt2/util/Util_test.cpp @@ -20,16 +20,17 @@ #include "test/Test.h" -using android::StringPiece; +using ::android::StringPiece; +using ::testing::Eq; +using ::testing::Ne; +using ::testing::SizeIs; namespace aapt { TEST(UtilTest, TrimOnlyWhitespace) { - const std::string full = "\n "; - - StringPiece trimmed = util::TrimWhitespace(full); + const StringPiece trimmed = util::TrimWhitespace("\n "); EXPECT_TRUE(trimmed.empty()); - EXPECT_EQ(0u, trimmed.size()); + EXPECT_THAT(trimmed, SizeIs(0u)); } TEST(UtilTest, StringEndsWith) { @@ -41,85 +42,74 @@ TEST(UtilTest, StringStartsWith) { } TEST(UtilTest, StringBuilderSplitEscapeSequence) { - EXPECT_EQ(StringPiece("this is a new\nline."), util::StringBuilder() - .Append("this is a new\\") - .Append("nline.") - .ToString()); + EXPECT_THAT(util::StringBuilder().Append("this is a new\\").Append("nline.").ToString(), + Eq("this is a new\nline.")); } TEST(UtilTest, StringBuilderWhitespaceRemoval) { - EXPECT_EQ(StringPiece("hey guys this is so cool"), - util::StringBuilder() - .Append(" hey guys ") - .Append(" this is so cool ") - .ToString()); - - EXPECT_EQ(StringPiece(" wow, so many \t spaces. what?"), - util::StringBuilder() - .Append(" \" wow, so many \t ") - .Append("spaces. \"what? ") - .ToString()); - - EXPECT_EQ(StringPiece("where is the pie?"), util::StringBuilder() - .Append(" where \t ") - .Append(" \nis the " - " pie?") - .ToString()); + EXPECT_THAT(util::StringBuilder().Append(" hey guys ").Append(" this is so cool ").ToString(), + Eq("hey guys this is so cool")); + EXPECT_THAT( + util::StringBuilder().Append(" \" wow, so many \t ").Append("spaces. \"what? ").ToString(), + Eq(" wow, so many \t spaces. what?")); + EXPECT_THAT(util::StringBuilder().Append(" where \t ").Append(" \nis the pie?").ToString(), + Eq("where is the pie?")); } TEST(UtilTest, StringBuilderEscaping) { - EXPECT_EQ(StringPiece("hey guys\n this \t is so\\ cool"), - util::StringBuilder() - .Append(" hey guys\\n ") - .Append(" this \\t is so\\\\ cool ") - .ToString()); - - EXPECT_EQ(StringPiece("@?#\\\'"), - util::StringBuilder().Append("\\@\\?\\#\\\\\\'").ToString()); + EXPECT_THAT(util::StringBuilder() + .Append(" hey guys\\n ") + .Append(" this \\t is so\\\\ cool ") + .ToString(), + Eq("hey guys\n this \t is so\\ cool")); + EXPECT_THAT(util::StringBuilder().Append("\\@\\?\\#\\\\\\'").ToString(), Eq("@?#\\\'")); } TEST(UtilTest, StringBuilderMisplacedQuote) { - util::StringBuilder builder{}; + util::StringBuilder builder; EXPECT_FALSE(builder.Append("they're coming!")); } TEST(UtilTest, StringBuilderUnicodeCodes) { - EXPECT_EQ(std::string("\u00AF\u0AF0 woah"), - util::StringBuilder().Append("\\u00AF\\u0AF0 woah").ToString()); - + EXPECT_THAT(util::StringBuilder().Append("\\u00AF\\u0AF0 woah").ToString(), + Eq("\u00AF\u0AF0 woah")); EXPECT_FALSE(util::StringBuilder().Append("\\u00 yo")); } +TEST(UtilTest, StringBuilderPreserveSpaces) { + EXPECT_THAT(util::StringBuilder(true /*preserve_spaces*/).Append("\"").ToString(), Eq("\"")); +} + TEST(UtilTest, TokenizeInput) { auto tokenizer = util::Tokenize(StringPiece("this| is|the|end"), '|'); auto iter = tokenizer.begin(); - ASSERT_EQ(*iter, StringPiece("this")); + ASSERT_THAT(*iter, Eq("this")); ++iter; - ASSERT_EQ(*iter, StringPiece(" is")); + ASSERT_THAT(*iter, Eq(" is")); ++iter; - ASSERT_EQ(*iter, StringPiece("the")); + ASSERT_THAT(*iter, Eq("the")); ++iter; - ASSERT_EQ(*iter, StringPiece("end")); + ASSERT_THAT(*iter, Eq("end")); ++iter; - ASSERT_EQ(tokenizer.end(), iter); + ASSERT_THAT(iter, Eq(tokenizer.end())); } TEST(UtilTest, TokenizeEmptyString) { auto tokenizer = util::Tokenize(StringPiece(""), '|'); auto iter = tokenizer.begin(); - ASSERT_NE(tokenizer.end(), iter); - ASSERT_EQ(StringPiece(), *iter); + ASSERT_THAT(iter, Ne(tokenizer.end())); + ASSERT_THAT(*iter, Eq(StringPiece())); ++iter; - ASSERT_EQ(tokenizer.end(), iter); + ASSERT_THAT(iter, Eq(tokenizer.end())); } TEST(UtilTest, TokenizeAtEnd) { auto tokenizer = util::Tokenize(StringPiece("one."), '.'); auto iter = tokenizer.begin(); - ASSERT_EQ(*iter, StringPiece("one")); + ASSERT_THAT(*iter, Eq("one")); ++iter; - ASSERT_NE(iter, tokenizer.end()); - ASSERT_EQ(*iter, StringPiece()); + ASSERT_THAT(iter, Ne(tokenizer.end())); + ASSERT_THAT(*iter, Eq(StringPiece())); } TEST(UtilTest, IsJavaClassName) { diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp index fb18ea316ca6..031801e1dd2f 100644 --- a/tools/aapt2/xml/XmlDom_test.cpp +++ b/tools/aapt2/xml/XmlDom_test.cpp @@ -28,17 +28,16 @@ constexpr const char* kXmlPreamble = TEST(XmlDomTest, Inflate) { std::stringstream in(kXmlPreamble); - in << R"EOF( - <Layout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - <TextView android:id="@+id/id" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> - </Layout> - )EOF"; - - const Source source = {"test.xml"}; + in << R"( + <Layout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + <TextView android:id="@+id/id" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + </Layout>)"; + + const Source source("test.xml"); StdErrDiagnostics diag; std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, &diag, source); ASSERT_NE(doc, nullptr); @@ -51,8 +50,8 @@ TEST(XmlDomTest, Inflate) { // Escaping is handled after parsing of the values for resource-specific values. TEST(XmlDomTest, ForwardEscapes) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( - <element value="\?hello" pattern="\\d{5}">\\d{5}</element>)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( + <element value="\?hello" pattern="\\d{5}">\\d{5}</element>)"); xml::Element* el = xml::FindRootElement(doc->root.get()); ASSERT_NE(nullptr, el); @@ -65,10 +64,20 @@ TEST(XmlDomTest, ForwardEscapes) { ASSERT_NE(nullptr, attr); EXPECT_EQ("\\?hello", attr->value); - ASSERT_EQ(1u, el->children.size()); xml::Text* text = xml::NodeCast<xml::Text>(el->children[0].get()); ASSERT_NE(nullptr, text); EXPECT_EQ("\\\\d{5}", text->text); } +TEST(XmlDomTest, XmlEscapeSequencesAreParsed) { + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<element value=""" />)"); + + xml::Element* el = xml::FindRootElement(doc.get()); + ASSERT_NE(nullptr, el); + + xml::Attribute* attr = el->FindAttribute({}, "value"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ("\"", attr->value); +} + } // namespace aapt |