View Javadoc
1   /*
2    * Redistribution and use of this software and associated documentation
3    * ("Software"), with or without modification, are permitted provided
4    * that the following conditions are met:
5    *
6    * 1. Redistributions of source code must retain copyright
7    *    statements and notices.  Redistributions must also contain a
8    *    copy of this document.
9    *
10   * 2. Redistributions in binary form must reproduce the
11   *    above copyright notice, this list of conditions and the
12   *    following disclaimer in the documentation and/or other
13   *    materials provided with the distribution.
14   *
15   * 3. The name "Exolab" must not be used to endorse or promote
16   *    products derived from this Software without prior written
17   *    permission of Intalio, Inc.  For written permission,
18   *    please contact info@exolab.org.
19   *
20   * 4. Products derived from this Software may not be called "Exolab"
21   *    nor may "Exolab" appear in their names without prior written
22   *    permission of Intalio, Inc. Exolab is a registered
23   *    trademark of Intalio, Inc.
24   *
25   * 5. Due credit should be given to the Exolab Project
26   *    (http://www.exolab.org/).
27   *
28   * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
29   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
32   * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39   * OF THE POSSIBILITY OF SUCH DAMAGE.
40   *
41   * Copyright 2001-2003 (C) Intalio, Inc. All Rights Reserved.
42   *
43   * $Id: XMLTestCase.java 6787 2007-01-29 06:00:49Z ekuns $
44   */
45  package org.castor.xmlctf;
46  
47  import java.io.File;
48  import java.io.FileInputStream;
49  import java.io.FileWriter;
50  import java.io.InputStream;
51  import java.io.PrintWriter;
52  import java.io.StringWriter;
53  import java.lang.reflect.Method;
54  import java.util.Enumeration;
55  import java.util.LinkedList;
56  import java.util.List;
57  
58  import junit.framework.TestCase;
59  
60  import org.castor.xmlctf.util.CTFUtils;
61  import org.exolab.castor.mapping.Mapping;
62  import org.exolab.castor.tests.framework.testDescriptor.CallMethod;
63  import org.exolab.castor.tests.framework.testDescriptor.Configuration;
64  import org.exolab.castor.tests.framework.testDescriptor.ConfigurationType;
65  import org.exolab.castor.tests.framework.testDescriptor.FailureType;
66  import org.exolab.castor.tests.framework.testDescriptor.ListenerType;
67  import org.exolab.castor.tests.framework.testDescriptor.UnitTestCase;
68  import org.exolab.castor.tests.framework.testDescriptor.Value;
69  import org.exolab.castor.tests.framework.testDescriptor.types.FailureStepType;
70  import org.exolab.castor.tests.framework.testDescriptor.types.TypeType;
71  import org.exolab.castor.util.NestedIOException;
72  import org.exolab.castor.xml.MarshalException;
73  import org.exolab.castor.xml.MarshalListener;
74  import org.exolab.castor.xml.Marshaller;
75  import org.exolab.castor.xml.Unmarshaller;
76  import org.exolab.castor.xml.XMLContext;
77  import org.xml.sax.InputSource;
78  
79  /**
80   * This class encapsulates all the common logic to run the test patterns for
81   * Castor XML. Basically it handle the marshalling/marshalling/comparing. This
82   * is used to factor the common code for the source generator test and the
83   * mapping/introspection tests as only the setup differ in the test patterns.
84   * <p>
85   * This class is not complete and expects to be extended.
86   *
87   * @author <a href="mailto:gignoux@kernelcenter.org">Sebastien Gignoux</a>
88   * @author <a href="mailto:blandin@intalio.com">Arnaud Blandin</a>
89   * @version $Revision: 6787 $ $Date: 2006-04-26 15:14:53 -0600 (Wed, 26 Apr 2006) $
90   */
91  public abstract class XMLTestCase extends TestCase {
92  
93      /** True if we desire a lot of info on what happen. */
94      public static boolean _verbose;
95      /** True if we dump the stack trace for any exception that occurs during testing. */
96      protected static boolean _printStack;
97  
98      static {
99          String v = System.getProperty(TestCaseAggregator.VERBOSE_PROPERTY);
100         _verbose = (v!=null && v.equals(Boolean.TRUE.toString()));
101 
102         v = System.getProperty(TestCaseAggregator.PRINT_STACK_TRACE);
103         _printStack = (v!=null && v.equals(Boolean.TRUE.toString()));
104     }
105 
106     /** Name of the test suite to which this test belongs. */
107     protected String               _suiteName;
108     /** The name of the root class for this test.  Must be set by a concrete class
109      * if a test with a random object is used. */
110     protected String               _rootClassName;
111     /** The root class for this test.  Must be set by a concrete class. */
112     protected Class                _rootClass;
113     /** If true, the dumpFields() function has been implemented in the root class. */
114     protected boolean              _hasDump;
115     /** The name of the mapping file to use in a Marshalling Framework Test Case.
116      * Must be set by a concrete class if a test with a reference document is used. */
117     protected Mapping              _mapping;
118     /** A listener for marshalling. */
119     protected Object               _listener;
120     /** Type of listener test -- Marshal, Unmarshal or Both. */
121     protected TypeType             _listenerType;
122     /** Gold file for listener. */
123     protected String               _listenerGoldFile;
124     /** The Configuration the Marshalling Framework. */
125     protected Configuration        _configuration;
126     /** Name of this test. */
127     protected final String         _name;
128     /** The unit test case this class represent. */
129     protected final UnitTestCase   _unitTest;
130     /** Place where the temporary file have to be put. */
131     protected final File           _outputRootFile;
132     /** True if the test needs to be skipped. */
133     protected final boolean        _skip;
134     /** The failure object that is not null if the test is expected to fail. */
135     protected final FailureType    _failure;
136     /** Used only to retrieve the classloader. */
137     protected final CastorTestCase _test;
138     /** The internal Castor XML context. */
139     private XMLContext _xmlContext;
140 
141     /**
142      * Instantiate a new XMLTestCase with the given name.
143      *
144      * @param name the name of this test case.
145      */
146     public XMLTestCase(final String name) {
147         super(name);
148         _name = cleanup(name);
149         throw new IllegalArgumentException("You cannot use the name-only constructor");
150     }
151 
152     /**
153      * Instantiates a new test case that represents the given UnitTestCase.
154      *
155      * @param test the reference to the jar/directory
156      * @param unit the UnitTestCase that wraps the configuration for this XML Test case.
157      */
158     public XMLTestCase(final CastorTestCase test, final UnitTestCase unit) {
159         super(unit.getName());
160         _name           = cleanup(unit.getName());
161         _unitTest       = unit;
162         _skip           = unit.getSkip();
163         _failure        = unit.getFailure();
164         _test           = test;
165         _outputRootFile = test.getOutputRootFile();
166         _xmlContext     = null;
167     }
168 
169     /**
170      * Instantiates a new test cases that has the same configuration of the given
171      * test case.
172      *
173      * @param name the name of the test case
174      * @param tc the XML test case that hold the configuration for this test case
175      */
176     public XMLTestCase(final String name, final XMLTestCase tc) {
177         super(name);
178         _name           = cleanup(tc._name);
179         _unitTest       = tc._unitTest;
180         _outputRootFile = tc._outputRootFile;
181         _skip           = tc._skip;
182         _failure        = tc._failure;
183         _test           = tc._test;
184         _xmlContext     = null;
185     }
186     
187     protected abstract void setUp() throws Exception;
188     
189     protected abstract void tearDown() throws Exception;
190 
191     public void setXMLContext(XMLContext xmlContext) {
192         _xmlContext = xmlContext;
193     }
194     
195     public XMLContext getXMLContext() {
196         return _xmlContext;
197     }
198 
199     /**
200      * Returns a version of the input name that is suitable for JUnit test case
201      * or test suite use. Apparently, JUnit truncates test case names after
202      * encountering certain characters, so we scrub those characters from the
203      * test case or test suite name.
204      * <p>
205      * We also translate all whitespace to blanks, remove all leading and
206      * trailing whitespace, and collapse consecutive whitespace to a single
207      * blank.
208      *
209      * @param name
210      *            the input name
211      * @return a name suitable for JUnit test case or test suite use.
212      */
213     static String cleanup(final String name) {
214         return name.replaceAll("[\\*()\\t\\n\\r\\f]", " ").replaceAll(" {2,}", " ").trim();
215     }
216 
217     /**
218      * Sets the name of the test suite to which this test case belongs to.
219      *
220      * @param suiteName the name of the test suite.
221      *
222      */
223     public void setTestSuiteName(final String suiteName) {
224         _suiteName = cleanup(suiteName);
225     }
226 
227     /**
228      * Returns the name of the test suite to which this test case belongs to.
229      * @return the name of the test suite to which this test case belongs to.
230      */
231     public String getTestSuiteName() {
232         return _suiteName;
233     }
234 
235     /**
236      * Called when a test case throws an Exception. Returns true if we expected
237      * this test to fail (and if this was the correct Exception class, if one is
238      * specified, and if this was the correct test step for the failure to
239      * occur, if one was specified).
240      *
241      * @param exception
242      *            the Exception that was thrown
243      * @param checkStep
244      *            the test step that threw an Exception
245      * @return true if we pass the test (because we expected this Exception)
246      */
247     protected boolean checkExceptionWasExpected(final Exception exception, final FailureStepType checkStep) {
248         if (_printStack) {
249             exception.printStackTrace();
250         }
251         // If we're not expecting any failure, then we're not expecting any Exception
252         if (_failure == null || !_failure.getContent()) {
253             return false;
254         }
255         if (checkStep == null) {
256             fail("CTF coding failure ... checkStep should never be null");
257         }
258 
259         // We expect failure.  Is this the failure we expected?
260 
261         String          exceptionName = _failure.getException();
262         FailureStepType expectedStep  = _failure.getFailureStep();
263 
264         // If no specific step or Exception, then any failure is OK
265         if (exceptionName == null && expectedStep == null) {
266             return true;
267         }
268 
269         // If we're expecting an exception, make sure we got the right one
270         Exception ex = exception;
271         if (ex instanceof NestedIOException) {
272             ex = ((NestedIOException)ex).getException();
273         }
274 
275         if (exceptionName != null) {
276             try {
277                 Class expected = Class.forName(exceptionName);
278                 if (!expected.isAssignableFrom(ex.getClass())) {
279                     String complaint = "Received Exception: '" + ex.getClass().getName()
280                             + ": " + ex + "' but expected: '" + exceptionName + "'.";
281 
282                     StringWriter sw = new StringWriter();
283                     PrintWriter pw = new PrintWriter(sw);
284                     pw.print(complaint);
285                     if (_verbose) {
286                         pw.println("Stacktrace for the Exception that was thrown:");
287                         ex.printStackTrace(pw);
288                     }
289                     pw.flush();
290                     System.out.println(sw.toString());
291 
292                     fail(complaint);
293                 }
294 
295             } catch (ClassNotFoundException cnfex) {
296                 fail("The Exception specified: '" + exceptionName + "' cannot be found in the CLASSPATH");
297             }
298         }
299 
300         // If we're expecting to fail at a specific step, make sure this is that step
301         if (expectedStep != null && !expectedStep.equals(checkStep)) {
302             fail("We expected to fail at test step '" + expectedStep.toString()
303                  + "' but actually failed at step '" + checkStep.toString() + "'");
304         }
305 
306         // We got the expected failure!  Return true.
307         return true;
308     }
309 
310     /**
311      * Marshals the object with the configuration of the test.
312      *
313      * @param object the object to marshall.
314      * @param fileName the name of the file where to marshall the object.
315      * @return a file containing marshalled output.
316      */
317     protected File testMarshal(final Object object, final String fileName) {
318         verbose("--> Marshaling to: '" + fileName + "'");
319 
320         File marshalOutput = new File(_outputRootFile, fileName);
321 
322         try {
323             Marshaller marshaller = createMarshaler(marshalOutput);
324             marshaller.marshal(object);
325         } catch (Exception e) {
326             if (!checkExceptionWasExpected(e, FailureStepType.MARSHAL_TO_DISK)) {
327                 fail("Exception marshaling to disk " + e);
328             }
329             return null;
330         }
331 
332         if (_failure != null && _failure.getContent() && _failure.getFailureStep() != null &&
333              _failure.getFailureStep().equals(FailureStepType.MARSHAL_TO_DISK)) {
334             fail("Unmarshaling was expected to fail, but succeeded");
335         }
336         return marshalOutput;
337     }
338 
339     private Marshaller createMarshaler(final File marshalOutput) throws Exception {
340         getXMLContext().getInternalContext().getXMLClassDescriptorResolver().cleanDescriptorCache();
341         Marshaller marshaller = getXMLContext().createMarshaller();
342         marshaller.setWriter(new FileWriter(marshalOutput));
343 
344         //-- Configuration for marshaler?  Use config from unit test case if available
345         Configuration config = _unitTest.getConfiguration();
346         if (config == null) {
347             config = _configuration;
348         }
349 
350         if (config != null) {
351             ConfigurationType marshal = config.getMarshal();
352             List returnValues = invokeEnumeratedMethods(marshaller, marshal);
353             returnValues.clear(); // We don't care about the return values
354         }//-- config != null
355 
356         if (_mapping != null) {
357             marshaller.setMapping(_mapping);
358         }
359 
360         if (_listener != null && _listener instanceof MarshalListener
361                 && _listenerType != TypeType.UNMARSHAL) {
362             marshaller.setMarshalListener((MarshalListener)_listener);
363         }
364         return marshaller;
365     }
366 
367     /**
368      * Unmarshals the given file with the configuration of that test. If we
369      * unmarshal null, complain and fail the test case.
370      *
371      * @param file the file containing the xml document to unmarshall.
372      * @return the result of the unmarshalling of the given file.
373      * @throws Exception if anything goes wrong during the test
374      */
375     protected Object testUnmarshal(final File file) throws Exception {
376         verbose("--> Unmarshaling '" + file.getName() + "'\n");
377         InputStream inputStream = new FileInputStream(file);
378         Object unmarshaledObject = testUnmarshal(inputStream);
379         inputStream.close();
380         assertNotNull("Unmarshaling '" + file.getName() + "' results in a NULL object.",
381                       unmarshaledObject);
382         return unmarshaledObject;
383     }
384 
385     /**
386      * Unmarshals the given input stream with the configuration of that test.
387      *
388      * @param stream
389      *            the input stream containing the xml document to unmarshall.
390      * @return the result of the unmarshalling of the given file, null if
391      *         anything went wrong.
392      * @throws Exception if anything goes wrong during the test
393      */
394     protected Object testUnmarshal(final InputStream stream) throws Exception {
395         Object result = null;
396         final Unmarshaller unmar = createUnmarshaler();
397         result = unmar.unmarshal(new InputSource(stream));
398         return result;
399     }
400 
401     private Unmarshaller createUnmarshaler() throws Exception {
402         //-- Configuration for unmarshaler?  Use config from unit test case if available
403         Configuration config = _unitTest.getConfiguration();
404         if (config == null) {
405             config = _configuration;
406         }
407 
408         final Unmarshaller unmar;
409         if (_mapping != null) {
410 //J            unmar = new Unmarshaller(_mapping);
411             unmar = getXMLContext().createUnmarshaller();
412             unmar.setMapping(_mapping);
413         } else {
414             if (_test.getClassLoader() != null) {
415 //J                unmar = new Unmarshaller(_rootClass, _test.getClassLoader());
416                 unmar = getXMLContext().createUnmarshaller();
417                 unmar.setClassLoader(_test.getClassLoader());
418                 unmar.setClass(_rootClass);
419             } else {
420 //J                unmar = new Unmarshaller(_rootClass);
421                 unmar = getXMLContext().createUnmarshaller();
422                 unmar.setClass(_rootClass);
423             }
424         }
425 
426         if (_listener != null
427                 && _listener instanceof org.exolab.castor.xml.UnmarshalListener
428                 && _listenerType != TypeType.MARSHAL) {
429             unmar.setUnmarshalListener((org.exolab.castor.xml.UnmarshalListener)_listener);
430         }
431 
432         if (_listener != null
433                 && _listener instanceof org.castor.xml.UnmarshalListener
434                 && _listenerType != TypeType.MARSHAL) {
435             unmar.setUnmarshalListener((org.castor.xml.UnmarshalListener)_listener);
436         }
437 
438         unmar.setDebug(_verbose);
439 
440         if (config != null) {
441             ConfigurationType unmarshal = config.getUnmarshal();
442             List returnValues = invokeEnumeratedMethods(unmar, unmarshal);
443             returnValues.clear(); // We don't care about the return values
444         }
445 
446         return unmar;
447     }
448 
449     /**
450      * Invokes all requested methods on the object we are supplied.  Keeps track
451      * of the objects returned from each invocation and returns a List containing
452      * all of the return values.  Some of the return values may be null.  This
453      * is not an error!
454      *
455      * @param objectInvoked the object on which to invoke the methods
456      * @param config configuration object listing the methods we are to call
457      * @throws java.lang.Exception if anything goes wrong during the test
458      */
459     protected List invokeEnumeratedMethods(final Object objectInvoked, final ConfigurationType config)
460                                                                   throws java.lang.Exception {
461         final List returnValues = new LinkedList();
462 
463         if (config == null) {
464             return returnValues;
465         }
466 
467         Enumeration methods = config.enumerateCallMethod();
468         //-- For each method defined, we invoke it on marshaller just created.
469         while (methods.hasMoreElements()) {
470             CallMethod method = (CallMethod) methods.nextElement();
471             //-- search for the method to invoke
472             Method toBeinvoked = getInvokeMethod(objectInvoked.getClass(), method.getName(), method.getValue());
473             //-- construct the objects representing the arguments of the method
474             Object[] arguments = getArguments(method.getValue());
475             //-- Invoke the method and keep a copy of the returned object
476             returnValues.add(toBeinvoked.invoke(objectInvoked, arguments));
477         }
478 
479         return returnValues;
480     }
481 
482     /**
483      * Initialize listeners for marshalling/unmarshalling
484      *
485      * @param listener the listener to initialize
486      * @throws ClassNotFoundException
487      * @throws IllegalAccessException
488      * @throws InstantiationException
489      */
490     protected void initializeListeners(final ListenerType listener) throws ClassNotFoundException,
491                                                                      IllegalAccessException,
492                                                                      InstantiationException {
493         String listenerClassName = listener.getClassName();
494         if (listenerClassName == null || listenerClassName.length() == 0) {
495             throw new IllegalArgumentException("ClassName must be provided for Listener element.");
496         }
497 
498         final Class listenerClass;
499         if (_test.getClassLoader() != null) {
500             listenerClass = _test.getClassLoader().loadClass(listenerClassName);
501         } else {
502             listenerClass = Thread.currentThread().getContextClassLoader().loadClass(listenerClassName);
503         }
504 
505         Object o = listenerClass.newInstance();
506         if (o instanceof org.castor.xml.UnmarshalListener ||
507                 o instanceof org.exolab.castor.xml.UnmarshalListener ||
508                 o instanceof MarshalListener) {
509             _listener = o;
510         } else {
511             _listener = null;
512         }
513 
514         _listenerGoldFile = listener.getGoldFile();
515         _listenerType = listener.getType();
516     }
517 
518     /**
519      * Returns an instance of the object model hardcoded in the given
520      * ObjectModelBuilder.
521      *
522      * @param builderName the name of the class used as a builder
523      * @return an instance of the object model hardcoded in the given
524      *         ObjectModelBuilder.
525      * @throws java.lang.Exception if anything goes wrong during the test
526      */
527     protected Object buildObjectModel(final String builderName) throws java.lang.Exception {
528         Class builderClass = null;
529         if (_test.getClassLoader() != null) {
530             builderClass = _test.getClassLoader().loadClass(builderName);
531         } else {
532             builderClass = this.getClass().getClassLoader().loadClass(builderName);
533         }
534         ObjectModelBuilder builder = (ObjectModelBuilder)builderClass.newInstance();
535         return builder.buildInstance();
536     }
537 
538     private Method getInvokeMethod(final Class dataBinder, final String methodName,
539                                    final Value[] values)
540                                  throws ClassNotFoundException, NoSuchMethodException {
541         if (methodName == null) {
542             throw new IllegalArgumentException("The name of the method to invoke is null");
543         }
544         Class[] argumentsClass = null;
545 
546         //-- the value object represents the arguments of the method if any
547         if (values != null && values.length > 0) {
548             argumentsClass = new Class[values.length];
549             for (int i = 0; i < values.length; i++) {
550                 Value value = values[i];
551                 argumentsClass[i] = CTFUtils.getClass(value.getType(), _test.getClassLoader());
552             }
553         }
554         return dataBinder.getMethod(methodName, argumentsClass);
555     }
556 
557     private Object[] getArguments(final Value[] values) throws ClassNotFoundException, MarshalException {
558         Object[] result = new Object[values.length];
559         for (int i = 0; i < values.length; i++) {
560             Value value = values[i];
561             result[i] = CTFUtils.instantiateObject(value.getType(), value.getContent(), _test.getClassLoader());
562         }
563         return result;
564     }
565 
566     /**
567      * print the message if in verbose mode.
568      * @param message the message to print
569      */
570     protected void verbose(final String message) {
571         if (_verbose) {
572             System.out.println(message);
573         }
574     }
575 
576 }