/*
* Copyright 2002-2004 The Apache Software Foundation
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied.
*
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.avalon.excalibur.testcase;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import junit.framework.TestResult;
import org.apache.avalon.excalibur.component.DefaultRoleManager;
import org.apache.avalon.excalibur.component.ExcaliburComponentManager;
import org.apache.avalon.excalibur.logger.DefaultLogKitManager;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.component.Component;
import org.apache.avalon.framework.component.ComponentException;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.context.DefaultContext;
import org.apache.avalon.framework.logger.LogKitLogger;
import org.apache.log.Hierarchy;
import org.apache.log.LogTarget;
import org.apache.log.Logger;
import org.apache.log.Priority;
import org.apache.log.format.PatternFormatter;
import org.apache.log.output.io.StreamTarget;
/**
* JUnit TestCase for Avalon Components.
* <p>
* This class extends the JUnit TestCase class to setup an environment which
* makes it possible to easily test Avalon Components. The following methods
* and instance variables are exposed for convenience testing:
* </p>
* <dl>
* <dt>manager</dt>
* <dd>
* This instance variable contains an initialized ComponentLocator which
* can be used to lookup Components configured in the test configuration
* file. (see below)
* </dd>
* <dt>getLogger()</dt>
* <dd>
* This method returns the default logger for this test case
* </dd>
* </dl>
* <p>
* The following test case configuration can be used as a basis for new tests.
* Detailed are explanations of the configuration elements can be found after
* the example. The example will log all logger output to the console and to
* a log file.
* </p>
* <pre>
* <testcase>
* <annotation>
* <![CDATA[
* <title>{Name of test}</title>
* <para>
* {Description of test}
* The configuration is specified in the file located in
* <parameter>avalon-excalibur/src/test/{path and name of conf file}.xtext</parameter>.
* </para>
* ]]>
* </annotation>
*
* <logkit log-level="INFO">
* <factories>
* <factory type="stream" class="org.apache.avalon.excalibur.logger.factory.StreamTargetFactory"/>
* <factory type="file" class="org.apache.avalon.excalibur.logger.factory.FileTargetFactory"/>
* </factories>
*
* <targets>
* <stream id="console">
* <stream>System.out</stream>
* <format type="avalon">
* %7.7{priority} %23.23{time:yyyy-MM-dd' 'HH:mm:ss.SSS} [%30.30{category}] (%{context}): %{message}\n%{throwable}
* </format>
* </stream>
* <file id="log-file">
* <filename>TEST-{full test class name}.log</filename>
* <format type="avalon">
* %7.7{priority} %23.23{time:yyyy-MM-dd' 'HH:mm:ss.SSS} [%30.30{category}] (%{context}): %{message}\n%{throwable}
* </format>
* </file>
* </targets>
*
* <categories>
* <category name="test" log-level="INFO">
* <log-target id-ref="console"/>
* <log-target id-ref="log-file"/>
* </category>
* <category name="jdbc" log-level="INFO">
* <log-target id-ref="console"/>
* <log-target id-ref="log-file"/>
* </category>
* </categories>
* </logkit>
*
* <context>
* <entry name="foo" value="bar"/>
* <entry name="baz" class="my.context.Class"/>
* </context>
*
* <roles>
* <role name="org.apache.avalon.excalibur.datasource.DataSourceComponentSelector"
* shorthand="datasources"
* default-class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector">
* <hint shorthand="jdbc" class="org.apache.avalon.excalibur.datasource.JdbcDataSource"/>
* </role>
* </roles>
*
* <components>
* <datasources>
* <jdbc name="personell" logger="jdbc">
* <pool-controller min="5" max="10"/>
* <jdbc name="personnel"/>
* <dburl>jdbc:odbc:test</dburl>
* <user>test</user>
* <password>test</password>
* <driver>sun.jdbc.odbc.JdbcOdbcDriver</driver>
* </jdbc>
* </datasources>
* </components>
* </testcase>
* </pre>
* <p>
* Element Explanation:
* <dl>
* <dt>testcase</dt>
* <dd>Defines a test case configuration. Must contain one each of the
* following elements: <code>annotation</code>, <code>logkit</code>,
* <code>context</code>, <code>roles</code>, and <code>components</code>
* </dd>.
*
* <dt>annotation</dt>
* <dd>Defines a test annotation. This element should define a block of
* XML enclosed within a CDATA element. The XML should be made up of a
* <code>title</code> element, naming the test, and a <code>para</code>
* element which is used to describe the test.</dd>
*
* <dt>logkit</dt>
* <dd>Configures the logger used by the test cases and the components used
* by the tests. The <code>logkit</code> element takes two optional
* attributes:
* <dl>
* <dt>logger</dt><dd>Uses to name the logger which is used to bootstrap
* the LogKit logger. (Defaults to <code>"lm"</code>)</dd>
* <dt>log-level</dt><dd>Because the logger used by the LogKit must be
* created before the Log Kit Manager is initialized, it must be fully
* configured before the <code>logkit</code> element is parsed. This
* attribute allows the Log Kit's log priority to be set. This log
* level will also become the default for the Role Manager, Component
* Manager, and all components if they do not have <code>category</code>
* elements declated in the <code>logkit</code> element.
* (Defaults to "INFO")</dd>
* </dl>
* The loggers used by test cases and components can be easily configured
* from within this file. The default test configuration, shown above,
* includes a "test" category. This category is used to configure the
* default logger for all test cases. If it is set to "DEBUG", then all
* test debug logging will be enabled. To enalble debug logging for a
* single test case, a child category must be defined for the
* "testCheckTotals" test case as follows:
* <pre>
* <categories>
* <category name="test" log-level="INFO">
* <log-target id-ref="console"/>
* <log-target id-ref="log-file"/>
*
* <category name="testCheckTotals" log-level="DEBUG">
* <log-target id-ref="console"/>
* <log-target id-ref="log-file"/>
* </category>
* </category>
* </categories>
* </pre>
* For general information on how to configure the LogKit Manager, please
* refer to the Log Kit documentation.
* </dd>
*
* <dt>context</dt>
* <dd>Allows context properties to be set in the context passed to any
* Contextualizable components.</dd>
*
* <dt>roles</dt>
* <dd>Roles configuration for the Components configured in the
* <code>components</code> element. The logger used by the RoleManager
* can be configured using a <code>logger</code> attribute, which defaults
* to "rm". By default this logger will have the same log level and
* formatting as the LogKit logger. It can be configured by adding a
* <code>category</code> within the <code>logkit</code> element.</dd>
*
* <dt>components</dt>
* <dd>Used to configure any Components used by the test cases. The logger
* used by the ComponentLocator can be configured using a <code>logger</code>
* attribute, which defaults to "cm". By default this logger will have the
* same log level and formatting as the LogKit logger. It can be configured
* by adding a <code>category</code> within the <code>logkit</code> element.
* </dd>
*
* </dl>
*
* @deprecated ECM is no longer supported
*
* @author <a href="mailto:dev@avalon.apache.org">Avalon Development Team</a>
* @version $Id: ExcaliburTestCase.java,v 1.6 2004/02/28 11:47:27 cziegeler Exp $
*/
public class ExcaliburTestCase
extends TestCase
{
///Format of default formatter
private static final String FORMAT =
"%7.7{priority} %23.23{time:yyyy-MM-dd' 'HH:mm:ss.SSS} [%30.30{category}] (%{context}): %{message}\n%{throwable}";
//The default logger
private Logger m_logger;
private LogKitLogger m_logEnabledLogger;
private ExcaliburComponentManager m_manager;
private DefaultLogKitManager m_logKitManager;
private static HashMap m_tests = new HashMap();
protected ComponentManager manager;
public ExcaliburTestCase( final String name )
{
super( name );
ArrayList methodList = (ArrayList)ExcaliburTestCase.m_tests.get( getClass() );
if( null == methodList )
{
Method[] methods = getClass().getMethods();
methodList = new ArrayList( methods.length );
for( int i = 0; i < methods.length; i++ )
{
String methodName = methods[ i ].getName();
if( methodName.startsWith( "test" ) &&
( Modifier.isPublic( methods[ i ].getModifiers() ) ) &&
( methods[ i ].getReturnType().equals( Void.TYPE ) ) &&
( methods[ i ].getParameterTypes().length == 0 ) )
{
methodList.add( methodName );
}
}
ExcaliburTestCase.m_tests.put( getClass(), methodList );
}
}
/** Return the logger */
protected Logger getLogger()
{
return m_logger;
}
/** Return the logger */
protected LogKitLogger getLogEnabledLogger()
{
return m_logEnabledLogger;
}
/**
* Initializes the ComponentLocator
*
* The configuration file is determined by the class name plus .xtest appended,
* all '.' replaced by '/' and loaded as a resource via classpath
*/
protected void prepare()
throws Exception
{
final String resourceName = getClass().getName().replace( '.', '/' ) + ".xtest";
URL resource = getClass().getClassLoader().getResource( resourceName );
if( resource != null )
{
getLogger().debug( "Loading resource " + resourceName );
prepare( resource.openStream() );
}
else
{
getLogger().debug( "Resource not found " + resourceName );
}
}
/**
* Initializes the ComponentLocator
*
* @param testconf The configuration file is passed as a <code>InputStream</code>
*
* A common way to supply a InputStream is to overwrite the initialize() method
* in the sub class, do there whatever is needed to get the right InputStream object
* supplying a conformant xtest configuartion and pass it to this initialize method.
* the mentioned initialize method is also the place to set a different logging priority
* to the member variable m_logPriority.
*/
protected final void prepare( final InputStream testconf )
throws Exception
{
getLogger().debug( "ExcaliburTestCase.initialize" );
final DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
final Configuration conf = builder.build( testconf );
String annotation = conf.getChild( "annotation" ).getValue( null );
if( ( null != annotation ) && !( "".equals( annotation ) ) )
{
m_logger.info( annotation );
}
Context context = setupContext( conf.getChild( "context" ) );
setupManagers( conf.getChild( "components" ),
conf.getChild( "roles" ),
conf.getChild( "logkit" ),
context );
manager = m_manager;
setCurrentLogger( "prepare" );
}
/**
* Disposes the <code>ComponentLocator</code>
*/
final private void done()
{
if( null != m_manager )
{
m_manager.dispose();
}
m_manager = null;
}
/**
* Exctract the base class name of a class.
*/
private String getBaseClassName( Class clazz )
{
String name = clazz.getName();
int pos = name.lastIndexOf( '.' );
if( pos >= 0 )
{
name = name.substring( pos + 1 );
}
return name;
}
/**
* Override <code>run</code> so that we can have code that is run once.
*/
final public void run( TestResult result )
{
ArrayList methodList = (ArrayList)ExcaliburTestCase.m_tests.get( getClass() );
if( null == methodList || methodList.isEmpty() )
{
return; // The test was already run! NOTE: this is a hack.
}
// Set the logger for the initialization phase.
setCurrentLogger( getBaseClassName( getClass() ) );
try
{
if( this instanceof Initializable )
{
( (Initializable)this ).initialize();
}
prepare();
Iterator tests = methodList.iterator();
while( tests.hasNext() )
{
String methodName = (String)tests.next();
setName( methodName );
setCurrentLogger( methodName );
if( getLogger().isDebugEnabled() )
{
getLogger().debug( "" );
getLogger().debug( "========================================" );
getLogger().debug( " begin test: " + methodName );
getLogger().debug( "========================================" );
}
super.run( result );
if( getLogger().isDebugEnabled() )
{
getLogger().debug( "========================================" );
getLogger().debug( " end test: " + methodName );
getLogger().debug( "========================================" );
getLogger().debug( "" );
}
}
}
catch( Exception e )
{
System.out.println( e );
e.printStackTrace();
result.addError( this, e );
}
finally
{
done();
if( this instanceof Disposable )
{
try
{
( (Disposable)this ).dispose();
}
catch( Exception e )
{
result.addFailure( this, new AssertionFailedError( "Disposal Error" ) );
}
}
}
methodList.clear();
ExcaliburTestCase.m_tests.put( getClass(), methodList );
}
/**
* Sets the logger which will be returned by getLogger and getLogEnabledLogger
*/
final private void setCurrentLogger( String name )
{
org.apache.log.Logger logger;
if( m_logKitManager == null )
{
// Logger for the portion of the configuration has been loaded.
logger = Hierarchy.getDefaultHierarchy().getLoggerFor( name );
logger.setPriority( Priority.INFO );
PatternFormatter formatter = new PatternFormatter( FORMAT );
StreamTarget target = new StreamTarget( System.out, formatter );
logger.setLogTargets( new LogTarget[]{target} );
}
else
{
logger = m_logKitManager.getLogger( "test." + name );
}
/*
//FIXME(GP): This method should setup a LogConfigurator and LogManager
// according to the configuration spec. not yet completed/implemented
// It will return a default logger for now.
final org.apache.log.Logger logger = Hierarchy.getDefaultHierarchy().getLoggerFor( name );
logger.setPriority( m_logPriority );
final PatternFormatter formatter = new PatternFormatter( FORMAT );
final StreamTarget target = new StreamTarget( System.out, formatter );
logger.setLogTargets( new LogTarget[] { target } );
*/
m_logger = logger;
m_logEnabledLogger = new LogKitLogger( m_logger );
}
/**
* set up a context according to the xtest configuration specifications context
* element.
*
* A method addContext(DefaultContext context) is called here to enable subclasses
* to put additional objects into the context programmatically.
*/
final private Context setupContext( final Configuration conf )
throws Exception
{
//FIXME(GP): This method should setup the Context object according to the
// configuration spec. not yet completed
final DefaultContext context = new DefaultContext();
final Configuration[] confs = conf.getChildren( "entry" );
for( int i = 0; i < confs.length; i++ )
{
final String key = confs[ i ].getAttribute( "name" );
final String value = confs[ i ].getAttribute( "value", null );
if( value == null )
{
String clazz = confs[ i ].getAttribute( "class" );
Object obj = getClass().getClassLoader().loadClass( clazz ).newInstance();
context.put( key, obj );
if( getLogger().isInfoEnabled() )
getLogger().info( "ExcaliburTestCase: added an instance of class " + clazz + " to context entry " + key );
}
else
{
context.put( key, value );
if( getLogger().isInfoEnabled() )
getLogger().info( "ExcaliburTestCase: added value \"" + value + "\" to context entry " + key );
}
}
addContext( context );
return ( context );
}
/**
* This method may be overwritten by subclasses to put additional objects
* into the context programmatically.
*/
protected void addContext( DefaultContext context )
{
}
final private void setupManagers( final Configuration confCM,
final Configuration confRM,
final Configuration confLM,
final Context context )
throws Exception
{
// Setup the log manager. Get the logger name and log level from attributes
// in the <logkit> node
String lmLoggerName = confLM.getAttribute( "logger", "lm" );
String lmLogLevel = confLM.getAttribute( "log-level", "INFO" );
Priority lmPriority = Priority.getPriorityForName( lmLogLevel );
DefaultLogKitManager logKitManager = new DefaultLogKitManager();
Logger lmLogger = Hierarchy.getDefaultHierarchy().getLoggerFor( lmLoggerName );
lmLogger.setPriority( lmPriority );
logKitManager.enableLogging( new LogKitLogger( lmLogger ) );
logKitManager.contextualize( context );
logKitManager.configure( confLM );
Hierarchy h = logKitManager.getHierarchy();
h.setDefaultPriority( lmPriority );
m_logKitManager = logKitManager;
// Setup the RoleManager
String rmLoggerName = confRM.getAttribute( "logger", "rm" );
DefaultRoleManager roleManager = new DefaultRoleManager();
roleManager.setLogger( logKitManager.getLogger( rmLoggerName ) );
roleManager.configure( confRM );
// Set up the ComponentLocator
String cmLoggerName = confCM.getAttribute( "logger", "cm" );
ExcaliburComponentManager ecManager = new ExcaliburComponentManager();
ecManager.setLogger( logKitManager.getLogger( cmLoggerName ) );
ecManager.setLogKitManager( logKitManager );
ecManager.contextualize( context );
ecManager.setRoleManager( roleManager );
ecManager.configure( confCM );
ecManager.initialize();
m_manager = ecManager;
}
protected final Object lookup( final String key )
throws ComponentException
{
return manager.lookup( key );
}
protected final void release( final Component object )
{
manager.release( object );
}
}