Skip to content

Commit

Permalink
Issue-829: Highlight multi-threading error in TestNG
Browse files Browse the repository at this point in the history
Test has a per class lifecycle model and jgiven a per method
lifecycle model. Therefore, if
a field is used by several tests it gets continuously overwritten by the scenario setup on the different threads leading to execution error. As a quick-fix, we are highlighting that this problem exists.

Signed-off-by: l-1sqared <30831153+l-1squared@users.noreply.github.com>
  • Loading branch information
l-1squared committed May 2, 2022
1 parent 7813347 commit e1a5d87
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.tngtech.jgiven.testng;

import com.tngtech.jgiven.annotation.ScenarioStage;
import com.tngtech.jgiven.exception.JGivenWrongUsageException;
import com.tngtech.jgiven.impl.util.ReflectionUtil;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.testng.ITestResult;
import org.testng.annotations.Test;

class IncompatibleMultithreadingChecker {

@SuppressWarnings("checkstyle:AbbreviationAsWordInName")
public void checkIncompatibleMultiThreading(ITestResult paramITestResult) {
Method testMethod = paramITestResult.getMethod().getConstructorOrMethod().getMethod();
boolean isMultiThreaded = isMultiThreaded(paramITestResult.getClass(), testMethod);
boolean hasInjectedStages = hasInjectedStages(paramITestResult.getClass());

if (isMultiThreaded && hasInjectedStages) {
throw new JGivenWrongUsageException("JGiven does not support using multi-threading and stage injection"
+ "in TestNG at the same time due to their different lifecycle models. "
+ "Please switch to single threaded execution or provide stages via inheriting from ScenarioTest");
}
}

private boolean isMultiThreaded(Class<?> testClass, Method testMethod) {
List<Test> testAnnotations = new ArrayList<>();
testAnnotations.addAll(Collections.singletonList(testClass.getAnnotation(Test.class)));
testAnnotations.addAll(Collections.singletonList(testMethod.getAnnotation(Test.class)));
return testAnnotations.stream().anyMatch(test -> !test.singleThreaded());
}

private boolean hasInjectedStages(Class<?> testClass) {
ThreadLocal<Boolean> hasInjectedStage = new ThreadLocal<>();
ReflectionUtil.forEachSuperClass(testClass, clazz ->
hasInjectedStage.set(Arrays.stream(clazz.getDeclaredFields())
.anyMatch(field -> field.isAnnotationPresent(ScenarioStage.class))));
return hasInjectedStage.get();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class ScenarioTestListener implements ITestListener {

@Override
public void onTestStart( ITestResult paramITestResult ) {
new IncompatibleMultithreadingChecker().checkIncompatibleMultiThreading(paramITestResult);
Object instance = paramITestResult.getInstance();

ScenarioBase scenario;
Expand Down

0 comments on commit e1a5d87

Please sign in to comment.