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
17package android.tests.sigtest;
18
19import android.content.res.Resources;
20import android.test.AndroidTestCase;
21import android.tests.sigtest.JDiffClassDescription.JDiffConstructor;
22import android.tests.sigtest.JDiffClassDescription.JDiffField;
23import android.tests.sigtest.JDiffClassDescription.JDiffMethod;
24import android.tests.sigtest.SignatureTestActivity.FAILURE_TYPE;
25
26import org.xmlpull.v1.XmlPullParser;
27import org.xmlpull.v1.XmlPullParserException;
28
29import java.io.IOException;
30import java.lang.reflect.Field;
31import java.lang.reflect.Modifier;
32import java.util.ArrayList;
33import java.util.Arrays;
34import java.util.HashSet;
35
36/**
37 * A simpler version of {@link SignatureTest} that performs the signature check via a JUnit test.
38 * <p/>
39 * Eventually the existing  {@link SignatureTest} and {@link SignatureActivity} will be deleted
40 * once the move to a tradefederation based CTS harness is complete.
41 */
42public class SimpleSignatureTest extends AndroidTestCase {
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 static ArrayList<String> mDebugArray = new ArrayList<String>();
73
74    private HashSet<String> mKeyTagSet;
75    private TestResultObserver mResultObserver;
76
77    private class TestResultObserver implements ResultObserver {
78        boolean mDidFail = false;
79        StringBuilder mErrorString = new StringBuilder();
80
81        public void notifyFailure(FAILURE_TYPE type, String name, String errorMessage) {
82            mDidFail = true;
83            mErrorString.append("\n");
84            mErrorString.append(type.toString().toLowerCase());
85            mErrorString.append(":\t");
86            mErrorString.append(name);
87        }
88    }
89
90    @Override
91    protected void setUp() throws Exception {
92        super.setUp();
93        mKeyTagSet = new HashSet<String>();
94        mKeyTagSet.addAll(Arrays.asList(new String[] {
95                TAG_PACKAGE, TAG_CLASS, TAG_INTERFACE, TAG_IMPLEMENTS, TAG_CONSTRUCTOR,
96                TAG_METHOD, TAG_PARAM, TAG_EXCEPTION, TAG_FIELD }));
97        mResultObserver = new TestResultObserver();
98    }
99
100    /**
101     * Tests that the device's API matches the expected set defined in xml.
102     * <p/>
103     * Will check the entire API, and then report the complete list of failures
104     */
105    public void testSignature() {
106        Resources r = getContext().getResources();
107        Class rClass = R.xml.class;
108        Field[] fs = rClass.getFields();
109        for (Field f : fs) {
110            try {
111                start(r.getXml(f.getInt(rClass)));
112            } catch (Exception e) {
113                mResultObserver.notifyFailure(FAILURE_TYPE.CAUGHT_EXCEPTION, e.getMessage(),
114                        e.getMessage());
115            }
116        }
117        if (mResultObserver.mDidFail) {
118            fail(mResultObserver.mErrorString.toString());
119        }
120    }
121
122    private  void beginDocument(XmlPullParser parser, String firstElementName)
123            throws XmlPullParserException, IOException {
124        int type;
125        while ((type=parser.next()) != XmlPullParser.START_TAG
126                   && type != XmlPullParser.END_DOCUMENT) { }
127
128        if (type != XmlPullParser.START_TAG) {
129            throw new XmlPullParserException("No start tag found");
130        }
131
132        if (!parser.getName().equals(firstElementName)) {
133            throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() +
134                    ", expected " + firstElementName);
135        }
136    }
137
138    /**
139     * Signature test entry point.
140     */
141    private void start(XmlPullParser parser) throws XmlPullParserException, IOException {
142        JDiffClassDescription currentClass = null;
143        String currentPackage = "";
144        JDiffMethod currentMethod = null;
145
146        beginDocument(parser, TAG_ROOT);
147        int type;
148        while (true) {
149            type = XmlPullParser.START_DOCUMENT;
150            while ((type=parser.next()) != XmlPullParser.START_TAG
151                       && type != XmlPullParser.END_DOCUMENT
152                       && type != XmlPullParser.END_TAG) {
153
154            }
155
156            if (type == XmlPullParser.END_TAG) {
157                if (TAG_CLASS.equals(parser.getName())
158                        || TAG_INTERFACE.equals(parser.getName())) {
159                    currentClass.checkSignatureCompliance();
160                } else if (TAG_PACKAGE.equals(parser.getName())) {
161                    currentPackage = "";
162                }
163                continue;
164            }
165
166            if (type == XmlPullParser.END_DOCUMENT) {
167                break;
168            }
169
170            String tagname = parser.getName();
171            if (!mKeyTagSet.contains(tagname)) {
172                continue;
173            }
174
175            if (type == XmlPullParser.START_TAG && tagname.equals(TAG_PACKAGE)) {
176                currentPackage = parser.getAttributeValue(null, ATTRIBUTE_NAME);
177            } else if (tagname.equals(TAG_CLASS)) {
178                currentClass = loadClassInfo(parser, false, currentPackage);
179            } else if (tagname.equals(TAG_INTERFACE)) {
180                currentClass = loadClassInfo(parser, true, currentPackage);
181            } else if (tagname.equals(TAG_IMPLEMENTS)) {
182                currentClass.addImplInterface(parser.getAttributeValue(null, ATTRIBUTE_NAME));
183            } else if (tagname.equals(TAG_CONSTRUCTOR)) {
184                JDiffConstructor constructor = loadConstructorInfo(parser, currentClass);
185                currentClass.addConstructor(constructor);
186                currentMethod = constructor;
187            } else if (tagname.equals(TAG_METHOD)) {
188                currentMethod = loadMethodInfo(currentClass.getClassName(), parser);
189                currentClass.addMethod(currentMethod);
190            } else if (tagname.equals(TAG_PARAM)) {
191                currentMethod.addParam(parser.getAttributeValue(null, ATTRIBUTE_TYPE));
192            } else if (tagname.equals(TAG_EXCEPTION)) {
193                currentMethod.addException(parser.getAttributeValue(null, ATTRIBUTE_TYPE));
194            } else if (tagname.equals(TAG_FIELD)) {
195                JDiffField field = loadFieldInfo(currentClass.getClassName(), parser);
196                currentClass.addField(field);
197            } else {
198                throw new RuntimeException(
199                        "unknow tag exception:" + tagname);
200            }
201        }
202    }
203
204    /**
205     * Load field information from xml to memory.
206     *
207     * @param className of the class being examined which will be shown in error messages
208     * @param parser The XmlPullParser which carries the xml information.
209     * @return the new field
210     */
211    private JDiffField loadFieldInfo(String className, XmlPullParser parser) {
212        String fieldName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
213        String fieldType = parser.getAttributeValue(null, ATTRIBUTE_TYPE);
214        int modifier = jdiffModifierToReflectionFormat(className, parser);
215        return new JDiffField(fieldName, fieldType, modifier);
216    }
217
218    /**
219     * Load method information from xml to memory.
220     *
221     * @param className of the class being examined which will be shown in error messages
222     * @param parser The XmlPullParser which carries the xml information.
223     * @return the newly loaded method.
224     */
225    private JDiffMethod loadMethodInfo(String className, XmlPullParser parser) {
226        String methodName = parser.getAttributeValue(null, ATTRIBUTE_NAME);
227        String returnType = parser.getAttributeValue(null, ATTRIBUTE_RETURN);
228        int modifier = jdiffModifierToReflectionFormat(className, parser);
229        return new JDiffMethod(methodName, modifier, returnType);
230    }
231
232    /**
233     * Load constructor information from xml to memory.
234     *
235     * @param parser The XmlPullParser which carries the xml information.
236     * @param currentClass the current class being loaded.
237     * @return the new constructor
238     */
239    private JDiffConstructor loadConstructorInfo(XmlPullParser parser,
240                                                 JDiffClassDescription currentClass) {
241        String name = currentClass.getClassName();
242        int modifier = jdiffModifierToReflectionFormat(name, parser);
243        return new JDiffConstructor(name, modifier);
244    }
245
246    /**
247     * Load class or interface information to memory.
248     *
249     * @param parser The XmlPullParser which carries the xml information.
250     * @param isInterface true if the current class is an interface, otherwise is false.
251     * @param pkg the name of the java package this class can be found in.
252     * @return the new class description.
253     */
254    private JDiffClassDescription loadClassInfo(XmlPullParser parser,
255                                                boolean isInterface,
256                                                String pkg) {
257        String className = parser.getAttributeValue(null, ATTRIBUTE_NAME);
258        JDiffClassDescription currentClass = new JDiffClassDescription(pkg,
259                                                                       className,
260                                                                       mResultObserver);
261        currentClass.setModifier(jdiffModifierToReflectionFormat(className, parser));
262        currentClass.setType(isInterface ? JDiffClassDescription.JDiffType.INTERFACE :
263                             JDiffClassDescription.JDiffType.CLASS);
264        currentClass.setExtendsClass(parser.getAttributeValue(null, ATTRIBUTE_EXTENDS));
265        return currentClass;
266    }
267
268    /**
269     * Convert string modifier to int modifier.
270     *
271     * @param name of the class/method/field being examined which will be shown in error messages
272     * @param key modifier name
273     * @param value modifier value
274     * @return converted modifier value
275     */
276    private static int modifierDescriptionToReflectedType(String name, String key, String value) {
277        if (key.equals(MODIFIER_ABSTRACT)) {
278            return value.equals("true") ? Modifier.ABSTRACT : 0;
279        } else if (key.equals(MODIFIER_FINAL)) {
280            return value.equals("true") ? Modifier.FINAL : 0;
281        } else if (key.equals(MODIFIER_NATIVE)) {
282            return value.equals("true") ? Modifier.NATIVE : 0;
283        } else if (key.equals(MODIFIER_STATIC)) {
284            return value.equals("true") ? Modifier.STATIC : 0;
285        } else if (key.equals(MODIFIER_SYNCHRONIZED)) {
286            return value.equals("true") ? Modifier.SYNCHRONIZED : 0;
287        } else if (key.equals(MODIFIER_TRANSIENT)) {
288            return value.equals("true") ? Modifier.TRANSIENT : 0;
289        } else if (key.equals(MODIFIER_VOLATILE)) {
290            return value.equals("true") ? Modifier.VOLATILE : 0;
291        } else if (key.equals(MODIFIER_VISIBILITY)) {
292            if (value.equals(MODIFIER_PRIVATE)) {
293                throw new RuntimeException("Private visibility found in API spec: " + name);
294            } else if (value.equals(MODIFIER_PROTECTED)) {
295                return Modifier.PROTECTED;
296            } else if (value.equals(MODIFIER_PUBLIC)) {
297                return Modifier.PUBLIC;
298            } else if ("".equals(value)) {
299                // If the visibility is "", it means it has no modifier.
300                // which is package private. We should return 0 for this modifier.
301                return 0;
302            } else {
303                throw new RuntimeException("Unknown modifier found in API spec: " + value);
304            }
305        }
306        return 0;
307    }
308
309    /**
310     * Transfer string modifier to int one.
311     *
312     * @param name of the class/method/field being examined which will be shown in error messages
313     * @param parser XML resource parser
314     * @return converted modifier
315     */
316    private static int jdiffModifierToReflectionFormat(String name, XmlPullParser parser){
317        int modifier = 0;
318        for (int i = 0;i < parser.getAttributeCount();i++) {
319            modifier |= modifierDescriptionToReflectedType(name, parser.getAttributeName(i),
320                    parser.getAttributeValue(i));
321        }
322        return modifier;
323    }
324}
325