/*
* Redistribution and use of this software and associated documentation
* ("Software"), with or without modification, are permitted provided
* that the following conditions are met:
*
* 1. Redistributions of source code must retain copyright
* statements and notices. Redistributions must also contain a
* copy of this document.
*
* 2. Redistributions in binary form must reproduce the
* above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* 3. The name "Exolab" must not be used to endorse or promote
* products derived from this Software without prior written
* permission of Intalio, Inc. For written permission,
* please contact info@exolab.org.
*
* 4. Products derived from this Software may not be called "Exolab"
* nor may "Exolab" appear in their names without prior written
* permission of Intalio, Inc. Exolab is a registered
* trademark of Intalio, Inc.
*
* 5. Due credit should be given to the Exolab Project
* (http://www.exolab.org/).
*
* THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Copyright 2002-2003 (C) Intalio, Inc. All Rights Reserved.
*
* $Id: CastorTestCase.java 6787 2007-01-29 06:00:49Z ekuns $
*/
package org.castor.xmlctf;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.JarFile;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.castor.xmlctf.util.FileServices;
import org.exolab.castor.tests.framework.testDescriptor.MarshallingTest;
import org.exolab.castor.tests.framework.testDescriptor.OnlySourceGenerationTest;
import org.exolab.castor.tests.framework.testDescriptor.SchemaTest;
import org.exolab.castor.tests.framework.testDescriptor.SourceGeneratorTest;
import org.exolab.castor.tests.framework.testDescriptor.TestDescriptor;
import org.exolab.castor.tests.framework.testDescriptor.TestDescriptorChoice;
import org.exolab.castor.tests.framework.testDescriptor.UnitTestCase;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.ValidationException;
/**
* Abstracts a test case in the CTF (Castor Test Framework). A CTF test case can
* be driven by a directory or by a JAR file.
*
* @author <a href="mailto:blandin@intalio.com">Arnaud Blandin</a>
* @version $Revision: 6787 $ $Date: 2004-03-08 17:23:25 -0700 (Mon, 08 Mar 2004) $
*/
public class CastorTestCase extends TestCase {
/** An unknown type of Castor test case. */
public static final short UNKNOWN = -1;
/** A directory-based Castor test case. */
public static final short DIRECTORY = 0;
/** A JAR-based Castor test case. */
public static final short JAR = 1;
/** Name of the resource for the test descriptor XML document. */
public static final String TEST_DESCRIPTOR = "TestDescriptor.xml";
/** Name of the resource for the test descriptor XML document if a JAR file is used. */
private static final String TEST_DESCRIPTOR_JAR = "META-INF/TestDescriptor.xml";
/** File separator for this system. */
private static final String FILE_SEPARATOR = System.getProperty("file.separator");
/** Java version of the JVM we are running in. */
private static final float JAVA_VERSION = Float.parseFloat(System.getProperty("java.specification.version"));
/** True if we desire a lot of information on what is happening during the test. */
private static final boolean VERBOSE;
static {
String v = System.getProperty(TestCaseAggregator.VERBOSE_PROPERTY);
VERBOSE = (v != null && v.equals("true"));
v = null;
}
/**
* True if we dump the stack trace that is generated from any validation
* exception or marshal exception caused by reading the XML Test
* Description.
*/
private static boolean _printStack;
static {
String v = System.getProperty(TestCaseAggregator.PRINT_STACK_TRACE);
_printStack = (v != null && v.equals("true"));
v = null;
}
/** Indicates whether or not the output root directory has been compiled. */
private boolean _compiled = false;
/** Class loader to use for the jar. */
private ClassLoader _loader;
/** The test descriptor from the jar. */
private TestDescriptor _testDescriptor;
/** The file that contains the tests. This can either be a directory or a jar file. */
private final File _testFile;
/** The Type of the test (directory or jar). */
private final short _type;
/** Place where the temporary files and other output are created. */
private final File _outputRootFile;
/** String containing the directory path from the test root to here. */
private final String _directoryToHere;
/**
* Constructs a CTF test case given only a test case name.
* @param name the name of the test case
*/
public CastorTestCase(final String name) {
super(name);
_testFile = null;
_outputRootFile = null;
_directoryToHere = "";
_type = UNKNOWN;
}
/**
* Constructs a CTF test case given a File (either a JAR file or a
* directory) and a directory where temporary files will be placed. The test
* case name will be derived from the file (JAR or directory) name.
*
* @param file Either a directory containing TestDescriptor.xml or a JAR
* file containing META-INF/TestDescriptor.xml
* @param directoryToHere directory path leading to the current test.
* @param outputRoot Directory where temporary files and output will go.
*/
public CastorTestCase(final File file, final String directoryToHere, final String outputRoot) {
super(directoryToHere);
_directoryToHere = directoryToHere;
if (file.isDirectory()) {
_type = DIRECTORY;
_outputRootFile = new File(outputRoot + FILE_SEPARATOR);
} else {
_type = JAR;
try {
new JarFile(file);
String fileName = file.getName();
fileName = fileName.substring(0, fileName.lastIndexOf("."));
_outputRootFile = new File(outputRoot + FILE_SEPARATOR + fileName);
} catch (java.util.zip.ZipException e) {
throw new IllegalStateException(file.getAbsolutePath() + " is not a valid JAR file.");
} catch (java.io.IOException ie) {
throw new IllegalStateException(file.getAbsolutePath() + " is not a valid JAR file.");
}
}
// Append to our current class loader the directory or JAR containing our test case
try {
URL[] urlList = {file.toURL()};
_loader = new URLClassLoader(urlList, this.getClass().getClassLoader());
} catch (MalformedURLException urle) {
//should never happen--> failure before
urle.printStackTrace();
}
_testFile = file;
_outputRootFile.mkdirs();
}
public ClassLoader getClassLoader() {
return _loader;
}
public File getTestFile() {
return _testFile;
}
public short getType() {
return _type;
}
public String getDirectoryToHere() {
return _directoryToHere;
}
public File getOutputRootFile() {
return _outputRootFile;
}
/**
* Returns a boolean that when true indicates the output directory has been
* compiled. This is useful for preventing the compilation of a directory
* multiple times when more than one test case exists in a given directory.
*
* @return true when the output root directory has already been compiled.
*/
public boolean isDirectoryCompiled() {
return _compiled;
} //-- isDirectoryCompiled
/**
* Sets the ClassLoader to use for loading the resources for this test case.
*
* @param loader the class loader to use
*/
public void setClassLoader(final ClassLoader loader) {
_loader = loader;
}
/**
* Sets a flag to indicate the output directory has been compiled. This
* prevents compiling a directory multiple times unnecessarily when more
* than one test case exists in a given directory.
*
* @param compiled true if the output directory for this test case has been
* compiled
*/
public void setDirectoryCompiled(final boolean compiled) {
_compiled = compiled;
} //-- setDirectoryCompiled
/**
* Assembles and returns a test suite containing all known tests.
*
* @return A non-null test suite if we can load the test descriptor
*/
public Test suite() {
final InputStream descriptor;
if (_type == JAR) {
descriptor = _loader.getResourceAsStream(TEST_DESCRIPTOR_JAR);
} else {
descriptor = _loader.getResourceAsStream(TEST_DESCRIPTOR);
}
if (descriptor == null) {
verbose("test '" + _testFile.getName() + "' has no TestDescriptor.xml");
return null;
}
try {
_testDescriptor = TestDescriptor.unmarshal(new InputStreamReader(descriptor));
} catch (ValidationException ve) {
verbose("Error reading: " + _testFile.getAbsolutePath());
verbose("-> " + ve.toString());
if (_printStack) {
ve.printStackTrace(System.out);
}
fail(ve.toString());
} catch (MarshalException me) {
verbose("Error reading: " + _testFile.getAbsolutePath());
verbose("-> " + me.toString());
if (_printStack) {
me.printStackTrace(System.out);
}
fail(me.toString());
} finally {
try {
descriptor.close();
} catch (IOException e) {
// ignore
}
}
if (_testDescriptor.hasMinimumJavaVersion()) {
// Get minimum Java version & convert to our Canonical form
float minVersion = _testDescriptor.getMinimumJavaVersion();
if (minVersion > 5F && minVersion < 10F) {
minVersion = 1.0F + (minVersion / 10F);
}
if (minVersion > JAVA_VERSION) {
verbose("-->Test requires Java " + minVersion + " but we are running Java " + JAVA_VERSION);
verbose("-->Skipping the test");
return null;
}
}
final String suiteName = _directoryToHere + _testDescriptor.getName();
final TestSuite suite = new TestSuite(suiteName);
verbose("Creating '" + suiteName + "' test suite");
TestDescriptorChoice choice = _testDescriptor.getTestDescriptorChoice();
MarshallingTest marshallingTests = choice.getMarshallingTest();
SourceGeneratorTest sourceGenTests = choice.getSourceGeneratorTest();
SchemaTest schemaTests = choice.getSchemaTest();
OnlySourceGenerationTest genOnlyTests = choice.getOnlySourceGenerationTest();
if (marshallingTests != null) {
setUpMarshallingTests(suiteName, suite, marshallingTests);
}
if (sourceGenTests != null) {
setUpSourceGeneratorTests(suiteName, suite, sourceGenTests);
}
if (schemaTests != null) {
setUpSchemaTests(suiteName, suite, schemaTests);
}
if (genOnlyTests != null) {
setUpGenerationOnlyTests(suiteName, suite, genOnlyTests);
}
return suite;
}
/**
* Loops over all Marshalling tests from our TestDescriptor.xml, configures
* each test and adds it to our suite.
*
* @param suiteName Test Suite name
* @param suite the Test Suite to add all unit tests to
* @param mar a collection of Marshalling Unit Tests
*/
private void setUpMarshallingTests(final String suiteName, final TestSuite suite,
final MarshallingTest mar) {
for (int i = 0; i < mar.getUnitTestCaseCount(); ++i) {
UnitTestCase tc = mar.getUnitTestCase(i);
MarshallingFrameworkTestCase mftc = new MarshallingFrameworkTestCase(this, tc, mar);
mftc._configuration = mar.getConfiguration();
mftc.setTestSuiteName(suiteName);
suite.addTest(mftc.suite());
}
}
/**
* Loops over all Source Generation tests from our TestDescriptor.xml,
* configures each test and adds it to our suite.
*
* @param suiteName Test Suite name
* @param suite the Test Suite to add all unit tests to
* @param sg a collection of Source Generation Unit Tests
*/
private void setUpSourceGeneratorTests(final String suiteName, final TestSuite suite,
final SourceGeneratorTest sg) {
for (int i = 0; i < sg.getUnitTestCaseCount(); ++i) {
UnitTestCase tc = sg.getUnitTestCase(i);
SourceGeneratorTestCase sgtc = new SourceGeneratorTestCase(this, tc, sg);
sgtc.setTestSuiteName(suiteName);
suite.addTest(sgtc.suite());
}
}
/**
* Loops over all Schema tests from our TestDescriptor.xml, configures each
* test and adds it to our suite.
*
* @param suiteName Test Suite name
* @param suite the Test Suite to add all unit tests to
* @param schemaTest a collection of Schema Unit Tests
*/
private void setUpSchemaTests(final String suiteName, final TestSuite suite,
final SchemaTest schemaTest) {
for (int i = 0; i < schemaTest.getUnitTestCaseCount(); i++) {
UnitTestCase tc = schemaTest.getUnitTestCase(i);
// Little trick: getUnitTestCaseChoice should not be null at this point
String name = tc.getUnitTestCaseChoice().getSchema();
if (name.equals("*")) {
File[] list = _testFile.listFiles();
for (int j = 0; j < list.length; ++j) {
String fileName = list[j].getName();
// FIXME: It would be better to use a file filter and to make
// sure our SchemaReader can read this file
if (fileName.endsWith(FileServices.XSD)) {
makeIndividualSchemaTest(suiteName, suite, tc, fileName);
}
}
} else {
makeIndividualSchemaTest(suiteName, suite, tc, name);
}
}
}
/**
* Loops over all Only-Source-Generation tests from our TestDescriptor.xml,
* configures each test and adds it to our suite.
*
* @param suiteName Test Suite name
* @param suite the Test Suite to add all unit tests to
* @param sg a collection of Source Generation Unit Tests
*/
private void setUpGenerationOnlyTests(final String suiteName, final TestSuite suite,
final OnlySourceGenerationTest sg) {
for (int i = 0; i < sg.getUnitTestCaseCount(); ++i) {
UnitTestCase tc = sg.getUnitTestCase(i);
OnlySourceGenerationTestCase sgtc = new OnlySourceGenerationTestCase(this, tc, sg);
sgtc.setTestSuiteName(suiteName);
suite.addTest(sgtc.suite());
}
}
/**
* Makes an individual Schema test and adds it to our Test Suite.
*
* @param suiteName Test Suite name
* @param suite the Test Suite to add all unit tests to
* @param tc our Test Case
* @param name Schema name
*/
private void makeIndividualSchemaTest(final String suiteName, final TestSuite suite,
final UnitTestCase tc, final String name) {
tc.setName(suiteName + '#' + name);
SchemaTestCase stc = new SchemaTestCase(this, tc);
stc.setSchemaName(name);
suite.addTest(stc);
}
/**
* Prints the provided message if verbose is true.
*
* @param message The message to display if verbose is true.
*/
private void verbose(final String message) {
if (VERBOSE) {
System.out.println(message);
}
}
}