/*
* Copyright (C) 2013 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 com.android.SdkConstants;
import com.android.testutils.TestUtils;
import com.google.common.collect.ListMultimap;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.regex.Pattern;
public class AssetMergerTest extends BaseTestCase {
private static AssetMerger sAssetMerger = null;
public void testMergeByCount() throws Exception {
AssetMerger merger = getAssetMerger();
assertEquals(5, merger.size());
}
public void testMergedAssetsByName() throws Exception {
AssetMerger merger = getAssetMerger();
verifyResourceExists(merger,
"foo/icon.png",
"icon2.png",
"main.xml",
"values.xml",
"foo/foo.dat"
);
}
public void testMergeWrite() throws Exception {
AssetMerger merger = getAssetMerger();
File folder = getWrittenResources();
RecordingLogger logger = new RecordingLogger();
AssetSet writtenSet = new AssetSet("unused");
writtenSet.addSource(folder);
writtenSet.loadFromFiles(logger);
checkLogger(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 AssetItem item.
compareResourceMaps(merger, writtenSet, false /*full compare*/);
}
public void testMergeBlob() throws Exception {
AssetMerger merger = getAssetMerger();
File folder = Files.createTempDir();
merger.writeBlobTo(folder, new MergedAssetWriter(Files.createTempDir()));
AssetMerger loadedMerger = new AssetMerger();
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("assets", "baseMerge");
File fakeRoot = getMergedBlobFolder(root);
AssetMerger assetMerger = new AssetMerger();
assetMerger.loadFromBlob(fakeRoot, true /*incrementalState*/);
checkSourceFolders(assetMerger);
List<AssetSet> sets = assetMerger.getDataSets();
for (AssetSet 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 testUpdate() throws Exception {
File root = getIncMergeRoot("basicFiles");
File fakeRoot = getMergedBlobFolder(root);
AssetMerger assetMerger = new AssetMerger();
assetMerger.loadFromBlob(fakeRoot, true /*incrementalState*/);
checkSourceFolders(assetMerger);
List<AssetSet> sets = assetMerger.getDataSets();
assertEquals(2, sets.size());
RecordingLogger logger = new RecordingLogger();
// ----------------
// first set is the main one, no change here
AssetSet mainSet = sets.get(0);
File mainFolder = new File(root, "main");
// touched/removed files:
File mainTouched = new File(mainFolder, "touched.png");
mainSet.updateWith(mainFolder, mainTouched, FileStatus.CHANGED, logger);
checkLogger(logger);
File mainRemoved = new File(mainFolder, "removed.png");
mainSet.updateWith(mainFolder, mainRemoved, FileStatus.REMOVED, logger);
checkLogger(logger);
File mainAdded = new File(mainFolder, "added.png");
mainSet.updateWith(mainFolder, mainAdded, FileStatus.NEW, logger);
checkLogger(logger);
// ----------------
// second set is the overlay one
AssetSet overlaySet = sets.get(1);
File overlayFolder = new File(root, "overlay");
// new/removed files:
File overlayAdded = new File(new File(overlayFolder, "foo"), "overlay_added.png");
overlaySet.updateWith(overlayFolder, overlayAdded, FileStatus.NEW, logger);
checkLogger(logger);
File overlayRemoved = new File(overlayFolder, "overlay_removed.png");
overlaySet.updateWith(overlayFolder, overlayRemoved, FileStatus.REMOVED, logger);
checkLogger(logger);
// validate for duplicates
assetMerger.validateDataSets();
// check the content.
ListMultimap<String, AssetItem> mergedMap = assetMerger.getDataMap();
// check untouched.png file is WRITTEN
List<AssetItem> untouchedItem = mergedMap.get("untouched.png");
assertEquals(1, untouchedItem.size());
assertTrue(untouchedItem.get(0).isWritten());
assertFalse(untouchedItem.get(0).isTouched());
assertFalse(untouchedItem.get(0).isRemoved());
// check touched.png file is TOUCHED
List<AssetItem> touchedItem = mergedMap.get("touched.png");
assertEquals(1, touchedItem.size());
assertTrue(touchedItem.get(0).isWritten());
assertTrue(touchedItem.get(0).isTouched());
assertFalse(touchedItem.get(0).isRemoved());
// check removed file is REMOVED
List<AssetItem> removedItem = mergedMap.get("removed.png");
assertEquals(1, removedItem.size());
assertTrue(removedItem.get(0).isWritten());
assertTrue(removedItem.get(0).isRemoved());
// check new overlay: two objects, last one is TOUCHED
List<AssetItem> overlayAddedItem = mergedMap.get("foo/overlay_added.png");
assertEquals(2, overlayAddedItem.size());
AssetItem newOverlay0 = overlayAddedItem.get(0);
assertTrue(newOverlay0.isWritten());
assertFalse(newOverlay0.isTouched());
AssetItem newOverlay1 = overlayAddedItem.get(1);
assertEquals(overlayAdded, newOverlay1.getSource().getFile());
assertFalse(newOverlay1.isWritten());
assertTrue(newOverlay1.isTouched());
// check removed overlay: two objects, last one is removed
List<AssetItem> overlayRemovedItem = mergedMap.get("overlay_removed.png");
assertEquals(2, overlayRemovedItem.size());
AssetItem overlayRemovedItem0 = overlayRemovedItem.get(0);
assertFalse(overlayRemovedItem0.isWritten());
assertFalse(overlayRemovedItem0.isTouched());
AssetItem overlayRemovedItem1 = overlayRemovedItem.get(1);
assertEquals(overlayRemoved, overlayRemovedItem1.getSource().getFile());
assertTrue(overlayRemovedItem1.isWritten());
assertTrue(overlayRemovedItem1.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, "assetOut"));
// write the content of the resource merger.
MergedAssetWriter writer = new MergedAssetWriter(resFolder);
assetMerger.mergeData(writer, false /*doCleanUp*/);
// Check the content by checking the colors. All files should be green
checkImageColor(new File(resFolder, "untouched.png"), (int) 0xFF00FF00);
checkImageColor(new File(resFolder, "touched.png"), (int) 0xFF00FF00);
checkImageColor(new File(resFolder, "added.png"), (int) 0xFF00FF00);
checkImageColor(new File(resFolder, "overlay_removed.png"), (int) 0xFF00FF00);
checkImageColor(new File(new File(resFolder, "foo"), "overlay_added.png"), (int) 0xFF00FF00);
// also check the removed file is not there.
assertFalse(new File(resFolder, "removed.png").isFile());
}
public void testCheckValidUpdate() throws Exception {
// first merger
AssetMerger 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.
AssetMerger 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 AssetSets.
File folder = Files.createTempDir();
merger1.writeBlobTo(folder, new MergedAssetWriter(Files.createTempDir()));
// reload it
AssetMerger loadedMerger = new AssetMerger();
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
AssetMerger 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.
AssetMerger 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
AssetMerger 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.
AssetMerger 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
AssetMerger 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.
AssetMerger 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
AssetMerger merger1 = createMerger(new String[][] {
new String[] { "main", "/main/res1", "/main/res2" },
});
// 2nd merger with different order source files in sets.
AssetMerger merger2 = createMerger(new String[][] {
new String[] { "main", "/main/res1" },
});
assertFalse(merger1.checkValidUpdate(merger2.getDataSets()));
}
public void testChangedIgnoredFile() throws Exception {
AssetSet assetSet = AssetSetTest.getBaseAssetSet();
AssetMerger assetMerger = new AssetMerger();
assetMerger.addDataSet(assetSet);
File root = TestUtils.getRoot("assets", "baseSet");
File changedCVSFoo = new File(root, "CVS/foo.txt");
FileValidity<AssetSet> fileValidity = assetMerger.findDataSetContaining(changedCVSFoo);
assertEquals(FileValidity.FileStatus.IGNORED_FILE, fileValidity.status);
}
/**
* Creates a fake merge with given sets.
*
* the data is an array of sets.
*
* Each set is [ setName, folder1, folder2, ...]
*
* @param data
* @return
*/
private static AssetMerger createMerger(String[][] data) {
AssetMerger merger = new AssetMerger();
for (String[] setData : data) {
AssetSet set = new AssetSet(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 AssetMerger getAssetMerger()
throws IOException, MergingException {
if (sAssetMerger == null) {
File root = TestUtils.getRoot("assets", "baseMerge");
AssetSet res = AssetSetTest.getBaseAssetSet();
RecordingLogger logger = new RecordingLogger();
AssetSet overlay = new AssetSet("overlay");
overlay.addSource(new File(root, "overlay"));
overlay.loadFromFiles(logger);
checkLogger(logger);
sAssetMerger = new AssetMerger();
sAssetMerger.addDataSet(res);
sAssetMerger.addDataSet(overlay);
}
return sAssetMerger;
}
private static File getWrittenResources() throws MergingException, IOException {
AssetMerger assetMerger = getAssetMerger();
File folder = Files.createTempDir();
MergedAssetWriter writer = new MergedAssetWriter(folder);
assetMerger.mergeData(writer, false /*doCleanUp*/);
return folder;
}
private File getIncMergeRoot(String name) throws IOException {
File root = TestUtils.getCanonicalRoot("assets", "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()));
}
}
}
}
}