From 7b6a0cac2f50694f37e64eb6ccf8e4eb1528fbd5 Mon Sep 17 00:00:00 2001 From: Balaji Dubey Date: Tue, 1 Jul 2025 21:56:23 +0530 Subject: [PATCH 1/2] Add BaseEngineParSeqTest.java to make test suite run in parallel with no race conditions --- CHANGELOG.md | 4 + build.gradle | 7 +- .../linkedin/parseq/BaseEngineParSeqTest.java | 155 ++++++++++++++++++ 3 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 subprojects/parseq-test-api/src/main/java/com/linkedin/parseq/BaseEngineParSeqTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index dbd757bb..a6ea8af1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +v5.1.21 +------ +* Add a new base class for tests called BaseEngineParSeqTest.java to allow test suite execution in parallel (with no race conditions). + v5.1.20 ------ * Upgrade bytebuddy and asm version for JDK 17 and JDK 21 support diff --git a/build.gradle b/build.gradle index 4dcec893..06d5c96f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,10 @@ buildscript { repositories { - jcenter() + mavenCentral() + gradlePluginPortal() } dependencies { - classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.21.0' + classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.23.4' classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.4' } } @@ -153,4 +154,4 @@ subprojects { p -> project.tasks.uploadArchives.doFirst { logger.lifecycle "Cleaning local ivy repo: $rootDir/build/ivy-repo" delete(file("$rootDir/build/ivy-repo")) -} \ No newline at end of file +} diff --git a/subprojects/parseq-test-api/src/main/java/com/linkedin/parseq/BaseEngineParSeqTest.java b/subprojects/parseq-test-api/src/main/java/com/linkedin/parseq/BaseEngineParSeqTest.java new file mode 100644 index 00000000..810767df --- /dev/null +++ b/subprojects/parseq-test-api/src/main/java/com/linkedin/parseq/BaseEngineParSeqTest.java @@ -0,0 +1,155 @@ +/* + * Copyright 2012 LinkedIn, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.linkedin.parseq; + +import com.linkedin.parseq.Engine; +import com.linkedin.parseq.ParSeqUnitTestHelper; +import com.linkedin.parseq.Task; +import java.util.concurrent.TimeUnit; +import javax.annotation.Nonnull; +import org.testng.annotations.AfterSuite; +import org.testng.annotations.BeforeSuite; + +/** + * This is a custom implementation of {@link com.linkedin.parseq.BaseEngineTest}. This custom + * implementation is necessary to avoid race-conditions that we have in BaseEngineTest when running tests + * in parallel. It's a variant of {@link com.linkedin.parseq.BaseEngineTest}, and has the following + * differences: + * + *

1) Setup and tearDown are executed at test-suite level. Whereas {@link + * com.linkedin.parseq.BaseEngineTest} executes setup()/teardown() at class level (which can cause + * race-conditions if two or more tests from the same class are executed in parallel). + * + *

2) It uses mutex locking mechanism to set up and tear down {@link ParSeqUnitTestHelper} to + * avoid race-condition on ParSeqUnitTestHelper instance (it's a common resource among tests). + * + *

3) Default timeout for runAndWait() method is 10 seconds (which is needed by majority of the tests). + * Tests can override this by using the runAndWait() method that accepts timeout param. + * + */ +public class BaseEngineParSeqTest { + private static final Object MUTEX = new Object(); + private static final int RUN_AND_WAIT_TIMEOUT_SECONDS = 10; + private static final ParSeqUnitTestHelper PAR_SEQ_UNIT_TEST_HELPER = new ParSeqUnitTestHelper(); + + /* + * Get ParSeqUnitTestHelper + * + * @return ParSeqUnitTestHelper instance. + */ + protected static ParSeqUnitTestHelper getParSeqUnitTestHelper() { + return PAR_SEQ_UNIT_TEST_HELPER; + } + + /** + * Setup ParSeqUnitTestHelper + * + * @throws Exception + */ + @BeforeSuite + public void setUpParSeqHelper() throws Exception { + synchronized (MUTEX) { + getParSeqUnitTestHelper().setUp(); + } + } + + /** + * Tear down ParSeqUnitTestHelper + * + * @throws Exception + */ + @AfterSuite + public void tearDownParSeqHelper() throws Exception { + synchronized (MUTEX) { + if (getParSeqUnitTestHelper().getEngine() != null) { + getParSeqUnitTestHelper().tearDown(); + } else { + throw new RuntimeException( + "Tried to shut down Engine but it either has not even been created or " + + "has already been shut down, in " + + this.getClass().getName()); + } + } + } + + /** + * Run a parseq task and wait for it to complete, including all side-effects (e.g. + * Task.withSafeSideEffect). The latter is necessary to run tests in parallel without any + * race-conditions. + * + *

Uses a default timeout (10 seconds). + * + * @param The type of the task. + * @param task The task to run. + * @return The same task after task completion, it is safe to call task.get() after running this + * method. + */ + protected T runAndWait(@Nonnull Task task) { + return runAndWait(task, RUN_AND_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + + /** + * Run a parseq task and wait for it to complete, including all side-effects (e.g. + * Task.withSafeSideEffect). The latter is necessary to run tests in parallel without any + * race-conditions. + * + *

Uses a custom timeout provided by the caller. + * + * @param The type of the task. + * @param task The task to run. + * @param timeOut the amount of time to wait for task completion. + * @param timeUnit the timeUnit (seconds, milliseconds, etc) for timeOut param. + * @return The same task after task completion, it is safe to call task.get() after running this + * method. + */ + protected T runAndWait(@Nonnull Task task, int timeOut, @Nonnull TimeUnit timeUnit) { + synchronized (MUTEX) { + return getParSeqUnitTestHelper().runAndWaitForPlanToComplete(this.getClass().getName(), task, timeOut, timeUnit); + } + } + + /** + * Run a parseq task and wait for it to complete, including all side-effects (e.g. + * Task.withSafeSideEffect). The latter is necessary to run tests in parallel without any + * race-conditions. + * + *

Also verify that the task threw an exception and then return that Exception. + * + *

Use a default timeout (10 seconds). + * + * @param task The Task that should be run. + * @param exceptionClass The class of Exception that should be thrown when the task completes. + * @param The type of Exception that should be returned. + * @return The exception that was encountered while the Task was running. + */ + protected T runAndWaitException(@Nonnull Task task, @Nonnull Class exceptionClass) { + synchronized (MUTEX) { + return getParSeqUnitTestHelper() + .runAndWaitExceptionOnPlanCompletion( + this.getClass().getName(), task, exceptionClass, RUN_AND_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } + } + + /** + * Get the instance of parseq engine used for running tasks in tests. + * + * @return instance of {@link Engine} used for running tasks in tests. + */ + protected Engine getEngine() { + return getParSeqUnitTestHelper().getEngine(); + } +} From 629b6e4f607a0512733e59bd47a54a9b59027590 Mon Sep 17 00:00:00 2001 From: Balaji Dubey Date: Tue, 23 Sep 2025 22:38:30 +0530 Subject: [PATCH 2/2] Update BaseEngineParSeqTest.java --- .../src/main/java/com/linkedin/parseq/BaseEngineParSeqTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subprojects/parseq-test-api/src/main/java/com/linkedin/parseq/BaseEngineParSeqTest.java b/subprojects/parseq-test-api/src/main/java/com/linkedin/parseq/BaseEngineParSeqTest.java index 810767df..86e34a9e 100644 --- a/subprojects/parseq-test-api/src/main/java/com/linkedin/parseq/BaseEngineParSeqTest.java +++ b/subprojects/parseq-test-api/src/main/java/com/linkedin/parseq/BaseEngineParSeqTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 LinkedIn, Inc + * Copyright 2025 LinkedIn, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of