You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by js...@apache.org on 2017/04/28 17:36:54 UTC

geode git commit: GEODE-2795: Clean up DUnit VMs after dynamically changing 'user.dir'

Repository: geode
Updated Branches:
  refs/heads/develop a5af40569 -> 953f1eebc


GEODE-2795: Clean up DUnit VMs after dynamically changing 'user.dir'


Project: http://git-wip-us.apache.org/repos/asf/geode/repo
Commit: http://git-wip-us.apache.org/repos/asf/geode/commit/953f1eeb
Tree: http://git-wip-us.apache.org/repos/asf/geode/tree/953f1eeb
Diff: http://git-wip-us.apache.org/repos/asf/geode/diff/953f1eeb

Branch: refs/heads/develop
Commit: 953f1eebcb0a1615bc761b684463fe890abe5692
Parents: a5af405
Author: Jared Stewart <js...@pivotal.io>
Authored: Fri Apr 28 10:30:35 2017 -0700
Committer: Jared Stewart <js...@pivotal.io>
Committed: Fri Apr 28 10:32:55 2017 -0700

----------------------------------------------------------------------
 .../ClassPathLoaderIntegrationTest.java         | 10 ++-
 .../ConnectToLocatorSSLDUnitTest.java           | 35 ++++++++---
 .../geode/management/JMXMBeanDUnitTest.java     | 64 ++++++++++++--------
 .../DeployCommandRedeployDUnitTest.java         |  2 +-
 .../configuration/ClusterConfigTestBase.java    |  2 +-
 .../test/dunit/rules/CleanupDUnitVMsRule.java   | 31 ++++++++++
 .../dunit/rules/LocatorServerStartupRule.java   | 25 +++-----
 .../apache/geode/test/dunit/rules/MemberVM.java | 23 +++++++
 .../geode/test/junit/rules/RestoreTCCLRule.java | 31 ++++++++++
 9 files changed, 164 insertions(+), 59 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/geode/blob/953f1eeb/geode-core/src/test/java/org/apache/geode/internal/ClassPathLoaderIntegrationTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/ClassPathLoaderIntegrationTest.java b/geode-core/src/test/java/org/apache/geode/internal/ClassPathLoaderIntegrationTest.java
index 2a3a7dd..b4776bf 100644
--- a/geode-core/src/test/java/org/apache/geode/internal/ClassPathLoaderIntegrationTest.java
+++ b/geode-core/src/test/java/org/apache/geode/internal/ClassPathLoaderIntegrationTest.java
@@ -27,7 +27,6 @@ import java.io.OutputStream;
 import java.net.URL;
 import java.util.Enumeration;
 import java.util.List;
-import java.util.Properties;
 import java.util.Vector;
 
 import org.apache.bcel.Constants;
@@ -39,8 +38,8 @@ import org.apache.geode.cache.execute.FunctionService;
 import org.apache.geode.cache.execute.ResultCollector;
 import org.apache.geode.distributed.DistributedSystem;
 import org.apache.geode.internal.cache.GemFireCacheImpl;
+import org.apache.geode.test.junit.rules.RestoreTCCLRule;
 import org.apache.geode.test.dunit.rules.ServerStarterRule;
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -48,10 +47,6 @@ import org.junit.contrib.java.lang.system.RestoreSystemProperties;
 import org.junit.experimental.categories.Category;
 import org.junit.rules.TemporaryFolder;
 
-import org.apache.geode.internal.ClassPathLoaderTest.BrokenClassLoader;
-import org.apache.geode.internal.ClassPathLoaderTest.NullClassLoader;
-import org.apache.geode.internal.ClassPathLoaderTest.SimpleClassLoader;
-
 import org.apache.geode.test.junit.categories.IntegrationTest;
 
 /**
@@ -68,6 +63,9 @@ public class ClassPathLoaderIntegrationTest {
   private File tempFile2;
 
   @Rule
+  public RestoreTCCLRule restoreTCCLRule = new RestoreTCCLRule();
+
+  @Rule
   public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
 
   @Rule

http://git-wip-us.apache.org/repos/asf/geode/blob/953f1eeb/geode-core/src/test/java/org/apache/geode/management/ConnectToLocatorSSLDUnitTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/management/ConnectToLocatorSSLDUnitTest.java b/geode-core/src/test/java/org/apache/geode/management/ConnectToLocatorSSLDUnitTest.java
index 1033b6c..844e032 100644
--- a/geode-core/src/test/java/org/apache/geode/management/ConnectToLocatorSSLDUnitTest.java
+++ b/geode-core/src/test/java/org/apache/geode/management/ConnectToLocatorSSLDUnitTest.java
@@ -38,6 +38,8 @@ import static org.apache.geode.util.test.TestUtil.getResourcePath;
 
 import org.apache.geode.management.internal.cli.i18n.CliStrings;
 import org.apache.geode.security.SecurableCommunicationChannels;
+import org.apache.geode.test.dunit.Host;
+import org.apache.geode.test.dunit.rules.CleanupDUnitVMsRule;
 import org.apache.geode.test.dunit.rules.GfshShellConnectionRule;
 import org.apache.geode.test.dunit.rules.LocatorServerStartupRule;
 import org.apache.geode.test.dunit.rules.MemberVM;
@@ -48,6 +50,7 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
+import org.junit.rules.RuleChain;
 import org.junit.rules.TemporaryFolder;
 
 import java.io.File;
@@ -57,14 +60,13 @@ import java.util.Properties;
 
 @Category(DistributedTest.class)
 public class ConnectToLocatorSSLDUnitTest {
+  private TemporaryFolder folder = new SerializableTemporaryFolder();
+  private LocatorServerStartupRule lsRule = new LocatorServerStartupRule();
+  private CleanupDUnitVMsRule cleanupDUnitVMsRule = new CleanupDUnitVMsRule();
 
   @Rule
-  public TemporaryFolder folder = new SerializableTemporaryFolder();
-  @Rule
-  public LocatorServerStartupRule lsRule = new LocatorServerStartupRule();
-
-  @Rule
-  public GfshShellConnectionRule gfshConnector = new GfshShellConnectionRule();
+  public RuleChain ruleChain =
+      RuleChain.outerRule(cleanupDUnitVMsRule).around(folder).around(lsRule);
 
   private File jks = null;
   private File securityPropsFile = null;
@@ -89,10 +91,25 @@ public class ConnectToLocatorSSLDUnitTest {
     OutputStream out = new FileOutputStream(securityPropsFile);
     securityProps.store(out, null);
 
-    gfshConnector.connect(locator, CliStrings.CONNECT__SECURITY_PROPERTIES,
-        securityPropsFile.getCanonicalPath());
 
-    assertTrue(gfshConnector.isConnected());
+    /*
+     * When using SSL, the GfshShellConnectionRule seems to leave behind state in the JVM that
+     * causes test flakinesss. (Each test method will pass if run in isolation, but when all run
+     * together, the second and third tests will fail.) To avoid this issue, we connect to our
+     * locator from a remote VM which is cleaned up by the CleanupDUnitVMsRule in between tests.
+     */
+
+    final int locatorPort = locator.getPort();
+    final String securityPropsFilePath = securityPropsFile.getCanonicalPath();
+    Host.getHost(0).getVM(1).invoke(() -> {
+      GfshShellConnectionRule gfshConnector = new GfshShellConnectionRule();
+      try {
+        gfshConnector.connectAndVerify(locatorPort, GfshShellConnectionRule.PortType.locator,
+            CliStrings.CONNECT__SECURITY_PROPERTIES, securityPropsFilePath);
+      } finally {
+        gfshConnector.close();
+      }
+    });
   }
 
   @Test

http://git-wip-us.apache.org/repos/asf/geode/blob/953f1eeb/geode-core/src/test/java/org/apache/geode/management/JMXMBeanDUnitTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/management/JMXMBeanDUnitTest.java b/geode-core/src/test/java/org/apache/geode/management/JMXMBeanDUnitTest.java
index ebc3f17..d2123f4 100644
--- a/geode-core/src/test/java/org/apache/geode/management/JMXMBeanDUnitTest.java
+++ b/geode-core/src/test/java/org/apache/geode/management/JMXMBeanDUnitTest.java
@@ -33,6 +33,8 @@ import static org.apache.geode.distributed.ConfigurationProperties.SSL_KEYSTORE_
 import static org.apache.geode.distributed.ConfigurationProperties.SSL_PROTOCOLS;
 import static org.apache.geode.distributed.ConfigurationProperties.SSL_TRUSTSTORE;
 import static org.apache.geode.distributed.ConfigurationProperties.SSL_TRUSTSTORE_PASSWORD;
+import static org.apache.geode.test.dunit.Host.getHost;
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.Assert.assertEquals;
 
@@ -40,16 +42,23 @@ import com.google.common.collect.Maps;
 
 import org.apache.geode.internal.AvailablePortHelper;
 import org.apache.geode.internal.security.SecurableCommunicationChannel;
+import org.apache.geode.test.dunit.rules.CleanupDUnitVMsRule;
+import org.apache.geode.test.dunit.rules.Locator;
 import org.apache.geode.test.dunit.rules.LocatorServerStartupRule;
 import org.apache.geode.test.dunit.rules.MBeanServerConnectionRule;
+import org.apache.geode.test.dunit.rules.MemberVM;
 import org.apache.geode.test.junit.categories.DistributedTest;
 import org.apache.geode.util.test.TestUtil;
 import org.junit.Before;
 import org.junit.BeforeClass;
+import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.contrib.java.lang.system.RestoreSystemProperties;
 import org.junit.experimental.categories.Category;
+import org.junit.rules.RuleChain;
 
+import java.io.Serializable;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
@@ -62,20 +71,21 @@ import javax.rmi.ssl.SslRMIClientSocketFactory;
  * ssl settings cleanly.
  */
 @Category(DistributedTest.class)
-public class JMXMBeanDUnitTest {
-  @Rule
-  public LocatorServerStartupRule lsRule = new LocatorServerStartupRule();
+public class JMXMBeanDUnitTest implements Serializable {
+  private LocatorServerStartupRule lsRule = new LocatorServerStartupRule();
+  private transient MBeanServerConnectionRule jmxConnector = new MBeanServerConnectionRule();
+  private transient RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
+  private transient CleanupDUnitVMsRule cleanupDUnitVMsRule = new CleanupDUnitVMsRule();
 
   @Rule
-  public MBeanServerConnectionRule jmxConnector = new MBeanServerConnectionRule();
+  public transient RuleChain ruleChain = RuleChain.outerRule(cleanupDUnitVMsRule)
+      .around(restoreSystemProperties).around(lsRule).around(jmxConnector);
 
   private int jmxPort;
   private Properties locatorProperties = null;
-  private Map<String, Object> clientEnv = null;
   private static Properties legacySSLProperties, sslProperties, sslPropertiesWithMultiKey;
   private static String singleKeystore, multiKeystore, multiKeyTruststore;
 
-
   @BeforeClass
   public static void beforeClass() {
     singleKeystore = TestUtil.getResourcePath(JMXMBeanDUnitTest.class, "/ssl/trusted.keystore");
@@ -119,19 +129,19 @@ public class JMXMBeanDUnitTest {
     locatorProperties = new Properties();
     locatorProperties.put(JMX_MANAGER_PORT, jmxPort + "");
     locatorProperties.setProperty(ENABLE_CLUSTER_CONFIGURATION, "false");
-    clientEnv = new HashMap<>();
   }
 
   @Test
   public void testJMXOverNonSSL() throws Exception {
-    lsRule.startLocatorVM(1, locatorProperties);
+    lsRule.startLocatorVM(0, locatorProperties);
     jmxConnector.connect(jmxPort);
-    validateJmxConnection();
+    validateJmxConnection(jmxConnector);
   }
 
   @Test
   public void testJMXOverNonSSLWithClientUsingIncorrectPort() throws Exception {
-    lsRule.startLocatorVM(1, locatorProperties);
+    assertThat(jmxPort).isNotEqualTo(9999);
+    lsRule.startLocatorVM(0, locatorProperties);
 
     assertThatThrownBy(() -> jmxConnector.connect(9999))
         .hasRootCauseExactlyInstanceOf(java.net.ConnectException.class);
@@ -140,11 +150,9 @@ public class JMXMBeanDUnitTest {
   @Test
   public void testJMXOverSSL() throws Exception {
     locatorProperties.putAll(Maps.fromProperties(sslProperties));
-    lsRule.startLocatorVM(0, locatorProperties);
-    clientEnv = getClientEnvironment(false);
-    jmxConnector.connect(jmxPort, clientEnv);
 
-    validateJmxConnection();
+    lsRule.startLocatorVM(0, locatorProperties);
+    remotelyValidateJmxConnection(false);
   }
 
 
@@ -152,18 +160,26 @@ public class JMXMBeanDUnitTest {
   public void testJMXOverSSLWithMultiKey() throws Exception {
     locatorProperties.putAll(Maps.fromProperties(sslPropertiesWithMultiKey));
     lsRule.startLocatorVM(0, locatorProperties);
-    clientEnv = getClientEnvironment(true);
-    jmxConnector.connect(jmxPort, clientEnv);
-    validateJmxConnection();
+
+    remotelyValidateJmxConnection(true);
   }
 
   @Test
   public void testJMXOverLegacySSL() throws Exception {
     locatorProperties.putAll(Maps.fromProperties(legacySSLProperties));
     lsRule.startLocatorVM(0, locatorProperties);
-    clientEnv = getClientEnvironment(false);
-    jmxConnector.connect(jmxPort, clientEnv);
-    validateJmxConnection();
+
+    remotelyValidateJmxConnection(false);
+  }
+
+  private void remotelyValidateJmxConnection(boolean withAlias) {
+    getHost(0).getVM(2).invoke(() -> {
+      beforeClass();
+      MBeanServerConnectionRule jmx = new MBeanServerConnectionRule();
+      Map<String, Object> env = getClientEnvironment(withAlias);
+      jmx.connect(jmxPort, env);
+      validateJmxConnection(jmx);
+    });
   }
 
 
@@ -174,17 +190,17 @@ public class JMXMBeanDUnitTest {
     System.setProperty("javax.net.ssl.trustStore", withAlias ? multiKeyTruststore : singleKeystore);
     System.setProperty("javax.net.ssl.trustStoreType", "JKS");
     System.setProperty("javax.net.ssl.trustStorePassword", "password");
-    Map<String, Object> environment = new HashMap();
+    Map<String, Object> environment = new HashMap<>();
     environment.put("com.sun.jndi.rmi.factory.socket", new SslRMIClientSocketFactory());
     return environment;
   }
 
 
-  private void validateJmxConnection() throws Exception {
-    MBeanServerConnection mbeanServerConnection = jmxConnector.getMBeanServerConnection();
+  private void validateJmxConnection(MBeanServerConnectionRule mBeanServerConnectionRule)
+      throws Exception {
     // Get MBean proxy instance that will be used to make calls to registered MBean
     DistributedSystemMXBean distributedSystemMXBean =
-        jmxConnector.getProxyMBean(DistributedSystemMXBean.class);
+        mBeanServerConnectionRule.getProxyMBean(DistributedSystemMXBean.class);
     assertEquals(1, distributedSystemMXBean.getMemberCount());
     assertEquals(1, distributedSystemMXBean.getLocatorCount());
   }

http://git-wip-us.apache.org/repos/asf/geode/blob/953f1eeb/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/DeployCommandRedeployDUnitTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/DeployCommandRedeployDUnitTest.java b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/DeployCommandRedeployDUnitTest.java
index d47b343..6972477 100644
--- a/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/DeployCommandRedeployDUnitTest.java
+++ b/geode-core/src/test/java/org/apache/geode/management/internal/cli/commands/DeployCommandRedeployDUnitTest.java
@@ -58,7 +58,7 @@ public class DeployCommandRedeployDUnitTest implements Serializable {
   private MemberVM server;
 
   @Rule
-  public LocatorServerStartupRule lsRule = new LocatorServerStartupRule(true);
+  public LocatorServerStartupRule lsRule = new LocatorServerStartupRule();
 
   @Rule
   public transient GfshShellConnectionRule gfshConnector = new GfshShellConnectionRule();

http://git-wip-us.apache.org/repos/asf/geode/blob/953f1eeb/geode-core/src/test/java/org/apache/geode/management/internal/configuration/ClusterConfigTestBase.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/management/internal/configuration/ClusterConfigTestBase.java b/geode-core/src/test/java/org/apache/geode/management/internal/configuration/ClusterConfigTestBase.java
index c5aaa74..fd8fb46 100644
--- a/geode-core/src/test/java/org/apache/geode/management/internal/configuration/ClusterConfigTestBase.java
+++ b/geode-core/src/test/java/org/apache/geode/management/internal/configuration/ClusterConfigTestBase.java
@@ -53,7 +53,7 @@ public abstract class ClusterConfigTestBase {
           .regions("regionForGroup2"));
 
   @Rule
-  public LocatorServerStartupRule lsRule = new LocatorServerStartupRule(true);
+  public LocatorServerStartupRule lsRule = new LocatorServerStartupRule();
 
   protected Properties locatorProps;
   protected Properties serverProps;

http://git-wip-us.apache.org/repos/asf/geode/blob/953f1eeb/geode-core/src/test/java/org/apache/geode/test/dunit/rules/CleanupDUnitVMsRule.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/test/dunit/rules/CleanupDUnitVMsRule.java b/geode-core/src/test/java/org/apache/geode/test/dunit/rules/CleanupDUnitVMsRule.java
new file mode 100644
index 0000000..1d8a355
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/test/dunit/rules/CleanupDUnitVMsRule.java
@@ -0,0 +1,31 @@
+/*
+ * 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.test.dunit.rules;
+
+import static org.apache.geode.test.dunit.Host.getHost;
+
+import org.apache.geode.test.dunit.VM;
+import org.junit.After;
+import org.junit.rules.ExternalResource;
+
+import java.io.Serializable;
+
+public class CleanupDUnitVMsRule extends ExternalResource implements Serializable {
+
+  @Override
+  public void after() {
+    getHost(0).getAllVMs().forEach(VM::bounce);
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/953f1eeb/geode-core/src/test/java/org/apache/geode/test/dunit/rules/LocatorServerStartupRule.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/test/dunit/rules/LocatorServerStartupRule.java b/geode-core/src/test/java/org/apache/geode/test/dunit/rules/LocatorServerStartupRule.java
index 34506c4..4219d02 100644
--- a/geode-core/src/test/java/org/apache/geode/test/dunit/rules/LocatorServerStartupRule.java
+++ b/geode-core/src/test/java/org/apache/geode/test/dunit/rules/LocatorServerStartupRule.java
@@ -21,7 +21,6 @@ import static org.apache.geode.distributed.ConfigurationProperties.NAME;
 import static org.apache.geode.test.dunit.Host.getHost;
 
 import org.apache.geode.internal.AvailablePortHelper;
-import org.apache.geode.test.dunit.Invoke;
 import org.apache.geode.test.dunit.VM;
 import org.apache.geode.test.dunit.standalone.DUnitLauncher;
 import org.apache.geode.test.junit.rules.serializable.SerializableTemporaryFolder;
@@ -31,6 +30,8 @@ import org.junit.rules.TemporaryFolder;
 import java.io.File;
 import java.io.IOException;
 import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Objects;
 import java.util.Properties;
 
 
@@ -57,37 +58,25 @@ public class LocatorServerStartupRule extends ExternalResource implements Serial
 
   private TemporaryFolder temporaryFolder = new SerializableTemporaryFolder();
   private MemberVM[] members;
-  private final boolean bounceVms;
 
   public LocatorServerStartupRule() {
-    this(false);
-  }
-
-  /**
-   * If your DUnit tests fail due to insufficient cleanup, try setting bounceVms=true.
-   */
-  public LocatorServerStartupRule(boolean bounceVms) {
     DUnitLauncher.launchIfNeeded();
-    this.bounceVms = bounceVms;
   }
 
   @Override
   protected void before() throws Throwable {
     restoreSystemProperties.before();
     temporaryFolder.create();
-    Invoke.invokeInEveryVM("Stop each VM", this::cleanupVm);
-    if (bounceVms) {
-      getHost(0).getAllVMs().forEach(VM::bounce);
-    }
     members = new MemberVM[4];
   }
 
   @Override
   protected void after() {
     DUnitLauncher.closeAndCheckForSuspects();
-    Invoke.invokeInEveryVM("Stop each VM", this::cleanupVm);
     restoreSystemProperties.after();
     temporaryFolder.delete();
+    Arrays.stream(members).filter(Objects::nonNull)
+        .forEach(MemberVM::stopMemberAndCleanupVMIfNecessary);
   }
 
   public MemberVM startLocatorVM(int index) throws Exception {
@@ -97,7 +86,7 @@ public class LocatorServerStartupRule extends ExternalResource implements Serial
   /**
    * Starts a locator instance with the given configuration properties inside
    * {@code getHost(0).getVM(index)}.
-   *
+   * 
    * @return VM locator vm
    */
   public MemberVM<Locator> startLocatorVM(int index, Properties properties) throws Exception {
@@ -150,7 +139,7 @@ public class LocatorServerStartupRule extends ExternalResource implements Serial
 
   public void stopMember(int index) {
     MemberVM member = members[index];
-    member.invoke(this::cleanupVm);
+    member.stopMemberAndCleanupVMIfNecessary();
   }
 
   /**
@@ -184,7 +173,7 @@ public class LocatorServerStartupRule extends ExternalResource implements Serial
     return temporaryFolder;
   }
 
-  private void cleanupVm() {
+  public static void stopMemberInThisVM() {
     if (serverStarter != null) {
       serverStarter.after();
       serverStarter = null;

http://git-wip-us.apache.org/repos/asf/geode/blob/953f1eeb/geode-core/src/test/java/org/apache/geode/test/dunit/rules/MemberVM.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/test/dunit/rules/MemberVM.java b/geode-core/src/test/java/org/apache/geode/test/dunit/rules/MemberVM.java
index 1626985..6da824e 100644
--- a/geode-core/src/test/java/org/apache/geode/test/dunit/rules/MemberVM.java
+++ b/geode-core/src/test/java/org/apache/geode/test/dunit/rules/MemberVM.java
@@ -20,6 +20,7 @@ import org.apache.geode.test.dunit.SerializableRunnableIF;
 import org.apache.geode.test.dunit.VM;
 
 import java.io.File;
+import java.nio.file.Paths;
 
 public class MemberVM<T extends Member> implements Member {
   private T member;
@@ -74,4 +75,26 @@ public class MemberVM<T extends Member> implements Member {
   public String getName() {
     return member.getName();
   }
+
+  public void stopMemberAndCleanupVMIfNecessary() {
+    stopMember();
+    cleanupVMIfNecessary();
+  }
+
+  private void cleanupVMIfNecessary() {
+    /**
+     * The LocatorServerStarterRule may dynamically change the "user.dir" system property to point
+     * to a temporary folder. The Path API caches the first value of "user.dir" that it sees, and
+     * this can result in a stale cached value of "user.dir" which points to a directory that no
+     * longer exists.
+     */
+    boolean vmIsClean = this.getVM().invoke(() -> Paths.get("").toAbsolutePath().toFile().exists());
+    if (!vmIsClean) {
+      this.getVM().bounce();
+    }
+  }
+
+  public void stopMember() {
+    this.invoke(LocatorServerStartupRule::stopMemberInThisVM);
+  }
 }

http://git-wip-us.apache.org/repos/asf/geode/blob/953f1eeb/geode-junit/src/main/java/org/apache/geode/test/junit/rules/RestoreTCCLRule.java
----------------------------------------------------------------------
diff --git a/geode-junit/src/main/java/org/apache/geode/test/junit/rules/RestoreTCCLRule.java b/geode-junit/src/main/java/org/apache/geode/test/junit/rules/RestoreTCCLRule.java
new file mode 100644
index 0000000..fff14fc
--- /dev/null
+++ b/geode-junit/src/main/java/org/apache/geode/test/junit/rules/RestoreTCCLRule.java
@@ -0,0 +1,31 @@
+/*
+ * 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.test.junit.rules;
+
+import org.junit.rules.ExternalResource;
+
+public class RestoreTCCLRule extends ExternalResource {
+  private ClassLoader oldTccl;
+
+  @Override
+  protected void before() {
+    this.oldTccl = Thread.currentThread().getContextClassLoader();
+  }
+
+  @Override
+  protected void after() {
+    Thread.currentThread().setContextClassLoader(this.oldTccl);
+  }
+}