Symbian developer community

 
wiki

Python on Symbian/17. Debugging Techniques

From Symbian Developer Community

Jump to: navigation, search

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.

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:

  1. Configure a COM port on your PC.
  2. Use an application like Hyperterminal or Putty to connect to this port.
  3. 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:

Figure 17.1:Message displayed to user in the event of an error.

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:

Figure 17.2: Screenshot of a debug SMS sent by an application

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.

Figure 17.3: Python interactive console

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…

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.

  • Can someone reasonably develop/profile on PC using debugger and then migrate code to PyS60


4. Further comments:

  • Branding
    • Tried to remove mention of PyS60 where possible, and refer to Python on Symbian. PyS60 has not gone away, but S60 itself is deprecated branding.


  • Introduction
    • Added pre introductory line. Please check
    • What is special about Python debugging? If its more limited than on PC this needs to be said (ie looks like mostly its just logging).
    • I have taken first shot, but may need to be extended based on your response to "1." above.
    • Where did the statistic come from on how much time spent debugging? I've watered this statement down a bit because numbers in this area are arguable.
  • Writing better code
    • Any other tools that should be mentioned? Debuggers, IDEs, Profilers, UML modelers, etc?, Test frameworks for Python to support UI and unit testing?
    • I modified the introduction to the tools slightly
    • Provide links for CVS, Subversion, Git. Can you link to comparison tables?
    • Add Bugzilla to list of trackers
    • Depending on your answer to "2" we should perhaps split this into sub headings instead of bullet. Then we could provide a few examples. ie, I'd quite like to see and example of using Python to document code, but wouldn't bother when using the current structure. However if we do have more to say we could split this into 3 sections - "Defensive programming", "Tools", "Methodologies" or similar
  • Python shell
    • Improved English, please check
    • I think we should remove "If you are importing modules from your code, put these modules under e:\Python\lib or add their path to sys.path..." and link to a section where we explain how to add modules into your system path - this would be in another chapter - possibly Python elements. Any ideas where?
      • If you think this is important here we should keep it as a note of some kind, linking to the right area in other chapter.
      • Can we ever see the console in a normal application - ie could we make our main view invisible in order to see the console - then we could use this in the shell, or even better, without having to use the shell at all.
    • Moved out the section on using a Text object into its own topic "Logging view" - you wouldn't need to use this in the shell. Ditto for the audio logging.
  • Python shell over bluetooth
    • I made this a sub heading of Python shell (figure its part of the same thing)
    • As I see it the benefit is that you can see your prints appearing at time they occur, rather than at end using normal console.
      • Is this correct?
      • Is there anything else special you can do? ie can you step through your code? Can you send commands while your script is running?
  • Python shell over wifi
    • I made this a sub heading of Python shell (figure its part of the same thing)
    • Any major differences if "value" of this method over using Bluetooth - ie can you do anything different
    • When you run the script to set up the connection on the phone, can I presume that this remains "live" when you then load your application script?
    • Do you need anything else, or do you just type instructions into the netcat utility and this is then the same as if you were on the console?
  • Logging view
    • Can you check this
    • The example is a bit simplistic. Do you think we could have one which has the log view as a true separate view that the user switches to? For bonus points, could this only be visible in a "release" build, or only visible if somehow specially enabled (key combination?). Point is to allow it to be selectively enabled in build, which is what you'd want to do in a real app.
  • Log file
    • Should the file log to C drive rather than E? Or even better check for the existence of the the log drive?
    • I slightly restructured.
  • Enhancing runtime error detection
    • Renamed to "Runtime error detection"
    • In several places (and screenshots) we say "This programs was unexpectedly" - should be "This program", or even better: "PROGRAMNAME was .."
  • Debugging defects on end-user phones
    • I've split this out into another section explaining that there is benefit in runtime testing/getting information out of production devices.
    • I added a line at the end stating that you could alternatively use the Internet, and perhaps even directly post defects into your defect system. (linking to the advanced chapter you wrote).
    • This should also add that one problem of this approach is that SMS message size is limited and might be quite expensive, however benefit is that most users will have SMS, but not all will have a data plan.
      • Can you do this?
    • If you're sending debug information you should probably change the example and screenshot to name the application rather than just refer to it as "This program". Because in this case you might have several applications under development.
  • Symbian platform Windows-hosted emulator
    • renamed from Nokia Emulator
    • As I understand it, the only thing this option gives us is the ability to test scripts on a PC right? I've said so because it wasn't clear why you would bother
    • I've pointed to the Python Quick Start for setup information rather than providing instructions here. There will keep up to date faster.
    • I've simplified the instructions
  • Summary
    • Needs one - not worth writing until 1et al above is answered. Can you take first shot at this?

review complete

Regards Hamish

--Hamishwillee 06:00, 26 March 2010 (UTC)

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…

Added a brief summary.

Cheers, Pankaj.

--Croozeus 11:48, 6 June 2010 (BST)

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…