/* * Copyright (C) 2014 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. */ package com.android.manifmerger; import static org.mockito.Mockito.when; import com.android.SdkConstants; import com.android.utils.StdLogger; import com.google.common.base.Optional; import junit.framework.TestCase; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.w3c.dom.Attr; import org.xml.sax.SAXException; import java.io.IOException; import java.util.List; import javax.xml.parsers.ParserConfigurationException; /** * Tests for {@link com.android.manifmerger.XmlAttribute} class */ public class XmlAttributeTest extends TestCase { @Mock XmlDocument mXmlDocument; @Mock XmlElement mXmlElement; @Mock Attr mAttr; @Override protected void setUp() throws Exception { super.setUp(); MockitoAnnotations.initMocks(this); when(mAttr.getNamespaceURI()).thenReturn(SdkConstants.ANDROID_URI); when(mAttr.getPrefix()).thenReturn("android"); when(mAttr.getLocalName()).thenReturn("name"); when(mXmlElement.getType()).thenReturn(ManifestModel.NodeTypes.ACTIVITY); when(mXmlElement.getDocument()).thenReturn(mXmlDocument); when(mXmlDocument.getPackageNameForAttributeExpansion()).thenReturn("com.foo.bar"); } public void testPackageSubstitution_noDot() { when(mAttr.getValue()).thenReturn("ActivityOne"); // this will reset the package. assertNotNull(new XmlAttribute(mXmlElement, mAttr, AttributeModel.newModel("ActivityOne").setIsPackageDependent().build())); Mockito.verify(mAttr).setValue("com.foo.bar.ActivityOne"); } public void testPackageSubstitution_withDot() { when(mAttr.getValue()).thenReturn(".ActivityOne"); // this will reset the package. assertNotNull(new XmlAttribute(mXmlElement, mAttr, AttributeModel.newModel("ActivityOne").setIsPackageDependent().build())); Mockito.verify(mAttr).setValue("com.foo.bar.ActivityOne"); } public void testNoPackageSubstitution() { when(mAttr.getValue()).thenReturn("com.foo.foo2.ActivityOne"); // this will NOT reset the package. assertNotNull(new XmlAttribute(mXmlElement, mAttr, AttributeModel.newModel("ActivityOne").setIsPackageDependent().build())); Mockito.verify(mAttr).getValue(); Mockito.verifyNoMoreInteractions(mAttr); } public void testAttributeRemoval() throws ParserConfigurationException, SAXException, IOException { String higherPriority = "" + "<manifest\n" + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + " package=\"com.example.lib3\">\n" + "\n" + " <activity android:name=\"activityOne\"\n" + " tools:remove=\"theme\"/>\n" + "\n" + "</manifest>"; String lowerPriority = "" + "<manifest\n" + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + " package=\"com.example.lib3\">\n" + "\n" + " <activity android:name=\"activityOne\" \n" + " android:theme=\"@oldtheme\"/>\n" + "\n" + "</manifest>"; XmlDocument refDocument = TestUtils.xmlDocumentFromString( TestUtils.sourceFile(getClass(), "higherPriority"), higherPriority); XmlDocument otherDocument = TestUtils.xmlLibraryFromString( TestUtils.sourceFile(getClass(), "lowerPriority"), lowerPriority); MergingReport.Builder mergingReportBuilder = new MergingReport.Builder( new StdLogger(StdLogger.Level.VERBOSE)); Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder); assertTrue(result.isPresent()); XmlDocument resultDocument = result.get(); Optional<XmlElement> activityOne = resultDocument.getRootNode() .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne"); assertTrue(activityOne.isPresent()); // verify that only android:name and tools:remove remains in the result. List<XmlAttribute> attributes = activityOne.get().getAttributes(); assertEquals(2, attributes.size()); assertTrue(activityOne.get().getAttribute( XmlNode.fromXmlName("android:name")).isPresent()); assertTrue(activityOne.get().getAttribute( XmlNode.fromNSName(SdkConstants.TOOLS_URI, "tools", "remove")).isPresent()); Actions actions = mergingReportBuilder.getActionRecorder().build(); // check the recorded actions. List<Actions.AttributeRecord> attributeRecords = actions.getAttributeRecords(activityOne.get().getId(), XmlNode.fromXmlName("android:theme")); assertNotNull(attributeRecords); assertEquals(1, attributeRecords.size()); Actions.AttributeRecord attributeRecord = attributeRecords.get(0); assertEquals(Actions.ActionType.REJECTED, attributeRecord.getActionType()); assertEquals(AttributeOperationType.REMOVE, attributeRecord.getOperationType()); assertEquals(6, attributeRecord.getActionLocation().getPosition().getStartLine()); } public void testNamespaceAwareAttributeRemoval() throws ParserConfigurationException, SAXException, IOException { String higherPriority = "" + "<manifest\n" + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + " package=\"com.example.lib3\">\n" + "\n" + " <activity android:name=\"activityOne\"\n" + " tools:remove=\"android:theme\"/>\n" + "\n" + "</manifest>"; String lowerPriority = "" + "<manifest\n" + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + " package=\"com.example.lib3\">\n" + "\n" + " <activity android:name=\"activityOne\" \n" + " android:theme=\"@oldtheme\"/>\n" + "\n" + "</manifest>"; XmlDocument refDocument = TestUtils.xmlDocumentFromString( TestUtils.sourceFile(getClass(), "higherPriority"), higherPriority); XmlDocument otherDocument = TestUtils.xmlLibraryFromString( TestUtils.sourceFile(getClass(), "lowerPriority"), lowerPriority); MergingReport.Builder mergingReportBuilder = new MergingReport.Builder( new StdLogger(StdLogger.Level.VERBOSE)); Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder); assertTrue(result.isPresent()); XmlDocument resultDocument = result.get(); Optional<XmlElement> activityOne = resultDocument.getRootNode() .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne"); assertTrue(activityOne.isPresent()); List<XmlAttribute> attributes = activityOne.get().getAttributes(); assertEquals(2, attributes.size()); assertTrue(activityOne.get().getAttribute( XmlNode.fromXmlName("android:name")).isPresent()); assertTrue(activityOne.get().getAttribute( XmlNode.fromNSName(SdkConstants.TOOLS_URI, "tools", "remove")).isPresent()); } public void testMultipleAttributesRemoval() throws ParserConfigurationException, SAXException, IOException { String higherPriority = "" + "<manifest\n" + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + " package=\"com.example.lib3\">\n" + "\n" + " <activity android:name=\"activityOne\"\n" + " tools:remove=\"theme, exported\"/>\n" + "\n" + "</manifest>"; String lowerPriority = "" + "<manifest\n" + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + " package=\"com.example.lib3\">\n" + "\n" + " <activity android:name=\"activityOne\" \n" + " android:exported=\"true\"\n" + " android:theme=\"@oldtheme\"/>\n" + "\n" + "</manifest>"; XmlDocument refDocument = TestUtils.xmlDocumentFromString( TestUtils.sourceFile(getClass(), "higherPriority"), higherPriority); XmlDocument otherDocument = TestUtils.xmlLibraryFromString( TestUtils.sourceFile(getClass(), "lowerPriority"), lowerPriority); MergingReport.Builder mergingReportBuilder = new MergingReport.Builder( new StdLogger(StdLogger.Level.VERBOSE)); Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder); assertTrue(result.isPresent()); XmlDocument resultDocument = result.get(); Optional<XmlElement> activityOne = resultDocument.getRootNode() .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne"); assertTrue(activityOne.isPresent()); List<XmlAttribute> attributes = activityOne.get().getAttributes(); assertEquals(2, attributes.size()); assertTrue(activityOne.get().getAttribute( XmlNode.fromXmlName("android:name")).isPresent()); assertTrue(activityOne.get().getAttribute( XmlNode.fromNSName(SdkConstants.TOOLS_URI, "tools", "remove")).isPresent()); } public void testDeepAttributeRemoval() throws ParserConfigurationException, SAXException, IOException { String higherPriority = "" + "<manifest\n" + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + " package=\"com.example.lib3\">\n" + "\n" + " <activity android:name=\"activityOne\"\n" + " tools:remove=\"theme, exported\"/>\n" + "\n" + "</manifest>"; String lowerPriority = "" + "<manifest\n" + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + " package=\"com.example.lib3\">\n" + "\n" + " <activity android:name=\"activityOne\" \n" + " android:screenOrientation=\"landscape\"\n" + " android:theme=\"@oldtheme\"/>\n" + "\n" + "</manifest>"; String evenLowerPriority = "" + "<manifest\n" + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + " package=\"com.example.lib3\">\n" + "\n" + " <activity android:name=\"activityOne\" \n" + " android:theme=\"@oldtheme\"\n" + " android:exported=\"true\"/>\n" + "\n" + "</manifest>"; XmlDocument highPriority = TestUtils.xmlDocumentFromString( TestUtils.sourceFile(getClass(), "highPriority"), higherPriority); XmlDocument lowPriority = TestUtils.xmlLibraryFromString( TestUtils.sourceFile(getClass(), "lowPriority"), lowerPriority); XmlDocument lowestPriority = TestUtils.xmlLibraryFromString( TestUtils.sourceFile(getClass(), "lowestPriority"), evenLowerPriority); MergingReport.Builder mergingReportBuilder = new MergingReport.Builder( new StdLogger(StdLogger.Level.VERBOSE)); Optional<XmlDocument> result = highPriority.merge(lowPriority, mergingReportBuilder); assertTrue(result.isPresent()); XmlDocument resultDocument = result.get(); result = resultDocument.merge(lowestPriority, mergingReportBuilder); assertTrue(result.isPresent()); resultDocument = result.get(); Optional<XmlElement> activityOne = resultDocument.getRootNode() .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne"); assertTrue(activityOne.isPresent()); List<XmlAttribute> attributes = activityOne.get().getAttributes(); assertEquals(3, attributes.size()); assertTrue(activityOne.get().getAttribute( XmlNode.fromXmlName("android:name")).isPresent()); assertTrue(activityOne.get().getAttribute( XmlNode.fromXmlName("android:screenOrientation")).isPresent()); assertTrue(activityOne.get().getAttribute( XmlNode.fromNSName(SdkConstants.TOOLS_URI, "tools", "remove")).isPresent()); Actions actions = mergingReportBuilder.getActionRecorder().build(); // check the recorded actions. List<Actions.AttributeRecord> attributeRecords = actions.getAttributeRecords(activityOne.get().getId(), XmlNode.fromXmlName("android:theme")); assertNotNull(attributeRecords); // theme was removed twice... assertEquals(2, attributeRecords.size()); Actions.AttributeRecord attributeRecord = attributeRecords.get(0); assertEquals(Actions.ActionType.REJECTED, attributeRecord.getActionType()); assertEquals(AttributeOperationType.REMOVE, attributeRecord.getOperationType()); assertEquals("XmlAttributeTest#lowPriority", attributeRecord.getActionLocation().getFile().print(true)); assertEquals(7, attributeRecord.getActionLocation().getPosition().getStartLine()); attributeRecord = attributeRecords.get(1); assertEquals(Actions.ActionType.REJECTED, attributeRecord.getActionType()); assertEquals(AttributeOperationType.REMOVE, attributeRecord.getOperationType()); assertEquals("XmlAttributeTest#lowestPriority", attributeRecord.getActionLocation().getFile().print(true)); assertEquals(6, attributeRecord.getActionLocation().getPosition().getStartLine()); } public void testDefaultValueIllegalOverriding() throws ParserConfigurationException, SAXException, IOException { String higherPriority = "" + "<manifest\n" + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + " package=\"com.example.lib3\">\n" + "\n" // implicit required=true attribute present. + " <uses-library android:name=\"libraryOne\"/>\n" + " <permission android:name=\"permissionOne\"/>\n" + "\n" + "</manifest>"; String lowerPriority = "" + "<manifest\n" + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + " package=\"com.example.lib3\">\n" + "\n" + " <uses-library android:name=\"libraryOne\" android:required=\"false\"/>\n" + " <permission android:name=\"permissionOne\" " + " android:protectionLevel=\"dangerous\"/>\n" + "\n" + "</manifest>"; XmlDocument refDocument = TestUtils.xmlDocumentFromString( TestUtils.sourceFile(getClass(), "higherPriority"), higherPriority); XmlDocument otherDocument = TestUtils.xmlLibraryFromString( TestUtils.sourceFile(getClass(), "lowerPriority"), lowerPriority); MergingReport.Builder mergingReportBuilder = new MergingReport.Builder( new StdLogger(StdLogger.Level.VERBOSE)); Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder); assertTrue(result.isPresent()); } public void testToolsAttributeMerging() throws ParserConfigurationException, SAXException, IOException { String higherPriority = "" + "<manifest\n" + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + " package=\"com.example.lib3\">\n" + "\n" + " <activity android:name=\"activityOne\"\n" + " tools:remove=\"theme,exported\"/>\n" + "\n" + "</manifest>"; String lowerPriority = "" + "<manifest\n" + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + " package=\"com.example.lib3\">\n" + "\n" + "\n" + " <activity android:name=\"activityOne\" \n" +" tools:remove=\"bar\"/>\n" + "\n" + "</manifest>"; XmlDocument refDocument = TestUtils.xmlDocumentFromString( TestUtils.sourceFile(getClass(), "higherPriority"), higherPriority); XmlDocument otherDocument = TestUtils.xmlDocumentFromString( TestUtils.sourceFile(getClass(), "lowerPriority"), lowerPriority); MergingReport.Builder mergingReportBuilder = new MergingReport.Builder( new StdLogger(StdLogger.Level.VERBOSE)); Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder); assertTrue(result.isPresent()); Optional<XmlElement> activityOne = result.get().getRootNode() .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne"); assertTrue(activityOne.isPresent()); // verify that only android:name and tools:remove remains in the result. List<XmlAttribute> attributes = activityOne.get().getAttributes(); assertEquals(2, attributes.size()); assertTrue(activityOne.get().getAttribute( XmlNode.fromXmlName("android:name")).isPresent()); Optional<XmlAttribute> toolsRemove = activityOne.get().getAttribute( XmlNode.fromNSName(SdkConstants.TOOLS_URI, "tools", "remove")); assertTrue(toolsRemove.isPresent()); assertEquals("theme,exported,bar", toolsRemove.get().getValue()); } public void testToolsNodeAttributeNotMerging() throws ParserConfigurationException, SAXException, IOException { String higherPriority = "" + "<manifest\n" + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + " package=\"com.example.lib3\">\n" + "\n" + " <activity android:name=\"activityOne\"\n" + " tools:node=\"replace\"/>\n" + "\n" + "</manifest>"; String lowerPriority = "" + "<manifest\n" + " xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + " package=\"com.example.lib3\">\n" + "\n" + "\n" + " <activity android:name=\"activityOne\" \n" +" tools:node=\"remove\"/>\n" + "\n" + "</manifest>"; XmlDocument refDocument = TestUtils.xmlDocumentFromString( TestUtils.sourceFile(getClass(), "higherPriority"), higherPriority); XmlDocument otherDocument = TestUtils.xmlDocumentFromString( TestUtils.sourceFile(getClass(), "lowerPriority"), lowerPriority); MergingReport.Builder mergingReportBuilder = new MergingReport.Builder( new StdLogger(StdLogger.Level.VERBOSE)); Optional<XmlDocument> result = refDocument.merge(otherDocument, mergingReportBuilder); assertTrue(result.isPresent()); Optional<XmlElement> activityOne = result.get().getRootNode() .getNodeByTypeAndKey(ManifestModel.NodeTypes.ACTIVITY, "com.example.lib3.activityOne"); assertTrue(activityOne.isPresent()); // verify that only android:name and tools:remove remains in the result. List<XmlAttribute> attributes = activityOne.get().getAttributes(); assertEquals(2, attributes.size()); assertTrue(activityOne.get().getAttribute( XmlNode.fromXmlName("android:name")).isPresent()); Optional<XmlAttribute> toolsRemove = activityOne.get().getAttribute( XmlNode.fromNSName(SdkConstants.TOOLS_URI, "tools", "node")); assertTrue(toolsRemove.isPresent()); assertEquals("replace", toolsRemove.get().getValue()); } }