Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2015 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
Adam Lesinski | 838a687 | 2015-05-01 13:14:05 -0700 | [diff] [blame] | 17 | #include "MockResolver.h" |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 18 | #include "ResourceTable.h" |
| 19 | #include "ResourceValues.h" |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 20 | #include "Util.h" |
| 21 | #include "XmlFlattener.h" |
| 22 | |
| 23 | #include <androidfw/AssetManager.h> |
| 24 | #include <androidfw/ResourceTypes.h> |
| 25 | #include <gtest/gtest.h> |
| 26 | #include <sstream> |
| 27 | #include <string> |
| 28 | |
Adam Lesinski | ca2fc35 | 2015-04-03 12:08:26 -0700 | [diff] [blame] | 29 | using namespace android; |
| 30 | |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 31 | namespace aapt { |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 32 | namespace xml { |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 33 | |
| 34 | constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; |
| 35 | |
| 36 | class XmlFlattenerTest : public ::testing::Test { |
| 37 | public: |
| 38 | virtual void SetUp() override { |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 39 | mResolver = std::make_shared<MockResolver>( |
Adam Lesinski | 838a687 | 2015-05-01 13:14:05 -0700 | [diff] [blame] | 40 | std::make_shared<ResourceTable>(), |
Adam Lesinski | 24aad16 | 2015-04-24 19:19:30 -0700 | [diff] [blame] | 41 | std::map<ResourceName, ResourceId>({ |
| 42 | { ResourceName{ u"android", ResourceType::kAttr, u"attr" }, |
| 43 | ResourceId{ 0x01010000u } }, |
| 44 | { ResourceName{ u"android", ResourceType::kId, u"id" }, |
| 45 | ResourceId{ 0x01020000u } }, |
| 46 | { ResourceName{ u"com.lib", ResourceType::kAttr, u"attr" }, |
| 47 | ResourceId{ 0x01010001u } }, |
| 48 | { ResourceName{ u"com.lib", ResourceType::kId, u"id" }, |
| 49 | ResourceId{ 0x01020001u } }})); |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 50 | } |
| 51 | |
Adam Lesinski | 24aad16 | 2015-04-24 19:19:30 -0700 | [diff] [blame] | 52 | ::testing::AssertionResult testFlatten(const std::string& in, ResXMLTree* outTree) { |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 53 | std::stringstream input(kXmlPreamble); |
Adam Lesinski | 24aad16 | 2015-04-24 19:19:30 -0700 | [diff] [blame] | 54 | input << in << std::endl; |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 55 | |
| 56 | SourceLogger logger(Source{ "test.xml" }); |
| 57 | std::unique_ptr<Node> root = inflate(&input, &logger); |
| 58 | if (!root) { |
| 59 | return ::testing::AssertionFailure(); |
| 60 | } |
| 61 | |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 62 | BigBuffer outBuffer(1024); |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 63 | if (!flattenAndLink(Source{ "test.xml" }, root.get(), std::u16string(u"android"), |
| 64 | mResolver, {}, &outBuffer)) { |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 65 | return ::testing::AssertionFailure(); |
| 66 | } |
| 67 | |
| 68 | std::unique_ptr<uint8_t[]> data = util::copy(outBuffer); |
Adam Lesinski | ca2fc35 | 2015-04-03 12:08:26 -0700 | [diff] [blame] | 69 | if (outTree->setTo(data.get(), outBuffer.size(), true) != NO_ERROR) { |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 70 | return ::testing::AssertionFailure(); |
| 71 | } |
| 72 | return ::testing::AssertionSuccess(); |
| 73 | } |
| 74 | |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 75 | std::shared_ptr<IResolver> mResolver; |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 76 | }; |
| 77 | |
| 78 | TEST_F(XmlFlattenerTest, ParseSimpleView) { |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 79 | std::string input = R"EOF( |
| 80 | <View xmlns:android="http://schemas.android.com/apk/res/android" |
| 81 | android:attr="@id/id" |
| 82 | class="str" |
| 83 | style="@id/id"> |
| 84 | </View> |
| 85 | )EOF"; |
Adam Lesinski | ca2fc35 | 2015-04-03 12:08:26 -0700 | [diff] [blame] | 86 | ResXMLTree tree; |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 87 | ASSERT_TRUE(testFlatten(input, &tree)); |
| 88 | |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 89 | while (tree.next() != ResXMLTree::START_TAG) { |
| 90 | ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); |
| 91 | ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); |
| 92 | } |
| 93 | |
| 94 | const StringPiece16 androidNs = u"http://schemas.android.com/apk/res/android"; |
| 95 | const StringPiece16 attrName = u"attr"; |
| 96 | ssize_t idx = tree.indexOfAttribute(androidNs.data(), androidNs.size(), attrName.data(), |
| 97 | attrName.size()); |
| 98 | ASSERT_GE(idx, 0); |
| 99 | EXPECT_EQ(tree.getAttributeNameResID(idx), 0x01010000u); |
| 100 | EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_REFERENCE); |
| 101 | |
| 102 | const StringPiece16 class16 = u"class"; |
| 103 | idx = tree.indexOfAttribute(nullptr, 0, class16.data(), class16.size()); |
| 104 | ASSERT_GE(idx, 0); |
| 105 | EXPECT_EQ(tree.getAttributeNameResID(idx), 0u); |
| 106 | EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_STRING); |
| 107 | EXPECT_EQ(tree.getAttributeData(idx), tree.getAttributeValueStringID(idx)); |
| 108 | |
| 109 | const StringPiece16 style16 = u"style"; |
| 110 | idx = tree.indexOfAttribute(nullptr, 0, style16.data(), style16.size()); |
| 111 | ASSERT_GE(idx, 0); |
| 112 | EXPECT_EQ(tree.getAttributeNameResID(idx), 0u); |
| 113 | EXPECT_EQ(tree.getAttributeDataType(idx), android::Res_value::TYPE_REFERENCE); |
| 114 | EXPECT_EQ((uint32_t) tree.getAttributeData(idx), 0x01020000u); |
| 115 | EXPECT_EQ(tree.getAttributeValueStringID(idx), -1); |
| 116 | |
Adam Lesinski | ca2fc35 | 2015-04-03 12:08:26 -0700 | [diff] [blame] | 117 | while (tree.next() != ResXMLTree::END_DOCUMENT) { |
| 118 | ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 119 | } |
| 120 | } |
| 121 | |
Adam Lesinski | 24aad16 | 2015-04-24 19:19:30 -0700 | [diff] [blame] | 122 | TEST_F(XmlFlattenerTest, ParseViewWithPackageAlias) { |
| 123 | std::string input = "<View xmlns:ns1=\"http://schemas.android.com/apk/res/android\"\n" |
| 124 | " xmlns:ns2=\"http://schemas.android.com/apk/res/android\"\n" |
| 125 | " ns1:attr=\"@ns2:id/id\">\n" |
| 126 | "</View>"; |
| 127 | ResXMLTree tree; |
| 128 | ASSERT_TRUE(testFlatten(input, &tree)); |
| 129 | |
| 130 | while (tree.next() != ResXMLTree::END_DOCUMENT) { |
| 131 | ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | ::testing::AssertionResult attributeNameAndValueEquals(ResXMLTree* tree, size_t index, |
| 136 | ResourceId nameId, ResourceId valueId) { |
| 137 | if (index >= tree->getAttributeCount()) { |
| 138 | return ::testing::AssertionFailure() << "index " << index << " is out of bounds (" |
| 139 | << tree->getAttributeCount() << ")"; |
| 140 | } |
| 141 | |
| 142 | if (tree->getAttributeNameResID(index) != nameId.id) { |
| 143 | return ::testing::AssertionFailure() |
| 144 | << "attribute at index " << index << " has ID " |
| 145 | << ResourceId{ (uint32_t) tree->getAttributeNameResID(index) } |
| 146 | << ". Expected ID " << nameId; |
| 147 | } |
| 148 | |
| 149 | if (tree->getAttributeDataType(index) != Res_value::TYPE_REFERENCE) { |
| 150 | return ::testing::AssertionFailure() << "attribute at index " << index << " has value of " |
| 151 | << "type " << std::hex |
| 152 | << tree->getAttributeDataType(index) << std::dec |
| 153 | << ". Expected reference (" << std::hex |
| 154 | << Res_value::TYPE_REFERENCE << std::dec << ")"; |
| 155 | } |
| 156 | |
| 157 | if ((uint32_t) tree->getAttributeData(index) != valueId.id) { |
| 158 | return ::testing::AssertionFailure() |
| 159 | << "attribute at index " << index << " has value " << "with ID " |
| 160 | << ResourceId{ (uint32_t) tree->getAttributeData(index) } |
| 161 | << ". Expected ID " << valueId; |
| 162 | } |
| 163 | return ::testing::AssertionSuccess(); |
| 164 | } |
| 165 | |
| 166 | TEST_F(XmlFlattenerTest, ParseViewWithShadowedPackageAlias) { |
| 167 | std::string input = "<View xmlns:app=\"http://schemas.android.com/apk/res/android\"\n" |
| 168 | " app:attr=\"@app:id/id\">\n" |
| 169 | " <View xmlns:app=\"http://schemas.android.com/apk/res/com.lib\"\n" |
| 170 | " app:attr=\"@app:id/id\"/>\n" |
| 171 | "</View>"; |
| 172 | ResXMLTree tree; |
| 173 | ASSERT_TRUE(testFlatten(input, &tree)); |
| 174 | |
| 175 | while (tree.next() != ResXMLTree::START_TAG) { |
| 176 | ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); |
| 177 | ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); |
| 178 | } |
| 179 | |
| 180 | ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010000u }, |
| 181 | ResourceId{ 0x01020000u })); |
| 182 | |
| 183 | while (tree.next() != ResXMLTree::START_TAG) { |
| 184 | ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); |
| 185 | ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); |
| 186 | } |
| 187 | |
| 188 | ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u }, |
| 189 | ResourceId{ 0x01020001u })); |
| 190 | } |
| 191 | |
| 192 | TEST_F(XmlFlattenerTest, ParseViewWithLocalPackageAndAliasOfTheSameName) { |
| 193 | std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/com.lib\"\n" |
| 194 | " android:attr=\"@id/id\"/>"; |
| 195 | ResXMLTree tree; |
| 196 | ASSERT_TRUE(testFlatten(input, &tree)); |
| 197 | |
| 198 | while (tree.next() != ResXMLTree::START_TAG) { |
| 199 | ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); |
| 200 | ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); |
| 201 | } |
| 202 | |
| 203 | // We expect the 'android:attr' to be converted to 'com.lib:attr' due to the namespace |
| 204 | // assignment. |
| 205 | // However, we didn't give '@id/id' a package, so it should use the default package |
| 206 | // 'android', and not be converted from 'android' to 'com.lib'. |
| 207 | ASSERT_TRUE(attributeNameAndValueEquals(&tree, 0u, ResourceId{ 0x01010001u }, |
| 208 | ResourceId{ 0x01020000u })); |
| 209 | } |
| 210 | |
| 211 | /* |
| 212 | * The device ResXMLParser in libandroidfw differentiates between empty namespace and null |
| 213 | * namespace. |
| 214 | */ |
| 215 | TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) { |
| 216 | std::string input = "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| 217 | " package=\"android\"/>"; |
| 218 | |
| 219 | ResXMLTree tree; |
| 220 | ASSERT_TRUE(testFlatten(input, &tree)); |
| 221 | |
| 222 | while (tree.next() != ResXMLTree::START_TAG) { |
| 223 | ASSERT_NE(tree.getEventType(), ResXMLTree::BAD_DOCUMENT); |
| 224 | ASSERT_NE(tree.getEventType(), ResXMLTree::END_DOCUMENT); |
| 225 | } |
| 226 | |
| 227 | const StringPiece16 kPackage = u"package"; |
| 228 | EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0); |
| 229 | } |
| 230 | |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 231 | } // namespace xml |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 232 | } // namespace aapt |