Python on Symbian/17. Debugging Techniques
From Symbian Developer Community
Original Author: Marcelo Barros
This chapter demonstrates the main debugging techniques for Python on the Symbian platform and gives a brief overview of tools and programming practices that can help you write robust Python code.
Contents |
Introduction
Defects can be introduced into software at any point in the software development lifecycle, and may be detected in any of these stages or later during unit testing and system testing or, in the worst case, by the end-user. There is plenty of evidence to show that defects are orders of magnitude easier and cheaper to fix early in the design cycle. There is also a lot of evidence to show that good programming and testing practices can eliminate and detect defects.
The first section of this chapter provides useful links to some of the techniques and tools that can help you write better code to avoid defects. The rest of the chapter demonstrates the techniques you use for debugging Python code on the Symbian platform. Unfortunately, Python on Symbian does not provide an interactive source code debugger; while you can use an IDE for your development if you choose (including the Python IDLE IDE) you can't step through your running code or debug on a target Symbian device. Instead, most of the techniques described use logging to report the state of the application as the code runs. The techniques are flexible and can be used to debug simple scripts or more complex applications.
Writing better code
The following development methodologies and tools can help improve the quality of your code.
- Release early and often. A frequent release cycle keeps your early-adopters and testers interested, and having more eyes on the code improves the chances of defects being detected. The idea of Release Early, Release Often comes from the book The Cathedral and the Bazaar by Eric S. Raymond.
- Perform code inspection by another programmer, which is an important tool for detecting defects and ensuring that code is maintainable.
- Pair programming is an agile methodology that takes the idea of code review further, and combines code generation with review. Free tools, such as Google Code, support code inspection and, even if your project does not have budget for an independent code review or pair programming, you still gain value by inspecting your own code with a critical eye.
- Test your code thoroughly. In particular, it is good programming practice to divide your code into modules and unit test each individually. test-driven development is a methodology that deserves some attention.
- Document your code. It is important to document your code as you write it, and maintain the documentation along with any code changes. Good documentation makes it easier for others to work on your code, and to understand it when fixing defects. Python makes it very easy to write code and documentation together.
- Use version control tools. Source Code Management (SCM)) allows you to keep a record of specific changes and to undo mistakes if necessary.
- Bzr (http://bazaar.canonical.com) and Mercurial (http://mercurial.selenic.com) are excellent options for small projects.
- CVS, Subversion and Git are more difficult to learn, but are helpful if you want to host your project on open source repositories like Sourceforge, Savanna, Maemo or Google Code.
- Use a bug tracker. For a very small project you can track defects using a spreadsheet. However, if you have a larger project, or you need to communicate your defects outside your immediate project team, a bug tracking tool is essential.
- Sourceforge, Savanna, Maemo or Google Code have their own bug tracker system.
- Mantis (http://www.mantisbt.org), Trac (http://trac.edgewall.org) and Bugzilla (http://www.bugzilla.org) are good options for a standalone bug tracker.
Debugging tools and techniques
Python shell
During the development phase it is common to execute all PyS60 applications from the Python shell. The most basic debug technique is to run the code within the Python shell application and use the print command to log application state to the console, as follows:
# instructions here
print "I am here"
# more instructions
print "now I am here"
# ...
You may also want to have an additional DEBUG flag to switch between the debug mode and normal execution mode. For example:
# script starts
import modules
# Debug
DEBUG = True
# instructions here
def myfunction():
if DEBUG:
print u"In myfunction()" # debug
# more instructions
# more instructions
To execute your script from the Python shell, select the menu option Run script (note that you will need to copy it to e:\Python or c:\data\Python first).
This technique is not particularly useful for debugging programs with a user interface because the console (and log messages) is usually hidden by the running application. Provided that the script does not call app.set_exit() to close the script shell, the messages will be available when the script finishes.
Python shell over Bluetooth
The Python shell can be run over a Bluetooth connection. This approach allows you to drive execution of your application from your computer, and ensure that log messages are visible at the point of execution. More information can be found at http://wiki.opensource.nokia.com/projects/PyS60_Bluetooth_console but, in summary, you need to:
- Configure a COM port on your PC.
- Use an application like Hyperterminal or Putty to connect to this port.
- In your phone, open the Python shell and select Bluetooth console from menu. Then select the Bluetooth name of your computer.
Python shell over WiFi
The Python shell can also be run over WiFi connections using the Netcat utility (http://netcat.sourceforge.net). If you are running Unix, Linux or Mac OS 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
Launch the following script on your phone. 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())
Audio logging
An alternative method to logging with printed messages, is to use "speak" the messages using the audio module's text to speech converter:
import audio
# instructions here
audio.say(u"I am here")
# more instructions
audio.say(u"now I am here")
# ...
The text to speech functionality is discussed in Chapter 7.
Logging view
An alternative to logging to the shell console is to log to a Text object. Having a dedicated logging view allows us to see the logs for a running UI application, which means that we don't need to run the script in a shell environment in order to debug it.
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 ...
The previous fragment logs to a Text object that is the current app.body. In a user interface application we would probably have the logview as a separate view and switch to it when necessary. We could even include this code in the production version of the application, hidden from the user until it is needed to diagnose a problem.
Log files
Some code is harder to debug, either by its very nature (e.g. multi-threaded programs) or because they close unexpectedly with no chance for exception handling. In such cases, log files can provide essential 'post-mortem' debugging information.
A log class is suggested in the next code snippet (just save it to a file called logfile.py):
# logfile.py
#
import sys
import time
import thread
import os
__all__ = [ "FLOG" ]
log_path = "C:\\Logs\\"
class FileLog(object):
def __init__(self,filename):
if (os.path.exists(log_path)):
# Log enabled
self.log = True
self.filename = log_path+filename
print filename
self.file = open(self.filename,'at')
self.lock = thread.allocate_lock()
else:
appuifw.note(u' Log diabled')
# Log diabled
self.log = False
def add(self,msg):
if (self.log):
# 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()
else:
pass
def close(self):
if (self.log):
self.file.close()
else:
pass
FLOG = FileLog("filelog.txt")
The logfile module is written in such a manner that existence of the path C:\Logs serves as a flag for enabling or disabling the logging. You may, of course, change this path according to your needs.
To use the logfile module, simply import the object FLOG and log messages with FLOG.add(msg):
import sys
# Append the path where you placed 'logfile.py'
sys.path.append(u"C:\\data\\python")
# import FLOG from the logfile module
from logfile import FLOG
# You can log to the file by using the 'add' function
FLOG.add("Log initialized")
# Instructions and your code go here
FLOG.add("Debug 1")
# More instructions and code
FLOG.add("Debug 2")
# Closing the log file
FLOG.close()
Caller information is automatically added to the log using the object sys._getframe, and a semaphore (created using thread.allocate_lock) is provided to avoid re-entrancy problems when writing to the file. After writing, the file is flushed and any pending bytes are written, so you can trust that a message is written to the file after the call to FLOG.add().
You may close the file by calling FLOG.close() when you have finished logging to the file.
Runtime error detection
Using the script shell is practical for debugging during development, but is not suitable for detecting runtime errors in standalone applications (which can be created as described in Chapter 16). It is not uncommon when first deploying a program as a standalone application to have errors related to missing modules, or use of the wrong paths in sys.path, and so on. In these situations, we use exception handling to detect and log runtime errors; we can then provide a comprehensive debug message to the user, and even notify ourselves of the problem automatically.
Tracebacks can be used extensively to extract, format and print stack traces of Python scripts. For example, the following code would print the traceback.
import traceback, appuifw, sys
def dangerous_function():
appuifw.non_existing_function()
try:
# something that would throw an exception
dangerous_function()
except:
# catch exception
print "Exception in user code:"
print '-'*30
traceback.print_exc()
print '-'*30
Different ways to print and format the stack traces can be found in the Python documentation library (http://docs.python.org/library/traceback.html).
Using a traceback module, the following code example shows how we can collect mobile device and exception data after a failure, and then create a new user interface to inform the user of how to report the problem:
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 (invalid) code snippet:
#...
try:
import camera
camera.take_photo(camera.RGB32) # error: invalid mode
except Exception, e:
#...
A screenshot of the error message that would be presented is shown in Figure 17.1:
Debugging defects on end-user phones
However careful you are in your software development process, it is still possible (even likely) that defects will escape into production code. Furthermore, with so many Symbian devices running in so many different locales, it is possible that issues will occur on devices you can't get access to. Getting your application's runtime debug information from a customer's mobile device is likely to be more useful for debugging than any amount of description they can provide, and can save a lot of time because they've already reproduced the problem for you!
The previous section showed how we might catch an exception and display a message asking the user to email it to us. The following goes one step further, modifying the previous code fragment to demonstrate how you might SMS a debug message to yourself.
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"
#Replace MyProgram with your app's name
err_msg = u"MyProgram 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()
Figure 17.2 shows the screenshot received:
SMS is just one alternative to sending a debug message. Another approach might be to send the debug message using MMS, or using an Internet connection. The techniques in Chapter 14 could even be used to post defect information directly into your web-based defect management system.
Working on Windows using an emulator
Normally, you'll test your applications on a real Symbian device, either in the Python shell or as a stand-alone application. However, if you don't have a device, you can instead test your scripts within the Python shell console on an emulator (a simulation of the Symbian platform running on top of the Windows operating system). Instructions on how to set up the emulator for Python development are given in Chapter 1.
When the emulator is running, locate and run your Python script shell. You can debug it, as shown in Figure 17.3.
If you want to debug a script, copy it into the following directory and select Run script from the Python shell menu.
<SDK Installation directory>\epoc32\winscw\c\Data\python
The other debugging techniques explained in the chapter can all be used when debugging on the emulator.
Summary
This chapter covered several techniques for debugging scripts and applications, including printing through the script shell, using a logging view, implementing robust file logging and using a crash log. The techniques described can be used for debugging both simple and complex scripts, and can help you debug and fix problems quickly and efficiently.
Comments
Contents |
Hamishwillee said…
Croozeus said…
Hi Hamish,
Thanks for the review.
My comments,
1. PyS60 doesn't have a dedicated IDE that supports SDK or on device debugging.
So, I mentioned this limitation in a paragraph, as you suggested, in the "Wrtiting better Code" section.
On a side note, there were plans fot this earlier - carbide.c++ support for PyS60 before 2 years on the roadmap, but don't know where it got lost!
2. Elaborated about tracebacks in the 'Runtime error detection' section - where it is used further to demonstrate its use in formatting exceptions.
Added flag for Debug in "Python Shell section".
3. Profilers - cProfile and profile are supported http://docs.python.org/library/profile.html - don't know where to mention them.
- Introduction
The introduction looks fine to me. Can't mention much since no IDE support for on device debugging.
Basically, my idea of having this small chapter - was identifying and listing such techniques separately rather than listing them scattered in all the
chapters- exception handling, printing, logging.
- Writing better code - The chapter didn't mean to have this section, but an addition is always good. We are keeping this as bullets - I think we should not further elaborate on
this section.
- Python shell - I added a small example for having a flag to switch between - debug and normal mode - in the print example.
Right - I removed the section of sys.path.append - did a search - it isn't mentioned anywhere in the book. We need to mention it somewhere - checking...
- Python shell over bluetooth and wifi - Just another way of printing. Marcelo to address if anything specific.
- Logging view - This looks ok. To enable selective switching, the user has to manually comment it. For example, if he is using this view in tabs, he needs
remove that code.
- Log file - I enhanced the code similar to the flogger API in Symbian C++. The path (C:\Logs) folder serves as a flag for the logs to be enabled or not.
Other running code is unaffected. Also, described this. Please check if its ok.
- Runtime error detection - changed text as you suggested, added explaination for tracebacks.
- Debugging defects on end-user phones - changed to "MyProgram"
- Symbian platform Windows-hosted emulator - changed link to the Introduction chapter rather than Quick start
- Python on Symbian/10. Location Based Services#Debug logging - we need to reword slightly here - as the Logfile can be disabled if the path is not present.
Will add a summary, need to take a break!
--Croozeus 11:24, 6 June 2010 (BST)
Croozeus said…
Hamishwillee said…
Hi
Looks good - I like the changes you've made; relatively small changes but they fix my issues.
I've tidied up the introduction and moved in the "no IDE" text (actually the issue is no interactive source code debugger, since you can have a debugger without an IDE). This is the right place to say this stuff, because its core to why all our techniques are some form of logging.
Can you make the log conditional on C:/Logs/Python/ or C:/Logs/MyApp rather than C:/Logs? C:/Logs will exist in most cases - usually something like TCPIP is enabled based on the existence of its logging specific folder, not the base folder.
Is the log file going to be provided as an example for download/easy inclusion. Are there any other examples to accompany this?
I tidied up the summary.
I added Profiler section (empty) as a level 3 heading under debugging techniques. Please fill in IF these profilers are known to work on Symbian code. This could equally well have gone as a level 2 heading just before Summary - since profiling is a different than debugging. I leave to your discretion.
"Writing better code" is full of links. Will be interesting to see what Satu does with this. If it becomes too choppy then should move it below the debugging techniques and reorder the introduction.
Regards Hamish
--Hamishwillee 02:37, 7 June 2010 (BST)
Croozeus said…
Hi Hamish,
Had to remove code profiling, since my tests didn't give favorable results. One cannot profile code on the PC, since the PyS60 modules are not present in Python for PC.
Regards, Pankaj.
--Croozeus 18:47, 22 June 2010 (BST)
Hamishwillee said…
Hi Pankaj
Fair enough. I thought about modifying the introduction to say this, but I think we'll leave as is. Good job.
Cheerio H
--Hamishwillee 01:09, 23 June 2010 (BST)
Sign in to comment…





Hi Marcelo
1. It seems to me that all the strategies here are logging - using print through a shell (whether that be in the device, emulator, PC via Bluetooth or Wifi) - using a dedicated logging window you create - using a log file - using a crash log
The big question here is whether there is any support for using and IDE, and whether you can use a debugger. Can you step through your code?
If so, we should have a section on IDEs and on-target debugging. If not, we should have at least some comment/section on the limitations of debugging with respect to PC based Python development
2. Are there any other defensive programming techniquest. In C++ we use assertions and invariants - should mention a bit about traceback functionality and anything else Python gives you for free - Should probably show how to selectively enable debugging/logging, or make a statement that its unlikely to effect running code. ie in C++ we'd use a #define to comment it out of running code - anything similar. - are there timing issues with prints? ie in some C++ code you can get caching, which means that logging isn't actually necessarily accurate, particularly in multithreaded code
3. Are there any profilers for Python.
4. Further comments:
review complete
Regards Hamish
--Hamishwillee 06:00, 26 March 2010 (UTC)