Symbian developer community

 
wiki

Debugging Techniques (Python on Symbian)

From Symbian Developer Community

Jump to: navigation, search

Contents

Introduction

Statistics about software development states that we may waste 3/4 of our project cycle life in testing/debugging. Did you get scared ? Yes, me too. And the unique scape is to write better software and to use efficient debug strategies. In this chapter we want to outline some strategies for writing/debugging better PyS60 code.

Writing better PyS60 code

If you are programming for years probably you are used to the several tools cited below. If not, I think it is a good opportunity to become familiar with them.

  • Release Early, Release Often: If you are starting, the book The Cathedral and the Bazaar, by Eric S. Raymond, is a must read for software developers. In special, the rule Release Early, Release Often is very useful to avoid later bugs. If you have a good number of users or at least a small and smart set of users, work as close as possible and try to keep them fed with frequent software updates.
  • Pair programming, code inspection: Several projects does not have budget to support pair programming or at least code inspection but they are important techniques to avoid bugs. Free tools like Google Code supports code inspection and you may require an inspection even if the project team is composed only by yourself.
  • Version control: Do not write code without it. Seriously. For small projects or few experience with Version Control System, Bzr and Mercurial are excellent options. CVS, Subversion and git are interesting (and more difficult to learn) but they will help if you want to host your project on open source repositories like Sourceforge, Savanna, Maemo or Google Code.
  • Bug tracker: Same advice: do not write code without it. They you help you to communicate with your users using a formal interface for issues tracker instead using only your email. Sourceforge, Savanna, Maemo or Google Code have their own bug tracker system and Mantis or Trac are some options for a standalone bug tracker.
  • Documentation: A good documentation will help a lot other people to join your project. Moreover, it may help when you need to fix a bug several months after. Python supports that you write code and documentation together with few additional efforts.
  • Unit testing: There are a lot of books talking about unit testing and test driven development that deserve some attention. Even if you do not want to change your development paradigm, it is a good practice to divide your code in modules, testing them individually.

Debugging strategies

Python shell

The most basic debug technique is based on sending logs the phone screen. Using the print command you will be able to follow the execution of your program if it does not need any user interface:

 
# instructions here
print "I am here"
# more instructions
print "now I am here"
# ...
 

Remember that you can execute a program from Python shell using the menu option "Run script". Only programs located at e:\Python or c:\data\Python are presented to you. If you are importing modules from your code, put these modules under e:\Python\lib or add their path to sys.path:

 
import sys
sys.path.append(u"e:\\python\\mydir")
 

Programs with user interface will switch quickly between script shell and the current user interface and you probably will not see the log messages. These messages will be available when you finish your program and return to the script shell. In this case, you can not call app.set_exit() or the script shell will close as well.

You can use a Text() object as an alternative to the shell console, adding messages to it when necessary.

 
from appuifw import *
 
logview = Text()
app.body = logview
 
def add_msg(msg):
global logview
# u"\u2029" is the new line code for Text()
logview.add(unicode(msg) + u"\u2029")
# put the cursor at the end
logview.set_pos(logview.len())
 
add_msg(u"Starting...")
# some code here
 
add_msg(u"Now running at this point ...")
# more code ...
 

Instead printed messages you can try to use audio messages:

 
import audio
# instructions here
audio.say(u"I am here")
# more instructions
audio.say(u"now I am here")
# ...
 

Python shell over Bluetooth

Other alternative is to run the Python shell over a Bluetooth connection. In this case, printed messages will not disturb your user interface and you will have more flexibility to type from your computer. This process is described in the Nokia Open Source site. In summary, you need:

  • Setup a COM port on your Bluetooth device.
  • Using an application like Hyperterminal or Putty, connect to this port.
  • In your phone, open the Python shell and select "Bluetooth console" from menu. Thus, select the Bluetooth name of your computer.

After these steps, you will have a Python shell in your PC.

Python shell over WiFi

It is also possible to use a console over WiFi connections using Netcat utility. If you are running Unix, Linux or MacOS X, type the following lines in a console:

# Running a TCP server on port 1025
stty raw -echo ; nc -l -p 1025 ; stty sane

There is a Netcat version for Windows but you can have some problems with control characters since stty is not available. In this case, use only:

nc -l -p 1025

In your phone, launch the script below. Do not forget to set the proper IP of your computer (we are using 10.0.0.10). You can improve it with calls to appuifw.query(), allowing users to select IP and port.

 
import btconsole
from socket import *
 
sock = socket(AF_INET,SOCK_STREAM)
# put the proper IP and port here
sock.connect(("10.0.0.10",1025))
btconsole.run_with_redirected_io(sock,btconsole.interact, None, None, locals())
 

Nokia emulator

Before using the emulator, check if your Symbian^1 SDK is properly installed. Once you have a running SDK check if you can launch the emulator by typing the following command (replace [SDK] by you SDK path):

[SDK]\epoc32\release\winscw\udeb\epoc.exe

Image:mba_emulator01.png

Next step is to install the Python patch. Download Python_2.0.0_SDK_3rdEdFP2.zip and extract it to the root of your SDK. Some files will be overwrited in the folder [SDK]\epoc32, just accept these modifications.

At this point, you should have Python running in the emulator. Launch the emulator and look for Python script shell in the Applications menu. Run it and select "Interactive console" from menu if you want to have a working Python interactive console.

Image:mba_emulator02.png

For starting you debug session, you can copy your scripts into the following directory:

[SDK]\epoc32\winscw\c\Data\python

You access them using "Run script" from Python shell menu. If you want to import the scripts, create the directory lib and copy your scripts into this directory:

mkdir [SDK]\epoc32\winscw\c\Data\python\lib

In fact, you can use any location inside [SDK]\epoc32\winscw\c after a proper configuration of sys.path. For instance, if your scripts are in [SDK]\epoc32\winscw\c\appdbg, you could type in the Python console:

 
import sys
sys.path.append(u"c:\\appdbg")
import [my_script_name_here]
 

Log files

Some programs are really hard to debug (multi thread programs, for instance) or your program may close unexpectedly with no chance for exception handling. In such cases, an additional technique is to use log files to debug. Post-mortem analysis are made simple with log files and no much effort is necessary to create your logging strategy.

A log class is suggested in the next code snippet. Just save it in a file called logfile.py and import the object FLOG. Any module may import this object and use it to add log messages with FLOG.add(msg):

 
from logfile import FLOG
 
FLOG.add("Log initialized")
 

Caller information is automatically added to the log using the object sys._getframe and a semaphore (created with thread.allocate_lock) is provided to avoid reentrance problems when writing to the file. After writing, the file is flushed and any pending byte is written so you can trust that message is written after the call to FLOG.add().

 
# logfile.py
#
import sys
import time
import thread
 
__all__ = [ "FLOG" ]
 
class FileLog(object):
def __init__(self,filename):
self.filename = filename
self.file = open(self.filename,'at')
self.lock = thread.allocate_lock()
 
def add(self,msg):
# collect caller information from stack call using _getframe
caller = sys._getframe(1).f_code.co_name
line = str(sys._getframe(1).f_code.co_firstlineno)
# create log message
timestamp = time.strftime("[%Y%m%d %H:%M:%S] ",time.localtime())
logmsg = timestamp + caller + ":" + line + " - " + msg + " \n"
# write log message, using a semaphore for controlling the file access
self.lock.acquire()
self.file.write(logmsg)
self.file.flush()
self.lock.release()
 
FLOG = FileLog("e:\\filelog.txt")
 

Enhancing runtime error detection

During the development phase it is common to execute all PyS60 applications from the Python shell. The option Run script is responsible to select and launch your application. It is possible to use the console as well, on the phone or remote, via bluetooth or WiFi.

However, when you decide to deploy your program using sis executables, a new bunch of problems may arise. In general these problems are related to missing modules, wrong paths in sys.path, different phone models and so on. In such situation you can use log files, proper exception handling or even improving your runtime error detection presenting a comprehensive message to the user. The debug strategy presented below is based on collecting all necessary information from phone and exception data after a failure, creating a new user interface and allowing the user to report them easily. Basically, the exception handler data is collected and presented to the user using a new user interface with menu and Text() as main body, as you can see in the following code:

 
try:
# Put you startup code here.
# For instance, import your module and run it.
# import my_module
# my_module.startup()
pass
except Exception, e:
# Oops, something wrong. Report problems to user
# and ask him/her to send them to you.
import appuifw
import traceback
import sys
import e32
 
# Collecting call stack info
e1,e2,e3 = sys.exc_info()
call_stack = unicode(traceback.format_exception(e1,e2,e3))
 
# Creating a friendly user message with exception details
new_line = u"\u2029"
err_msg = u"This programs was unexpectedly closed due to the following error: "
err_msg += unicode(repr(e)) + new_line
err_msg += u"Please, copy and past the text presented here and "
err_msg += u"send it to email@server.com. "
err_msg += u"Thanks in advance and sorry for this inconvenience." + new_line*2
err_msg += u"Call stack:" + new_line + call_stack
 
# Small PyS60 application
lock = e32.Ao_lock()
appuifw.app.body = appuifw.Text(err_msg)
appuifw.app.body.set_pos(0)
appuifw.app.menu = [(u"Exit", lambda: lock.signal())]
appuifw.app.title = u"Error log"
lock.wait()
 

For instance, suppose your main program is just the following code snippet:

 
#...
try:
import camera
camera.take_photo(camera.RGB32) # error: invalid mode
except Exception, e:
#...
 

The following message will be presented to the user:

This programs was unexpectedly closed due to the following error: AttributeError("'module' object has no attribute 'RGB32'",)
Please, copy and past the text presented here and send it to email@server.com. Thanks in advance and sorry for this inconvenience.

Call stack:
['Traceback (most recent call last):\n', '  File "e:\\python\\demo.py", line 7, in <module>\n    camera.take_photo(camera.RGB32)\n', "AttributeError: 'module' object has no attribute 'RGB32'\n"]

Image:MBA_base_skel_runtime_err.png


This skeleton may be improved with some new actions. For instance, SMS/MMS messages could be created and sent to program's author using messaging module or you could take a screenshot using graphics module. The modified program is below, followed by a screenshot of the received message.

 
try:
# Put you startup code here.
# For instance, import your module and run it.
# import my_module
# my_module.startup()
import camera
camera.take_photo(camera.RGB32) # error: invalid mode
except Exception, e:
# Oops, something wrong. Report problems to user
# and ask him/her to send them to you.
import appuifw
import traceback
import sys
import e32
 
# Collecting call stack info
e1,e2,e3 = sys.exc_info()
call_stack = unicode(traceback.format_exception(e1,e2,e3))
 
# Creating a friendly user message with exception details
new_line = u"\u2029"
err_msg = u"This programs was unexpectedly closed due to the following error: "
err_msg += unicode(repr(e)) + new_line
err_msg += u"Please, copy and past the text presented here and "
err_msg += u"send it to email@server.com. "
err_msg += u"Thanks in advance and sorry for this inconvenience." + new_line*2
err_msg += u"Call stack:" + new_line + call_stack
 
def send_sms():
import messaging
msg = appuifw.app.body.get()
messaging.sms_send("+5516xxxxxxxx",msg) # put your phone here
appuifw.note(u"A message was created and sent. Thanks a lot.","info")
 
def save_screenshot():
import graphics
ss = graphics.screenshot()
ss.save(u"e:\\screenshot.png")
appuifw.note(u"Screenshot saved in e:\\ ","info")
 
# Small PyS60 application
lock = e32.Ao_lock()
appuifw.app.body = appuifw.Text(err_msg)
appuifw.app.body.set_pos(0)
appuifw.app.menu = [(u"Send report via SMS",send_sms),
(u"Save screenshot",save_screenshot),
(u"Exit", lambda: lock.signal())]
appuifw.app.title = u"Error log"
lock.wait()
 

Image:MBA_sms_runtime_err.png

Comments

Sign in to comment…