Bug 553609: [Debugger] stepping commands fail for include() statements

Change-Id: I4f7ce623f350ba4d6d6c709d66ce3ba04f39116d
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/AbstractEaseDebugger.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/AbstractEaseDebugger.java
index f01693c..a9dcb6f 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/AbstractEaseDebugger.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/AbstractEaseDebugger.java
@@ -17,6 +17,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 
 import org.eclipse.core.resources.IMarker;
 import org.eclipse.core.runtime.CoreException;
@@ -44,6 +45,7 @@
 import org.eclipse.ease.debugging.events.debugger.SuspendedEvent;
 import org.eclipse.ease.debugging.events.debugger.VariablesEvent;
 import org.eclipse.ease.debugging.events.model.BreakpointRequest;
+import org.eclipse.ease.debugging.events.model.DisconnectRequest;
 import org.eclipse.ease.debugging.events.model.EvaluateExpressionRequest;
 import org.eclipse.ease.debugging.events.model.GetStackFramesRequest;
 import org.eclipse.ease.debugging.events.model.GetVariablesRequest;
@@ -185,6 +187,19 @@
 					}
 				}
 
+			} else if (event instanceof DisconnectRequest) {
+				fBreakpoints.clear();
+
+				fDispatcher = null;
+
+				for (final Entry<Object, ThreadState> entry : fThreadStates.entrySet()) {
+					resume(DebugEvent.CLIENT_REQUEST, entry.getKey());
+					final ThreadState threadState = entry.getValue();
+					synchronized (threadState) {
+						threadState.notify();
+					}
+				}
+
 			} else if (event instanceof AbstractEvent) {
 				fEvaluationRequests.add((AbstractEvent) event);
 				final ThreadState threadState = getThreadState(((AbstractEvent) event).getThread());
@@ -206,7 +221,6 @@
 	 *            Breakpoint information.
 	 */
 	protected void breakpointAdded(final Script script, final IBreakpoint breakpoint) {
-
 	}
 
 	/**
@@ -220,7 +234,6 @@
 	 *            Breakpoint information.
 	 */
 	protected void breakpointRemoved(final Script script, final IBreakpoint breakpoint) {
-
 	}
 
 	protected void suspend(final IDebuggerEvent event) {
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/events/model/DisconnectRequest.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/events/model/DisconnectRequest.java
new file mode 100644
index 0000000..0cb8258
--- /dev/null
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/events/model/DisconnectRequest.java
@@ -0,0 +1,16 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Christian Pontesegger and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * Contributors:
+ *     Christian Pontesegger - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ease.debugging.events.model;
+
+import org.eclipse.ease.debugging.events.AbstractEvent;
+
+public class DisconnectRequest extends AbstractEvent implements IModelRequest {
+}
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/events/model/ResumeRequest.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/events/model/ResumeRequest.java
index 015908d..9e882ee 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/events/model/ResumeRequest.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/events/model/ResumeRequest.java
@@ -36,7 +36,7 @@
 			return "BREAKPOINT";
 
 		default:
-			return "<unknown>";
+			return "<unknown>:" + type;
 		}
 	}
 
@@ -61,6 +61,7 @@
 		return fType;
 	}
 
+	@Override
 	public Object getThread() {
 		return fThread;
 	}
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugProcess.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugProcess.java
index f508abd..60754d0 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugProcess.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugProcess.java
@@ -29,8 +29,7 @@
 import org.eclipse.ease.debugging.events.debugger.ThreadCreatedEvent;
 import org.eclipse.ease.debugging.events.debugger.ThreadTerminatedEvent;
 import org.eclipse.ease.debugging.events.model.BreakpointRequest;
-import org.eclipse.ease.debugging.events.model.BreakpointRequest.Mode;
-import org.eclipse.ease.debugging.events.model.ResumeRequest;
+import org.eclipse.ease.debugging.events.model.DisconnectRequest;
 import org.eclipse.ease.debugging.events.model.TerminateRequest;
 
 public class EaseDebugProcess extends EaseDebugElement implements IProcess, IEventProcessor {
@@ -103,7 +102,7 @@
 
 			else {
 				if (!debugThread.getState().equals(State.STEPPING)) {
-					debugThread.resume();
+					debugThread.resume(DebugEvent.UNSPECIFIED);
 				}
 			}
 
@@ -196,17 +195,12 @@
 
 	@Override
 	public synchronized void disconnect() {
-		// remove all breakpoints
-		getDebugTarget().fireDispatchEvent(new BreakpointRequest(Mode.REMOVE));
-
-		// resume interpreter
-		for (final EaseDebugThread thread : getThreads()) {
-			if (thread.isSuspended())
-				getDebugTarget().fireDispatchEvent(new ResumeRequest(DebugEvent.CLIENT_REQUEST, thread.getThread()));
-		}
+		getDebugTarget().fireDispatchEvent(new DisconnectRequest());
 
 		// cleanup removes dispatcher instance. Needs to be called after all events have been sent
 		getDebugTarget().cleanupOnTermination();
+
+		setTerminated();
 	}
 
 	@Override
diff --git a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugThread.java b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugThread.java
index 5fedf1c..566986b 100644
--- a/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugThread.java
+++ b/plugins/org.eclipse.ease/src/org/eclipse/ease/debugging/model/EaseDebugThread.java
@@ -219,8 +219,11 @@
 
 	@Override
 	public void resume() {
-		getDebugTarget().fireDispatchEvent(new ResumeRequest(DebugEvent.CLIENT_REQUEST, getThread()));
+		resume(DebugEvent.CLIENT_REQUEST);
+	}
 
+	public void resume(int type) {
+		getDebugTarget().fireDispatchEvent(new ResumeRequest(type, getThread()));
 	}
 
 	@Override
diff --git a/tests/org.eclipse.ease.lang.javascript.rhino.test/src/org/eclipse/ease/lang/javascript/rhino/RhinoDebugTest.java b/tests/org.eclipse.ease.lang.javascript.rhino.test/src/org/eclipse/ease/lang/javascript/rhino/RhinoDebugTest.java
index 195cb06..1c28e0c 100644
--- a/tests/org.eclipse.ease.lang.javascript.rhino.test/src/org/eclipse/ease/lang/javascript/rhino/RhinoDebugTest.java
+++ b/tests/org.eclipse.ease.lang.javascript.rhino.test/src/org/eclipse/ease/lang/javascript/rhino/RhinoDebugTest.java
@@ -12,52 +12,36 @@
 package org.eclipse.ease.lang.javascript.rhino;
 
 import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
 
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IMarker;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.debug.core.DebugPlugin;
-import org.eclipse.debug.core.model.IBreakpoint;
-import org.eclipse.debug.core.model.LineBreakpoint;
 import org.eclipse.ease.lang.javascript.rhino.debugger.RhinoDebuggerEngine;
 import org.eclipse.ease.testhelper.AbstractDebugTest;
 
 public class RhinoDebugTest extends AbstractDebugTest {
 
 	@Override
-	protected String getScriptSource() throws IOException {
-		return readResource("org.eclipse.ease.testhelper", "/resources/DebugTest/main.js");
-	}
+	protected Map<String, String> getScriptSources() throws IOException {
+		final Map<String, String> sources = new HashMap<>();
 
-	@Override
-	protected LineBreakpoint setBreakpoint(IFile file, int lineNumber) throws CoreException {
-		final IMarker marker = file.createMarker("org.eclipse.wst.jsdt.debug.core.line.breakpoint.marker");
-		marker.setAttribute("org.eclipse.debug.core.enabled", true);
-		marker.setAttribute("org.eclipse.debug.core.id", "org.eclipse.wst.jsdt.debug.model");
-		marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
+		sources.put(MAIN_SCRIPT, readResource("org.eclipse.ease.testhelper", "/resources/DebugTest/main.js"));
+		sources.put(INCLUDE_SCRIPT, readResource("org.eclipse.ease.testhelper", "/resources/DebugTest/include.js"));
 
-		final LineBreakpoint breakpoint = new LineBreakpoint() {
-
-			@Override
-			public String getModelIdentifier() {
-				return "org.eclipse.wst.jsdt.debug.model";
-			}
-		};
-
-		breakpoint.setMarker(marker);
-
-		DebugPlugin.getDefault().getBreakpointManager().addBreakpoint(breakpoint);
-
-		return breakpoint;
-	}
-
-	@Override
-	protected IBreakpoint[] getBreakpoints() throws CoreException {
-		return DebugPlugin.getDefault().getBreakpointManager().getBreakpoints("org.eclipse.wst.jsdt.debug.model");
+		return sources;
 	}
 
 	@Override
 	protected String getEngineId() {
 		return RhinoDebuggerEngine.ENGINE_ID;
 	}
+
+	@Override
+	protected String getDebugModelId() {
+		return "org.eclipse.wst.jsdt.debug.model";
+	}
+
+	@Override
+	protected String getBreakpointId() {
+		return "org.eclipse.wst.jsdt.debug.core.line.breakpoint.marker";
+	}
 }
diff --git a/tests/org.eclipse.ease.lang.python.jython.test/src/org/eclipse/ease/lang/python/jython/debugger/JythonDebugTest.java b/tests/org.eclipse.ease.lang.python.jython.test/src/org/eclipse/ease/lang/python/jython/debugger/JythonDebugTest.java
index 1496e20..2e2fe85 100644
--- a/tests/org.eclipse.ease.lang.python.jython.test/src/org/eclipse/ease/lang/python/jython/debugger/JythonDebugTest.java
+++ b/tests/org.eclipse.ease.lang.python.jython.test/src/org/eclipse/ease/lang/python/jython/debugger/JythonDebugTest.java
@@ -12,51 +12,35 @@
 package org.eclipse.ease.lang.python.jython.debugger;
 
 import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
 
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IMarker;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.debug.core.DebugPlugin;
-import org.eclipse.debug.core.model.IBreakpoint;
-import org.eclipse.debug.core.model.LineBreakpoint;
 import org.eclipse.ease.testhelper.AbstractDebugTest;
 
 public class JythonDebugTest extends AbstractDebugTest {
 
 	@Override
-	protected String getScriptSource() throws IOException {
-		return readResource("org.eclipse.ease.testhelper", "/resources/DebugTest/main.py");
-	}
+	protected Map<String, String> getScriptSources() throws IOException {
+		final Map<String, String> sources = new HashMap<>();
 
-	@Override
-	protected LineBreakpoint setBreakpoint(IFile file, int lineNumber) throws CoreException {
-		final IMarker marker = file.createMarker("org.python.pydev.debug.pyStopBreakpointMarker");
-		marker.setAttribute("org.eclipse.debug.core.enabled", true);
-		marker.setAttribute("org.eclipse.debug.core.id", "org.python.pydev.debug");
-		marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
+		sources.put(MAIN_SCRIPT, readResource("org.eclipse.ease.testhelper", "/resources/DebugTest/main.py"));
+		sources.put(INCLUDE_SCRIPT, readResource("org.eclipse.ease.testhelper", "/resources/DebugTest/include.py"));
 
-		final LineBreakpoint breakpoint = new LineBreakpoint() {
-
-			@Override
-			public String getModelIdentifier() {
-				return "org.python.pydev.debug";
-			}
-		};
-
-		breakpoint.setMarker(marker);
-
-		DebugPlugin.getDefault().getBreakpointManager().addBreakpoint(breakpoint);
-
-		return breakpoint;
-	}
-
-	@Override
-	protected IBreakpoint[] getBreakpoints() throws CoreException {
-		return DebugPlugin.getDefault().getBreakpointManager().getBreakpoints("org.python.pydev.debug");
+		return sources;
 	}
 
 	@Override
 	protected String getEngineId() {
 		return JythonDebuggerEngine.ENGINE_ID;
 	}
+
+	@Override
+	protected String getDebugModelId() {
+		return "org.python.pydev.debug";
+	}
+
+	@Override
+	protected String getBreakpointId() {
+		return "org.python.pydev.debug.pyStopBreakpointMarker";
+	}
 }
diff --git a/tests/org.eclipse.ease.lang.python.py4j.test/src/org/eclipse/ease/lang/python/py4j/Py4jDebugTest.java b/tests/org.eclipse.ease.lang.python.py4j.test/src/org/eclipse/ease/lang/python/py4j/Py4jDebugTest.java
index 357b7e1..67e943b 100644
--- a/tests/org.eclipse.ease.lang.python.py4j.test/src/org/eclipse/ease/lang/python/py4j/Py4jDebugTest.java
+++ b/tests/org.eclipse.ease.lang.python.py4j.test/src/org/eclipse/ease/lang/python/py4j/Py4jDebugTest.java
@@ -12,13 +12,10 @@
 package org.eclipse.ease.lang.python.py4j;
 
 import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
 
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IMarker;
 import org.eclipse.core.runtime.CoreException;
-import org.eclipse.debug.core.DebugPlugin;
-import org.eclipse.debug.core.model.IBreakpoint;
-import org.eclipse.debug.core.model.LineBreakpoint;
 import org.eclipse.ease.lang.python.py4j.internal.Py4jDebuggerEngine;
 import org.eclipse.ease.testhelper.AbstractDebugTest;
 import org.junit.Ignore;
@@ -27,40 +24,28 @@
 public class Py4jDebugTest extends AbstractDebugTest {
 
 	@Override
+	protected Map<String, String> getScriptSources() throws IOException {
+		final Map<String, String> sources = new HashMap<>();
+
+		sources.put(MAIN_SCRIPT, readResource("org.eclipse.ease.testhelper", "/resources/DebugTest/main.py"));
+		sources.put(INCLUDE_SCRIPT, readResource("org.eclipse.ease.testhelper", "/resources/DebugTest/include.py"));
+
+		return sources;
+	}
+
+	@Override
 	protected String getEngineId() {
 		return Py4jDebuggerEngine.ENGINE_ID;
 	}
 
 	@Override
-	protected String getScriptSource() throws IOException {
-		return readResource("org.eclipse.ease.testhelper", "/resources/DebugTest/main.py");
+	protected String getDebugModelId() {
+		return "org.python.pydev.debug";
 	}
 
 	@Override
-	protected LineBreakpoint setBreakpoint(IFile file, int lineNumber) throws CoreException {
-		final IMarker marker = file.createMarker("org.python.pydev.debug.pyStopBreakpointMarker");
-		marker.setAttribute("org.eclipse.debug.core.enabled", true);
-		marker.setAttribute("org.eclipse.debug.core.id", "org.python.pydev.debug");
-		marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
-
-		final LineBreakpoint breakpoint = new LineBreakpoint() {
-
-			@Override
-			public String getModelIdentifier() {
-				return "org.python.pydev.debug";
-			}
-		};
-
-		breakpoint.setMarker(marker);
-
-		DebugPlugin.getDefault().getBreakpointManager().addBreakpoint(breakpoint);
-
-		return breakpoint;
-	}
-
-	@Override
-	protected IBreakpoint[] getBreakpoints() throws CoreException {
-		return DebugPlugin.getDefault().getBreakpointManager().getBreakpoints("org.python.pydev.debug");
+	protected String getBreakpointId() {
+		return "org.python.pydev.debug.pyStopBreakpointMarker";
 	}
 
 	@Override
@@ -76,4 +61,11 @@
 	public void nativeArrayVariable() throws CoreException, IOException {
 		// TODO: Currently not possible to access native Python variables
 	}
+
+	@Override
+	@Test
+	@Ignore
+	public void stepIntoIncludeCommand() throws CoreException {
+		// TODO see https://bugs.eclipse.org/bugs/show_bug.cgi?id=553619
+	}
 }
diff --git a/tests/org.eclipse.ease.testhelper/resources/DebugTest/include.js b/tests/org.eclipse.ease.testhelper/resources/DebugTest/include.js
new file mode 100644
index 0000000..1ac445f
--- /dev/null
+++ b/tests/org.eclipse.ease.testhelper/resources/DebugTest/include.js
@@ -0,0 +1,7 @@
+print("include file start");
+
+function includedFoo() {
+	print("include function called");
+}
+
+print("Include file processed");
\ No newline at end of file
diff --git a/tests/org.eclipse.ease.testhelper/resources/DebugTest/include.py b/tests/org.eclipse.ease.testhelper/resources/DebugTest/include.py
new file mode 100644
index 0000000..c990ab3
--- /dev/null
+++ b/tests/org.eclipse.ease.testhelper/resources/DebugTest/include.py
@@ -0,0 +1,6 @@
+print("include file start")
+
+def includedFoo():
+    print("include function called")
+
+print("include file processed")
diff --git a/tests/org.eclipse.ease.testhelper/resources/DebugTest/main.js b/tests/org.eclipse.ease.testhelper/resources/DebugTest/main.js
index 7b53726..6be7aac 100644
--- a/tests/org.eclipse.ease.testhelper/resources/DebugTest/main.js
+++ b/tests/org.eclipse.ease.testhelper/resources/DebugTest/main.js
@@ -12,6 +12,8 @@
 	return a + b;	// testMethod-result-hook
 }
 
+include("include.js"); // include-command-hook
+print("line after include file");
 
 var result = testMethod(2, 3);	// testMethod-call-hook
 print("Result of testMethod = " + result);
diff --git a/tests/org.eclipse.ease.testhelper/resources/DebugTest/main.py b/tests/org.eclipse.ease.testhelper/resources/DebugTest/main.py
index 1c51847..839de61 100644
--- a/tests/org.eclipse.ease.testhelper/resources/DebugTest/main.py
+++ b/tests/org.eclipse.ease.testhelper/resources/DebugTest/main.py
@@ -17,6 +17,7 @@
     primitiveInteger = -42.0
     return a + b    # testMethod-result-hook
 
+include("include.py")  # include-command-hook
 result = testMethod(2, 3)   # testMethod-call-hook
 print("Result of testMethod = " + str(result))
 print("End of script")
diff --git a/tests/org.eclipse.ease.testhelper/src/org/eclipse/ease/testhelper/AbstractDebugTest.java b/tests/org.eclipse.ease.testhelper/src/org/eclipse/ease/testhelper/AbstractDebugTest.java
index fbb092d..5a15f67 100644
--- a/tests/org.eclipse.ease.testhelper/src/org/eclipse/ease/testhelper/AbstractDebugTest.java
+++ b/tests/org.eclipse.ease.testhelper/src/org/eclipse/ease/testhelper/AbstractDebugTest.java
@@ -23,8 +23,11 @@
 import java.io.UnsupportedEncodingException;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 
 import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
 import org.eclipse.core.resources.IProject;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.debug.core.DebugException;
@@ -56,15 +59,18 @@
 
 public abstract class AbstractDebugTest extends WorkspaceTestHelper {
 
+	public static final String MAIN_SCRIPT = "Main";
+	public static final String INCLUDE_SCRIPT = "include";
+
 	private static final int TEST_TIMEOUT = 7000;
 
 	private interface IDebugElementProvider {
 		EaseDebugElement getDebugElement();
 	}
 
-	private int getLineNumber(String text) {
+	private int getLineNumber(String script, String text) {
 		try {
-			final List<String> lines = Arrays.asList(getScriptSource().split("\r?\n"));
+			final List<String> lines = Arrays.asList(getScriptSources().get(script).split("\r?\n"));
 
 			for (int index = 0; index < lines.size(); index++) {
 				if (lines.get(index).contains(text))
@@ -90,7 +96,6 @@
 			DebugPlugin.getDefault().getBreakpointManager().removeBreakpoint(breakpoint, true);
 	}
 
-	private IFile fFile;
 	private ILaunch fLaunchMock;
 	private IDebugTarget fDebugTarget = null;
 	private IDebugEngine fScriptEngine;
@@ -107,7 +112,9 @@
 		fScriptEngine.setupDebugger(fLaunchMock, false, false, false);
 
 		final IProject project = createProject("Debug Test");
-		fFile = createFile("DebugTest." + fScriptEngine.getDescription().getSupportedScriptTypes().get(0).getDefaultExtension(), getScriptSource(), project);
+		for (final Entry<String, String> entry : getScriptSources().entrySet())
+			createFile(entry.getKey() + "." + fScriptEngine.getDescription().getSupportedScriptTypes().get(0).getDefaultExtension(), entry.getValue(), project);
+
 		clearBreakpoints();
 	}
 
@@ -128,14 +135,14 @@
 
 	@Test(timeout = TEST_TIMEOUT)
 	public void breakpointLocation() throws CoreException {
-		setBreakpoint(fFile, getLineNumber("primitive-integer-definition-hook"));
+		setBreakpoint(getFile(MAIN_SCRIPT), getLineNumber(MAIN_SCRIPT, "primitive-integer-definition-hook"));
 		assertEquals(1, getBreakpoints().length);
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, () -> {
 			final EaseDebugStackFrame stackFrame = getTopmostStackFrame();
 			if (stackFrame != null) {
-				assertEquals(getLineNumber("primitive-integer-definition-hook"), stackFrame.getLineNumber());
+				assertEquals(getLineNumber(MAIN_SCRIPT, "primitive-integer-definition-hook"), stackFrame.getLineNumber());
 				getDebugTarget().resume();
 			}
 		});
@@ -146,10 +153,10 @@
 	// ---------- step over tests ---------------------------------------------------------------
 
 	public void stepOverTestTemplate(IDebugElementProvider elementProvider) throws CoreException {
-		setBreakpoint(fFile, getLineNumber("testMethod-call-hook"));
+		setBreakpoint(getFile(MAIN_SCRIPT), getLineNumber(MAIN_SCRIPT, "testMethod-call-hook"));
 		assertEquals(1, getBreakpoints().length);
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, new Runnable() {
 
 			private boolean fFirstSuspend = true;
@@ -161,14 +168,14 @@
 
 					final IStackFrame[] stackFrames = getStackFrames();
 					assertEquals(1, stackFrames.length);
-					assertEquals(getLineNumber("testMethod-call-hook"), getTopmostStackFrame().getLineNumber());
+					assertEquals(getLineNumber(MAIN_SCRIPT, "testMethod-call-hook"), getTopmostStackFrame().getLineNumber());
 
 					elementProvider.getDebugElement().stepOver();
 
 				} else {
 					final IStackFrame[] stackFrames = getStackFrames();
 					assertEquals(1, stackFrames.length);
-					assertEquals(getLineNumber("testMethod-call-hook") + 1, getTopmostStackFrame().getLineNumber());
+					assertEquals(getLineNumber(MAIN_SCRIPT, "testMethod-call-hook") + 1, getTopmostStackFrame().getLineNumber());
 
 					elementProvider.getDebugElement().resume();
 				}
@@ -201,10 +208,10 @@
 	// ---------- step into tests ---------------------------------------------------------------
 
 	public void stepIntoTestTemplate(IDebugElementProvider elementProvider) throws CoreException {
-		setBreakpoint(fFile, getLineNumber("testMethod-call-hook"));
+		setBreakpoint(getFile(MAIN_SCRIPT), getLineNumber(MAIN_SCRIPT, "testMethod-call-hook"));
 		assertEquals(1, getBreakpoints().length);
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, new Runnable() {
 
 			private boolean fFirstSuspend = true;
@@ -216,14 +223,14 @@
 
 					final IStackFrame[] stackFrames = getStackFrames();
 					assertEquals(1, stackFrames.length);
-					assertEquals(getLineNumber("testMethod-call-hook"), getTopmostStackFrame().getLineNumber());
+					assertEquals(getLineNumber(MAIN_SCRIPT, "testMethod-call-hook"), getTopmostStackFrame().getLineNumber());
 
 					elementProvider.getDebugElement().stepInto();
 
 				} else {
 					final IStackFrame[] stackFrames = getStackFrames();
 					assertEquals(2, stackFrames.length);
-					assertEquals(getLineNumber("testMethod-def-hook"), getTopmostStackFrame().getLineNumber());
+					assertEquals(getLineNumber(MAIN_SCRIPT, "testMethod-def-hook"), getTopmostStackFrame().getLineNumber());
 
 					elementProvider.getDebugElement().resume();
 				}
@@ -256,10 +263,10 @@
 	// ---------- step return tests -------------------------------------------------------------
 
 	public void stepReturnTestTemplate(IDebugElementProvider elementProvider) throws CoreException {
-		setBreakpoint(fFile, getLineNumber("testMethod-result-hook"));
+		setBreakpoint(getFile(MAIN_SCRIPT), getLineNumber(MAIN_SCRIPT, "testMethod-result-hook"));
 		assertEquals(1, getBreakpoints().length);
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, new Runnable() {
 
 			private boolean fFirstSuspend = true;
@@ -271,14 +278,14 @@
 
 					final IStackFrame[] stackFrames = getStackFrames();
 					assertEquals(2, stackFrames.length);
-					assertEquals(getLineNumber("testMethod-result-hook"), getTopmostStackFrame().getLineNumber());
+					assertEquals(getLineNumber(MAIN_SCRIPT, "testMethod-result-hook"), getTopmostStackFrame().getLineNumber());
 
 					elementProvider.getDebugElement().stepReturn();
 
 				} else {
 					final IStackFrame[] stackFrames = getStackFrames();
 					assertEquals(1, stackFrames.length);
-					assertEquals(getLineNumber("testMethod-call-hook"), getTopmostStackFrame().getLineNumber());
+					assertEquals(getLineNumber(MAIN_SCRIPT, "testMethod-call-hook"), getTopmostStackFrame().getLineNumber());
 
 					elementProvider.getDebugElement().resume();
 				}
@@ -308,13 +315,100 @@
 		stepReturnTestTemplate(() -> getTopmostStackFrame());
 	}
 
+	// ---------- step commands when suspended on include command -------------------------------
+
+	@Test
+	public void stepOverIncludeCommand() throws CoreException {
+		setBreakpoint(getFile(MAIN_SCRIPT), getLineNumber(MAIN_SCRIPT, "include-command-hook"));
+		assertEquals(1, getBreakpoints().length);
+
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
+		final int suspendedEvents = runUntilTerminated(fScriptEngine, new Runnable() {
+
+			private boolean fFirstSuspend = true;
+
+			@Override
+			public void run() {
+				if (fFirstSuspend) {
+					fFirstSuspend = false;
+
+					final IStackFrame[] stackFrames = getStackFrames();
+					assertEquals(1, stackFrames.length);
+					assertEquals(getLineNumber(MAIN_SCRIPT, "include-command-hook"), getTopmostStackFrame().getLineNumber());
+
+					getThread().stepOver();
+
+				} else {
+					final IStackFrame[] stackFrames = getStackFrames();
+					assertEquals(1, stackFrames.length);
+					assertEquals(getLineNumber(MAIN_SCRIPT, "include-command-hook") + 1, getTopmostStackFrame().getLineNumber());
+
+					getThread().resume();
+				}
+			}
+		});
+
+		assertEquals(2, suspendedEvents);
+	}
+
+	@Test
+	public void stepReturnIncludeCommand() throws CoreException {
+		setBreakpoint(getFile(MAIN_SCRIPT), getLineNumber(MAIN_SCRIPT, "include-command-hook"));
+		assertEquals(1, getBreakpoints().length);
+
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
+		final int suspendedEvents = runUntilTerminated(fScriptEngine, () -> {
+			final IStackFrame[] stackFrames = getStackFrames();
+			assertEquals(1, stackFrames.length);
+			assertEquals(getLineNumber(MAIN_SCRIPT, "include-command-hook"), getTopmostStackFrame().getLineNumber());
+
+			getThread().stepReturn();
+		});
+
+		assertEquals(1, suspendedEvents);
+	}
+
+	@Test
+	public void stepIntoIncludeCommand() throws CoreException {
+		setBreakpoint(getFile(MAIN_SCRIPT), getLineNumber(MAIN_SCRIPT, "include-command-hook"));
+		assertEquals(1, getBreakpoints().length);
+
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
+		final int suspendedEvents = runUntilTerminated(fScriptEngine, new Runnable() {
+
+			private boolean fFirstSuspend = true;
+
+			@Override
+			public void run() {
+				if (fFirstSuspend) {
+					fFirstSuspend = false;
+
+					final IStackFrame[] stackFrames = getStackFrames();
+					assertEquals(1, stackFrames.length);
+					assertEquals(getLineNumber(MAIN_SCRIPT, "include-command-hook"), getTopmostStackFrame().getLineNumber());
+
+					getThread().stepInto();
+
+				} else {
+					final IStackFrame[] stackFrames = getStackFrames();
+					assertEquals(2, stackFrames.length);
+					assertEquals(1, getTopmostStackFrame().getLineNumber());
+
+					getThread().resume();
+				}
+			}
+		});
+
+		assertEquals(2, suspendedEvents);
+	}
+
 	// ---------- resume tests ------------------------------------------------------------------
 
 	public void resumeTestTemplate(IDebugElementProvider elementProvider) throws CoreException, IOException {
-		setBreakpoint(fFile, getLastLineNumber());
+		setBreakpoint(getFile(MAIN_SCRIPT), getLastLineNumber());
 		assertEquals(1, getBreakpoints().length);
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, () -> {
 			elementProvider.getDebugElement().resume();
 		});
@@ -345,11 +439,11 @@
 	// ---------- terminate tests ---------------------------------------------------------------
 
 	public void terminateTestTemplate(IDebugElementProvider elementProvider) throws CoreException {
-		setBreakpoint(fFile, 1);
-		setBreakpoint(fFile, 2);
+		setBreakpoint(getFile(MAIN_SCRIPT), 1);
+		setBreakpoint(getFile(MAIN_SCRIPT), 2);
 		assertEquals(2, getBreakpoints().length);
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, () -> {
 			elementProvider.getDebugElement().terminate();
 		});
@@ -385,11 +479,11 @@
 	// ---------- disconnect tests --------------------------------------------------------------
 
 	public void disconnectTestTemplate(IDebugElementProvider elementProvider) throws CoreException {
-		setBreakpoint(fFile, 1);
-		setBreakpoint(fFile, 2);
+		setBreakpoint(getFile(MAIN_SCRIPT), 1);
+		setBreakpoint(getFile(MAIN_SCRIPT), 2);
 		assertEquals(2, getBreakpoints().length);
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, () -> {
 			getDebugTarget().disconnect();
 
@@ -428,10 +522,10 @@
 	public void evaluateWatchExpression() throws CoreException, IOException {
 		final IWatchExpressionListener expressionListener = mock(IWatchExpressionListener.class);
 
-		setBreakpoint(fFile, getLastLineNumber());
+		setBreakpoint(getFile(MAIN_SCRIPT), getLastLineNumber());
 		assertEquals(1, getBreakpoints().length);
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, () -> {
 
 			final EaseWatchExpressionDelegate expressionDelegate = new EaseWatchExpressionDelegate();
@@ -458,10 +552,10 @@
 
 	@Test(timeout = TEST_TIMEOUT)
 	public void suspendedState() throws CoreException, IOException {
-		setBreakpoint(fFile, getLastLineNumber());
+		setBreakpoint(getFile(MAIN_SCRIPT), getLastLineNumber());
 		assertEquals(1, getBreakpoints().length);
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, () -> {
 
 			assertFalse(getDebugTarget().isDisconnected());
@@ -528,7 +622,7 @@
 	@Test(timeout = TEST_TIMEOUT)
 	public void terminatedState() throws CoreException {
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, () -> {
 		});
 
@@ -584,10 +678,10 @@
 
 	@Test(timeout = TEST_TIMEOUT)
 	public void primitiveDoubleVariable() throws CoreException, IOException {
-		setBreakpoint(fFile, getLastLineNumber());
+		setBreakpoint(getFile(MAIN_SCRIPT), getLastLineNumber());
 		assertEquals(1, getBreakpoints().length);
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, () -> {
 			try {
 				final IStackFrame stackFrame = getTopmostStackFrame();
@@ -612,10 +706,10 @@
 
 	@Test(timeout = TEST_TIMEOUT)
 	public void primitiveStringVariable() throws CoreException, IOException {
-		setBreakpoint(fFile, getLastLineNumber());
+		setBreakpoint(getFile(MAIN_SCRIPT), getLastLineNumber());
 		assertEquals(1, getBreakpoints().length);
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, () -> {
 			try {
 				final IStackFrame stackFrame = getTopmostStackFrame();
@@ -640,10 +734,10 @@
 
 	@Test(timeout = TEST_TIMEOUT)
 	public void nullVariable() throws CoreException, IOException {
-		setBreakpoint(fFile, getLastLineNumber());
+		setBreakpoint(getFile(MAIN_SCRIPT), getLastLineNumber());
 		assertEquals(1, getBreakpoints().length);
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, () -> {
 			try {
 				final IStackFrame stackFrame = getTopmostStackFrame();
@@ -668,10 +762,10 @@
 
 	@Test(timeout = TEST_TIMEOUT)
 	public void nativeArrayVariable() throws CoreException, IOException {
-		setBreakpoint(fFile, getLastLineNumber());
+		setBreakpoint(getFile(MAIN_SCRIPT), getLastLineNumber());
 		assertEquals(1, getBreakpoints().length);
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, () -> {
 			final IStackFrame stackFrame = getTopmostStackFrame();
 			assertNotNull(stackFrame);
@@ -716,10 +810,10 @@
 
 	@Test(timeout = TEST_TIMEOUT)
 	public void arrayVariableSorting() throws CoreException, IOException {
-		setBreakpoint(fFile, getLastLineNumber());
+		setBreakpoint(getFile(MAIN_SCRIPT), getLastLineNumber());
 		assertEquals(1, getBreakpoints().length);
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, () -> {
 			try {
 				final IStackFrame stackFrame = getTopmostStackFrame();
@@ -745,10 +839,10 @@
 
 	@Test(timeout = TEST_TIMEOUT)
 	public void nativeObjectVariable() throws CoreException, IOException {
-		setBreakpoint(fFile, getLastLineNumber());
+		setBreakpoint(getFile(MAIN_SCRIPT), getLastLineNumber());
 		assertEquals(1, getBreakpoints().length);
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, () -> {
 			final IStackFrame stackFrame = getTopmostStackFrame();
 			assertNotNull(stackFrame);
@@ -785,10 +879,10 @@
 
 	@Test(timeout = TEST_TIMEOUT)
 	public void javaClassVariable() throws CoreException, IOException {
-		setBreakpoint(fFile, getLastLineNumber());
+		setBreakpoint(getFile(MAIN_SCRIPT), getLastLineNumber());
 		assertEquals(1, getBreakpoints().length);
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, () -> {
 			try {
 				final IStackFrame stackFrame = getTopmostStackFrame();
@@ -854,10 +948,10 @@
 
 	@Test(timeout = TEST_TIMEOUT)
 	public void innerScopeVariableBeforeOuterScopeVariable() throws CoreException {
-		setBreakpoint(fFile, getLineNumber("testMethod-result-hook"));
+		setBreakpoint(getFile(MAIN_SCRIPT), getLineNumber(MAIN_SCRIPT, "testMethod-result-hook"));
 		assertEquals(1, getBreakpoints().length);
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, () -> {
 			try {
 				final IStackFrame stackFrame = getTopmostStackFrame();
@@ -878,10 +972,10 @@
 
 	@Test
 	public void modifyVariableKeepingType() throws CoreException, IOException {
-		setBreakpoint(fFile, getLineNumber("testMethod-call-hook"));
+		setBreakpoint(getFile(MAIN_SCRIPT), getLineNumber(MAIN_SCRIPT, "testMethod-call-hook"));
 		assertEquals(1, getBreakpoints().length);
 
-		fScriptEngine.executeAsync(fFile);
+		fScriptEngine.executeAsync(getFile(MAIN_SCRIPT));
 
 		final int suspendedEvents = runUntilTerminated(fScriptEngine, new Runnable() {
 
@@ -1001,12 +1095,46 @@
 	}
 
 	private int getLastLineNumber() throws IOException {
-		return getScriptSource().trim().split("\r?\n").length;
+		return getScriptSources().get(MAIN_SCRIPT).trim().split("\r?\n").length;
 	}
 
-	protected abstract LineBreakpoint setBreakpoint(IFile file, int lineNumber) throws CoreException;
+	protected IFile getFile(String identifier) {
+		try {
+			final IProject project = createProject("Debug Test");
+			return project.getFile(identifier + "." + fScriptEngine.getDescription().getSupportedScriptTypes().get(0).getDefaultExtension());
+		} catch (final CoreException e) {
+			throw new RuntimeException("Cannot access test file: " + identifier);
+		}
+	}
 
-	protected abstract IBreakpoint[] getBreakpoints() throws CoreException;
+	private LineBreakpoint setBreakpoint(IFile file, int lineNumber) throws CoreException {
+		final IMarker marker = file.createMarker(getBreakpointId());
+		marker.setAttribute("org.eclipse.debug.core.enabled", true);
+		marker.setAttribute("org.eclipse.debug.core.id", getDebugModelId());
+		marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
 
-	protected abstract String getScriptSource() throws IOException;
+		final LineBreakpoint breakpoint = new LineBreakpoint() {
+
+			@Override
+			public String getModelIdentifier() {
+				return getDebugModelId();
+			}
+		};
+
+		breakpoint.setMarker(marker);
+
+		DebugPlugin.getDefault().getBreakpointManager().addBreakpoint(breakpoint);
+
+		return breakpoint;
+	}
+
+	private IBreakpoint[] getBreakpoints() throws CoreException {
+		return DebugPlugin.getDefault().getBreakpointManager().getBreakpoints(getDebugModelId());
+	}
+
+	protected abstract String getDebugModelId();
+
+	protected abstract String getBreakpointId();
+
+	protected abstract Map<String, String> getScriptSources() throws IOException;
 }