You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by zh...@apache.org on 2017/08/11 17:26:41 UTC

[11/23] geode git commit: GEODE-3413: overhaul launcher and process classes and tests

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/BlockingProcessStreamReaderIntegrationTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/BlockingProcessStreamReaderIntegrationTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/BlockingProcessStreamReaderIntegrationTest.java
new file mode 100755
index 0000000..d22d92a
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/BlockingProcessStreamReaderIntegrationTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.internal.process;
+
+import static org.apache.geode.internal.lang.SystemUtils.isWindows;
+import static org.apache.geode.internal.process.ProcessStreamReader.ReadingMode.BLOCKING;
+import static org.junit.Assume.assumeFalse;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.internal.process.ProcessStreamReader.ReadingMode;
+import org.apache.geode.test.junit.categories.IntegrationTest;
+
+/**
+ * Functional integration tests for BlockingProcessStreamReader. All tests are skipped on Windows
+ * due to TRAC #51967: "GFSH start hangs on Windows"
+ *
+ * @see BlockingProcessStreamReaderWindowsTest
+ * @see NonBlockingProcessStreamReaderIntegrationTest
+ *
+ * @since GemFire 8.2
+ */
+@Category(IntegrationTest.class)
+public class BlockingProcessStreamReaderIntegrationTest
+    extends BaseProcessStreamReaderIntegrationTest {
+
+  @Before
+  public void setUp() throws Exception {
+    assumeFalse(isWindows());
+  }
+
+  @Test
+  public void canCloseStreams() throws Exception {
+    // arrange
+    givenRunningProcessWithStreamReaders(ProcessSleeps.class);
+
+    // act
+    process.getOutputStream().close();
+    process.getErrorStream().close();
+    process.getInputStream().close();
+
+    // assert
+    assertThatProcessIsAlive(process);
+  }
+
+  @Test
+  public void canStopReaders() throws Exception {
+    // arrange
+    givenRunningProcessWithStreamReaders(ProcessSleeps.class);
+
+    // act
+    stdout.stop();
+    stderr.stop();
+
+    // assert
+    assertThatProcessIsAlive(process);
+  }
+
+  @Test
+  public void capturesStdout() throws Exception {
+    // arrange
+    givenStartedProcessWithStreamListeners(ProcessPrintsToStdout.class);
+
+    // act
+    waitUntilProcessStops();
+
+    // assert
+    assertThatProcessAndReadersStopped();
+    assertThatStdOutContainsExactly(ProcessPrintsToStdout.STDOUT);
+    assertThatStdErrContainsExactly(ProcessPrintsToStdout.STDERR);
+  }
+
+  @Test
+  public void capturesStderr() throws Exception {
+    // arrange
+    givenStartedProcessWithStreamListeners(ProcessPrintsToStderr.class);
+
+    // act
+    waitUntilProcessStops();
+
+    // assert
+    assertThatProcessAndReadersStopped();
+    assertThatStdOutContainsExactly(ProcessPrintsToStderr.STDOUT);
+    assertThatStdErrContainsExactly(ProcessPrintsToStderr.STDERR);
+  }
+
+  @Test
+  public void capturesStdoutAndStderr() throws Exception {
+    // arrange
+    givenStartedProcessWithStreamListeners(ProcessPrintsToBoth.class);
+
+    // act
+    waitUntilProcessStops();
+
+    // assert
+    assertThatProcessAndReadersStopped();
+    assertThatStdOutContainsExactly(ProcessPrintsToBoth.STDOUT);
+    assertThatStdErrContainsExactly(ProcessPrintsToBoth.STDERR);
+  }
+
+  @Test
+  public void capturesStderrWhenProcessFailsDuringStart() throws Exception {
+    // arrange
+    givenStartedProcessWithStreamListeners(ProcessThrowsError.class);
+
+    // act
+    waitUntilProcessStops();
+
+    // assert
+    assertThatProcessAndReadersStoppedWithExitValue(1);
+    assertThatStdOutContainsExactly(ProcessThrowsError.STDOUT);
+    assertThatStdErrContains(ProcessThrowsError.ERROR_MSG);
+  }
+
+  @Override
+  protected ReadingMode getReadingMode() {
+    return BLOCKING;
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/BlockingProcessStreamReaderJUnitTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/BlockingProcessStreamReaderJUnitTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/BlockingProcessStreamReaderJUnitTest.java
deleted file mode 100755
index 7d52e56..0000000
--- a/geode-core/src/test/java/org/apache/geode/internal/process/BlockingProcessStreamReaderJUnitTest.java
+++ /dev/null
@@ -1,443 +0,0 @@
-/*
- * 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.internal.process;
-
-import static org.junit.Assert.*;
-import static org.junit.Assume.*;
-
-import java.io.IOException;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-
-import org.apache.geode.internal.lang.SystemUtils;
-import org.apache.geode.internal.process.ProcessStreamReader.InputListener;
-import org.apache.geode.internal.process.ProcessStreamReader.ReadingMode;
-import org.apache.geode.test.junit.categories.IntegrationTest;
-
-/**
- * Tests BlockingProcessStreamReader. Most tests are skipped on Windows due to TRAC bug #51967 which
- * is caused by a JDK bug. The test {@link #hangsOnWindows} verifies the existence of the bug.
- * 
- * @since GemFire 8.2
- */
-@Category(IntegrationTest.class)
-public class BlockingProcessStreamReaderJUnitTest extends ProcessStreamReaderTestCase {
-
-  /** Timeout to confirm hang on Windows */
-  private static final int HANG_TIMEOUT = 10;
-
-  private ExecutorService futures;
-
-  @Before
-  public void createFutures() {
-    this.futures = Executors.newSingleThreadExecutor();
-  }
-
-  @After
-  public void shutdownFutures() {
-    assertTrue(this.futures.shutdownNow().isEmpty());
-  }
-
-  @Test
-  public void hangsOnWindows() throws Exception {
-    assumeTrue(SystemUtils.isWindows());
-
-    this.process = new ProcessBuilder(createCommandLine(ProcessSleeps.class)).start();
-
-    this.stderr = new ProcessStreamReader.Builder(this.process)
-        .inputStream(this.process.getErrorStream()).build();
-
-    this.stdout = new ProcessStreamReader.Builder(this.process)
-        .inputStream(this.process.getInputStream()).build();
-
-    this.stderr.start();
-    this.stdout.start();
-
-    assertIsAlive(this.process);
-
-    assertEventuallyIsRunning(this.stderr);
-    assertEventuallyIsRunning(this.stdout);
-
-    Future<Boolean> future = this.futures.submit(new Callable<Boolean>() {
-      @Override
-      public Boolean call() throws IOException {
-        process.getErrorStream().close();
-        process.getOutputStream().close();
-        process.getInputStream().close();
-        return true;
-      }
-    });
-
-    try {
-      future.get(HANG_TIMEOUT, TimeUnit.SECONDS);
-      // if the following fails then perhaps we're testing with a new JRE that
-      // fixes blocking reads of process streams on windows
-      fail("future should have timedout due to hang on windows");
-    } catch (TimeoutException expected) {
-      // verified hang on windows which causes TRAC bug #51967
-    }
-
-    this.process.destroy();
-  }
-
-  @Test
-  public void canCloseStreamsWhileProcessIsAlive() throws Exception {
-    assumeFalse(SystemUtils.isWindows());
-
-    this.process = new ProcessBuilder(createCommandLine(ProcessSleeps.class)).start();
-
-    this.stderr = new ProcessStreamReader.Builder(this.process)
-        .inputStream(this.process.getErrorStream()).build();
-
-    this.stdout = new ProcessStreamReader.Builder(this.process)
-        .inputStream(this.process.getInputStream()).build();
-
-    this.stderr.start();
-    this.stdout.start();
-
-    assertIsAlive(this.process);
-
-    assertEventuallyIsRunning(this.stderr);
-    assertEventuallyIsRunning(this.stdout);
-
-    this.process.getErrorStream().close();
-    this.process.getOutputStream().close();
-    this.process.getInputStream().close();
-
-    assertIsAlive(this.process);
-
-    this.process.destroy();
-  }
-
-  @Test
-  public void canStopReadersWhileProcessIsAlive() throws Exception {
-    assumeFalse(SystemUtils.isWindows());
-
-    this.process = new ProcessBuilder(createCommandLine(ProcessSleeps.class)).start();
-
-    this.stderr = new ProcessStreamReader.Builder(this.process)
-        .inputStream(this.process.getErrorStream()).build();
-
-    this.stdout = new ProcessStreamReader.Builder(this.process)
-        .inputStream(this.process.getInputStream()).build();
-
-    this.stderr.start();
-    this.stdout.start();
-
-    assertIsAlive(this.process);
-
-    assertEventuallyIsRunning(this.stderr);
-    assertEventuallyIsRunning(this.stdout);
-
-    this.stderr.stop();
-    this.stdout.stop();
-
-    this.process.getErrorStream().close();
-    this.process.getOutputStream().close();
-    this.process.getInputStream().close();
-
-    assertIsAlive(this.process);
-
-    this.process.destroy();
-  }
-
-  @Test
-  public void capturesStdoutWhileProcessIsAlive() throws Exception {
-    assumeFalse(SystemUtils.isWindows());
-
-    this.process = new ProcessBuilder(createCommandLine(ProcessPrintsToStdout.class)).start();
-
-    final StringBuffer stderrBuffer = new StringBuffer();
-    InputListener stderrListener = new InputListener() {
-      @Override
-      public void notifyInputLine(String line) {
-        stderrBuffer.append(line);
-      }
-    };
-
-    final StringBuffer stdoutBuffer = new StringBuffer();
-    InputListener stdoutListener = new InputListener() {
-      @Override
-      public void notifyInputLine(String line) {
-        stdoutBuffer.append(line);
-      }
-    };
-
-    this.stderr =
-        new ProcessStreamReader.Builder(this.process).inputStream(this.process.getErrorStream())
-            .inputListener(stderrListener).readingMode(ReadingMode.NON_BLOCKING).build();
-
-    this.stdout =
-        new ProcessStreamReader.Builder(this.process).inputStream(this.process.getInputStream())
-            .inputListener(stdoutListener).readingMode(ReadingMode.NON_BLOCKING).build();
-
-    this.stderr.start();
-    this.stdout.start();
-
-    assertEventuallyIsRunning(this.stderr);
-    assertEventuallyIsRunning(this.stdout);
-
-    // wait for process to die
-    assertEventuallyFalse("Process never died", new Callable<Boolean>() {
-      @Override
-      public Boolean call() throws Exception {
-        return ProcessUtils.isProcessAlive(process);
-      }
-    }, WAIT_FOR_PROCESS_TO_DIE_TIMEOUT, INTERVAL);
-
-    final int exitValue = this.process.exitValue();
-    assertEquals(0, exitValue);
-
-    this.stderr.join(READER_JOIN_TIMEOUT);
-    assertFalse(this.stderr.isRunning());
-
-    this.stdout.join(READER_JOIN_TIMEOUT);
-    assertFalse(this.stdout.isRunning());
-
-    // System.out.println("Stopping ProcessStreamReader");
-    this.stderr.stop();
-    this.stdout.stop();
-
-    // System.out.println("stderr=\n" + stderrBuffer.toString());
-    assertEquals("", stderrBuffer.toString());
-
-    // System.out.println("stdout=\n" + stdoutBuffer.toString());
-    StringBuilder sb = new StringBuilder().append(ProcessPrintsToStdout.LINES[0])
-        .append(ProcessPrintsToStdout.LINES[1]).append(ProcessPrintsToStdout.LINES[2]);
-    assertEquals(sb.toString(), stdoutBuffer.toString());
-
-    // System.out.println("Closing streams");
-    this.process.getErrorStream().close();
-    this.process.getInputStream().close();
-
-    this.process.destroy();
-  }
-
-  @Test
-  public void capturesStderrWhileProcessIsAlive() throws Exception {
-    assumeFalse(SystemUtils.isWindows());
-
-    this.process = new ProcessBuilder(createCommandLine(ProcessPrintsToStderr.class)).start();
-
-    final StringBuffer stderrBuffer = new StringBuffer();
-    InputListener stderrListener = new InputListener() {
-      @Override
-      public void notifyInputLine(String line) {
-        stderrBuffer.append(line);
-      }
-    };
-
-    final StringBuffer stdoutBuffer = new StringBuffer();
-    InputListener stdoutListener = new InputListener() {
-      @Override
-      public void notifyInputLine(String line) {
-        stdoutBuffer.append(line);
-      }
-    };
-
-    this.stderr =
-        new ProcessStreamReader.Builder(this.process).inputStream(this.process.getErrorStream())
-            .inputListener(stderrListener).readingMode(ReadingMode.NON_BLOCKING).build();
-
-    this.stdout =
-        new ProcessStreamReader.Builder(this.process).inputStream(this.process.getInputStream())
-            .inputListener(stdoutListener).readingMode(ReadingMode.NON_BLOCKING).build();
-
-    this.stderr.start();
-    this.stdout.start();
-
-    // wait for process to die
-    assertEventuallyFalse("Process never died", new Callable<Boolean>() {
-      @Override
-      public Boolean call() throws Exception {
-        return ProcessUtils.isProcessAlive(process);
-      }
-    }, WAIT_FOR_PROCESS_TO_DIE_TIMEOUT, INTERVAL);
-
-    final int exitValue = this.process.exitValue();
-    assertEquals(0, exitValue);
-
-    this.stderr.join(READER_JOIN_TIMEOUT);
-    assertFalse(this.stderr.isRunning());
-
-    this.stdout.join(READER_JOIN_TIMEOUT);
-    assertFalse(this.stdout.isRunning());
-
-    // System.out.println("Stopping ProcessStreamReader");
-    this.stderr.stop();
-    this.stdout.stop();
-
-    // System.out.println("stderr=\n" + stderrBuffer.toString());
-    StringBuilder sb = new StringBuilder().append(ProcessPrintsToStderr.LINES[0])
-        .append(ProcessPrintsToStderr.LINES[1]).append(ProcessPrintsToStderr.LINES[2]);
-    assertEquals(sb.toString(), stderrBuffer.toString());
-
-    // System.out.println("stdout=\n" + stdoutBuffer.toString());
-    assertEquals("", stdoutBuffer.toString());
-
-    // System.out.println("Closing streams");
-    this.process.getErrorStream().close();
-    this.process.getInputStream().close();
-
-    this.process.destroy();
-  }
-
-  @Test
-  public void capturesBothWhileProcessIsAlive() throws Exception {
-    assumeFalse(SystemUtils.isWindows());
-
-    this.process = new ProcessBuilder(createCommandLine(ProcessPrintsToBoth.class)).start();
-
-    final StringBuffer stderrBuffer = new StringBuffer();
-    InputListener stderrListener = new InputListener() {
-      @Override
-      public void notifyInputLine(String line) {
-        stderrBuffer.append(line);
-      }
-    };
-
-    final StringBuffer stdoutBuffer = new StringBuffer();
-    InputListener stdoutListener = new InputListener() {
-      @Override
-      public void notifyInputLine(String line) {
-        stdoutBuffer.append(line);
-      }
-    };
-
-    this.stderr =
-        new ProcessStreamReader.Builder(this.process).inputStream(this.process.getErrorStream())
-            .inputListener(stderrListener).readingMode(ReadingMode.NON_BLOCKING).build();
-
-    this.stdout =
-        new ProcessStreamReader.Builder(this.process).inputStream(this.process.getInputStream())
-            .inputListener(stdoutListener).readingMode(ReadingMode.NON_BLOCKING).build();
-
-    this.stderr.start();
-    this.stdout.start();
-
-    // wait for process to die
-    assertEventuallyFalse("Process never died", new Callable<Boolean>() {
-      @Override
-      public Boolean call() throws Exception {
-        return ProcessUtils.isProcessAlive(process);
-      }
-    }, WAIT_FOR_PROCESS_TO_DIE_TIMEOUT, INTERVAL);
-
-    final int exitValue = this.process.exitValue();
-    assertEquals(0, exitValue);
-
-    this.stderr.join(READER_JOIN_TIMEOUT);
-    assertFalse(this.stderr.isRunning());
-
-    this.stdout.join(READER_JOIN_TIMEOUT);
-    assertFalse(this.stdout.isRunning());
-
-    // System.out.println("Stopping ProcessStreamReader");
-    this.stderr.stop();
-    this.stdout.stop();
-
-    // System.out.println("stderr=\n" + stderrBuffer.toString());
-    StringBuilder sb = new StringBuilder().append(ProcessPrintsToBoth.ERR_LINES[0])
-        .append(ProcessPrintsToBoth.ERR_LINES[1]).append(ProcessPrintsToBoth.ERR_LINES[2]);
-    assertEquals(sb.toString(), stderrBuffer.toString());
-
-    // System.out.println("stdout=\n" + stdoutBuffer.toString());
-    sb = new StringBuilder().append(ProcessPrintsToBoth.OUT_LINES[0])
-        .append(ProcessPrintsToBoth.OUT_LINES[1]).append(ProcessPrintsToBoth.OUT_LINES[2]);
-    assertEquals(sb.toString(), stdoutBuffer.toString());
-
-    // System.out.println("Closing streams");
-    this.process.getErrorStream().close();
-    this.process.getInputStream().close();
-
-    this.process.destroy();
-  }
-
-  @Test
-  public void capturesStderrWhenProcessFailsDuringStart() throws Exception {
-    assumeFalse(SystemUtils.isWindows());
-
-    this.process = new ProcessBuilder(createCommandLine(ProcessThrowsError.class)).start();
-
-    final StringBuffer stderrBuffer = new StringBuffer();
-    InputListener stderrListener = new InputListener() {
-      @Override
-      public void notifyInputLine(String line) {
-        stderrBuffer.append(line);
-      }
-    };
-
-    final StringBuffer stdoutBuffer = new StringBuffer();
-    InputListener stdoutListener = new InputListener() {
-      @Override
-      public void notifyInputLine(String line) {
-        stdoutBuffer.append(line);
-      }
-    };
-
-    this.stderr =
-        new ProcessStreamReader.Builder(this.process).inputStream(this.process.getErrorStream())
-            .inputListener(stderrListener).readingMode(ReadingMode.NON_BLOCKING).build();
-
-    this.stdout =
-        new ProcessStreamReader.Builder(this.process).inputStream(this.process.getInputStream())
-            .inputListener(stdoutListener).readingMode(ReadingMode.NON_BLOCKING).build();
-
-    this.stderr.start();
-    this.stdout.start();
-
-    // wait for process to die
-    assertEventuallyFalse("Process never died", new Callable<Boolean>() {
-      @Override
-      public Boolean call() throws Exception {
-        return ProcessUtils.isProcessAlive(process);
-      }
-    }, WAIT_FOR_PROCESS_TO_DIE_TIMEOUT, INTERVAL);
-
-    final int exitValue = this.process.exitValue();
-    assertNotEquals(0, exitValue);
-
-    this.stderr.join(READER_JOIN_TIMEOUT);
-    assertFalse(this.stderr.isRunning());
-
-    this.stdout.join(READER_JOIN_TIMEOUT);
-    assertFalse(this.stdout.isRunning());
-
-    // System.out.println("Stopping ProcessStreamReader");
-    this.stderr.stop();
-    this.stdout.stop();
-
-    // System.out.println("stderr=\n" + stderrBuffer.toString());
-    assertTrue(stderrBuffer.toString() + " does not contain " + ProcessThrowsError.ERROR_MSG,
-        stderrBuffer.toString().contains(ProcessThrowsError.ERROR_MSG));
-
-    // System.out.println("stdout=\n" + stdoutBuffer.toString());
-
-    // System.out.println("Closing streams");
-    this.process.getErrorStream().close();
-    this.process.getInputStream().close();
-
-    this.process.destroy();
-  }
-}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/BlockingProcessStreamReaderWindowsTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/BlockingProcessStreamReaderWindowsTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/BlockingProcessStreamReaderWindowsTest.java
new file mode 100644
index 0000000..60c94e4
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/BlockingProcessStreamReaderWindowsTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.internal.process;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.geode.internal.lang.SystemUtils.isWindows;
+import static org.apache.geode.internal.process.ProcessStreamReader.ReadingMode.BLOCKING;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assume.assumeTrue;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeoutException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.internal.process.ProcessStreamReader.ReadingMode;
+import org.apache.geode.test.junit.categories.IntegrationTest;
+
+/**
+ * Functional integration test {@link #hangsOnWindows} for BlockingProcessStreamReader which
+ * verifies TRAC #51967: "GFSH start hangs on Windows." The hang is supposedly caused by a JDK bug
+ * in which a thread invoking readLine() will block forever and ignore any interrupts. The thread
+ * will respond to interrupts as expected on Mac, Linux and Solaris.
+ *
+ * @see BlockingProcessStreamReaderIntegrationTest
+ * @see NonBlockingProcessStreamReaderIntegrationTest
+ *
+ * @since GemFire 8.2
+ */
+@Category(IntegrationTest.class)
+public class BlockingProcessStreamReaderWindowsTest
+    extends AbstractProcessStreamReaderIntegrationTest {
+
+  /** Timeout to confirm hang on Windows */
+  private static final int HANG_TIMEOUT_SECONDS = 10;
+
+  private ExecutorService futures;
+
+  @Before
+  public void setUp() throws Exception {
+    assumeTrue(isWindows());
+
+    futures = Executors.newSingleThreadExecutor();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    if (futures != null) {
+      assertThat(futures.shutdownNow()).isEmpty();
+    }
+  }
+
+  @Test
+  public void hangsOnWindows() throws Exception {
+    // arrange
+    givenRunningProcessWithStreamReaders(ProcessSleeps.class);
+
+    // act
+    Future<Boolean> future = futures.submit(() -> {
+      process.getOutputStream().close();
+      process.getErrorStream().close();
+      process.getInputStream().close();
+      return true;
+    });
+
+    // assert
+    assertThatThrownBy(() -> future.get(HANG_TIMEOUT_SECONDS, SECONDS))
+        .isInstanceOf(TimeoutException.class);
+  }
+
+  @Override
+  protected ReadingMode getReadingMode() {
+    return BLOCKING;
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/ControlFileWatchdogIntegrationTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/ControlFileWatchdogIntegrationTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/ControlFileWatchdogIntegrationTest.java
new file mode 100644
index 0000000..46c81f7
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/ControlFileWatchdogIntegrationTest.java
@@ -0,0 +1,241 @@
+/*
+ * 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.internal.process;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.awaitility.Awaitility.await;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import java.io.File;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.TemporaryFolder;
+
+import org.apache.geode.internal.process.ControlFileWatchdog.ControlRequestHandler;
+import org.apache.geode.internal.process.io.EmptyFileWriter;
+import org.apache.geode.test.junit.categories.IntegrationTest;
+
+/**
+ * Functional integration tests for {@link ControlFileWatchdog}.
+ */
+@Category(IntegrationTest.class)
+public class ControlFileWatchdogIntegrationTest {
+
+  private static final int TWO_MINUTES_MILLIS = 2 * 60 * 1000;
+
+  private File directory;
+  private String requestFileName;
+  private File requestFile;
+  private ControlRequestHandler requestHandler;
+  private boolean stopAfterRequest;
+
+  @Rule
+  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  @Before
+  public void before() throws Exception {
+    directory = temporaryFolder.getRoot();
+    requestFileName = "myFile";
+    requestFile = new File(directory, requestFileName);
+    requestHandler = mock(ControlRequestHandler.class);
+    stopAfterRequest = false;
+  }
+
+  @Test
+  public void isAlive_returnsFalse_beforeStart() throws Exception {
+    // arrange
+    ControlFileWatchdog watchdog =
+        new ControlFileWatchdog(directory, requestFileName, requestHandler, stopAfterRequest);
+
+    // act: nothing
+
+    // assert
+    assertThat(watchdog.isAlive()).isFalse();
+  }
+
+  @Test
+  public void isAlive_returnsTrue_afterStart() throws Exception {
+    // arrange
+    ControlFileWatchdog watchdog =
+        new ControlFileWatchdog(directory, requestFileName, requestHandler, stopAfterRequest);
+
+    // act
+    watchdog.start();
+
+    // assert
+    assertThat(watchdog.isAlive()).isTrue();
+  }
+
+  @Test
+  public void isAlive_returnsFalse_afterStop() throws Exception {
+    // arrange
+    ControlFileWatchdog watchdog =
+        new ControlFileWatchdog(directory, requestFileName, requestHandler, stopAfterRequest);
+    watchdog.start();
+
+    // act
+    watchdog.stop();
+
+    // assert
+    assertThat(watchdog.isAlive()).isFalse();
+  }
+
+  @Test
+  public void nullFileName_throwsIllegalArgumentException() throws Exception {
+    // arrange
+    requestFileName = null;
+
+    // act/assert
+    assertThatThrownBy(
+        () -> new ControlFileWatchdog(directory, requestFileName, requestHandler, stopAfterRequest))
+            .isInstanceOf(IllegalArgumentException.class);
+  }
+
+  @Test
+  public void nullDirectory_throwsIllegalArgumentException() throws Exception {
+    // arrange
+    directory = null;
+
+    // act/assert
+    assertThatThrownBy(
+        () -> new ControlFileWatchdog(directory, requestFileName, requestHandler, stopAfterRequest))
+            .isInstanceOf(IllegalArgumentException.class);
+  }
+
+  @Test
+  public void nullRequestHandler_throwsIllegalArgumentException() throws Exception {
+    // arrange
+    requestHandler = null;
+
+    // act/assert
+    assertThatThrownBy(
+        () -> new ControlFileWatchdog(directory, requestFileName, requestHandler, stopAfterRequest))
+            .isInstanceOf(IllegalArgumentException.class);
+  }
+
+  @Test
+  public void invokesRequestHandler_afterFileCreation() throws Exception {
+    // arrange
+    requestHandler = mock(ControlRequestHandler.class);
+    ControlFileWatchdog watchdog =
+        new ControlFileWatchdog(directory, requestFileName, requestHandler, stopAfterRequest);
+    watchdog.start();
+
+    // act
+    File file = new EmptyFileWriter(requestFile).createNewFile();
+
+    // assert
+    verify(requestHandler, timeout(TWO_MINUTES_MILLIS)).handleRequest();
+    assertThat(file).doesNotExist();
+  }
+
+  @Test
+  public void deletesFile_afterInvokingRequestHandler() throws Exception {
+    // arrange
+    requestHandler = mock(ControlRequestHandler.class);
+    ControlFileWatchdog watchdog =
+        new ControlFileWatchdog(directory, requestFileName, requestHandler, stopAfterRequest);
+    watchdog.start();
+
+    // act
+    File file = new EmptyFileWriter(requestFile).createNewFile();
+
+    // assert
+    verify(requestHandler, timeout(TWO_MINUTES_MILLIS)).handleRequest();
+    await().atMost(2, MINUTES).until(() -> assertThat(file).doesNotExist());
+    assertThat(file).doesNotExist();
+  }
+
+  @Test
+  public void doesNotInvokeRequestHandler_whileFileDoesNotExist() throws Exception {
+    // arrange
+    ControlFileWatchdog watchdog =
+        new ControlFileWatchdog(directory, requestFileName, requestHandler, stopAfterRequest);
+
+    // act
+    watchdog.start();
+
+    // assert
+    verifyZeroInteractions(requestHandler); // would be prefer to wait some time
+  }
+
+  @Test
+  public void nothingHappens_beforeStart() throws Exception {
+    // arrange
+    requestHandler = mock(ControlRequestHandler.class);
+    new ControlFileWatchdog(directory, requestFileName, requestHandler, stopAfterRequest);
+
+    // act
+    File file = new EmptyFileWriter(requestFile).createNewFile();
+
+    // assert
+    verifyZeroInteractions(requestHandler); // would be prefer to wait some time
+    assertThat(file).exists();
+  }
+
+  @Test
+  public void stops_afterInvokingRequestHandler_whenStopAfterRequest() throws Exception {
+    // arrange
+    requestHandler = mock(ControlRequestHandler.class);
+    stopAfterRequest = true;
+    ControlFileWatchdog watchdog =
+        new ControlFileWatchdog(directory, requestFileName, requestHandler, stopAfterRequest);
+    watchdog.start();
+
+    // act
+    File file = new EmptyFileWriter(requestFile).createNewFile();
+
+    // assert
+    verify(requestHandler, timeout(TWO_MINUTES_MILLIS)).handleRequest();
+    await().atMost(2, MINUTES).until(() -> assertThat(watchdog.isAlive()).isFalse());
+    assertThat(file).doesNotExist();
+  }
+
+  @Test
+  public void doesNotStop_afterInvokingRequestHandler_whenNotStopAfterRequest() throws Exception {
+    // arrange
+    requestHandler = mock(ControlRequestHandler.class);
+    ControlFileWatchdog watchdog =
+        new ControlFileWatchdog(directory, requestFileName, requestHandler, stopAfterRequest);
+    watchdog.start();
+
+    // act
+    File file = new EmptyFileWriter(requestFile).createNewFile();
+
+    // assert
+    verify(requestHandler, timeout(TWO_MINUTES_MILLIS)).handleRequest();
+    assertThat(watchdog.isAlive()).isTrue();
+    assertThat(file).doesNotExist();
+  }
+
+  @Test
+  public void toStringIsUsefulForDebugging() throws Exception {
+    // arrange
+    ControlFileWatchdog watchdog =
+        new ControlFileWatchdog(directory, requestFileName, requestHandler, stopAfterRequest);
+
+    // act/assert
+    assertThat(watchdog.toString()).isNotEmpty().contains("directory=").contains("file=")
+        .contains("alive=").contains("stopAfterRequest=");
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/ControllableProcessIntegrationTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/ControllableProcessIntegrationTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/ControllableProcessIntegrationTest.java
new file mode 100644
index 0000000..903f08f
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/ControllableProcessIntegrationTest.java
@@ -0,0 +1,196 @@
+/*
+ * 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.internal.process;
+
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.apache.geode.internal.process.ProcessUtils.identifyPid;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.TemporaryFolder;
+
+import org.apache.geode.distributed.AbstractLauncher.ServiceState;
+import org.apache.geode.internal.process.ControlFileWatchdog.ControlRequestHandler;
+import org.apache.geode.internal.process.io.EmptyFileWriter;
+import org.apache.geode.test.junit.categories.IntegrationTest;
+
+@Category(IntegrationTest.class)
+public class ControllableProcessIntegrationTest {
+
+  private LocalProcessLauncher localProcessLauncher;
+  private ControlFileWatchdog stopRequestFileWatchdog;
+  private ControlFileWatchdog statusRequestFileWatchdog;
+  private ProcessType processType;
+  private File directory;
+  private File pidFile;
+  private int pid;
+  private File statusRequestFile;
+  private File stopRequestFile;
+  private File statusFile;
+
+  @Rule
+  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  @Before
+  public void before() throws Exception {
+    processType = ProcessType.LOCATOR;
+    directory = temporaryFolder.getRoot();
+    pidFile = new File(directory, processType.getPidFileName());
+    pid = identifyPid();
+    statusRequestFile = new File(directory, processType.getStatusRequestFileName());
+    stopRequestFile = new File(directory, processType.getStopRequestFileName());
+    statusFile = new File(directory, processType.getStatusFileName());
+    statusRequestFileWatchdog = new ControlFileWatchdog(directory,
+        processType.getStatusRequestFileName(), mock(ControlRequestHandler.class), false);
+    stopRequestFileWatchdog = new ControlFileWatchdog(directory,
+        processType.getStopRequestFileName(), mock(ControlRequestHandler.class), false);
+  }
+
+  @Test
+  public void getDirectoryExists() throws Exception {
+    // arrange
+    localProcessLauncher = new LocalProcessLauncher(pidFile, false);
+
+    // act
+    ControllableProcess controllable = new ControllableProcess(directory, processType,
+        localProcessLauncher, stopRequestFileWatchdog, statusRequestFileWatchdog);
+
+    // assert
+    assertThat(controllable.getDirectory()).isEqualTo(directory);
+  }
+
+  @Test
+  public void creationDeletesStatusRequestFileInDirectory() throws Exception {
+    // arrange
+    localProcessLauncher = new LocalProcessLauncher(pidFile, false);
+    File file = new EmptyFileWriter(statusRequestFile).createNewFile();
+
+    // act
+    new ControllableProcess(directory, processType, localProcessLauncher, stopRequestFileWatchdog,
+        statusRequestFileWatchdog);
+
+    // assert
+    assertThat(file).doesNotExist();
+  }
+
+  @Test
+  public void creationDeletesStatusResponseFileInDirectory() throws Exception {
+    // arrange
+    localProcessLauncher = new LocalProcessLauncher(pidFile, false);
+    File file = new EmptyFileWriter(statusFile).createNewFile();
+
+    // act
+    new ControllableProcess(directory, processType, localProcessLauncher, stopRequestFileWatchdog,
+        statusRequestFileWatchdog);
+
+    // assert
+    assertThat(file).doesNotExist();
+  }
+
+  @Test
+  public void creationDeletesStopRequestFileInDirectory() throws Exception {
+    // arrange
+    localProcessLauncher = new LocalProcessLauncher(pidFile, false);
+    File file = new EmptyFileWriter(stopRequestFile).createNewFile();
+
+    // act
+    new ControllableProcess(directory, processType, localProcessLauncher, stopRequestFileWatchdog,
+        statusRequestFileWatchdog);
+
+    // assert
+    assertThat(file).doesNotExist();
+  }
+
+  @Test
+  public void getPidReturnsPid() throws Exception {
+    // arrange
+    localProcessLauncher = new LocalProcessLauncher(pidFile, false);
+
+    // act
+    ControllableProcess controllable = new ControllableProcess(directory, processType,
+        localProcessLauncher, stopRequestFileWatchdog, statusRequestFileWatchdog);
+
+    // assert
+    assertThat(controllable.getPid()).isEqualTo(pid);
+  }
+
+  @Test
+  public void getPidFileExists() throws Exception {
+    // arrange
+    localProcessLauncher = new LocalProcessLauncher(pidFile, false);
+
+    // act
+    ControllableProcess controllable = new ControllableProcess(directory, processType,
+        localProcessLauncher, stopRequestFileWatchdog, statusRequestFileWatchdog);
+
+    // assert
+    assertThat(controllable.getPidFile()).exists().hasContent(String.valueOf(pid));
+  }
+
+  @Test
+  public void stopsBothControlFileWatchdogs() throws Exception {
+    // arrange
+    ControlFileWatchdog stopRequestFileWatchdog = new ControlFileWatchdog(directory,
+        "stopRequestFile", mock(ControlRequestHandler.class), false);
+    ControlFileWatchdog statusRequestFileWatchdog = new ControlFileWatchdog(directory,
+        "statusRequestFile", mock(ControlRequestHandler.class), false);
+
+    stopRequestFileWatchdog = spy(stopRequestFileWatchdog);
+    statusRequestFileWatchdog = spy(statusRequestFileWatchdog);
+
+    localProcessLauncher = new LocalProcessLauncher(pidFile, false);
+
+    ControllableProcess controllable = new ControllableProcess(directory, processType,
+        localProcessLauncher, stopRequestFileWatchdog, statusRequestFileWatchdog);
+
+    // act
+    controllable.stop();
+
+    // assert
+    verify(stopRequestFileWatchdog).stop();
+    verify(statusRequestFileWatchdog).stop();
+  }
+
+  @Test
+  public void statusRequestFileIsDeletedAndStatusFileIsCreated() throws Exception {
+    // arrange
+    File statusRequestFile = new File(directory, processType.getStatusRequestFileName());
+    File statusFile = new File(directory, processType.getStatusFileName());
+
+    ServiceState mockServiceState = mock(ServiceState.class);
+    when(mockServiceState.toJson()).thenReturn("json");
+    ControlNotificationHandler mockHandler = mock(ControlNotificationHandler.class);
+    when(mockHandler.handleStatus()).thenReturn(mockServiceState);
+    new ControllableProcess(mockHandler, directory, processType, false);
+
+    // act
+    boolean created = statusRequestFile.createNewFile();
+
+    // assert
+    assertThat(created).isTrue();
+    await().atMost(2, MINUTES).until(() -> assertThat(statusRequestFile).doesNotExist());
+    assertThat(statusFile).exists();
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/FileProcessControllerIntegrationJUnitTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/FileProcessControllerIntegrationJUnitTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/FileProcessControllerIntegrationJUnitTest.java
deleted file mode 100755
index 6ed12d4..0000000
--- a/geode-core/src/test/java/org/apache/geode/internal/process/FileProcessControllerIntegrationJUnitTest.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * 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.internal.process;
-
-import static com.googlecode.catchexception.CatchException.*;
-import static org.awaitility.Awaitility.*;
-import static java.util.concurrent.TimeUnit.*;
-import static org.assertj.core.api.Assertions.*;
-import static org.mockito.Mockito.*;
-import static org.hamcrest.Matchers.*;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.TemporaryFolder;
-import org.junit.rules.TestName;
-
-import org.apache.geode.distributed.LocatorLauncher;
-import org.apache.geode.distributed.AbstractLauncher.Status;
-import org.apache.geode.distributed.LocatorLauncher.Builder;
-import org.apache.geode.distributed.LocatorLauncher.LocatorState;
-import org.apache.geode.test.junit.categories.IntegrationTest;
-
-/**
- * Integration tests for FileProcessController.
- */
-@Category(IntegrationTest.class)
-public class FileProcessControllerIntegrationJUnitTest {
-
-  private ProcessType processType;
-  private ExecutorService executor;
-
-  @Rule
-  public TemporaryFolder temporaryFolder = new TemporaryFolder();
-
-  @Rule
-  public TestName testName = new TestName();
-
-  @Before
-  public void setUp() throws Exception {
-    this.processType = ProcessType.LOCATOR;
-  }
-
-  @After
-  public void tearDown() throws Exception {
-    if (this.executor != null) {
-      this.executor.shutdownNow();
-    }
-  }
-
-  @Test
-  public void statusShouldAwaitTimeoutWhileFileIsEmpty() throws Exception {
-    // given: FileProcessController with empty pidFile
-    int pid = ProcessUtils.identifyPid();
-    File emptyPidFile = this.temporaryFolder.newFile(this.processType.getPidFileName());
-    FileControllerParameters params = mock(FileControllerParameters.class);
-    when(params.getPidFile()).thenReturn(emptyPidFile);
-    when(params.getProcessId()).thenReturn(pid);
-    when(params.getProcessType()).thenReturn(this.processType);
-    when(params.getWorkingDirectory()).thenReturn(this.temporaryFolder.getRoot());
-
-    FileProcessController controller = new FileProcessController(params, 1, 10, MILLISECONDS);
-
-    // when
-    verifyException(controller).status();
-
-    // then: we expect TimeoutException to be thrown
-    assertThat((Exception) caughtException()).isInstanceOf(TimeoutException.class)
-        .hasMessageContaining("Timed out waiting for process to create").hasNoCause();
-  }
-
-  @Test
-  public void statusShouldReturnJsonFromStatusFile() throws Exception {
-    // given: FileProcessController with pidFile containing real pid
-    int pid = ProcessUtils.identifyPid();
-    File pidFile = this.temporaryFolder.newFile(this.processType.getPidFileName());
-    writeToFile(pidFile, String.valueOf(pid));
-
-    FileControllerParameters params = mock(FileControllerParameters.class);
-    when(params.getPidFile()).thenReturn(pidFile);
-    when(params.getProcessId()).thenReturn(pid);
-    when(params.getProcessType()).thenReturn(this.processType);
-    when(params.getWorkingDirectory()).thenReturn(this.temporaryFolder.getRoot());
-
-    FileProcessController controller = new FileProcessController(params, pid, 1, MINUTES);
-
-    // when: status is called in one thread and json is written to the file
-    AtomicReference<String> status = new AtomicReference<String>();
-    AtomicReference<Exception> exception = new AtomicReference<Exception>();
-    this.executor = Executors.newSingleThreadExecutor();
-    this.executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        try {
-          status.set(controller.status());
-        } catch (Exception e) {
-          exception.set(e);
-        }
-      }
-    });
-
-    // write status
-    String statusJson = generateStatusJson();
-    File statusFile = this.temporaryFolder.newFile(this.processType.getStatusFileName());
-    writeToFile(statusFile, statusJson);
-
-    // then: returned status should be the json in the file
-    assertThat(exception.get()).isNull();
-    with().pollInterval(10, MILLISECONDS).await().atMost(2, MINUTES).untilAtomic(status,
-        equalTo(statusJson));
-    assertThat(status.get()).isEqualTo(statusJson);
-    System.out.println(statusJson);
-  }
-
-  private static void writeToFile(final File file, final String value) throws IOException {
-    final FileWriter writer = new FileWriter(file);
-    writer.write(value);
-    writer.flush();
-    writer.close();
-  }
-
-  private static String generateStatusJson() {
-    Builder builder = new Builder();
-    LocatorLauncher defaultLauncher = builder.build();
-    Status status = Status.ONLINE;
-    LocatorState locatorState = new LocatorState(defaultLauncher, status);
-    return locatorState.toJson();
-  }
-}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/FileProcessControllerIntegrationTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/FileProcessControllerIntegrationTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/FileProcessControllerIntegrationTest.java
new file mode 100755
index 0000000..6eda29f
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/FileProcessControllerIntegrationTest.java
@@ -0,0 +1,249 @@
+/*
+ * 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.internal.process;
+
+import static com.googlecode.catchexception.CatchException.caughtException;
+import static com.googlecode.catchexception.CatchException.verifyException;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.awaitility.Awaitility;
+import org.awaitility.core.ConditionFactory;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.ErrorCollector;
+import org.junit.rules.TemporaryFolder;
+import org.junit.rules.TestName;
+
+import org.apache.geode.distributed.AbstractLauncher.Status;
+import org.apache.geode.distributed.LocatorLauncher;
+import org.apache.geode.distributed.LocatorLauncher.Builder;
+import org.apache.geode.distributed.LocatorLauncher.LocatorState;
+import org.apache.geode.internal.process.io.EmptyFileWriter;
+import org.apache.geode.internal.process.io.IntegerFileWriter;
+import org.apache.geode.internal.process.io.StringFileWriter;
+import org.apache.geode.test.junit.categories.IntegrationTest;
+
+/**
+ * Integration tests for {@link FileProcessController}.
+ *
+ * <p>
+ * This test shows one of the more appropriate uses of ErrorCollector Rule -- catching any failed
+ * assertions in another thread that isn't the main JUnit thread.
+ */
+@Category(IntegrationTest.class)
+public class FileProcessControllerIntegrationTest {
+
+  private static final String STATUS_JSON = generateStatusJson();
+
+  private final AtomicReference<String> statusRef = new AtomicReference<>();
+
+  private File pidFile;
+  private File statusFile;
+  private File statusRequestFile;
+  private File stopRequestFile;
+  private int pid;
+  private FileControllerParameters params;
+  private ExecutorService executor;
+
+  @Rule
+  public ErrorCollector errorCollector = new ErrorCollector();
+
+  @Rule
+  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  @Rule
+  public TestName testName = new TestName();
+
+  @Before
+  public void setUp() throws Exception {
+    ProcessType processType = ProcessType.LOCATOR;
+    File directory = temporaryFolder.getRoot();
+    pidFile = new File(directory, processType.getPidFileName());
+    statusFile = new File(directory, processType.getStatusFileName());
+    statusRequestFile = new File(directory, processType.getStatusRequestFileName());
+    stopRequestFile = new File(directory, processType.getStopRequestFileName());
+    pid = ProcessUtils.identifyPid();
+
+    params = mock(FileControllerParameters.class);
+    when(params.getPidFile()).thenReturn(pidFile);
+    when(params.getProcessId()).thenReturn(pid);
+    when(params.getProcessType()).thenReturn(processType);
+    when(params.getDirectory()).thenReturn(temporaryFolder.getRoot());
+
+    executor = Executors.newSingleThreadExecutor();
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    assertThat(executor.shutdownNow()).isEmpty();
+  }
+
+  @Test
+  public void statusShouldAwaitTimeoutWhileFileIsEmpty() throws Exception {
+    // given: FileProcessController with empty pidFile
+    FileProcessController controller = new FileProcessController(params, pid, 10, MILLISECONDS);
+
+    // when:
+    verifyException(controller).status();
+
+    // then: we expect TimeoutException to be thrown
+    assertThat((Exception) caughtException()).isInstanceOf(TimeoutException.class)
+        .hasMessageContaining("Timed out waiting for process to create").hasNoCause();
+  }
+
+  @Test
+  public void statusShouldReturnJsonFromStatusFile() throws Exception {
+    // given: FileProcessController with pidFile containing real pid
+    new IntegerFileWriter(pidFile).writeToFile(pid);
+    FileProcessController controller = new FileProcessController(params, pid, 2, MINUTES);
+
+    // when: status is called in one thread
+    executor.execute(() -> {
+      try {
+        statusRef.set(controller.status());
+      } catch (Exception e) {
+        errorCollector.addError(e);
+      }
+    });
+
+    // and: json is written to the status file
+    new StringFileWriter(statusFile).writeToFile(STATUS_JSON);
+
+    // then: returned status should be the json in the file
+    await().until(() -> assertThat(statusRef.get()).isEqualTo(STATUS_JSON));
+  }
+
+  /**
+   * This is a new test written for GEODE-3413: "Overhaul launcher tests and process tests."
+   * Unfortunately, it hangs so I have filed GEODE-3278. This test should be used to fix and verify
+   * that bug.
+   */
+  @Ignore("GEODE-3278: Empty status file causes status server and status locator to hang")
+  @Test
+  public void emptyStatusFileCausesStatusToHang() throws Exception {
+    // given: FileProcessController with pidFile containing real pid
+    new IntegerFileWriter(pidFile).writeToFile(pid);
+    FileProcessController controller = new FileProcessController(params, pid, 2, MINUTES);
+
+    // when: status is called in one thread
+    executor.execute(() -> {
+      try {
+        statusRef.set(controller.status());
+      } catch (Exception e) {
+        errorCollector.addError(e);
+      }
+    });
+
+    // and: json is written to the status file
+    new EmptyFileWriter(statusFile).createNewFile();
+
+    // then: returned status should be the json in the file
+    await().until(() -> assertThat(statusRef.get()).isEqualTo(STATUS_JSON));
+  }
+
+  @Test
+  public void stopCreatesStopRequestFile() throws Exception {
+    // arrange
+    FileProcessController controller = new FileProcessController(params, pid);
+    assertThat(stopRequestFile).doesNotExist();
+
+    // act
+    controller.stop();
+
+    // assert
+    assertThat(stopRequestFile).exists();
+  }
+
+  @Test
+  public void stop_withStopRequestFileExists_doesNotFail() throws Exception {
+    // arrange
+    FileProcessController controller = new FileProcessController(params, pid);
+    assertThat(stopRequestFile.createNewFile()).isTrue();
+
+    // act
+    controller.stop();
+
+    // assert
+    assertThat(stopRequestFile).exists();
+  }
+
+  @Test
+  public void status_withStatusRequestFileExists_doesNotFail() throws Exception {
+    // arrange
+    FileProcessController controller = new FileProcessController(params, pid);
+    assertThat(statusRequestFile.createNewFile()).isTrue();
+
+    // act
+    executor.execute(() -> {
+      try {
+        statusRef.set(controller.status());
+      } catch (Exception e) {
+        errorCollector.addError(e);
+      }
+    });
+
+    new StringFileWriter(statusFile).writeToFile(STATUS_JSON);
+
+    // assert
+    assertThat(statusRequestFile).exists();
+  }
+
+  @Test
+  public void statusCreatesStatusRequestFile() throws Exception {
+    // arrange
+    new IntegerFileWriter(pidFile).writeToFile(pid);
+    FileProcessController controller = new FileProcessController(params, pid, 2, MINUTES);
+
+    // act
+    executor.execute(() -> {
+      try {
+        assertThatThrownBy(() -> statusRef.set(controller.status()))
+            .isInstanceOf(InterruptedException.class);
+      } catch (Throwable t) {
+        errorCollector.addError(t);
+      }
+    });
+
+    // assert
+    await().until(() -> assertThat(statusRequestFile).exists());
+  }
+
+  private ConditionFactory await() {
+    return Awaitility.await().atMost(2, MINUTES);
+  }
+
+  private static String generateStatusJson() {
+    Builder builder = new Builder();
+    LocatorLauncher defaultLauncher = builder.build();
+    Status status = Status.ONLINE;
+    LocatorState locatorState = new LocatorState(defaultLauncher, status);
+    return locatorState.toJson();
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/FileProcessControllerTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/FileProcessControllerTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/FileProcessControllerTest.java
new file mode 100644
index 0000000..b742e1b
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/FileProcessControllerTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.internal.process;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.geode.internal.process.FileProcessController.DEFAULT_STATUS_TIMEOUT_MILLIS;
+import static org.apache.geode.internal.process.ProcessUtils.identifyPid;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.lang.AttachAPINotFoundException;
+import org.apache.geode.test.junit.categories.UnitTest;
+
+/**
+ * Unit tests for {@link FileProcessController}.
+ */
+@Category(UnitTest.class)
+public class FileProcessControllerTest {
+
+  private FileProcessController controller;
+  private FileControllerParameters mockParameters;
+  private int pid;
+  private long timeout;
+  private TimeUnit units;
+
+  @Before
+  public void before() throws Exception {
+    mockParameters = mock(FileControllerParameters.class);
+    pid = identifyPid();
+    timeout = 0;
+    units = SECONDS;
+
+    controller = new FileProcessController(mockParameters, pid, timeout, units);
+  }
+
+  @Test
+  public void pidLessThanOne_throwsIllegalArgumentException() throws Exception {
+    pid = 0;
+
+    assertThatThrownBy(() -> new FileProcessController(mockParameters, pid, timeout, units))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Invalid pid '" + pid + "' specified");
+  }
+
+  @Test
+  public void getProcessId_returnsPid() throws Exception {
+    assertThat(controller.getProcessId()).isEqualTo(pid);
+  }
+
+  @Test
+  public void checkPidSupport_throwsAttachAPINotFoundException() throws Exception {
+    assertThatThrownBy(() -> controller.checkPidSupport())
+        .isInstanceOf(AttachAPINotFoundException.class);
+  }
+
+  @Test
+  public void statusTimeoutMillis_defaultsToOneMinute() throws Exception {
+    FileProcessController controller = new FileProcessController(mockParameters, pid);
+
+    assertThat(controller.getStatusTimeoutMillis()).isEqualTo(DEFAULT_STATUS_TIMEOUT_MILLIS);
+  }
+
+  @Test
+  public void timeoutLessThanZero_throwsIllegalArgumentException() throws Exception {
+    timeout = -1;
+
+    assertThatThrownBy(() -> new FileProcessController(mockParameters, pid, timeout, units))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Invalid timeout '" + timeout + "' specified");
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessControllerJUnitTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessControllerJUnitTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessControllerJUnitTest.java
deleted file mode 100755
index b132647..0000000
--- a/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessControllerJUnitTest.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * 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.internal.process;
-
-import static org.junit.Assert.*;
-
-import java.lang.management.ManagementFactory;
-import java.util.Set;
-
-import javax.management.MBeanAttributeInfo;
-import javax.management.MBeanInfo;
-import javax.management.MBeanOperationInfo;
-import javax.management.MBeanServer;
-import javax.management.ObjectInstance;
-import javax.management.ObjectName;
-import javax.management.Query;
-import javax.management.QueryExp;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.TestName;
-
-import org.apache.geode.internal.process.mbean.Process;
-import org.apache.geode.test.junit.categories.UnitTest;
-
-/**
- * Unit tests for LocalProcessController.
- * 
- * @since GemFire 7.0
- */
-@Category(UnitTest.class)
-public class LocalProcessControllerJUnitTest {
-
-  private MBeanServer server;
-  private ObjectName objectName;
-  private int pid;
-
-  @Rule
-  public TestName testName = new TestName();
-
-  @Before
-  public void setUp() throws Exception {
-    pid = ProcessUtils.identifyPid();
-    final Process process = new Process(pid, true);
-
-    this.objectName = ObjectName
-        .getInstance(getClass().getSimpleName() + ":testName=" + testName.getMethodName());
-    this.server = ManagementFactory.getPlatformMBeanServer();
-
-    final ObjectInstance instance = this.server.registerMBean(process, objectName);
-    assertNotNull(instance);
-  }
-
-  @After
-  public void tearDown() throws Exception {
-    this.server.unregisterMBean(objectName);
-  }
-
-  @Test
-  public void testProcessMBean() throws Exception {
-    // validate basics of the ProcessMBean
-    Set<ObjectName> mbeanNames = this.server.queryNames(objectName, null);
-    assertFalse("Zero matching mbeans", mbeanNames.isEmpty());
-    assertEquals(1, mbeanNames.size());
-    final ObjectName name = mbeanNames.iterator().next();
-
-    final MBeanInfo info = this.server.getMBeanInfo(name);
-
-    final MBeanOperationInfo[] operInfo = info.getOperations();
-    assertEquals(1, operInfo.length);
-    assertEquals("stop", operInfo[0].getName());
-
-    final MBeanAttributeInfo[] attrInfo = info.getAttributes();
-    assertEquals(2, attrInfo.length);
-    // The order of these attributes is indeterminate
-    assertTrue("Pid".equals(attrInfo[0].getName()) || "Process".equals(attrInfo[0].getName()));
-    assertTrue("Pid".equals(attrInfo[1].getName()) || "Process".equals(attrInfo[1].getName()));
-    assertNotNull(this.server.getAttribute(name, "Pid"));
-    assertNotNull(this.server.getAttribute(name, "Process"));
-
-    assertEquals(pid, this.server.getAttribute(name, "Pid"));
-    assertEquals(true, this.server.getAttribute(name, "Process"));
-
-    // validate query using only Pid attribute
-    QueryExp constraint = Query.eq(Query.attr("Pid"), Query.value(pid));
-    mbeanNames = this.server.queryNames(objectName, constraint);
-    assertFalse("Zero matching mbeans", mbeanNames.isEmpty());
-
-    // validate query with wrong Pid finds nothing
-    constraint = Query.eq(Query.attr("Pid"), Query.value(pid + 1));
-    mbeanNames = this.server.queryNames(objectName, constraint);
-    assertTrue("Found matching mbeans", mbeanNames.isEmpty());
-
-    // validate query using both attributes
-    constraint = Query.and(Query.eq(Query.attr("Process"), Query.value(true)),
-        Query.eq(Query.attr("Pid"), Query.value(pid)));
-    mbeanNames = this.server.queryNames(objectName, constraint);
-    assertFalse("Zero matching mbeans", mbeanNames.isEmpty());
-
-    // validate query with wrong attribute finds nothing
-    constraint = Query.and(Query.eq(Query.attr("Process"), Query.value(false)),
-        Query.eq(Query.attr("Pid"), Query.value(pid)));
-    mbeanNames = this.server.queryNames(objectName, constraint);
-    assertTrue("Found matching mbeans", mbeanNames.isEmpty());
-  }
-}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessLauncherDUnitTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessLauncherDUnitTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessLauncherDUnitTest.java
deleted file mode 100755
index e1f3e6e..0000000
--- a/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessLauncherDUnitTest.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * 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.internal.process;
-
-import static org.junit.Assert.*;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
-
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-
-import org.apache.geode.test.dunit.Host;
-import org.apache.geode.test.dunit.SerializableRunnable;
-import org.apache.geode.test.dunit.internal.JUnit4DistributedTestCase;
-import org.apache.geode.test.junit.categories.DistributedTest;
-
-/**
- * Multi-process tests for ProcessLauncher.
- * 
- * @since GemFire 7.0
- */
-@Category(DistributedTest.class)
-@SuppressWarnings("serial")
-public class LocalProcessLauncherDUnitTest extends JUnit4DistributedTestCase {
-
-  public LocalProcessLauncherDUnitTest() {
-    super();
-  }
-
-  @Override
-  public final void postSetUp() throws Exception {
-    new File(getClass().getSimpleName()).mkdir();
-  }
-
-  @Test
-  public void testExistingPidFileThrows() throws Exception {
-    final File pidFile =
-        new File(getClass().getSimpleName() + File.separator + "testExistingPidFileThrows.pid");
-    final String absolutePath = pidFile.getAbsolutePath();
-
-    assertFalse(pidFile.exists());
-    new LocalProcessLauncher(pidFile, false);
-    assertTrue(pidFile.exists());
-
-    Host.getHost(0).getVM(0).invoke(
-        new SerializableRunnable("LocalProcessLauncherDUnitTest#testExistingPidFileThrows") {
-          @Override
-          public void run() {
-            try {
-              new LocalProcessLauncher(new File(absolutePath), false);
-              fail("Two processes both succeeded in creating " + pidFile);
-            } catch (FileAlreadyExistsException e) {
-              // passed
-            } catch (IllegalStateException e) {
-              throw new AssertionError(e);
-            } catch (IOException e) {
-              throw new AssertionError(e);
-            } catch (PidUnavailableException e) {
-              throw new AssertionError(e);
-            }
-          }
-        });
-  }
-
-  @Test
-  public void testForceReplacesExistingPidFile() throws Exception {
-    final File pidFile = new File(
-        getClass().getSimpleName() + File.separator + "testForceReplacesExistingPidFile.pid");
-    final String absolutePath = pidFile.getAbsolutePath();
-
-    assertFalse(pidFile.exists());
-    final LocalProcessLauncher launcher = new LocalProcessLauncher(pidFile, false);
-    assertTrue(pidFile.exists());
-    assertNotNull(launcher);
-    final int pid = launcher.getPid();
-
-    Host.getHost(0).getVM(0).invoke(
-        new SerializableRunnable("LocalProcessLauncherDUnitTest#testForceReplacesExistingPidFile") {
-          @Override
-          public void run() {
-            try {
-              final LocalProcessLauncher launcher =
-                  new LocalProcessLauncher(new File(absolutePath), true);
-              assertNotSame(pid, launcher.getPid());
-              assertFalse(pid == launcher.getPid());
-
-              final FileReader fr = new FileReader(absolutePath);
-              final BufferedReader br = new BufferedReader(fr);
-              final int pidFromFile = Integer.parseInt(br.readLine());
-              br.close();
-
-              assertNotSame(pid, pidFromFile);
-              assertFalse(pid == pidFromFile);
-              assertEquals(launcher.getPid(), pidFromFile);
-            } catch (IllegalStateException e) {
-              throw new AssertionError(e);
-            } catch (IOException e) {
-              throw new AssertionError(e);
-            } catch (FileAlreadyExistsException e) {
-              throw new AssertionError(e);
-            } catch (PidUnavailableException e) {
-              throw new AssertionError(e);
-            }
-          }
-        });
-  }
-
-  @Test
-  public void testPidFileReadByOtherProcess() throws Exception {
-    final File pidFile =
-        new File(getClass().getSimpleName() + File.separator + "testPidFileReadByOtherProcess.pid");
-    final String absolutePath = pidFile.getAbsolutePath();
-
-    assertFalse(pidFile.exists());
-    final LocalProcessLauncher launcher = new LocalProcessLauncher(pidFile, false);
-    assertTrue(pidFile.exists());
-    assertNotNull(launcher);
-    final int pid = launcher.getPid();
-
-    Host.getHost(0).getVM(0).invoke(
-        new SerializableRunnable("LocalProcessLauncherDUnitTest#testPidFileReadByOtherProcess") {
-          @Override
-          public void run() {
-            try {
-              final FileReader fr = new FileReader(absolutePath);
-              final BufferedReader br = new BufferedReader(fr);
-              final int pidFromFile = Integer.parseInt(br.readLine());
-              br.close();
-              assertEquals(pid, pidFromFile);
-            } catch (FileNotFoundException e) {
-              throw new Error(e);
-            } catch (IOException e) {
-              throw new Error(e);
-            }
-          }
-        });
-  }
-}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessLauncherDistributedTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessLauncherDistributedTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessLauncherDistributedTest.java
new file mode 100755
index 0000000..deefa41
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessLauncherDistributedTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.internal.process;
+
+import static org.apache.geode.internal.process.ProcessUtils.identifyPid;
+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 java.io.File;
+import java.io.IOException;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.internal.process.io.IntegerFileReader;
+import org.apache.geode.test.dunit.DistributedTestCase;
+import org.apache.geode.test.dunit.VM;
+import org.apache.geode.test.junit.categories.DistributedTest;
+import org.apache.geode.test.junit.rules.serializable.SerializableTemporaryFolder;
+import org.apache.geode.test.junit.rules.serializable.SerializableTestName;
+
+/**
+ * Two-process functional tests for {@link LocalProcessLauncher}.
+ * 
+ * @since GemFire 7.0
+ */
+@Category(DistributedTest.class)
+public class LocalProcessLauncherDistributedTest extends DistributedTestCase {
+
+  private int pid;
+  private File pidFile;
+  private VM otherVM;
+
+  @Rule
+  public SerializableTemporaryFolder temporaryFolder = new SerializableTemporaryFolder();
+
+  @Rule
+  public SerializableTestName testName = new SerializableTestName();
+
+  @Before
+  public void before() throws Exception {
+    pid = identifyPid();
+    pidFile = new File(temporaryFolder.getRoot(), testName.getMethodName() + ".pid");
+    otherVM = getHost(0).getVM(0);
+  }
+
+  @Test
+  public void existingPidFileThrowsFileAlreadyExistsException() throws Exception {
+    // arrange
+    otherVM.invoke(this::createPidFile);
+    int firstPid = new IntegerFileReader(pidFile).readFromFile();
+
+    // act/assert
+    assertThatThrownBy(() -> new LocalProcessLauncher(pidFile, false))
+        .isInstanceOf(FileAlreadyExistsException.class);
+    assertThat(new IntegerFileReader(pidFile).readFromFile()).isEqualTo(firstPid);
+  }
+
+  @Test
+  public void forceReplacesExistingPidFile() throws Exception {
+    // arrange
+    otherVM.invoke(this::createPidFile);
+    int firstPid = new IntegerFileReader(pidFile).readFromFile();
+
+    // act
+    new LocalProcessLauncher(pidFile, true);
+
+    // assert
+    int secondPid = new IntegerFileReader(pidFile).readFromFile();
+    assertThat(secondPid).isNotEqualTo(firstPid).isEqualTo(pid);
+  }
+
+  private void createPidFile()
+      throws FileAlreadyExistsException, IOException, PidUnavailableException {
+    new LocalProcessLauncher(pidFile, false);
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessLauncherIntegrationTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessLauncherIntegrationTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessLauncherIntegrationTest.java
new file mode 100644
index 0000000..2bcdeb3
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessLauncherIntegrationTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.internal.process;
+
+import static org.apache.geode.internal.process.LocalProcessLauncher.PROPERTY_IGNORE_IS_PID_ALIVE;
+import static org.apache.geode.internal.process.ProcessUtils.identifyPid;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.io.File;
+
+import org.junit.Before;
+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.TemporaryFolder;
+
+import org.apache.geode.internal.process.io.EmptyFileWriter;
+import org.apache.geode.internal.process.io.IntegerFileWriter;
+import org.apache.geode.internal.process.lang.AvailablePid;
+import org.apache.geode.test.junit.Retry;
+import org.apache.geode.test.junit.categories.IntegrationTest;
+import org.apache.geode.test.junit.rules.RetryRule;
+
+/**
+ * Functional integration tests for {@link LocalProcessLauncher}.
+ *
+ * <p>
+ * Tests involving fakePid use {@link RetryRule} because the fakePid may become used by a real
+ * process before the test executes.
+ */
+@Category(IntegrationTest.class)
+public class LocalProcessLauncherIntegrationTest {
+
+  private static final int PREFERRED_FAKE_PID = 42;
+
+  private File pidFile;
+  private int actualPid;
+  private int fakePid;
+
+  @Rule
+  public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
+
+  @Rule
+  public RetryRule retryRule = new RetryRule();
+
+  @Rule
+  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  @Before
+  public void before() throws Exception {
+    pidFile = new File(temporaryFolder.getRoot(), "my.pid");
+    actualPid = identifyPid();
+    fakePid = new AvailablePid().findAvailablePid(PREFERRED_FAKE_PID);
+
+    assertThat(pidFile).doesNotExist();
+  }
+
+  @Test
+  public void createsPidFile() throws Exception {
+    // act
+    new LocalProcessLauncher(pidFile, false);
+
+    // assert
+    assertThat(pidFile).exists().hasContent(String.valueOf(actualPid));
+  }
+
+  @Test
+  @Retry(3)
+  public void existingPidFileThrowsFileAlreadyExistsException() throws Exception {
+    // arrange
+    System.setProperty(PROPERTY_IGNORE_IS_PID_ALIVE, "true");
+    new IntegerFileWriter(pidFile).writeToFile(fakePid);
+
+    // act/assert
+    assertThatThrownBy(() -> new LocalProcessLauncher(pidFile, false))
+        .isInstanceOf(FileAlreadyExistsException.class);
+  }
+
+  @Test
+  public void overwritesPidFileIfForce() throws Exception {
+    // arrange
+    new IntegerFileWriter(pidFile).writeToFile(actualPid);
+
+    // act
+    new LocalProcessLauncher(pidFile, true);
+
+    // assert
+    assertThat(pidFile).exists().hasContent(String.valueOf(actualPid));
+  }
+
+  @Test
+  @Retry(3)
+  public void overwritesPidFileIfOtherPidIsNotAlive() throws Exception {
+    // arrange
+    new IntegerFileWriter(pidFile).writeToFile(fakePid);
+
+    // act
+    new LocalProcessLauncher(pidFile, false);
+
+    // assert
+    assertThat(pidFile).exists().hasContent(String.valueOf(actualPid));
+  }
+
+  @Test
+  public void overwritesEmptyPidFile() throws Exception {
+    // arrange
+    new EmptyFileWriter(pidFile).createNewFile();
+
+    // act
+    new LocalProcessLauncher(pidFile, false);
+
+    // assert
+    assertThat(pidFile).exists().hasContent(String.valueOf(actualPid));
+  }
+
+  @Test
+  public void getPidReturnsActualPid() throws Exception {
+    // arrange
+    LocalProcessLauncher launcher = new LocalProcessLauncher(pidFile, false);
+
+    // act/assert
+    assertThat(launcher.getPid()).isEqualTo(actualPid);
+  }
+
+  @Test
+  public void getPidFileReturnsPidFile() throws Exception {
+    // arrange
+    LocalProcessLauncher launcher = new LocalProcessLauncher(pidFile, false);
+
+    // act/assert
+    assertThat(launcher.getPidFile()).isEqualTo(pidFile);
+  }
+
+  @Test
+  public void closeDeletesPidFile() throws Exception {
+    // arrange
+    LocalProcessLauncher launcher = new LocalProcessLauncher(pidFile, false);
+    assertThat(pidFile).exists();
+
+    // act
+    launcher.close();
+
+    // assert
+    assertThat(pidFile).doesNotExist();
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessLauncherJUnitTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessLauncherJUnitTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessLauncherJUnitTest.java
deleted file mode 100755
index 9393949..0000000
--- a/geode-core/src/test/java/org/apache/geode/internal/process/LocalProcessLauncherJUnitTest.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * 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.internal.process;
-
-import static org.junit.Assert.*;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.FileWriter;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.TemporaryFolder;
-import org.junit.rules.TestName;
-
-import org.apache.geode.internal.OSProcess;
-import org.apache.geode.test.junit.categories.IntegrationTest;
-
-/**
- * Unit tests for ProcessLauncher.
- * 
- * @since GemFire 7.0
- */
-@Category(IntegrationTest.class)
-public class LocalProcessLauncherJUnitTest {
-
-  @Rule
-  public TemporaryFolder temporaryFolder = new TemporaryFolder();
-
-  @Rule
-  public TestName testName = new TestName();
-
-  private File pidFile;
-
-  @Before
-  public void setUp() throws Exception {
-    this.pidFile = new File(this.temporaryFolder.getRoot(), testName.getMethodName() + ".pid");
-  }
-
-  @Test
-  public void testPidAccuracy() throws PidUnavailableException {
-    int pid = ProcessUtils.identifyPid();
-    assertTrue(pid > 0);
-    int osProcessPid = OSProcess.getId();
-    if (osProcessPid > 0) {
-      assertEquals(OSProcess.getId(), pid);
-    } else {
-      // not much to test if OSProcess native code is unusable
-    }
-  }
-
-  @Test
-  public void testPidFileIsCreated() throws Exception {
-    assertFalse(pidFile.exists());
-    new LocalProcessLauncher(pidFile, false);
-    assertTrue(pidFile.exists());
-  }
-
-  @Test
-  public void testPidFileContainsPid() throws Exception {
-    final LocalProcessLauncher launcher = new LocalProcessLauncher(pidFile, false);
-    assertNotNull(launcher);
-    assertTrue(pidFile.exists());
-
-    final FileReader fr = new FileReader(pidFile);
-    final BufferedReader br = new BufferedReader(fr);
-    final int pid = Integer.parseInt(br.readLine());
-    br.close();
-
-    assertTrue(pid > 0);
-    assertEquals(launcher.getPid(), pid);
-    assertEquals(ProcessUtils.identifyPid(), pid);
-  }
-
-  @Test
-  public void testPidFileIsCleanedUp() throws Exception {
-    final LocalProcessLauncher launcher = new LocalProcessLauncher(pidFile, false);
-    assertTrue(pidFile.exists());
-    launcher.close(); // TODO: launch an external JVM and then close it nicely
-    assertFalse(pidFile.exists());
-  }
-
-  @Test
-  public void testExistingPidFileThrows() throws Exception {
-    assertTrue(pidFile.createNewFile());
-    assertTrue(pidFile.exists());
-
-    final FileWriter writer = new FileWriter(pidFile);
-    // use a read pid that exists
-    writer.write(String.valueOf(ProcessUtils.identifyPid()));
-    writer.close();
-
-    try {
-      new LocalProcessLauncher(pidFile, false);
-      fail("LocalProcessLauncher should have thrown FileAlreadyExistsException");
-    } catch (FileAlreadyExistsException e) {
-      // passed
-    }
-  }
-
-  @Test
-  public void testStalePidFileIsReplaced() throws Exception {
-    assertTrue(pidFile.createNewFile());
-    assertTrue(pidFile.exists());
-
-    final FileWriter writer = new FileWriter(pidFile);
-    writer.write(String.valueOf(Integer.MAX_VALUE));
-    writer.close();
-
-    try {
-      new LocalProcessLauncher(pidFile, false);
-    } catch (FileAlreadyExistsException e) {
-      fail("LocalProcessLauncher should not have thrown FileAlreadyExistsException");
-    }
-
-    final FileReader fr = new FileReader(pidFile);
-    final BufferedReader br = new BufferedReader(fr);
-    final int pid = Integer.parseInt(br.readLine());
-    br.close();
-
-    assertTrue(pid > 0);
-    assertEquals(ProcessUtils.identifyPid(), pid);
-  }
-
-  @Test
-  public void testForceReplacesExistingPidFile() throws Exception {
-    assertTrue("testForceReplacesExistingPidFile is broken if PID == Integer.MAX_VALUE",
-        ProcessUtils.identifyPid() != Integer.MAX_VALUE);
-
-    assertTrue(pidFile.createNewFile());
-    assertTrue(pidFile.exists());
-
-    final FileWriter writer = new FileWriter(pidFile);
-    writer.write(String.valueOf(Integer.MAX_VALUE));
-    writer.close();
-
-    try {
-      new LocalProcessLauncher(pidFile, true);
-    } catch (FileAlreadyExistsException e) {
-      fail("LocalProcessLauncher should not have thrown FileAlreadyExistsException");
-    }
-
-    final FileReader fr = new FileReader(pidFile);
-    final BufferedReader br = new BufferedReader(fr);
-    final int pid = Integer.parseInt(br.readLine());
-    br.close();
-
-    assertTrue(pid > 0);
-    assertEquals(ProcessUtils.identifyPid(), pid);
-  }
-
-  @Test
-  public void testPidUnavailableThrows() {
-    final String name = "Name without PID";
-    try {
-      ProcessUtils.identifyPid(name);
-      fail("PidUnavailableException should have been thrown for " + name);
-    } catch (PidUnavailableException e) {
-      // passed
-    }
-  }
-}