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