1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.signature.cts;
18 
19 import android.content.res.Resources;
20 import android.signature.R;
21 import android.signature.cts.JDiffClassDescription.JDiffConstructor;
22 import android.signature.cts.JDiffClassDescription.JDiffField;
23 import android.signature.cts.JDiffClassDescription.JDiffMethod;
24 import android.test.AndroidTestCase;
25 import android.util.Log;
26 
27 import org.xmlpull.v1.XmlPullParser;
28 import org.xmlpull.v1.XmlPullParserException;
29 
30 import java.io.IOException;
31 import java.lang.reflect.Field;
32 import java.lang.reflect.Modifier;
33 import java.util.ArrayList;
34 import java.util.Arrays;
35 import java.util.HashSet;
36 
37 /**
38  * Performs the signature check via a JUnit test.
39  */
40 public class SignatureTest extends AndroidTestCase {
41 
42     private static final String TAG = SignatureTest.class.getSimpleName();
43 
44     private static final String TAG_ROOT = "api";
45     private static final String TAG_PACKAGE = "package";
46     private static final String TAG_CLASS = "class";
47     private static final String TAG_INTERFACE = "interface";
48     private static final String TAG_IMPLEMENTS = "implements";
49     private static final String TAG_CONSTRUCTOR = "constructor";
50     private static final String TAG_METHOD = "method";
51     private static final String TAG_PARAM = "parameter";
52     private static final String TAG_EXCEPTION = "exception";
53     private static final String TAG_FIELD = "field";
54 
55     private static final String MODIFIER_ABSTRACT = "abstract";
56     private static final String MODIFIER_FINAL = "final";
57     private static final String MODIFIER_NATIVE = "native";
58     private static final String MODIFIER_PRIVATE = "private";
59     private static final String MODIFIER_PROTECTED = "protected";
60     private static final String MODIFIER_PUBLIC = "public";
61     private static final String MODIFIER_STATIC = "static";
62     private static final String MODIFIER_SYNCHRONIZED = "synchronized";
63     private static final String MODIFIER_TRANSIENT = "transient";
64     private static final String MODIFIER_VOLATILE = "volatile";
65     private static final String MODIFIER_VISIBILITY = "visibility";
66 
67     private static final String ATTRIBUTE_NAME = "name";
68     private static final String ATTRIBUTE_EXTENDS = "extends";
69     private static final String ATTRIBUTE_TYPE = "type";
70     private static final String ATTRIBUTE_RETURN = "return";
71 
72     private HashSet<String> mKeyTagSet;
73     private TestResultObserver mResultObserver;
74 
75     private class TestResultObserver implements ResultObserver {
76         boolean mDidFail = false;
77         StringBuilder mErrorString = new StringBuilder();
78 
79         @Override
notifyFailure(FailureType type, String name, String errorMessage)80         public void notifyFailure(FailureType type, String name, String errorMessage) {
81             mDidFail = true;
82             mErrorString.append("\n");
83             mErrorString.append(type.toString().toLowerCase());
84             mErrorString.append(":\t");
85             mErrorString.append(name);
86             mErrorString.append("\tError: ");
87             mErrorString.append(errorMessage);
88         }
89     }
90 
91     @Override
setUp()92     protected void setUp() throws Exception {
93         super.setUp();
94         mKeyTagSet = new HashSet<String>();
95         mKeyTagSet.addAll(Arrays.asList(new String[] {
96                 TAG_PACKAGE, TAG_CLASS, TAG_INTERFACE, TAG_IMPLEMENTS, TAG_CONSTRUCTOR,
97                 TAG_METHOD, TAG_PARAM, TAG_EXCEPTION, TAG_FIELD }));
98         mResultObserver = new TestResultObserver();
99     }
100 
101     /**
102      * Tests that the device's API matches the expected set defined in xml.
103      * <p/>
104      * Will check the entire API, and then report the complete list of failures
105      */
testSignature()106     public void testSignature() {
107         Resources r = getContext().getResources();
108         Class rClass = R.xml.class;
109         logd(String.format("Class: %s", rClass.toString()));
110         Field[] fs = rClass.getFields();
111         for (Field f : fs) {
112             logd(String.format("Field: %s", f.toString()));
113             try {
114                 start(r.getXml(f.getInt(rClass)));
115             } catch (Exception e) {
116                 mResultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION, e.getMessage(),
117                         e.getMessage());
118             }
119         }
120         if (mResultObserver.mDidFail) {
121             fail(mResultObserver.mErrorString.toString());
122         }
123     }
124 
beginDocument(XmlPullParser parser, String firstElementName)125     private  void beginDocument(XmlPullParser parser, String firstElementName)
126             throws XmlPullParserException, IOException {
127         int type;
128         while ((type=parser.next()) != XmlPullParser.START_TAG
129                    && type != XmlPullParser.END_DOCUMENT) { }
130 
131         if (type != XmlPullParser.START_TAG) {
132             throw new XmlPullParserException("No start tag found");
133         }
134 
135         if (!parser.getName().equals(firstElementName)) {
136             throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
137                     ", expected " + firstElementName);
138         }
139     }
140 
141     /**
142      * Signature test entry point.
143      */
start(XmlPullParser parser)144     private void start(XmlPullParser parser) throws XmlPullParserException, IOException {
145         logd(String.format("Name: %s", parser.getName()));
146         logd(String.format("Text: %s", parser.getText()));
147         logd(String.format("Namespace: %s", parser.getNamespace()));
148         logd(String.format("Line Number: %s", parser.getLineNumber()));
149         logd(String.format("Column Number: %s", parser.getColumnNumber()));
150         logd(String.format("Position Description: %s", parser.getPositionDescription()));
151         JDiffClassDescription currentClass = null;
152         String currentPackage = "";
153         JDiffMethod currentMethod = null;
154 
155         beginDocument(parser, TAG_ROOT);
156         int type;
157         while (true) {
158             type = XmlPullParser.START_DOCUMENT;
159             while ((type=parser.next()) != XmlPullParser.START_TAG
160                        && type != XmlPullParser.END_DOCUMENT
161                        && type != XmlPullParser.END_TAG) {
162 
163             }
164 
165             if (type == XmlPullParser.END_TAG) {
166                 if (TAG_CLASS.equals(parser.getName())
167                         || TAG_INTERFACE.equals(parser.getName())) {
168                     currentClass.checkSignatureCompliance();
169                 } else if (TAG_PACKAGE.equals(parser.getName())) {
170                     currentPackage = "";
171                 }
172                 continue;
173             }
174 
175             if (type == XmlPullParser.END_DOCUMENT) {
176                 break;
177             }
178 
179             String tagname = parser.getName();
180             if (!mKeyTagSet.contains(tagname)) {
181                 continue;
182             }
183 
184             if (type == XmlPullParser.START_TAG && tagname.equals(TAG_PACKAGE)) {
185                 currentPackage = parser.getAttributeValue(null, ATTRIBUTE_NAME);
186             } else if (tagname.equals(TAG_CLASS)) {
187                 currentClass = loadClassInfo(parser, false, currentPackage);
188             } else if (tagname.equals(TAG_INTERFACE)) {
189                 currentClass = loadClassInfo(parser, true, currentPackage);
190             } else if (tagname.equals(TAG_IMPLEMENTS)) {
191                 currentClass.addImplInterface(parser.getAttributeValue(null, ATTRIBUTE_NAME));
192             } else if (tagname.equals(TAG_CONSTRUCTOR)) {
193                 JDiffConstructor constructor = loadConstructorInfo(parser, currentClass);
194                 currentClass.addConstructor(constructor);
195                 currentMethod = constructor;
196             } else if (tagname.equals(TAG_METHOD)) {
197                 currentMethod = loadMethodInfo(currentClass.getClassName(), parser);
198                 currentClass.addMethod(currentMethod);
199             } else if (tagname.equals(TAG_PARAM)) {
200                 currentMethod.addParam(parser.getAttributeValue(null, ATTRIBUTE_TYPE));
201             } else if (tagname.equals(TAG_EXCEPTION)) {
202                 currentMethod.addException(parser.getAttributeValue(null, ATTRIBUTE_TYPE));
203             } else if (tagname.equals(TAG_FIELD)) {
204                 JDiffField field = loadFieldInfo(currentClass.getClassName(), parser);
205                 currentClass.addField(field);
206             } else {
207                 throw new RuntimeException(
208                         "unknown tag exception:" + tagname);
209             }
210             if (currentPackage != null) {
211                 logd(String.format("currentPackage: %s", currentPackage));
212             }
213             if (currentClass != null) {
214                 logd(String.format("currentClass: %s", currentClass.toSignatureString()));
215             }
216             if (currentMethod != null) {
217                 logd(String.format("currentMethod: %s", currentMethod.toSignatureString()));
218             }
219         }
220     }
221 
222     /**
223      * Load field information from xml to memory.
224      *
225      * @param className of the class being examined which will be shown in error messages
226      * @param parser The XmlPullParser which carries the xml information.
227      * @return the new field
228      */
loadFieldInfo(String className, XmlPullParser parser)229     private JDiffField loadFieldInfo(String className, XmlPullParser parser) {
230         String fieldName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
231         String fieldType = parser.getAttributeValue(null, ATTRIBUTE_TYPE);
232         int modifier = jdiffModifierToReflectionFormat(className, parser);
233         return new JDiffField(fieldName, fieldType, modifier);
234     }
235 
236     /**
237      * Load method information from xml to memory.
238      *
239      * @param className of the class being examined which will be shown in error messages
240      * @param parser The XmlPullParser which carries the xml information.
241      * @return the newly loaded method.
242      */
loadMethodInfo(String className, XmlPullParser parser)243     private JDiffMethod loadMethodInfo(String className, XmlPullParser parser) {
244         String methodName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
245         String returnType = parser.getAttributeValue(null, ATTRIBUTE_RETURN);
246         int modifier = jdiffModifierToReflectionFormat(className, parser);
247         return new JDiffMethod(methodName, modifier, returnType);
248     }
249 
250     /**
251      * Load constructor information from xml to memory.
252      *
253      * @param parser The XmlPullParser which carries the xml information.
254      * @param currentClass the current class being loaded.
255      * @return the new constructor
256      */
loadConstructorInfo(XmlPullParser parser, JDiffClassDescription currentClass)257     private JDiffConstructor loadConstructorInfo(XmlPullParser parser,
258                                                  JDiffClassDescription currentClass) {
259         String name = currentClass.getClassName();
260         int modifier = jdiffModifierToReflectionFormat(name, parser);
261         return new JDiffConstructor(name, modifier);
262     }
263 
264     /**
265      * Load class or interface information to memory.
266      *
267      * @param parser The XmlPullParser which carries the xml information.
268      * @param isInterface true if the current class is an interface, otherwise is false.
269      * @param pkg the name of the java package this class can be found in.
270      * @return the new class description.
271      */
loadClassInfo(XmlPullParser parser, boolean isInterface, String pkg)272     private JDiffClassDescription loadClassInfo(XmlPullParser parser,
273                                                 boolean isInterface,
274                                                 String pkg) {
275         String className = parser.getAttributeValue(null, ATTRIBUTE_NAME);
276         JDiffClassDescription currentClass = new JDiffClassDescription(pkg,
277                                                                        className,
278                                                                        mResultObserver);
279         currentClass.setModifier(jdiffModifierToReflectionFormat(className, parser));
280         currentClass.setType(isInterface ? JDiffClassDescription.JDiffType.INTERFACE :
281                              JDiffClassDescription.JDiffType.CLASS);
282         currentClass.setExtendsClass(parser.getAttributeValue(null, ATTRIBUTE_EXTENDS));
283         return currentClass;
284     }
285 
286     /**
287      * Convert string modifier to int modifier.
288      *
289      * @param name of the class/method/field being examined which will be shown in error messages
290      * @param key modifier name
291      * @param value modifier value
292      * @return converted modifier value
293      */
modifierDescriptionToReflectedType(String name, String key, String value)294     private static int modifierDescriptionToReflectedType(String name, String key, String value) {
295         if (key.equals(MODIFIER_ABSTRACT)) {
296             return value.equals("true") ? Modifier.ABSTRACT : 0;
297         } else if (key.equals(MODIFIER_FINAL)) {
298             return value.equals("true") ? Modifier.FINAL : 0;
299         } else if (key.equals(MODIFIER_NATIVE)) {
300             return value.equals("true") ? Modifier.NATIVE : 0;
301         } else if (key.equals(MODIFIER_STATIC)) {
302             return value.equals("true") ? Modifier.STATIC : 0;
303         } else if (key.equals(MODIFIER_SYNCHRONIZED)) {
304             return value.equals("true") ? Modifier.SYNCHRONIZED : 0;
305         } else if (key.equals(MODIFIER_TRANSIENT)) {
306             return value.equals("true") ? Modifier.TRANSIENT : 0;
307         } else if (key.equals(MODIFIER_VOLATILE)) {
308             return value.equals("true") ? Modifier.VOLATILE : 0;
309         } else if (key.equals(MODIFIER_VISIBILITY)) {
310             if (value.equals(MODIFIER_PRIVATE)) {
311                 throw new RuntimeException("Private visibility found in API spec: " + name);
312             } else if (value.equals(MODIFIER_PROTECTED)) {
313                 return Modifier.PROTECTED;
314             } else if (value.equals(MODIFIER_PUBLIC)) {
315                 return Modifier.PUBLIC;
316             } else if ("".equals(value)) {
317                 // If the visibility is "", it means it has no modifier.
318                 // which is package private. We should return 0 for this modifier.
319                 return 0;
320             } else {
321                 throw new RuntimeException("Unknown modifier found in API spec: " + value);
322             }
323         }
324         return 0;
325     }
326 
327     /**
328      * Transfer string modifier to int one.
329      *
330      * @param name of the class/method/field being examined which will be shown in error messages
331      * @param parser XML resource parser
332      * @return converted modifier
333      */
jdiffModifierToReflectionFormat(String name, XmlPullParser parser)334     private static int jdiffModifierToReflectionFormat(String name, XmlPullParser parser){
335         int modifier = 0;
336         for (int i = 0;i < parser.getAttributeCount();i++) {
337             modifier |= modifierDescriptionToReflectedType(name, parser.getAttributeName(i),
338                     parser.getAttributeValue(i));
339         }
340         return modifier;
341     }
342 
loge(String msg, Exception e)343     public static void loge(String msg, Exception e) {
344         Log.e(TAG, msg, e);
345     }
346 
logd(String msg)347     public static void logd(String msg) {
348         Log.d(TAG, msg);
349     }
350 }
351