/*
 * 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

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 org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

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 {

    @Rule
    public TemporaryFolder temporaryFolder = new TemporaryFolder();

    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();
    }

    @Before
    public void setUp() throws Exception {
        parser = new ToolOutputParser(new AaptOutputParser(),
                new StdLogger(StdLogger.Level.VERBOSE));
    }

    @After
    public void tearDown() throws Exception {
        if (sourceFile != null) {
            sourceFile.delete();
        }
    }

    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);
    }

    @Test
    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 = temporaryFolder.newFolder();
        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\"" +
                        "           xmlns:tools=\"http://schemas.android.com/tools\"" +
                        "           xmlns:alias_for_tools=\"http://schemas.android.com/tools\">\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]", 11, pos.getStartLine() + 1);
        assertEquals("[position column]", 35, pos.getStartColumn() + 1);
    }

    @Test
    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 = temporaryFolder.newFolder();
        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 &amp; instead)
    }

    public void test() throws Exception {
        File tempDir = temporaryFolder.newFolder();
        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();
    }

    @Test
    public void testDashes() throws Exception {
        File tempDir = temporaryFolder.newFolder();
        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();
    }

    @Test
    public void testLayoutFileSuffix() throws Exception {
        File tempDir = temporaryFolder.newFolder();
        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();
    }

    @Test
    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();
    }
}
