Monday, September 13, 2010

pdb with Sikuli

As I mentioned in my last post, the big barrier to debugging a Sikuli script with pdb is that, when you enter a debugging command like "next", "continue", etc. that is supposed to allow program flow to continue, you are in the debugging console window, and your Sikuli script will send the keyclicks and mouse actions into the debugging console window instead of the application your script was supposed to be driving.

Here's a minor modification to pdb that can change that by using Alt-TAB to switch back to the application window every time program flow moves from the debugger to the application. Save this code as sikpdb.py in the Lib directory that you unzipped Sikuli into, for example, ~/Sikuli-IDE/Lib. (/Lib is not created by default, but it is the first item in sys.path.)

#! /usr/bin/env python

"""A Python debugger.

A slight modification of ``pdb``; attempts to switch
screen focus back to the invoking app before running commands
in the program being debugged. This should prevent Sikuli
commands from being "typed" or "clicked" into the debugger
window when they should go into the window of the app being
debugged."""
# catherinedevlin.blogspot.com
# (See pdb.doc for documentation.)

import pdb
import sys

class Pdb(pdb.Pdb):
def _to_running_window(self):
"""Moves focus to the window a Sikuli script was running
before the debugger was invoked, so that subsequent commands
will act in the app instead of the debugger."""
self.onecmd("SCREEN.type(Key.TAB, KEY_ALT)")

def do_step(self, arg):
self._to_running_window()
return pdb.Pdb.do_step(self, arg)
do_s = do_step

def do_next(self, arg):
self._to_running_window()
return pdb.Pdb.do_next(self, arg)
do_n = do_next

def do_return(self, arg):
self._to_running_window()
return pdb.Pdb.do_return(self, arg)
do_r = do_return

def do_continue(self, arg):
self._to_running_window()
return pdb.Pdb.do_continue(self, arg)
do_c = do_cont = do_continue

def set_trace():
Pdb().set_trace(sys._getframe().f_back)

Now, at the point in your Sikuli script where you want to enter the debugger, insert
import sikpdb; sikpdb.set_trace()

Also remember that you need to add -Dsikuli.console=false to your .sh or .bat script in order to allow pdb input/output to appear in the console window you used to invoke Sikuli. Otherwise, Sikuli will just hang when you attempt to enter the debugger (it's waiting for your input - in some nonexistant interactive session). For instance:
$ cat sikuli-ide.sh
#!/bin/sh
DIR=`dirname $0`
java -Xms64M -Dsikuli.console=false -Xmx512M -Dfile.encoding=UTF-8 -jar $DIR/sikuli-ide.jar $*


The remaining problem is that sikpdb.py depends on the notion that ALT-TAB will reliably return you to the application's window. This is true, but only if you do not browse around among your open windows while in the debugger; if you do, you'll change the z-ordering of the windows. It would be more reliable to call switchApp() with the title of the application's window, but I don't know of a cross-platform way to automatically detect it - the best I can think of would be to manually pass it as an argument to set_trace, have set_trace store it in an instance variable like self._application_window_title, then call self.onecmd("switchApp('%s')" % self._application_window_title) in place of self.onecmd("SCREEN.type(Key.TAB, KEY_ALT)") . It's more complex to use, but more reliable... feel free to use that approach if you prefer it.

1 comment:

ptmcg said...

I have similar problems with pdb, when I want to evaluate a variable named 'c', 's', or 'n'. Being lazy, I just type in the variable name, forgetting that I am actually typing in a pdb command!