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