Skip to content

Commit

Permalink
Report deadlocks when running in sharedthreadpool
Browse files Browse the repository at this point in the history
Closes #3028
  • Loading branch information
krmahadevan committed Apr 29, 2024
1 parent 43af2cf commit 459000b
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Current (7.11.0)
Fixed: GITHUB-3028: Execution stalls when using "use-global-thread-pool" (Krishnan Mahadevan)

7.10.2
Fixed: GITHUB-3117: ListenerComparator doesn't work (Krishnan Mahadevan)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.testng;

/** Represents a dead lock condition in TestNG wherein execution can get stalled. */
public class TestNGDeadLockException extends TestNGException {
public TestNGDeadLockException(String string) {
super(string);
}
}
38 changes: 35 additions & 3 deletions testng-core/src/main/java/org/testng/TestRunner.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import org.testng.util.TimeUtils;
import org.testng.xml.XmlClass;
import org.testng.xml.XmlPackage;
import org.testng.xml.XmlSuite;
import org.testng.xml.XmlTest;

/** This class takes care of running one Test. */
Expand Down Expand Up @@ -807,11 +808,42 @@ public List<IWorker<ITestNGMethod>> createWorkers(List<ITestNGMethod> methods) {
.testContext(this)
.listeners(this.m_classListeners.values())
.build();
return AbstractParallelWorker.newWorker(
m_xmlTest.getParallel(), m_xmlTest.getGroupByInstances())
.createWorkers(args);
List<IWorker<ITestNGMethod>> result =
AbstractParallelWorker.newWorker(m_xmlTest.getParallel(), m_xmlTest.getGroupByInstances())
.createWorkers(args);
long dataDrivenTestCount =
result.stream()
.flatMap(it -> it.getTasks().stream())
.filter(ITestNGMethod::isDataDriven)
.count();
int threads = getCurrentXmlTest().getThreadCount();
XmlSuite.ParallelMode parallelMode = getCurrentXmlTest().getParallel();
XmlSuite suite = getSuite().getXmlSuite();
if (suite.useGlobalThreadPool()
&& parallelModes.contains(parallelMode)
&& dataDrivenTestCount >= threads) {
String msg =
"[Deadlock condition detected] "
+ "Cannot run "
+ dataDrivenTestCount
+ " data driven tests on just "
+ threads
+ " threads when "
+ "using common thread pool. "
+ "Please increase the number of threads to at-least "
+ (dataDrivenTestCount + 1)
+ ".";
throw new TestNGDeadLockException(msg);
}
return result;
}

private static final List<XmlSuite.ParallelMode> parallelModes =
List.of(
XmlSuite.ParallelMode.METHODS,
XmlSuite.ParallelMode.INSTANCES,
XmlSuite.ParallelMode.CLASSES);

private void afterRun() {
// invoke @AfterTest
ITestNGMethod[] testConfigurationMethods = getAfterTestConfigurationMethods();
Expand Down
26 changes: 26 additions & 0 deletions testng-core/src/test/java/test/thread/SharedThreadPoolTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@
import java.util.Queue;
import java.util.stream.Collectors;
import org.testng.TestNG;
import org.testng.TestNGDeadLockException;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.testng.xml.XmlSuite;
import test.SimpleBaseTest;
import test.thread.issue2019.TestClassSample;
import test.thread.issue3028.AnotherDataDrivenTestSample;
import test.thread.issue3028.DataDrivenTestSample;
import test.thread.issue3028.FactoryPoweredDataDrivenTestSample;

public class SharedThreadPoolTest extends SimpleBaseTest {

Expand Down Expand Up @@ -68,6 +73,27 @@ public void ensureCommonThreadPoolIsNotUsedWhenUsedWithSuiteFiles() {
.hasSizeBetween(4, 10);
}

@Test(expectedExceptions = TestNGDeadLockException.class, dataProvider = "modes")
public void ensureDeadLocksAreDetectedForDataDrivenTestsRunningInParallel(
XmlSuite.ParallelMode mode, Class<?>... classes) {
TestNG testng = create(classes);
testng.shouldUseGlobalThreadPool(true);
testng.setParallel(mode);
testng.setThreadCount(2);
testng.run();
}

@DataProvider(name = "modes")
public Object[][] parallelModes() {
return new Object[][] {
{XmlSuite.ParallelMode.METHODS, DataDrivenTestSample.class},
{XmlSuite.ParallelMode.INSTANCES, FactoryPoweredDataDrivenTestSample.class},
{
XmlSuite.ParallelMode.CLASSES, DataDrivenTestSample.class, AnotherDataDrivenTestSample.class
},
};
}

private static List<Long> runSimpleTest(String suite) {
TestNG testng = create();
testng.setTestSuites(Collections.singletonList(suite));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package test.thread.issue3028;

import org.testng.ITestResult;
import org.testng.Reporter;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class AnotherDataDrivenTestSample {

@Test(dataProvider = "dp")
public void a(int ignored) {
log();
}

@Test(dataProvider = "dp")
public void b(int ignored) {
log();
}

private void log() {
ITestResult itr = Reporter.getCurrentTestResult();
long id = Thread.currentThread().getId();
Reporter.log("Running " + itr.toString() + " on Thread " + id);
}

@DataProvider(name = "dp", parallel = true)
public Object[][] getData() {
return new Object[][] {{1}, {2}, {3}, {4}, {5}};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package test.thread.issue3028;

import org.testng.ITestResult;
import org.testng.Reporter;
import org.testng.annotations.*;

public class DataDrivenTestSample {

@Test(dataProvider = "dp")
public void a(int ignored) {
log();
}

@Test(dataProvider = "dp")
public void b(int ignored) {
log();
}

private void log() {
ITestResult itr = Reporter.getCurrentTestResult();
long id = Thread.currentThread().getId();
Reporter.log("Running " + itr.toString() + " on Thread " + id);
}

@DataProvider(name = "dp", parallel = true)
public Object[][] getData() {
return new Object[][] {{1}, {2}, {3}, {4}, {5}};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package test.thread.issue3028;

import org.testng.ITestResult;
import org.testng.Reporter;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;

public class FactoryPoweredDataDrivenTestSample {

@Factory
public static Object[] objects() {
return new Object[] {
new FactoryPoweredDataDrivenTestSample(), new FactoryPoweredDataDrivenTestSample()
};
}

@Test(dataProvider = "dp")
public void a(int ignored) {
log();
}

@Test(dataProvider = "dp")
public void b(int ignored) {
log();
}

private void log() {
ITestResult itr = Reporter.getCurrentTestResult();
long id = Thread.currentThread().getId();
Reporter.log("Running " + itr.toString() + " on Thread " + id);
}

@DataProvider(name = "dp", parallel = true)
public Object[][] getData() {
return new Object[][] {{1}, {2}, {3}, {4}, {5}};
}
}

0 comments on commit 459000b

Please sign in to comment.