Bug 550051: Engine termination should be improved
reworked termination process
on user requested termination we now set an exception as execution
result
interface cleanup
Change-Id: I9fed889b754f92420820c4b50383485b22f364ec
diff --git a/plugins/org.eclipse.ease.lang.javascript.rhino/src/org/eclipse/ease/lang/javascript/rhino/ObservingContextFactory.java b/plugins/org.eclipse.ease.lang.javascript.rhino/src/org/eclipse/ease/lang/javascript/rhino/ObservingContextFactory.java
index 6d71ec3..82d72fd 100644
--- a/plugins/org.eclipse.ease.lang.javascript.rhino/src/org/eclipse/ease/lang/javascript/rhino/ObservingContextFactory.java
+++ b/plugins/org.eclipse.ease.lang.javascript.rhino/src/org/eclipse/ease/lang/javascript/rhino/ObservingContextFactory.java
@@ -13,18 +13,18 @@
import java.util.HashSet;
import java.util.Set;
-import org.eclipse.ease.ExitException;
+import org.eclipse.ease.ScriptExecutionException;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ContextFactory;
public class ObservingContextFactory extends ContextFactory {
- private final Set<Context> mTerminationRequests = new HashSet<Context>();
+ private final Set<Context> mTerminationRequests = new HashSet<>();
@Override
protected synchronized void observeInstructionCount(final Context cx, final int instructionCount) {
if (mTerminationRequests.remove(cx))
- throw new ExitException();
+ throw new ScriptExecutionException("Engine got terminated");
super.observeInstructionCount(cx, instructionCount);
}
diff --git a/plugins/org.eclipse.ease.lang.javascript.rhino/src/org/eclipse/ease/lang/javascript/rhino/RhinoScriptEngine.java b/plugins/org.eclipse.ease.lang.javascript.rhino/src/org/eclipse/ease/lang/javascript/rhino/RhinoScriptEngine.java
index df40762..afe0905 100644
--- a/plugins/org.eclipse.ease.lang.javascript.rhino/src/org/eclipse/ease/lang/javascript/rhino/RhinoScriptEngine.java
+++ b/plugins/org.eclipse.ease.lang.javascript.rhino/src/org/eclipse/ease/lang/javascript/rhino/RhinoScriptEngine.java
@@ -289,8 +289,11 @@
@Override
public void terminateCurrent() {
- // typically requested by a different thread, so do not use getContext() here
- ((ObservingContextFactory) ContextFactory.getGlobal()).terminate(fContext);
+ if (Thread.currentThread().equals(getThread()))
+ throw new ScriptExecutionException("Script got terminated");
+ else
+ // requested by a different thread, so do not use getContext() here
+ ((ObservingContextFactory) ContextFactory.getGlobal()).terminate(fContext);
}
@Override
diff --git a/plugins/org.eclipse.ease.ui.scripts/src/org/eclipse/ease/ui/scripts/keywordhandler/ShutdownHandler.java b/plugins/org.eclipse.ease.ui.scripts/src/org/eclipse/ease/ui/scripts/keywordhandler/ShutdownHandler.java
index d04acb2..77f79a1 100644
--- a/plugins/org.eclipse.ease.ui.scripts/src/org/eclipse/ease/ui/scripts/keywordhandler/ShutdownHandler.java
+++ b/plugins/org.eclipse.ease.ui.scripts/src/org/eclipse/ease/ui/scripts/keywordhandler/ShutdownHandler.java
@@ -40,30 +40,27 @@
@Override
protected IStatus run(final IProgressMonitor monitor) {
// wait for engines to be completed
- for (IScriptEngine engine : fEngines) {
- long timeout = (fStartTime + fShutdownTimeout) - System.currentTimeMillis();
+ for (final IScriptEngine engine : fEngines) {
+ final long timeout = (fStartTime + fShutdownTimeout) - System.currentTimeMillis();
if (timeout > 0) {
try {
- engine.join(timeout);
- } catch (InterruptedException e) {
+ engine.joinEngine(timeout);
+ } catch (final InterruptedException e) {
}
} else
break;
}
// terminate engines that are not completed
- for (IScriptEngine engine : fEngines) {
+ for (final IScriptEngine engine : fEngines) {
if (!engine.isFinished())
engine.terminate();
}
// call final shutdown
- Display.getDefault().asyncExec(new Runnable() {
- @Override
- public void run() {
- fShutdownScripts.clear();
- PlatformUI.getWorkbench().close();
- }
+ Display.getDefault().asyncExec(() -> {
+ fShutdownScripts.clear();
+ PlatformUI.getWorkbench().close();
});
return Status.OK_STATUS;
@@ -71,7 +68,7 @@
}
/** Registered shutdown scripts. */
- Collection<IScript> fShutdownScripts = new HashSet<IScript>();
+ Collection<IScript> fShutdownScripts = new HashSet<>();
/** Default script timeout: 10s. */
private long fShutdownTimeout = 10 * 1000;
@@ -85,7 +82,7 @@
@Override
public void handleEvent(final Event event) {
final IScript script = (IScript) event.getProperty("script");
- String value = (String) event.getProperty("value");
+ final String value = (String) event.getProperty("value");
if (value == null)
fShutdownScripts.remove(script);
@@ -95,7 +92,7 @@
if (!value.isEmpty()) {
try {
fShutdownTimeout = Math.max(fShutdownTimeout, Integer.parseInt(value) * 1000);
- } catch (NumberFormatException e) {
+ } catch (final NumberFormatException e) {
Logger.error(Activator.PLUGIN_ID, "Invalid onShutdown timeout for script: " + script.getLocation());
}
}
@@ -111,8 +108,8 @@
public boolean preShutdown(final IWorkbench workbench, final boolean forced) {
if ((!forced) && (!fShutdownScripts.isEmpty())) {
fStartTime = System.currentTimeMillis();
- fEngines = new HashSet<IScriptEngine>();
- for (IScript script : fShutdownScripts)
+ fEngines = new HashSet<>();
+ for (final IScript script : fShutdownScripts)
fEngines.add(script.run());
new ShutdownJob().schedule();
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/AbstractReplScriptEngine.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/AbstractReplScriptEngine.java
index de78362..5b4bbec 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/AbstractReplScriptEngine.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/AbstractReplScriptEngine.java
@@ -61,24 +61,12 @@
return fTerminateOnIdle;
}
- /**
- * Get termination status of the interpreter. A terminated interpreter cannot be restarted.
- *
- * @return true if interpreter is terminated.
- */
@Override
- protected boolean isTerminated() {
- return fTerminateOnIdle && isIdle();
- }
+ protected boolean shallTerminate() {
+ if (getTerminateOnIdle())
+ return super.shallTerminate();
- /**
- * Get idle status of the interpreter. The interpreter is IDLE if there are no pending execution requests and the interpreter is not terminated.
- *
- * @return true if interpreter is IDLE
- */
- @Override
- public boolean isIdle() {
- return super.isTerminated();
+ return getMonitor().isCanceled();
}
@Override
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/AbstractScriptEngine.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/AbstractScriptEngine.java
index 4c6f1b1..2322217 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/AbstractScriptEngine.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/AbstractScriptEngine.java
@@ -24,8 +24,10 @@
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IProgressMonitorWithBlocking;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.ILaunch;
@@ -36,6 +38,7 @@
import org.eclipse.ease.security.ScriptUIAccess;
import org.eclipse.ease.service.EngineDescription;
import org.eclipse.ease.tools.ResourceTools;
+import org.eclipse.ui.internal.progress.ProgressManager.JobMonitor;
/**
* Base implementation for a script engine. Handles Job implementation of script engine, adding script code for execution, module loading support and a basic
@@ -44,37 +47,6 @@
public abstract class AbstractScriptEngine extends Job implements IScriptEngine {
/**
- * Watches over the script execution job and forwards termination requests from the Progress UI.
- */
- private class ScriptTerminator extends Job {
-
- private final IProgressMonitor fMonitor;
-
- /**
- * @param monitor
- * monitor from the script engine job
- */
- public ScriptTerminator(IProgressMonitor monitor) {
- super("ScriptEngine termination guard");
- fMonitor = monitor;
-
- setSystem(true);
- }
-
- @Override
- protected IStatus run(IProgressMonitor monitor) {
- if (fMonitor.isCanceled())
- terminate();
-
- else
- // check again in a second
- schedule(1000);
-
- return Status.OK_STATUS;
- }
- }
-
- /**
* Get the current script engine. Works only if executed from the script engine thread.
*
* @return script engine or <code>null</code>
@@ -86,6 +58,23 @@
return null;
}
+ /**
+ * Get the beautified name of a file to be set as part of the job title.
+ *
+ * @param file
+ * executed file
+ * @return beautified name or <code>null</code>
+ */
+ private static String getFilename(Object file) {
+ if (file instanceof IFile) {
+ return ResourceTools.toAbsoluteLocation(file, null);
+ } else if (file instanceof File) {
+ return ResourceTools.toAbsoluteLocation(file, null);
+ } else {
+ return null;
+ }
+ }
+
/** List of code junks to be executed. */
private final List<Script> fScheduledScripts = Collections.synchronizedList(new ArrayList<Script>());
@@ -108,8 +97,6 @@
private boolean fCloseStreamsOnTerminate;
- private boolean fTerminated = false;
-
/** Registered security checks for engine actions. */
private final HashMap<ActionType, List<ISecurityCheck>> fSecurityChecks = new HashMap<>();
@@ -198,16 +185,6 @@
return result.getResult();
}
- private static String getFilename(Object file) {
- if (file instanceof IFile) {
- return ResourceTools.toAbsoluteLocation(file, null);
- } else if (file instanceof File) {
- return ResourceTools.toAbsoluteLocation(file, null);
- } else {
- return null;
- }
- }
-
/**
* Inject script code to the script engine. Injected code is processed synchronous by the current thread unless <i>uiThread</i> is set to <code>true</code>.
* Nevertheless this is a blocking call.
@@ -234,7 +211,7 @@
if (securityChecks != null) {
for (final ISecurityCheck check : securityChecks) {
if (!check.doIt(ActionType.INJECT_CODE, script, uiThread))
- throw new ExitException();
+ throw new ScriptEngineException("Security check failed: " + check.toString());
}
}
@@ -246,9 +223,6 @@
script.setResult(execute(script, script.getFile(), fStackTrace.get(0).getName(), uiThread));
- } catch (final ExitException e) {
- script.setResult(e.getCondition());
-
} catch (final BreakException e) {
script.setResult(e.getCondition());
@@ -274,9 +248,6 @@
return script.getResult();
}
- /**
- * @param filename
- */
private void updateJobName(String filename) {
if (filename != null) {
String baseName = getName();
@@ -291,8 +262,40 @@
protected IStatus run(final IProgressMonitor monitor) {
fMonitor = monitor;
+ addStopButtonMonitor();
+
+ IStatus returnStatus = setupRun();
+ if (Status.OK_STATUS.equals(returnStatus)) {
+ // main loop
+ while (!shallTerminate()) {
+
+ // execute code
+ if (!fScheduledScripts.isEmpty()) {
+ final Script piece = fScheduledScripts.remove(0);
+ final ScriptResult scriptResult = inject(piece, true, false);
+ if (scriptResult.hasException())
+ returnStatus = new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Script execution failed", scriptResult.getException());
+
+ } else {
+ synchronized (this) {
+ try {
+ Logger.trace(Activator.PLUGIN_ID, TRACE_SCRIPT_ENGINE, "Engine idle: " + getName());
+ wait();
+ } catch (final InterruptedException e) {
+ }
+ }
+ }
+ }
+ }
+
+ if ((Status.OK_STATUS.equals(returnStatus)) && (getMonitor().isCanceled()))
+ returnStatus = Status.CANCEL_STATUS;
+
+ return cleanupRun(returnStatus);
+ }
+
+ private IStatus setupRun() {
Logger.trace(Activator.PLUGIN_ID, TRACE_SCRIPT_ENGINE, "Engine started: " + getName());
- IStatus returnStatus = Status.OK_STATUS;
addSecurityCheck(ActionType.INJECT_CODE, ScriptUIAccess.getInstance());
@@ -300,9 +303,6 @@
setupEngine();
fSetupDone = true;
- if (!isSystem())
- new ScriptTerminator(monitor).schedule();
-
// engine is initialized, set buffered variables
for (final Entry<String, Object> entry : fBufferedVariables.entrySet()) {
setVariable(entry.getKey(), entry.getValue());
@@ -315,78 +315,121 @@
notifyExecutionListeners(null, IExecutionListener.ENGINE_START);
- // main loop
- while ((!monitor.isCanceled()) && (!isTerminated())) {
-
- // execute code
- if (!fScheduledScripts.isEmpty()) {
- final Script piece = fScheduledScripts.remove(0);
- inject(piece, true, false);
-
- } else {
-
- synchronized (this) {
- if (!isTerminated()) {
- try {
- Logger.trace(Activator.PLUGIN_ID, TRACE_SCRIPT_ENGINE, "Engine idle: " + getName());
- wait();
- } catch (final InterruptedException e) {
- }
- }
- }
- }
- }
-
- returnStatus = (!isTerminated()) ? Status.OK_STATUS : Status.CANCEL_STATUS;
-
} catch (final ScriptEngineException e) {
- returnStatus = new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Could not setup script engine", e);
-
- } finally {
- // discard pending code pieces
- synchronized (fScheduledScripts) {
- for (final Script script : fScheduledScripts)
- script.setException(new ExitException());
- }
-
- fScheduledScripts.clear();
-
- notifyExecutionListeners(null, IExecutionListener.ENGINE_END);
-
- try {
- teardownEngine();
- } catch (final ScriptEngineException e) {
- if (returnStatus.getSeverity() < IStatus.ERROR) {
- // We were almost all OK (or just warnings/infos) but then we failed at shutdown
- // Note we don't override a CANCEL
- returnStatus = new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Could not teardown script engine", e);
- }
- } finally {
- fTerminated = true;
- synchronized (this) {
- notifyAll();
- }
-
- // discard pending code pieces
- synchronized (fScheduledScripts) {
- for (final Script script : fScheduledScripts)
- script.setException(new ExitException());
-
- fScheduledScripts.clear();
- }
-
- closeStreams();
-
- Logger.trace(Activator.PLUGIN_ID, TRACE_SCRIPT_ENGINE, "Engine terminated: " + getName());
- }
+ return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Could not setup script engine", e);
}
- monitor.done();
- fMonitor = null;
+ return Status.OK_STATUS;
+ }
+
+ private IStatus cleanupRun(IStatus returnStatus) {
+
+ // discard pending code pieces
+ synchronized (fScheduledScripts) {
+ for (final Script script : fScheduledScripts)
+ script.setException(new ScriptExecutionException("Engine got terminated"));
+ }
+
+ fScheduledScripts.clear();
+
+ notifyExecutionListeners(null, IExecutionListener.ENGINE_END);
+
+ try {
+ teardownEngine();
+ } catch (final ScriptEngineException e) {
+ if (returnStatus.getSeverity() < IStatus.ERROR) {
+ // We were almost all OK (or just warnings/infos) but then we failed at shutdown
+ // Note we don't override a CANCEL
+ returnStatus = new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Could not teardown script engine", e);
+ }
+ } finally {
+ synchronized (this) {
+ notifyAll();
+ }
+
+ closeStreams();
+
+ Logger.trace(Activator.PLUGIN_ID, TRACE_SCRIPT_ENGINE, "Engine terminated: " + getName());
+
+ fMonitor.done();
+ fMonitor = null;
+ }
return returnStatus;
}
+ /**
+ * Add monitor to detect clicks on the stop button in the Progress view.
+ */
+ private void addStopButtonMonitor() {
+ if (fMonitor instanceof JobMonitor)
+ ((JobMonitor) fMonitor).addProgressListener(new ScriptEngineMonitor());
+ }
+
+ /**
+ * Evaluate if the engine shall terminate.
+ *
+ * @return <code>true</code> when termination is requested or there is no more work to be done
+ */
+ protected boolean shallTerminate() {
+ return getMonitor().isCanceled() || fScheduledScripts.isEmpty();
+ }
+
+ @Override
+ public void terminate() {
+
+ final IProgressMonitor monitor = getMonitor();
+ if ((monitor != null) && (!monitor.isCanceled()))
+ monitor.setCanceled(true);
+
+ terminateCurrent();
+
+ synchronized (this) {
+ notify();
+ }
+ }
+
+ /**
+ * Check engine for cancellation request and terminate if indicated by the monitor.
+ */
+ public void checkForCancellation() {
+ final IProgressMonitor monitor = getMonitor();
+ if ((monitor != null) && (monitor.isCanceled())) {
+ if (Thread.currentThread().equals(getThread()))
+ throw new ScriptExecutionException("Engine got terminated");
+ }
+ }
+
+ @Override
+ public boolean isFinished() {
+ // setup was done, hence we were started
+ return (Job.NONE == getState()) && fSetupDone;
+ }
+
+ @Override
+ public void joinEngine() throws InterruptedException {
+ if (!Thread.currentThread().equals(getThread())) {
+ // we cannot join our own thread
+
+ synchronized (this) {
+ while (!isFinished())
+ wait(1000);
+ }
+ }
+ }
+
+ @Override
+ public void joinEngine(final long timeout) throws InterruptedException {
+ if (!Thread.currentThread().equals(getThread())) {
+ // we cannot join our own thread
+
+ synchronized (this) {
+ if (!isFinished())
+ wait(timeout);
+ }
+ }
+ }
+
@Override
public IProgressMonitor getMonitor() {
return fMonitor;
@@ -464,15 +507,6 @@
fErrorStream = null;
}
- /**
- * Get termination status of the interpreter. A terminated interpreter cannot be restarted.
- *
- * @return true if interpreter is terminated.
- */
- protected boolean isTerminated() {
- return fScheduledScripts.isEmpty();
- }
-
@Override
public void addExecutionListener(final IExecutionListener listener) {
fExecutionListeners.add(listener);
@@ -488,20 +522,6 @@
((IExecutionListener) listener).notify(this, script, status);
}
- @Override
- public void terminate() {
- fScheduledScripts.clear();
- terminateCurrent();
-
- // ask thread to terminate
- cancel();
-
- // see bug 512607
- final Thread thread = getThread();
- if (thread != null)
- thread.interrupt();
- }
-
public ScriptStackTrace getStackTrace() {
return fStackTrace;
}
@@ -584,19 +604,6 @@
}
@Override
- public boolean isFinished() {
- return fTerminated;
- }
-
- @Override
- public void join(final long timeout) throws InterruptedException {
- synchronized (this) {
- if (!isFinished())
- wait(timeout);
- }
- }
-
- @Override
public void addSecurityCheck(ActionType type, ISecurityCheck check) {
if (!fSecurityChecks.containsKey(type))
fSecurityChecks.put(type, new ArrayList<ISecurityCheck>());
@@ -672,4 +679,28 @@
* any exception thrown during script execution
*/
protected abstract Object execute(Script script, Object reference, String fileName, boolean uiThread) throws Throwable;
+
+ /**
+ * Simple monitor to forward cancellation requests to the script engine.
+ */
+ private class ScriptEngineMonitor extends NullProgressMonitor implements IProgressMonitorWithBlocking {
+
+ @Override
+ public void setCanceled(boolean cancelled) {
+ super.setCanceled(cancelled);
+
+ if (isCanceled())
+ terminate();
+ }
+
+ @Override
+ public void setBlocked(IStatus reason) {
+ // nothing to do
+ }
+
+ @Override
+ public void clearBlocked() {
+ // nothing to do
+ }
+ }
}
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/IReplEngine.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/IReplEngine.java
index f6e16e5..9feb8e8 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/IReplEngine.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/IReplEngine.java
@@ -17,14 +17,6 @@
public interface IReplEngine extends IScriptEngine {
/**
- * Returns the execution state of the engine. If the engine is processing code or is terminated this will return <code>false</code>. If the engine is
- * waiting for further scripts to execute this will return <code>true</code>.
- *
- * @return execution state.
- */
- boolean isIdle();
-
- /**
* Set a marker that the interpreter should terminate instead entering IDLE mode. If set, the interpreter will execute all pending requests and terminate
* afterwards.
*
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/IScriptEngine.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/IScriptEngine.java
index 83a6c08..7119c43 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/IScriptEngine.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/IScriptEngine.java
@@ -200,14 +200,22 @@
void registerJar(final URL url);
/**
- * Join engine execution thread. Waits for engine execution up to <i>timeout</i>
+ * Join engine execution thread. Waits for engine execution up to <i>timeout</i> milliseconds.
*
* @param timeout
* command timeout in milliseconds
* @throws InterruptedException
* when join command got interrupted
*/
- void join(long timeout) throws InterruptedException;
+ void joinEngine(long timeout) throws InterruptedException;
+
+ /**
+ * Join engine execution thread. Waits for engine termination.
+ *
+ * @throws InterruptedException
+ * when join command got interrupted
+ */
+ void joinEngine() throws InterruptedException;
/**
* Verify that engine was started and terminated.
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/ScriptExecutionException.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/ScriptExecutionException.java
index 4a7961f..66b925b 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/ScriptExecutionException.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/ScriptExecutionException.java
@@ -44,6 +44,15 @@
fErrorName = null;
}
+ public ScriptExecutionException(String message) {
+ super(message);
+
+ fLineSource = null;
+ fColumnNumber = 0;
+ fScriptStackTrace = null;
+ fErrorName = null;
+ }
+
/**
* Instantiate wrapper exception.
*
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/EnvironmentModule.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/EnvironmentModule.java
index da59ae0..a55add1 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/EnvironmentModule.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/modules/EnvironmentModule.java
@@ -555,8 +555,21 @@
fModuleCallbacks.add(callbackProvider);
}
- // needed by dynamic script code
+ //
+ /**
+ * Check if java callbacks are registered for a module method. This method get called on each module function invokation.
+ * <p>
+ * ATTENTION: needed by dynamic script code, do not alter synopsis!
+ * </p>
+ *
+ * @param methodToken
+ * unique method token
+ * @return <code>true</code> when callbacks are registered
+ */
public boolean hasMethodCallback(String methodToken) {
+ if (getScriptEngine() instanceof AbstractScriptEngine)
+ ((AbstractScriptEngine) getScriptEngine()).checkForCancellation();
+
final Method method = fRegisteredMethods.get(methodToken);
for (final IModuleCallbackProvider callbackProvider : fModuleCallbacks) {
diff --git a/tests/org.eclipse.ease.test/src/org/eclipse/ease/AbstractReplScriptEngineTest.java b/tests/org.eclipse.ease.test/src/org/eclipse/ease/AbstractReplScriptEngineTest.java
index 3ebd914..29e2b0d 100644
--- a/tests/org.eclipse.ease.test/src/org/eclipse/ease/AbstractReplScriptEngineTest.java
+++ b/tests/org.eclipse.ease.test/src/org/eclipse/ease/AbstractReplScriptEngineTest.java
@@ -1,9 +1,6 @@
package org.eclipse.ease;
-import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.net.URL;
@@ -16,173 +13,109 @@
import org.mockito.runners.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
-public class AbstractReplScriptEngineTest {
+public class AbstractReplScriptEngineTest extends AbstractScriptEngineTest {
private static final String SAMPLE_CODE = "Hello world";
- private AbstractReplScriptEngine fTestEngine;
+ private class MockedScriptEngine extends AbstractReplScriptEngine {
+ public MockedScriptEngine() {
+ super("Mocked Engine");
+ }
+
+ @Override
+ public void terminateCurrent() {
+ }
+
+ @Override
+ public void registerJar(URL url) {
+ }
+
+ @Override
+ protected Object internalGetVariable(String name) {
+ return null;
+ }
+
+ @Override
+ protected Map<String, Object> internalGetVariables() {
+ return null;
+ }
+
+ @Override
+ protected boolean internalHasVariable(String name) {
+ return false;
+ }
+
+ @Override
+ protected void internalSetVariable(String name, Object content) {
+ }
+
+ @Override
+ protected void setupEngine() throws ScriptEngineException {
+ }
+
+ @Override
+ protected Object execute(Script script, Object reference, String fileName, boolean uiThread) throws Throwable {
+ final String input = script.getCommand().toString();
+ if (input.contains(ERROR_MARKER))
+ throw new RuntimeException(input);
+ else
+ getOutputStream().write(input.getBytes());
+
+ return input;
+ }
+ }
+
+ @Override
@Before
public void setup() {
- fTestEngine = new AbstractReplScriptEngine("Test engine") {
-
- @Override
- public void terminateCurrent() {
- }
-
- @Override
- public void registerJar(final URL url) {
- }
-
- @Override
- protected void teardownEngine() throws ScriptEngineException {
- }
-
- @Override
- protected void setupEngine() throws ScriptEngineException {
- }
-
- @Override
- protected void internalSetVariable(final String name, final Object content) {
- }
-
- @Override
- protected boolean internalHasVariable(final String name) {
- return false;
- }
-
- @Override
- protected Map<String, Object> internalGetVariables() {
- return null;
- }
-
- @Override
- protected Object internalGetVariable(final String name) {
- return null;
- }
-
- @Override
- protected Object execute(final Script script, final Object reference, final String fileName, final boolean uiThread) throws Exception {
- return script.getCommand();
- }
- };
+ fTestEngine = new MockedScriptEngine();
}
- @Test
- public void isJob() {
- assertTrue(fTestEngine instanceof Job);
+ private AbstractReplScriptEngine getTestEngine() {
+ return (AbstractReplScriptEngine) fTestEngine;
}
- @Test(timeout = 1000)
- public void executeAsync() {
-
- final ScriptResult result = fTestEngine.executeAsync(SAMPLE_CODE);
- assertFalse(result.isReady());
-
- fTestEngine.schedule();
-
- synchronized (result) {
- try {
- while (!result.isReady())
- result.wait();
- } catch (final InterruptedException e) {
- }
- }
- assertTrue(result.isReady());
- }
-
- @Test(timeout = 1000)
- public void executeSync() throws InterruptedException {
-
- fTestEngine.setTerminateOnIdle(false);
- fTestEngine.schedule();
-
- final ScriptResult result = fTestEngine.executeSync(SAMPLE_CODE);
- assertTrue(result.isReady());
-
- fTestEngine.terminate();
- }
-
- @Test(timeout = 1000)
- public void inject() throws InterruptedException {
- assertEquals(SAMPLE_CODE, fTestEngine.inject(SAMPLE_CODE));
- }
-
- @Test
- public void streamsAvailable() {
- assertNotNull(fTestEngine.getOutputStream());
- assertNotNull(fTestEngine.getErrorStream());
- assertNotNull(fTestEngine.getInputStream());
- }
-
- @Test(timeout = 1000)
- public void terminateOnIdle() throws InterruptedException {
- fTestEngine.setTerminateOnIdle(true);
- fTestEngine.schedule();
- fTestEngine.join();
-
- // test valid if it terminates within the timeout period
- }
-
- @Test(timeout = 1000)
+ @Test(timeout = TEST_TIMEOUT)
public void terminateOnIdleAfterSchedule() throws InterruptedException {
- fTestEngine.setTerminateOnIdle(false);
- fTestEngine.schedule();
+ getTestEngine().setTerminateOnIdle(false);
+ getTestEngine().schedule();
while (true) {
Thread.sleep(10);
- if (fTestEngine.getState() != Job.RUNNING) {
+ if (getTestEngine().getState() != Job.RUNNING) {
// eclipse job has not started yet
continue;
}
- if (!fTestEngine.isIdle()) {
- // ease still has work to do in the job
- continue;
- }
- if (fTestEngine.getThread().getState() != Thread.State.WAITING) {
+ if (getTestEngine().getThread().getState() != Thread.State.WAITING) {
// thread is still running, we want it to be waiting
continue;
}
break;
}
- fTestEngine.setTerminateOnIdle(true);
- fTestEngine.join();
+ getTestEngine().setTerminateOnIdle(true);
+ getTestEngine().joinEngine();
// test valid if it terminates within the timeout period
}
- @Test
+ @Test(timeout = TEST_TIMEOUT)
public void keepRunningOnIdle() throws InterruptedException {
- fTestEngine.setTerminateOnIdle(false);
- fTestEngine.executeAsync(SAMPLE_CODE);
- fTestEngine.schedule();
+ getTestEngine().setTerminateOnIdle(false);
+ getTestEngine().executeAsync(SAMPLE_CODE);
+ getTestEngine().schedule();
- final ScriptResult result = fTestEngine.executeSync(SAMPLE_CODE);
+ final ScriptResult result = getTestEngine().executeSync(SAMPLE_CODE);
assertTrue(result.isReady());
}
- @Test
+ @Test(timeout = TEST_TIMEOUT)
public void terminateEngine() throws InterruptedException {
- fTestEngine.setTerminateOnIdle(false);
- fTestEngine.schedule();
+ getTestEngine().setTerminateOnIdle(false);
+ getTestEngine().schedule();
- fTestEngine.terminate();
- fTestEngine.join(5000);
+ getTestEngine().terminate();
+ getTestEngine().joinEngine();
- assertEquals(Job.NONE, fTestEngine.getState());
- }
-
- @Test
- public void extractEmptyArguments() {
- assertEquals(0, AbstractScriptEngine.extractArguments(null).length);
- assertEquals(0, AbstractScriptEngine.extractArguments("").length);
- assertEquals(0, AbstractScriptEngine.extractArguments(" ").length);
- assertEquals(0, AbstractScriptEngine.extractArguments("\t\t").length);
- }
-
- @Test
- public void extractArguments() {
- assertArrayEquals(new String[] { "one" }, AbstractScriptEngine.extractArguments("one"));
- assertArrayEquals(new String[] { "one with spaces" }, AbstractScriptEngine.extractArguments("one with spaces"));
- assertArrayEquals(new String[] { "one", "and", "another" }, AbstractScriptEngine.extractArguments("one,and, another"));
+ assertEquals(Job.NONE, getTestEngine().getState());
}
}
diff --git a/tests/org.eclipse.ease.test/src/org/eclipse/ease/AbstractScriptEngineTest.java b/tests/org.eclipse.ease.test/src/org/eclipse/ease/AbstractScriptEngineTest.java
index 2e43bb6..40ec096 100644
--- a/tests/org.eclipse.ease.test/src/org/eclipse/ease/AbstractScriptEngineTest.java
+++ b/tests/org.eclipse.ease.test/src/org/eclipse/ease/AbstractScriptEngineTest.java
@@ -1,57 +1,358 @@
package org.eclipse.ease;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
-import static org.mockito.Mockito.mock;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.net.URL;
import java.util.Arrays;
+import java.util.Map;
+import org.eclipse.core.runtime.jobs.Job;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-import org.mockito.Mockito;
public class AbstractScriptEngineTest {
+ protected static final int TEST_TIMEOUT = 3000;
- private AbstractScriptEngine fAbstractScriptEngineTest;
+ protected static final String VALID_SAMPLE_CODE = "1";
+
+ protected static final CharSequence ERROR_MARKER = "ERROR";
+
+ private class MockedScriptEngine extends AbstractScriptEngine {
+
+ public MockedScriptEngine() {
+ super("Mocked Engine");
+ }
+
+ @Override
+ public void terminateCurrent() {
+ }
+
+ @Override
+ public void registerJar(URL url) {
+ }
+
+ @Override
+ protected Object internalGetVariable(String name) {
+ return null;
+ }
+
+ @Override
+ protected Map<String, Object> internalGetVariables() {
+ return null;
+ }
+
+ @Override
+ protected boolean internalHasVariable(String name) {
+ return false;
+ }
+
+ @Override
+ protected void internalSetVariable(String name, Object content) {
+ }
+
+ @Override
+ protected void setupEngine() throws ScriptEngineException {
+ }
+
+ @Override
+ protected void teardownEngine() throws ScriptEngineException {
+ }
+
+ @Override
+ protected Object execute(Script script, Object reference, String fileName, boolean uiThread) throws Throwable {
+ final String input = script.getCommand().toString();
+ if (input.contains(ERROR_MARKER))
+ throw new RuntimeException(input);
+ else
+ getOutputStream().write(input.getBytes());
+
+ return input;
+ }
+ }
+
+ protected AbstractScriptEngine fTestEngine;
@Before
public void setup() {
- fAbstractScriptEngineTest = mock(AbstractScriptEngine.class, Mockito.CALLS_REAL_METHODS);
+ fTestEngine = new MockedScriptEngine();
+ }
+
+ @After
+ public void teardown() throws InterruptedException {
+ if (fTestEngine.getState() != Job.NONE) {
+ fTestEngine.terminate();
+ fTestEngine.joinEngine();
+ }
+ }
+
+ @Test
+ public void streamsAvailable() {
+ assertNotNull(fTestEngine.getOutputStream());
+ assertNotNull(fTestEngine.getErrorStream());
+ assertNotNull(fTestEngine.getInputStream());
}
@Test
public void setNotNullOutputStream() throws IOException {
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
- fAbstractScriptEngineTest.setOutputStream(bos);
+ fTestEngine.setOutputStream(bos);
- assertNotNull(fAbstractScriptEngineTest.getOutputStream());
- fAbstractScriptEngineTest.getOutputStream().print("test");
+ assertNotNull(fTestEngine.getOutputStream());
+ fTestEngine.getOutputStream().print("test");
Arrays.equals("test".getBytes(), bos.toByteArray());
}
@Test
public void setNullOutputStream() throws IOException {
- fAbstractScriptEngineTest.setOutputStream(null);
+ fTestEngine.setOutputStream(null);
- assertEquals(System.out, fAbstractScriptEngineTest.getOutputStream());
+ assertEquals(System.out, fTestEngine.getOutputStream());
}
@Test
public void setNotNullErrorStream() throws IOException {
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
- assertNotNull(fAbstractScriptEngineTest.getErrorStream());
- fAbstractScriptEngineTest.getErrorStream().print("test");
+ assertNotNull(fTestEngine.getErrorStream());
+ fTestEngine.getErrorStream().print("test");
Arrays.equals("test".getBytes(), bos.toByteArray());
}
@Test
public void setNullErrorStream() {
- fAbstractScriptEngineTest.setErrorStream(null);
+ fTestEngine.setErrorStream(null);
- assertEquals(System.err, fAbstractScriptEngineTest.getErrorStream());
+ assertEquals(System.err, fTestEngine.getErrorStream());
+ }
+
+ @Test
+ public void isJob() {
+ assertTrue(fTestEngine instanceof Job);
+ }
+
+ @Test
+ public void executeValidCodeAndTerminate() throws InterruptedException {
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ fTestEngine.setOutputStream(bos);
+
+ final ScriptResult result1 = fTestEngine.executeAsync(VALID_SAMPLE_CODE);
+ final ScriptResult result2 = fTestEngine.executeAsync("2");
+ fTestEngine.schedule();
+
+ fTestEngine.joinEngine();
+
+ assertTrue(fTestEngine.isFinished());
+ assertEquals("12", bos.toString());
+
+ assertTrue(result1.isReady());
+ assertEquals(VALID_SAMPLE_CODE, result1.getResult());
+ assertFalse(result1.hasException());
+ assertNull(result1.getException());
+
+ assertTrue(result2.isReady());
+ assertEquals("2", result2.getResult());
+ assertFalse(result2.hasException());
+ assertNull(result2.getException());
+ }
+
+ @Test
+ public void executeErrorCodeAndTerminate() throws InterruptedException {
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ fTestEngine.setOutputStream(bos);
+
+ final ScriptResult result1 = fTestEngine.executeAsync(VALID_SAMPLE_CODE);
+ final ScriptResult result2 = fTestEngine.executeAsync("ERROR");
+ fTestEngine.schedule();
+
+ fTestEngine.joinEngine();
+
+ assertTrue(fTestEngine.isFinished());
+ assertEquals(VALID_SAMPLE_CODE, bos.toString());
+
+ assertTrue(result1.isReady());
+ assertEquals(VALID_SAMPLE_CODE, result1.getResult());
+ assertFalse(result1.hasException());
+ assertNull(result1.getException());
+
+ assertTrue(result2.isReady());
+ assertNull(result2.getResult());
+ assertTrue(result2.hasException());
+ assertEquals(RuntimeException.class, result2.getException().getClass());
+ }
+
+ @Test
+ public void executeSync() throws InterruptedException {
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ fTestEngine.setOutputStream(bos);
+
+ final ScriptResult result1 = fTestEngine.executeSync(VALID_SAMPLE_CODE);
+
+ fTestEngine.joinEngine();
+
+ assertTrue(fTestEngine.isFinished());
+ assertEquals(VALID_SAMPLE_CODE, bos.toString());
+
+ assertTrue(result1.isReady());
+ assertEquals(VALID_SAMPLE_CODE, result1.getResult());
+ assertFalse(result1.hasException());
+ assertNull(result1.getException());
+ }
+
+ @Test(timeout = TEST_TIMEOUT)
+ public void inject() throws InterruptedException {
+ assertEquals(VALID_SAMPLE_CODE, fTestEngine.inject(VALID_SAMPLE_CODE));
+ }
+
+ @Test(timeout = TEST_TIMEOUT)
+ public void engineTerminatesWhenIdle() throws InterruptedException {
+ fTestEngine.schedule();
+ fTestEngine.joinEngine();
+ }
+
+ @Test(timeout = TEST_TIMEOUT)
+ public void terminateViaTerminateMethod() throws InterruptedException {
+ final MockedScriptEngine engine = new MockedScriptEngine() {
+ @Override
+ protected Object execute(Script script, Object reference, String fileName, boolean uiThread) throws Throwable {
+ Thread.sleep(100);
+ return super.execute(script, reference, fileName, uiThread);
+ }
+ };
+
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ engine.setOutputStream(bos);
+
+ ScriptResult scriptResult = null;
+ for (int loop = 0; loop <= 100; loop++)
+ scriptResult = engine.executeAsync("Loop " + loop + "\n");
+
+ engine.schedule();
+
+ // wait for engine to produce output
+ while (bos.toString().isEmpty())
+ Thread.yield();
+
+ engine.terminate();
+ engine.joinEngine();
+
+ assertFalse(bos.toString().contains("Loop 100"));
+
+ assertTrue(scriptResult.isReady());
+ assertNull(scriptResult.getResult());
+ assertTrue(scriptResult.hasException());
+ assertEquals(ScriptExecutionException.class, scriptResult.getException().getClass());
+ }
+
+ @Test(timeout = TEST_TIMEOUT)
+ public void terminateViaMonitorCancellation() throws InterruptedException {
+ final MockedScriptEngine engine = new MockedScriptEngine() {
+ @Override
+ protected Object execute(Script script, Object reference, String fileName, boolean uiThread) throws Throwable {
+ Thread.sleep(100);
+ return super.execute(script, reference, fileName, uiThread);
+ }
+ };
+
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ engine.setOutputStream(bos);
+
+ ScriptResult scriptResult = null;
+ for (int loop = 0; loop <= 100; loop++)
+ scriptResult = engine.executeAsync("Loop " + loop + "\n");
+
+ engine.schedule();
+
+ // wait for engine to produce output
+ while (bos.toString().isEmpty())
+ Thread.yield();
+
+ engine.getMonitor().setCanceled(true);
+ engine.joinEngine();
+
+ assertFalse(bos.toString().contains("Loop 100"));
+
+ assertTrue("result " + scriptResult.hashCode() + " is not ready", scriptResult.isReady());
+ assertNull(scriptResult.getResult());
+ assertTrue(scriptResult.hasException());
+ assertEquals(ScriptExecutionException.class, scriptResult.getException().getClass());
+ }
+
+ @Test(timeout = TEST_TIMEOUT)
+ public void terminateViaMethodCallback() throws InterruptedException {
+ final MockedScriptEngine engine = new MockedScriptEngine() {
+ @Override
+ protected Object execute(Script script, Object reference, String fileName, boolean uiThread) throws Throwable {
+ getMonitor().setCanceled(true);
+ checkForCancellation();
+ return super.execute(script, reference, fileName, uiThread);
+ }
+ };
+
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ engine.setOutputStream(bos);
+
+ final ScriptResult scriptResult = engine.executeAsync(VALID_SAMPLE_CODE);
+
+ engine.schedule();
+ engine.joinEngine();
+
+ assertTrue(bos.toString().isEmpty());
+
+ assertTrue(scriptResult.isReady());
+ assertNull(scriptResult.getResult());
+ assertTrue(scriptResult.hasException());
+ assertEquals(ScriptExecutionException.class, scriptResult.getException().getClass());
+ }
+
+ @Test(timeout = TEST_TIMEOUT)
+ public void terminateMultipleTimes() {
+ final MockedScriptEngine engine = new MockedScriptEngine() {
+ @Override
+ protected Object execute(Script script, Object reference, String fileName, boolean uiThread) throws Throwable {
+ Thread.sleep(300);
+ return super.execute(script, reference, fileName, uiThread);
+ }
+ };
+
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ engine.setOutputStream(bos);
+
+ for (int loop = 0; loop < 10; loop++)
+ engine.executeAsync("Loop " + loop + "\n");
+
+ engine.schedule();
+
+ // wait for engine to produce output
+ while (bos.toString().isEmpty())
+ Thread.yield();
+
+ while (engine.getState() != Job.NONE)
+ engine.terminate();
+
+ // this test is pass when it does not throw an Exception
+ }
+
+ @Test
+ public void extractEmptyArguments() {
+ assertEquals(0, AbstractScriptEngine.extractArguments(null).length);
+ assertEquals(0, AbstractScriptEngine.extractArguments("").length);
+ assertEquals(0, AbstractScriptEngine.extractArguments(" ").length);
+ assertEquals(0, AbstractScriptEngine.extractArguments("\t\t").length);
+ }
+
+ @Test
+ public void extractArguments() {
+ assertArrayEquals(new String[] { "one" }, AbstractScriptEngine.extractArguments("one"));
+ assertArrayEquals(new String[] { "one with spaces" }, AbstractScriptEngine.extractArguments("one with spaces"));
+ assertArrayEquals(new String[] { "one", "and", "another" }, AbstractScriptEngine.extractArguments("one,and, another"));
}
}