/** * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package org.python.pydev.debug.ui; import java.io.ByteArrayInputStream; import java.util.HashSet; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.Path; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.core.model.IDebugTarget; import org.eclipse.debug.core.model.IStackFrame; import org.eclipse.debug.core.model.IThread; import org.eclipse.debug.core.model.IVariable; import org.eclipse.jface.text.source.IVerticalRulerInfo; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.WorkbenchException; import org.python.pydev.debug.model.PyDebugTarget; import org.python.pydev.debug.model.PyVariable; import org.python.pydev.debug.ui.actions.PyBreakpointRulerAction; import org.python.pydev.debug.ui.launching.JythonLaunchShortcut; import org.python.pydev.editor.PyEdit; import org.python.pydev.editor.codecompletion.revisited.javaintegration.AbstractWorkbenchTestCase; import org.python.pydev.editorinput.PyOpenEditor; import org.python.pydev.plugin.PydevPlugin; import org.python.pydev.utils.ICallback; public class DebuggerTestWorkbench extends AbstractWorkbenchTestCase { /** * File used to debug */ private IFile debugFile; /** * The editor that'll be created on debug */ private PyEdit debugEditor; /** * Maximum number of loops (used with the timeout) */ private final int MAX_LOOPS = 300; /** * Maximum time for each loop in millis */ private final int STEP_TIMEOUT = 100; /** * Number of steps in the tests that will have busy loops until some condition is hit. */ private final int TOTAL_STEPS = 3; /** * Total time in millis that the test has for finishing */ private final int TOTAL_TIME_FOR_TESTS = MAX_LOOPS * STEP_TIMEOUT * (TOTAL_STEPS + 1); /** * Used for having wait() */ private Object lock = new Object(); /** * An exception that occurred that was thrown and didn't let the tests finish */ private Throwable failException = null; /** * Only true when the test finishes without exceptions. */ private boolean finished = false; private String currentStep = "<unspecified>"; /** * Creates the debug file and editor. */ @Override protected void setUp() throws Exception { super.setUp(); debugFile = initFile.getParent().getFile(new Path("debug_file.py")); String mod1Contents = "from pack1.pack2 import mod1\nprint mod1\nprint 'now'\n"; debugFile.create(new ByteArrayInputStream(mod1Contents.getBytes()), true, null); debugFile.refreshLocal(IResource.DEPTH_ZERO, null); debugEditor = (PyEdit) PyOpenEditor.doOpenEditor(debugFile); } /** * Removes the debug file and closes the debug editor */ @Override protected void tearDown() throws Exception { super.tearDown(); if (debugFile != null) { debugFile.delete(true, null); } if (debugEditor != null) { debugEditor.close(false); } } /** * In this test, a thread is started and then we wait on a busy loop until the thread finishes with the tests. */ public void testDebugger() throws Exception { //start the thread that'll do the test threadTest.start(); //wait on a busy loop until the test is finished or an exception is thrown. goToManual(TOTAL_TIME_FOR_TESTS, new com.aptana.shared_core.callbacks.ICallback<Boolean, Object>() { public Boolean call(Object arg) { return finished || failException != null; } }); //Make it fail if we encountered some problem if (failException != null) { failException.printStackTrace(); fail("Current Step: " + currentStep + "\n" + failException.getMessage()); } if (!finished) { if (failException == null) { fail("Current Step: " + currentStep + "\nThe test didn't finish in the available time: " + TOTAL_TIME_FOR_TESTS / 1000 + " secs."); } } } /** * This is the thread that'll make the test. */ Thread threadTest = new Thread() { @Override public void run() { try { currentStep = "launchEditorInDebug"; //make a launch for debugging launchEditorInDebug(); //switch to debug perspective, because otherwise, when we hit a breakpoint it'll ask if we want to show it. switchToPerspective("org.eclipse.debug.ui.DebugPerspective"); PyBreakpointRulerAction createAddBreakPointAction = createAddBreakPointAction(1); createAddBreakPointAction.run(); currentStep = "waitForLaunchAvailable"; ILaunch launch = waitForLaunchAvailable(); PyDebugTarget target = (PyDebugTarget) waitForDebugTargetAvailable(launch); currentStep = "waitForSuspendedThread"; IThread suspendedThread = waitForSuspendedThread(target); assertTrue(suspendedThread.getName().startsWith("MainThread")); IStackFrame topStackFrame = suspendedThread.getTopStackFrame(); assertTrue("Was not expecting: " + topStackFrame.getName(), topStackFrame.getName().indexOf("debug_file.py:2") != 0); IVariable[] variables = topStackFrame.getVariables(); HashSet<String> varNames = new HashSet<String>(); for (IVariable variable : variables) { PyVariable var = (PyVariable) variable; varNames.add(var.getName()); } HashSet<String> expected = new HashSet<String>(); expected.add("Globals"); expected.add("__doc__"); expected.add("__file__"); expected.add("__name__"); expected.add("mod1"); assertEquals(expected, varNames); assertTrue(target.canTerminate()); target.terminate(); finished = true; } catch (Throwable e) { failException = e; } } }; /** * Creates a run in debug mode for the debug editor */ private void launchEditorInDebug() { final IWorkbench workBench = PydevPlugin.getDefault().getWorkbench(); Display display = workBench.getDisplay(); // Make sure to run the UI thread. display.syncExec(new Runnable() { public void run() { JythonLaunchShortcut launchShortcut = new JythonLaunchShortcut(); launchShortcut.launch(debugEditor, "debug"); } }); } /** * @return an action that can be run to create a breakpoint in the given line */ private PyBreakpointRulerAction createAddBreakPointAction(final int line) { PyBreakpointRulerAction ret = new PyBreakpointRulerAction(debugEditor, new IVerticalRulerInfo() { public int getLineOfLastMouseButtonActivity() { return line; } public Control getControl() { throw new RuntimeException("Not Implemented"); } public int getWidth() { throw new RuntimeException("Not Implemented"); } public int toDocumentLineNumber(int y_coordinate) { throw new RuntimeException("Not Implemented"); } }); ret.update(); return ret; } /** * This method can be used to switch to a given perspective * @param perspectiveId the id of the perspective that should be activated. */ protected void switchToPerspective(final String perspectiveId) { final IWorkbench workBench = PydevPlugin.getDefault().getWorkbench(); Display display = workBench.getDisplay(); // Make sure to run the UI thread. display.syncExec(new Runnable() { public void run() { IWorkbenchWindow window = workBench.getActiveWorkbenchWindow(); try { workBench.showPerspective(perspectiveId, window); } catch (WorkbenchException e) { failException = e; } } }); } /** * Waits until some thread is suspended. */ protected IThread waitForSuspendedThread(final PyDebugTarget target) throws Throwable { final IThread[] ret = new IThread[1]; waitForCondition(new ICallback() { public Object call(Object args) throws Exception { IThread[] threads = target.getThreads(); for (IThread thread : threads) { if (thread.isSuspended()) { ret[0] = thread; return true; } } return false; } }, "waitForSuspendedThread"); return ret[0]; } /** * Waits until a launch becomes available * @return the launch that was found */ private ILaunch waitForLaunchAvailable() throws Throwable { final ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); waitForCondition(new ICallback() { public Object call(Object args) throws Exception { ILaunch[] launches = launchManager.getLaunches(); return launches.length > 0; } }, "waitForLaunchAvailable"); return launchManager.getLaunches()[0]; } /** * Waits until a debug target is available in the passed launch * @return the debug target found */ private IDebugTarget waitForDebugTargetAvailable(final ILaunch launch) throws Throwable { waitForCondition(new ICallback() { public Object call(Object args) throws Exception { return launch.getDebugTarget() != null; } }, "waitForDebugTargetAvailable"); return launch.getDebugTarget(); } /** * Keeps on a busy loop with a timeout until the given callback returns true (otherwise, an * exception is thrown when the total time is elapsed). */ private void waitForCondition(ICallback callback, String errorMessage) throws Throwable { if (failException != null) { throw failException; } int loops = MAX_LOOPS; for (int i = 0; i < loops; i++) { if ((Boolean) callback.call(new Object[] {})) { return; } synchronized (lock) { try { Thread.yield(); lock.wait(STEP_TIMEOUT); } catch (InterruptedException e) { } } } fail("Unable to get to condition after " + (loops * STEP_TIMEOUT) / 1000 + " seconds.\nMessage: " + errorMessage); } }