Add an alternate, simpler method for running signature test.

This change will allow the signature test to be executed just like a normal JUnit
based test, and will thus save a lot of unnecessary logic in the tradefed harness.

The existing signature test entry point is still retained, to avoid changing the
legacy CTS test harness.

Change-Id: I4a21811ee89c11960de99dfa4aa14df06a85afe4
diff --git a/tests/SignatureTest/Android.mk b/tests/SignatureTest/Android.mk
index 18c7cea..4166002 100644
--- a/tests/SignatureTest/Android.mk
+++ b/tests/SignatureTest/Android.mk
@@ -20,7 +20,7 @@
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 
diff --git a/tests/SignatureTest/AndroidManifest.xml b/tests/SignatureTest/AndroidManifest.xml
index 696dc57..762dfae 100644
--- a/tests/SignatureTest/AndroidManifest.xml
+++ b/tests/SignatureTest/AndroidManifest.xml
@@ -30,10 +30,16 @@
             </intent-filter>
         </activity>
 
+        <uses-library android:name="android.test.runner" />
     </application>
 
     <instrumentation android:name=".InstrumentationRunner"
                      android:targetPackage="android.tests.sigtest"
                      android:label="API Signature Test"/>
 
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+        android:targetPackage="android.tests.sigtest"
+        android:label="Simple API Signature Test">
+    </instrumentation>
+
 </manifest>
diff --git a/tests/SignatureTest/src/android/tests/sigtest/SimpleSignatureTest.java b/tests/SignatureTest/src/android/tests/sigtest/SimpleSignatureTest.java
new file mode 100644
index 0000000..c206c9a
--- /dev/null
+++ b/tests/SignatureTest/src/android/tests/sigtest/SimpleSignatureTest.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2011 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 android.tests.sigtest;
+
+import android.content.res.Resources;
+import android.test.AndroidTestCase;
+import android.tests.sigtest.JDiffClassDescription.JDiffConstructor;
+import android.tests.sigtest.JDiffClassDescription.JDiffField;
+import android.tests.sigtest.JDiffClassDescription.JDiffMethod;
+import android.tests.sigtest.SignatureTestActivity.FAILURE_TYPE;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+
+/**
+ * A simpler version of {@link SignatureTest} that performs the signature check via a JUnit test.
+ * <p/>
+ * Eventually the existing  {@link SignatureTest} and {@link SignatureActivity} will be deleted
+ * once the move to a tradefederation based CTS harness is complete.
+ */
+public class SimpleSignatureTest extends AndroidTestCase {
+
+    private static final String TAG_ROOT = "api";
+    private static final String TAG_PACKAGE = "package";
+    private static final String TAG_CLASS = "class";
+    private static final String TAG_INTERFACE = "interface";
+    private static final String TAG_IMPLEMENTS = "implements";
+    private static final String TAG_CONSTRUCTOR = "constructor";
+    private static final String TAG_METHOD = "method";
+    private static final String TAG_PARAM = "parameter";
+    private static final String TAG_EXCEPTION = "exception";
+    private static final String TAG_FIELD = "field";
+
+    private static final String MODIFIER_ABSTRACT = "abstract";
+    private static final String MODIFIER_FINAL = "final";
+    private static final String MODIFIER_NATIVE = "native";
+    private static final String MODIFIER_PRIVATE = "private";
+    private static final String MODIFIER_PROTECTED = "protected";
+    private static final String MODIFIER_PUBLIC = "public";
+    private static final String MODIFIER_STATIC = "static";
+    private static final String MODIFIER_SYNCHRONIZED = "synchronized";
+    private static final String MODIFIER_TRANSIENT = "transient";
+    private static final String MODIFIER_VOLATILE = "volatile";
+    private static final String MODIFIER_VISIBILITY = "visibility";
+
+    private static final String ATTRIBUTE_NAME = "name";
+    private static final String ATTRIBUTE_EXTENDS = "extends";
+    private static final String ATTRIBUTE_TYPE = "type";
+    private static final String ATTRIBUTE_RETURN = "return";
+
+    private static ArrayList<String> mDebugArray = new ArrayList<String>();
+
+    private HashSet<String> mKeyTagSet;
+    private TestResultObserver mResultObserver;
+
+    private class TestResultObserver implements ResultObserver {
+        boolean mDidFail = false;
+        StringBuilder mErrorString = new StringBuilder();
+
+        public void notifyFailure(FAILURE_TYPE type, String name, String errorMessage) {
+            mDidFail = true;
+            mErrorString.append("\n");
+            mErrorString.append(type.toString().toLowerCase());
+            mErrorString.append(":\t");
+            mErrorString.append(name);
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mKeyTagSet = new HashSet<String>();
+        mKeyTagSet.addAll(Arrays.asList(new String[] {
+                TAG_PACKAGE, TAG_CLASS, TAG_INTERFACE, TAG_IMPLEMENTS, TAG_CONSTRUCTOR,
+                TAG_METHOD, TAG_PARAM, TAG_EXCEPTION, TAG_FIELD }));
+        mResultObserver = new TestResultObserver();
+    }
+
+    /**
+     * Tests that the device's API matches the expected set defined in xml.
+     * <p/>
+     * Will check the entire API, and then report the complete list of failures
+     */
+    public void testSignature() {
+        Resources r = getContext().getResources();
+        Class rClass = R.xml.class;
+        Field[] fs = rClass.getFields();
+        for (Field f : fs) {
+            try {
+                start(r.getXml(f.getInt(rClass)));
+            } catch (Exception e) {
+                mResultObserver.notifyFailure(FAILURE_TYPE.CAUGHT_EXCEPTION, e.getMessage(),
+                        e.getMessage());
+            }
+        }
+        if (mResultObserver.mDidFail) {
+            fail(mResultObserver.mErrorString.toString());
+        }
+    }
+
+    private  void beginDocument(XmlPullParser parser, String firstElementName)
+            throws XmlPullParserException, IOException {
+        int type;
+        while ((type=parser.next()) != XmlPullParser.START_TAG
+                   && type != XmlPullParser.END_DOCUMENT) { }
+
+        if (type != XmlPullParser.START_TAG) {
+            throw new XmlPullParserException("No start tag found");
+        }
+
+        if (!parser.getName().equals(firstElementName)) {
+            throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
+                    ", expected " + firstElementName);
+        }
+    }
+
+    /**
+     * Signature test entry point.
+     */
+    private void start(XmlPullParser parser) throws XmlPullParserException, IOException {
+        JDiffClassDescription currentClass = null;
+        String currentPackage = "";
+        JDiffMethod currentMethod = null;
+
+        beginDocument(parser, TAG_ROOT);
+        int type;
+        while (true) {
+            type = XmlPullParser.START_DOCUMENT;
+            while ((type=parser.next()) != XmlPullParser.START_TAG
+                       && type != XmlPullParser.END_DOCUMENT
+                       && type != XmlPullParser.END_TAG) {
+
+            }
+
+            if (type == XmlPullParser.END_TAG) {
+                if (TAG_CLASS.equals(parser.getName())
+                        || TAG_INTERFACE.equals(parser.getName())) {
+                    currentClass.checkSignatureCompliance();
+                } else if (TAG_PACKAGE.equals(parser.getName())) {
+                    currentPackage = "";
+                }
+                continue;
+            }
+
+            if (type == XmlPullParser.END_DOCUMENT) {
+                break;
+            }
+
+            String tagname = parser.getName();
+            if (!mKeyTagSet.contains(tagname)) {
+                continue;
+            }
+
+            if (type == XmlPullParser.START_TAG && tagname.equals(TAG_PACKAGE)) {
+                currentPackage = parser.getAttributeValue(null, ATTRIBUTE_NAME);
+            } else if (tagname.equals(TAG_CLASS)) {
+                currentClass = loadClassInfo(parser, false, currentPackage);
+            } else if (tagname.equals(TAG_INTERFACE)) {
+                currentClass = loadClassInfo(parser, true, currentPackage);
+            } else if (tagname.equals(TAG_IMPLEMENTS)) {
+                currentClass.addImplInterface(parser.getAttributeValue(null, ATTRIBUTE_NAME));
+            } else if (tagname.equals(TAG_CONSTRUCTOR)) {
+                JDiffConstructor constructor = loadConstructorInfo(parser, currentClass);
+                currentClass.addConstructor(constructor);
+                currentMethod = constructor;
+            } else if (tagname.equals(TAG_METHOD)) {
+                currentMethod = loadMethodInfo(currentClass.getClassName(), parser);
+                currentClass.addMethod(currentMethod);
+            } else if (tagname.equals(TAG_PARAM)) {
+                currentMethod.addParam(parser.getAttributeValue(null, ATTRIBUTE_TYPE));
+            } else if (tagname.equals(TAG_EXCEPTION)) {
+                currentMethod.addException(parser.getAttributeValue(null, ATTRIBUTE_TYPE));
+            } else if (tagname.equals(TAG_FIELD)) {
+                JDiffField field = loadFieldInfo(currentClass.getClassName(), parser);
+                currentClass.addField(field);
+            } else {
+                throw new RuntimeException(
+                        "unknow tag exception:" + tagname);
+            }
+        }
+    }
+
+    /**
+     * Load field information from xml to memory.
+     *
+     * @param className of the class being examined which will be shown in error messages
+     * @param parser The XmlPullParser which carries the xml information.
+     * @return the new field
+     */
+    private JDiffField loadFieldInfo(String className, XmlPullParser parser) {
+        String fieldName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
+        String fieldType = parser.getAttributeValue(null, ATTRIBUTE_TYPE);
+        int modifier = jdiffModifierToReflectionFormat(className, parser);
+        return new JDiffField(fieldName, fieldType, modifier);
+    }
+
+    /**
+     * Load method information from xml to memory.
+     *
+     * @param className of the class being examined which will be shown in error messages
+     * @param parser The XmlPullParser which carries the xml information.
+     * @return the newly loaded method.
+     */
+    private JDiffMethod loadMethodInfo(String className, XmlPullParser parser) {
+        String methodName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
+        String returnType = parser.getAttributeValue(null, ATTRIBUTE_RETURN);
+        int modifier = jdiffModifierToReflectionFormat(className, parser);
+        return new JDiffMethod(methodName, modifier, returnType);
+    }
+
+    /**
+     * Load constructor information from xml to memory.
+     *
+     * @param parser The XmlPullParser which carries the xml information.
+     * @param currentClass the current class being loaded.
+     * @return the new constructor
+     */
+    private JDiffConstructor loadConstructorInfo(XmlPullParser parser,
+                                                 JDiffClassDescription currentClass) {
+        String name = currentClass.getClassName();
+        int modifier = jdiffModifierToReflectionFormat(name, parser);
+        return new JDiffConstructor(name, modifier);
+    }
+
+    /**
+     * Load class or interface information to memory.
+     *
+     * @param parser The XmlPullParser which carries the xml information.
+     * @param isInterface true if the current class is an interface, otherwise is false.
+     * @param pkg the name of the java package this class can be found in.
+     * @return the new class description.
+     */
+    private JDiffClassDescription loadClassInfo(XmlPullParser parser,
+                                                boolean isInterface,
+                                                String pkg) {
+        String className = parser.getAttributeValue(null, ATTRIBUTE_NAME);
+        JDiffClassDescription currentClass = new JDiffClassDescription(pkg,
+                                                                       className,
+                                                                       mResultObserver);
+        currentClass.setModifier(jdiffModifierToReflectionFormat(className, parser));
+        currentClass.setType(isInterface ? JDiffClassDescription.JDiffType.INTERFACE :
+                             JDiffClassDescription.JDiffType.CLASS);
+        currentClass.setExtendsClass(parser.getAttributeValue(null, ATTRIBUTE_EXTENDS));
+        return currentClass;
+    }
+
+    /**
+     * Convert string modifier to int modifier.
+     *
+     * @param name of the class/method/field being examined which will be shown in error messages
+     * @param key modifier name
+     * @param value modifier value
+     * @return converted modifier value
+     */
+    private static int modifierDescriptionToReflectedType(String name, String key, String value) {
+        if (key.equals(MODIFIER_ABSTRACT)) {
+            return value.equals("true") ? Modifier.ABSTRACT : 0;
+        } else if (key.equals(MODIFIER_FINAL)) {
+            return value.equals("true") ? Modifier.FINAL : 0;
+        } else if (key.equals(MODIFIER_NATIVE)) {
+            return value.equals("true") ? Modifier.NATIVE : 0;
+        } else if (key.equals(MODIFIER_STATIC)) {
+            return value.equals("true") ? Modifier.STATIC : 0;
+        } else if (key.equals(MODIFIER_SYNCHRONIZED)) {
+            return value.equals("true") ? Modifier.SYNCHRONIZED : 0;
+        } else if (key.equals(MODIFIER_TRANSIENT)) {
+            return value.equals("true") ? Modifier.TRANSIENT : 0;
+        } else if (key.equals(MODIFIER_VOLATILE)) {
+            return value.equals("true") ? Modifier.VOLATILE : 0;
+        } else if (key.equals(MODIFIER_VISIBILITY)) {
+            if (value.equals(MODIFIER_PRIVATE)) {
+                throw new RuntimeException("Private visibility found in API spec: " + name);
+            } else if (value.equals(MODIFIER_PROTECTED)) {
+                return Modifier.PROTECTED;
+            } else if (value.equals(MODIFIER_PUBLIC)) {
+                return Modifier.PUBLIC;
+            } else if ("".equals(value)) {
+                // If the visibility is "", it means it has no modifier.
+                // which is package private. We should return 0 for this modifier.
+                return 0;
+            } else {
+                throw new RuntimeException("Unknown modifier found in API spec: " + value);
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Transfer string modifier to int one.
+     *
+     * @param name of the class/method/field being examined which will be shown in error messages
+     * @param parser XML resource parser
+     * @return converted modifier
+     */
+    private static int jdiffModifierToReflectionFormat(String name, XmlPullParser parser){
+        int modifier = 0;
+        for (int i = 0;i < parser.getAttributeCount();i++) {
+            modifier |= modifierDescriptionToReflectedType(name, parser.getAttributeName(i),
+                    parser.getAttributeValue(i));
+        }
+        return modifier;
+    }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
index 79723c1..a57e229 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
@@ -33,6 +33,8 @@
 class TestPackageDef implements ITestPackageDef {
 
     private static final String LOG_TAG = "TestPackageDef";
+    private static final String SIGNATURE_TEST_METHOD = "testSignature";
+    private static final String SIGNATURE_TEST_CLASS = "android.tests.sigtest.SimpleSignatureTest";
 
     private String mUri = null;
     private String mAppNameSpace = null;
@@ -129,10 +131,26 @@
             hostTest.setTests(filterTests(mTests, className, methodName));
             return hostTest;
         } else if (mIsSignatureTest) {
-            // TODO: implement this
-            Log.w(LOG_TAG, String.format("Skipping currently unsupported signature test %s",
-                    mName));
-            return null;
+            // TODO: hardcode the runner/class/method for now, since current package xml
+            // points to specialized instrumentation. Eventually this special case for signatureTest
+            // can be removed, and it can be treated just like a normal InstrumentationTest
+            Log.d(LOG_TAG, String.format("Creating signature test %s", mName));
+            InstrumentationTest instrTest = new InstrumentationTest();
+            instrTest.setPackageName(mAppNameSpace);
+            instrTest.setRunnerName("android.test.InstrumentationTestRunner");
+            instrTest.setClassName(SIGNATURE_TEST_CLASS);
+            instrTest.setMethodName(SIGNATURE_TEST_METHOD);
+            // add signature test to list of known tests
+            addTest(new TestIdentifier(SIGNATURE_TEST_CLASS, SIGNATURE_TEST_METHOD));
+            // mName means 'apk file name' for instrumentation tests
+            File apkFile = new File(testCaseDir, String.format("%s.apk", mName));
+            if (!apkFile.exists()) {
+                Log.w(LOG_TAG, String.format("Could not find apk file %s",
+                        apkFile.getAbsolutePath()));
+                return null;
+            }
+            instrTest.setInstallFile(apkFile);
+            return instrTest;
         } else if (mIsReferenceAppTest) {
             // TODO: implement this
             Log.w(LOG_TAG, String.format("Skipping currently unsupported reference app test %s",