/* * 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 "link/TableMerger.h" #include "filter/ConfigFilter.h" #include "io/FileSystem.h" #include "test/Test.h" namespace aapt { struct TableMergerTest : public ::testing::Test { std::unique_ptr context_; void SetUp() override { context_ = test::ContextBuilder() // We are compiling this package. .SetCompilationPackage("com.app.a") // Merge all packages that have this package ID. .SetPackageId(0x7f) // Mangle all packages that do not have this package name. .SetNameManglerPolicy(NameManglerPolicy{"com.app.a", {"com.app.b"}}) .Build(); } }; TEST_F(TableMergerTest, SimpleMerge) { std::unique_ptr table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) .AddReference("com.app.a:id/foo", "com.app.a:id/bar") .AddReference("com.app.a:id/bar", "com.app.b:id/foo") .AddValue( "com.app.a:styleable/view", test::StyleableBuilder().AddItem("com.app.b:id/foo").Build()) .Build(); std::unique_ptr table_b = test::ResourceTableBuilder() .SetPackageId("com.app.b", 0x7f) .AddSimple("com.app.b:id/foo") .Build(); ResourceTable final_table; TableMerger merger(context_.get(), &final_table, TableMergerOptions{}); io::FileCollection collection; ASSERT_TRUE(merger.Merge({}, table_a.get())); ASSERT_TRUE( merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection)); EXPECT_TRUE(merger.merged_packages().count("com.app.b") != 0); // Entries from com.app.a should not be mangled. AAPT_EXPECT_TRUE( final_table.FindResource(test::ParseNameOrDie("com.app.a:id/foo"))); AAPT_EXPECT_TRUE( final_table.FindResource(test::ParseNameOrDie("com.app.a:id/bar"))); AAPT_EXPECT_TRUE(final_table.FindResource( test::ParseNameOrDie("com.app.a:styleable/view"))); // The unmangled name should not be present. AAPT_EXPECT_FALSE( final_table.FindResource(test::ParseNameOrDie("com.app.b:id/foo"))); // Look for the mangled name. AAPT_EXPECT_TRUE(final_table.FindResource( test::ParseNameOrDie("com.app.a:id/com.app.b$foo"))); } TEST_F(TableMergerTest, MergeFile) { ResourceTable final_table; TableMergerOptions options; options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); ResourceFile file_desc; file_desc.config = test::ParseConfigOrDie("hdpi-v4"); file_desc.name = test::ParseNameOrDie("layout/main"); file_desc.source = Source("res/layout-hdpi/main.xml"); test::TestFile test_file("path/to/res/layout-hdpi/main.xml.flat"); ASSERT_TRUE(merger.MergeFile(file_desc, &test_file)); FileReference* file = test::GetValueForConfig( &final_table, "com.app.a:layout/main", test::ParseConfigOrDie("hdpi-v4")); ASSERT_NE(nullptr, file); EXPECT_EQ(std::string("res/layout-hdpi-v4/main.xml"), *file->path); } TEST_F(TableMergerTest, MergeFileOverlay) { ResourceTable final_table; TableMergerOptions options; options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); ResourceFile file_desc; file_desc.name = test::ParseNameOrDie("xml/foo"); test::TestFile file_a("path/to/fileA.xml.flat"); test::TestFile file_b("path/to/fileB.xml.flat"); ASSERT_TRUE(merger.MergeFile(file_desc, &file_a)); ASSERT_TRUE(merger.MergeFileOverlay(file_desc, &file_b)); } TEST_F(TableMergerTest, MergeFileReferences) { std::unique_ptr table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) .AddFileReference("com.app.a:xml/file", "res/xml/file.xml") .Build(); std::unique_ptr table_b = test::ResourceTableBuilder() .SetPackageId("com.app.b", 0x7f) .AddFileReference("com.app.b:xml/file", "res/xml/file.xml") .Build(); ResourceTable final_table; TableMerger merger(context_.get(), &final_table, TableMergerOptions{}); io::FileCollection collection; collection.InsertFile("res/xml/file.xml"); ASSERT_TRUE(merger.Merge({}, table_a.get())); ASSERT_TRUE( merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection)); FileReference* f = test::GetValue(&final_table, "com.app.a:xml/file"); ASSERT_NE(f, nullptr); EXPECT_EQ(std::string("res/xml/file.xml"), *f->path); f = test::GetValue(&final_table, "com.app.a:xml/com.app.b$file"); ASSERT_NE(f, nullptr); EXPECT_EQ(std::string("res/xml/com.app.b$file.xml"), *f->path); } TEST_F(TableMergerTest, OverrideResourceWithOverlay) { std::unique_ptr base = test::ResourceTableBuilder() .SetPackageId("", 0x00) .AddValue("bool/foo", ResourceUtils::TryParseBool("true")) .Build(); std::unique_ptr overlay = test::ResourceTableBuilder() .SetPackageId("", 0x00) .AddValue("bool/foo", ResourceUtils::TryParseBool("false")) .Build(); ResourceTable final_table; TableMergerOptions options; options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); ASSERT_TRUE(merger.Merge({}, base.get())); ASSERT_TRUE(merger.MergeOverlay({}, overlay.get())); BinaryPrimitive* foo = test::GetValue(&final_table, "com.app.a:bool/foo"); ASSERT_NE(nullptr, foo); EXPECT_EQ(0x0u, foo->value.data); } TEST_F(TableMergerTest, OverrideSameResourceIdsWithOverlay) { std::unique_ptr base = test::ResourceTableBuilder() .SetPackageId("", 0x7f) .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), SymbolState::kPublic) .Build(); std::unique_ptr overlay = test::ResourceTableBuilder() .SetPackageId("", 0x7f) .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), SymbolState::kPublic) .Build(); ResourceTable final_table; TableMergerOptions options; options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); ASSERT_TRUE(merger.Merge({}, base.get())); ASSERT_TRUE(merger.MergeOverlay({}, overlay.get())); } TEST_F(TableMergerTest, FailToOverrideConflictingTypeIdsWithOverlay) { std::unique_ptr base = test::ResourceTableBuilder() .SetPackageId("", 0x7f) .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), SymbolState::kPublic) .Build(); std::unique_ptr overlay = test::ResourceTableBuilder() .SetPackageId("", 0x7f) .SetSymbolState("bool/foo", ResourceId(0x7f, 0x02, 0x0001), SymbolState::kPublic) .Build(); ResourceTable final_table; TableMergerOptions options; options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); ASSERT_TRUE(merger.Merge({}, base.get())); ASSERT_FALSE(merger.MergeOverlay({}, overlay.get())); } TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) { std::unique_ptr base = test::ResourceTableBuilder() .SetPackageId("", 0x7f) .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), SymbolState::kPublic) .Build(); std::unique_ptr overlay = test::ResourceTableBuilder() .SetPackageId("", 0x7f) .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0002), SymbolState::kPublic) .Build(); ResourceTable final_table; TableMergerOptions options; options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); ASSERT_TRUE(merger.Merge({}, base.get())); ASSERT_FALSE(merger.MergeOverlay({}, overlay.get())); } TEST_F(TableMergerTest, MergeAddResourceFromOverlay) { std::unique_ptr table_a = test::ResourceTableBuilder() .SetPackageId("", 0x7f) .SetSymbolState("bool/foo", {}, SymbolState::kUndefined) .Build(); std::unique_ptr table_b = test::ResourceTableBuilder() .SetPackageId("", 0x7f) .AddValue("bool/foo", ResourceUtils::TryParseBool("true")) .Build(); ResourceTable final_table; TableMerger merger(context_.get(), &final_table, TableMergerOptions{}); ASSERT_TRUE(merger.Merge({}, table_a.get())); ASSERT_TRUE(merger.MergeOverlay({}, table_b.get())); } TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) { std::unique_ptr table_a = test::ResourceTableBuilder().SetPackageId("", 0x7f).Build(); std::unique_ptr table_b = test::ResourceTableBuilder() .SetPackageId("", 0x7f) .AddValue("bool/foo", ResourceUtils::TryParseBool("true")) .Build(); ResourceTable final_table; TableMergerOptions options; options.auto_add_overlay = true; TableMerger merger(context_.get(), &final_table, options); ASSERT_TRUE(merger.Merge({}, table_a.get())); ASSERT_TRUE(merger.MergeOverlay({}, table_b.get())); } TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) { std::unique_ptr table_a = test::ResourceTableBuilder().SetPackageId("", 0x7f).Build(); std::unique_ptr table_b = test::ResourceTableBuilder() .SetPackageId("", 0x7f) .AddValue("bool/foo", ResourceUtils::TryParseBool("true")) .Build(); ResourceTable final_table; TableMergerOptions options; options.auto_add_overlay = false; TableMerger merger(context_.get(), &final_table, options); ASSERT_TRUE(merger.Merge({}, table_a.get())); ASSERT_FALSE(merger.MergeOverlay({}, table_b.get())); } TEST_F(TableMergerTest, OverlaidStyleablesShouldBeMerged) { std::unique_ptr table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) .AddValue("com.app.a:styleable/Foo", test::StyleableBuilder() .AddItem("com.app.a:attr/bar") .AddItem("com.app.a:attr/foo", ResourceId(0x01010000)) .Build()) .Build(); std::unique_ptr table_b = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) .AddValue("com.app.a:styleable/Foo", test::StyleableBuilder() .AddItem("com.app.a:attr/bat") .AddItem("com.app.a:attr/foo") .Build()) .Build(); ResourceTable final_table; TableMergerOptions options; options.auto_add_overlay = true; TableMerger merger(context_.get(), &final_table, options); ASSERT_TRUE(merger.Merge({}, table_a.get())); ASSERT_TRUE(merger.MergeOverlay({}, table_b.get())); Styleable* styleable = test::GetValue(&final_table, "com.app.a:styleable/Foo"); ASSERT_NE(nullptr, styleable); std::vector expected_refs = { Reference(test::ParseNameOrDie("com.app.a:attr/bar")), Reference(test::ParseNameOrDie("com.app.a:attr/bat")), Reference(test::ParseNameOrDie("com.app.a:attr/foo"), ResourceId(0x01010000)), }; EXPECT_EQ(expected_refs, styleable->entries); } } // namespace aapt