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 2002-2003 (C) Intalio, Inc. All Rights Reserved.
32   *
33   * $Id: CastorTestCase.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.IOException;
39  import java.io.InputStream;
40  import java.io.InputStreamReader;
41  import java.net.MalformedURLException;
42  import java.net.URL;
43  import java.net.URLClassLoader;
44  import java.util.jar.JarFile;
45  
46  import junit.framework.Test;
47  import junit.framework.TestCase;
48  import junit.framework.TestSuite;
49  
50  import org.castor.xmlctf.util.FileServices;
51  import org.exolab.castor.tests.framework.testDescriptor.MarshallingTest;
52  import org.exolab.castor.tests.framework.testDescriptor.OnlySourceGenerationTest;
53  import org.exolab.castor.tests.framework.testDescriptor.SchemaTest;
54  import org.exolab.castor.tests.framework.testDescriptor.SourceGeneratorTest;
55  import org.exolab.castor.tests.framework.testDescriptor.TestDescriptor;
56  import org.exolab.castor.tests.framework.testDescriptor.TestDescriptorChoice;
57  import org.exolab.castor.tests.framework.testDescriptor.UnitTestCase;
58  import org.exolab.castor.xml.MarshalException;
59  import org.exolab.castor.xml.ValidationException;
60  
61  /**
62   * Abstracts a test case in the CTF (Castor Test Framework). A CTF test case can be driven by a
63   * directory or by a JAR file.
64   *
65   * @author <a href="mailto:blandin@intalio.com">Arnaud Blandin</a>
66   * @version $Revision: 6787 $ $Date: 2004-03-08 17:23:25 -0700 (Mon, 08 Mar 2004) $
67   */
68  public class CastorTestCase extends TestCase {
69  
70    /** An unknown type of Castor test case. */
71    public static final short UNKNOWN = -1;
72    /** A directory-based Castor test case. */
73    public static final short DIRECTORY = 0;
74    /** A JAR-based Castor test case. */
75    public static final short JAR = 1;
76  
77    /** Name of the resource for the test descriptor XML document. */
78    public static final String TEST_DESCRIPTOR = "TestDescriptor.xml";
79    /** Name of the resource for the test descriptor XML document if a JAR file is used. */
80    private static final String TEST_DESCRIPTOR_JAR = "META-INF/TestDescriptor.xml";
81    /** File separator for this system. */
82    private static final String FILE_SEPARATOR = System.getProperty("file.separator");
83    /** Java version of the JVM we are running in. */
84    private static final float JAVA_VERSION =
85        Float.parseFloat(System.getProperty("java.specification.version"));
86    /** True if we desire a lot of information on what is happening during the test. */
87    private static final boolean VERBOSE;
88  
89    static {
90      String v = System.getProperty(TestCaseAggregator.VERBOSE_PROPERTY);
91      VERBOSE = (v != null && v.equals("true"));
92      v = null;
93    }
94  
95    /**
96     * True if we dump the stack trace that is generated from any validation exception or marshal
97     * exception caused by reading the XML Test Description.
98     */
99    private static boolean _printStack;
100 
101   static {
102     String v = System.getProperty(TestCaseAggregator.PRINT_STACK_TRACE);
103     _printStack = (v != null && v.equals("true"));
104     v = null;
105   }
106 
107   /** Indicates whether or not the output root directory has been compiled. */
108   private boolean _compiled = false;
109   /** Class loader to use for the jar. */
110   private ClassLoader _loader;
111   /** The test descriptor from the jar. */
112   private TestDescriptor _testDescriptor;
113   /** The file that contains the tests. This can either be a directory or a jar file. */
114   private final File _testFile;
115   /** The Type of the test (directory or jar). */
116   private final short _type;
117   /** Place where the temporary files and other output are created. */
118   private final File _outputRootFile;
119   /** String containing the directory path from the test root to here. */
120   private final String _directoryToHere;
121 
122   /**
123    * Constructs a CTF test case given only a test case name.
124    * 
125    * @param name the name of the test case
126    */
127   public CastorTestCase(final String name) {
128     super(name);
129     _testFile = null;
130     _outputRootFile = null;
131     _directoryToHere = "";
132     _type = UNKNOWN;
133   }
134 
135   /**
136    * Constructs a CTF test case given a File (either a JAR file or a directory) and a directory
137    * where temporary files will be placed. The test case name will be derived from the file (JAR or
138    * directory) name.
139    *
140    * @param file Either a directory containing TestDescriptor.xml or a JAR file containing
141    *        META-INF/TestDescriptor.xml
142    * @param directoryToHere directory path leading to the current test.
143    * @param outputRoot Directory where temporary files and output will go.
144    */
145   public CastorTestCase(final File file, final String directoryToHere, final String outputRoot) {
146     super(directoryToHere);
147     _directoryToHere = directoryToHere;
148 
149     if (file.isDirectory()) {
150       _type = DIRECTORY;
151       _outputRootFile = new File(outputRoot + FILE_SEPARATOR);
152     } else {
153       _type = JAR;
154       try {
155         new JarFile(file);
156         String fileName = file.getName();
157         fileName = fileName.substring(0, fileName.lastIndexOf("."));
158         _outputRootFile = new File(outputRoot + FILE_SEPARATOR + fileName);
159       } catch (java.util.zip.ZipException e) {
160         throw new IllegalStateException(file.getAbsolutePath() + " is not a valid JAR file.");
161       } catch (java.io.IOException ie) {
162         throw new IllegalStateException(file.getAbsolutePath() + " is not a valid JAR file.");
163       }
164     }
165 
166     // Append to our current class loader the directory or JAR containing our test case
167     try {
168       URL[] urlList = {file.toURL()};
169       _loader = new URLClassLoader(urlList, this.getClass().getClassLoader());
170     } catch (MalformedURLException urle) {
171       // should never happen--> failure before
172       urle.printStackTrace();
173     }
174     _testFile = file;
175     _outputRootFile.mkdirs();
176   }
177 
178   public ClassLoader getClassLoader() {
179     return _loader;
180   }
181 
182   public File getTestFile() {
183     return _testFile;
184   }
185 
186   public short getType() {
187     return _type;
188   }
189 
190   public String getDirectoryToHere() {
191     return _directoryToHere;
192   }
193 
194   public File getOutputRootFile() {
195     return _outputRootFile;
196   }
197 
198   /**
199    * Returns a boolean that when true indicates the output directory has been compiled. This is
200    * useful for preventing the compilation of a directory multiple times when more than one test
201    * case exists in a given directory.
202    *
203    * @return true when the output root directory has already been compiled.
204    */
205   public boolean isDirectoryCompiled() {
206     return _compiled;
207   } // -- isDirectoryCompiled
208 
209   /**
210    * Sets the ClassLoader to use for loading the resources for this test case.
211    *
212    * @param loader the class loader to use
213    */
214   public void setClassLoader(final ClassLoader loader) {
215     _loader = loader;
216   }
217 
218   /**
219    * Sets a flag to indicate the output directory has been compiled. This prevents compiling a
220    * directory multiple times unnecessarily when more than one test case exists in a given
221    * directory.
222    *
223    * @param compiled true if the output directory for this test case has been compiled
224    */
225   public void setDirectoryCompiled(final boolean compiled) {
226     _compiled = compiled;
227   } // -- setDirectoryCompiled
228 
229   /**
230    * Assembles and returns a test suite containing all known tests.
231    *
232    * @return A non-null test suite if we can load the test descriptor
233    */
234   public Test suite() {
235     final InputStream descriptor;
236     if (_type == JAR) {
237       descriptor = _loader.getResourceAsStream(TEST_DESCRIPTOR_JAR);
238     } else {
239       descriptor = _loader.getResourceAsStream(TEST_DESCRIPTOR);
240     }
241 
242     if (descriptor == null) {
243       verbose("test '" + _testFile.getName() + "' has no TestDescriptor.xml");
244       return null;
245     }
246 
247     try {
248       _testDescriptor = TestDescriptor.unmarshal(new InputStreamReader(descriptor));
249     } catch (ValidationException ve) {
250       verbose("Error reading: " + _testFile.getAbsolutePath());
251       verbose("-> " + ve.toString());
252       if (_printStack) {
253         ve.printStackTrace(System.out);
254       }
255       fail(ve.toString());
256     } catch (MarshalException me) {
257       verbose("Error reading: " + _testFile.getAbsolutePath());
258       verbose("-> " + me.toString());
259       if (_printStack) {
260         me.printStackTrace(System.out);
261       }
262       fail(me.toString());
263     } finally {
264       try {
265         descriptor.close();
266       } catch (IOException e) {
267         // ignore
268       }
269     }
270 
271     if (_testDescriptor.hasMinimumJavaVersion()) {
272       // Get minimum Java version & convert to our Canonical form
273       float minVersion = _testDescriptor.getMinimumJavaVersion();
274       if (minVersion >= 5F && minVersion < 10F) {
275         minVersion = 1.0F + (minVersion / 10F);
276       }
277       if (JAVA_VERSION < minVersion) {
278         verbose("-->Test requires at minimum Java " + minVersion + ", but we are running Java "
279             + JAVA_VERSION);
280         verbose("-->Skipping the test");
281         return null;
282       }
283     }
284 
285     if (_testDescriptor.hasMaximumJavaVersion()) {
286       // Get maximum Java version & convert to our Canonical form
287       float maxVersion = _testDescriptor.getMaximumJavaVersion();
288       if (maxVersion >= 5F && maxVersion < 10F) {
289         maxVersion = 1.0F + (maxVersion / 10F);
290       }
291       if (JAVA_VERSION > maxVersion) {
292         verbose("-->Test is designed to run up to Java " + maxVersion + ", but we are running Java "
293             + JAVA_VERSION);
294         verbose("-->Skipping the test");
295         return null;
296       }
297     }
298 
299     final String suiteName = _directoryToHere + _testDescriptor.getName();
300     final TestSuite suite = new TestSuite(suiteName);
301     verbose("Creating '" + suiteName + "' test suite");
302 
303     TestDescriptorChoice choice = _testDescriptor.getTestDescriptorChoice();
304     MarshallingTest marshallingTests = choice.getMarshallingTest();
305     SourceGeneratorTest sourceGenTests = choice.getSourceGeneratorTest();
306     SchemaTest schemaTests = choice.getSchemaTest();
307     OnlySourceGenerationTest genOnlyTests = choice.getOnlySourceGenerationTest();
308 
309     if (marshallingTests != null) {
310       setUpMarshallingTests(suiteName, suite, marshallingTests);
311     }
312     if (sourceGenTests != null) {
313       setUpSourceGeneratorTests(suiteName, suite, sourceGenTests);
314     }
315     if (schemaTests != null) {
316       setUpSchemaTests(suiteName, suite, schemaTests);
317     }
318     if (genOnlyTests != null) {
319       setUpGenerationOnlyTests(suiteName, suite, genOnlyTests);
320     }
321 
322     return suite;
323   }
324 
325   /**
326    * Loops over all Marshalling tests from our TestDescriptor.xml, configures each test and adds it
327    * to our suite.
328    *
329    * @param suiteName Test Suite name
330    * @param suite the Test Suite to add all unit tests to
331    * @param mar a collection of Marshalling Unit Tests
332    */
333   private void setUpMarshallingTests(final String suiteName, final TestSuite suite,
334       final MarshallingTest mar) {
335     for (int i = 0; i < mar.getUnitTestCaseCount(); ++i) {
336       UnitTestCase tc = mar.getUnitTestCase(i);
337       MarshallingFrameworkTestCase mftc = new MarshallingFrameworkTestCase(this, tc, mar);
338       mftc._configuration = mar.getConfiguration();
339       mftc.setTestSuiteName(suiteName);
340       suite.addTest(mftc.suite());
341     }
342   }
343 
344   /**
345    * Loops over all Source Generation tests from our TestDescriptor.xml, configures each test and
346    * adds it to our suite.
347    *
348    * @param suiteName Test Suite name
349    * @param suite the Test Suite to add all unit tests to
350    * @param sg a collection of Source Generation Unit Tests
351    */
352   private void setUpSourceGeneratorTests(final String suiteName, final TestSuite suite,
353       final SourceGeneratorTest sg) {
354     for (int i = 0; i < sg.getUnitTestCaseCount(); ++i) {
355       UnitTestCase tc = sg.getUnitTestCase(i);
356       SourceGeneratorTestCase sgtc = new SourceGeneratorTestCase(this, tc, sg);
357       sgtc.setTestSuiteName(suiteName);
358       suite.addTest(sgtc.suite());
359     }
360   }
361 
362   /**
363    * Loops over all Schema tests from our TestDescriptor.xml, configures each test and adds it to
364    * our suite.
365    *
366    * @param suiteName Test Suite name
367    * @param suite the Test Suite to add all unit tests to
368    * @param schemaTest a collection of Schema Unit Tests
369    */
370   private void setUpSchemaTests(final String suiteName, final TestSuite suite,
371       final SchemaTest schemaTest) {
372     for (int i = 0; i < schemaTest.getUnitTestCaseCount(); i++) {
373       UnitTestCase tc = schemaTest.getUnitTestCase(i);
374       // Little trick: getUnitTestCaseChoice should not be null at this point
375       String name = tc.getUnitTestCaseChoice().getSchema();
376       if (name.equals("*")) {
377         File[] list = _testFile.listFiles();
378         for (int j = 0; j < list.length; ++j) {
379           String fileName = list[j].getName();
380           // FIXME: It would be better to use a file filter and to make
381           // sure our SchemaReader can read this file
382           if (fileName.endsWith(FileServices.XSD)) {
383             makeIndividualSchemaTest(suiteName, suite, tc, fileName);
384           }
385         }
386       } else {
387         makeIndividualSchemaTest(suiteName, suite, tc, name);
388       }
389     }
390   }
391 
392   /**
393    * Loops over all Only-Source-Generation tests from our TestDescriptor.xml, configures each test
394    * and adds it to our suite.
395    *
396    * @param suiteName Test Suite name
397    * @param suite the Test Suite to add all unit tests to
398    * @param sg a collection of Source Generation Unit Tests
399    */
400   private void setUpGenerationOnlyTests(final String suiteName, final TestSuite suite,
401       final OnlySourceGenerationTest sg) {
402     for (int i = 0; i < sg.getUnitTestCaseCount(); ++i) {
403       UnitTestCase tc = sg.getUnitTestCase(i);
404       OnlySourceGenerationTestCase sgtc = new OnlySourceGenerationTestCase(this, tc, sg);
405       sgtc.setTestSuiteName(suiteName);
406       suite.addTest(sgtc.suite());
407     }
408   }
409 
410   /**
411    * Makes an individual Schema test and adds it to our Test Suite.
412    *
413    * @param suiteName Test Suite name
414    * @param suite the Test Suite to add all unit tests to
415    * @param tc our Test Case
416    * @param name Schema name
417    */
418   private void makeIndividualSchemaTest(final String suiteName, final TestSuite suite,
419       final UnitTestCase tc, final String name) {
420     tc.setName(suiteName + '#' + name);
421     SchemaTestCase stc = new SchemaTestCase(this, tc);
422     stc.setSchemaName(name);
423     suite.addTest(stc);
424   }
425 
426   /**
427    * Prints the provided message if verbose is true.
428    *
429    * @param message The message to display if verbose is true.
430    */
431   private void verbose(final String message) {
432     if (VERBOSE) {
433       System.out.println(message);
434     }
435   }
436 
437 }