/*
* Copyright 2004-2015 the Seasar Foundation and the Others.
*
* 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.seasar.framework.unit;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.Filterable;
import org.junit.runner.manipulation.NoTestsRemainException;
import org.junit.runner.manipulation.Sortable;
import org.junit.runner.manipulation.Sorter;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.seasar.framework.container.annotation.tiger.Binding;
import org.seasar.framework.container.annotation.tiger.BindingType;
import org.seasar.framework.unit.impl.ConventionTestIntrospector;
import org.seasar.framework.util.tiger.ReflectionUtil;
/**
* テストクラスに定義されたすべてのテストメソッドを扱うランナーです。
*
* @author taedium
*/
public class S2TestClassMethodsRunner extends Runner implements Filterable,
Sortable {
private static class FailedBefore extends Exception {
private static final long serialVersionUID = 1L;
}
/** {@link S2TestClassMethodsRunner}の振る舞いを提供するプロバイダ */
protected static Provider provider;
/** テストメソッドのリスト */
protected final List<Method> testMethods;
/** テストクラス */
protected final Class<?> testClass;
/**
* インスタンスを構築します。
*
* @param clazz
* テストクラス
*/
public S2TestClassMethodsRunner(final Class<?> clazz) {
testClass = clazz;
testMethods = getTestMethods();
}
/**
* このクラスを破棄します。
*/
public static void dispose() {
provider = null;
}
@Override
public void run(final RunNotifier notifier) {
try {
runBefores(notifier);
runMethods(notifier);
} catch (final FailedBefore e) {
} finally {
runAfters(notifier);
}
}
/**
* テストメソッド群を実行します。
*
* @param notifier
* ノティファイアー
*/
protected void runMethods(final RunNotifier notifier) {
if (testMethods.isEmpty()) {
RunNotifierCompatibility.testAborted(notifier,
getDescription(), new Exception("No runnable methods"));
}
for (final Method method : testMethods) {
invokeTestMethod(method, notifier);
}
}
/**
* テストクラスの初期化メソッドを実行します。
*
* @param notifier
* ノティファイアー
* @throws FailedBefore
* 何らかの例外が発生した場合
*/
protected void runBefores(final RunNotifier notifier) throws FailedBefore {
try {
final List<Method> befores = getBeforeClassMethods();
for (final Method before : befores)
before.invoke(null);
} catch (final InvocationTargetException e) {
addFailure(e.getTargetException(), notifier);
throw new FailedBefore();
} catch (final Throwable e) {
addFailure(e, notifier);
throw new FailedBefore();
}
}
/**
* テストクラスの解放メソッドを実行します。
*
* @param notifier
* ノティフィアー
*/
protected void runAfters(final RunNotifier notifier) {
final List<Method> afters = getAfterClassMethods();
for (final Method after : afters)
try {
after.invoke(null);
} catch (final InvocationTargetException e) {
addFailure(e.getTargetException(), notifier);
} catch (final Throwable e) {
addFailure(e, notifier);
}
}
/**
* テストの失敗を登録します。
*
* @param targetException
* 例外
* @param notifier
* ノティフィアー
*/
protected void addFailure(final Throwable targetException,
final RunNotifier notifier) {
final Failure failure = new Failure(getDescription(), targetException);
notifier.fireTestFailure(failure);
}
/**
* テストメソッドのリストを返します。
*
* @return テストメソッドのリスト
*/
protected List<Method> getTestMethods() {
return getProvider().getTestMethods(testClass);
}
/**
* テストクラスの初期化メソッドのリストを返します。
*
* @return 初期化メソッドのリスト
*/
protected List<Method> getBeforeClassMethods() {
return getProvider().getBeforeClassMethods(testClass);
}
/**
* テストクラスの解放メソッドのリストを返します。
*
* @return 解放メソッドのリスト
*/
protected List<Method> getAfterClassMethods() {
return getProvider().getAfterClassMethods(testClass);
}
@Override
public Description getDescription() {
final Description spec = Description.createSuiteDescription(getName());
for (final Method method : testMethods)
spec.addChild(methodDescription(method));
return spec;
}
/**
* テストクラスの名前を返します。
*
* @return テストクラスの名前
*/
protected String getName() {
return getTestClass().getName();
}
/**
* テストクラスのインスタンスを作成します。
*
* @return テストクラスのインスタンス
* @throws Exception
* 何らかの例外が発生した場合
*/
protected Object createTest() throws Exception {
return getTestClass().getConstructor().newInstance();
}
/**
* テストメソッドを実行します。
*
* @param method
* テストメソッド
* @param notifier
* ノティファイアー
*/
protected void invokeTestMethod(final Method method,
final RunNotifier notifier) {
Object test = null;
try {
test = createTest();
} catch (final InvocationTargetException e) {
RunNotifierCompatibility.testAborted(notifier,
methodDescription(method), e.getCause());
return;
} catch (final Exception e) {
RunNotifierCompatibility.testAborted(notifier,
methodDescription(method), e);
return;
}
createMethodRunner(test, method, notifier).run();
}
/**
* テストメソッドランナーを作成します。
*
* @param test
* テスト
* @param method
* テストメソッド
* @param notifier
* ノティファイアー
* @return テストメソッドランナー
*/
protected S2TestMethodRunner createMethodRunner(final Object test,
final Method method, RunNotifier notifier) {
return getProvider().createMethodRunner(test, method, notifier,
methodDescription(method));
}
/**
* テストメソッドの名前を返します。
*
* @param method
* テストメソッド
* @return テストメソッドの名前
*/
protected String testName(final Method method) {
return method.getName();
}
/**
* テストのディスクリプションを返します。
*
* @param method
* テストメソッド
* @return ディスクリプション
*/
protected Description methodDescription(final Method method) {
return Description.createTestDescription(getTestClass(),
testName(method));
}
public void filter(final Filter filter) throws NoTestsRemainException {
for (final Iterator<Method> iter = testMethods.iterator(); iter
.hasNext();) {
final Method method = iter.next();
if (!filter.shouldRun(methodDescription(method))) {
iter.remove();
}
}
if (testMethods.isEmpty()) {
throw new NoTestsRemainException();
}
}
public void sort(final Sorter sorter) {
Collections.sort(testMethods, new Comparator<Method>() {
public int compare(final Method o1, final Method o2) {
return sorter.compare(methodDescription(o1),
methodDescription(o2));
}
});
}
/**
* テストクラスを返します。
*
* @return テストクラス
*/
protected Class<?> getTestClass() {
return testClass;
}
/**
* {@link S2TestClassMethodsRunner}の振る舞いを提供するプロバイダを返します。
*
* @return 振る舞いを提供するプロバイダ
*/
protected static Provider getProvider() {
if (provider == null) {
provider = new DefaultProvider();
}
return provider;
}
/**
* {@link S2TestClassMethodsRunner}の振る舞いを提供するプロバイダを設定します。
*
* @param p
* 振る舞いを提供するプロバイダ
*/
protected static void setProvider(final Provider p) {
provider = p;
}
/**
* {@link S2TestClassMethodsRunner}の振る舞いを提供します。
*
* @author taedium
*/
public interface Provider {
/**
* テストメソッドのリストを返します。
*
* @param clazz
* テストクラス
* @return テストメソッドのリスト
*/
List<Method> getTestMethods(Class<?> clazz);
/**
* テストクラスの初期化メソッドのリストを返します。
*
* @param clazz
* テストクラス
* @return 初期化メソッド
*/
List<Method> getBeforeClassMethods(Class<?> clazz);
/**
* テストクラスの解放メソッドのリストを返します。
*
* @param clazz
* テストクラス
* @return 解放メソッドのリスト
*/
List<Method> getAfterClassMethods(Class<?> clazz);
/**
* テストメソッドランナーを作成します。
*
* @param test
* テストクラスのインスタンス
* @param method
* テストメソッド
* @param notifier
* ノティファイアー
* @param description
* ディスクリプション
* @return テストメソッドランナー
*/
S2TestMethodRunner createMethodRunner(Object test, Method method,
RunNotifier notifier, Description description);
}
/**
* {@link S2TestClassMethodsRunner}の振る舞いを提供するデフォルトの実装クラスです。
*
* @author taedium
*/
public static class DefaultProvider implements Provider {
/** テストクラスのイントロスペクター */
protected S2TestIntrospector introspector;
/** メソッドランナー */
protected Class<? extends S2TestMethodRunner> methodRunnerClass;
/** メソッドランナーのコンストラクタ */
protected Constructor<? extends S2TestMethodRunner> constructor;
/**
* インスタンスを構築します。
*/
public DefaultProvider() {
final ConventionTestIntrospector conventionIntrospector = new ConventionTestIntrospector();
conventionIntrospector.init();
this.introspector = conventionIntrospector;
setTestMethodRunnerClass(S2TestMethodRunner.class);
}
/**
* テストクラスのイントロスペクターを設定します。
*
* @param introspector
* イントロスペクター
*/
@Binding(bindingType = BindingType.MAY)
public void setTestIntrospector(final S2TestIntrospector introspector) {
this.introspector = introspector;
}
/**
* メソッドランナーのクラスを設定します。
*
* @param methodRunnerClass
* メソッドランナーのクラス
*/
@Binding(bindingType = BindingType.MAY)
public void setTestMethodRunnerClass(
final Class<? extends S2TestMethodRunner> methodRunnerClass) {
this.methodRunnerClass = methodRunnerClass;
this.constructor = ReflectionUtil.getConstructor(methodRunnerClass,
Object.class, Method.class, RunNotifier.class,
Description.class, S2TestIntrospector.class);
}
public List<Method> getTestMethods(final Class<?> clazz) {
return introspector.getTestMethods(clazz);
}
public List<Method> getBeforeClassMethods(final Class<?> clazz) {
return introspector.getBeforeClassMethods(clazz);
}
public List<Method> getAfterClassMethods(final Class<?> clazz) {
return introspector.getAfterClassMethods(clazz);
}
public S2TestMethodRunner createMethodRunner(final Object test,
final Method method, final RunNotifier notifier,
final Description description) {
return ReflectionUtil.newInstance(constructor, test, method,
notifier, description, introspector);
}
}
}