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

How to dynamically adjust the number of TestNG threads after IExecutorFactory is deprecated? #3066

Closed
7 tasks
alex-postnov opened this issue Feb 13, 2024 · 1 comment · Fixed by #3075
Closed
7 tasks

Comments

@alex-postnov
Copy link

alex-postnov commented Feb 13, 2024

TestNG Version

Note: only the latest version is supported

7.9+

Expected behavior

Hi folks!

We are using TestNG to launch long running mobile e2e tests. One test suite run can take 30 minutes or even more. Throughout the test run, the availability of devices may fluctuate—some devices may become faulty, while others become ready to run tests. Ideally, the number of available devices should match the number of threads allocated in TestNG, ensuring that neither tests nor devices remain idle.

We aim to dynamically adjust the number of TestNG threads during test execution. Java's ThreadPoolExecutor supports this functionality through its methods setCorePoolSize() and setMaximumPoolSize(). Thus, our primary requirement is to obtain references to the ThreadPoolExecutors.

To get the references, we introduced a class that implements IExecutorFactory and stores weak references to created Executors. This class is then passed to TestNG via the -threadPoolFactoryClass parameter. And we can access the executors.

This scheme is working fine for us at the moment.

Actual behavior

However, in the latest version of TestNG IExecutorFactory is marked as deprecated and is supposed to be removed in one of the future versions.

Question: How can we achieve the same functionality in the new versions once IExecutorFactory is removed? Alternatively, do you have suggestions for a better approach to dynamically adjusting the number of running threads?

Is the issue reproducible on runner?

  • Shell
  • Maven
  • Gradle
  • Ant
  • Eclipse
  • IntelliJ
  • NetBeans

Test case sample

Please, share the test case (as small as possible) which shows the issue

Contribution guidelines

Incase you plan to raise a pull request to fix this issue, please make sure you refer our Contributing section for detailed set of steps.

Some code examples:

class ThreadPoolExecutorFactory : IExecutorFactory {
    companion object {
        val suiteExecutorInstances = mutableListOf<WeakReference<GraphThreadPoolExecutor<ISuite>>>()
        val methodExecutorInstances = mutableListOf<WeakReference<GraphThreadPoolExecutor<ITestNGMethod>>>()
    }

    override fun newSuiteExecutor(
        name: String?,
        graph: IDynamicGraph<ISuite>?,
        factory: IThreadWorkerFactory<ISuite>?,
        corePoolSize: Int,
        maximumPoolSize: Int,
        keepAliveTime: Long,
        unit: TimeUnit?,
        workQueue: BlockingQueue<Runnable>?,
        comparator: Comparator<ISuite>?
    ): ITestNGThreadPoolExecutor {
        val instance = GraphThreadPoolExecutor(
            name,
            graph,
            factory,
            corePoolSize,
            maximumPoolSize,
            keepAliveTime,
            unit,
            workQueue,
            comparator
        )
        suiteExecutorInstances.add(WeakReference(instance))
        return instance
    }

    override fun newTestMethodExecutor(
        name: String?,
        graph: IDynamicGraph<ITestNGMethod>?,
        factory: IThreadWorkerFactory<ITestNGMethod>?,
        corePoolSize: Int,
        maximumPoolSize: Int,
        keepAliveTime: Long,
        unit: TimeUnit?,
        workQueue: BlockingQueue<Runnable>?,
        comparator: Comparator<ITestNGMethod>?
    ): ITestNGThreadPoolExecutor {
        val instance = GraphThreadPoolExecutor(
            name,
            graph,
            factory,
            corePoolSize,
            maximumPoolSize,
            keepAliveTime,
            unit,
            workQueue,
            comparator
        )
        methodExecutorInstances.add(WeakReference(instance))
        return instance
    }
}
class TestNGThreadPoolSize {
    private val logger: Logger = LoggerFactory.getLogger("TestNGTheadPoolSize")

    fun currentPoolSize(): Int {
        return getMethodExecutor().poolSize
    }

    fun currentCorePoolSize(): Int {
        return getMethodExecutor().corePoolSize
    }

    fun currentMaximumPoolSize(): Int {
        return getMethodExecutor().maximumPoolSize
    }

    @Synchronized
    fun increaseByOne() {
        logger.info("Increasing TestNG thread pool size by 1")
//        Order of the following calls matters. Wrong order will result in an IllegalArgumentException
        getMethodExecutor().maximumPoolSize++
        getMethodExecutor().corePoolSize++
    }

    @Synchronized
    fun decreaseByOne() {
        logger.info("Decreasing TestNG thread pool size by 1")
//        Order of the following calls matters. Wrong order will result in an IllegalArgumentException
        getMethodExecutor().corePoolSize -= 1
        getMethodExecutor().maximumPoolSize -= 1
    }

    private fun getMethodExecutor(): GraphThreadPoolExecutor<ITestNGMethod> {
        val runningExecutors = ThreadPoolExecutorFactory.methodExecutorInstances
            .mapNotNull { it.get() }
            .filterNot { it.isTerminated }

//        currently we only allow 1 suite to be run at a time so only one method executor should be in Running state
        val runningExecutorsCount = runningExecutors.size
        if (runningExecutorsCount != 1) {
            throw CountMismatchException(
                "Unexpected amount of running thread pool executor instances: $runningExecutorsCount. Should be 1."
            )
        }

        return runningExecutors.first()
    }
}
krmahadevan added a commit to krmahadevan/testng that referenced this issue Feb 20, 2024
Closes testng-team#3066

Following should be done.

* Implement `org.testng.IExecutorServiceFactory`
* plugin the fully qualified class name of the 
above implementation via the configuration parameter
"-threadpoolfactoryclass"

If using Maven surefire plugin then it can be wired
in as below: 

<configuration>
  <properties>
      <property>
          <name>threadpoolfactoryclass</name>
          <value>test.thread.MyExecutorServiceFactory</value>
      </property>
  </properties>
</configuration>

If using TestNG API, then it can be wired in as 
below:

TestNG testng = new TestNG();
testng.setExecutorServiceFactory(new MyExecutorServiceFactory());
@krmahadevan krmahadevan added this to the 7.10.0 milestone Feb 20, 2024
@krmahadevan
Copy link
Member

@alex-postnov - Please check the PR and share your observations. A minimal user facing documentation is available in the commit message.

krmahadevan added a commit to krmahadevan/testng that referenced this issue Feb 20, 2024
Closes testng-team#3066

Following should be done.

* Implement `org.testng.IExecutorServiceFactory`
* plugin the fully qualified class name of the 
above implementation via the configuration parameter
"-threadpoolfactoryclass"

If using Maven surefire plugin then it can be wired
in as below: 

<configuration>
  <properties>
      <property>
          <name>threadpoolfactoryclass</name>
          <value>test.thread.MyExecutorServiceFactory</value>
      </property>
  </properties>
</configuration>

If using TestNG API, then it can be wired in as 
below:

TestNG testng = new TestNG();
testng.setExecutorServiceFactory(new MyExecutorServiceFactory());
krmahadevan added a commit to krmahadevan/testng that referenced this issue Feb 21, 2024
Closes testng-team#3066

Following should be done.

* Implement `org.testng.IExecutorServiceFactory`
* plugin the fully qualified class name of the 
above implementation via the configuration parameter
"-threadpoolfactoryclass"

If using Maven surefire plugin then it can be wired
in as below: 

<configuration>
  <properties>
      <property>
          <name>threadpoolfactoryclass</name>
          <value>test.thread.MyExecutorServiceFactory</value>
      </property>
  </properties>
</configuration>

If using TestNG API, then it can be wired in as 
below:

TestNG testng = new TestNG();
testng.setExecutorServiceFactory(new MyExecutorServiceFactory());
krmahadevan added a commit that referenced this issue Feb 22, 2024
* Allow custom thread pool executors to be wired in.

Closes #3066

Following should be done.

* Implement `org.testng.IExecutorServiceFactory`
* plugin the fully qualified class name of the 
above implementation via the configuration parameter
"-threadpoolfactoryclass"

If using Maven surefire plugin then it can be wired
in as below: 

<configuration>
  <properties>
      <property>
          <name>threadpoolfactoryclass</name>
          <value>test.thread.MyExecutorServiceFactory</value>
      </property>
  </properties>
</configuration>

If using TestNG API, then it can be wired in as 
below:

```java
TestNG testng = new TestNG();
testng.setExecutorServiceFactory(new MyExecutorServiceFactory());
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants