/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ide.common.blame.parser; import static com.android.SdkConstants.DOT_XML; import static com.android.SdkConstants.PLATFORM_WINDOWS; import static com.android.SdkConstants.currentPlatform; import static com.android.utils.SdkUtils.createPathComment; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.ide.common.blame.Message; import com.android.ide.common.blame.SourceFilePosition; import com.android.ide.common.blame.SourcePosition; import com.android.ide.common.blame.parser.aapt.AaptOutputParser; import com.android.ide.common.blame.parser.aapt.AbstractAaptOutputParser; import com.android.utils.StdLogger; import com.android.utils.StringHelper; import com.google.common.base.Charsets; import com.google.common.io.Closeables; import com.google.common.io.Files; import junit.framework.TestCase; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.Collection; import java.util.List; import java.util.Locale; /** * Tests for {@link ToolOutputParser}. */ public class AaptOutputParserTest extends TestCase { private File sourceFile; private String sourceFilePath; private ToolOutputParser parser; @Nullable private static String getSystemIndependentSourcePath(@NonNull Message message) { String sourcePath = message.getSourcePath(); return sourcePath == null ? null : sourcePath.replace('\\', '/'); } private static boolean setupSdkHome() { AbstractAaptOutputParser.ourRootDir = new File("."); return true; } @NonNull private static String toString(@NonNull List<Message> messages) { StringBuilder sb = new StringBuilder(); for (int i = 0, n = messages.size(); i < n; i++) { Message message = messages.get(i); sb.append(Integer.toString(i)).append(':').append(' '); sb.append( StringHelper.capitalize(message.getKind().toString().toLowerCase(Locale.US))) .append(':'); // INFO => Info sb.append(message.getText()); if (!message.getSourceFilePositions().isEmpty() && !message.getSourceFilePositions().get(0).getPosition().equals( SourcePosition.UNKNOWN)) { sb.append('\n'); sb.append('\t'); sb.append(message.getSourceFilePositions().get(0).toString()); } sb.append('\n'); } return sb.toString(); } @Override public void setUp() throws Exception { super.setUp(); parser = new ToolOutputParser(new AaptOutputParser(), new StdLogger(StdLogger.Level.VERBOSE)); } @Override public void tearDown() throws Exception { if (sourceFile != null) { sourceFile.delete(); } super.tearDown(); } public void testParseDisplayingUnhandledMessages() { String output = " **--- HELLO WORLD ---**"; List<Message> gradleMessages = parser.parseToolOutput(output); assertEquals(1, gradleMessages.size()); Message message = gradleMessages.get(0); assertEquals(output, message.getText()); assertEquals(Message.Kind.SIMPLE, message.getKind()); } public void testParseAaptOutputWithRange1() throws IOException { createTempXmlFile(); writeToFile("<manifest xmlns:android='http://schemas.android.com/apk/res/android'", " android:versionCode='12' android:versionName='2.0' package='com.android.tests.basic'>", " <uses-sdk android:minSdkVersion='16' android:targetSdkVersion='16'/>", " <application android:icon='@drawable/icon' android:label='@string/app_name2'>"); String messageText = "No resource found that matches the given name (at 'label' with value " + "'@string/app_name2')."; String err = sourceFilePath + ":4: error: Error: " + messageText; Collection<Message> messages = parser.parseToolOutput(err); assertHasCorrectErrorMessage(messages, messageText, 4, 61); } public void testParseAaptOutputWithRange2() throws IOException { // Check that when the actual aapt error occurs on a line later than the original error line, // the forward search which looks for a value match does not stop on an earlier line that // happens to have the same value prefix createTempXmlFile(); writeToFile("<manifest xmlns:android='http://schemas.android.com/apk/res/android'", " android:versionCode='12' android:versionName='2.0' package='com.android.tests.basic'>", " <uses-sdk android:minSdkVersion='16' android:targetSdkVersion='16'/>", " <application android:icon='@drawable/icon' android:label=", " '@string/app_name2'>"); String messageText = "No resource found that matches the given name (at 'label' with value " + "'@string/app_name2')."; String err = sourceFilePath + ":4: error: Error: " + messageText; Collection<Message> messages = parser.parseToolOutput(err); assertHasCorrectErrorMessage(messages, messageText, 5, 8); } public void testParseAaptOutputWithRange3() throws IOException { // Check that when we have a duplicate resource error, we highlight both the original property // and the original definition. // This tests the second, duplicate declaration ration. createTempXmlFile(); writeToFile("<resources xmlns:android='http://schemas.android.com/apk/res/android'>", " <style name='repeatedStyle1'>", " <item name='android:gravity'>left</item>", " </style>", " <style name='repeatedStyle1'>", " <item name='android:gravity'>left</item>"); String messageText = "Resource entry repeatedStyle1 already has bag item android:gravity."; String err = sourceFilePath + ":6: error: " + messageText; Collection<Message> messages = parser.parseToolOutput(err); assertHasCorrectErrorMessage(messages, messageText, 6, 17); } public void testParseAaptOutputWithRange4() throws IOException { // Check that when we have a duplicate resource error, we highlight both the original property // and the original definition. // This tests the original definition. Note that we don't have enough position info so we simply // highlight the whitespace portion of the line. createTempXmlFile(); writeToFile("<resources xmlns:android='http://schemas.android.com/apk/res/android'>", " <style name='repeatedStyle1'>", " <item name='android:gravity'>left</item>"); String messageText = "Originally defined here."; String err = sourceFilePath + ":3: " + messageText; Collection<Message> messages = parser.parseToolOutput(err); assertHasCorrectErrorMessage(messages, messageText, 3, 5); } public void testParseAaptOutputWithRange5() throws IOException { // Check for aapt error which occurs when the attribute name in an item style declaration is // non-existent. createTempXmlFile(); writeToFile("<resources xmlns:android='http://schemas.android.com/apk/res/android'>", " <style name='wrongAttribute'>", " <item name='nonexistent'>left</item>"); String messageText = "No resource found that matches the given name: attr 'nonexistent'."; String err = sourceFilePath + ":3: error: Error: " + messageText; Collection<Message> messages = parser.parseToolOutput(err); assertHasCorrectErrorMessage(messages, messageText, 3, 17); } public void testParseAaptOutputWithRange6() throws IOException { // Test missing resource name. createTempXmlFile(); writeToFile("<resources xmlns:android='http://schemas.android.com/apk/res/android'>", " <style>"); String messageText = "A 'name' attribute is required for <style>"; String err = sourceFilePath + ":2: error: " + messageText; Collection<Message> messages = parser.parseToolOutput(err); assertHasCorrectErrorMessage(messages, messageText, 2, 3); } public void testParseAaptOutputWithRange7() throws IOException { createTempXmlFile(); writeToFile("<resources xmlns:android='http://schemas.android.com/apk/res/android'>", " <item>"); String messageText = "A 'type' attribute is required for <item>"; String err = sourceFilePath + ":2: error: " + messageText; Collection<Message> messages = parser.parseToolOutput(err); assertHasCorrectErrorMessage(messages, messageText, 2, 3); } public void testParseAaptOutputWithRange8() throws IOException { createTempXmlFile(); writeToFile("<resources xmlns:android='http://schemas.android.com/apk/res/android'>", " <item>"); String messageText = "A 'name' attribute is required for <item>"; String err = sourceFilePath + ":2: error: " + messageText; Collection<Message> messages = parser.parseToolOutput(err); assertHasCorrectErrorMessage(messages, messageText, 2, 3); } public void testParseAaptOutputWithRange9() throws IOException { createTempXmlFile(); writeToFile("<resources xmlns:android='http://schemas.android.com/apk/res/android'>", " <style name='test'>", " <item name='android:layout_width'></item>"); String messageText = "String types not allowed (at 'android:layout_width' with value '')."; String err = sourceFilePath + ":3: error: " + messageText; Collection<Message> messages = parser.parseToolOutput(err); assertHasCorrectErrorMessage(messages, messageText, 3, 21); } public void testParseAaptOutputWithRange10() throws IOException { createTempXmlFile(); writeToFile("<FrameLayout", " xmlns:android='http://schemas.android.com/apk/res/android'", " android:layout_width='wrap_content'", " android:layout_height='match_parent'>", " <TextView", " android:layout_width='fill_parent'", " android:layout_height='wrap_content'", " android:layout_marginTop=''", " android:layout_marginLeft=''"); String messageText = "String types not allowed (at 'layout_marginTop' with value '')."; String err = sourceFilePath + ":5: error: Error: " + messageText; Collection<Message> messages = parser.parseToolOutput(err); assertHasCorrectErrorMessage(messages, messageText, 8, 34); } public void testParseAaptOutputWithRange11() throws IOException { createTempXmlFile(); writeToFile("<FrameLayout", " xmlns:android='http://schemas.android.com/apk/res/android'", " android:layout_width='wrap_content'", " android:layout_height='match_parent'>", " <TextView", " android:layout_width='fill_parent'", " android:layout_height='wrap_content'", " android:layout_marginTop=''", " android:layout_marginLeft=''"); String messageText = "String types not allowed (at 'layout_marginLeft' with value '')."; String err = sourceFilePath + ":5: error: Error: " + messageText; Collection<Message> messages = parser.parseToolOutput(err); assertHasCorrectErrorMessage(messages, messageText, 9, 35); } public void testParseAaptOutputWithRange12() throws IOException { createTempXmlFile(); writeToFile("<FrameLayout", " xmlns:android='http://schemas.android.com/apk/res/android'", " android:layout_width='wrap_content'", " android:layout_height='match_parent'>", " <TextView", " android:id=''"); String messageText = "String types not allowed (at 'id' with value '')."; String err = sourceFilePath + ":5: error: Error: " + messageText; Collection<Message> messages = parser.parseToolOutput(err); assertHasCorrectErrorMessage(messages, messageText, 6, 20); } private void createTempXmlFile() throws IOException { createTempFile(DOT_XML); } private void createTempFile(@NonNull String fileExtension) throws IOException { sourceFile = File.createTempFile(AaptOutputParserTest.class.getName(), fileExtension); sourceFilePath = sourceFile.getAbsolutePath(); } @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") private void writeToFile(@NonNull String... lines) throws IOException { BufferedWriter out = null; try { out = new BufferedWriter(new FileWriter(sourceFile)); for (String line : lines) { out.write(line); out.newLine(); } } finally { Closeables.close(out, true /* swallowIOException */); } } /** * Assert the error message is correct. * * @param messages a collection of Messages * @param expectedText the text the single gradle message should have. * @param expectedLine the 1-based line * @param expectedColumn the 1-based column. */ private void assertHasCorrectErrorMessage(@NonNull Collection<Message> messages, @NonNull String expectedText, int expectedLine, int expectedColumn) { assertEquals("[message count]", 1, messages.size()); Message message = messages.iterator().next(); assertEquals("[source file position count]", 1, message.getSourceFilePositions().size()); SourceFilePosition position = message.getSourceFilePositions().get(0); assertEquals("[file path]", sourceFilePath, position.getFile().toString()); assertEquals("[message severity]", Message.Kind.ERROR, message.getKind()); assertEquals("[message text]", expectedText, message.getText()); assertEquals("[position line]", expectedLine, position.getPosition().getStartLine() + 1); assertEquals("[position column]", expectedColumn, position.getPosition().getStartColumn() + 1); } public void testRedirectValueLinksOutput() throws Exception { if (!setupSdkHome()) { System.out.println( "Skipping testRedirectValueLinksOutput because sdk-common was not found"); return; } // Need file to be named (exactly) values.xml File tempDir = Files.createTempDir(); File valueDir = new File(tempDir, "values-en"); valueDir.mkdirs(); sourceFile = new File(valueDir, "values.xml"); // Keep in sync with MergedResourceWriter.FN_VALUES_XML sourceFilePath = sourceFile.getAbsolutePath(); writeToFile( "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<resources xmlns:ns1=\"urn:oasis:names:tc:xliff:document:1.2\">\n" + "\n" + " <!-- From: src/test/resources/testData/resources/baseSet/values/values.xml -->\n" + " <string-array name=\"string_array\" translatable=\"false\">\n" + " <item/> <!-- 0 -->\n" + " <item/> <!-- 1 -->\n" + " <item>ABC</item> <!-- 2 -->\n" + " <item>DEF</item> <!-- 3 -->\n" + " <item>GHI</item> <!-- 4 -->\n" + " <item>JKL</item> <!-- 5 -->\n" + " <item>MNO</item> <!-- 6 -->\n" + " <item>PQRS</item> <!-- 7 -->\n" + " <item>TUV</item> <!-- 8 -->\n" + " <item>WXYZ</item> <!-- 9 -->\n" + " </string-array>\n" + "\n" + " <attr name=\"dimen_attr\" format=\"dimension\" />\n" + " <attr name=\"enum_attr\">\n" + " <enum name=\"normal\" value=\"0\" />\n" + " <enum name=\"sans\" value=\"1\" />\n" + " <enum name=\"serif\" value=\"2\" />\n" + " <enum name=\"monospace\" value=\"3\" />\n" + " </attr>\n" + " <attr name=\"flag_attr\">\n" + " <flag name=\"normal\" value=\"0\" />\n" + " <flag name=\"bold\" value=\"1\" />\n" + " <flag name=\"italic\" value=\"2\" />\n" + " </attr>\n" + " <attr name=\"string_attr\" format=\"string\" />\n" + " <!-- From: src/test/resources/testData/resources/baseMerge/overlay/values/values.xml -->\n" + " <color name=\"color\">#FFFFFFFF</color>\n" + " <!-- From: src/test/resources/testData/resources/baseSet/values/values.xml -->\n" + " <declare-styleable name=\"declare_styleable\">\n" + "\n" + " <!-- ============== -->\n" + " <!-- Generic styles -->\n" + " <!-- ============== -->\n" + " <eat-comment />\n" + "\n" + " <!-- Default color of foreground imagery. -->\n" + " <attr name=\"blah\" format=\"color\" />\n" + " <!-- Default color of foreground imagery on an inverted background. -->\n" + " <attr name=\"android:colorForegroundInverse\" />\n" + " </declare-styleable>\n" + "\n" + " <dimen name=\"dimen\">164dp</dimen>\n" + "\n" + " <drawable name=\"color_drawable\">#ffffffff</drawable>\n" + " <drawable name=\"drawable_ref\">@drawable/stat_notify_sync_anim0</drawable>\n" + "\n" + " <item name=\"item_id\" type=\"id\"/>\n" + "\n" + " <integer name=\"integer\">75</integer>\n" + " <!-- From: src/test/resources/testData/resources/baseMerge/overlay/values/values.xml -->\n" + " <item name=\"file_replaced_by_alias\" type=\"layout\">@layout/ref</item>\n" + " <!-- From: src/test/resources/testData/resources/baseSet/values/values.xml -->\n" + " <item name=\"layout_ref\" type=\"layout\">@layout/ref</item>\n" + " <!-- From: src/test/resources/testData/resources/baseMerge/overlay/values/values.xml -->\n" + " <string name=\"basic_string\">overlay_string</string>\n" + " <!-- From: src/test/resources/testData/resources/baseSet/values/values.xml -->\n" + " <string name=\"styled_string\">Forgot your username or password\\?\\nVisit <b>google.com/accounts/recovery</b>.</string>\n" + " <string name=\"xliff_string\"><ns1:g id=\"number\" example=\"123\">%1$s</ns1:g><ns1:g id=\"unit\" example=\"KB\">%2$s</ns1:g></string>\n" + "\n" + " <style name=\"style\" parent=\"@android:style/Holo.Light\">\n" + " <item name=\"android:singleLine\">true</item>\n" + " <item name=\"android:textAppearance\">@style/TextAppearance.WindowTitle</item>\n" + " <item name=\"android:shadowColor\">#BB000000</item>\n" + " <item name=\"android:shadowRadius\">2.75</item>\n" + " <item name=\"foo\">foo</item>\n" + " </style>\n" + "\n" + "</resources>\n"); String messageText = "String types not allowed (at 'drawable_ref' with value '@drawable/stat_notify_sync_anim0')."; String err = sourceFilePath + ":46: error: Error: " + messageText; Collection<Message> messages = parser.parseToolOutput(err); assertEquals(1, messages.size()); assertEquals("[message count]", 1, messages.size()); Message message = messages.iterator().next(); assertNotNull(message); // NOT sourceFilePath; should be translated back from source comment assertEquals(new File ("src/test/resources/testData/resources/baseSet/values/values.xml").getAbsolutePath(), getSystemIndependentSourcePath(message)); assertEquals("[message severity]", Message.Kind.ERROR, message.getKind()); assertEquals("[message text]", messageText, message.getText()); assertEquals(1, message.getSourceFilePositions().size()); SourcePosition pos = message.getSourceFilePositions().get(0).getPosition(); assertEquals("[position line]", 9, pos.getStartLine() + 1); assertEquals("[position column]", 35, pos.getStartColumn() + 1); } public void testRedirectFileLinksOutput() throws Exception { if (!setupSdkHome()) { System.out.println( "Skipping testRedirectFileLinksOutput because sdk-common was not found"); return; } // Need file to be named (exactly) values.xml File tempDir = Files.createTempDir(); File layoutDir = new File(tempDir, "layout-land"); layoutDir.mkdirs(); sourceFile = new File(layoutDir, "main.xml"); sourceFilePath = sourceFile.getAbsolutePath(); writeToFile( "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " android:orientation=\"vertical\"\n" + " android:layout_width=\"fill_parent\"\n" + " android:layout_height=\"fill_parent\"\n" + " >\n" + "<TextView\n" + " android:layout_width=\"fill_parent\"\n" + " android:layout_height=\"wrap_content\"\n" + " android:text=\"Test App - Basic\"\n" + " android:id=\"@+id/text\"\n" + " />\n" + "</LinearLayout>\n" + "\n" + "<!-- From: file:src/test/resources/testData/resources/incMergeData/filesVsValues/main/layout/main.xml -->"); String messageText = "Random error message here"; String err = sourceFilePath + ":4: error: Error: " + messageText; Collection<Message> messages = parser.parseToolOutput(err); assertEquals("[message count]", 1, messages.size()); Message message = messages.iterator().next(); assertNotNull(message); // NOT sourceFilePath; should be translated back from source comment String expected = new File("src/test/resources/testData/resources/incMergeData/filesVsValues/main/layout/main.xml") .getAbsolutePath(); assertEquals("[file path]", expected, getSystemIndependentSourcePath(message)); assertEquals("[message severity]", Message.Kind.ERROR, message.getKind()); assertEquals("[message text]", messageText, message.getText()); assertEquals("[position line]", 4, message.getSourceFilePositions().get(0).getPosition().getStartLine() + 1); //assertEquals("[position column]", 35, message.getColumn()); // TODO: Test encoding issues (e.g. & in path where the XML source comment would be & instead) } public void test() throws Exception { File tempDir = Files.createTempDir(); sourceFile = new File(tempDir, "values.xml"); // Name matters for position search sourceFilePath = sourceFile.getAbsolutePath(); File source = new File(tempDir, "dimens.xml"); Files.write("<resources>\n" + " <!-- Default screen margins, per the Android Design guidelines. -->\n" + " <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n" + " <dimen name=\"activity_vertical_margin\">16dp</dimen>\n" + " <dimen name=\"new_name\">50</dimen>\n" + "</resources>", source, Charsets.UTF_8); source.deleteOnExit(); Files.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<resources>\n" + " <!-- From: file:/Users/unittest/AndroidStudioProjects/BlankProject1Project/BlankProject1/build/exploded-bundles/ComAndroidSupportAppcompatV71800.aar/res/values/values.xml -->\n" + " <dimen name=\"abc_action_bar_default_height\">48dip</dimen>\n" + " <dimen name=\"abc_action_bar_icon_vertical_padding\">8dip</dimen>\n" + " <!-- From: file:" + source.getPath() + " -->\n" + " <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n" + " <dimen name=\"activity_vertical_margin\">16dp</dimen>\n" + " <dimen name=\"ok\">50dp</dimen>\n" + " <dimen name=\"new_name\">50</dimen>\n" + " <!-- From: file:/Users/unittest/AndroidStudioProjects/BlankProject1Project/BlankProject1/build/exploded-bundles/ComAndroidSupportAppcompatV71800.aar/res/values/values.xml -->\n" + " <item name=\"action_bar_activity_content\" type=\"id\"/>\n" + " <item name=\"action_menu_divider\" type=\"id\"/>\n" + " <item name=\"action_menu_presenter\" type=\"id\"/>\n" + " <item name=\"home\" type=\"id\"/>\n" + "</resources>\n", sourceFile, Charsets.UTF_8); String output = ":BlankProject1:prepareComAndroidSupportAppcompatV71800Library UP-TO-DATE\n" + ":BlankProject1:prepareDebugDependencies\n" + ":BlankProject1:mergeDebugAssets UP-TO-DATE\n" + ":BlankProject1:compileDebugRenderscript UP-TO-DATE\n" + ":BlankProject1:mergeDebugResources UP-TO-DATE\n" + ":BlankProject1:processDebugManifest UP-TO-DATE\n" + ":BlankProject1:processDebugResources\n" + sourceFilePath + ":10: error: Error: Integer types not allowed (at 'new_name' with value '50').\n" + ":BlankProject1:processDebugResources FAILED\n" + "\n"; /* + "FAILURE: Build failed with an exception.\n" + "\n" + "* What went wrong:\n" + "Execution failed for task ':BlankProject1:processDebugResources'.\n" + "> Failed to run command:\n" + " \t/Users/tnorbye/dev/sdks/build-tools/18.0.1/aapt package -f --no-crunch -I ...\n" + " Error Code:\n" + " \t1\n" + " Output:\n" + " \t" + sourceFilePath + ":10: error: Error: Integer types not allowed (at 'new_name' with value '50').\n" + "\n" + "\n" + "* Try:\n" + "Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.\n" + "\n" + "BUILD FAILED\n" + "\n" + "Total time: 5.435 secs"; */ String expected = "0: Simple::BlankProject1:prepareComAndroidSupportAppcompatV71800Library UP-TO-DATE\n" + "1: Simple::BlankProject1:prepareDebugDependencies\n" + "2: Simple::BlankProject1:mergeDebugAssets UP-TO-DATE\n" + "3: Simple::BlankProject1:compileDebugRenderscript UP-TO-DATE\n" + "4: Simple::BlankProject1:mergeDebugResources UP-TO-DATE\n" + "5: Simple::BlankProject1:processDebugManifest UP-TO-DATE\n" + "6: Simple::BlankProject1:processDebugResources\n" + "7: Error:Integer types not allowed (at 'new_name' with value '50').\n" + "\t" + source.getPath() + ":5:28-30\n" + "8: Simple::BlankProject1:processDebugResources FAILED\n"; /* + "9: Error:Error while executing aapt command\n" + "10: Error:Integer types not allowed (at 'new_name' with value '50').\n" + "\t" + source.getPath() + ":5:28\n" + "11: Error:Execution failed for task ':BlankProject1:processDebugResources'.\n" + "12: Info:BUILD FAILED\n" + "13: Info:Total time: 5.435 secs\n";*/ String actual = toString(parser.parseToolOutput(output)); assertEquals(expected, actual); sourceFile.delete(); source.delete(); tempDir.delete(); } public void testDashes() throws Exception { File tempDir = Files.createTempDir(); String dirName = currentPlatform() == PLATFORM_WINDOWS ? "My -- Q&A Dir" : "My -- Q&A< Dir"; File dir = new File(tempDir, dirName); // path which should force encoding of path chars, see for example issue 60050 dir.mkdirs(); sourceFile = new File(dir, "values.xml"); // Name matters for position search sourceFilePath = sourceFile.getAbsolutePath(); File source = new File(dir, "dimens.xml"); Files.write("<resources>\n" + " <!-- Default screen margins, per the Android Design guidelines. -->\n" + " <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n" + " <dimen name=\"activity_vertical_margin\">16dp</dimen>\n" + " <dimen name=\"new_name\">50</dimen>\n" + "</resources>", source, Charsets.UTF_8); source.deleteOnExit(); Files.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<resources>\n" + " <!-- From: file:/Users/unittest/AndroidStudioProjects/BlankProject1Project/BlankProject1/build/exploded-bundles/ComAndroidSupportAppcompatV71800.aar/res/values/values.xml -->\n" + " <dimen name=\"abc_action_bar_default_height\">48dip</dimen>\n" + " <dimen name=\"abc_action_bar_icon_vertical_padding\">8dip</dimen>\n" + " <!-- " + createPathComment(source, false) + " -->\n" + " <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n" + " <dimen name=\"activity_vertical_margin\">16dp</dimen>\n" + " <dimen name=\"ok\">50dp</dimen>\n" + " <dimen name=\"new_name\">50</dimen>\n" + " <!-- From: file:/Users/unittest/AndroidStudioProjects/BlankProject1Project/BlankProject1/build/exploded-bundles/ComAndroidSupportAppcompatV71800.aar/res/values/values.xml -->\n" + " <item name=\"action_bar_activity_content\" type=\"id\"/>\n" + " <item name=\"action_menu_divider\" type=\"id\"/>\n" + " <item name=\"action_menu_presenter\" type=\"id\"/>\n" + " <item name=\"home\" type=\"id\"/>\n" + "</resources>\n", sourceFile, Charsets.UTF_8); // TODO: Test layout too String output = ":BlankProject1:prepareComAndroidSupportAppcompatV71800Library UP-TO-DATE\n" + ":BlankProject1:prepareDebugDependencies\n" + ":BlankProject1:mergeDebugAssets UP-TO-DATE\n" + ":BlankProject1:compileDebugRenderscript UP-TO-DATE\n" + ":BlankProject1:mergeDebugResources UP-TO-DATE\n" + ":BlankProject1:processDebugManifest UP-TO-DATE\n" + ":BlankProject1:processDebugResources\n" + sourceFilePath + ":10: error: Error: Integer types not allowed (at 'new_name' with value '50').\n" + ":BlankProject1:processDebugResources FAILED\n"; String expected = "0: Simple::BlankProject1:prepareComAndroidSupportAppcompatV71800Library UP-TO-DATE\n" + "1: Simple::BlankProject1:prepareDebugDependencies\n" + "2: Simple::BlankProject1:mergeDebugAssets UP-TO-DATE\n" + "3: Simple::BlankProject1:compileDebugRenderscript UP-TO-DATE\n" + "4: Simple::BlankProject1:mergeDebugResources UP-TO-DATE\n" + "5: Simple::BlankProject1:processDebugManifest UP-TO-DATE\n" + "6: Simple::BlankProject1:processDebugResources\n" + "7: Error:Integer types not allowed (at 'new_name' with value '50').\n" + "\t" + source.getPath() + ":5:28-30\n" + "8: Simple::BlankProject1:processDebugResources FAILED\n"; String actual = toString(parser.parseToolOutput(output)); assertEquals(expected, actual); sourceFile.delete(); source.delete(); dir.delete(); tempDir.delete(); } public void testLayoutFileSuffix() throws Exception { File tempDir = Files.createTempDir(); sourceFile = new File(tempDir, "layout.xml"); sourceFilePath = sourceFile.getAbsolutePath(); File source = new File(tempDir, "real-layout.xml"); Files.write( "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + " android:layout_width=\"match_parent\"\n" + " android:layout_height=\"match_parent\"\n" + " android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n" + " android:paddingRight=\"@dimen/activity_horizontal_margin\"\n" + " android:paddingTop=\"@dimen/activity_vertical_margin\"\n" + " android:paddingBottom=\"@dimen/activity_vertical_margin\"\n" + " tools:context=\".MainActivity\">\n" + "\n" + "\n" + " <Button\n" + " android:layout_width=\"wrap_content\"\n" + " android:layout_height=\"wrap_content\"\n" + " android:hint=\"fy faen\"\n" + " android:text=\"@string/hello_world\"\n" + " android:slayout_alignParentTop=\"true\"\n" + " android:layout_alignParentLeft=\"true\" />\n" + "\n" + "</RelativeLayout>\n", source, Charsets.UTF_8); source.deleteOnExit(); Files.write( "<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" + " xmlns:tools=\"http://schemas.android.com/tools\"\n" + " android:layout_width=\"match_parent\"\n" + " android:layout_height=\"match_parent\"\n" + " android:paddingLeft=\"@dimen/activity_horizontal_margin\"\n" + " android:paddingRight=\"@dimen/activity_horizontal_margin\"\n" + " android:paddingTop=\"@dimen/activity_vertical_margin\"\n" + " android:paddingBottom=\"@dimen/activity_vertical_margin\"\n" + " tools:context=\".MainActivity\">\n" + "\n" + " <!--style=\"@style/Buttons\"-->\n" + " <Button\n" + " android:layout_width=\"wrap_content\"\n" + " android:layout_height=\"wrap_content\"\n" + " android:hint=\"fy faen\"\n" + " android:text=\"@string/hello_world\"\n" + " android:slayout_alignParentTop=\"true\"\n" + " android:layout_alignParentLeft=\"true\" />\n" + "\n" + "</RelativeLayout>\n" + "<!-- " + createPathComment(source, false) + " -->", sourceFile, Charsets.UTF_8); String output = ":BlankProject1:preBuild UP-TO-DATE\n" + ":BlankProject1:preDebugBuild UP-TO-DATE\n" + ":BlankProject1:preReleaseBuild UP-TO-DATE\n" + ":BlankProject1:prepareComAndroidSupportAppcompatV71800Library UP-TO-DATE\n" + ":BlankProject1:prepareDebugDependencies\n" + ":BlankProject1:compileDebugAidl UP-TO-DATE\n" + ":BlankProject1:compileDebugRenderscript UP-TO-DATE\n" + ":BlankProject1:generateDebugBuildConfig UP-TO-DATE\n" + ":BlankProject1:mergeDebugAssets UP-TO-DATE\n" + ":BlankProject1:mergeDebugResources UP-TO-DATE\n" + ":BlankProject1:processDebugManifest UP-TO-DATE\n" + ":BlankProject1:processDebugResources\n" + sourceFilePath + ":12: error: No resource identifier found for attribute 'slayout_alignParentTop' in package 'android'\n" + ":BlankProject1:processDebugResources FAILED\n"; assertEquals("0: Simple::BlankProject1:preBuild UP-TO-DATE\n" + "1: Simple::BlankProject1:preDebugBuild UP-TO-DATE\n" + "2: Simple::BlankProject1:preReleaseBuild UP-TO-DATE\n" + "3: Simple::BlankProject1:prepareComAndroidSupportAppcompatV71800Library UP-TO-DATE\n" + "4: Simple::BlankProject1:prepareDebugDependencies\n" + "5: Simple::BlankProject1:compileDebugAidl UP-TO-DATE\n" + "6: Simple::BlankProject1:compileDebugRenderscript UP-TO-DATE\n" + "7: Simple::BlankProject1:generateDebugBuildConfig UP-TO-DATE\n" + "8: Simple::BlankProject1:mergeDebugAssets UP-TO-DATE\n" + "9: Simple::BlankProject1:mergeDebugResources UP-TO-DATE\n" + "10: Simple::BlankProject1:processDebugManifest UP-TO-DATE\n" + "11: Simple::BlankProject1:processDebugResources\n" + "12: Error:No resource identifier found for attribute 'slayout_alignParentTop' in package 'android'\n" + "\t" + source.getPath() + ":12\n" + "13: Simple::BlankProject1:processDebugResources FAILED\n", toString(parser.parseToolOutput(output))); sourceFile.delete(); source.delete(); tempDir.delete(); } public void testMismatchedTag() throws Exception { // https://code.google.com/p/android/issues/detail?id=59824 createTempXmlFile(); String output = ":AudioPlayer:prepareDebugDependencies\n" + ":AudioPlayer:compileDebugAidl UP-TO-DATE\n" + ":AudioPlayer:generateDebugBuildConfig UP-TO-DATE\n" + ":AudioPlayer:mergeDebugAssets UP-TO-DATE\n" + ":AudioPlayer:compileDebugRenderscript UP-TO-DATE\n" + ":AudioPlayer:mergeDebugResources UP-TO-DATE\n" + ":AudioPlayer:processDebugManifest UP-TO-DATE\n" + ":AudioPlayer:processDebugResources\n" + sourceFilePath + ":101: error: Error parsing XML: mismatched tag\n" + ":AudioPlayer:processDebugResources FAILED\n" + "\n"; assertEquals("0: Simple::AudioPlayer:prepareDebugDependencies\n" + "1: Simple::AudioPlayer:compileDebugAidl UP-TO-DATE\n" + "2: Simple::AudioPlayer:generateDebugBuildConfig UP-TO-DATE\n" + "3: Simple::AudioPlayer:mergeDebugAssets UP-TO-DATE\n" + "4: Simple::AudioPlayer:compileDebugRenderscript UP-TO-DATE\n" + "5: Simple::AudioPlayer:mergeDebugResources UP-TO-DATE\n" + "6: Simple::AudioPlayer:processDebugManifest UP-TO-DATE\n" + "7: Simple::AudioPlayer:processDebugResources\n" + "8: Error:Error parsing XML: mismatched tag\n" + "\t" + sourceFilePath + ":101\n" + "9: Simple::AudioPlayer:processDebugResources FAILED\n", toString(parser.parseToolOutput(output))); sourceFile.delete(); } }