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