/**
* Copyright (C) 2008 Mathieu Carbou <mathieu.carbou@gmail.com>
*
* 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 com.mycila.testing.core;
import com.mycila.log.Logger;
import com.mycila.log.Loggers;
import com.mycila.log.jdk.format.ClassFormatter;
import com.mycila.log.jdk.handler.StderrHandler;
import com.mycila.log.jdk.handler.StdoutHandler;
import com.mycila.log.jdk.hook.AsyncInvocationHandler;
import com.mycila.plugin.spi.PluginManager;
import com.mycila.testing.core.annot.ConfigureMycilaPlugins;
import com.mycila.testing.core.annot.MycilaPlugins;
import com.mycila.testing.core.api.Cache;
import static com.mycila.testing.core.api.Ensure.*;
import com.mycila.testing.core.api.TestNotifier;
import com.mycila.testing.core.introspect.Filter;
import static com.mycila.testing.core.introspect.Filters.*;
import com.mycila.testing.core.introspect.Introspector;
import com.mycila.testing.core.plugin.TestPlugin;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.StreamHandler;
/**
* @author Mathieu Carbou (mathieu.carbou@gmail.com)
*/
public final class MycilaTesting {
public static final String DEFAULT_PLUGIN_DESCRIPTOR = "META-INF/mycila/testing/plugins.properties";
private static final Logger LOGGER = Loggers.get(MycilaTesting.class);
private static final Map<String, MycilaTesting> instances = new HashMap<String, MycilaTesting>();
private static MycilaTesting customTestHandler;
private final PluginManager<TestPlugin> pluginManager;
private MycilaTesting() {
LOGGER.debug("Creating new empty plugin manager");
pluginManager = new PluginManager<TestPlugin>(TestPlugin.class);
}
private MycilaTesting(String descriptor) {
LOGGER.debug("Creating new plugin manager from descriptor %s", descriptor);
pluginManager = new PluginManager<TestPlugin>(TestPlugin.class, descriptor);
}
public PluginManager<TestPlugin> pluginManager() {
return pluginManager;
}
public TestNotifier createNotifier(Object testInstance) {
notNull("Test instance", testInstance);
return new TestContextImpl(pluginManager, testInstance);
}
/**
* Configure the Plugin manager of this MycilaTesting from the given class. It will search for all methods
* annotated by {@link com.mycila.testing.core.annot.ConfigureMycilaPlugins}s and call those having the
* {@link com.mycila.plugin.spi.PluginManager} as a parameter:
* {@code @ConfigureMycilaPlugins void configure(PluginManager<TestPlugin> pluginManager) {...} }
*
* @param testInstance The object having configure methods
* @return this
*/
public MycilaTesting configure(Object testInstance) {
notNull("Test instance", testInstance);
final Introspector introspector = new Introspector(testInstance);
final List<Method> methods = introspector.selectMethods(excludeOverridenMethods(and(methodsAnnotatedBy(ConfigureMycilaPlugins.class), new Filter<Method>() {
@Override
protected boolean accept(Method method) {
final Class<?>[] types = method.getParameterTypes();
return types.length == 1 && types[0].equals(PluginManager.class);
}
})));
final PluginManager<TestPlugin> pluginManager = pluginManager();
for (Method method : methods) {
LOGGER.debug("Configuring plugin manager through method %s.%s...", method.getDeclaringClass().getName(), method.getName());
try {
method.invoke(testInstance, pluginManager);
} catch (IllegalAccessException e) {
throw new RuntimeException(e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getTargetException().getMessage(), e.getTargetException());
}
}
return this;
}
/**
* Get a static TestSetup with the default plugin descriptor which is {@link #DEFAULT_PLUGIN_DESCRIPTOR}
*
* @return a TestSetup instance which can be used to prepare a test with plugins.
* This instance is registered statically to avoid reloading plugins each time
*/
public static MycilaTesting staticDefaultSetup() {
return staticSetup(DEFAULT_PLUGIN_DESCRIPTOR);
}
/**
* Creates a new TestSetup the default plugin descriptor which is {@link #DEFAULT_PLUGIN_DESCRIPTOR}
*
* @return a TestSetup instance which can be used to prepare a test with plugins
*/
public static MycilaTesting newDefaultSetup() {
return newSetup(DEFAULT_PLUGIN_DESCRIPTOR);
}
/**
* Get a static TestSetup instance using a specific plugin descriptor to loads plugins.
* Default plugin descriptor is {@link #DEFAULT_PLUGIN_DESCRIPTOR}
*
* @param pluginDescriptor The plugin descriptort to use. It is a property files containing a list of plugins to load. Default plugin descriptor is
* {@link #DEFAULT_PLUGIN_DESCRIPTOR}
* @return a TestSetup instance which can be used to prepare a test with plugins.
* This instance is registered statically to avoid reloading plugins each time
*/
public static MycilaTesting staticSetup(String pluginDescriptor) {
notNull("Plugin descriptor", pluginDescriptor);
MycilaTesting testSetup = instances.get(pluginDescriptor);
if (testSetup == null) {
testSetup = newSetup(pluginDescriptor);
LOGGER.debug("Registering new shared plugins for descriptor %s", pluginDescriptor);
instances.put(pluginDescriptor, testSetup);
} else {
LOGGER.debug("Reusing shared plugins for descriptor %s", pluginDescriptor);
}
return testSetup;
}
/**
* Creates a new TestSetup instance using a specific plugin descriptor to loads plugins.
* Default plugin descriptor is {@link #DEFAULT_PLUGIN_DESCRIPTOR}
*
* @param pluginDescriptor The plugin descriptort to use. It is a property files containing a list of plugins to load. Default plugin descriptor is
* {@link #DEFAULT_PLUGIN_DESCRIPTOR}
* @return a TestSetup instance which can be used to prepare a test with plugins
*/
public static MycilaTesting newSetup(String pluginDescriptor) {
notNull("Plugin descriptor", pluginDescriptor);
return new MycilaTesting(pluginDescriptor);
}
/**
* Get a static custom TestSetup instance with an empty {@link com.mycila.plugin.spi.PluginManager}.
* It will be up to you to add your own plugins at runtime
*
* @return a TestSetup instance which can be used to prepare a test with plugins.
* This instance is registered statically to avoid reloading and recreating plugins each time.
*/
public static MycilaTesting staticCustomSetup() {
if (customTestHandler == null) {
LOGGER.debug("Registering new shared empty plugin manager");
customTestHandler = newCustomSetup();
} else {
LOGGER.debug("Reusing existing shared empty plugin manager");
}
return customTestHandler;
}
/**
* Creates a new custom TestSetup with an empty {@link com.mycila.plugin.spi.PluginManager}.
* It will be up to you to add your own plugins at runtime
*
* @return a TestSetup instance which can be used to prepare a test with plugins
*/
public static MycilaTesting newCustomSetup() {
return new MycilaTesting();
}
/**
* Get a MycilaTesting instance using the strategy defined for this class potentially annotated by
* {@link com.mycila.testing.core.annot.MycilaPlugins}.
*
* @param c test class
* @return a TestSetup instance which can be used to prepare a test with plugins
*/
public static MycilaTesting from(Class<?> c) {
notNull("Test class", c);
MycilaPlugins mycilaPlugins = c.getAnnotation(MycilaPlugins.class);
if (mycilaPlugins == null) {
mycilaPlugins = new MycilaPlugins() {
public Cache value() {
return Cache.SHARED;
}
public String descriptor() {
return DEFAULT_PLUGIN_DESCRIPTOR;
}
public Class<? extends Annotation> annotationType() {
return MycilaPlugins.class;
}
};
}
return from(mycilaPlugins);
}
/**
* Get a MycilaTesting instance using the strategy defined in the provided annotation.
*
* @param mycilaPlugins the annotation
* @return a TestSetup instance which can be used to prepare a test with plugins
*/
public static MycilaTesting from(MycilaPlugins mycilaPlugins) {
notNull("MycilaPlugins annotation", mycilaPlugins);
if (mycilaPlugins.value() == null) {
return staticDefaultSetup();
}
boolean descBlank = mycilaPlugins.descriptor() == null || mycilaPlugins.descriptor().trim().length() == 0;
switch (mycilaPlugins.value()) {
case SHARED:
return descBlank ? staticCustomSetup() : staticSetup(mycilaPlugins.descriptor());
case UNSHARED:
return descBlank ? newCustomSetup() : newSetup(mycilaPlugins.descriptor());
}
throw new AssertionError("Use case not defined for value of enum Cache: " + mycilaPlugins.value());
}
public static void debug() {
StdoutHandler stdoutHandler = new StdoutHandler();
stdoutHandler.setLevel(Level.ALL);
stdoutHandler.setMaxLevel(Level.INFO);
stdoutHandler.setFormatter(new ClassFormatter());
stdoutHandler.setHook(new AsyncInvocationHandler<StreamHandler>());
StderrHandler stderrHandler = new StderrHandler();
stderrHandler.setLevel(Level.WARNING);
stderrHandler.setMaxLevel(Level.SEVERE);
stderrHandler.setFormatter(new ClassFormatter());
stderrHandler.setHook(new AsyncInvocationHandler<StreamHandler>());
java.util.logging.Logger logger = java.util.logging.Logger.getLogger("com.mycila");
logger.setLevel(Level.ALL);
logger.addHandler(stdoutHandler);
logger.addHandler(stderrHandler);
}
}