/*
* JBoss, Home of Professional Open Source
* Copyright 2010-2016, Red Hat, Inc. and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.richfaces.tests.configurator.unstable;
import static java.text.MessageFormat.format;
import java.lang.reflect.Method;
import java.util.List;
import org.richfaces.tests.configurator.unstable.annotation.Unstable;
import org.richfaces.tests.utils.ReflectionUtils;
import org.testng.IHookCallBack;
import org.testng.ITestResult;
import org.testng.annotations.BeforeMethod;
/**
* Configurator is used to create proxy for {@link org.testng.IHookCallBack IHookCallBack} interface to modify the test
* execution behavior. For marked tests it will re-run the execution until the first success occurs or the maximum number of
* retry attempts is reached (defined in {@link org.richfaces.tests.configurator.unstable.annotation.Unstable @Unstable}).
* <br/>
* How to use:<br/>
* 1) override <code>run</code> method from Arquillian in your test base:<br/>
* <code>@Override public void run(final IHookCallBack callBack, final ITestResult testResult) {</code><br/>
* <code> super.run(UnstableTestConfigurator.getGuardedCallback(callBack), testResult);</code><br/>
* <code>}</code><br/>
* 2) mark the unstable test method/class with annotation
* {@link org.richfaces.tests.configurator.unstable.annotation.Unstable @Unstable}<br/>
*
* @author <a href="mailto:jstefek@redhat.com">Jiri Stefek</a>
*/
public class UnstableTestConfigurator {
/**
* Returns unstable-guarded test callback.
*
* @param callBack
* @return
*/
public static IHookCallBack getGuardedCallback(IHookCallBack callBack) {
return new UnstableGuardedHookCallBackProxy(callBack);
}
private static class UnstableGuardedHookCallBackProxy implements IHookCallBack {
private static final String ARQUILLIAN_GROUP = "arquillian";
private static final String CONFIGURE_METHOD_NAME = "configure";
private final IHookCallBack callBack;
private UnstableGuardedHookCallBackProxy(IHookCallBack callBack) {
this.callBack = callBack;
}
@Override
public Object[] getParameters() {
return callBack.getParameters();
}
private boolean groupInBeforeMethodIsNotInArquillianGroup(Method m) {
for (String group : m.getAnnotation(BeforeMethod.class).groups()) {
if (group.equals(ARQUILLIAN_GROUP)) {
return false;
}
}
return true;
}
private void invokeBeforeMethods(ITestResult testResult) {
List<Method> allMethodsAnnotatedWith = ReflectionUtils.getAllMethodsAnnotatedWith(BeforeMethod.class, testResult.getInstance());
// TODO: this could not work as expected when there are depending methods
for (Method m : allMethodsAnnotatedWith) {
// invoke all before methods except method 'configure' and methods in 'arquillian' group
// TODO: this could not work as expected when not invoking the configuration step
if (!m.getName().equals(CONFIGURE_METHOD_NAME) && groupInBeforeMethodIsNotInArquillianGroup(m)) {
try {
m.invoke(testResult.getInstance());
} catch (Throwable t) {
throw new RuntimeException("Was not able to invoke @BeforeMethod with name " + m.getName(), t);
}
}
}
}
@Override
public void runTestMethod(ITestResult testResult) {
Method m = testResult.getMethod().getConstructorOrMethod().getMethod();
Unstable annotationOnMethod = m.getAnnotation(Unstable.class);
Unstable annotationOnTestClass = testResult.getInstance().getClass().getAnnotation(Unstable.class);
Unstable annotation = (annotationOnMethod != null ? annotationOnMethod : annotationOnTestClass);
if (annotation != null) {// is annotation present?
runUnstableMethod(m, annotation, testResult);
} else {// no annotation >>> call the test method normally
callBack.runTestMethod(testResult);
}
}
/**
* Invokes test method repeatedly until maximum number of attempts or first success is reached
*
* @param callBack
* @param testResult
*/
private void runUnstableMethod(Method m, Unstable u, ITestResult testResult) {
int retryAttempts = u.retryAttempts();
boolean firstRun = true;
// try to invoke the test method for the number of attempts specified in annotation
for (int i = 1; i <= retryAttempts; i++) {
try {
if (!firstRun) {
System.err.println(format("Trying to invoke the test method {0}, attempt #{1}.", m.getName(), i));
// invoke before methods
invokeBeforeMethods(testResult);
}
// invoke test
m.invoke(testResult.getInstance());
// success >>> set test status to SUCCESS
setSuccessTestResult(testResult);
return;
} catch (Throwable t) {
firstRun = false;
if (i == retryAttempts) {
// no more retry attempts >>> set test status to FAILED
setFailedTestResult(testResult, t);
return;
}
}
}
}
private void setFailedTestResult(ITestResult testResult, Throwable t) {
setTestResult(testResult, ITestResult.FAILURE, t);
}
private void setSuccessTestResult(ITestResult testResult) {
setTestResult(testResult, ITestResult.SUCCESS, null);
}
private void setTestResult(ITestResult testResult, int status, Throwable t) {
testResult.setEndMillis(System.currentTimeMillis());
testResult.setThrowable(t);
testResult.setStatus(status);
}
}
}