| /* |
| * Copyright (C) 2012 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.ide.common.res2; |
| |
| import static com.android.SdkConstants.ATTR_NAME; |
| import static com.android.SdkConstants.FD_RES_DRAWABLE; |
| import static com.android.SdkConstants.FD_RES_LAYOUT; |
| import static com.android.SdkConstants.TAG_ATTR; |
| |
| import com.android.SdkConstants; |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.ide.common.internal.PngCruncher; |
| import com.android.resources.ResourceFolderType; |
| import com.android.resources.ResourceType; |
| import com.android.testutils.TestUtils; |
| import com.android.utils.SdkUtils; |
| import com.google.common.base.Charsets; |
| import com.google.common.collect.ListMultimap; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.google.common.io.Files; |
| |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.NodeList; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import javax.xml.parsers.DocumentBuilderFactory; |
| |
| public class ResourceMergerTest extends BaseTestCase { |
| |
| @Mock |
| PngCruncher mPngCruncher; |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| MockitoAnnotations.initMocks(this); |
| } |
| |
| public void testMergeByCount() throws Exception { |
| ResourceMerger merger = getResourceMerger(false /*normalize*/); |
| |
| assertEquals(32, merger.size()); |
| } |
| |
| public void testMergeWithNormalizationByCount() throws Exception { |
| ResourceMerger merger = getResourceMerger(true /*normalize*/); |
| |
| assertEquals(31, merger.size()); |
| } |
| |
| public void testMergedResourcesByName() throws Exception { |
| ResourceMerger merger = getResourceMerger(false /*normalize*/); |
| |
| verifyResourceExists(merger, |
| "drawable/icon", |
| "drawable-ldpi/icon", |
| "drawable/icon2", |
| "drawable/patch", |
| "raw/foo", |
| "layout/main", |
| "layout/layout_ref", |
| "layout/alias_replaced_by_file", |
| "layout/file_replaced_by_alias", |
| "drawable/color_drawable", |
| "drawable/drawable_ref", |
| "color/color", |
| "string/basic_string", |
| "string/xliff_string", |
| "string/xliff_with_carriage_return", |
| "string/styled_string", |
| "style/style", |
| "array/string_array", |
| "attr/dimen_attr", |
| "attr/string_attr", |
| "attr/enum_attr", |
| "attr/flag_attr", |
| "attr/blah", |
| "attr/blah2", |
| "attr/flagAttr", |
| "declare-styleable/declare_styleable", |
| "dimen/dimen", |
| "dimen-sw600dp/offset", |
| "id/item_id", |
| "integer/integer" |
| ); |
| } |
| |
| public void testMergedResourcesWithNormalizationByName() throws Exception { |
| ResourceMerger merger = getResourceMerger(true /*normalize*/); |
| |
| verifyResourceExists(merger, |
| "drawable/icon", |
| "drawable-ldpi-v4/icon", |
| "drawable/icon2", |
| "drawable/patch", |
| "raw/foo", |
| "layout/main", |
| "layout/layout_ref", |
| "layout/alias_replaced_by_file", |
| "layout/file_replaced_by_alias", |
| "drawable/color_drawable", |
| "drawable/drawable_ref", |
| "color/color", |
| "string/basic_string", |
| "string/xliff_string", |
| "string/xliff_with_carriage_return", |
| "string/styled_string", |
| "style/style", |
| "array/string_array", |
| "attr/dimen_attr", |
| "attr/string_attr", |
| "attr/enum_attr", |
| "attr/flag_attr", |
| "attr/blah", |
| "attr/blah2", |
| "attr/flagAttr", |
| "declare-styleable/declare_styleable", |
| "dimen/dimen", |
| "dimen-sw600dp-v13/offset", |
| "id/item_id", |
| "integer/integer" |
| ); |
| } |
| |
| private static String getPlatformPath(String path) { |
| return path.replaceAll("/", Matcher.quoteReplacement(File.separator)); |
| } |
| |
| public void testReplacedLayout() throws Exception { |
| ResourceMerger merger = getResourceMerger(false /*normalize*/); |
| ListMultimap<String, ResourceItem> mergedMap = merger.getDataMap(); |
| |
| List<ResourceItem> values = mergedMap.get("layout/main"); |
| |
| // the overlay means there's 2 versions of this resource. |
| assertEquals(2, values.size()); |
| ResourceItem mainLayout = values.get(1); |
| |
| ResourceFile sourceFile = mainLayout.getSource(); |
| assertTrue(sourceFile.getFile().getAbsolutePath() |
| .endsWith(getPlatformPath("overlay/layout/main.xml"))); |
| } |
| |
| public void testReplacedAlias() throws Exception { |
| ResourceMerger merger = getResourceMerger(false /*normalize*/); |
| ListMultimap<String, ResourceItem> mergedMap = merger.getDataMap(); |
| |
| List<ResourceItem> values = mergedMap.get("layout/alias_replaced_by_file"); |
| |
| // the overlay means there's 2 versions of this resource. |
| assertEquals(2, values.size()); |
| ResourceItem layout = values.get(1); |
| |
| // since it's replaced by a file, there's no node. |
| assertNull(layout.getValue()); |
| } |
| |
| public void testReplacedFile() throws Exception { |
| ResourceMerger merger = getResourceMerger(false /*normalize*/); |
| ListMultimap<String, ResourceItem> mergedMap = merger.getDataMap(); |
| |
| List<ResourceItem> values = mergedMap.get("layout/file_replaced_by_alias"); |
| |
| // the overlay means there's 2 versions of this resource. |
| assertEquals(2, values.size()); |
| ResourceItem layout = values.get(1); |
| |
| // since it's replaced by an alias, there's a node |
| assertNotNull(layout.getValue()); |
| } |
| |
| public void testMergeWrite() throws Exception { |
| ResourceMerger merger = getResourceMerger(false /*normalize*/); |
| RecordingLogger logger = new RecordingLogger(); |
| |
| File folder = getWrittenResources(); |
| |
| ResourceSet writtenSet = new ResourceSet("unused"); |
| writtenSet.addSource(folder); |
| writtenSet.loadFromFiles(logger); |
| |
| // compare the two maps, but not using the full map as the set loaded from the output |
| // won't contains all versions of each ResourceItem item. |
| compareResourceMaps(merger, writtenSet, false /*full compare*/); |
| checkLogger(logger); |
| } |
| |
| public void testXliffString() throws Exception { |
| ResourceMerger merger = getResourceMerger(false /*normalize*/); |
| |
| // check the result of the load |
| List<ResourceItem> values = merger.getDataMap().get("string/xliff_string"); |
| |
| assertEquals(1, values.size()); |
| ResourceItem string = values.get(0); |
| |
| // Even though the content is |
| // <xliff:g id="firstName">%1$s</xliff:g> <xliff:g id="lastName">%2$s</xliff:g> |
| // The valueText is going to skip the <g> node so we skip them from the comparison. |
| // What matters here is that the whitespaces are kept. |
| assertEquals("Loaded String in merger", |
| "%1$s %2$s", |
| string.getValueText()); |
| |
| File folder = getWrittenResources(); |
| |
| RecordingLogger logger = new RecordingLogger(); |
| ResourceSet writtenSet = new ResourceSet("unused"); |
| writtenSet.addSource(folder); |
| writtenSet.loadFromFiles(logger); |
| |
| values = writtenSet.getDataMap().get("string/xliff_string"); |
| |
| assertEquals(1, values.size()); |
| string = values.get(0); |
| |
| // Even though the content is |
| // <xliff:g id="firstName">%1$s</xliff:g> <xliff:g id="lastName">%2$s</xliff:g> |
| // The valueText is going to skip the <g> node so we skip them from the comparison. |
| // What matters here is that the whitespaces are kept. |
| assertEquals("Rewritten String through merger", |
| "%1$s %2$s", |
| string.getValueText()); |
| } |
| |
| public void testXliffStringWithCarriageReturn() throws Exception { |
| ResourceMerger merger = getResourceMerger(false /*normalize*/); |
| |
| // check the result of the load |
| List<ResourceItem> values = merger.getDataMap().get("string/xliff_with_carriage_return"); |
| |
| assertEquals(1, values.size()); |
| ResourceItem string = values.get(0); |
| |
| // Even though the content has xliff nodes |
| // The valueText is going to skip the <g> node so we skip them from the comparison. |
| // What matters here is that the whitespaces are kept. |
| String value = string.getValueText(); |
| assertEquals("Loaded String in merger", |
| "This is should be followed by whitespace:\n %1$s", |
| value); |
| |
| File folder = getWrittenResources(); |
| |
| RecordingLogger logger = new RecordingLogger(); |
| ResourceSet writtenSet = new ResourceSet("unused"); |
| writtenSet.addSource(folder); |
| writtenSet.loadFromFiles(logger); |
| |
| values = writtenSet.getDataMap().get("string/xliff_with_carriage_return"); |
| |
| assertEquals(1, values.size()); |
| string = values.get(0); |
| |
| // Even though the content has xliff nodes |
| // The valueText is going to skip the <g> node so we skip them from the comparison. |
| // What matters here is that the whitespaces are kept. |
| String newValue = string.getValueText(); |
| assertEquals("Rewritten String through merger", |
| value, |
| newValue); |
| } |
| |
| public void testNotMergedAttr() throws Exception { |
| RecordingLogger logger = new RecordingLogger(); |
| |
| File folder = getWrittenResources(); |
| |
| ResourceSet writtenSet = new ResourceSet("unused"); |
| writtenSet.addSource(folder); |
| writtenSet.loadFromFiles(logger); |
| |
| List<ResourceItem> items = writtenSet.getDataMap().get("attr/blah"); |
| assertEquals(1, items.size()); |
| assertTrue(items.get(0).getIgnoredFromDiskMerge()); |
| |
| checkLogger(logger); |
| } |
| |
| public void testMergedAttr() throws Exception { |
| RecordingLogger logger = new RecordingLogger(); |
| |
| File folder = getWrittenResources(); |
| |
| ResourceSet writtenSet = new ResourceSet("unused"); |
| writtenSet.addSource(folder); |
| writtenSet.loadFromFiles(logger); |
| |
| List<ResourceItem> items = writtenSet.getDataMap().get("attr/blah2"); |
| assertEquals(1, items.size()); |
| assertFalse(items.get(0).getIgnoredFromDiskMerge()); |
| |
| checkLogger(logger); |
| } |
| |
| public void testNotMergedAttrFromMerge() throws Exception { |
| ResourceMerger merger = getResourceMerger(false /*normalize*/); |
| |
| File folder = Files.createTempDir(); |
| merger.writeBlobTo(folder, |
| new MergedResourceWriter(Files.createTempDir(), mPngCruncher, false, false, null)); |
| |
| ResourceMerger loadedMerger = new ResourceMerger(); |
| assertTrue(loadedMerger.loadFromBlob(folder, true /*incrementalState*/)); |
| |
| // check that attr/blah is ignoredFromDiskMerge. |
| List<ResourceItem> items = loadedMerger.getDataSets().get(0).getDataMap().get("attr/blah"); |
| assertEquals(1, items.size()); |
| assertTrue(items.get(0).getIgnoredFromDiskMerge()); |
| } |
| |
| public void testWrittenDeclareStyleable() throws Exception { |
| RecordingLogger logger = new RecordingLogger(); |
| |
| File folder = getWrittenResources(); |
| |
| ResourceSet writtenSet = new ResourceSet("unused"); |
| writtenSet.addSource(folder); |
| writtenSet.loadFromFiles(logger); |
| |
| List<ResourceItem> items = writtenSet.getDataMap().get("declare-styleable/declare_styleable"); |
| assertEquals(1, items.size()); |
| |
| Node styleableNode = items.get(0).getValue(); |
| assertNotNull(styleableNode); |
| |
| // inspect the node |
| NodeList nodes = styleableNode.getChildNodes(); |
| |
| boolean foundBlah = false; |
| boolean foundAndroidColorForegroundInverse = false; |
| boolean foundBlah2 = false; |
| |
| for (int i = 0, n = nodes.getLength(); i < n; i++) { |
| Node node = nodes.item(i); |
| |
| if (node.getNodeType() != Node.ELEMENT_NODE) { |
| continue; |
| } |
| |
| String nodeName = node.getLocalName(); |
| if (ResourceType.ATTR.getName().equals(nodeName)) { |
| Attr attribute = (Attr) node.getAttributes().getNamedItemNS(null, ATTR_NAME); |
| |
| if (attribute != null) { |
| String name = attribute.getValue(); |
| if ("blah".equals(name)) { |
| foundBlah = true; |
| } else if ("android:colorForegroundInverse".equals(name)) { |
| foundAndroidColorForegroundInverse = true; |
| } else if ("blah2".equals(name)) { |
| foundBlah2 = true; |
| } |
| } |
| |
| } |
| } |
| |
| assertTrue(foundBlah); |
| assertTrue(foundAndroidColorForegroundInverse); |
| assertTrue(foundBlah2); |
| |
| checkLogger(logger); |
| } |
| |
| public void testMergeBlob() throws Exception { |
| ResourceMerger merger = getResourceMerger(false /*normalize*/); |
| |
| File folder = Files.createTempDir(); |
| merger.writeBlobTo(folder, |
| new MergedResourceWriter(Files.createTempDir(), mPngCruncher, false, false, null)); |
| |
| ResourceMerger loadedMerger = new ResourceMerger(); |
| assertTrue(loadedMerger.loadFromBlob(folder, true /*incrementalState*/)); |
| |
| compareResourceMaps(merger, loadedMerger, true /*full compare*/); |
| } |
| |
| /** |
| * Tests the path replacement in the merger.xml file loaded from testData/ |
| * @throws Exception |
| */ |
| public void testLoadingTestPathReplacement() throws Exception { |
| File root = TestUtils.getRoot("resources", "baseMerge"); |
| File fakeRoot = getMergedBlobFolder(root); |
| |
| ResourceMerger resourceMerger = new ResourceMerger(); |
| assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/)); |
| checkSourceFolders(resourceMerger); |
| |
| List<ResourceSet> sets = resourceMerger.getDataSets(); |
| for (ResourceSet set : sets) { |
| List<File> sourceFiles = set.getSourceFiles(); |
| |
| // there should only be one |
| assertEquals(1, sourceFiles.size()); |
| |
| File sourceFile = sourceFiles.get(0); |
| assertTrue(String.format("File %s is located in %s", sourceFile, root), |
| sourceFile.getAbsolutePath().startsWith(root.getAbsolutePath())); |
| } |
| } |
| |
| public void testUpdateWithBasicFiles() throws Exception { |
| File root = getIncMergeRoot("basicFiles"); |
| File fakeRoot = getMergedBlobFolder(root); |
| ResourceMerger resourceMerger = new ResourceMerger(); |
| assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/)); |
| checkSourceFolders(resourceMerger); |
| |
| List<ResourceSet> sets = resourceMerger.getDataSets(); |
| assertEquals(2, sets.size()); |
| |
| RecordingLogger logger = new RecordingLogger(); |
| |
| // ---------------- |
| // first set is the main one, no change here |
| ResourceSet mainSet = sets.get(0); |
| File mainBase = new File(root, "main"); |
| File mainDrawable = new File(mainBase, "drawable"); |
| File mainDrawableLdpi = new File(mainBase, "drawable-ldpi"); |
| |
| // touched/removed files: |
| File mainDrawableTouched = new File(mainDrawable, "touched.png"); |
| mainSet.updateWith(mainBase, mainDrawableTouched, FileStatus.CHANGED, logger); |
| checkLogger(logger); |
| |
| File mainDrawableRemoved = new File(mainDrawable, "removed.png"); |
| mainSet.updateWith(mainBase, mainDrawableRemoved, FileStatus.REMOVED, logger); |
| checkLogger(logger); |
| |
| File mainDrawableLdpiRemoved = new File(mainDrawableLdpi, "removed.png"); |
| mainSet.updateWith(mainBase, mainDrawableLdpiRemoved, FileStatus.REMOVED, logger); |
| checkLogger(logger); |
| |
| // ---------------- |
| // second set is the overlay one |
| ResourceSet overlaySet = sets.get(1); |
| File overlayBase = new File(root, "overlay"); |
| File overlayDrawable = new File(overlayBase, "drawable"); |
| File overlayDrawableHdpi = new File(overlayBase, "drawable-hdpi"); |
| |
| // new/removed files: |
| File overlayDrawableNewOverlay = new File(overlayDrawable, "new_overlay.png"); |
| overlaySet.updateWith(overlayBase, overlayDrawableNewOverlay, FileStatus.NEW, logger); |
| checkLogger(logger); |
| |
| File overlayDrawableRemovedOverlay = new File(overlayDrawable, "removed_overlay.png"); |
| overlaySet.updateWith(overlayBase, overlayDrawableRemovedOverlay, FileStatus.REMOVED, |
| logger); |
| checkLogger(logger); |
| |
| File overlayDrawableHdpiNewAlternate = new File(overlayDrawableHdpi, "new_alternate.png"); |
| overlaySet.updateWith(overlayBase, overlayDrawableHdpiNewAlternate, FileStatus.NEW, logger); |
| checkLogger(logger); |
| |
| // validate for duplicates |
| resourceMerger.validateDataSets(); |
| |
| // check the content. |
| ListMultimap<String, ResourceItem> mergedMap = resourceMerger.getDataMap(); |
| |
| // check unchanged file is WRITTEN |
| List<ResourceItem> drawableUntouched = mergedMap.get("drawable/untouched"); |
| assertEquals(1, drawableUntouched.size()); |
| assertTrue(drawableUntouched.get(0).isWritten()); |
| assertFalse(drawableUntouched.get(0).isTouched()); |
| assertFalse(drawableUntouched.get(0).isRemoved()); |
| |
| // check replaced file is TOUCHED |
| List<ResourceItem> drawableTouched = mergedMap.get("drawable/touched"); |
| assertEquals(1, drawableTouched.size()); |
| assertTrue(drawableTouched.get(0).isWritten()); |
| assertTrue(drawableTouched.get(0).isTouched()); |
| assertFalse(drawableTouched.get(0).isRemoved()); |
| |
| // check removed file is REMOVED |
| List<ResourceItem> drawableRemoved = mergedMap.get("drawable/removed"); |
| assertEquals(1, drawableRemoved.size()); |
| assertTrue(drawableRemoved.get(0).isWritten()); |
| assertTrue(drawableRemoved.get(0).isRemoved()); |
| |
| // check new overlay: two objects, last one is TOUCHED |
| List<ResourceItem> drawableNewOverlay = mergedMap.get("drawable/new_overlay"); |
| assertEquals(2, drawableNewOverlay.size()); |
| ResourceItem newOverlay = drawableNewOverlay.get(1); |
| assertEquals(overlayDrawableNewOverlay, newOverlay.getSource().getFile()); |
| assertFalse(newOverlay.isWritten()); |
| assertTrue(newOverlay.isTouched()); |
| |
| // check new alternate: one objects, last one is TOUCHED |
| List<ResourceItem> drawableHdpiNewAlternate = mergedMap.get("drawable-hdpi/new_alternate"); |
| assertEquals(1, drawableHdpiNewAlternate.size()); |
| ResourceItem newAlternate = drawableHdpiNewAlternate.get(0); |
| assertEquals(overlayDrawableHdpiNewAlternate, newAlternate.getSource().getFile()); |
| assertFalse(newAlternate.isWritten()); |
| assertTrue(newAlternate.isTouched()); |
| |
| // write and check the result of writeResourceFolder |
| // copy the current resOut which serves as pre incremental update state. |
| File resFolder = getFolderCopy(new File(root, "resOut")); |
| |
| // write the content of the resource merger. |
| MergedResourceWriter writer = new MergedResourceWriter(resFolder, mPngCruncher, false, |
| false, null); |
| resourceMerger.mergeData(writer, false /*doCleanUp*/); |
| |
| // Check the content. |
| checkImageColor(new File(resFolder, "drawable" + File.separator + "touched.png"), |
| (int) 0xFF00FF00); |
| checkImageColor(new File(resFolder, "drawable" + File.separator + "untouched.png"), |
| (int) 0xFF00FF00); |
| checkImageColor(new File(resFolder, "drawable" + File.separator + "new_overlay.png"), |
| (int) 0xFF00FF00); |
| checkImageColor(new File(resFolder, "drawable" + File.separator + "removed_overlay.png"), |
| (int) 0xFF00FF00); |
| checkImageColor(new File(resFolder, "drawable-hdpi" + File.separator + "new_alternate.png"), |
| (int) 0xFF00FF00); |
| assertFalse(new File(resFolder, "drawable-ldpi" + File.separator + "removed.png").isFile()); |
| } |
| |
| public void testUpdateWithBasicValues() throws Exception { |
| File root = getIncMergeRoot("basicValues"); |
| File fakeRoot = getMergedBlobFolder(root); |
| ResourceMerger resourceMerger = new ResourceMerger(); |
| assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/)); |
| checkSourceFolders(resourceMerger); |
| |
| List<ResourceSet> sets = resourceMerger.getDataSets(); |
| assertEquals(2, sets.size()); |
| |
| RecordingLogger logger = new RecordingLogger(); |
| |
| // ---------------- |
| // first set is the main one, no change here |
| ResourceSet mainSet = sets.get(0); |
| File mainBase = new File(root, "main"); |
| File mainValues = new File(mainBase, "values"); |
| File mainValuesEn = new File(mainBase, "values-en"); |
| |
| // touched file: |
| File mainValuesTouched = new File(mainValues, "values.xml"); |
| mainSet.updateWith(mainBase, mainValuesTouched, FileStatus.CHANGED, logger); |
| checkLogger(logger); |
| |
| |
| // removed files |
| File mainValuesEnRemoved = new File(mainValuesEn, "values.xml"); |
| mainSet.updateWith(mainBase, mainValuesEnRemoved, FileStatus.REMOVED, logger); |
| checkLogger(logger); |
| |
| |
| // ---------------- |
| // second set is the overlay one |
| ResourceSet overlaySet = sets.get(1); |
| File overlayBase = new File(root, "overlay"); |
| File overlayValues = new File(overlayBase, "values"); |
| File overlayValuesFr = new File(overlayBase, "values-fr"); |
| |
| // new files: |
| File overlayValuesNew = new File(overlayValues, "values.xml"); |
| overlaySet.updateWith(overlayBase, overlayValuesNew, FileStatus.NEW, logger); |
| checkLogger(logger); |
| |
| File overlayValuesFrNew = new File(overlayValuesFr, "values.xml"); |
| overlaySet.updateWith(overlayBase, overlayValuesFrNew, FileStatus.NEW, logger); |
| checkLogger(logger); |
| |
| // validate for duplicates |
| resourceMerger.validateDataSets(); |
| |
| // check the content. |
| ListMultimap<String, ResourceItem> mergedMap = resourceMerger.getDataMap(); |
| |
| // check unchanged string is WRITTEN |
| List<ResourceItem> valuesUntouched = mergedMap.get("string/untouched"); |
| assertEquals(1, valuesUntouched.size()); |
| assertTrue(valuesUntouched.get(0).isWritten()); |
| assertFalse(valuesUntouched.get(0).isTouched()); |
| assertFalse(valuesUntouched.get(0).isRemoved()); |
| |
| // check replaced file is TOUCHED |
| List<ResourceItem> valuesTouched = mergedMap.get("string/touched"); |
| assertEquals(1, valuesTouched.size()); |
| assertTrue(valuesTouched.get(0).isWritten()); |
| assertTrue(valuesTouched.get(0).isTouched()); |
| assertFalse(valuesTouched.get(0).isRemoved()); |
| |
| // check removed file is REMOVED |
| List<ResourceItem> valuesRemoved = mergedMap.get("string/removed"); |
| assertEquals(1, valuesRemoved.size()); |
| assertTrue(valuesRemoved.get(0).isWritten()); |
| assertTrue(valuesRemoved.get(0).isRemoved()); |
| |
| valuesRemoved = mergedMap.get("string-en/removed"); |
| assertEquals(1, valuesRemoved.size()); |
| assertTrue(valuesRemoved.get(0).isWritten()); |
| assertTrue(valuesRemoved.get(0).isRemoved()); |
| |
| // check new overlay: two objects, last one is TOUCHED |
| List<ResourceItem> valuesNewOverlay = mergedMap.get("string/new_overlay"); |
| assertEquals(2, valuesNewOverlay.size()); |
| ResourceItem newOverlay = valuesNewOverlay.get(1); |
| assertFalse(newOverlay.isWritten()); |
| assertTrue(newOverlay.isTouched()); |
| |
| // check new alternate: one objects, last one is TOUCHED |
| List<ResourceItem> valuesFrNewAlternate = mergedMap.get("string-fr/new_alternate"); |
| assertEquals(1, valuesFrNewAlternate.size()); |
| ResourceItem newAlternate = valuesFrNewAlternate.get(0); |
| assertFalse(newAlternate.isWritten()); |
| assertTrue(newAlternate.isTouched()); |
| |
| // write and check the result of writeResourceFolder |
| // copy the current resOut which serves as pre incremental update state. |
| File resFolder = getFolderCopy(new File(root, "resOut")); |
| |
| // write the content of the resource merger. |
| MergedResourceWriter writer = new MergedResourceWriter(resFolder, mPngCruncher, false, |
| false, null); |
| resourceMerger.mergeData(writer, false /*doCleanUp*/); |
| |
| // Check the content. |
| // values/values.xml |
| Map<String, String> map = quickStringOnlyValueFileParser( |
| new File(resFolder, "values" + File.separator + "values.xml")); |
| assertEquals("untouched", map.get("untouched")); |
| assertEquals("touched", map.get("touched")); |
| assertEquals("new_overlay", map.get("new_overlay")); |
| |
| // values-fr/values-fr.xml |
| map = quickStringOnlyValueFileParser( |
| new File(resFolder, "values-fr" + File.separator + "values-fr.xml")); |
| assertEquals("new_alternate", map.get("new_alternate")); |
| |
| // deleted values-en/values-en.xml |
| assertFalse(new File(resFolder, "values-en" + File.separator + "values-en.xml").isFile()); |
| } |
| |
| public void testUpdateWithBasicValues2() throws Exception { |
| File root = getIncMergeRoot("basicValues2"); |
| File fakeRoot = getMergedBlobFolder(root); |
| ResourceMerger resourceMerger = new ResourceMerger(); |
| assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/)); |
| checkSourceFolders(resourceMerger); |
| |
| List<ResourceSet> sets = resourceMerger.getDataSets(); |
| assertEquals(2, sets.size()); |
| |
| RecordingLogger logger = new RecordingLogger(); |
| |
| // ---------------- |
| // first set is the main one, no change here |
| |
| // ---------------- |
| // second set is the overlay one |
| ResourceSet overlaySet = sets.get(1); |
| File overlayBase = new File(root, "overlay"); |
| File overlayValues = new File(overlayBase, "values"); |
| |
| // new files: |
| File overlayValuesNew = new File(overlayValues, "values.xml"); |
| overlaySet.updateWith(overlayBase, overlayValuesNew, FileStatus.REMOVED, logger); |
| checkLogger(logger); |
| |
| |
| // validate for duplicates |
| resourceMerger.validateDataSets(); |
| |
| // check the content. |
| ListMultimap<String, ResourceItem> mergedMap = resourceMerger.getDataMap(); |
| |
| // check unchanged string is WRITTEN |
| List<ResourceItem> valuesUntouched = mergedMap.get("string/untouched"); |
| assertEquals(1, valuesUntouched.size()); |
| assertTrue(valuesUntouched.get(0).isWritten()); |
| assertFalse(valuesUntouched.get(0).isTouched()); |
| assertFalse(valuesUntouched.get(0).isRemoved()); |
| |
| // check removed_overlay is present twice. |
| List<ResourceItem> valuesRemovedOverlay = mergedMap.get("string/removed_overlay"); |
| assertEquals(2, valuesRemovedOverlay.size()); |
| // first is untouched |
| assertFalse(valuesRemovedOverlay.get(0).isWritten()); |
| assertFalse(valuesRemovedOverlay.get(0).isTouched()); |
| assertFalse(valuesRemovedOverlay.get(0).isRemoved()); |
| // other is removed |
| assertTrue(valuesRemovedOverlay.get(1).isWritten()); |
| assertFalse(valuesRemovedOverlay.get(1).isTouched()); |
| assertTrue(valuesRemovedOverlay.get(1).isRemoved()); |
| |
| // write and check the result of writeResourceFolder |
| // copy the current resOut which serves as pre incremental update state. |
| File resFolder = getFolderCopy(new File(root, "resOut")); |
| |
| // write the content of the resource merger. |
| MergedResourceWriter writer = new MergedResourceWriter(resFolder, mPngCruncher, false, |
| false, null); |
| resourceMerger.mergeData(writer, false /*doCleanUp*/); |
| |
| // Check the content. |
| // values/values.xml |
| Map<String, String> map = quickStringOnlyValueFileParser( |
| new File(resFolder, "values" + File.separator + "values.xml")); |
| assertEquals("untouched", map.get("untouched")); |
| assertEquals("untouched", map.get("removed_overlay")); |
| } |
| |
| public void testUpdateWithFilesVsValues() throws Exception { |
| File root = getIncMergeRoot("filesVsValues"); |
| File fakeRoot = getMergedBlobFolder(root); |
| ResourceMerger resourceMerger = new ResourceMerger(); |
| assertTrue(resourceMerger.loadFromBlob(fakeRoot, true /*incrementalState*/)); |
| checkSourceFolders(resourceMerger); |
| |
| List<ResourceSet> sets = resourceMerger.getDataSets(); |
| assertEquals(1, sets.size()); |
| |
| RecordingLogger logger = new RecordingLogger(); |
| |
| // ---------------- |
| // Load the main set |
| ResourceSet mainSet = sets.get(0); |
| File mainBase = new File(root, "main"); |
| File mainValues = new File(mainBase, ResourceFolderType.VALUES.getName()); |
| File mainLayout = new File(mainBase, ResourceFolderType.LAYOUT.getName()); |
| |
| // touched file: |
| File mainValuesTouched = new File(mainValues, "values.xml"); |
| mainSet.updateWith(mainBase, mainValuesTouched, FileStatus.CHANGED, logger); |
| checkLogger(logger); |
| |
| // new file: |
| File mainLayoutNew = new File(mainLayout, "alias_replaced_by_file.xml"); |
| mainSet.updateWith(mainBase, mainLayoutNew, FileStatus.NEW, logger); |
| checkLogger(logger); |
| |
| // removed file |
| File mainLayoutRemoved = new File(mainLayout, "file_replaced_by_alias.xml"); |
| mainSet.updateWith(mainBase, mainLayoutRemoved, FileStatus.REMOVED, logger); |
| checkLogger(logger); |
| |
| // validate for duplicates |
| resourceMerger.validateDataSets(); |
| |
| // check the content. |
| ListMultimap<String, ResourceItem> mergedMap = resourceMerger.getDataMap(); |
| |
| // check layout/main is unchanged |
| List<ResourceItem> layoutMain = mergedMap.get("layout/main"); |
| assertEquals(1, layoutMain.size()); |
| assertTrue(layoutMain.get(0).isWritten()); |
| assertFalse(layoutMain.get(0).isTouched()); |
| assertFalse(layoutMain.get(0).isRemoved()); |
| |
| // check file_replaced_by_alias has 2 version, 2nd is TOUCHED, and contains a Node |
| List<ResourceItem> layoutReplacedByAlias = mergedMap.get("layout/file_replaced_by_alias"); |
| assertEquals(2, layoutReplacedByAlias.size()); |
| // 1st one is removed version, as it already existed in the item multimap |
| ResourceItem replacedByAlias = layoutReplacedByAlias.get(0); |
| assertTrue(replacedByAlias.isWritten()); |
| assertFalse(replacedByAlias.isTouched()); |
| assertTrue(replacedByAlias.isRemoved()); |
| assertNull(replacedByAlias.getValue()); |
| assertEquals("file_replaced_by_alias.xml", replacedByAlias.getSource().getFile().getName()); |
| // 2nd version is the new one |
| replacedByAlias = layoutReplacedByAlias.get(1); |
| assertFalse(replacedByAlias.isWritten()); |
| assertTrue(replacedByAlias.isTouched()); |
| assertFalse(replacedByAlias.isRemoved()); |
| assertNotNull(replacedByAlias.getValue()); |
| assertEquals("values.xml", replacedByAlias.getSource().getFile().getName()); |
| |
| // check alias_replaced_by_file has 2 version, 2nd is TOUCHED, and contains a Node |
| List<ResourceItem> layoutReplacedByFile = mergedMap.get("layout/alias_replaced_by_file"); |
| // 1st one is removed version, as it already existed in the item multimap |
| assertEquals(2, layoutReplacedByFile.size()); |
| ResourceItem replacedByFile = layoutReplacedByFile.get(0); |
| assertTrue(replacedByFile.isWritten()); |
| assertFalse(replacedByFile.isTouched()); |
| assertTrue(replacedByFile.isRemoved()); |
| assertNotNull(replacedByFile.getValue()); |
| assertEquals("values.xml", replacedByFile.getSource().getFile().getName()); |
| // 2nd version is the new one |
| replacedByFile = layoutReplacedByFile.get(1); |
| assertFalse(replacedByFile.isWritten()); |
| assertTrue(replacedByFile.isTouched()); |
| assertFalse(replacedByFile.isRemoved()); |
| assertNull(replacedByFile.getValue()); |
| assertEquals("alias_replaced_by_file.xml", replacedByFile.getSource().getFile().getName()); |
| |
| // write and check the result of writeResourceFolder |
| // copy the current resOut which serves as pre incremental update state. |
| File resFolder = getFolderCopy(new File(root, "resOut")); |
| |
| // write the content of the resource merger. |
| MergedResourceWriter writer = new MergedResourceWriter(resFolder, mPngCruncher, false, |
| false, null); |
| resourceMerger.mergeData(writer, false /*doCleanUp*/); |
| |
| // deleted layout/file_replaced_by_alias.xml |
| assertFalse(new File(resFolder, "layout" + File.separator + "file_replaced_by_alias.xml") |
| .isFile()); |
| // new file layout/alias_replaced_by_file.xml |
| assertTrue(new File(resFolder, "layout" + File.separator + "alias_replaced_by_file.xml") |
| .isFile()); |
| |
| // quick load of the values file |
| File valuesFile = new File(resFolder, "values" + File.separator + "values.xml"); |
| assertTrue(valuesFile.isFile()); |
| String content = Files.toString(valuesFile, Charsets.UTF_8); |
| assertTrue(content.contains("name=\"file_replaced_by_alias\"")); |
| assertFalse(content.contains("name=\"alias_replaced_by_file\"")); |
| } |
| |
| public void testCheckValidUpdate() throws Exception { |
| // first merger |
| ResourceMerger merger1 = createMerger(new String[][] { |
| new String[] { "main", ("/main/res1"), ("/main/res2") }, |
| new String[] { "overlay", ("/overlay/res1"), ("/overlay/res2") }, |
| }); |
| |
| // 2nd merger with different order source files in sets. |
| ResourceMerger merger2 = createMerger(new String[][] { |
| new String[] { "main", ("/main/res2"), ("/main/res1") }, |
| new String[] { "overlay", ("/overlay/res1"), ("/overlay/res2") }, |
| }); |
| |
| assertTrue(merger1.checkValidUpdate(merger2.getDataSets())); |
| |
| // write merger1 on disk to test writing empty ResourceSets. |
| File folder = Files.createTempDir(); |
| merger1.writeBlobTo(folder, |
| new MergedResourceWriter(Files.createTempDir(), mPngCruncher, false, false, null)); |
| |
| // reload it |
| ResourceMerger loadedMerger = new ResourceMerger(); |
| assertTrue(loadedMerger.loadFromBlob(folder, true /*incrementalState*/)); |
| |
| String expected = merger1.toString(); |
| String actual = loadedMerger.toString(); |
| if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) { |
| expected = expected.replaceAll(Pattern.quote(File.separator), "/"). |
| replaceAll("[A-Z]:/", "/"); |
| actual = actual.replaceAll(Pattern.quote(File.separator), "/"). |
| replaceAll("[A-Z]:/", "/"); |
| assertEquals("Actual: " + actual + "\nExpected: " + expected, expected, actual); |
| } else { |
| assertTrue("Actual: " + actual + "\nExpected: " + expected, |
| loadedMerger.checkValidUpdate(merger1.getDataSets())); |
| } |
| } |
| |
| public void testUpdateWithRemovedOverlay() throws Exception { |
| // Test with removed overlay |
| ResourceMerger merger1 = createMerger(new String[][] { |
| new String[] { "main", "/main/res1", "/main/res2" }, |
| new String[] { "overlay", "/overlay/res1", "/overlay/res2" }, |
| }); |
| |
| // 2nd merger with different order source files in sets. |
| ResourceMerger merger2 = createMerger(new String[][]{ |
| new String[]{"main", "/main/res2", "/main/res1"}, |
| }); |
| |
| assertFalse(merger1.checkValidUpdate(merger2.getDataSets())); |
| } |
| |
| public void testUpdateWithReplacedOverlays() throws Exception { |
| // Test with different overlays |
| ResourceMerger merger1 = createMerger(new String[][] { |
| new String[] { "main", "/main/res1", "/main/res2" }, |
| new String[] { "overlay", "/overlay/res1", "/overlay/res2" }, |
| }); |
| |
| // 2nd merger with different order source files in sets. |
| ResourceMerger merger2 = createMerger(new String[][] { |
| new String[] { "main", "/main/res2", "/main/res1" }, |
| new String[] { "overlay2", "/overlay2/res1", "/overlay2/res2" }, |
| }); |
| |
| assertFalse(merger1.checkValidUpdate(merger2.getDataSets())); |
| } |
| |
| public void testUpdateWithReorderedOverlays() throws Exception { |
| // Test with different overlays |
| ResourceMerger merger1 = createMerger(new String[][] { |
| new String[] { "main", "/main/res1", "/main/res2" }, |
| new String[] { "overlay1", "/overlay1/res1", "/overlay1/res2" }, |
| new String[] { "overlay2", "/overlay2/res1", "/overlay2/res2" }, |
| }); |
| |
| // 2nd merger with different order source files in sets. |
| ResourceMerger merger2 = createMerger(new String[][] { |
| new String[] { "main", "/main/res2", "/main/res1" }, |
| new String[] { "overlay2", "/overlay2/res1", "/overlay2/res2" }, |
| new String[] { "overlay1", "/overlay1/res1", "/overlay1/res2" }, |
| }); |
| |
| assertFalse(merger1.checkValidUpdate(merger2.getDataSets())); |
| } |
| |
| public void testUpdateWithRemovedSourceFile() throws Exception { |
| // Test with different source files |
| ResourceMerger merger1 = createMerger(new String[][] { |
| new String[] { "main", "/main/res1", "/main/res2" }, |
| }); |
| |
| // 2nd merger with different order source files in sets. |
| ResourceMerger merger2 = createMerger(new String[][]{ |
| new String[]{"main", "/main/res1"}, |
| }); |
| |
| assertFalse(merger1.checkValidUpdate(merger2.getDataSets())); |
| } |
| |
| public void testChangedIgnoredFile() throws Exception { |
| ResourceSet res = ResourceSetTest.getBaseResourceSet(false /*normalize*/); |
| |
| ResourceMerger resourceMerger = new ResourceMerger(); |
| resourceMerger.addDataSet(res); |
| |
| File root = TestUtils.getRoot("resources", "baseSet"); |
| File changedCVSFoo = new File(root, "CVS/foo.txt"); |
| FileValidity<ResourceSet> fileValidity = resourceMerger.findDataSetContaining( |
| changedCVSFoo); |
| |
| assertEquals(FileValidity.FileStatus.IGNORED_FILE, fileValidity.status); |
| } |
| |
| public void testIncDataForRemovedFile() throws Exception { |
| File root = TestUtils.getCanonicalRoot("resources", "removedFile"); |
| File fakeBlobRoot = getMergedBlobFolder(root); |
| |
| ResourceMerger resourceMerger = new ResourceMerger(); |
| assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/)); |
| checkSourceFolders(resourceMerger); |
| |
| List<ResourceSet> sets = resourceMerger.getDataSets(); |
| assertEquals(1, sets.size()); |
| |
| RecordingLogger logger = new RecordingLogger(); |
| |
| // ---------------- |
| // Load the main set |
| ResourceSet mainSet = sets.get(0); |
| File resBase = new File(root, "res"); |
| File resDrawable = new File(resBase, ResourceFolderType.DRAWABLE.getName()); |
| |
| // removed file |
| File resIconRemoved = new File(resDrawable, "removed.png"); |
| mainSet.updateWith(resBase, resIconRemoved, FileStatus.REMOVED, logger); |
| checkLogger(logger); |
| |
| // validate for duplicates |
| resourceMerger.validateDataSets(); |
| |
| // check the content. |
| ListMultimap<String, ResourceItem> mergedMap = resourceMerger.getDataMap(); |
| |
| // check layout/main is unchanged |
| List<ResourceItem> removedIcon = mergedMap.get("drawable/removed"); |
| assertEquals(1, removedIcon.size()); |
| assertTrue(removedIcon.get(0).isRemoved()); |
| assertTrue(removedIcon.get(0).isWritten()); |
| assertFalse(removedIcon.get(0).isTouched()); |
| |
| // write and check the result of writeResourceFolder |
| // copy the current resOut which serves as pre incremental update state. |
| File outFolder = getFolderCopy(new File(root, "out")); |
| |
| // write the content of the resource merger. |
| MergedResourceWriter writer = new MergedResourceWriter(outFolder, mPngCruncher, false, |
| false, null); |
| resourceMerger.mergeData(writer, false /*doCleanUp*/); |
| |
| File outDrawableFolder = new File(outFolder, ResourceFolderType.DRAWABLE.getName()); |
| |
| // check the files are correct |
| assertFalse(new File(outDrawableFolder, "removed.png").isFile()); |
| assertTrue(new File(outDrawableFolder, "icon.png").isFile()); |
| |
| // now write the blob |
| File outBlobFolder = Files.createTempDir(); |
| resourceMerger.writeBlobTo(outBlobFolder, writer); |
| |
| // check the removed icon is not present. |
| ResourceMerger resourceMerger2 = new ResourceMerger(); |
| assertTrue(resourceMerger2.loadFromBlob(outBlobFolder, true /*incrementalState*/)); |
| |
| mergedMap = resourceMerger2.getDataMap(); |
| removedIcon = mergedMap.get("drawable/removed"); |
| assertTrue(removedIcon.isEmpty()); |
| } |
| |
| public void testMergedDeclareStyleable() throws Exception { |
| File root = TestUtils.getRoot("resources", "declareStyleable"); |
| |
| // load both base and overlay set |
| File baseRoot = new File(root, "base"); |
| ResourceSet baseSet = new ResourceSet("main"); |
| baseSet.addSource(baseRoot); |
| RecordingLogger logger = new RecordingLogger(); |
| baseSet.loadFromFiles(logger); |
| checkLogger(logger); |
| |
| File overlayRoot = new File(root, "overlay"); |
| ResourceSet overlaySet = new ResourceSet("overlay"); |
| overlaySet.addSource(overlayRoot); |
| logger = new RecordingLogger(); |
| overlaySet.loadFromFiles(logger); |
| checkLogger(logger); |
| |
| // create a merger |
| ResourceMerger resourceMerger = new ResourceMerger(); |
| resourceMerger.addDataSet(baseSet); |
| resourceMerger.addDataSet(overlaySet); |
| |
| // write the merge result. |
| File folder = Files.createTempDir(); |
| folder.deleteOnExit(); |
| |
| MergedResourceWriter writer = new MergedResourceWriter(folder, mPngCruncher, false, false, null); |
| resourceMerger.mergeData(writer, false /*doCleanUp*/); |
| |
| // load the result as a set. |
| ResourceSet mergedSet = new ResourceSet("merged"); |
| mergedSet.addSource(folder); |
| logger = new RecordingLogger(); |
| mergedSet.loadFromFiles(logger); |
| checkLogger(logger); |
| |
| ListMultimap<String, ResourceItem> map = mergedSet.getDataMap(); |
| assertEquals(4, map.size()); |
| |
| List<ResourceItem> items = map.get("declare-styleable/foo"); |
| assertNotNull(items); |
| assertEquals(1, items.size()); |
| |
| ResourceItem item = items.get(0); |
| assertNotNull(item); |
| |
| // now we need to look at the item's value (which is the XML). |
| // We're looking for 3 attributes. |
| List<String> expectedAttrs = Lists.newArrayList("bar", "bar1", "boo"); |
| Node rootNode = item.getValue(); |
| assertNotNull(rootNode); |
| NodeList sourceNodes = rootNode.getChildNodes(); |
| for (int i = 0, n = sourceNodes.getLength(); i < n; i++) { |
| Node sourceNode = sourceNodes.item(i); |
| |
| if (sourceNode.getNodeType() != Node.ELEMENT_NODE || |
| !TAG_ATTR.equals(sourceNode.getLocalName())) { |
| continue; |
| } |
| |
| Attr attr = (Attr) sourceNode.getAttributes().getNamedItem(ATTR_NAME); |
| if (attr == null) { |
| continue; |
| } |
| |
| String attrName = attr.getValue(); |
| |
| assertTrue("Check expected " + attrName, expectedAttrs.contains(attrName)); |
| expectedAttrs.remove(attrName); |
| } |
| |
| assertTrue("Check emptiness of " + expectedAttrs.toString(), expectedAttrs.isEmpty()); |
| } |
| |
| public void testUnchangedMergedItem() throws Exception { |
| // locate the merger file that contains exactly the result of the source folders. |
| File root = TestUtils.getRoot("resources", "declareStyleable"); |
| File fakeBlobRoot = getMergedBlobFolder(root, new File(root, "unchanged_merger.xml")); |
| |
| // load a resource merger based on it. |
| ResourceMerger resourceMerger = new ResourceMerger(); |
| assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/)); |
| checkSourceFolders(resourceMerger); |
| |
| // create a fake consumer |
| FakeMergeConsumer consumer = new FakeMergeConsumer(); |
| |
| // do the merge |
| resourceMerger.mergeData(consumer, false /*doCleanUp*/); |
| |
| // test result of merger. |
| assertTrue(consumer.touchedItems.isEmpty()); |
| assertTrue(consumer.removedItems.isEmpty()); |
| } |
| |
| public void testRemovedMergedItem() throws Exception { |
| // locate the merger file that contains exactly the result of the source folders. |
| File root = TestUtils.getCanonicalRoot("resources", "declareStyleable"); |
| File fakeBlobRoot = getMergedBlobFolder(root, new File(root, "removed_merger.xml")); |
| |
| // load a resource merger based on it. |
| ResourceMerger resourceMerger = new ResourceMerger(); |
| assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/)); |
| checkSourceFolders(resourceMerger); |
| |
| // we know have to tell the merger that the values files have been touched |
| // to trigger the removal detection based on the original merger blob. |
| |
| List<ResourceSet> sets = resourceMerger.getDataSets(); |
| assertEquals(2, sets.size()); |
| |
| RecordingLogger logger = new RecordingLogger(); |
| |
| // ---------------- |
| // Load the main set |
| ResourceSet mainSet = sets.get(0); |
| File mainRoot = new File(root, "base"); |
| File mainValues = new File(mainRoot, ResourceFolderType.VALUES.getName()); |
| |
| // trigger changed file event |
| File touchedValueFile = new File(mainValues, "values.xml"); |
| mainSet.updateWith(mainRoot, touchedValueFile, FileStatus.CHANGED, logger); |
| checkLogger(logger); |
| |
| // same with overlay set. |
| ResourceSet overlaySet = sets.get(1); |
| File overlayRoot = new File(root, "overlay"); |
| File overlayValues = new File(overlayRoot, ResourceFolderType.VALUES.getName()); |
| |
| // trigger changed file event |
| touchedValueFile = new File(overlayValues, "values.xml"); |
| overlaySet.updateWith(overlayRoot, touchedValueFile, FileStatus.CHANGED, logger); |
| checkLogger(logger); |
| |
| // create a fake consumer |
| FakeMergeConsumer consumer = new FakeMergeConsumer(); |
| |
| // do the merge |
| resourceMerger.mergeData(consumer, false /*doCleanUp*/); |
| |
| // test result of merger. |
| assertTrue(consumer.touchedItems.isEmpty()); |
| assertEquals(1, consumer.removedItems.size()); |
| } |
| |
| public void testTouchedMergedItem() throws Exception { |
| // locate the merger file that contains exactly the result of the source folders. |
| File root = TestUtils.getCanonicalRoot("resources", "declareStyleable"); |
| File fakeBlobRoot = getMergedBlobFolder(root, new File(root, "touched_merger.xml")); |
| |
| // load a resource merger based on it. |
| ResourceMerger resourceMerger = new ResourceMerger(); |
| assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/)); |
| checkSourceFolders(resourceMerger); |
| |
| // we know have to tell the merger that the values files have been touched |
| // to trigger the removal detection based on the original merger blob. |
| |
| List<ResourceSet> sets = resourceMerger.getDataSets(); |
| assertEquals(2, sets.size()); |
| |
| RecordingLogger logger = new RecordingLogger(); |
| |
| // ---------------- |
| // Load the main set |
| ResourceSet mainSet = sets.get(0); |
| File mainRoot = new File(root, "base"); |
| File mainValues = new File(mainRoot, ResourceFolderType.VALUES.getName()); |
| |
| // trigger changed file event |
| File touchedValueFile = new File(mainValues, "values.xml"); |
| mainSet.updateWith(mainRoot, touchedValueFile, FileStatus.CHANGED, logger); |
| checkLogger(logger); |
| |
| // create a fake consumer |
| FakeMergeConsumer consumer = new FakeMergeConsumer(); |
| |
| // do the merge |
| resourceMerger.mergeData(consumer, false /*doCleanUp*/); |
| |
| // test result of merger. |
| assertEquals(1, consumer.touchedItems.size()); |
| assertTrue(consumer.removedItems.isEmpty()); |
| } |
| |
| public void testTouchedNoDiffMergedItem() throws Exception { |
| // locate the merger file that contains exactly the result of the source folders. |
| File root = TestUtils.getCanonicalRoot("resources", "declareStyleable"); |
| File fakeBlobRoot = getMergedBlobFolder(root, new File(root, "touched_nodiff_merger.xml")); |
| |
| // load a resource merger based on it. |
| ResourceMerger resourceMerger = new ResourceMerger(); |
| assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/)); |
| checkSourceFolders(resourceMerger); |
| |
| // we know have to tell the merger that the values files have been touched |
| // to trigger the removal detection based on the original merger blob. |
| |
| List<ResourceSet> sets = resourceMerger.getDataSets(); |
| assertEquals(2, sets.size()); |
| |
| RecordingLogger logger = new RecordingLogger(); |
| |
| // ---------------- |
| // Load the overlay set |
| ResourceSet overlaySet = sets.get(1); |
| File overlayRoot = new File(root, "overlay"); |
| File overlayValues = new File(overlayRoot, ResourceFolderType.VALUES.getName()); |
| |
| // trigger changed file event |
| File touchedValueFile = new File(overlayValues, "values.xml"); |
| overlaySet.updateWith(overlayRoot, touchedValueFile, FileStatus.CHANGED, logger); |
| checkLogger(logger); |
| |
| // create a fake consumer |
| FakeMergeConsumer consumer = new FakeMergeConsumer(); |
| |
| // do the merge |
| resourceMerger.mergeData(consumer, false /*doCleanUp*/); |
| |
| // test result of merger. |
| assertTrue(consumer.touchedItems.isEmpty()); |
| assertTrue(consumer.removedItems.isEmpty()); |
| } |
| |
| public void testRemovedOtherWithNoNoDiffTouchMergedItem() throws Exception { |
| // test that when a non-merged resources is changed/removed, the result of the merge still |
| // contain the merged items even if they were touched but had no change. |
| |
| // locate the merger file that contains exactly the result of the source folders. |
| File root = TestUtils.getCanonicalRoot("resources", "declareStyleable"); |
| File fakeBlobRoot = getMergedBlobFolder(root, new File(root, "removed_other_merger.xml")); |
| |
| // load a resource merger based on it. |
| ResourceMerger resourceMerger = new ResourceMerger(); |
| assertTrue(resourceMerger.loadFromBlob(fakeBlobRoot, true /*incrementalState*/)); |
| checkSourceFolders(resourceMerger); |
| |
| // we know have to tell the merger that the values files have been touched |
| // to trigger the removal detection based on the original merger blob. |
| |
| List<ResourceSet> sets = resourceMerger.getDataSets(); |
| assertEquals(2, sets.size()); |
| |
| RecordingLogger logger = new RecordingLogger(); |
| |
| // ---------------- |
| // Load the main set |
| ResourceSet mainSet = sets.get(0); |
| File mainRoot = new File(root, "base"); |
| File mainValues = new File(mainRoot, ResourceFolderType.VALUES.getName()); |
| |
| // trigger changed file event |
| File touchedValueFile = new File(mainValues, "values.xml"); |
| mainSet.updateWith(mainRoot, touchedValueFile, FileStatus.CHANGED, logger); |
| checkLogger(logger); |
| |
| // same for overlay |
| ResourceSet overlaySet = sets.get(1); |
| File overlayRoot = new File(root, "overlay"); |
| File overlayValues = new File(overlayRoot, ResourceFolderType.VALUES.getName()); |
| |
| // trigger changed file event |
| touchedValueFile = new File(overlayValues, "values.xml"); |
| overlaySet.updateWith(overlayRoot, touchedValueFile, FileStatus.CHANGED, logger); |
| checkLogger(logger); |
| |
| // create a fake consumer |
| FakeMergeConsumer consumer = new FakeMergeConsumer(); |
| |
| // do the merge |
| resourceMerger.mergeData(consumer, false /*doCleanUp*/); |
| |
| // test result of merger. |
| // only 3 items added since attr/bar isn't added (declared inline) |
| assertEquals(3, consumer.addedItems.size()); |
| // no touched items |
| assertTrue(consumer.touchedItems.isEmpty()); |
| // one removed string item |
| assertEquals(1, consumer.removedItems.size()); |
| } |
| |
| public void testStringWhiteSpaces() throws Exception { |
| File root = TestUtils.getRoot("resources", "stringWhiteSpaces"); |
| |
| // load res folder |
| ResourceSet baseSet = new ResourceSet("main"); |
| baseSet.addSource(root); |
| RecordingLogger logger = new RecordingLogger(); |
| baseSet.loadFromFiles(logger); |
| checkLogger(logger); |
| |
| // create a merger |
| ResourceMerger resourceMerger = new ResourceMerger(); |
| resourceMerger.addDataSet(baseSet); |
| |
| // write the merge result. |
| File folder = Files.createTempDir(); |
| folder.deleteOnExit(); |
| |
| MergedResourceWriter writer = new MergedResourceWriter(folder, mPngCruncher, false, false, null); |
| resourceMerger.mergeData(writer, false /*doCleanUp*/); |
| |
| // load the result as a set. |
| ResourceSet mergedSet = new ResourceSet("merged"); |
| mergedSet.addSource(folder); |
| logger = new RecordingLogger(); |
| mergedSet.loadFromFiles(logger); |
| checkLogger(logger); |
| |
| ListMultimap<String, ResourceItem> originalItems = baseSet.getDataMap(); |
| ListMultimap<String, ResourceItem> mergedItems = mergedSet.getDataMap(); |
| |
| for (Map.Entry<String, Collection<ResourceItem>> entry : originalItems.asMap().entrySet()) { |
| Collection<ResourceItem> originalItemList = entry.getValue(); |
| Collection<ResourceItem> mergedItemList = mergedItems.asMap().get(entry.getKey()); |
| |
| // the collection should only have a single items |
| assertEquals(1, originalItemList.size()); |
| assertEquals(1, mergedItemList.size()); |
| |
| ResourceItem originalItem = originalItemList.iterator().next(); |
| ResourceItem mergedItem = mergedItemList.iterator().next(); |
| |
| assertTrue(originalItem.compareValueWith(mergedItem)); |
| } |
| } |
| |
| /** |
| * Creates a fake merge with given sets. |
| * |
| * the data is an array of sets. |
| * |
| * Each set is [ setName, folder1, folder2, ...] |
| * |
| * @param data the data sets |
| * @return the merger |
| */ |
| private static ResourceMerger createMerger(String[][] data) { |
| ResourceMerger merger = new ResourceMerger(); |
| for (String[] setData : data) { |
| ResourceSet set = new ResourceSet(setData[0]); |
| merger.addDataSet(set); |
| for (int i = 1, n = setData.length; i < n; i++) { |
| set.addSource(new File(setData[i])); |
| } |
| } |
| |
| return merger; |
| } |
| |
| private static ResourceMerger getResourceMerger(boolean normalize) |
| throws MergingException, IOException { |
| File root = TestUtils.getRoot("resources", "baseMerge"); |
| |
| ResourceSet res = ResourceSetTest.getBaseResourceSet(normalize); |
| |
| RecordingLogger logger = new RecordingLogger(); |
| |
| ResourceSet overlay = new ResourceSet("overlay"); |
| overlay.setNormalizeResources(normalize); |
| overlay.addSource(new File(root, "overlay")); |
| overlay.loadFromFiles(logger); |
| |
| checkLogger(logger); |
| |
| ResourceMerger resourceMerger = new ResourceMerger(); |
| resourceMerger.addDataSet(res); |
| resourceMerger.addDataSet(overlay); |
| |
| return resourceMerger; |
| } |
| |
| private File getWrittenResources() throws MergingException, IOException { |
| ResourceMerger resourceMerger = getResourceMerger(false /*normalize*/); |
| |
| File folder = Files.createTempDir(); |
| |
| MergedResourceWriter writer = new MergedResourceWriter(folder, mPngCruncher, false, false, null); |
| resourceMerger.mergeData(writer, false /*doCleanUp*/); |
| |
| return folder; |
| } |
| |
| private static File getIncMergeRoot(String name) throws IOException { |
| File root = TestUtils.getCanonicalRoot("resources", "incMergeData"); |
| return new File(root, name); |
| } |
| |
| private static File getFolderCopy(File folder) throws IOException { |
| File dest = Files.createTempDir(); |
| copyFolder(folder, dest); |
| return dest; |
| } |
| |
| private static void copyFolder(File from, File to) throws IOException { |
| if (from.isFile()) { |
| Files.copy(from, to); |
| } else if (from.isDirectory()) { |
| if (!to.exists()) { |
| to.mkdirs(); |
| } |
| |
| File[] children = from.listFiles(); |
| if (children != null) { |
| for (File f : children) { |
| copyFolder(f, new File(to, f.getName())); |
| } |
| } |
| } |
| } |
| |
| private static Map<String, String> quickStringOnlyValueFileParser(File file) |
| throws IOException, MergingException { |
| Map<String, String> result = Maps.newHashMap(); |
| |
| Document document = ValueResourceParser2.parseDocument(file); |
| |
| // get the root node |
| Node rootNode = document.getDocumentElement(); |
| if (rootNode == null) { |
| return Collections.emptyMap(); |
| } |
| |
| NodeList nodes = rootNode.getChildNodes(); |
| |
| for (int i = 0, n = nodes.getLength(); i < n; i++) { |
| Node node = nodes.item(i); |
| |
| if (node.getNodeType() != Node.ELEMENT_NODE) { |
| continue; |
| } |
| if (node.getNodeName().equals(SdkConstants.TAG_EAT_COMMENT)) { |
| continue; |
| } |
| |
| ResourceType type = ValueResourceParser2.getType(node, file); |
| if (type != ResourceType.STRING) { |
| throw new IllegalArgumentException("Only String resources supported."); |
| } |
| String name = ValueResourceParser2.getName(node); |
| |
| String value = null; |
| |
| NodeList nodeList = node.getChildNodes(); |
| nodeLoop: for (int ii = 0, nn = nodes.getLength(); ii < nn; ii++) { |
| Node subNode = nodeList.item(ii); |
| |
| switch (subNode.getNodeType()) { |
| case Node.COMMENT_NODE: |
| break; |
| case Node.TEXT_NODE: |
| value = subNode.getNodeValue().trim(); // TODO: remove trim. |
| break nodeLoop; |
| case Node.ELEMENT_NODE: |
| break; |
| } |
| } |
| |
| result.put(name, value != null ? value : ""); |
| } |
| |
| return result; |
| } |
| |
| public void testAppendedSourceComment() throws Exception { |
| ResourceMerger merger = getResourceMerger(false /*normalize*/); |
| RecordingLogger logger = new RecordingLogger(); |
| File folder = getWrittenResources(); |
| ResourceSet writtenSet = new ResourceSet("unused"); |
| writtenSet.addSource(folder); |
| writtenSet.loadFromFiles(logger); |
| compareResourceMaps(merger, writtenSet, false /*full compare*/); |
| checkLogger(logger); |
| |
| File layout = new File(folder, FD_RES_LAYOUT + File.separator + "main.xml"); |
| assertTrue(layout.exists()); |
| String layoutXml = Files.toString(layout, Charsets.UTF_8); |
| assertTrue(layoutXml.contains("main.xml")); // in <!-- From: /full/path/to/main.xml --> |
| int index = layoutXml.indexOf("From: "); |
| assertTrue(index != -1); |
| String path = layoutXml.substring(index + 6, layoutXml.indexOf(' ', index + 6)); |
| File file = SdkUtils.urlToFile(path); |
| assertTrue(path, file.exists()); |
| assertFalse(Arrays.equals(Files.toByteArray(file), Files.toByteArray(layout))); |
| |
| // Also make sure .png files were NOT modified |
| File root = TestUtils.getRoot("resources", "baseMerge"); |
| assertNotNull(root); |
| File original = new File(root, |
| "overlay/drawable-ldpi/icon.png".replace('/', File.separatorChar)); |
| File copied = new File(folder, FD_RES_DRAWABLE + File.separator + "icon.png"); |
| assertTrue(Arrays.equals(Files.toByteArray(original), Files.toByteArray(copied))); |
| } |
| |
| public void testWritePermission() throws Exception { |
| ResourceMerger merger = getResourceMerger(false /*normalize*/); |
| |
| File folder = Files.createTempDir(); |
| boolean writable = folder.setWritable(false); |
| if (!writable) { |
| // Not supported on this platform |
| return; |
| } |
| try { |
| merger.writeBlobTo(folder, |
| new MergedResourceWriter(Files.createTempDir(), mPngCruncher, false, false, null)); |
| } catch (MergingException e) { |
| File file = new File(folder, "merger.xml"); |
| assertEquals(file.getPath() + ": Error: (Permission denied)", |
| e.getMessage()); |
| return; |
| } |
| fail("Exception not thrown as expected"); |
| } |
| |
| public void testWriteAndReadBlob() throws Exception { |
| ResourceMerger merger = getResourceMerger(false /*normalize*/); |
| |
| File folder = Files.createTempDir(); |
| merger.writeBlobTo(folder, |
| new MergedResourceWriter(Files.createTempDir(), mPngCruncher, false, false, null)); |
| |
| // new merger to read the blob |
| ResourceMerger loadedMerger = new ResourceMerger(); |
| assertTrue(loadedMerger.loadFromBlob(folder, true /*incrementalState*/)); |
| } |
| |
| public void testInvalidFileNames() throws Exception { |
| File root = TestUtils.getRoot("resources", "brokenSet5"); |
| ResourceSet resourceSet = new ResourceSet("brokenSet5"); |
| resourceSet.addSource(root); |
| RecordingLogger logger = new RecordingLogger(); |
| |
| try { |
| resourceSet.loadFromFiles(logger); |
| } catch (MergingException e) { |
| File file = new File(root, "layout" + File.separator + "ActivityMain.xml"); |
| file = file.getAbsoluteFile(); |
| assertEquals( |
| file.getPath() + |
| ": Error: File-based resource names must start with a lowercase letter", |
| e.getMessage()); |
| return; |
| } |
| fail("Expected error"); |
| } |
| |
| public void testStricterInvalidFileNames() throws Exception { |
| File root = TestUtils.getRoot("resources", "brokenSetDrawableFileName"); |
| ResourceSet resourceSet = new ResourceSet("brokenSetDrawableFileName"); |
| resourceSet.addSource(root); |
| RecordingLogger logger = new RecordingLogger(); |
| |
| try { |
| resourceSet.loadFromFiles(logger); |
| } catch (MergingException e) { |
| File file = new File(root, "drawable" + File.separator + "1icon.png"); |
| file = file.getAbsoluteFile(); |
| assertEquals( |
| file.getPath() + |
| ": Error: File-based resource names must start with a lowercase letter", |
| e.getMessage()); |
| return; |
| } |
| fail("Expected error"); |
| } |
| |
| public void testXmlParseError1() throws Exception { |
| File root = TestUtils.getRoot("resources", "brokenSet6"); |
| try { |
| ResourceSet resourceSet = new ResourceSet("brokenSet6"); |
| resourceSet.addSource(root); |
| RecordingLogger logger = new RecordingLogger(); |
| resourceSet.loadFromFiles(logger); |
| |
| ResourceMerger resourceMerger = new ResourceMerger(); |
| resourceMerger.addDataSet(resourceSet); |
| |
| |
| File folder = Files.createTempDir(); |
| MergedResourceWriter writer = new MergedResourceWriter(folder, mPngCruncher, false, |
| false, null); |
| resourceMerger.mergeData(writer, false /*doCleanUp*/); |
| } catch (MergingException e) { |
| File file = new File(root, "values" + File.separator + "dimens.xml"); |
| file = file.getAbsoluteFile(); |
| assertEquals(file.getPath() + ":3:5: Error: The content of elements must consist " |
| + "of well-formed character data or markup.", |
| e.getMessage()); |
| return; |
| } |
| fail("Expected error"); |
| } |
| |
| public void testXmlParseError7() throws Exception { |
| File root = TestUtils.getRoot("resources", "brokenSet7"); |
| try { |
| ResourceSet resourceSet = new ResourceSet("brokenSet7"); |
| resourceSet.addSource(root); |
| RecordingLogger logger = new RecordingLogger(); |
| resourceSet.loadFromFiles(logger); |
| |
| ResourceMerger resourceMerger = new ResourceMerger(); |
| resourceMerger.addDataSet(resourceSet); |
| |
| |
| File folder = Files.createTempDir(); |
| MergedResourceWriter writer = new MergedResourceWriter(folder, mPngCruncher, false, |
| false, null); |
| resourceMerger.mergeData(writer, false /*doCleanUp*/); |
| } catch (MergingException e) { |
| File file = new File(root, "values" + File.separator + "dimens.xml"); |
| file = file.getAbsoluteFile(); |
| assertEquals(file.getPath() + ":1:16: Error: Open quote is expected for " |
| + "attribute \"{1}\" associated with an element type \"name\".", |
| e.getMessage()); |
| return; |
| } |
| fail("Expected error"); |
| } |
| |
| |
| // create a fake consumer |
| private static class FakeMergeConsumer implements MergeConsumer<ResourceItem> { |
| final List<ResourceItem> addedItems = Lists.newArrayList(); |
| final List<ResourceItem> touchedItems = Lists.newArrayList(); |
| final List<ResourceItem> removedItems = Lists.newArrayList(); |
| |
| @Override |
| public void start(@NonNull DocumentBuilderFactory factory) |
| throws ConsumerException { |
| // do nothing |
| } |
| |
| @Override |
| public void end() throws ConsumerException { |
| // do nothing |
| } |
| |
| @Override |
| public void addItem(@NonNull ResourceItem item) throws ConsumerException { |
| // the default res merge writer calls this, so we should too. |
| // this is to test that the merged item are properly created |
| @SuppressWarnings("UnusedDeclaration") |
| ResourceFile.FileType type = item.getSourceType(); |
| |
| if (item.isTouched()) { |
| touchedItems.add(item); |
| } |
| |
| addedItems.add(item); |
| } |
| |
| @Override |
| public void removeItem(@NonNull ResourceItem removedItem, |
| @Nullable ResourceItem replacedBy) |
| throws ConsumerException { |
| removedItems.add(removedItem); |
| } |
| |
| @Override |
| public boolean ignoreItemInMerge(ResourceItem item) { |
| return item.getIgnoredFromDiskMerge(); |
| } |
| } |
| } |