/*
* 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 com.android.manifmerger.ManifestMerger2.SystemProperty;
import static com.android.manifmerger.MergingReport.Record;
import com.android.annotations.Nullable;
import com.android.utils.StdLogger;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
import junit.framework.Test;
import junit.framework.TestSuite;
import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Tests for the {@link com.android.manifmerger.ManifestMerger2} class
*/
public class ManifestMerger2Test extends ManifestMergerTest {
// so far, I only support 3 original tests.
private static String[] sDataFiles = new String[]{
"00_noop",
"03_inject_attributes.xml",
"05_inject_package.xml",
"05_inject_package_placeholder.xml",
"05_inject_package_with_overlays.xml",
"06_inject_attributes_with_specific_prefix.xml",
"07_no_package_provided.xml",
"08_no_library_package_provided.xml",
"09_overlay_package_provided.xml",
"08b_library_injection.xml",
"09b_overlay_package_different.xml",
"09c_overlay_package_not_provided.xml",
"10_activity_merge",
"11_activity_dup",
"12_alias_dup",
"13_service_dup",
"14_receiver_dup",
"15_provider_dup",
"16_fqcn_merge",
"17_fqcn_conflict",
"18_fqcn_success",
"20_uses_lib_merge",
"21_uses_main_errors",
"22_uses_lib_errors",
"25_permission_merge",
"26_permission_dup",
"28_uses_perm_merge",
"29_uses_perm_selector",
"29b_uses_perm_invalidSelector",
"30_uses_sdk_ok",
"32_uses_sdk_minsdk_ok",
"33_uses_sdk_minsdk_conflict",
"33b_uses_sdk_minsdk_override.xml",
"33c_uses_sdk_minsdk_override_and_conflict.xml",
"34_inject_uses_sdk_no_dup.xml",
"36_uses_sdk_targetsdk_warning",
"40_uses_feat_merge",
"41_uses_feat_errors",
"45_uses_feat_gles_once",
"47_uses_feat_gles_conflict",
"50_uses_conf_warning",
"52_support_screens_warning",
"54_compat_screens_warning",
"56_support_gltext_warning",
"60_merge_order",
"65_override_app",
"66_remove_app",
"67_override_activities",
"68_override_uses",
"69_remove_uses",
"70_expand_fqcns",
"71_extract_package_prefix",
"75_app_metadata_merge",
"76_app_metadata_ignore",
"77_app_metadata_conflict",
"78_removeAll",
"79_custom_node.xml",
};
@Override
protected String getTestDataDirectory() {
return "data2";
}
/**
* This overrides the default test suite created by junit. The test suite is a bland TestSuite
* with a dedicated name. We inject as many instances of {@link ManifestMergerTest} in the suite
* as we have declared data files above.
*
* @return A new {@link junit.framework.TestSuite}.
*/
public static Test suite() {
TestSuite suite = new TestSuite();
// Give a non-generic name to our test suite, for better unit reports.
suite.setName("ManifestMergerTestSuite");
for (String fileName : sDataFiles) {
suite.addTest(TestSuite.createTest(ManifestMerger2Test.class, fileName));
}
return suite;
}
public ManifestMerger2Test(String testName) {
super(testName);
}
/**
* Processes the data from the given
* {@link com.android.manifmerger.ManifestMergerTest.TestFiles} by invoking {@link
* ManifestMerger#process(java.io.File, java.io.File, java.io.File[], java.util.Map, String)}:
* the given library files are applied consecutively to the main XML document and the output is
* generated. <p/> Then the expected and actual outputs are loaded into a DOM, dumped again to a
* String using an XML transform and compared. This makes sure only the structure is checked and
* that any formatting is ignored in the comparison.
*
* @param testFiles The test files to process. Must not be null.
* @throws Exception when this go wrong.
*/
@Override
void processTestFiles(TestFiles testFiles) throws Exception {
StdLogger stdLogger = new StdLogger(StdLogger.Level.VERBOSE);
ManifestMerger2.Invoker invoker = ManifestMerger2.newMerger(testFiles.getMain(),
stdLogger, ManifestMerger2.MergeType.APPLICATION)
.addLibraryManifests(testFiles.getLibs())
.addFlavorAndBuildTypeManifests(testFiles.getOverlayFiles())
.withFeatures(ManifestMerger2.Invoker.Feature.KEEP_INTERMEDIARY_STAGES,
ManifestMerger2.Invoker.Feature.REMOVE_TOOLS_DECLARATIONS);
if (!Strings.isNullOrEmpty(testFiles.getPackageOverride())) {
invoker.setOverride(SystemProperty.PACKAGE, testFiles.getPackageOverride());
}
for (Map.Entry<String, String> injectable : testFiles.getInjectAttributes().entrySet()) {
SystemProperty systemProperty = getSystemProperty(injectable.getKey());
if (systemProperty != null) {
invoker.setOverride(systemProperty, injectable.getValue());
} else {
invoker.setPlaceHolderValue(injectable.getKey(), injectable.getValue());
}
}
MergingReport mergeReport = invoker.merge();
// this is obviously quite hacky, refine once merge output is better defined.
boolean notExpectingError = !isExpectingError(testFiles.getExpectedErrors());
mergeReport.log(stdLogger);
if (mergeReport.getMergedDocument().isPresent()) {
XmlDocument actualResult = mergeReport.getMergedDocument().get();
String prettyResult = actualResult.prettyPrint();
stdLogger.info(prettyResult);
if (testFiles.getActualResult() != null) {
FileWriter writer = new FileWriter(testFiles.getActualResult());
try {
writer.append(prettyResult);
} finally {
writer.close();
}
}
if (!notExpectingError) {
fail("Did not get expected error : " + testFiles.getExpectedErrors());
}
XmlDocument expectedResult = TestUtils.xmlDocumentFromString(
TestUtils.sourceFile(getClass(), testFiles.getMain().getName()),
testFiles.getExpectedResult());
Optional<String> comparingMessage =
expectedResult.compareTo(actualResult);
if (comparingMessage.isPresent()) {
Logger.getAnonymousLogger().severe(comparingMessage.get());
fail(comparingMessage.get());
}
// process any warnings.
if (mergeReport.getResult() == MergingReport.Result.WARNING) {
compareExpectedAndActualErrors(mergeReport, testFiles.getExpectedErrors());
}
} else {
for (Record record : mergeReport.getLoggingRecords()) {
Logger.getAnonymousLogger().info("Returned log: " + record);
}
compareExpectedAndActualErrors(mergeReport, testFiles.getExpectedErrors());
assertFalse(notExpectingError);
}
}
private boolean isExpectingError(String expectedOutput) throws IOException {
StringReader stringReader = new StringReader(expectedOutput);
BufferedReader reader = new BufferedReader(stringReader);
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("ERROR")) return true;
}
return false;
}
private void compareExpectedAndActualErrors(
MergingReport mergeReport,
String expectedOutput) throws IOException {
StringReader stringReader = new StringReader(expectedOutput);
BufferedReader reader = new BufferedReader(stringReader);
String line = reader.readLine();
List<Record> records = new ArrayList<Record>(mergeReport.getLoggingRecords());
while (line != null) {
if (line.startsWith("WARNING") || line.startsWith("ERROR")) {
String message = line;
do {
line = reader.readLine();
if (line != null && line.startsWith(" ")) {
message = message + "\n" + line;
}
} while (line != null && line.startsWith(" "));
// next might generate an exception which will make the test fail when we
// get unexpected error message.
if (!findLineInRecords(message, records)) {
StringBuilder errorMessage = new StringBuilder();
dumpRecords(records, errorMessage);
errorMessage.append("Cannot find expected error : \n").append(message);
fail(errorMessage.toString());
}
}
}
// check that we do not have any unexpected error messages.
if (!records.isEmpty()) {
StringBuilder message = new StringBuilder();
dumpRecords(records, message);
message.append("Unexpected error message(s)");
fail(message.toString());
}
}
private boolean findLineInRecords(String errorLine, List<Record> records) {
String severity = errorLine.substring(0, errorLine.indexOf(':'));
String message = errorLine.substring(errorLine.indexOf(':') + 1);
for (Record record : records) {
int indexOfSuggestions = record.getMessage().indexOf("\n\tSuggestion:");
String messageRecord = indexOfSuggestions != -1
? record.getMessage().substring(0, indexOfSuggestions)
: record.getMessage();
Pattern pattern = Pattern.compile(message);
Matcher matcher = pattern.matcher(messageRecord.replaceAll("\t", " "));
if (matcher.matches() && record.getSeverity() == Record.Severity.valueOf(severity)) {
records.remove(record);
return true;
}
}
return false;
}
@Nullable
private SystemProperty getSystemProperty(String name) {
for (SystemProperty systemProperty : SystemProperty.values()) {
if (systemProperty.toCamelCase().equals(name)) {
return systemProperty;
}
}
return null;
}
private void dumpRecords(List<Record> records, StringBuilder stringBuilder) {
stringBuilder.append("\n------------ Records : \n");
for (Record record : records) {
stringBuilder.append(record.toString());
stringBuilder.append("\n");
}
stringBuilder.append("------------ End of records.\n");
}
}