ART: Add smali-based run-test

Add run-test 800 for smali-based tests. To use, drop a smali file
into the src/ directory and add a TestCase in src/Main.java.

Bug: 17814475

Change-Id: Ica9eb830689862cb3a4ffa0019fbc447c01af744
diff --git a/test/800-smali/build b/test/800-smali/build
new file mode 100644
index 0000000..1b5a4e3
--- /dev/null
+++ b/test/800-smali/build
@@ -0,0 +1,32 @@
+#!/bin/bash
+#
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Stop if something fails.
+set -e
+
+# Compile Java classes
+mkdir classes
+${JAVAC} -d classes `find src -name '*.java'`
+${DX} -JXmx256m --debug --dex --output=java_classes.dex classes
+
+# Compile Smali classes
+${SMALI} -JXmx256m --output smali_classes.dex `find src -name '*.smali'`
+
+# Combine files.
+${DXMERGER} classes.dex java_classes.dex smali_classes.dex
+
+# Zip up output.
+zip $TEST_NAME.jar classes.dex
diff --git a/test/800-smali/expected.txt b/test/800-smali/expected.txt
new file mode 100644
index 0000000..468e7a6
--- /dev/null
+++ b/test/800-smali/expected.txt
@@ -0,0 +1,2 @@
+b/17790197
+Done!
diff --git a/test/800-smali/info.txt b/test/800-smali/info.txt
new file mode 100644
index 0000000..cfcc230
--- /dev/null
+++ b/test/800-smali/info.txt
@@ -0,0 +1,4 @@
+Smali-based tests.
+Will compile and run all the smali files in src/ and run the test cases mentioned in src/Main.java.
+
+Obviously needs to run under Dalvik or ART.
diff --git a/test/800-smali/src/Main.java b/test/800-smali/src/Main.java
new file mode 100644
index 0000000..0ef3a9d
--- /dev/null
+++ b/test/800-smali/src/Main.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.lang.reflect.Method;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Smali excercise.
+ */
+public class Main {
+
+    private static class TestCase {
+        public TestCase(String testName, String testClass, String testMethodName, Object[] values,
+                        Throwable expectedException, Object expectedReturn) {
+            this.testName = testName;
+            this.testClass = testClass;
+            this.testMethodName = testMethodName;
+            this.values = values;
+            this.expectedException = expectedException;
+            this.expectedReturn = expectedReturn;
+        }
+
+        String testName;
+        String testClass;
+        String testMethodName;
+        Object[] values;
+        Throwable expectedException;
+        Object expectedReturn;
+    }
+
+    private List<TestCase> testCases;
+
+    public Main() {
+        // Create the test cases.
+        testCases = new LinkedList<TestCase>();
+
+        testCases.add(new TestCase("b/17790197", "B17790197", "getInt", null, null, 100));
+    }
+
+    public void runTests() {
+        for (TestCase tc : testCases) {
+            System.out.println(tc.testName);
+            try {
+                runTest(tc);
+            } catch (Exception exc) {
+                exc.printStackTrace(System.out);
+            }
+        }
+    }
+
+    private void runTest(TestCase tc) throws Exception {
+        Class<?> c = Class.forName(tc.testClass);
+
+        Method[] methods = c.getDeclaredMethods();
+
+        // For simplicity we assume that test methods are not overloaded. So searching by name
+        // will give us the method we need to run.
+        Method method = null;
+        for (Method m : methods) {
+            if (m.getName().equals(tc.testMethodName)) {
+                method = m;
+                break;
+            }
+        }
+
+        if (method == null) {
+            throw new IllegalArgumentException("Could not find test method " + tc.testMethodName +
+                    " in class " + tc.testClass + " for test " + tc.testName);
+        }
+
+        Exception errorReturn = null;
+        try {
+            Object retValue = method.invoke(null, tc.values);
+            if (tc.expectedException != null) {
+                errorReturn = new IllegalStateException("Expected an exception in test " +
+                                                        tc.testName);
+            }
+            if (tc.expectedReturn == null && retValue != null) {
+                errorReturn = new IllegalStateException("Expected a null result in test " +
+                                                        tc.testName);
+            } else if (tc.expectedReturn != null &&
+                       (retValue == null || !tc.expectedReturn.equals(retValue))) {
+                errorReturn = new IllegalStateException("Expected return " + tc.expectedReturn +
+                                                        ", but got " + retValue);
+            }
+        } catch (Exception exc) {
+            if (tc.expectedException == null) {
+                errorReturn = new IllegalStateException("Did not expect exception", exc);
+            } else if (!tc.expectedException.getClass().equals(exc.getClass())) {
+                errorReturn = new IllegalStateException("Expected " +
+                                                tc.expectedException.getClass().getName() +
+                                                ", but got " + exc.getClass(), exc);
+            }
+        } finally {
+            if (errorReturn != null) {
+                throw errorReturn;
+            }
+        }
+    }
+
+    public static void main(String[] args) throws Exception {
+        Main main = new Main();
+
+        main.runTests();
+
+        System.out.println("Done!");
+    }
+}
diff --git a/test/800-smali/src/b_17790197.smali b/test/800-smali/src/b_17790197.smali
new file mode 100644
index 0000000..7560fcf
--- /dev/null
+++ b/test/800-smali/src/b_17790197.smali
@@ -0,0 +1,17 @@
+.class public LB17790197;
+
+.super Ljava/lang/Object;
+
+.method public static getInt()I
+    .registers 4
+    const/16 v0, 100
+    const/4 v1, 1
+    const/4 v2, 7
+    :loop
+    if-eq v2, v0, :done
+    add-int v2, v2, v1
+    goto :loop
+    :done
+    add-float v3, v0, v1
+    return v2
+.end method
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index 57b7b33..9082b47 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -36,9 +36,11 @@
 # $(1): the test number
 define define-build-art-run-test
   dmart_target := $(art_run_tests_dir)/art-run-tests/$(1)/touch
-$$(dmart_target): $(DX) $(HOST_OUT_EXECUTABLES)/jasmin
+$$(dmart_target): $(DX) $(HOST_OUT_EXECUTABLES)/jasmin $(HOST_OUT_EXECUTABLES)/smali $(HOST_OUT_EXECUTABLES)/dexmerger
 	$(hide) rm -rf $$(dir $$@) && mkdir -p $$(dir $$@)
 	$(hide) DX=$(abspath $(DX)) JASMIN=$(abspath $(HOST_OUT_EXECUTABLES)/jasmin) \
+	  SMALI=$(abspath $(HOST_OUT_EXECUTABLES)/smali) \
+	  DXMERGER=$(abspath $(HOST_OUT_EXECUTABLES)/dexmerger) \
 	  $(LOCAL_PATH)/run-test --build-only --output-path $$(abspath $$(dir $$@)) $(1)
 	$(hide) touch $$@
 
@@ -50,7 +52,7 @@
 include $(CLEAR_VARS)
 LOCAL_MODULE_TAGS := tests
 LOCAL_MODULE := art-run-tests
-LOCAL_ADDITIONAL_DEPENDENCIES := $(TEST_ART_RUN_TEST_BUILD_RULES)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(TEST_ART_RUN_TEST_BUILD_RULES) smali dexmerger
 # The build system use this flag to pick up files generated by declare-make-art-run-test.
 LOCAL_PICKUP_FILES := $(art_run_tests_dir)
 
@@ -455,9 +457,11 @@
       $$(run_test_options)
 $$(run_test_rule_name): PRIVATE_RUN_TEST_OPTIONS := $$(run_test_options)
 .PHONY: $$(run_test_rule_name)
-$$(run_test_rule_name): $(DX) $(HOST_OUT_EXECUTABLES)/jasmin $$(prereq_rule)
+$$(run_test_rule_name): $(DX) $(HOST_OUT_EXECUTABLES)/jasmin $(HOST_OUT_EXECUTABLES)/smali $(HOST_OUT_EXECUTABLES)/dexmerger $$(prereq_rule)
 	$(hide) $$(call ART_TEST_SKIP,$$@) && \
 	  DX=$(abspath $(DX)) JASMIN=$(abspath $(HOST_OUT_EXECUTABLES)/jasmin) \
+	    SMALI=$(abspath $(HOST_OUT_EXECUTABLES)/smali) \
+	    DXMERGER=$(abspath $(HOST_OUT_EXECUTABLES)/dexmerger) \
 	    art/test/run-test $$(PRIVATE_RUN_TEST_OPTIONS) $(9) \
 	      && $$(call ART_TEST_PASSED,$$@) || $$(call ART_TEST_FAILED,$$@)
 	$$(hide) (echo $(MAKECMDGOALS) | grep -q $$@ && \
diff --git a/test/run-test b/test/run-test
index 3b5df0d..3e98d6d 100755
--- a/test/run-test
+++ b/test/run-test
@@ -55,6 +55,16 @@
   export JASMIN="jasmin"
 fi
 
+# If smali was not set by the environment variable, assume it is in the path.
+if [ -z "$SMALI" ]; then
+  export SMALI="smali"
+fi
+
+# If dexmerger was not set by the environment variable, assume it is in the path.
+if [ -z "$DXMERGER" ]; then
+  export DXMERGER="dexmerger"
+fi
+
 
 info="info.txt"
 build="build"