diff options
4 files changed, 147 insertions, 5 deletions
diff --git a/org.eclipse.debug.core/core/org/eclipse/debug/core/model/RuntimeProcess.java b/org.eclipse.debug.core/core/org/eclipse/debug/core/model/RuntimeProcess.java index 8ee0a099d..1bfb08a1e 100644 --- a/org.eclipse.debug.core/core/org/eclipse/debug/core/model/RuntimeProcess.java +++ b/org.eclipse.debug.core/core/org/eclipse/debug/core/model/RuntimeProcess.java @@ -20,6 +20,7 @@ import java.nio.charset.UnsupportedCharsetException; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.PlatformObject; @@ -219,7 +220,11 @@ public class RuntimeProcess extends PlatformObject implements IProcess { } catch (IllegalThreadStateException ie) { } try { - Thread.sleep(TIME_TO_WAIT_FOR_THREAD_DEATH); + if (process != null) { + process.waitFor(TIME_TO_WAIT_FOR_THREAD_DEATH, TimeUnit.MILLISECONDS); + } else { + Thread.sleep(TIME_TO_WAIT_FOR_THREAD_DEATH); + } } catch (InterruptedException e) { } attempts++; diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java index 9e3efa615..32e1ed3c0 100644 --- a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java +++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/AutomatedSuite.java @@ -23,6 +23,7 @@ import org.eclipse.debug.tests.console.IOConsoleFixedWidthTests; import org.eclipse.debug.tests.console.IOConsoleTests; import org.eclipse.debug.tests.console.ProcessConsoleManagerTests; import org.eclipse.debug.tests.console.ProcessConsoleTests; +import org.eclipse.debug.tests.console.RuntimeProcessTests; import org.eclipse.debug.tests.console.StreamsProxyTests; import org.eclipse.debug.tests.console.TextConsoleViewerTest; import org.eclipse.debug.tests.launching.AcceleratorSubstitutionTests; @@ -123,6 +124,7 @@ public class AutomatedSuite extends TestSuite { addTest(new TestSuite(ProcessConsoleTests.class)); addTest(new TestSuite(StreamsProxyTests.class)); addTest(new TestSuite(TextConsoleViewerTest.class)); + addTest(new TestSuite(RuntimeProcessTests.class)); // Launch Groups addTest(new TestSuite(LaunchGroupTests.class)); diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/MockProcess.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/MockProcess.java index 7bd305e18..aa84545c0 100644 --- a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/MockProcess.java +++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/MockProcess.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019 Paul Pazderski and others. + * Copyright (c) 2019, 2020 Paul Pazderski and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -17,6 +17,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.debug.core.DebugPlugin; @@ -57,6 +58,8 @@ public class MockProcess extends Process { * </p> */ private long endTime; + /** The simulated exit code. */ + private int exitCode = 0; /** * Create new silent mockup process which runs for a given amount of time. @@ -171,7 +174,24 @@ public class MockProcess extends Process { } } } - return 0; + return exitCode; + } + + @Override + public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException { + long remainingMs = unit.toMillis(timeout); + final long timeoutMs = System.currentTimeMillis() + remainingMs; + synchronized (waitForTerminationLock) { + while (!isTerminated() && remainingMs > 0) { + long waitTime = endTime == RUN_FOREVER ? Long.MAX_VALUE : endTime - System.currentTimeMillis(); + waitTime = Math.min(waitTime, remainingMs); + if (waitTime > 0) { + waitForTerminationLock.wait(waitTime); + } + remainingMs = timeoutMs - System.currentTimeMillis(); + } + } + return isTerminated(); } @Override @@ -180,13 +200,23 @@ public class MockProcess extends Process { final String end = (endTime == RUN_FOREVER ? "never." : "in " + (endTime - System.currentTimeMillis()) + " ms."); throw new IllegalThreadStateException("Mockup process terminates " + end); } - return 0; + return exitCode; } @Override public void destroy() { + destroy(0); + } + + /** + * Simulate a delay for the mockup process shutdown. + * + * @param delay amount of milliseconds must pass after destroy was called + * and before the mockup process goes in terminated state + */ + public void destroy(int delay) { synchronized (waitForTerminationLock) { - endTime = System.currentTimeMillis(); + endTime = System.currentTimeMillis() + delay; waitForTerminationLock.notifyAll(); } } @@ -201,6 +231,15 @@ public class MockProcess extends Process { } /** + * Set the exit code returned once the process is finished. + * + * @param exitCode new exit code + */ + public void setExitValue(int exitCode) { + this.exitCode = exitCode; + } + + /** * Create a {@link RuntimeProcess} which wraps this {@link MockProcess}. * <p> * Note: the process will only be connected to a minimal dummy launch diff --git a/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/RuntimeProcessTests.java b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/RuntimeProcessTests.java new file mode 100644 index 000000000..c3d631b10 --- /dev/null +++ b/org.eclipse.debug.tests/src/org/eclipse/debug/tests/console/RuntimeProcessTests.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2020 Paul Pazderski and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Paul Pazderski - initial API and implementation + *******************************************************************************/ +package org.eclipse.debug.tests.console; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.debug.core.DebugEvent; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.IDebugEventSetListener; +import org.eclipse.debug.core.model.RuntimeProcess; +import org.eclipse.debug.tests.AbstractDebugTest; +import org.eclipse.debug.tests.TestUtil; + +public class RuntimeProcessTests extends AbstractDebugTest { + + public RuntimeProcessTests() { + super(RuntimeProcessTests.class.getSimpleName()); + } + + public RuntimeProcessTests(String name) { + super(name); + } + + /** + * Test behavior of {@link RuntimeProcess} if the wrapped process + * terminates. + */ + public void testProcessTerminated() throws Exception { + AtomicInteger processTerminateEvents = new AtomicInteger(); + DebugPlugin.getDefault().addDebugEventListener(new IDebugEventSetListener() { + @Override + public void handleDebugEvents(DebugEvent[] events) { + for (DebugEvent event : events) { + if (event.getKind() == DebugEvent.TERMINATE) { + processTerminateEvents.incrementAndGet(); + } + } + } + }); + + MockProcess mockProcess = new MockProcess(MockProcess.RUN_FOREVER); + RuntimeProcess runtimeProcess = mockProcess.toRuntimeProcess(); + + assertFalse("RuntimeProcess already terminated.", runtimeProcess.isTerminated()); + assertTrue(runtimeProcess.canTerminate()); + + mockProcess.setExitValue(1); + mockProcess.destroy(); + + TestUtil.waitWhile(p -> !p.isTerminated(), runtimeProcess, 1000, p -> "RuntimePocess not terminated."); + TestUtil.waitForJobs(getName(), 25, 500); + assertEquals("Wrong number of terminate events.", 1, processTerminateEvents.get()); + assertEquals("RuntimeProcess reported wrong exit code.", 1, runtimeProcess.getExitValue()); + } + + /** Test {@link RuntimeProcess} terminating the wrapped process. */ + public void testTerminateProcess() throws Exception { + AtomicInteger processTerminateEvents = new AtomicInteger(); + DebugPlugin.getDefault().addDebugEventListener(new IDebugEventSetListener() { + @Override + public void handleDebugEvents(DebugEvent[] events) { + for (DebugEvent event : events) { + if (event.getKind() == DebugEvent.TERMINATE) { + processTerminateEvents.incrementAndGet(); + } + } + } + }); + + MockProcess mockProcess = new MockProcess(MockProcess.RUN_FOREVER); + RuntimeProcess runtimeProcess = mockProcess.toRuntimeProcess(); + + assertFalse("RuntimeProcess already terminated.", runtimeProcess.isTerminated()); + assertTrue(runtimeProcess.canTerminate()); + + mockProcess.setExitValue(1); + runtimeProcess.terminate(); + assertFalse("RuntimeProcess failed to terminated wrapped process.", mockProcess.isAlive()); + + TestUtil.waitWhile(p -> !p.isTerminated(), runtimeProcess, 1000, p -> "RuntimePocess not terminated."); + TestUtil.waitForJobs(getName(), 25, 500); + assertEquals("Wrong number of terminate events.", 1, processTerminateEvents.get()); + assertEquals("RuntimeProcess reported wrong exit code.", 1, runtimeProcess.getExitValue()); + } +} |