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 - } - } -}