blob: 40fdbc43fcbce0d3222d9c455d2ddf1d9cf948d5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.deltaspike.testcontrol.api.junit;
import org.apache.deltaspike.cdise.api.CdiContainer;
import org.apache.deltaspike.cdise.api.CdiContainerLoader;
import org.apache.deltaspike.core.api.config.ConfigResolver;
import org.apache.deltaspike.core.api.config.PropertyLoader;
import org.apache.deltaspike.core.spi.filter.ClassFilter;
import org.apache.deltaspike.core.spi.activation.Deactivatable;
import org.apache.deltaspike.core.spi.config.ConfigSource;
import org.apache.deltaspike.core.util.ClassDeactivationUtils;
import org.apache.deltaspike.testcontrol.api.TestControl;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.Suite;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.RunnerBuilder;
import javax.inject.Named;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.lang.Boolean.TRUE;
@SuppressWarnings("UnusedDeclaration")
public class CdiTestSuiteRunner extends Suite
{
/**
* Configuration key to define a custom configuration properties file.
* e.g.:
* deltaspike.testcontrol.test-container.config-file=META-INF/dsTestContainerBootConfig.properties
*/
public static final String CUSTOM_TEST_CONTAINER_CONFIG_FILE_KEY =
"deltaspike.testcontrol.test-container.config-file";
/**
* Default resource location of the property file which gets used
* for the container bootstrap.
* This value can be overridden by using {@link #CUSTOM_TEST_CONTAINER_CONFIG_FILE_KEY}
*/
public static final String DEFAULT_TEST_CONTAINER_CONFIG_FILE_NAME =
"META-INF/apache-deltaspike_test-container";
private static final boolean STOP_CONTAINER;
private static final ThreadLocal<Boolean> IS_CDI_TEST_RUNNER_EXECUTION = new ThreadLocal<Boolean>();
private static volatile boolean containerStarted; //TODO
private final Class<?> testSuiteClass;
static
{
STOP_CONTAINER = TestBaseConfig.ContainerIntegration.STOP_CONTAINER;
}
public CdiTestSuiteRunner(Class<?> klass, RunnerBuilder builder) throws InitializationError
{
super(klass, builder);
this.testSuiteClass = klass;
}
protected CdiTestSuiteRunner(Class<?> klass, Class<?>[] suiteClasses) throws InitializationError
{
super(klass, suiteClasses);
this.testSuiteClass = klass;
}
protected CdiTestSuiteRunner(RunnerBuilder builder, Class<?> klass, Class<?>[] suiteClasses)
throws InitializationError
{
super(builder, klass, suiteClasses);
this.testSuiteClass = klass;
}
protected CdiTestSuiteRunner(Class<?> klass, List<Runner> runners) throws InitializationError
{
super(klass, runners);
this.testSuiteClass = klass;
}
@Override
public void run(RunNotifier notifier)
{
if (this.testSuiteClass == null)
{
throw new IllegalStateException("no test-suite class found");
}
CdiContainer container = CdiContainerLoader.getCdiContainer();
if (!containerStarted)
{
applyTestSpecificMetaData(getTestClass().getJavaClass());
container.boot(getTestContainerConfig());
containerStarted = true;
}
notifier.addListener(new LogRunListener());
try
{
super.run(notifier);
}
finally
{
if (STOP_CONTAINER)
{
container.shutdown();
containerStarted = false;
}
}
}
public static boolean isContainerStarted()
{
return containerStarted;
}
static Boolean isStopContainerAllowed()
{
return STOP_CONTAINER;
}
static ThreadLocal<Boolean> getCdiTestRunnerExecutionRef()
{
return IS_CDI_TEST_RUNNER_EXECUTION;
}
static void setContainerStarted(boolean containerStarted)
{
CdiTestSuiteRunner.containerStarted = containerStarted;
}
static class LogRunListener extends RunListener
{
private final Logger logger = Logger.getLogger(LogRunListener.class.getName());
LogRunListener()
{
}
@Override
public void testStarted(Description description) throws Exception
{
Level level = this.logger.getLevel();
this.logger.setLevel(Level.INFO);
if (TRUE.equals(IS_CDI_TEST_RUNNER_EXECUTION.get()))
{
this.logger.info("[run] " + description.getClassName() + "#" + description.getMethodName());
}
try
{
super.testRunStarted(description);
}
finally
{
this.logger.setLevel(level);
}
}
@Override
public void testFinished(Description description) throws Exception
{
Level level = this.logger.getLevel();
this.logger.setLevel(Level.INFO);
if (TRUE.equals(IS_CDI_TEST_RUNNER_EXECUTION.get()))
{
this.logger.info("[finished] " + description.getClassName() + "#" + description.getMethodName());
}
try
{
super.testFinished(description);
}
finally
{
this.logger.setLevel(level);
}
}
@Override
public void testFailure(Failure failure) throws Exception
{
Level level = this.logger.getLevel();
this.logger.setLevel(Level.INFO);
if (TRUE.equals(IS_CDI_TEST_RUNNER_EXECUTION.get()))
{
Description description = failure.getDescription();
this.logger.info("[failed] " + description.getClassName() + "#" + description.getMethodName() +
" message: " + failure.getMessage());
}
try
{
super.testFailure(failure);
}
finally
{
this.logger.setLevel(level);
}
}
}
public static Properties getTestContainerConfig()
{
String cdiTestRunnerConfig = ConfigResolver.getProjectStageAwarePropertyValue(
CUSTOM_TEST_CONTAINER_CONFIG_FILE_KEY, DEFAULT_TEST_CONTAINER_CONFIG_FILE_NAME);
return PropertyLoader.getProperties(cdiTestRunnerConfig);
}
//just here, because all shared methods are in this class
static void applyTestSpecificMetaData(Class<?> currentAnnotationSource)
{
TestControl testControl = currentAnnotationSource.getAnnotation(TestControl.class);
String activeAlternativeLabel = checkForLabeledAlternativeConfig(testControl);
initTestEnvConfig(currentAnnotationSource, activeAlternativeLabel, testControl);
}
private static String checkForLabeledAlternativeConfig(TestControl testControl)
{
String activeAlternativeLabel = "";
if (testControl != null)
{
Class<? extends TestControl.Label> activeTypedAlternativeLabel =
testControl.activeAlternativeLabel();
if (!TestControl.Label.class.equals(activeTypedAlternativeLabel))
{
Named labelName = activeTypedAlternativeLabel.getAnnotation(Named.class);
if (labelName != null)
{
activeAlternativeLabel = labelName.value();
}
else
{
String labelClassName = activeTypedAlternativeLabel.getSimpleName();
activeAlternativeLabel = labelClassName.substring(0, 1).toLowerCase();
if (labelClassName.length() > 1)
{
activeAlternativeLabel += labelClassName.substring(1);
}
}
}
}
return activeAlternativeLabel;
}
private static void initTestEnvConfig(Class<?> testClass, String activeAlternativeLabel, TestControl testControl)
{
if (ClassDeactivationUtils.isActivated(TestConfigSource.class))
{
TestConfigSource testConfigSource = null;
for (ConfigSource configSource : ConfigResolver.getConfigSources())
{
if (configSource instanceof TestConfigSource)
{
//if it happens: parallel test-execution can't be supported with labeled alternatives
testConfigSource = (TestConfigSource) configSource;
}
}
if (testConfigSource == null)
{
testConfigSource = new TestConfigSource();
ConfigResolver.addConfigSources(Arrays.<ConfigSource>asList(testConfigSource));
}
//always set it even if it is empty (it might overrule the value of the prev. test
testConfigSource.getProperties().put("activeAlternativeLabel", activeAlternativeLabel);
testConfigSource.getProperties().put("activeAlternativeLabelSource", testClass.getName());
if (testControl != null)
{
testConfigSource.getProperties().put(TestControl.class.getName(), testClass.getName());
testConfigSource.getProperties().put(ClassFilter.class.getName(), testControl.classFilter().getName());
}
else
{
//reset it to avoid leaks between tests
testConfigSource.getProperties().put(TestControl.class.getName(), TestControl.class.getName());
testConfigSource.getProperties().put(ClassFilter.class.getName(), ClassFilter.class.getName());
}
}
else
{
//always set it even if it is empty (it might overrule the value of the prev. test
System.setProperty("activeAlternativeLabel", activeAlternativeLabel); //will be picked up by ds-core
System.setProperty("activeAlternativeLabelSource", testClass.getName()); //can be used for custom logic
if (testControl != null) //can be used for custom logic
{
System.setProperty(TestControl.class.getName(), testClass.getName());
System.setProperty(ClassFilter.class.getName(), testControl.classFilter().getName());
}
else
{
//reset it to avoid leaks between tests
System.setProperty(TestControl.class.getName(), TestControl.class.getName());
System.setProperty(ClassFilter.class.getName(), ClassFilter.class.getName());
}
}
}
//config-sources are already stored per classloader
//keep it public to allow type-safe deactivation (if needed)
public static class TestConfigSource implements ConfigSource, Deactivatable
{
private Map<String, String> testConfig = new ConcurrentHashMap<String, String>();
@Override
public int getOrdinal()
{
return Integer.MIN_VALUE;
}
@Override
public Map<String, String> getProperties()
{
return testConfig;
}
@Override
public String getPropertyValue(String key)
{
return testConfig.get(key);
}
@Override
public String getConfigName()
{
return "ds-test-config";
}
@Override
public boolean isScannable()
{
return true;
}
}
}