GEODE-8637: Give each test worker a unique working dir (#5649)

* Give each test worker a unique working dir

Before running a test task, wrap its TestFramework in a wrapper that
creates a unique working dir for each new test worker.
diff --git a/buildSrc/src/main/java/org/apache/geode/gradle/RunInSubdirectoryTestFramework.java b/buildSrc/src/main/java/org/apache/geode/gradle/RunInSubdirectoryTestFramework.java
new file mode 100644
index 0000000..546ae1e
--- /dev/null
+++ b/buildSrc/src/main/java/org/apache/geode/gradle/RunInSubdirectoryTestFramework.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.tasks.testing.TestFramework;
+import org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory;
+import org.gradle.api.internal.tasks.testing.detection.TestFrameworkDetector;
+import org.gradle.api.tasks.testing.TestFrameworkOptions;
+import org.gradle.process.internal.JavaExecHandleBuilder;
+import org.gradle.process.internal.worker.WorkerProcessBuilder;
+
+/**
+ * Wraps a test framework to run each test worker in a separate working directory.
+ */
+public class RunInSubdirectoryTestFramework implements TestFramework {
+  private static final String GEMFIRE_PROPERTIES = "gemfire.properties";
+  private final AtomicLong workerId = new AtomicLong();
+  private final TestFramework delegate;
+
+  public RunInSubdirectoryTestFramework(TestFramework delegate) {
+    this.delegate = delegate;
+  }
+
+  @Override
+  public TestFrameworkDetector getDetector() {
+    return delegate.getDetector();
+  }
+
+  @Override
+  public TestFrameworkOptions getOptions() {
+    return delegate.getOptions();
+  }
+
+  @Override
+  public WorkerTestClassProcessorFactory getProcessorFactory() {
+    return delegate.getProcessorFactory();
+  }
+
+  /**
+   * Return an action that configures the test worker builder to run the test worker in a unique
+   * subdirectory of the task's working directory.
+   */
+  @Override
+  public Action<WorkerProcessBuilder> getWorkerConfigurationAction() {
+    return workerProcessBuilder -> {
+      delegate.getWorkerConfigurationAction().execute(workerProcessBuilder);
+      JavaExecHandleBuilder javaCommand = workerProcessBuilder.getJavaCommand();
+
+      Path taskWorkingDir = javaCommand.getWorkingDir().toPath();
+      String workerWorkingDirName = String.format("test-worker-%06d", workerId.incrementAndGet());
+      Path workerWorkingDir = taskWorkingDir.resolve(workerWorkingDirName);
+
+      createWorkingDir(workerWorkingDir);
+      copyGemFirePropertiesFile(taskWorkingDir, workerWorkingDir);
+
+      javaCommand.setWorkingDir(workerWorkingDir);
+    };
+  }
+
+  private void copyGemFirePropertiesFile(Path taskWorkingDir, Path workerWorkingDir) {
+    Path taskPropertiesFile = taskWorkingDir.resolve(GEMFIRE_PROPERTIES);
+    if (!Files.exists(taskPropertiesFile)) {
+      return;
+    }
+    Path workerPropertiesFile = workerWorkingDir.resolve(taskPropertiesFile.getFileName());
+    try {
+      Files.copy(taskPropertiesFile, workerPropertiesFile, COPY_ATTRIBUTES);
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
+
+  private void createWorkingDir(Path workerWorkingDir) {
+    try {
+      Files.createDirectories(workerWorkingDir);
+    } catch (IOException e) {
+      throw new UncheckedIOException(e);
+    }
+  }
+}
diff --git a/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/AbstractSessionsTest.java b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/AbstractSessionsTest.java
index e30a15d..95250d3 100644
--- a/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/AbstractSessionsTest.java
+++ b/extensions/geode-modules-test/src/main/java/org/apache/geode/modules/session/AbstractSessionsTest.java
@@ -54,7 +54,8 @@
 
   // Set up the servers we need
   protected static void setupServer(final DeltaSessionManager manager) throws Exception {
-    FileUtils.copyDirectory(Paths.get("..", "resources", "integrationTest", "tomcat").toFile(),
+    FileUtils.copyDirectory(
+        Paths.get("..", "..", "resources", "integrationTest", "tomcat").toFile(),
         new File("./tomcat"));
     port = SocketUtils.findAvailableTcpPort();
     server = new EmbeddedTomcat(port, "JVM-1");
diff --git a/gradle/test.gradle b/gradle/test.gradle
index 15d0e5b..e12c3e1 100644
--- a/gradle/test.gradle
+++ b/gradle/test.gradle
@@ -1,6 +1,7 @@
 import org.apache.geode.gradle.TestPropertiesWriter
 import org.apache.geode.gradle.RepeatTest
 import org.apache.geode.gradle.plugins.DependencyConstraints
+import org.apache.geode.gradle.RunInSubdirectoryTestFramework
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one or more
@@ -161,6 +162,17 @@
   }
 }
 
+configure([acceptanceTest, distributedTest, integrationTest, uiTest, upgradeTest, repeatAcceptanceTest, repeatDistributedTest, repeatIntegrationTest, repeatUnitTest, repeatUpgradeTest]) {
+  doFirst {
+    // Wrap the task's test framework in a wrapper that runs each test worker JVM in a unique
+    // subdirectory.
+    def subdirFramework = new RunInSubdirectoryTestFramework(testFramework)
+    // This call works for now, but the Test class declares useTestFramework() as protected, so
+    // this could become troublesome in a future version of Gradle/Groovy.
+    useTestFramework subdirFramework
+  }
+}
+
 configure([repeatDistributedTest, repeatIntegrationTest, repeatUpgradeTest, repeatUnitTest, repeatAcceptanceTest]) {
   times = Integer.parseInt(repeat)
   useJUnit {}