Intermittently getting ParameterResolutionException for JUnit5 parameterized tests running in parallel

huangapple go评论118阅读模式
英文:

Intermittently getting ParameterResolutionException for JUnit5 parameterized tests running in parallel

问题

我正在编写参数化测试,其中测试类和方法并行运行。以下是配置。

junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.mode.classes.default = concurrent
junit.jupiter.execution.parallel.config.dynamic.factor = 2

测试代码段如下 -

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

@ExtendWith(SimpleCallbackListener.class)
public class HelloWorldTest {
    private static final Map<String, List<TestData>> testDataMap;

    static {
        testDataMap = TestsConfigManager.readTestConfig("test-data");
    }

    private static List<Object[]> data() {
        final List<Object[]> testDataList = prepareTestData(testDataMap);
        return testDataList;
    }

    @ParameterizedTest(name = "{index}: {0}")
    @MethodSource("data")
    @Tag("regression")
    public void testMethodName(
            final String expected,
            final String actual
    ) {
        // test code goes here
    }
}

SimpleCallbackListener 如下 -

public class SimpleCallbackListener implements BeforeAllCallback, ExtensionContext.Store.CloseableResource {
    /**
     * 防止同一例程中的多个线程的门卫
     */
    private static final Lock LOCK = new ReentrantLock();
    private static boolean started = false;
    private Instant startTime;

    @Override
    public void beforeAll(final ExtensionContext context) {
        LOCK.lock();
        try {
            if (!started) {
                startTime = Instant.now();
                requireApplicationInitializer();
                // 下面一行在根测试上下文关闭时注册回调钩子
                context.getRoot().getStore(GLOBAL).put(this.getClass().getName(), this);
                started = true;
            }
        } finally {
            LOCK.unlock();
        }
    }
}

问题

此设置导致以下间歇性异常。

org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter ... in method ...
	at java.base@17.0.7/java.util.concurrent.RecursiveAction.exec(RecursiveAction.java:194)
	at java.base@17.0.7/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
	at java.base@17.0.7/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
	at java.base@17.0.7/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
	at java.base@17.0.7/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
	at java.base@17.0.7/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

查看了以下建议的答案 -

  1. https://stackoverflow.com/questions/65934665/why-do-i-have-parameterresolutionexception-when-i-dont-even-use-parametrized-tehttps://stackoverflow.com/questions/65934665/why-do-i-have-parameterresolutionexception-when-i-dont-even-use-parametrized-te
  2. https://stackoverflow.com/questions/51867650/junit-5-no-parameterresolver-registered-for-parameter
英文:

I am writing parameterized tests, where test classes and methods are running in parallel. Below is the configuration.

junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
junit.jupiter.execution.parallel.mode.classes.default = concurrent
junit.jupiter.execution.parallel.config.dynamic.factor = 2

The tests snippet is below -

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

@ExtendWith(SimpleCallbackListener.class)
public class HelloWorldTest {
    private static final Map&lt;String, List&lt;TestData&gt;&gt; testDataMap;

    static {
        testDataMap = TestsConfigManager.readTestConfig(&quot;test-data&quot;);
    }

    private static List&lt;Object[]&gt; data() {
        final List&lt;Object[]&gt; testDataList = prepareTestData(testDataMap);
        return testDataList;
    }

    @ParameterizedTest(name = &quot;{index}: {0}&quot;)
    @MethodSource(&quot;data&quot;)
    @Tag(&quot;regression&quot;)
    public void testMethodName(
            final String expected,
            final String actual
    ) {
        // test code goes here
    }
}

SimpleCallbackListener is as below -

public class SimpleCallbackListener implements BeforeAllCallback, ExtensionContext.Store.CloseableResource {
    /**
     * gatekeeper to prevent multiple Threads within the same routine
     */
    private static final Lock LOCK = new ReentrantLock();
    private static boolean started = false;
    private Instant startTime;

    @Override
    public void beforeAll(final ExtensionContext context) {
        LOCK.lock();
        try {
            if (!started) {
                startTime = Instant.now();
                requireApplicationInitializer();
                // The following line registers a callback hook when the root test context is shut down
                context.getRoot().getStore(GLOBAL).put(this.getClass().getName(), this);
                started = true;
            }
        } finally {
            LOCK.unlock();
        }
    }
}

Question

This setup is causing below intermittent exception.

org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter ... in method ...
	at java.base@17.0.7/java.util.concurrent.RecursiveAction.exec(RecursiveAction.java:194)
	at java.base@17.0.7/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373)
	at java.base@17.0.7/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182)
	at java.base@17.0.7/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655)
	at java.base@17.0.7/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622)
	at java.base@17.0.7/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165)

Looked at answers suggested at -

  1. https://stackoverflow.com/questions/65934665/why-do-i-have-parameterresolutionexception-when-i-dont-even-use-parametrized-tehttps://stackoverflow.com/questions/65934665/why-do-i-have-parameterresolutionexception-when-i-dont-even-use-parametrized-te
  2. https://stackoverflow.com/questions/51867650/junit-5-no-parameterresolver-registered-for-parameter

答案1

得分: 0

我成功找出了根本原因。

问题出在以下方法上。

public static List<Object[]> prepareTestData() {
    final List<Object[]> testDataList = new ArrayList<>();

    testDataMap
        .entrySet()
        .parallelStream()
        .forEach(entry -> {
            final String storeAlias = entry.getKey();
            final List<TestData> testDataList = entry.getValue();
            final List<Object[]> list = testDataList
                    .stream()
                    ...
                    .map(testData -> new Object[]{someObj1, someObj2})
                    .toList();
            testDataList.addAll(list);
        });
}

在这里,我试图并发地将元素添加到 ArrayList 中。
但后来我意识到 ArrayList 不是线程安全的,因此这导致了问题。

解决方案?

我使用了 CopyOnWriteArrayList

英文:

I was able to figure out the root cause.

The issue was with the following method.

public static List&lt;Object[]&gt; prepareTestData() {
    final List&lt;Object[]&gt; testDataList = new ArrayList&lt;&gt;();
    
    testDataMap
    	.entrySet()
    	.parallelStream()
    	.forEach(entry -&gt; {
    		final String storeAlias = entry.getKey();
    		final List&lt;TestData&gt; testDataList = entry.getValue();
    		final List&lt;Object[]&gt; list = testDataList
    				.stream()
    				...
    				.map(testData -&gt; new Object[]{someObj1, someObj2})
    				.toList();
    		testDataList.addAll(list);
    	});
}

Here, I was trying to add the elements to an ArrayList concurrently.
But, then I realized that ArrayList is not thread-safe, and hence this was causing issues.

Solution?

I used CopyOnWriteArrayList.

huangapple
  • 本文由 发表于 2023年8月10日 21:46:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/76876345.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定