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