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

[02/14] 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/NativeProcessUtilsIntegrationTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/NativeProcessUtilsIntegrationTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/NativeProcessUtilsIntegrationTest.java
new file mode 100644
index 0000000..000318c
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/NativeProcessUtilsIntegrationTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.awaitility.Awaitility.await;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+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.apache.geode.internal.util.StopWatch;
+import org.apache.geode.test.junit.categories.IntegrationTest;
+
+/**
+ * Functional integration tests for {@link NativeProcessUtils}.
+ */
+@Category(IntegrationTest.class)
+public class NativeProcessUtilsIntegrationTest {
+
+  /** Max sleep timeout for {@link ProcessSleeps} */
+  private static final int PROCESS_TIMEOUT_MILLIS = 10 * 60 * 1000;
+
+  private static final String FILE_NAME = "pid.txt";
+
+  private NativeProcessUtils nativeProcessUtils;
+  private Process process;
+  private File pidFile;
+  private int pid;
+
+  @Rule
+  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  @Before
+  public void before() throws Exception {
+    File directory = temporaryFolder.getRoot();
+
+    List<String> command = new ArrayList<>();
+    command
+        .add(new File(new File(System.getProperty("java.home"), "bin"), "java").getCanonicalPath());
+    command.add("-cp");
+    command.add(System.getProperty("java.class.path"));
+    command.add(ProcessSleeps.class.getName());
+
+    process = new ProcessBuilder(command).directory(directory).start();
+    assertThat(process.isAlive()).isTrue();
+
+    pidFile = new File(directory, FILE_NAME);
+    await().atMost(2, MINUTES).until(() -> assertThat(pidFile).exists());
+
+    pid = new PidFile(pidFile).readPid();
+    assertThat(pid).isGreaterThan(0);
+
+    nativeProcessUtils = new NativeProcessUtils();
+  }
+
+  @After
+  public void after() throws Exception {
+    process.destroyForcibly();
+  }
+
+  @Test
+  public void killProcessKillsOtherProcess() throws Exception {
+    // act
+    nativeProcessUtils.killProcess(pid);
+
+    // assert
+    await().atMost(2, MINUTES).until(() -> assertThat(process.isAlive()).isFalse());
+  }
+
+  @Test
+  public void isProcessAliveReturnsTrueForLiveProcess() throws Exception {
+    // act/assert
+    assertThat(nativeProcessUtils.isProcessAlive(pid)).isTrue();
+  }
+
+  @Test
+  public void isProcessAliveReturnsFalseForDeadProcess() throws Exception {
+    // arrange
+    process.destroyForcibly();
+
+    // act/assert
+    await().atMost(2, MINUTES).until(() -> assertThat(process.isAlive()).isFalse());
+    assertThat(nativeProcessUtils.isProcessAlive(pid)).isFalse();
+  }
+
+  /**
+   * Class with main that uses LocalProcessLauncher to create a PidFile and then sleeps.
+   */
+  protected static class ProcessSleeps {
+    public static void main(final String... args) throws Exception {
+      new LocalProcessLauncher(new File(FILE_NAME), false);
+      StopWatch stopWatch = new StopWatch(true);
+      while (stopWatch.elapsedTimeMillis() < PROCESS_TIMEOUT_MILLIS) {
+        Thread.sleep(1000);
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/NativeProcessUtilsTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/NativeProcessUtilsTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/NativeProcessUtilsTest.java
new file mode 100644
index 0000000..9c845b8
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/NativeProcessUtilsTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.internal.process.lang.AvailablePid;
+import org.apache.geode.test.junit.Retry;
+import org.apache.geode.test.junit.categories.UnitTest;
+import org.apache.geode.test.junit.rules.RetryRule;
+
+/**
+ * Unit tests for {@link NativeProcessUtils}.
+ *
+ * <p>
+ * Tests involving fakePid use {@link RetryRule} because the fakePid may become used by a real
+ * process before the test executes.
+ */
+@Category(UnitTest.class)
+public class NativeProcessUtilsTest {
+
+  private static final int PREFERRED_FAKE_PID = 42;
+
+  private int actualPid;
+  private int fakePid;
+  private NativeProcessUtils nativeProcessUtils;
+
+  @Rule
+  public RetryRule retryRule = new RetryRule();
+
+  @Before
+  public void before() throws Exception {
+    actualPid = identifyPid();
+    fakePid = new AvailablePid().findAvailablePid(PREFERRED_FAKE_PID);
+    nativeProcessUtils = new NativeProcessUtils();
+  }
+
+  @Test
+  public void isAttachApiAvailable_returnsFalse() throws Exception {
+    assertThat(nativeProcessUtils.isAttachApiAvailable()).isFalse();
+  }
+
+  @Test
+  public void isAvailable_returnsTrue() throws Exception {
+    assertThat(nativeProcessUtils.isAvailable()).isTrue();
+  }
+
+  @Test
+  public void isProcessAlive_livePid_returnsTrue() throws Exception {
+    assertThat(nativeProcessUtils.isProcessAlive(actualPid)).isTrue();
+  }
+
+  @Test
+  @Retry(3)
+  public void isProcessAlive_deadPid_returnsFalse() throws Exception {
+    assertThat(nativeProcessUtils.isProcessAlive(fakePid)).isFalse();
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/NonBlockingProcessStreamReaderIntegrationTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/NonBlockingProcessStreamReaderIntegrationTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/NonBlockingProcessStreamReaderIntegrationTest.java
new file mode 100755
index 0000000..05b7cbc
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/NonBlockingProcessStreamReaderIntegrationTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.ProcessStreamReader.ReadingMode.NON_BLOCKING;
+
+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 NonBlockingProcessStreamReader which was introduced to fix TRAC
+ * #51967: "GFSH start hangs on Windows"
+ *
+ * @see BlockingProcessStreamReaderIntegrationTest
+ * @see BlockingProcessStreamReaderWindowsTest
+ *
+ * @since GemFire 8.2
+ */
+@Category(IntegrationTest.class)
+public class NonBlockingProcessStreamReaderIntegrationTest
+    extends BaseProcessStreamReaderIntegrationTest {
+
+  /**
+   * This test hangs on Windows if the implementation is blocking instead of non-blocking. Geode
+   * will always use the non-blocking implementation on Windows. If someone accidentally changes
+   * this, then the probably the first thing you'll notice is this test hanging.
+   */
+  @Test
+  public void canCloseStreamsWhileProcessIsAlive() throws Exception {
+    // arrange
+    givenRunningProcessWithStreamReaders(ProcessSleeps.class);
+
+    // act
+    process.getOutputStream().close();
+    process.getErrorStream().close();
+    process.getInputStream().close();
+
+    // assert
+    assertThatProcessIsAlive(process);
+  }
+
+  @Test
+  public void canStopReadersWhileProcessIsAlive() throws Exception {
+    // arrange
+    givenRunningProcessWithStreamReaders(ProcessSleeps.class);
+
+    // act
+    stdout.stop();
+    stderr.stop();
+
+    // assert
+    assertThatProcessIsAlive(process);
+  }
+
+  @Test
+  public void capturesStdoutWhileProcessIsAlive() throws Exception {
+    // arrange
+    givenStartedProcessWithStreamListeners(ProcessPrintsToStdout.class);
+
+    // act
+    waitUntilProcessStops();
+
+    // assert
+    assertThatProcessAndReadersStopped();
+    assertThatStdOutContainsExactly(ProcessPrintsToStdout.STDOUT);
+    assertThatStdErrContainsExactly(ProcessPrintsToStdout.STDERR);
+  }
+
+  @Test
+  public void capturesStderrWhileProcessIsAlive() throws Exception {
+    // arrange
+    givenStartedProcessWithStreamListeners(ProcessPrintsToStderr.class);
+
+    // act
+    waitUntilProcessStops();
+
+    // assert
+    assertThatProcessAndReadersStopped();
+    assertThatStdOutContainsExactly(ProcessPrintsToStderr.STDOUT);
+    assertThatStdErrContainsExactly(ProcessPrintsToStderr.STDERR);
+  }
+
+  @Test
+  public void capturesBothWhileProcessIsAlive() 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 NON_BLOCKING;
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/NonBlockingProcessStreamReaderJUnitTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/NonBlockingProcessStreamReaderJUnitTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/NonBlockingProcessStreamReaderJUnitTest.java
deleted file mode 100755
index 105e7f0..0000000
--- a/geode-core/src/test/java/org/apache/geode/internal/process/NonBlockingProcessStreamReaderJUnitTest.java
+++ /dev/null
@@ -1,365 +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.util.concurrent.Callable;
-
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-
-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 NonBlockingProcessStreamReader which was introduced to fix TRAC bug #51967.
- * 
- * None of the tests should be skipped or hang on Windows.
- * 
- * @since GemFire 8.2
- */
-@Category(IntegrationTest.class)
-public class NonBlockingProcessStreamReaderJUnitTest extends ProcessStreamReaderTestCase {
-
-  @Test
-  public void canCloseStreamsWhileProcessIsAlive() throws Exception {
-    this.process = new ProcessBuilder(createCommandLine(ProcessSleeps.class)).start();
-
-    this.stderr = new ProcessStreamReader.Builder(this.process)
-        .inputStream(this.process.getErrorStream()).readingMode(ReadingMode.NON_BLOCKING).build();
-
-    this.stdout = new ProcessStreamReader.Builder(this.process)
-        .inputStream(this.process.getInputStream()).readingMode(ReadingMode.NON_BLOCKING).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();
-
-    this.stderr.stop();
-    this.stdout.stop();
-
-    assertIsAlive(this.process);
-
-    this.process.destroy();
-  }
-
-  @Test
-  public void canStopReadersWhileProcessIsAlive() throws Exception {
-    this.process = new ProcessBuilder(createCommandLine(ProcessSleeps.class)).start();
-
-    this.stderr = new ProcessStreamReader.Builder(this.process)
-        .inputStream(this.process.getErrorStream()).readingMode(ReadingMode.NON_BLOCKING).build();
-
-    this.stdout = new ProcessStreamReader.Builder(this.process)
-        .inputStream(this.process.getInputStream()).readingMode(ReadingMode.NON_BLOCKING).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 {
-    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();
-
-    // 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 {
-    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 {
-    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 {
-    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/PidFileIntegrationTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/PidFileIntegrationTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/PidFileIntegrationTest.java
new file mode 100755
index 0000000..584f185
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/PidFileIntegrationTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+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.apache.geode.internal.process.io.IntegerFileWriter;
+import org.apache.geode.internal.process.lang.AvailablePid;
+import org.apache.geode.test.junit.categories.IntegrationTest;
+
+/**
+ * Functional integration tests for {@link PidFile}.
+ * 
+ * @since GemFire 8.2
+ */
+@Category(IntegrationTest.class)
+public class PidFileIntegrationTest {
+
+  private File directory;
+  private File pidFile;
+  private String pidFileName;
+  private ExecutorService futures;
+  private int pid;
+
+  @Rule
+  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  @Before
+  public void before() throws Exception {
+    directory = temporaryFolder.getRoot();
+    pidFile = new File(directory, "pid.txt");
+    pidFileName = pidFile.getName();
+    futures = Executors.newFixedThreadPool(2);
+    pid = identifyPid();
+  }
+
+  @After
+  public void after() {
+    assertThat(this.futures.shutdownNow()).isEmpty();
+  }
+
+  @Test
+  public void readsIntFromFile() throws Exception {
+    // arrange
+    String value = "42";
+    new IntegerFileWriter(pidFile).writeToFile(value);
+
+    // act
+    int readValue = new PidFile(pidFile).readPid();
+
+    // assert
+    assertThat(readValue).isEqualTo(Integer.parseInt(value));
+  }
+
+  @Test
+  public void readingEmptyFileThrowsIllegalArgumentException() throws Exception {
+    // arrange
+    new IntegerFileWriter(pidFile).writeToFile("");
+
+    // act/assert
+    assertThatThrownBy(() -> new PidFile(pidFile).readPid())
+        .isInstanceOf(IllegalArgumentException.class);
+  }
+
+  @Test
+  public void readingFileWithNonIntegerThrowsIllegalArgumentException() throws Exception {
+    // arrange
+    String value = "forty two";
+    new IntegerFileWriter(pidFile).writeToFile(value);
+
+    // act/assert
+    assertThatThrownBy(() -> new PidFile(pidFile).readPid())
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("Invalid pid '" + value + "' found");
+  }
+
+  @Test
+  public void readingFileWithNegativeIntegerThrowsIllegalArgumentException() throws Exception {
+    // arrange
+    String value = "-42";
+    new IntegerFileWriter(pidFile).writeToFile(value);
+
+    // act/assert
+    assertThatThrownBy(() -> new PidFile(pidFile).readPid())
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("Invalid pid '" + value + "' found");
+  }
+
+  @Test
+  public void readingNullFileThrowsNullPointerException() throws Exception {
+    // arrange
+    pidFile = null;
+
+    // act/assert
+    assertThatThrownBy(() -> new PidFile(pidFile).readPid())
+        .isInstanceOf(IllegalArgumentException.class);
+  }
+
+  @Test
+  public void findsCorrectFileByName() throws Exception {
+    // arrange
+    new IntegerFileWriter(pidFile).writeToFile(pid);
+    int[] pids = new AvailablePid().findAvailablePids(4);
+    for (int i = 1; i <= pids.length; i++) {
+      new IntegerFileWriter(new File(directory, "pid" + i + ".txt")).writeToFile(pids[i - 1]);
+    }
+    assertThat(directory.listFiles()).hasSize(pids.length + 1);
+
+    // act
+    PidFile namedPidFile = new PidFile(directory, pidFile.getName());
+
+    // assert
+    assertThat(namedPidFile.getFile()).hasContent(String.valueOf(pid));
+    assertThat(namedPidFile.readPid()).isEqualTo(pid);
+  }
+
+  @Test
+  public void missingFileInEmptyDirectoryThrowsFileNotFoundException() throws Exception {
+    // arrange
+    assertThat(pidFile).doesNotExist();
+
+    // act/assert
+    assertThatThrownBy(() -> new PidFile(directory, pidFileName).readPid())
+        .isInstanceOf(FileNotFoundException.class).hasMessage(
+            "Unable to find PID file '" + pidFileName + "' in directory '" + directory + "'");
+  }
+
+  @Test
+  public void fileForDirectoryThrowsIllegalArgumentException() throws Exception {
+    // arrange
+    File directoryIsFile = temporaryFolder.newFile("my.file");
+
+    // act/assert
+    assertThatThrownBy(() -> new PidFile(directoryIsFile, pidFileName).readPid())
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Nonexistent directory '" + directoryIsFile + "' specified");
+  }
+
+  @Test
+  public void missingFileThrowsFileNotFoundException() throws Exception {
+    // act/assert
+    assertThatThrownBy(() -> new PidFile(pidFile).readPid())
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Nonexistent file '" + pidFile + "' specified");
+  }
+
+  @Test
+  public void missingFileInFullDirectoryThrowsFileNotFoundException() throws Exception {
+    // arrange
+    int[] pids = new AvailablePid().findAvailablePids(4);
+    for (int i = 1; i <= pids.length; i++) {
+      new IntegerFileWriter(new File(directory, "pid" + i + ".txt")).writeToFile(pids[i - 1]);
+    }
+    assertThat(directory.listFiles()).hasSameSizeAs(pids);
+
+    // act/assert
+    assertThatThrownBy(() -> new PidFile(directory, pidFileName).readPid())
+        .isInstanceOf(FileNotFoundException.class).hasMessage(
+            "Unable to find PID file '" + pidFileName + "' in directory '" + directory + "'");
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/PidFileJUnitTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/PidFileJUnitTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/PidFileJUnitTest.java
deleted file mode 100755
index 0ca73c4..0000000
--- a/geode-core/src/test/java/org/apache/geode/internal/process/PidFileJUnitTest.java
+++ /dev/null
@@ -1,275 +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.File;
-import java.io.FileNotFoundException;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-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.jmock.Mockery;
-import org.jmock.lib.concurrent.Synchroniser;
-import org.jmock.lib.legacy.ClassImposteriser;
-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.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-
-import org.apache.geode.internal.util.StopWatch;
-import org.apache.geode.test.junit.categories.IntegrationTest;
-import org.apache.geode.test.junit.rules.ExpectedTimeoutRule;
-
-/**
- * Unit tests the PidFile class.
- * 
- * @since GemFire 8.2
- */
-@Category(IntegrationTest.class)
-public class PidFileJUnitTest {
-
-  @Rule
-  public TemporaryFolder testFolder = new TemporaryFolder();
-
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
-  @Rule
-  public ExpectedTimeoutRule timeout = ExpectedTimeoutRule.none();
-
-  protected Mockery mockContext;
-  private ExecutorService futures;
-
-  @Before
-  public void before() {
-    mockContext = new Mockery() {
-      {
-        setImposteriser(ClassImposteriser.INSTANCE);
-        setThreadingPolicy(new Synchroniser());
-      }
-    };
-    this.futures = Executors.newFixedThreadPool(2);
-  }
-
-  @After
-  public void after() {
-    mockContext.assertIsSatisfied();
-    assertTrue(this.futures.shutdownNow().isEmpty());
-  }
-
-  @Test
-  public void readsIntFromFile() throws Exception {
-    final File file = testFolder.newFile("my.pid");
-    final String value = "42";
-    writeToFile(file, value);
-
-    final int readValue = new PidFile(file).readPid();
-    assertEquals(Integer.parseInt(value), readValue);
-  }
-
-  @Test
-  public void readingEmptyFileThrowsIllegalArgumentException() throws Exception {
-    final File file = testFolder.newFile("my.pid");
-
-    thrown.expect(IllegalArgumentException.class);
-    thrown.expectMessage("Invalid pid 'null' found");
-
-    new PidFile(file).readPid();
-  }
-
-  @Test
-  public void readingFileWithNonIntegerThrowsIllegalArgumentException() throws Exception {
-    final File file = testFolder.newFile("my.pid");
-    final String value = "fortytwo";
-    writeToFile(file, value);
-
-    thrown.expect(IllegalArgumentException.class);
-    thrown.expectMessage("Invalid pid '" + value + "' found");
-
-    new PidFile(file).readPid();
-  }
-
-  @Test
-  public void readingFileWithNegativeIntegerThrowsIllegalArgumentException() throws Exception {
-    final File file = testFolder.newFile("my.pid");
-    final String value = "-42";
-    writeToFile(file, value);
-
-    thrown.expect(IllegalArgumentException.class);
-    thrown.expectMessage("Invalid pid '" + value + "' found");
-
-    new PidFile(file).readPid();
-  }
-
-  @Test
-  public void readingNullFileThrowsNullPointerException() throws Exception {
-    final File file = null;
-
-    thrown.expect(NullPointerException.class);
-
-    new PidFile(file).readPid();
-  }
-
-  @Test
-  public void timesOutReadingFromEmptyFile() throws Exception {
-    final File file = testFolder.newFile("my.pid");
-
-    timeout.expect(TimeoutException.class);
-    timeout.expectMessage("Invalid pid 'null' found");
-    timeout.expectMinimumDuration(1000);
-    timeout.expectMaximumDuration(10000);
-    timeout.expectTimeUnit(TimeUnit.MILLISECONDS);
-
-    new PidFile(file).readPid(1500, TimeUnit.MILLISECONDS);
-  }
-
-  @Test
-  public void readsIntBeforeTimeout() throws Exception {
-    final int AWAIT_LATCH_TIMEOUT_MILLIS = 10 * 1000;
-    final int OPEN_LATCH_DELAY_MILLIS = 2 * 1000;
-    final int FUTURE_GET_TIMEOUT_MILLIS = 2 * 1000;
-    final int READ_PID_TIMEOUT_MILLIS = 2 * OPEN_LATCH_DELAY_MILLIS;
-
-    final File file = testFolder.newFile("my.pid");
-    final FileWriter writer = new FileWriter(file);
-
-    final CountDownLatch writePidLatch = new CountDownLatch(1);
-    final String value = "42";
-
-    // start Future to write the pid later but before timeout
-    Future<Boolean> futureWritePid = this.futures.submit(new Callable<Boolean>() {
-      @Override
-      public Boolean call() throws Exception {
-        writePidLatch.await(AWAIT_LATCH_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-        writeToFile(file, value);
-        return true;
-      }
-    });
-
-    // start Future to sleep and release the delay
-    Future<Boolean> futureOpenLatch = this.futures.submit(new Callable<Boolean>() {
-      @Override
-      public Boolean call() throws Exception {
-        Thread.sleep(OPEN_LATCH_DELAY_MILLIS);
-        writePidLatch.countDown();
-        return true;
-      }
-    });
-
-    StopWatch stopWatch = new StopWatch(true);
-    final int readValue = new PidFile(file).readPid(READ_PID_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-    assertEquals(Integer.parseInt(value), readValue);
-
-    long duration = stopWatch.elapsedTimeMillis();
-    assertTrue(duration > OPEN_LATCH_DELAY_MILLIS);
-    assertTrue(duration < READ_PID_TIMEOUT_MILLIS);
-
-    assertEquals(0, writePidLatch.getCount());
-    assertTrue(futureOpenLatch.get(FUTURE_GET_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
-    assertTrue(futureWritePid.get(FUTURE_GET_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
-  }
-
-  @Test
-  public void findsCorrectFile() throws Exception {
-    final File directory = testFolder.getRoot();
-
-    final String fileNames[] = new String[] {"other.txt", "my.txt", "a.log", "b.log"};
-    for (String fileName : fileNames) {
-      testFolder.newFile(fileName);
-    }
-
-    final int pidValue = 42;
-    final File file = testFolder.newFile("my.pid");
-    writeToFile(file, String.valueOf(pidValue));
-
-    final File other = testFolder.newFile("other.pid");
-    writeToFile(other, "43");
-
-    final File[] files = directory.listFiles();
-    assertEquals(fileNames.length + 2, files.length);
-
-    PidFile pidFile = new PidFile(directory, file.getName());
-    assertEquals(file, pidFile.getFile());
-
-    int value = pidFile.readPid();
-    assertEquals(pidValue, value);
-  }
-
-  @Test
-  public void missingFileInEmptyDirectoryThrowsFileNotFoundException() throws Exception {
-    final File directory = testFolder.getRoot();
-
-    final String pidFileName = "my.pid";
-
-    thrown.expect(FileNotFoundException.class);
-    thrown.expectMessage("Unable to find PID file '" + pidFileName + "' in directory " + directory);
-
-    new PidFile(directory, pidFileName);
-  }
-
-  @Test
-  public void missingFileThrowsFileNotFoundException() throws Exception {
-    final String pidFileName = "my.pid";
-
-    final File directory = testFolder.getRoot();
-    final File file = new File(directory, pidFileName);
-
-    thrown.expect(FileNotFoundException.class);
-    thrown.expectMessage("Unable to find PID file '" + file + "'");
-
-    new PidFile(file);
-  }
-
-  @Test
-  public void missingFileInFullDirectoryThrowsFileNotFoundException() throws Exception {
-    final File directory = testFolder.getRoot();
-
-    final String fileNames[] = new String[] {"other.txt", "my.txt", "a.log", "b.log"};
-    for (String fileName : fileNames) {
-      testFolder.newFile(fileName);
-    }
-
-    final File other = testFolder.newFile("other.pid");
-    writeToFile(other, "43");
-
-    final File[] files = directory.listFiles();
-    assertEquals(fileNames.length + 1, files.length);
-
-    final String pidFileName = "my.pid";
-
-    thrown.expect(FileNotFoundException.class);
-    thrown.expectMessage("Unable to find PID file '" + pidFileName + "' in directory " + directory);
-
-    new PidFile(directory, pidFileName);
-  }
-
-  private void writeToFile(final File file, String value) throws IOException {
-    final FileWriter writer = new FileWriter(file);
-    writer.write(value);
-    writer.flush();
-    writer.close();
-  }
-}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/ProcessControllerFactoryIntegrationTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/ProcessControllerFactoryIntegrationTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/ProcessControllerFactoryIntegrationTest.java
new file mode 100644
index 0000000..aafa260
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/ProcessControllerFactoryIntegrationTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.internal.process.ProcessUtils.isProcessAlive;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+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.IntegerFileWriter;
+import org.apache.geode.test.junit.categories.IntegrationTest;
+
+/**
+ * Functional integration tests for {@link ProcessControllerFactory}.
+ */
+@Category(IntegrationTest.class)
+public class ProcessControllerFactoryIntegrationTest {
+
+  private ProcessControllerFactory factory;
+  private ProcessControllerParameters parameters;
+  private File directory;
+  private File pidFile;
+  private String pidFileName;
+  private int pid;
+
+  @Rule
+  public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
+
+  @Rule
+  public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+  @Before
+  public void before() throws Exception {
+    factory = new ProcessControllerFactory();
+    parameters = mock(ProcessControllerParameters.class);
+    directory = temporaryFolder.getRoot();
+    pidFile = new File(directory, "pid.txt");
+    pidFileName = pidFile.getName();
+    pid = identifyPid();
+
+    assertThat(pidFile).doesNotExist();
+    assertThat(isProcessAlive(pid)).isTrue();
+  }
+
+  @Test
+  public void createProcessController_withoutPidFile_throwsFileNotFoundException()
+      throws Exception {
+    // act/assert
+    assertThatThrownBy(() -> factory.createProcessController(parameters, directory, pidFileName))
+        .isInstanceOf(FileNotFoundException.class);
+  }
+
+  @Test
+  public void createProcessController_returnsMBeanProcessController() throws Exception {
+    // arrange
+    new IntegerFileWriter(pidFile).writeToFile(pid);
+
+    // act
+    ProcessController controller =
+        factory.createProcessController(parameters, directory, pidFileName);
+
+    // assert
+    assertThat(controller).isInstanceOf(MBeanProcessController.class);
+  }
+
+  @Test
+  public void createProcessController_withoutAttachAPI_returnsFileProcessController()
+      throws Exception {
+    // arrange
+    new IntegerFileWriter(pidFile).writeToFile(pid);
+    System.setProperty(ProcessControllerFactory.PROPERTY_DISABLE_ATTACH_API, "true");
+    factory = new ProcessControllerFactory();
+
+    // act
+    ProcessController controller =
+        factory.createProcessController(parameters, directory, pidFileName);
+
+    // assert
+    assertThat(controller).isInstanceOf(FileProcessController.class);
+  }
+
+  @Test
+  public void createProcessController_nullParameters_throwsNullPointerException() throws Exception {
+    // arrange
+    parameters = null;
+
+    // act/assert
+    assertThatThrownBy(() -> factory.createProcessController(parameters, directory, pidFileName))
+        .isInstanceOf(IllegalArgumentException.class);
+  }
+
+  @Test
+  public void createProcessController_nullDirectory_throwsNullPointerException() throws Exception {
+    // arrange
+    directory = null;
+
+    // act/assert
+    assertThatThrownBy(() -> factory.createProcessController(parameters, directory, pidFileName))
+        .isInstanceOf(IllegalArgumentException.class);
+  }
+
+  @Test
+  public void createProcessController_nullPidFileName_throwsNullPointerException()
+      throws Exception {
+    // arrange
+    pidFileName = null;
+
+    // act/assert
+    assertThatThrownBy(() -> factory.createProcessController(parameters, directory, pidFileName))
+        .isInstanceOf(IllegalArgumentException.class);
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/ProcessControllerFactoryJUnitTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/ProcessControllerFactoryJUnitTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/ProcessControllerFactoryJUnitTest.java
deleted file mode 100755
index b9c8030..0000000
--- a/geode-core/src/test/java/org/apache/geode/internal/process/ProcessControllerFactoryJUnitTest.java
+++ /dev/null
@@ -1,176 +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.File;
-
-import javax.management.ObjectName;
-
-import org.junit.After;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-
-import org.apache.geode.test.junit.categories.UnitTest;
-
-/**
- * @since GemFire 8.0
- */
-@Category(UnitTest.class)
-public class ProcessControllerFactoryJUnitTest {
-
-  @After
-  public void tearDown() throws Exception {
-    enableAttachApi();
-  }
-
-  @Test
-  public void testIsAttachAPIFound() throws Exception {
-    validateProcessControllerFactory(true);
-    disableAttachApi();
-    validateProcessControllerFactory(false);
-    enableAttachApi();
-    validateProcessControllerFactory(true);
-  }
-
-  private void validateProcessControllerFactory(boolean isAttachAPIFound) throws Exception {
-    final ProcessControllerFactory factory = new ProcessControllerFactory();
-    assertEquals(isAttachAPIFound, factory.isAttachAPIFound());
-    if (isAttachAPIFound) {
-      final ProcessControllerParameters parms = new NullMBeanControllerParameters();
-      final ProcessController controller =
-          factory.createProcessController(parms, ProcessUtils.identifyPid());
-      assertTrue(controller instanceof MBeanProcessController);
-    } else {
-      final ProcessControllerParameters parms = new NullFileControllerParameters();
-      final ProcessController controller =
-          factory.createProcessController(parms, ProcessUtils.identifyPid());
-      assertTrue(controller instanceof FileProcessController);
-    }
-  }
-
-  private static void disableAttachApi() {
-    System.setProperty(ProcessControllerFactory.PROPERTY_DISABLE_ATTACH_API, "true");
-  }
-
-  private static void enableAttachApi() {
-    System.clearProperty(ProcessControllerFactory.PROPERTY_DISABLE_ATTACH_API);
-  }
-
-  private static class NullMBeanControllerParameters implements ProcessControllerParameters {
-    @Override
-    public int getProcessId() {
-      return 0;
-    }
-
-    @Override
-    public ProcessType getProcessType() {
-      return null;
-    }
-
-    @Override
-    public ObjectName getNamePattern() {
-      return null;
-    }
-
-    @Override
-    public String getPidAttribute() {
-      return null;
-    }
-
-    @Override
-    public String getStatusMethod() {
-      return null;
-    }
-
-    @Override
-    public String getStopMethod() {
-      return null;
-    }
-
-    @Override
-    public String[] getAttributes() {
-      return null;
-    }
-
-    @Override
-    public Object[] getValues() {
-      return null;
-    }
-
-    @Override
-    public File getPidFile() {
-      throw new UnsupportedOperationException("Not implemented by NullMBeanControllerParameters");
-    }
-
-    @Override
-    public File getWorkingDirectory() {
-      throw new UnsupportedOperationException("Not implemented by NullMBeanControllerParameters");
-    }
-  }
-
-  private static class NullFileControllerParameters implements ProcessControllerParameters {
-    @Override
-    public int getProcessId() {
-      throw new UnsupportedOperationException("Not implemented by NullFileControllerParameters");
-    }
-
-    @Override
-    public ProcessType getProcessType() {
-      throw new UnsupportedOperationException("Not implemented by NullFileControllerParameters");
-    }
-
-    @Override
-    public ObjectName getNamePattern() {
-      throw new UnsupportedOperationException("Not implemented by NullFileControllerParameters");
-    }
-
-    @Override
-    public String getPidAttribute() {
-      throw new UnsupportedOperationException("Not implemented by NullFileControllerParameters");
-    }
-
-    @Override
-    public String getStatusMethod() {
-      throw new UnsupportedOperationException("Not implemented by NullFileControllerParameters");
-    }
-
-    @Override
-    public String getStopMethod() {
-      throw new UnsupportedOperationException("Not implemented by NullFileControllerParameters");
-    }
-
-    @Override
-    public String[] getAttributes() {
-      throw new UnsupportedOperationException("Not implemented by NullFileControllerParameters");
-    }
-
-    @Override
-    public Object[] getValues() {
-      throw new UnsupportedOperationException("Not implemented by NullFileControllerParameters");
-    }
-
-    @Override
-    public File getPidFile() {
-      return null;
-    }
-
-    @Override
-    public File getWorkingDirectory() {
-      return null;
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/ProcessControllerFactoryTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/ProcessControllerFactoryTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/ProcessControllerFactoryTest.java
new file mode 100755
index 0000000..55f5d58
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/ProcessControllerFactoryTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.ProcessControllerFactory.PROPERTY_DISABLE_ATTACH_API;
+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 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.apache.geode.test.junit.categories.UnitTest;
+
+/**
+ * Unit tests for {@link ProcessControllerFactory}.
+ *
+ * @since GemFire 8.0
+ */
+@Category(UnitTest.class)
+public class ProcessControllerFactoryTest {
+
+  private ProcessControllerFactory factory;
+  private ProcessControllerParameters parameters;
+  private int pid;
+
+  @Rule
+  public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
+
+  @Before
+  public void before() throws Exception {
+    factory = new ProcessControllerFactory();
+    parameters = mock(ProcessControllerParameters.class);
+    pid = identifyPid();
+  }
+
+  @Test
+  public void isAttachAPIFound_withAttachAPI_returnsTrue() throws Exception {
+    // act/assert
+    assertThat(factory.isAttachAPIFound()).isTrue();
+  }
+
+  @Test
+  public void isAttachAPIFound_withoutAttachAPI_returnsFalse() throws Exception {
+    // arrange
+    System.setProperty(PROPERTY_DISABLE_ATTACH_API, "true");
+    factory = new ProcessControllerFactory();
+
+    // act/assert
+    assertThat(factory.isAttachAPIFound()).isFalse();
+  }
+
+  @Test
+  public void createProcessController_withAttachAPI_returnsMBeanProcessController()
+      throws Exception {
+    // act
+    ProcessController controller = factory.createProcessController(parameters, pid);
+
+    // assert
+    assertThat(controller).isInstanceOf(MBeanProcessController.class);
+  }
+
+  @Test
+  public void createProcessController_withoutAttachAPI_returnsFileProcessController()
+      throws Exception {
+    // arrange
+    System.setProperty(PROPERTY_DISABLE_ATTACH_API, "true");
+    factory = new ProcessControllerFactory();
+
+    // act
+    ProcessController controller = factory.createProcessController(parameters, pid);
+
+    // assert
+    assertThat(controller).isInstanceOf(FileProcessController.class);
+  }
+
+  @Test
+  public void createProcessController_withNullParameters_throwsNullPointerException()
+      throws Exception {
+    // arrange
+    parameters = null;
+
+    // act/assert
+    assertThatThrownBy(() -> factory.createProcessController(parameters, pid))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("Invalid parameters 'null' specified");
+  }
+
+  @Test
+  public void createProcessController_withZeroPid_throwsIllegalArgumentException()
+      throws Exception {
+    // arrange
+    pid = 0;
+
+    // act/assert
+    assertThatThrownBy(() -> factory.createProcessController(parameters, 0))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("Invalid pid '" + 0 + "' specified");
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/ProcessLauncherContextTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/ProcessLauncherContextTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/ProcessLauncherContextTest.java
new file mode 100644
index 0000000..c98f026
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/ProcessLauncherContextTest.java
@@ -0,0 +1,208 @@
+/*
+ * 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.Collections.synchronizedList;
+import static org.apache.geode.internal.process.ProcessLauncherContext.getOverriddenDefaults;
+import static org.apache.geode.internal.process.ProcessLauncherContext.getStartupListener;
+import static org.apache.geode.internal.process.ProcessLauncherContext.isRedirectingOutput;
+import static org.apache.geode.internal.process.ProcessLauncherContext.remove;
+import static org.apache.geode.internal.process.ProcessLauncherContext.set;
+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.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.test.junit.categories.UnitTest;
+
+/**
+ * Unit tests for {@link ProcessLauncherContext}.
+ */
+@Category(UnitTest.class)
+public class ProcessLauncherContextTest {
+
+  private boolean redirectOutput;
+  private Properties overriddenDefaults;
+  private StartupStatusListener startupListener;
+  private List<String> statusMessageList;
+
+  @Before
+  public void before() throws Exception {
+    redirectOutput = false;
+    overriddenDefaults = new Properties();
+    startupListener = mock(StartupStatusListener.class);
+    statusMessageList = synchronizedList(new ArrayList<>());
+  }
+
+  @After
+  public void after() throws Exception {
+    remove();
+  }
+
+  @Test
+  public void isRedirectingOutput_defaultsToFalse() throws Exception {
+    assertThat(isRedirectingOutput()).isFalse();
+  }
+
+  @Test
+  public void getOverriddenDefaults_defaultsToEmpty() throws Exception {
+    assertThat(getOverriddenDefaults()).isEmpty();
+  }
+
+  @Test
+  public void getStartupListener_defaultsToNull() throws Exception {
+    assertThat(getStartupListener()).isNull();
+  }
+
+  @Test
+  public void null_overriddenDefaults_throwsIllegalArgumentException() throws Exception {
+    // arrange
+    overriddenDefaults = null;
+
+    // act/assert
+    assertThatThrownBy(() -> set(redirectOutput, overriddenDefaults, startupListener))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Invalid overriddenDefaults 'null' specified");
+  }
+
+  @Test
+  public void null_startupListener_isAllowed() throws Exception {
+    // arrange
+    startupListener = null;
+
+    // act
+    set(redirectOutput, overriddenDefaults, startupListener);
+
+    // assert
+    assertThat(getStartupListener()).isNull();
+  }
+
+  @Test
+  public void empty_overriddenDefaults_isAllowed() throws Exception {
+    // act
+    set(redirectOutput, overriddenDefaults, startupListener);
+
+    // assert
+    assertThat(getOverriddenDefaults()).isEmpty();
+  }
+
+  @Test
+  public void isRedirectingOutput_returnsPassedValue() throws Exception {
+    // arrange
+    redirectOutput = true;
+
+    // act
+    set(redirectOutput, overriddenDefaults, startupListener);
+
+    // assert
+    assertThat(isRedirectingOutput()).isTrue();
+  }
+
+  @Test
+  public void getOverriddenDefaults_returnsPassedInProps() throws Exception {
+    // arrange
+    overriddenDefaults.setProperty("key", "value");
+
+    // act
+    set(redirectOutput, overriddenDefaults, startupListener);
+
+    // assert
+    assertThat(getOverriddenDefaults()).hasSize(1).containsEntry("key", "value");
+  }
+
+  @Test
+  public void getStartupListener_returnsPassedInListener() throws Exception {
+    // arrange
+    overriddenDefaults.setProperty("key", "value");
+
+    // act
+    set(redirectOutput, overriddenDefaults, startupListener);
+
+    // assert
+    assertThat(getStartupListener()).isSameAs(startupListener);
+  }
+
+  @Test
+  public void remove_clearsOverriddenDefaults() throws Exception {
+    // arrange
+    overriddenDefaults.setProperty("key", "value");
+    set(false, overriddenDefaults, startupListener);
+
+    // act
+    remove();
+
+    // assert
+    assertThat(getOverriddenDefaults()).isEmpty();
+  }
+
+  @Test
+  public void remove_unsetsRedirectOutput() throws Exception {
+    // arrange
+    redirectOutput = true;
+    set(redirectOutput, overriddenDefaults, startupListener);
+
+    // act
+    remove();
+
+    // assert
+    assertThat(isRedirectingOutput()).isFalse();
+  }
+
+  @Test
+  public void remove_clearsStartupListener() throws Exception {
+    // arrange
+    startupListener = statusMessage -> statusMessageList.add(statusMessage);
+    set(redirectOutput, overriddenDefaults, startupListener);
+
+    // act
+    remove();
+
+    // assert
+    assertThat(getStartupListener()).isNull();
+  }
+
+  @Test
+  public void startupListener_installsInStartupStatus() throws Exception {
+    // arrange
+    startupListener = statusMessage -> statusMessageList.add(statusMessage);
+
+    // act
+    set(redirectOutput, overriddenDefaults, startupListener);
+
+    // assert
+    assertThat(StartupStatus.getStartupListener()).isSameAs(startupListener);
+  }
+
+  @Test
+  public void remove_uninstallsInStartupStatus() throws Exception {
+    // arrange
+    startupListener = statusMessage -> statusMessageList.add(statusMessage);
+    set(redirectOutput, overriddenDefaults, startupListener);
+
+    // act
+    remove();
+
+    // assert
+    assertThat(StartupStatus.getStartupListener()).isNull();
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/ProcessStreamReaderTestCase.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/ProcessStreamReaderTestCase.java b/geode-core/src/test/java/org/apache/geode/internal/process/ProcessStreamReaderTestCase.java
deleted file mode 100755
index 32a7f79..0000000
--- a/geode-core/src/test/java/org/apache/geode/internal/process/ProcessStreamReaderTestCase.java
+++ /dev/null
@@ -1,254 +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.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.Callable;
-
-import org.junit.After;
-import org.junit.Test;
-
-import org.apache.geode.internal.util.StopWatch;
-
-/**
- * Functional tests for ProcessStreamReader.
- * 
- */
-public abstract class ProcessStreamReaderTestCase {
-
-  /** Sleep timeout for {@link ProcessSleeps} instead of sleeping Long.MAX_VALUE */
-  protected static final int PROCESS_FAILSAFE_TIMEOUT = 10 * 60 * 1000;
-
-  /** Additional time for launched processes to live before terminating */
-  protected static final int PROCESS_TIME_TO_LIVE = 3 * 500;
-
-  /** Timeout to wait for a forked process to start */
-  protected static final int WAIT_FOR_PROCESS_TO_START_TIMEOUT = 60 * 1000;
-
-  /**
-   * Timeout to wait for a running process to die -- this keeps timing out so I'm increasing it very
-   * large
-   */
-  protected static final int WAIT_FOR_PROCESS_TO_DIE_TIMEOUT = 5 * 60 * 1000;
-
-  /** Timeout to wait for a new {@link ProcessStreamReader} to be running */
-  protected static final int WAIT_FOR_READER_IS_RUNNING_TIMEOUT = 20 * 1000;
-
-  /** Timeout to join to a running ProcessStreamReader thread */
-  protected static final int READER_JOIN_TIMEOUT = 20 * 1000;
-
-  /** Brief time to sleep before repeating a conditional check */
-  protected static final int INTERVAL = 20;
-
-  protected Process process;
-  protected ProcessStreamReader stderr;
-  protected ProcessStreamReader stdout;
-
-  @After
-  public void stopReadersAndDestroyProcess() throws Exception {
-    if (this.stderr != null) {
-      this.stderr.stop();
-    }
-    if (this.stdout != null) {
-      this.stdout.stop();
-    }
-    if (this.process != null) {
-      this.process.destroy(); // this is async and can require more than 10 seconds in Jenkins
-      /*
-       * assertEventuallyFalse("Timed out destroying process after " +
-       * WAIT_FOR_PROCESS_TO_DIE_TIMEOUT/(60*1000) + " minutes", new Callable<Boolean>() {
-       * 
-       * @Override public Boolean call() throws Exception { return isAlive(process); } },
-       * WAIT_FOR_PROCESS_TO_DIE_TIMEOUT, INTERVAL);
-       */
-    }
-  }
-
-  @Test
-  public void processLivesAfterClosingStreams() throws Exception {
-    this.process = new ProcessBuilder(createCommandLine(ProcessSleeps.class)).start();
-    this.process.getErrorStream().close();
-    this.process.getOutputStream().close();
-    this.process.getInputStream().close();
-    assertIsAlive(process);
-    this.process.destroy();
-  }
-
-  @Test
-  public void processTerminatesWhenDestroyed() throws Exception {
-    this.process = new ProcessBuilder(createCommandLine(ProcessSleeps.class)).start();
-    assertIsAlive(this.process);
-    this.process.destroy();
-    assertEventuallyFalse("Timed out destroying process", new Callable<Boolean>() {
-      @Override
-      public Boolean call() throws Exception {
-        return isAlive(process);
-      }
-    }, WAIT_FOR_PROCESS_TO_DIE_TIMEOUT, INTERVAL);
-    assertNotEquals(0, this.process.exitValue());
-  }
-
-  protected static void assertEventuallyTrue(final String message, final Callable<Boolean> callable,
-      final int timeout, final int interval) throws Exception {
-    boolean done = false;
-    for (StopWatch time = new StopWatch(true); !done && time.elapsedTimeMillis() < timeout; done =
-        (callable.call())) {
-      Thread.sleep(interval);
-    }
-    assertTrue(message + " within timeout of " + timeout + " milliseconds", done);
-  }
-
-  protected static void assertEventuallyFalse(final String message,
-      final Callable<Boolean> callable, final int timeout, final int interval) throws Exception {
-    boolean done = false;
-    for (StopWatch time = new StopWatch(true); !done && time.elapsedTimeMillis() < timeout; done =
-        (!callable.call())) {
-      Thread.sleep(interval);
-    }
-    assertTrue(message + " within timeout of " + timeout + " milliseconds", done);
-  }
-
-  protected static void assertIsAlive(final Process process) {
-    assertTrue(isAlive(process));
-  }
-
-  protected static void assertIsNotAlive(final Process process) {
-    assertFalse(isAlive(process));
-  }
-
-  protected static boolean isAlive(final Process process) {
-    try {
-      process.exitValue();
-      return false;
-    } catch (IllegalThreadStateException e) {
-      return true;
-    }
-  }
-
-  protected static String getJavaPath() {
-    String java = "java";
-    // if (SystemUtils.isWindows()) {
-    // java = "javaw";
-    // }
-    return new File(new File(System.getProperty("java.home"), "bin"), java).getPath();
-  }
-
-  protected static String getClassPath() {
-    return System.getProperty("java.class.path");
-  }
-
-  protected static String[] createCommandLine(final Class<?> clazz) {
-    return createCommandLine(clazz, null);
-  }
-
-  protected static String[] createCommandLine(final Class<?> clazz, final String[] jvmArgsOpts) {
-    List<String> commandLine = new ArrayList<>();
-
-    commandLine.add(getJavaPath());
-    commandLine.add("-server");
-    commandLine.add("-classpath");
-    commandLine.add(getClassPath());
-
-    addJvmArgumentsAndOptions(commandLine, jvmArgsOpts);
-
-    commandLine.add("-Djava.awt.headless=true");
-    commandLine.add(clazz.getName());
-
-    return commandLine.toArray(new String[commandLine.size()]);
-  }
-
-  protected static void addJvmArgumentsAndOptions(final List<String> commandLine,
-      final String[] jvmArgsOpts) {
-    if (jvmArgsOpts != null) {
-      commandLine.addAll(Arrays.asList(jvmArgsOpts));
-    }
-  }
-
-  protected static void assertEventuallyIsRunning(final ProcessStreamReader reader)
-      throws Exception {
-    assertEventuallyTrue("Waiting for ProcessStreamReader to be running", new Callable<Boolean>() {
-      @Override
-      public Boolean call() throws Exception {
-        return reader.isRunning();
-      }
-    }, WAIT_FOR_READER_IS_RUNNING_TIMEOUT, INTERVAL);
-  }
-
-  protected static class ProcessSleeps {
-    public static void main(String[] args) throws InterruptedException {
-      Thread.sleep(PROCESS_FAILSAFE_TIMEOUT);
-    }
-  }
-
-  protected static class ProcessThrowsError {
-    protected static String[] LINES = new String[] {"ProcessThrowsError is starting\n",
-        "ProcessThrowsError is sleeping\n", "ProcessThrowsError is throwing\n"};
-    protected static String ERROR_MSG = "ProcessThrowsError throws Error";
-
-    public static void main(String[] args) throws InterruptedException {
-      System.err.print(LINES[0]);
-      System.err.print(LINES[1]);
-      Thread.sleep(PROCESS_TIME_TO_LIVE);
-      System.err.print(LINES[2]);
-      throw new Error(ERROR_MSG);
-    }
-  }
-
-  protected static class ProcessPrintsToStdout {
-    protected static String[] LINES = new String[] {"ProcessPrintsToStdout is starting\n",
-        "ProcessPrintsToStdout is sleeping\n", "ProcessPrintsToStdout is exiting\n"};
-
-    public static void main(String[] args) throws InterruptedException {
-      System.out.print(LINES[0]);
-      System.out.print(LINES[1]);
-      Thread.sleep(PROCESS_TIME_TO_LIVE);
-      System.out.print(LINES[2]);
-    }
-  }
-
-  protected static class ProcessPrintsToStderr {
-    protected static String[] LINES = new String[] {"ProcessPrintsToStdout is starting\n",
-        "ProcessPrintsToStdout is sleeping\n", "ProcessPrintsToStdout is exiting\n"};
-
-    public static void main(String[] args) throws InterruptedException {
-      System.err.print(LINES[0]);
-      System.err.print(LINES[1]);
-      Thread.sleep(PROCESS_TIME_TO_LIVE);
-      System.err.print(LINES[2]);
-    }
-  }
-
-  protected static class ProcessPrintsToBoth {
-    protected static String[] OUT_LINES = new String[] {"ProcessPrintsToBoth(out) is starting\n",
-        "ProcessPrintsToBoth(out) is sleeping\n", "ProcessPrintsToBoth(out) is exiting\n"};
-    protected static String[] ERR_LINES = new String[] {"ProcessPrintsToBoth(err) is starting\n",
-        "ProcessPrintsToBoth(err) is sleeping\n", "ProcessPrintsToBoth(err) is exiting\n"};
-
-    public static void main(String[] args) throws InterruptedException {
-      System.out.print(OUT_LINES[0]);
-      System.err.print(ERR_LINES[0]);
-      System.out.print(OUT_LINES[1]);
-      System.err.print(ERR_LINES[1]);
-      Thread.sleep(PROCESS_TIME_TO_LIVE);
-      System.out.print(OUT_LINES[2]);
-      System.err.print(ERR_LINES[2]);
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/StartupStatusTest.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/StartupStatusTest.java b/geode-core/src/test/java/org/apache/geode/internal/process/StartupStatusTest.java
new file mode 100644
index 0000000..04e23f3
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/StartupStatusTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.Collections.synchronizedList;
+import static org.apache.geode.internal.process.StartupStatus.clearListener;
+import static org.apache.geode.internal.process.StartupStatus.getStartupListener;
+import static org.apache.geode.internal.process.StartupStatus.setListener;
+import static org.apache.geode.internal.process.StartupStatus.startup;
+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.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import org.apache.geode.i18n.StringId;
+import org.apache.geode.test.junit.categories.UnitTest;
+
+@Category(UnitTest.class)
+public class StartupStatusTest {
+
+  private StartupStatusListener listener;
+  private List<String> statusMessageList;
+
+  @Before
+  public void before() throws Exception {
+    listener = mock(StartupStatusListener.class);
+    statusMessageList = synchronizedList(new ArrayList<>());
+  }
+
+  @After
+  public void after() throws Exception {
+    clearListener();
+  }
+
+  @Test
+  public void getStartupListener_returnsNullByDefault() throws Exception {
+    // act/assert
+    assertThat(getStartupListener()).isNull();
+  }
+
+  @Test
+  public void setListener_null_clearsStartupListener() throws Exception {
+    // arrange
+    listener = null;
+
+    // act
+    setListener(listener);
+
+    // assert
+    assertThat(getStartupListener()).isNull();
+  }
+
+  @Test
+  public void getStartupListener_returnsSetListener() throws Exception {
+    // arrange
+    setListener(listener);
+
+    // act/assert
+    assertThat(getStartupListener()).isSameAs(listener);
+  }
+
+  @Test
+  public void clearListener_doesNothingIfNull() throws Exception {
+    // arrange
+    listener = null;
+    setListener(listener);
+    assertThat(getStartupListener()).isNull();
+
+    // act
+    clearListener();
+
+    // assert
+    assertThat(getStartupListener()).isNull();
+  }
+
+  @Test
+  public void clearListener_unsetsListener() throws Exception {
+    // arrange
+    setListener(listener);
+    assertThat(getStartupListener()).isNotNull();
+
+    // act
+    clearListener();
+
+    // assert
+    assertThat(getStartupListener()).isNull();
+  }
+
+  @Test
+  public void startup_nullStringId_throwsIllegalArgumentException() throws Exception {
+    // arrange
+    StringId stringId = null;
+    Object[] params = new Object[0];
+
+    // act/assert
+    assertThatThrownBy(() -> startup(stringId, params)).isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Invalid msgId 'null' specified");
+  }
+
+  @Test
+  public void startup_emptyParams() throws Exception {
+    // arrange
+    StringId stringId = new StringId(1, "my string");
+    Object[] params = new Object[0];
+
+    // act
+    startup(stringId, params);
+
+    // assert (does not throw)
+    assertThat(getStartupListener()).isNull();
+  }
+
+  @Test
+  public void startup_doesNothingIfNoListener() throws Exception {
+    // arrange
+    StringId stringId = new StringId(1, "my string");
+    Object[] params = new Object[0];
+
+    // act
+    startup(stringId, params);
+
+    // assert (does nothing)
+    assertThat(getStartupListener()).isNull();
+  }
+
+  @Test
+  public void startup_invokesListener() throws Exception {
+    // arrange
+    listener = statusMessage -> statusMessageList.add(statusMessage);
+    StringId stringId = new StringId(1, "my string");
+    Object[] params = new Object[0];
+    setListener(listener);
+
+    // act
+    startup(stringId, params);
+
+    // assert
+    assertThat(statusMessageList).hasSize(1).contains("my string");
+  }
+
+  @Test
+  public void startupTwice_invokesListenerTwice() throws Exception {
+    // arrange
+    listener = statusMessage -> statusMessageList.add(statusMessage);
+    StringId stringIdOne = new StringId(1, "my string");
+    StringId stringIdTwo = new StringId(2, "other string");
+    Object[] params = new Object[0];
+    setListener(listener);
+
+    // act
+    startup(stringIdOne, params);
+    startup(stringIdTwo, params);
+
+    // assert
+    assertThat(statusMessageList).hasSize(2).contains("my string").contains("other string");
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/io/EmptyFileWriter.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/io/EmptyFileWriter.java b/geode-core/src/test/java/org/apache/geode/internal/process/io/EmptyFileWriter.java
new file mode 100644
index 0000000..3acf0a6
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/io/EmptyFileWriter.java
@@ -0,0 +1,40 @@
+/*
+ * 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.io;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Creates an empty file.
+ */
+public class EmptyFileWriter {
+
+  private final File file;
+
+  public EmptyFileWriter(final File file) {
+    this.file = file;
+  }
+
+  public File createNewFile() throws IOException {
+    assertThat(file).doesNotExist();
+    assertThat(file.createNewFile()).isTrue();
+    assertThat(file).exists();
+    return file;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/io/IntegerFileReader.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/io/IntegerFileReader.java b/geode-core/src/test/java/org/apache/geode/internal/process/io/IntegerFileReader.java
new file mode 100644
index 0000000..4f405bf
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/io/IntegerFileReader.java
@@ -0,0 +1,38 @@
+/*
+ * 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.io;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+
+public class IntegerFileReader {
+
+  private final File file;
+
+  public IntegerFileReader(final File file) {
+    this.file = file;
+  }
+
+  public int readFromFile() throws IOException {
+    assertThat(file).exists();
+    try (BufferedReader bufferedReader = new BufferedReader(new FileReader(file))) {
+      return Integer.parseInt(bufferedReader.readLine());
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/io/IntegerFileWriter.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/io/IntegerFileWriter.java b/geode-core/src/test/java/org/apache/geode/internal/process/io/IntegerFileWriter.java
new file mode 100644
index 0000000..47f54bc
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/io/IntegerFileWriter.java
@@ -0,0 +1,33 @@
+/*
+ * 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.io;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Writes an integer to the file.
+ */
+public class IntegerFileWriter extends StringFileWriter {
+
+  public IntegerFileWriter(final File file) {
+    super(file);
+  }
+
+  public void writeToFile(final int pid) throws IOException {
+    writeToFile(String.valueOf(pid));
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/test/java/org/apache/geode/internal/process/io/StringFileWriter.java
----------------------------------------------------------------------
diff --git a/geode-core/src/test/java/org/apache/geode/internal/process/io/StringFileWriter.java b/geode-core/src/test/java/org/apache/geode/internal/process/io/StringFileWriter.java
new file mode 100644
index 0000000..76db475
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/internal/process/io/StringFileWriter.java
@@ -0,0 +1,43 @@
+/*
+ * 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.io;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+/**
+ * Writes a string to the file.
+ */
+public class StringFileWriter {
+
+  private final File file;
+
+  public StringFileWriter(final File file) {
+    this.file = file;
+  }
+
+  public void writeToFile(final String string) throws IOException {
+    assertThat(file).doesNotExist();
+    try (FileWriter writer = new FileWriter(file)) {
+      writer.write(string);
+      writer.flush();
+    }
+    assertThat(file).exists();
+  }
+
+}