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