/*
* $Id: AbstractMuleTestCase.java 22017 2011-05-27 20:28:27Z pablo.kraan $
* --------------------------------------------------------------------------------------
* Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com
*
* The software in this package is published under the terms of the CPAL v1.0
* license, a copy of which has been included with this distribution in the
* LICENSE.txt file.
*/
package org.mule.tck.junit4;
import org.mule.RequestContext;
import org.mule.tck.junit4.rule.WarningTimeout;
import org.mule.util.ClassUtils;
import org.mule.util.IOUtils;
import org.mule.util.MuleUrlStreamHandlerFactory;
import org.mule.util.StringMessageUtils;
import org.mule.util.StringUtils;
import org.mule.util.SystemUtils;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.MethodRule;
import org.junit.rules.TestName;
import org.junit.rules.Timeout;
import static org.junit.Assume.assumeThat;
/**
* <code>AbstractMuleTestCase</code> is a base class for Mule test cases. This
* implementation provides services to test code for creating mock and test
* objects.
*/
public abstract class AbstractMuleTestCase
{
public static final int DEFAULT_TEST_TIMEOUT_SECS = 60;
public static final String TEST_TIMEOUT_SYSTEM_PROPERTY = "mule.test.timeoutSecs";
/**
* Indicates whether the text boxes will be logged when starting each test case.
*/
private static final boolean verbose;
/**
* Indicates if the current test class was excluded using the mule test
* exclusion files. Test are executed sequentially, so is not required to
* maintain a list of classes.
*/
private static Boolean excluded = null;
static
{
String muleOpts = SystemUtils.getenv("MULE_TEST_OPTS");
if (StringUtils.isNotBlank(muleOpts))
{
Map<String, String> parsedOpts = SystemUtils.parsePropertyDefinitions(muleOpts);
String optVerbose = parsedOpts.get("mule.verbose");
verbose = Boolean.valueOf(optVerbose);
}
else
{
verbose = true;
}
// register the custom UrlStreamHandlerFactory.
MuleUrlStreamHandlerFactory.installUrlStreamHandlerFactory();
}
protected final transient Log logger = LogFactory.getLog(this.getClass());
/**
* Should be set to a string message describing any prerequisites not met.
*/
private boolean offline = "true".equalsIgnoreCase(System.getProperty("org.mule.offline"));
private int testTimeoutSecs = getTimeoutSystemProperty();
@Rule
public TestName name = new TestName();
@Rule
public MethodRule globalTimeout = createTestTimeoutRule();
public AbstractMuleTestCase()
{
if (excluded == null)
{
excluded = isTestIncludedInExclusionFile(this);
}
}
/**
* Creates the timeout rule that will be used to run the test.
*
* @return the rule used to check for test execution timeouts.
*/
protected MethodRule createTestTimeoutRule()
{
int millisecondsTimeout = getTestTimeoutSecs() * 1000;
if (isFailOnTimeout())
{
return new Timeout(millisecondsTimeout);
}
else
{
return new WarningTimeout(millisecondsTimeout);
}
}
/**
* Reads the mule-exclusion file for the current test class and
* @param test
*/
protected boolean isTestIncludedInExclusionFile(AbstractMuleTestCase test)
{
boolean result = false;
final String name = test.getClass().getName();
try
{
// We find the physical classpath root URL of the test class and
// use that to find the correct resource. Works fine everywhere,
// regardless of classloaders. See MULE-2414
URL classUrl = ClassUtils.getClassPathRoot(test.getClass());
URLClassLoader tempClassLoader = new URLClassLoader(new URL[] {classUrl});
URL fileUrl = tempClassLoader.getResource("mule-test-exclusions.txt");
if (fileUrl != null)
{
InputStream in = null;
try
{
in = fileUrl.openStream();
// this iterates over all lines in the exclusion file
Iterator<?> lines = IOUtils.lineIterator(in, "UTF-8");
// ..and this finds non-comments that match the test case name
result = IteratorUtils.filteredIterator(lines, new Predicate()
{
public boolean evaluate(Object object)
{
return StringUtils.equals(name, StringUtils.trimToEmpty((String) object));
}
}).hasNext();
}
finally
{
IOUtils.closeQuietly(in);
}
}
}
catch (IOException ioex)
{
// ignore
}
return result;
}
/**
* Defines the number of seconds that a test has in order to run before
* throwing a timeout. If the property if not defined then uses the
* <code>DEFAULT_MULE_TEST_TIMEOUT_SECS</code> constant.
*
* @return the timeout value expressed in seconds
*/
protected int getTimeoutSystemProperty()
{
String timeoutString = System.getProperty(TEST_TIMEOUT_SYSTEM_PROPERTY, null);
if (timeoutString == null)
{
// unix style: MULE_TEST_TIMEOUTSECS
String variableName = TEST_TIMEOUT_SYSTEM_PROPERTY.toUpperCase().replace(".", "_");
timeoutString = System.getenv(variableName);
}
int result = DEFAULT_TEST_TIMEOUT_SECS;
if (timeoutString != null)
{
try
{
result = Integer.parseInt(timeoutString);
}
catch (NumberFormatException e)
{
// Uses the default value
}
}
return result;
}
/**
* Subclasses can override this method to skip the execution of the entire test class.
*
* @return <code>true</code> if the test class should not be run.
*/
protected boolean isDisabledInThisEnvironment()
{
return false;
}
/**
* Indicates whether this test has been explicitly disabled through the configuration
* file loaded by TestInfo.
*
* @return whether the test has been explicitly disabled
*/
protected boolean isExcluded()
{
return excluded;
}
/**
* Should this test run?
*
* @param testMethodName name of the test method
* @return whether the test should execute in the current environment
*/
protected boolean isDisabledInThisEnvironment(String testMethodName)
{
return false;
}
public boolean isOffline(String method)
{
if (offline)
{
logger.warn(StringMessageUtils.getBoilerPlate(
"Working offline cannot run test: " + method, '=', 80));
}
return offline;
}
/**
* Defines the timeout in seconds that will be used to run the test.
*
* @return the timeout in seconds
*/
public int getTestTimeoutSecs()
{
return testTimeoutSecs;
}
@Before
public final void initializeMuleTest()
{
printTestHeader();
skipTestWhenExcluded();
skipTestWhenDisabledInCurrentEnvironment();
}
private void printTestHeader()
{
if (verbose)
{
System.out.println(StringMessageUtils.getBoilerPlate("Testing: " + name.getMethodName(), '=', 80));
}
}
private void skipTestWhenExcluded()
{
assumeThat(this, new BaseMatcher<AbstractMuleTestCase>()
{
public boolean matches(Object o)
{
return !isExcluded();
}
public void describeTo(Description description)
{
description.appendText("Test " + name.getMethodName() + " is excluded");
}
});
}
private void skipTestWhenDisabledInCurrentEnvironment()
{
assumeThat(this, new BaseMatcher<AbstractMuleTestCase>()
{
public boolean matches(Object o)
{
return !(isDisabledInThisEnvironment() || isDisabledInThisEnvironment(name.getMethodName()));
}
public void describeTo(Description description)
{
description.appendText("Test " + name.getMethodName() + " disabled in this environment");
}
});
}
/**
* Indicates whether the test should fail when a timeout is reached.
* <p/>
* This feature was added to support old test cases that depend on 3rd-party
* resources such as a public web service. In such cases it may be desirable
* to not fail the test upon timeout but rather to simply log a warning.
*
* @return true if it must fail on timeout and false otherwise. Default value
* is true.
*/
protected boolean isFailOnTimeout()
{
return true;
}
@After
public final void clearRequestContext()
{
RequestContext.clear();
}
@AfterClass
public static final void clearExcludedFlag()
{
excluded = null;
}
}