001: /*
002:
003: Licensed to the Apache Software Foundation (ASF) under one or more
004: contributor license agreements. See the NOTICE file distributed with
005: this work for additional information regarding copyright ownership.
006: The ASF licenses this file to You under the Apache License, Version 2.0
007: (the "License"); you may not use this file except in compliance with
008: the License. You may obtain a copy of the License at
009:
010: http://www.apache.org/licenses/LICENSE-2.0
011:
012: Unless required by applicable law or agreed to in writing, software
013: distributed under the License is distributed on an "AS IS" BASIS,
014: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: See the License for the specific language governing permissions and
016: limitations under the License.
017:
018: */
019: package org.apache.batik.test.xml;
020:
021: import java.io.File;
022: import java.io.StringWriter;
023: import java.io.PrintWriter;
024:
025: import java.net.URL;
026: import java.net.MalformedURLException;
027:
028: import java.util.HashSet;
029: import java.util.Set;
030: import java.util.List;
031: import java.util.ArrayList;
032:
033: import javax.xml.parsers.DocumentBuilderFactory;
034: import javax.xml.parsers.DocumentBuilder;
035:
036: import org.apache.batik.test.DefaultTestSuite;
037: import org.apache.batik.test.DefaultTestReport;
038: import org.apache.batik.test.TestReport;
039: import org.apache.batik.test.TestSuite;
040: import org.apache.batik.test.Test;
041: import org.apache.batik.test.TestFilter;
042: import org.apache.batik.test.TestException;
043: import org.apache.batik.test.TestReportProcessor;
044:
045: import org.w3c.dom.Element;
046: import org.w3c.dom.Document;
047: import org.w3c.dom.Node;
048: import org.w3c.dom.NodeList;
049:
050: /**
051: * This class can be used to build and run a <tt>TestSuite</tt> from
052: * an XML description following the "XML Test Run" and "XML Test Suite"
053: * formats, whose constants are defined in the <tt>XTRunConstants</tt>
054: * and <tt>XTSConstants</tt> interfaces.
055: *
056: * This class takes a "Test Run" XML description as an input. That
057: * description contains: <br />
058: * + pointers to a number of "Test Suite" XML descriptions,
059: * which contain the definition of the set of <tt>Tests</tt> to be
060: * run and their configuration.<br />
061: * + a description of the set of <tt>TestReportProcessor</tt> and
062: * their configuration that should be used to process the reports
063: * generated by the various <tt>TestSuites</tt>.<br />
064: *
065: * @author <a href="mailto:vhardy@apache.org">Vincent Hardy</a>
066: * @version $Id: XMLTestSuiteRunner.java 482121 2006-12-04 10:00:39Z dvholten $
067: */
068: public class XMLTestSuiteRunner implements XTRunConstants, XTSConstants {
069: /**
070: * Displayed when no test or testSuite matching the input id was
071: * found.
072: * {0} : unmatched id set
073: */
074: public static final String MESSAGE_UNMATCHED_TEST_IDS = "XMLTestSuiteRunner.messages.unmatched.test.ids";
075:
076: /**
077: * An error happened while processing a <tt>TestreportProcessor</tt>
078: * description.
079: * {0} : the <testReportProcessor> "className" attribute value
080: * {1} : exception's class name
081: * {2} : exception's message
082: * {3} : exception's stack trace
083: */
084: public static final String CANNOT_CREATE_TEST_REPORT_PROCESSOR = "xml.XMLTestSuiteRunner.error.cannot.create.test.report.processor";
085:
086: /**
087: * An error happened while running the <tt>TestSuite</tt>
088: * {0} : <tt>TestSuite</tt> name
089: * {1} : <tt>TestSuite</tt> class name.
090: * {1} : exception's class name.
091: * {2} : exception's message
092: * {3} : exception's stack trace.
093: */
094: public static final String TEST_SUITE_EXCEPTION = "xml.XMLTestSuiteRunner.test.suite.exception";
095:
096: /**
097: * An error happened while processing the <tt>TestReport</tt>
098: * generated by the <tt>TestSuite</tt>
099: * {0} : <tt>TestReportProcessor</tt> class name.
100: * {1} : exception's class name.
101: * {2} : exception's message
102: * {3} : exception's stack trace.
103: */
104: public static final String TEST_REPORT_PROCESSING_EXCEPTION = "xml.XMLTestSuiteRunner.error.test.report.processing.exception";
105:
106: /**
107: * Test filter which accepts all tests
108: */
109: public static class AcceptAllTestsFilter implements TestFilter {
110: public Test filter(Test t) {
111: return t;
112: }
113: }
114:
115: /**
116: * Test filter which only accepts tests with ids matching
117: * the ones passed to its constructor.
118: */
119: public static class IdBasedTestFilter implements TestFilter {
120: protected String[] ids;
121: protected Set unmatchedIds = new HashSet();
122:
123: public IdBasedTestFilter(String[] ids) {
124: this .ids = ids;
125: for (int i = 0; i < ids.length; i++) {
126: unmatchedIds.add(ids[i]);
127: }
128: }
129:
130: public String traceUnusedIds() {
131: Object[] ui = unmatchedIds.toArray();
132: StringBuffer sb = null;
133: if (ui != null && ui.length > 0) {
134: sb = new StringBuffer();
135: sb.append(ui[0].toString());
136: for (int i = 1; i < ui.length; i++) {
137: sb.append(", ");
138: sb.append(ui[i].toString());
139: }
140: }
141: return sb != null ? sb.toString() : null;
142: }
143:
144: /**
145: * Remove children <tt>Test</tt> instances from the <tt>TestSuite</tt>
146: * if they are filtered out.
147: */
148: public void filterTestSuite(TestSuite ts) {
149: Test[] t = ts.getChildrenTests();
150: int nTests = t != null ? t.length : 0;
151: for (int i = 0; i < nTests; i++) {
152: if (filter(t[i]) == null) {
153: ts.removeTest(t[i]);
154: }
155: }
156: }
157:
158: /**
159: * Accept a test if one of the ids is found (i.e., an
160: * exact match or a substring) in the <tt>Test</tt>'s
161: * qualified id.
162: * <tt>TestSuite</tt>s are accepted if they have children and
163: * rejected if they have none.
164: */
165: public Test filter(Test t) {
166: String id = t.getQualifiedId();
167: boolean isRequested = isRequestedId(id);
168:
169: //
170: // First, handle TestSuite children
171: //
172: if (t instanceof TestSuite) {
173: TestSuite ts = (TestSuite) t;
174: filterTestSuite(ts);
175: if (ts.getChildrenCount() > 0) {
176: return t;
177: }
178: return null;
179: }
180:
181: //
182: // Now, handle leaf Tests
183: //
184: if (isRequested) {
185: return t;
186: }
187:
188: return null;
189: }
190:
191: protected boolean isRequestedId(String id) {
192: for (int i = 0; i < ids.length; i++) {
193: //
194: // id substring of ids[i]
195: // ======================
196: // if the test identifier (id) is a substring of one of the requested
197: // then the test is one of the requested test parents and should be accepted.
198: // Example: id = "all.B" with requested ids = "all.A all.B.B3"
199: // "all.B" (id) is a substring of "all.B.B3" (ids[1])
200: // Conclusion: id is accepted because it is a parent test of ids[1]
201: //
202: // ids[i] substring of id
203: // ======================
204: // if one of the requested test identifiers (id[i]) is a substring of the
205: // test id, then one of the test children is requested, so the test should
206: // be accepted.
207: // Example: id = "all.B.B3" with requested ids = "all.B"
208: // "all.B" (ids[0]) is a substring of "all.B.B3" (id)
209: // Conclusion: id is accepted because it is a child test of ids[0]
210: //
211: if (ids[i].lastIndexOf(id) == 0) {
212: // System.out.println("accepting " + id + ". It is (or is the a parent of) " + ids[i]);
213: unmatchedIds.remove(ids[i]);
214: return true;
215: }
216:
217: if (id.lastIndexOf(ids[i]) != -1) {
218: // System.out.println("accepting " + id + " it is (or is a child of) the requested " + ids[i]);
219: unmatchedIds.remove(ids[i]);
220: return true;
221: }
222: }
223: return false;
224: }
225:
226: }
227:
228: /**
229: * Builds an array of <tt>TestReportProcessor</tt> from the input
230: * element, assuming the input element is a <testSuite> instance,
231: */
232: protected TestReportProcessor[] extractTestReportProcessor(
233: Element element) throws TestException {
234: List processors = new ArrayList();
235:
236: NodeList children = element.getChildNodes();
237: if (children != null && children.getLength() > 0) {
238: int n = children.getLength();
239: for (int i = 0; i < n; i++) {
240: Node child = children.item(i);
241: if (child.getNodeType() == Node.ELEMENT_NODE) {
242: Element childElement = (Element) child;
243: String tagName = childElement.getTagName().intern();
244: if (tagName == XTRun_TEST_REPORT_PROCESSOR_TAG) {
245: processors.add(buildProcessor(childElement));
246: }
247: }
248: }
249: }
250:
251: TestReportProcessor[] p = null;
252: if (processors.size() > 0) {
253: p = new TestReportProcessor[processors.size()];
254: processors.toArray(p);
255: }
256:
257: return p;
258: }
259:
260: /**
261: * Builds a <tt>TestResultProcessor</tt> from an element.
262: */
263: protected TestReportProcessor buildProcessor(Element element)
264: throws TestException {
265: try {
266: return (TestReportProcessor) XMLReflect
267: .buildObject(element);
268: } catch (Exception e) {
269: StringWriter sw = new StringWriter();
270: PrintWriter pw = new PrintWriter(sw);
271: e.printStackTrace(pw);
272: throw new TestException(
273: CANNOT_CREATE_TEST_REPORT_PROCESSOR, new Object[] {
274: element.getAttribute(XR_CLASS_ATTRIBUTE),
275: e.getClass().getName(), e.getMessage(),
276: sw.toString() }, e);
277: }
278: }
279:
280: /**
281: * Builds a <tt>TestSuite</tt> from an input element.
282: * This method assumes that element is a <testRun>
283: * instance. The element is scanned for children
284: * <testSuite> elements which is loaded into
285: * a <tt>Test</tt> and composited into a <tt>TestSuite</tt>
286: */
287: protected DefaultTestSuite buildTestRunTestSuite(Element element)
288: throws TestException {
289: DefaultTestSuite testSuite = new DefaultTestSuite();
290:
291: //
292: // Set the testRun name and id on the top level testSuite
293: //
294: String name = element.getAttribute(XTRun_NAME_ATTRIBUTE);
295: testSuite.setName(name);
296:
297: String id = element.getAttribute(XTRun_ID_ATTRIBUTE);
298: testSuite.setId(id);
299:
300: Element[] testSuites = getChildrenByTagName(element,
301: XTRun_TEST_SUITE_TAG);
302:
303: int n = testSuites != null ? testSuites.length : 0;
304: for (int i = 0; i < n; i++) {
305: String suiteHref = testSuites[i]
306: .getAttribute(XTRun_HREF_ATTRIBUTE);
307:
308: Test test = XMLTestSuiteLoader.loadTestSuite(suiteHref,
309: testSuite);
310: if (test != null) {
311: testSuite.addTest(test);
312: }
313: }
314:
315: return testSuite;
316: }
317:
318: /**
319: * Gets all the children of a given type.
320: */
321: protected Element[] getChildrenByTagName(Element element,
322: String tagName) {
323: tagName = tagName.intern();
324: List childrenWithTagName = new ArrayList();
325:
326: NodeList children = element.getChildNodes();
327: if (children != null && children.getLength() > 0) {
328: int n = children.getLength();
329: for (int i = 0; i < n; i++) {
330: Node child = children.item(i);
331: if (child.getNodeType() == Node.ELEMENT_NODE) {
332: Element childElement = (Element) child;
333: String childTagName = childElement.getTagName()
334: .intern();
335: if (childTagName == tagName) {
336: childrenWithTagName.add(childElement);
337: }
338: }
339: }
340: }
341:
342: Element[] a = null;
343: if (childrenWithTagName.size() > 0) {
344: a = new Element[childrenWithTagName.size()];
345: childrenWithTagName.toArray(a);
346: }
347:
348: return a;
349: }
350:
351: /**
352: * Runs the test suite described by the input
353: * Document object. If the input ids array
354: * is null or of zero length, then all the tests will be run.
355: * Otherwise, only the tests identified by
356: * the array will be run.
357: */
358: public TestReport run(Document doc, String[] ids)
359: throws TestException {
360: Element root = doc.getDocumentElement();
361:
362: return run(root, ids);
363: }
364:
365: protected TestReport runTest(Test test) throws TestException {
366: try {
367: return test.run();
368: } catch (Exception e) {
369: StringWriter sw = new StringWriter();
370: PrintWriter pw = new PrintWriter(sw);
371: e.printStackTrace(pw);
372: throw new TestException(TEST_SUITE_EXCEPTION, new Object[] {
373: test.getName(), test.getClass().getName(),
374: e.getClass().getName(), e.getMessage(),
375: sw.toString() }, e);
376: }
377: }
378:
379: protected void processReport(TestReport report,
380: TestReportProcessor[] processors) throws TestException {
381: int n = processors.length;
382: int i = 0;
383: try {
384: for (; i < n; i++) {
385: processors[i].processReport(report);
386: }
387: } catch (Exception e) {
388: StringWriter sw = new StringWriter();
389: PrintWriter pw = new PrintWriter(sw);
390: e.printStackTrace(pw);
391: throw new TestException(TEST_REPORT_PROCESSING_EXCEPTION,
392: new Object[] { processors[i].getClass().getName(),
393: e.getClass().getName(), e.getMessage(),
394: sw.toString() }, e);
395: }
396: }
397:
398: protected TestReport run(Element testRunElement, String[] ids)
399: throws TestException {
400: //
401: // First, build entire suite of tests
402: //
403: Test testRun = buildTestRunTestSuite(testRunElement);
404:
405: //
406: // Filter testSuite if necessary
407: //
408: Test filteredTestRun = testRun;
409: if (ids != null && ids.length > 0) {
410: IdBasedTestFilter filter = new IdBasedTestFilter(ids);
411: filteredTestRun = filter.filter(testRun);
412: String unusedIds = filter.traceUnusedIds();
413: if (unusedIds != null) {
414: System.err.println(Messages.formatMessage(
415: MESSAGE_UNMATCHED_TEST_IDS,
416: new Object[] { unusedIds }));
417: }
418: }
419:
420: if (filteredTestRun == null) {
421: DefaultTestReport report = new DefaultTestReport(testRun);
422: report.setPassed(true);
423: return report;
424: }
425:
426: //
427: // Now, get the set of TestReportProcessors
428: // that can use the data
429: //
430: TestReportProcessor[] processors = extractTestReportProcessor(testRunElement);
431:
432: //
433: // Run the test
434: //
435: TestReport report = runTest(testRun);
436:
437: //
438: // Process the report
439: //
440: if (processors != null) {
441: processReport(report, processors);
442: }
443:
444: return report;
445: }
446:
447: /**
448: * Displayed when the user passes no arguments to the command line.
449: */
450: public static final String USAGE = "XMLTestSuiteRunner.messages.error.usage";
451:
452: /**
453: * Displayed when the input argument does not represent an existing
454: * file to notify the user that the argument is going to be
455: * interpreted as a URI.
456: */
457: public static final String NOT_A_FILE_TRY_URI = "XMLTestSuiteRunner.messages.error.not.a.file.try.uri";
458:
459: /**
460: * Displayed when the input file name cannot be turned into a URL
461: */
462: public static final String COULD_NOT_CONVERT_FILE_NAME_TO_URI = "XMLTestSuiteRunner.messages.error.could.not.convert.file.name.to.uri";
463:
464: /**
465: * Displayed when the input argument does not represent a valid
466: * URI
467: */
468: public static final String INVALID_URI = "XMLTestSuiteRunner.messages.error.invalid.uri";
469:
470: /**
471: * Displayed when the input document cannot be parsed.
472: * {0} : uri of the invalid document.
473: * {1} : exception generated while parsing
474: * {2} : exception message
475: */
476: public static final String INVALID_DOCUMENT = "XMLTestSuiteRunner.messages.error.invalid.document";
477:
478: /**
479: * Error displayed when an error occurs while running the
480: * test suite
481: */
482: public static final String ERROR_RUNNING_TEST_SUITE = "XMLTestSuiteRunner.messages.error.running.test.suite";
483:
484: public static void main(String[] args) {
485: if (args.length < 1) {
486: System.err.println(Messages.formatMessage(USAGE, null));
487: System.exit(0);
488: }
489:
490: String uriStr = args[0];
491: String[] ids = new String[args.length - 1];
492: System.arraycopy(args, 1, ids, 0, args.length - 1);
493:
494: File file = new File(uriStr);
495: URL url = null;
496: if (file.exists()) {
497: try {
498: url = file.toURL();
499: } catch (MalformedURLException e) {
500: System.err.println(Messages.formatMessage(
501: COULD_NOT_CONVERT_FILE_NAME_TO_URI,
502: new Object[] { uriStr }));
503: System.exit(0);
504: }
505: }
506:
507: else {
508: System.err.println(Messages.formatMessage(
509: NOT_A_FILE_TRY_URI, new Object[] { uriStr }));
510: try {
511: url = new URL(uriStr);
512: } catch (MalformedURLException e) {
513: System.err.println(Messages.formatMessage(INVALID_URI,
514: new Object[] { uriStr }));
515: System.exit(0);
516: }
517: }
518:
519: Document doc = null;
520:
521: try {
522: System.err.println("Loading document ...");
523:
524: DocumentBuilder docBuilder = DocumentBuilderFactory
525: .newInstance().newDocumentBuilder();
526:
527: doc = docBuilder.parse(url.toString());
528: } catch (Exception e) {
529: e.printStackTrace();
530: System.err.println(Messages.formatMessage(INVALID_DOCUMENT,
531: new Object[] { uriStr, e.getClass().getName(),
532: e.getMessage() }));
533: System.exit(0);
534: }
535:
536: try {
537: System.err.println("Running test run...");
538: XMLTestSuiteRunner r = new XMLTestSuiteRunner();
539: r.run(doc, ids);
540: } catch (TestException e) {
541: System.err.println(Messages.formatMessage(
542: ERROR_RUNNING_TEST_SUITE, new Object[] { e
543: .getMessage() }));
544: System.exit(0);
545: }
546:
547: System.exit(1);
548:
549: }
550: }
|