Merge "Enable test without runtime to run on Windows"
diff --git a/build.gradle b/build.gradle
index eb09333..f85dd6b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -98,7 +98,8 @@
     examplesAndroidOCompile group: 'org.ow2.asm', name: 'asm', version: '5.1'
 }
 
-def osString = OperatingSystem.current().isLinux() ? "linux" : "mac"
+def osString = OperatingSystem.current().isLinux() ? "linux" :
+        OperatingSystem.current().isMacOsX() ? "mac" : "windows"
 
 def cloudDependencies = [
         "tests"      : [
@@ -370,7 +371,11 @@
     }
     task "dex_debuginfo_examples"(type: Exec,
             dependsOn: ["jar_debuginfo_examples", "downloadDeps"]) {
-        executable file("tools/linux/dx/bin/dx");
+        if (OperatingSystem.current().isWindows()) {
+            executable file("tools/windows/dx/bin/dx.bat")
+        } else {
+            executable file("tools/linux/dx/bin/dx");
+        }
         args "--dex"
         args "--output=build/test/${hostDexJar}"
         args "build/test/${hostJar}"
@@ -560,11 +565,12 @@
 }
 
 task buildExamples {
-    if (OperatingSystem.current().isMacOsX()) {
-        logger.lifecycle("WARNING: Testing (including building examples) is only partially supported on Mac OS.")
+    if (OperatingSystem.current().isMacOsX() || OperatingSystem.current().isWindows()) {
+        logger.lifecycle("WARNING: Testing (including building examples) is only partially supported on your " +
+                "platform (" + OperatingSystem.current().getName() + ").")
     } else if (!OperatingSystem.current().isLinux()) {
       logger.lifecycle("WARNING: Testing (including building examples) is not supported on your platform. " +
-          "It is fully supported on Linux and partially supported on Mac OS")
+          "It is fully supported on Linux and partially supported on Mac OS and Windows")
       return;
     }
     dependsOn buildDebugTestResourcesJars
@@ -725,12 +731,18 @@
         systemProperty 'jctf_compile_only', '1'
     }
 
-    if (OperatingSystem.current().isLinux() || OperatingSystem.current().isMacOsX()) {
+    if (OperatingSystem.current().isLinux()
+            || OperatingSystem.current().isMacOsX()
+            || OperatingSystem.current().isWindows()) {
         if (OperatingSystem.current().isMacOsX()) {
             logger.lifecycle("WARNING: Testing in only partially supported on Mac OS. " +
                 "Art only runs on Linux and tests requiring Art runs in a Docker container, which must be present. " +
                 "See tools/docker/README.md for details.")
         }
+        if (OperatingSystem.current().isWindows()) {
+            logger.lifecycle("WARNING: Testing in only partially supported on Windows. " +
+                    "Art only runs on Linux and tests requiring Art will be skipped")
+        }
         dependsOn downloadDeps
         dependsOn buildExamples
         dependsOn buildSmali
@@ -740,7 +752,7 @@
         dependsOn buildPreNJdwpTestsJar
     } else {
         logger.lifecycle("WARNING: Testing in not supported on your platform. Testing is only fully supported on " +
-            "Linux and partially supported on Mac OS. Art does not run on other platforms.")
+            "Linux and partially supported on Mac OS and Windows. Art does not run on other platforms.")
     }
 }
 
diff --git a/buildSrc/src/main/java/dx/Dx.java b/buildSrc/src/main/java/dx/Dx.java
index e4b89c4..86d1bce 100644
--- a/buildSrc/src/main/java/dx/Dx.java
+++ b/buildSrc/src/main/java/dx/Dx.java
@@ -64,7 +64,8 @@
       public void execute(ExecSpec execSpec) {
         try {
           if (dxExecutable == null) {
-            dxExecutable = new File("tools/" + Utils.toolsDir() + "/dx/bin/dx");
+            String dxExecutableName = Utils.toolsDir().equals("windows") ? "dx.bat" : "dx";
+            dxExecutable = new File("tools/" + Utils.toolsDir() + "/dx/bin/" + dxExecutableName);
           }
           execSpec.setExecutable(dxExecutable);
           execSpec.args("--dex");
diff --git a/buildSrc/src/main/java/utils/Utils.java b/buildSrc/src/main/java/utils/Utils.java
index 5e5e9d7..4cbc90c 100644
--- a/buildSrc/src/main/java/utils/Utils.java
+++ b/buildSrc/src/main/java/utils/Utils.java
@@ -5,6 +5,13 @@
 
 public class Utils {
   public static String toolsDir() {
-    return System.getProperty("os.name").equals("Mac OS X") ? "mac" : "linux";
+    String osName = System.getProperty("os.name");
+    if (osName.equals("Mac OS X")) {
+      return "mac";
+    } else if (osName.contains("Windows")) {
+      return "windows";
+    } else {
+      return "linux";
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/PreloadedResourceProvider.java b/src/main/java/com/android/tools/r8/utils/PreloadedResourceProvider.java
index fcb89d4..9601ff6 100644
--- a/src/main/java/com/android/tools/r8/utils/PreloadedResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/PreloadedResourceProvider.java
@@ -11,6 +11,8 @@
 import com.android.tools.r8.ResourceProvider;
 import com.android.tools.r8.errors.CompilationError;
 import com.google.common.io.ByteStreams;
+
+import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.nio.file.Path;
@@ -73,9 +75,12 @@
     assert name != null;
     assert name.endsWith(CLASS_EXTENSION) :
         "Name " + name + " must have " + CLASS_EXTENSION + " suffix";
-    String descriptor = name.substring(0, name.length() - CLASS_EXTENSION.length());
+    String fileName =
+            File.separatorChar == '/' ? name.toString() :
+                    name.toString().replace(File.separatorChar, '/');
+    String descriptor = fileName.substring(0, fileName.length() - CLASS_EXTENSION.length());
     if (descriptor.contains(".")) {
-      throw new CompilationError("Unexpected file name in the archive: " + name);
+      throw new CompilationError("Unexpected file name in the archive: " + fileName);
     }
     return 'L' + descriptor + ';';
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index d7fd468..83e1a2b 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -41,6 +41,7 @@
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import joptsimple.internal.Strings;
+import org.junit.Assume;
 import org.junit.rules.TemporaryFolder;
 
 public class ToolHelper {
@@ -53,6 +54,8 @@
   public static final String EXAMPLES_ANDROID_O_BUILD_DIR = BUILD_DIR + "test/examplesAndroidO/";
   public static final String SMALI_BUILD_DIR = BUILD_DIR + "test/smali/";
 
+  public static final String LINE_SEPARATOR = System.getProperty("line.separator");
+
   private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
   private static final int DEFAULT_MIN_SDK = 14;
 
@@ -519,6 +522,7 @@
   }
 
   public static ProcessResult runDX(String[] args) throws IOException {
+    Assume.assumeTrue(ToolHelper.artSupported());
     DXCommandBuilder builder = new DXCommandBuilder();
     for (String arg : args) {
       builder.appendProgramArgument(arg);
@@ -601,6 +605,7 @@
   }
 
   private static ProcessResult runArtProcess(ArtCommandBuilder builder) throws IOException {
+    Assume.assumeTrue(ToolHelper.artSupported());
     ProcessResult result = runProcess(builder.asProcessBuilder());
     if (result.exitCode != 0) {
       fail("Unexpected art failure: '" + result.stderr + "'\n" + result.stdout);
@@ -640,6 +645,7 @@
   }
 
   public static void runDex2Oat(Path file, Path outFile) throws IOException {
+    Assume.assumeTrue(ToolHelper.artSupported());
     assert Files.exists(file);
     assert ByteStreams.toByteArray(Files.newInputStream(file)).length > 0;
     List<String> command = new ArrayList<>();
diff --git a/src/test/java/com/android/tools/r8/debuginfo/LocalsInSwitchTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/LocalsInSwitchTestRunner.java
index 020809a..af726bf 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/LocalsInSwitchTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/LocalsInSwitchTestRunner.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApp;
 import org.junit.Test;
 
@@ -17,7 +18,8 @@
     AndroidApp d8App = compileWithD8(clazz);
     AndroidApp dxApp = getDxCompiledSources();
 
-    String expected = "55\n1862\n15130\n";
+    String expected = "55" + ToolHelper.LINE_SEPARATOR + "1862" + ToolHelper.LINE_SEPARATOR
+            + "15130" + ToolHelper.LINE_SEPARATOR;
     assertEquals(expected, runOnJava(clazz));
     assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
     assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
diff --git a/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java b/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java
index 7370ca2..7cee4a3 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/SynchronizedMethodTestRunner.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.AndroidApp;
 import org.junit.Test;
 
@@ -17,7 +18,7 @@
     AndroidApp d8App = compileWithD8(clazz);
     AndroidApp dxApp = getDxCompiledSources();
 
-    String expected = "42\n42\n";
+    String expected = "42" + ToolHelper.LINE_SEPARATOR + "42" + ToolHelper.LINE_SEPARATOR;
     assertEquals(expected, runOnJava(clazz));
     assertEquals(expected, runOnArt(d8App, clazz.getCanonicalName()));
     assertEquals(expected, runOnArt(dxApp, clazz.getCanonicalName()));
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java b/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
index 984e40e..155438f 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidDebugInfoTests.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.ToolHelper;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
@@ -66,7 +67,7 @@
         "  invokestatic Test/foo(I)V",
         "  return");
 
-    String expected = "42\n0\n";
+    String expected = "42" + ToolHelper.LINE_SEPARATOR + "0" + ToolHelper.LINE_SEPARATOR;
     String javaResult = runOnJava(builder, clazz.name);
     assertEquals(expected, javaResult);
     String artResult = runOnArtD8(builder, clazz.name);
@@ -115,7 +116,7 @@
         "  invokestatic Test/foo(II)V",
         "  return");
 
-    String expected = "42\n";
+    String expected = "42" + ToolHelper.LINE_SEPARATOR;
     String javaResult = runOnJava(builder, clazz.name);
     assertEquals(expected, javaResult);
     String artResult = runOnArtD8(builder, clazz.name);
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java b/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java
index 197b4d1..c74464f 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvokeSpecialTests.java
@@ -5,7 +5,9 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.ToolHelper;
 import com.google.common.collect.ImmutableList;
+import org.junit.AssumptionViolatedException;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -72,7 +74,9 @@
 
     // TODO(zerny): Should we fail early on the above code? Art fails with a verification error
     // because Test.foo is expected to be in the direct method table.
-    thrown.expect(AssertionError.class);
+    if (ToolHelper.artSupported()) {
+      thrown.expect(AssertionError.class);
+    }
     String artResult = runOnArt(builder, clazz.name);
     assertEquals(expected, artResult);
   }
diff --git a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
index 34ddd69..bd060b5 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JumpSubroutineTests.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.utils.AndroidApp;
 import com.google.common.collect.ImmutableList;
@@ -308,7 +309,8 @@
         "  invokestatic Test/foo()V",
         "  return");
 
-    runTest(builder, clazz.name, "Got zero\nGot non-zero\n");
+    runTest(builder, clazz.name, "Got zero" + ToolHelper.LINE_SEPARATOR + "Got non-zero"
+            + ToolHelper.LINE_SEPARATOR);
   }
 
   @Test
@@ -351,7 +353,9 @@
         "  invokestatic Test/foo()V",
         "  return");
 
-    runTest(builder, clazz.name, "Got zero\nGot non-zero, calling nested\nIn nested subroutine\n");
+    runTest(builder, clazz.name, "Got zero" + ToolHelper.LINE_SEPARATOR
+            + "Got non-zero, calling nested" + ToolHelper.LINE_SEPARATOR + "In nested subroutine"
+            + ToolHelper.LINE_SEPARATOR);
   }
 
   @Test
@@ -527,7 +531,8 @@
         "  invokestatic Test/foo()V",
         "  return");
 
-    runTest(builder, clazz.name, "Divided by zero\nDivided by non-zero\n");
+    runTest(builder, clazz.name, "Divided by zero" + ToolHelper.LINE_SEPARATOR
+            + "Divided by non-zero" + ToolHelper.LINE_SEPARATOR);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/jasmin/TryCatchStateTests.java b/src/test/java/com/android/tools/r8/jasmin/TryCatchStateTests.java
index 92f99f8..a24ffd2 100644
--- a/src/test/java/com/android/tools/r8/jasmin/TryCatchStateTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/TryCatchStateTests.java
@@ -5,6 +5,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.ToolHelper;
 import com.google.common.collect.ImmutableList;
 import org.junit.Test;
 
@@ -49,7 +50,7 @@
         "  invokevirtual java/io/PrintStream/print(I)V",
         "  return");
 
-    String expected = "0\n6";
+    String expected = "0" + ToolHelper.LINE_SEPARATOR + "6";
     String javaResult = runOnJava(builder, clazz.name);
     assertEquals(expected, javaResult);
     String artResult = runOnArt(builder, clazz.name);
@@ -97,7 +98,7 @@
         "  invokevirtual java/io/PrintStream/print(I)V",
         "  return");
 
-    String expected = "12\n21";
+    String expected = "12" + ToolHelper.LINE_SEPARATOR + "21";
     String javaResult = runOnJava(builder, clazz.name);
     assertEquals(expected, javaResult);
     String artResult = runOnArt(builder, clazz.name);
@@ -148,7 +149,7 @@
         "  invokevirtual java/io/PrintStream/print(I)V",
         "  return");
 
-    String expected = "12\n21";
+    String expected = "12" + ToolHelper.LINE_SEPARATOR + "21";
     String javaResult = runOnJava(builder, clazz.name);
     assertEquals(expected, javaResult);
     String artResult = runOnArt(builder, clazz.name);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index ae0f650..ca242b7 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -142,7 +142,7 @@
           .collect(Collectors.toList());
       Collections.sort(resultMainDexList);
       String[] refList = new String(Files.readAllBytes(
-          expectedMainDexList), StandardCharsets.UTF_8).split("\n");
+          expectedMainDexList), StandardCharsets.UTF_8).split(ToolHelper.LINE_SEPARATOR);
       for (int i = 0; i < refList.length; i++) {
         String reference = refList[i];
         String computed = resultMainDexList.get(i);
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 5d5bc17..52bbda6 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -11,6 +11,7 @@
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexAccessFlags;
 import com.android.tools.r8.graph.DexItemFactory;
 import java.io.IOException;
@@ -45,6 +46,8 @@
       INVALID_PROGUARD_DIR + "including-2.flags";
   private static final String LIBRARY_JARS =
       VALID_PROGUARD_DIR + "library-jars.flags";
+  private static final String LIBRARY_JARS_WIN =
+          VALID_PROGUARD_DIR + "library-jars-win.flags";
   private static final String SEEDS =
       VALID_PROGUARD_DIR + "seeds.flags";
   private static final String SEEDS_2 =
@@ -281,7 +284,11 @@
   @Test
   public void parseLibraryJars() throws IOException, ProguardRuleParserException {
     ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
-    parser.parse(Paths.get(LIBRARY_JARS));
+    if (!ToolHelper.isLinux() && !ToolHelper.isMac()) {
+      parser.parse(Paths.get(LIBRARY_JARS_WIN));
+    } else {
+      parser.parse(Paths.get(LIBRARY_JARS));
+    }
     assertEquals(4, parser.getConfig().getLibraryjars().size());
   }
 
diff --git a/src/test/java/com/android/tools/r8/utils/ArtCommandBuilderTest.java b/src/test/java/com/android/tools/r8/utils/ArtCommandBuilderTest.java
index 0344a27..fa3497b 100644
--- a/src/test/java/com/android/tools/r8/utils/ArtCommandBuilderTest.java
+++ b/src/test/java/com/android/tools/r8/utils/ArtCommandBuilderTest.java
@@ -9,10 +9,17 @@
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.DexVm;
 import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
 import org.junit.Test;
 
 public class ArtCommandBuilderTest {
 
+  @Before
+  public void setUp() {
+    Assume.assumeTrue(ToolHelper.artSupported());
+  }
+
   @Test
   public void noArguments() {
     ArtCommandBuilder builder = new ArtCommandBuilder();
diff --git a/src/test/proguard/valid/library-jars-win.flags b/src/test/proguard/valid/library-jars-win.flags
new file mode 100644
index 0000000..c6ffbb4
--- /dev/null
+++ b/src/test/proguard/valid/library-jars-win.flags
@@ -0,0 +1,2 @@
+-libraryjars some/library.jar
+-libraryjars some/library.jar;some/other/library.jar;yet/another/library.jar
\ No newline at end of file
diff --git a/tools/windows/README.dx b/tools/windows/README.dx
new file mode 100644
index 0000000..99db256
--- /dev/null
+++ b/tools/windows/README.dx
@@ -0,0 +1,23 @@
+Dx version: 1.12
+dx.bat and dx.jar are fetched from SDK build tools 24.0.0.
+dx.bat has been modified so that the code that checks if 'java' if on current path
+is removed and replaced by a direct reference to it.
+This is done because this relies on a tool found in the SDK and not present in the
+current package.
+Diff:
+
+26,29c26,29
+< set java_exe=
+< if exist    "%~dp0..\tools\lib\find_java.bat" call    "%~dp0..\tools\lib\find_java.bat"
+< if exist "%~dp0..\..\tools\lib\find_java.bat" call "%~dp0..\..\tools\lib\find_java.bat"
+< if not defined java_exe goto :EOF
+---
+> REM set java_exe=
+> REM if exist    "%~dp0..\tools\lib\find_java.bat" call    "%~dp0..\tools\lib\find_java.bat"
+> REM if exist "%~dp0..\..\tools\lib\find_java.bat" call "%~dp0..\..\tools\lib\find_java.bat"
+> REM if not defined java_exe goto :EOF
+87c87
+< call "%java_exe%" %javaOpts% -Djava.ext.dirs="%frameworkdir%" -jar "%jarpath%" %params%
+---
+> call java %javaOpts% -Djava.ext.dirs="%frameworkdir%" -jar "%jarpath%" %params%
+
diff --git a/tools/windows/dx.tar.gz.sha1 b/tools/windows/dx.tar.gz.sha1
new file mode 100644
index 0000000..beadad3
--- /dev/null
+++ b/tools/windows/dx.tar.gz.sha1
@@ -0,0 +1 @@
+1d680c9efc9e17d4fc51e8afd0bbe1b3d8724903
\ No newline at end of file