Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IHookable and IConfigurable callback discrepancy #2713

Merged
merged 1 commit into from Jan 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.txt
@@ -1,6 +1,7 @@
Current
7.6.0
Fixed: GITHUB-2709: Testnames not working together with suites in suite (Martin Aldrin)
Fixed: GITHUB-2704: IHookable and IConfigurable callback discrepancy (Krishnan Mahadevan)
Fixed: GITHUB-2637: Upgrade to JDK11 as the minimum JDK requirements(Krishnan Mahadevan)

7.5
Expand Down
@@ -0,0 +1,25 @@
package org.testng;

/**
* Represents an exception that is thrown when a configuration method is not invoked. One of the
* use-cases when this can happen is when the user does the following:
*
* <ul>
* <li>User defines a configuration method
* <li>The class that houses the configuration method defines support for callbacks via {@link
* IConfigurable} implementation
* <li>User willfully skips invoking the callback and also fails at altering the configuration
* method's status via {@link ITestResult#setStatus(int)}
* </ul>
*/
public class ConfigurationNotInvokedException extends TestNGException {

public ConfigurationNotInvokedException(ITestNGMethod tm) {
super(
tm.getQualifiedName()
+ " defines a callback via "
+ IConfigurable.class.getName()
+ " but neither the callback was invoked nor the status was altered to "
+ String.join("|", ITestResult.finalStatuses()));
}
}
22 changes: 22 additions & 0 deletions testng-core-api/src/main/java/org/testng/ITestResult.java
@@ -1,5 +1,6 @@
package org.testng;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.testng.internal.thread.ThreadTimeoutException;
Expand Down Expand Up @@ -114,6 +115,27 @@ default List<ITestNGMethod> getSkipCausedBy() {
*/
String id();

/**
* @return - <code>true</code> if the current test result is either {@link ITestResult#STARTED} or
* {@link ITestResult#CREATED}
*/
default boolean isNotRunning() {
return getStatus() == STARTED || getStatus() == CREATED;
}

/**
* @return - A list of all user facing statuses viz.,
* <ul>
* <li>{@link ITestResult#SUCCESS}
* <li>{@link ITestResult#SUCCESS_PERCENTAGE_FAILURE}
* <li>{@link ITestResult#FAILURE}
* <li>{@link ITestResult#SKIP}
* </ul>
*/
static List<String> finalStatuses() {
return Arrays.asList("SUCCESS", "FAILURE", "SKIP", "SUCCESS_PERCENTAGE_FAILURE");
}

/**
* @param result - The test result of a method
* @return - <code>true</code> if the test failure was due to a timeout.
Expand Down
@@ -0,0 +1,24 @@
package org.testng;

/**
* Represents an exception that is thrown when a test method is not invoked. One of the use-cases
* when this can happen is when the user does the following:
*
* <ul>
* <li>User defines a test method
* <li>The class that houses the test method defines support for callbacks via {@link IHookable}
* implementation
* <li>User willfully skips invoking the callback and also fails at altering the test method's
* status via {@link ITestResult#setStatus(int)}
* </ul>
*/
public class TestNotInvokedException extends TestNGException {
public TestNotInvokedException(ITestNGMethod tm) {
super(
tm.getQualifiedName()
+ " defines a callback via "
+ IHookable.class.getName()
+ " but neither the callback was invoked nor the status was altered to "
+ String.join("|", ITestResult.finalStatuses()));
}
}
Expand Up @@ -484,11 +484,8 @@ private static MatchResults matchMethod(ITestNGMethod[] methods, String regexp)
String thisMethodName = thisMethod.getName();
String methodName = usePackage ? calculateMethodCanonicalName(method) : thisMethodName;
Pair<String, String> cacheKey = Pair.create(regexp, methodName);
Boolean match = MATCH_CACHE.get(cacheKey);
if (match == null) {
match = pattern.matcher(methodName).matches();
MATCH_CACHE.put(cacheKey, match);
}
boolean match =
MATCH_CACHE.computeIfAbsent(cacheKey, key -> pattern.matcher(methodName).matches());
if (match) {
results.matchedMethods.add(method);
results.foundAtLeastAMethod = true;
Expand Down
Expand Up @@ -11,6 +11,7 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.testng.ConfigurationNotInvokedException;
import org.testng.IClass;
import org.testng.IConfigurable;
import org.testng.IInvokedMethodListener;
Expand Down Expand Up @@ -374,15 +375,24 @@ private void invokeConfigurationMethod(
testResult.setStatus(ITestResult.SUCCESS);
return;
}
if (configurableInstance != null) {
MethodInvocationHelper.invokeConfigurable(
targetInstance, params, configurableInstance, method.getMethod(), testResult);
boolean willfullyIgnored = false;
boolean usesConfigurableInstance = configurableInstance != null;
if (usesConfigurableInstance) {
willfullyIgnored =
!MethodInvocationHelper.invokeConfigurable(
targetInstance, params, configurableInstance, method.getMethod(), testResult);
} else {
MethodInvocationHelper.invokeMethodConsideringTimeout(
tm, method, targetInstance, params, testResult);
}
boolean testStatusRemainedUnchanged = testResult.isNotRunning();
if (usesConfigurableInstance && willfullyIgnored && testStatusRemainedUnchanged) {
throw new ConfigurationNotInvokedException(tm);
}
testResult.setStatus(ITestResult.SUCCESS);
} catch (InvocationTargetException | IllegalAccessException ex) {
} catch (ConfigurationNotInvokedException
| InvocationTargetException
| IllegalAccessException ex) {
throwConfigurationFailure(testResult, ex);
testResult.setStatus(ITestResult.FAILURE);
throw ex;
krmahadevan marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
@@ -1,13 +1,14 @@
package org.testng.internal.invokers;

import java.util.Optional;
import java.util.concurrent.Callable;
import org.testng.IHookable;
import org.testng.ITestNGMethod;
import org.testng.ITestResult;
import org.testng.internal.ConstructorOrMethod;

/** A Runnable Method invoker. */
public class InvokeMethodRunnable implements Callable<Void> {
public class InvokeMethodRunnable implements Callable<Boolean> {
private final ITestNGMethod m_method;
private final Object m_instance;
private final Object[] m_parameters;
Expand All @@ -27,51 +28,54 @@ public InvokeMethodRunnable(
m_testResult = testResult;
}

public void run() throws TestNGRuntimeException {
public boolean run() throws TestNGRuntimeException {
try {
call();
return call();
} catch (Exception e) {
throw new TestNGRuntimeException(e);
}
}

private void runOne() {
private boolean runOne() {
boolean invoked;
try {
RuntimeException t = null;
try {
ConstructorOrMethod m = m_method.getConstructorOrMethod();
if (m_hookable == null) {
invoked = true;
MethodInvocationHelper.invokeMethod(m.getMethod(), m_instance, m_parameters);
} else {
MethodInvocationHelper.invokeHookable(
m_instance, m_parameters, m_hookable, m.getMethod(), m_testResult);
invoked =
MethodInvocationHelper.invokeHookable(
m_instance, m_parameters, m_hookable, m.getMethod(), m_testResult);
}
} catch (Throwable e) {
Throwable cause = e.getCause();
if (cause == null) {
cause = e;
}
invoked = true;
Throwable cause = Optional.ofNullable(e.getCause()).orElse(e);
t = new TestNGRuntimeException(cause);
}
if (null != t) {
Thread.currentThread().interrupt();
throw t;
}
return invoked;
} finally {
m_method.incrementCurrentInvocationCount();
}
}

@Override
public Void call() throws Exception {
public Boolean call() throws Exception {
boolean flag = true;
if (m_method.getInvocationTimeOut() > 0) {
for (int i = 0; i < m_method.getInvocationCount(); i++) {
runOne();
flag = flag && runOne();
}
} else {
runOne();
flag = runOne();
}
return null;
return flag;
}

public static class TestNGRuntimeException extends RuntimeException {
Expand Down