+ * CAVEAT: The types of these parameters are declared internal by Gradle v6.8.3. Future + * versions of Gradle may not include these types, or may change their implementation. + *
+ * CAVEAT: The Gradle v6.8.3 documentation does not list these types among the services that
+ * Gradle will inject into plugins. Future versions of Gradle may not inject these values.
+ */
+ @Inject
+ DockerizedTestPlugin(MessagingServer gradleMessagingServer,
+ WorkerProcessFactory gradleWorkerProcessFactory) {
+ initializeGradleWorkerProcessFactory(gradleWorkerProcessFactory)
+ initializeGradleMessagingServer(gradleMessagingServer)
+ }
+
+ @Override
+ void apply(Project project) {
+ if (!project.hasProperty('parallelDunit')) {
+ return
+ }
+
+ initializeMessagingServer()
+
+ def dockerTestWorkerConfig = new DockerTestWorkerConfig(project)
+ def dockerProcessLauncher = new DockerProcessLauncher(dockerTestWorkerConfig, new WorkingDirectoryIsolator())
+ def dockerWorkerProcessFactory = Workers.createWorkerProcessFactory(
+ gradleWorkerProcessFactory,
+ dockerProcessLauncher,
+ dockerMessagingServer)
+
+ def useDockerTestWorker = {
+ it.doFirst {
+ testExecuter = Executers.withFactory(it, dockerWorkerProcessFactory)
+ }
+ }
+
+ project.tasks.withType(Test).each(useDockerTestWorker)
+ project.tasks.whenTaskAdded() {
+ if (it instanceof Test) {
+ it.configure(useDockerTestWorker)
+ }
+ }
+ }
+
+ synchronized static initializeGradleMessagingServer(server) {
+ if (!gradleMessagingServer) {
+ gradleMessagingServer = server
+ }
+ }
+
+ synchronized static void initializeGradleWorkerProcessFactory(factory) {
+ if (!gradleWorkerProcessFactory) {
+ gradleWorkerProcessFactory = factory
+ }
+ }
+
+ synchronized static void initializeMessagingServer() {
+ if (dockerMessagingServer) {
+ return
+ }
+
+ def gradleConnector = gradleMessagingServer.connector
+ def gradleExecutorFactory = gradleConnector.executorFactory
+ def gradleIdGenerator = gradleConnector.idGenerator
+
+ /**
+ * Use a custom {@link WildcardBindingInetAddressFactory} to allow connections from
+ * processes in Docker containers.
+ */
+ def wildcardAddressFactory = new WildcardBindingInetAddressFactory()
+ def dockerConnector = new TcpIncomingConnector(
+ gradleExecutorFactory,
+ wildcardAddressFactory,
+ gradleIdGenerator
+ )
+ /**
+ * Use a custom {@link DockerMessagingServer} that yields connection addresses usable
+ * by processes in Docker containers.
+ */
+ dockerMessagingServer = new DockerMessagingServer(dockerConnector, gradleExecutorFactory)
+ }
+}
diff --git a/buildSrc/src/main/groovy/org/apache/geode/gradle/testing/repeat/RepeatTest.groovy b/buildSrc/src/main/groovy/org/apache/geode/gradle/testing/repeat/RepeatTest.groovy
new file mode 100644
index 000000000000..bafeab9f4f52
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/apache/geode/gradle/testing/repeat/RepeatTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.gradle.testing.repeat
+
+import org.gradle.StartParameter
+import org.gradle.api.file.FileTree
+import org.gradle.api.internal.DocumentationRegistry
+import org.gradle.api.internal.tasks.testing.JvmTestExecutionSpec
+import org.gradle.api.internal.tasks.testing.TestExecuter
+import org.gradle.api.internal.tasks.testing.filter.DefaultTestFilter
+import org.gradle.api.tasks.testing.Test
+import org.gradle.internal.time.Clock
+import org.gradle.internal.work.WorkerLeaseRegistry
+
+class RepeatTest extends Test {
+ int times = 1
+
+ /**
+ * Submit each test class for processing multiple times.
+ */
+ @Override
+ FileTree getCandidateClassFiles() {
+ FileTree candidates = super.getCandidateClassFiles()
+ int additionalRuns = times - 1
+ for (int i = 0; i < additionalRuns; i++) {
+ candidates = candidates.plus(super.getCandidateClassFiles())
+ }
+
+ return candidates
+ }
+
+ /**
+ * Use a custom {@link TestExecuter} that processes each test class as many times as submitted.
+ */
+ @Override
+ protected TestExecuterState flows
- *
- *
- *
- *
- * State is controlled on all control methods:
- *
- *
- */
-public class DockerizedExecHandle implements ExecHandle, ProcessSettings {
-
- private static final Logger LOGGER = Logging.getLogger(DockerizedExecHandle.class);
-
- private final String displayName;
-
- /**
- * The working directory of the process.
- */
- private final File directory;
-
- /**
- * The executable to run.
- */
- private final String command;
-
- /**
- * Arguments to pass to the executable.
- */
- private final List