blob: c206c9a4942fb481b714c66ecfcb9aadf78ab2ca [file] [log] [blame]
/*
* 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;
}
}