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