• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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 import java.io.BufferedWriter;
17 import java.io.File;
18 import java.io.FileNotFoundException;
19 import java.io.FileOutputStream;
20 import java.io.FileWriter;
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Iterator;
25 import java.util.Set;
26 
27 import javax.xml.parsers.DocumentBuilderFactory;
28 import javax.xml.parsers.ParserConfigurationException;
29 import javax.xml.transform.Transformer;
30 import javax.xml.transform.TransformerException;
31 import javax.xml.transform.TransformerFactory;
32 import javax.xml.transform.TransformerFactoryConfigurationError;
33 import javax.xml.transform.dom.DOMSource;
34 import javax.xml.transform.stream.StreamResult;
35 
36 import org.w3c.dom.Attr;
37 import org.w3c.dom.Document;
38 import org.w3c.dom.Node;
39 import org.w3c.dom.NodeList;
40 
41 import vogar.ExpectationStore;
42 import vogar.Expectation;
43 
44 import com.sun.javadoc.AnnotationDesc;
45 import com.sun.javadoc.AnnotationTypeDoc;
46 import com.sun.javadoc.AnnotationValue;
47 import com.sun.javadoc.ClassDoc;
48 import com.sun.javadoc.Doclet;
49 import com.sun.javadoc.MethodDoc;
50 import com.sun.javadoc.RootDoc;
51 import com.sun.javadoc.AnnotationDesc.ElementValuePair;
52 
53 /**
54  * This is only a very simple and brief JavaDoc parser for the CTS.
55  *
56  * Input: The source files of the test cases. It will be represented
57  *          as a list of ClassDoc
58  * Output: Generate file description.xml, which defines the TestPackage
59  *          TestSuite and TestCases.
60  *
61  * Note:
62  *  1. Since this class has dependencies on com.sun.javadoc package which
63  *       is not implemented on Android. So this class can't be compiled.
64  *  2. The TestSuite can be embedded, which means:
65  *      TestPackage := TestSuite*
66  *      TestSuite := TestSuite* | TestCase*
67  */
68 public class DescriptionGenerator extends Doclet {
69     static final String HOST_CONTROLLER = "dalvik.annotation.HostController";
70     static final String KNOWN_FAILURE = "dalvik.annotation.KnownFailure";
71     static final String SUPPRESSED_TEST = "android.test.suitebuilder.annotation.Suppress";
72     static final String CTS_EXPECTATION_DIR = "cts/tests/expectations";
73 
74     static final String JUNIT_TEST_CASE_CLASS_NAME = "junit.framework.testcase";
75     static final String TAG_PACKAGE = "TestPackage";
76     static final String TAG_SUITE = "TestSuite";
77     static final String TAG_CASE = "TestCase";
78     static final String TAG_TEST = "Test";
79     static final String TAG_DESCRIPTION = "Description";
80 
81     static final String ATTRIBUTE_NAME_VERSION = "version";
82     static final String ATTRIBUTE_VALUE_VERSION = "1.0";
83     static final String ATTRIBUTE_NAME_FRAMEWORK = "AndroidFramework";
84     static final String ATTRIBUTE_VALUE_FRAMEWORK = "Android 1.0";
85 
86     static final String ATTRIBUTE_NAME = "name";
87     static final String ATTRIBUTE_ABIS = "abis";
88     static final String ATTRIBUTE_HOST_CONTROLLER = "HostController";
89 
90     static final String XML_OUTPUT_PATH = "./description.xml";
91 
92     static final String OUTPUT_PATH_OPTION = "-o";
93     static final String ARCHITECTURE_OPTION = "-a";
94 
95     /**
96      * Start to parse the classes passed in by javadoc, and generate
97      * the xml file needed by CTS packer.
98      *
99      * @param root The root document passed in by javadoc.
100      * @return Whether the document has been processed.
101      */
start(RootDoc root)102     public static boolean start(RootDoc root) {
103         ClassDoc[] classes = root.classes();
104         if (classes == null) {
105             Log.e("No class found!", null);
106             return true;
107         }
108 
109         String outputPath = XML_OUTPUT_PATH;
110         String architecture = null;
111         String[][] options = root.options();
112         for (String[] option : options) {
113             if (option.length == 2) {
114                 if (option[0].equals(OUTPUT_PATH_OPTION)) {
115                     outputPath = option[1];
116                 } else if (option[0].equals(ARCHITECTURE_OPTION)) {
117                     architecture = option[1];
118                 }
119             }
120         }
121         if (architecture == null || architecture.equals("")) {
122             Log.e("Missing architecture!", null);
123             return false;
124         }
125 
126         XMLGenerator xmlGenerator = null;
127         try {
128             xmlGenerator = new XMLGenerator(outputPath);
129         } catch (ParserConfigurationException e) {
130             Log.e("Cant initialize XML Generator!", e);
131             return true;
132         }
133 
134         ExpectationStore ctsExpectationStore = null;
135         try {
136             ctsExpectationStore = VogarUtils.provideExpectationStore("./" + CTS_EXPECTATION_DIR);
137         } catch (IOException e) {
138             Log.e("Couldn't load expectation store.", e);
139             return false;
140         }
141 
142         for (ClassDoc clazz : classes) {
143             if ((!clazz.isAbstract()) && (isValidJUnitTestCase(clazz))) {
144                 xmlGenerator.addTestClass(new TestClass(clazz, ctsExpectationStore, architecture));
145             }
146         }
147 
148         try {
149             xmlGenerator.dump();
150         } catch (Exception e) {
151             Log.e("Can't dump to XML file!", e);
152         }
153 
154         return true;
155     }
156 
157     /**
158      * Return the length of any doclet options we recognize
159      * @param option The option name
160      * @return The number of words this option takes (including the option) or 0 if the option
161      * is not recognized.
162      */
optionLength(String option)163     public static int optionLength(String option) {
164         if (option.equals(OUTPUT_PATH_OPTION)) {
165             return 2;
166         }
167         return 0;
168     }
169 
170     /**
171      * Check if the class is valid test case inherited from JUnit TestCase.
172      *
173      * @param clazz The class to be checked.
174      * @return If the class is valid test case inherited from JUnit TestCase, return true;
175      *         else, return false.
176      */
isValidJUnitTestCase(ClassDoc clazz)177     static boolean isValidJUnitTestCase(ClassDoc clazz) {
178         while((clazz = clazz.superclass()) != null) {
179             if (JUNIT_TEST_CASE_CLASS_NAME.equals(clazz.qualifiedName().toLowerCase())) {
180                 return true;
181             }
182         }
183 
184         return false;
185     }
186 
187     /**
188      * Log utility.
189      */
190     static class Log {
191         private static boolean TRACE = true;
192         private static BufferedWriter mTraceOutput = null;
193 
194         /**
195          * Log the specified message.
196          *
197          * @param msg The message to be logged.
198          */
e(String msg, Exception e)199         static void e(String msg, Exception e) {
200             System.out.println(msg);
201 
202             if (e != null) {
203                 e.printStackTrace();
204             }
205         }
206 
207         /**
208          * Add the message to the trace stream.
209          *
210          * @param msg The message to be added to the trace stream.
211          */
t(String msg)212         public static void t(String msg) {
213             if (TRACE) {
214                 try {
215                     if ((mTraceOutput != null) && (msg != null)) {
216                         mTraceOutput.write(msg + "\n");
217                         mTraceOutput.flush();
218                     }
219                 } catch (IOException e) {
220                     e.printStackTrace();
221                 }
222             }
223         }
224 
225         /**
226          * Initialize the trace stream.
227          *
228          * @param name The class name.
229          */
initTrace(String name)230         public static void initTrace(String name) {
231             if (TRACE) {
232                 try {
233                     if (mTraceOutput == null) {
234                         String fileName = "cts_debug_dg_" + name + ".txt";
235                         mTraceOutput = new BufferedWriter(new FileWriter(fileName));
236                     }
237                 } catch (IOException e) {
238                     e.printStackTrace();
239                 }
240             }
241         }
242 
243         /**
244          * Close the trace stream.
245          */
closeTrace()246         public static void closeTrace() {
247             if (mTraceOutput != null) {
248                 try {
249                     mTraceOutput.close();
250                     mTraceOutput = null;
251                 } catch (IOException e) {
252                     e.printStackTrace();
253                 }
254             }
255         }
256     }
257 
258     static class XMLGenerator {
259         String mOutputPath;
260 
261         /**
262          * This document is used to represent the description XML file.
263          * It is construct by the classes passed in, which contains the
264          * information of all the test package, test suite and test cases.
265          */
266         Document mDoc;
267 
XMLGenerator(String outputPath)268         XMLGenerator(String outputPath) throws ParserConfigurationException {
269             mOutputPath = outputPath;
270 
271             mDoc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
272 
273             Node testPackageElem = mDoc.appendChild(mDoc.createElement(TAG_PACKAGE));
274 
275             setAttribute(testPackageElem, ATTRIBUTE_NAME_VERSION, ATTRIBUTE_VALUE_VERSION);
276             setAttribute(testPackageElem, ATTRIBUTE_NAME_FRAMEWORK, ATTRIBUTE_VALUE_FRAMEWORK);
277         }
278 
addTestClass(TestClass tc)279         void addTestClass(TestClass tc) {
280             appendSuiteToElement(mDoc.getDocumentElement(), tc);
281         }
282 
dump()283         void dump() throws TransformerFactoryConfigurationError,
284                 FileNotFoundException, TransformerException {
285             //rebuildDocument();
286 
287             Transformer t = TransformerFactory.newInstance().newTransformer();
288 
289             // enable indent in result file
290             t.setOutputProperty("indent", "yes");
291             t.setOutputProperty("{http://xml.apache.org/xslt}indent-amount","4");
292 
293             File file = new File(mOutputPath);
294             file.getParentFile().mkdirs();
295 
296             t.transform(new DOMSource(mDoc),
297                     new StreamResult(new FileOutputStream(file)));
298         }
299 
300         /**
301          * Rebuild the document, merging empty suite nodes.
302          */
rebuildDocument()303         void rebuildDocument() {
304             // merge empty suite nodes
305             Collection<Node> suiteElems = getUnmutableChildNodes(mDoc.getDocumentElement());
306             Iterator<Node> suiteIterator = suiteElems.iterator();
307             while (suiteIterator.hasNext()) {
308                 Node suiteElem = suiteIterator.next();
309 
310                 mergeEmptySuites(suiteElem);
311             }
312         }
313 
314         /**
315          * Merge the test suite which only has one sub-suite. In this case, unify
316          * the name of the two test suites.
317          *
318          * @param suiteElem The suite element of which to be merged.
319          */
mergeEmptySuites(Node suiteElem)320         void mergeEmptySuites(Node suiteElem) {
321             Collection<Node> suiteChildren = getSuiteChildren(suiteElem);
322             if (suiteChildren.size() > 1) {
323                 for (Node suiteChild : suiteChildren) {
324                     mergeEmptySuites(suiteChild);
325                 }
326             } else if (suiteChildren.size() == 1) {
327                 // do merge
328                 Node child = suiteChildren.iterator().next();
329 
330                 // update name
331                 String newName = getAttribute(suiteElem, ATTRIBUTE_NAME) + "."
332                         + getAttribute(child, ATTRIBUTE_NAME);
333                 setAttribute(child, ATTRIBUTE_NAME, newName);
334 
335                 // update parent node
336                 Node parentNode = suiteElem.getParentNode();
337                 parentNode.removeChild(suiteElem);
338                 parentNode.appendChild(child);
339 
340                 mergeEmptySuites(child);
341             }
342         }
343 
344         /**
345          * Get the unmuatable child nodes for specified node.
346          *
347          * @param node The specified node.
348          * @return A collection of copied child node.
349          */
getUnmutableChildNodes(Node node)350         private Collection<Node> getUnmutableChildNodes(Node node) {
351             ArrayList<Node> nodes = new ArrayList<Node>();
352             NodeList nodelist = node.getChildNodes();
353 
354             for (int i = 0; i < nodelist.getLength(); i++) {
355                 nodes.add(nodelist.item(i));
356             }
357 
358             return nodes;
359         }
360 
361         /**
362          * Append a named test suite to a specified element. Including match with
363          * the existing suite nodes and do the real creation and append.
364          *
365          * @param elem The specified element.
366          * @param testSuite The test suite to be appended.
367          */
appendSuiteToElement(Node elem, TestClass testSuite)368         void appendSuiteToElement(Node elem, TestClass testSuite) {
369             String suiteName = testSuite.mName;
370             Collection<Node> children = getSuiteChildren(elem);
371             int dotIndex = suiteName.indexOf('.');
372             String name = dotIndex == -1 ? suiteName : suiteName.substring(0, dotIndex);
373 
374             boolean foundMatch = false;
375             for (Node child : children) {
376                 String childName = child.getAttributes().getNamedItem(ATTRIBUTE_NAME)
377                         .getNodeValue();
378 
379                 if (childName.equals(name)) {
380                     foundMatch = true;
381                     if (dotIndex == -1) {
382                         appendTestCases(child, testSuite.mCases);
383                     } else {
384                         testSuite.mName = suiteName.substring(dotIndex + 1, suiteName.length());
385                         appendSuiteToElement(child, testSuite);
386                     }
387                 }
388 
389             }
390 
391             if (!foundMatch) {
392                 appendSuiteToElementImpl(elem, testSuite);
393             }
394         }
395 
396         /**
397          * Get the test suite child nodes of a specified element.
398          *
399          * @param elem The specified element node.
400          * @return The matched child nodes.
401          */
getSuiteChildren(Node elem)402         Collection<Node> getSuiteChildren(Node elem) {
403             ArrayList<Node> suites = new ArrayList<Node>();
404 
405             NodeList children = elem.getChildNodes();
406             for (int i = 0; i < children.getLength(); i++) {
407                 Node child = children.item(i);
408 
409                 if (child.getNodeName().equals(DescriptionGenerator.TAG_SUITE)) {
410                     suites.add(child);
411                 }
412             }
413 
414             return suites;
415         }
416 
417         /**
418          * Create test case node according to the given method names, and append them
419          * to the test suite element.
420          *
421          * @param elem The test suite element.
422          * @param cases A collection of test cases included by the test suite class.
423          */
appendTestCases(Node elem, Collection<TestMethod> cases)424         void appendTestCases(Node elem, Collection<TestMethod> cases) {
425             if (cases.isEmpty()) {
426                 // if no method, remove from parent
427                 elem.getParentNode().removeChild(elem);
428             } else {
429                 for (TestMethod caze : cases) {
430                     if (caze.mIsBroken || caze.mIsSuppressed || caze.mKnownFailure != null) {
431                         continue;
432                     }
433                     Node caseNode = elem.appendChild(mDoc.createElement(TAG_TEST));
434 
435                     setAttribute(caseNode, ATTRIBUTE_NAME, caze.mName);
436                     String abis = caze.mAbis.toString();
437                     setAttribute(caseNode, ATTRIBUTE_ABIS, abis.substring(1, abis.length() - 1));
438                     if ((caze.mController != null) && (caze.mController.length() != 0)) {
439                         setAttribute(caseNode, ATTRIBUTE_HOST_CONTROLLER, caze.mController);
440                     }
441 
442                     if (caze.mDescription != null && !caze.mDescription.equals("")) {
443                         caseNode.appendChild(mDoc.createElement(TAG_DESCRIPTION))
444                                 .setTextContent(caze.mDescription);
445                     }
446                 }
447             }
448         }
449 
450         /**
451          * Set the attribute of element.
452          *
453          * @param elem The element to be set attribute.
454          * @param name The attribute name.
455          * @param value The attribute value.
456          */
setAttribute(Node elem, String name, String value)457         protected void setAttribute(Node elem, String name, String value) {
458             Attr attr = mDoc.createAttribute(name);
459             attr.setNodeValue(value);
460 
461             elem.getAttributes().setNamedItem(attr);
462         }
463 
464         /**
465          * Get the value of a specified attribute of an element.
466          *
467          * @param elem The element node.
468          * @param name The attribute name.
469          * @return The value of the specified attribute.
470          */
getAttribute(Node elem, String name)471         private String getAttribute(Node elem, String name) {
472             return elem.getAttributes().getNamedItem(name).getNodeValue();
473         }
474 
475         /**
476          * Do the append, including creating test suite nodes and test case nodes, and
477          * append them to the element.
478          *
479          * @param elem The specified element node.
480          * @param testSuite The test suite to be append.
481          */
appendSuiteToElementImpl(Node elem, TestClass testSuite)482         void appendSuiteToElementImpl(Node elem, TestClass testSuite) {
483             Node parent = elem;
484             String suiteName = testSuite.mName;
485 
486             int dotIndex;
487             while ((dotIndex = suiteName.indexOf('.')) != -1) {
488                 String name = suiteName.substring(0, dotIndex);
489 
490                 Node suiteElem = parent.appendChild(mDoc.createElement(TAG_SUITE));
491                 setAttribute(suiteElem, ATTRIBUTE_NAME, name);
492 
493                 parent = suiteElem;
494                 suiteName = suiteName.substring(dotIndex + 1, suiteName.length());
495             }
496 
497             Node leafSuiteElem = parent.appendChild(mDoc.createElement(TAG_CASE));
498             setAttribute(leafSuiteElem, ATTRIBUTE_NAME, suiteName);
499 
500             appendTestCases(leafSuiteElem, testSuite.mCases);
501         }
502     }
503 
504     /**
505      * Represent the test class.
506      */
507     static class TestClass {
508         String mName;
509         Collection<TestMethod> mCases;
510 
511         /**
512          * Construct an test suite object.
513          *
514          * @param name Full name of the test suite, such as "com.google.android.Foo"
515          * @param cases The test cases included in this test suite.
516          */
TestClass(String name, Collection<TestMethod> cases)517         TestClass(String name, Collection<TestMethod> cases) {
518             mName = name;
519             mCases = cases;
520         }
521 
522         /**
523          * Construct a TestClass object using ClassDoc.
524          *
525          * @param clazz The specified ClassDoc.
526          */
TestClass(ClassDoc clazz, ExpectationStore expectationStore, String architecture)527         TestClass(ClassDoc clazz, ExpectationStore expectationStore, String architecture) {
528             mName = clazz.toString();
529             mCases = getTestMethods(expectationStore, architecture, clazz);
530         }
531 
532         /**
533          * Get all the TestMethod from a ClassDoc, including inherited methods.
534          *
535          * @param clazz The specified ClassDoc.
536          * @return A collection of TestMethod.
537          */
getTestMethods(ExpectationStore expectationStore, String architecture, ClassDoc clazz)538         Collection<TestMethod> getTestMethods(ExpectationStore expectationStore,
539                 String architecture, ClassDoc clazz) {
540             Collection<MethodDoc> methods = getAllMethods(clazz);
541 
542             ArrayList<TestMethod> cases = new ArrayList<TestMethod>();
543             Iterator<MethodDoc> iterator = methods.iterator();
544 
545             while (iterator.hasNext()) {
546                 MethodDoc method = iterator.next();
547 
548                 String name = method.name();
549 
550                 AnnotationDesc[] annotations = method.annotations();
551                 String controller = "";
552                 String knownFailure = null;
553                 boolean isBroken = false;
554                 boolean isSuppressed = false;
555                 for (AnnotationDesc cAnnot : annotations) {
556 
557                     AnnotationTypeDoc atype = cAnnot.annotationType();
558                     if (atype.toString().equals(HOST_CONTROLLER)) {
559                         controller = getAnnotationDescription(cAnnot);
560                     } else if (atype.toString().equals(KNOWN_FAILURE)) {
561                         knownFailure = getAnnotationDescription(cAnnot);
562                     } else if (atype.toString().equals(SUPPRESSED_TEST)) {
563                         isSuppressed = true;
564                     }
565                 }
566 
567                 if (VogarUtils.isVogarKnownFailure(expectationStore, clazz.toString(), name)) {
568                     isBroken = true;
569                 }
570 
571                 if (name.startsWith("test")) {
572                     Expectation expectation = expectationStore.get(
573                             VogarUtils.buildFullTestName(clazz.toString(), name));
574                     Set<String> supportedAbis =
575                             VogarUtils.extractSupportedAbis(architecture, expectation);
576                     cases.add(new TestMethod(
577                             name, method.commentText(), controller, supportedAbis,
578                                     knownFailure, isBroken, isSuppressed));
579                 }
580             }
581 
582             return cases;
583         }
584 
585         /**
586          * Get annotation description.
587          *
588          * @param cAnnot The annotation.
589          */
getAnnotationDescription(AnnotationDesc cAnnot)590         String getAnnotationDescription(AnnotationDesc cAnnot) {
591             ElementValuePair[] cpairs = cAnnot.elementValues();
592             ElementValuePair evp = cpairs[0];
593             AnnotationValue av = evp.value();
594             String description = av.toString();
595             // FIXME: need to find out the reason why there are leading and trailing "
596             description = description.substring(1, description.length() -1);
597             return description;
598         }
599 
600         /**
601          * Get all MethodDoc of a ClassDoc, including inherited methods.
602          *
603          * @param clazz The specified ClassDoc.
604          * @return A collection of MethodDoc.
605          */
getAllMethods(ClassDoc clazz)606         Collection<MethodDoc> getAllMethods(ClassDoc clazz) {
607             ArrayList<MethodDoc> methods = new ArrayList<MethodDoc>();
608 
609             for (MethodDoc method : clazz.methods()) {
610                 methods.add(method);
611             }
612 
613             ClassDoc superClass = clazz.superclass();
614             while (superClass != null) {
615                 for (MethodDoc method : superClass.methods()) {
616                     methods.add(method);
617                 }
618 
619                 superClass = superClass.superclass();
620             }
621 
622             return methods;
623         }
624 
625     }
626 
627     /**
628      * Represent the test method inside the test class.
629      */
630     static class TestMethod {
631         String mName;
632         String mDescription;
633         String mController;
634         Set<String> mAbis;
635         String mKnownFailure;
636         boolean mIsBroken;
637         boolean mIsSuppressed;
638 
639         /**
640          * Construct an test case object.
641          *
642          * @param name The name of the test case.
643          * @param description The description of the test case.
644          * @param knownFailure The reason of known failure.
645          */
TestMethod(String name, String description, String controller, Set<String> abis, String knownFailure, boolean isBroken, boolean isSuppressed)646         TestMethod(String name, String description, String controller, Set<String> abis,
647                 String knownFailure, boolean isBroken, boolean isSuppressed) {
648             mName = name;
649             mDescription = description;
650             mController = controller;
651             mAbis = abis;
652             mKnownFailure = knownFailure;
653             mIsBroken = isBroken;
654             mIsSuppressed = isSuppressed;
655         }
656     }
657 }
658