Testing, debugging and profiling

Interactive test

The Brythons site, or its mirror available for download, include a console where you can test Python code

Please note that the namespace is not refreshed when you click on "run", you must reload the page for that

For debugging and testing Brython, a number of test scripts are grouped in the directory tests ; you can access them by clicking the link "Test pages" in the console, then select the different tests and run them

Debugging scripts

Whatever the debugging level, syntax errors are reported in the browser console (or at the place defined by sys.stderr)

For instance, the code

x = $a

generates the message

SyntaxError: unknown token [$]
module '__main__' line 1
x = $a
^

By setting the debugging level to 1 in the call to function brython(debug_mode), the exceptions raised at runtime and not caught by an except also produce an error message, as close as possible to the one generated by Python3

This code :

x = [1,2]
x[3]

generates :

IndexError: list index out of range
module '__main__' line 2
x[3]

Debugging Javascript Generated Python Code

TL;DR if you want to use the browser builtin debugger to step through your python Javascript code write __debugger__ in your code and open the developer tools.

This statement is equivalent to the Javascript debugger statement.

Modern browsers such as FireFox and Google Chrome have built in debuggers, these debuggers allow developers to step through the code stepping into function calls and out (you know like debuggers in IDEs)

It is possible to debug javascript code by placing breakpoints on the line numbers inside the script tab in the developer tools.

However Brython generated javascript is generated during runtime and thus does not appear in a file, fortunately, browsers have added a special keyword to the javascript language called debugger this statement manually inserts a breakpoint into the script so that in runtime if the developer tools are open it will halt execution and start a debugging session there.

We have added to the Brython interpreter the keyword __debugger__ which will be translated by the tokenizer to debugger thus triggering the same process.

To try it out now head over to the editor, type __debugger__ in your code, open the developer tools (in chrome right-click inspect element), then click run.

to learn more about the chrome developer tools visit their documentation or this short course by code school.

Debugging Python Code

As of this writing it is not full featured and supports only line step.
You will find documentation on how each function in the debugger works (in case you want to build on it)

Currently only python language specific programs are supported.

The debugger does not fully support the input statements; only supporting input with a string literal for argument (more on this below).

rython_Debugger For Developers

The debugger provides 4 hooks (on_debugging_started, on_step_update, on_debugging_end, and on_debugging_error) which take a callback that you can decide to do whatever you want with.

The way the debugger works in record mode (default) when you run start_debugger is by parsing the python code into brython generated js and then injecting a trace function before each $line_info occurrence (which requires running brython in debug mode higher than 0).

Additional trace calls; are injected at the start of the code before any line, for pointing at the first line; after while loops and at the end of the program, for pointing at the correct lines when debugging in an editor.

Since the debugger is not run live but recorded the parser replaces each call to the brython input function with a trace of type input with the arguments that were meant to be passed to the input function (currently only support string literals).

After injecting trace is complete the debugger runs the code which then fires the trace calls while running.

Each line trace call gets a state object as parameter with the current top frame and line number and records it. Before doing so the previous state's next line number is updated with the current state's line number; as while stepping in the editor the next line not the current line is what gets highlighted.

If the line trace is of type afterwhile or eof then it's state is not recorded.

If an input trace is called then a line state trace of type input is added and the debugger halts code execution, starting the debugging session.

When the line trace of type input is stepped on the user is prompted for input based on Brython's defined input function, the result is recorded and the program gets re-executed.

If there was no input trace then the debugging session will start after the parsed code is executed normally.

This debugger is still under development and changes will occur to the API

The debugger is available in the global scope from the window object under Brython_Debugger.

If you want to add additional trace points call the setTrace function provided by the API inside your own function (currently must be globally accessible)

The following is the debugger public API you can find more details description in the code at www/tests/debugger/main.js

Brython_Debugger.start_debugger()

start the debugging session, takes code to debug as parameter as well as an optional boolean flag for whether to live debug or record. Currently live debug is not supported and debugging by default starts in record mode. The on_debugging_started callback is called at the end of this step

Brython_Debugger.stop_debugger()

function to call when you want to stop the debugging session on_debugging_end is called at this step

Brython_Debugger.step_debugger()

This function when called steps forward one step in the recorded debugging session

Brython_Debugger.step_back_debugger()

This function when called steps backward one step in the recorded debugging session

Brython_Debugger.can_step(n)

check if you can step to the specified step

Brython_Debugger.set_step(n)

seek to a specific step in the recorded debugging session take a number from 0 to the last step as parameter. If a number larger than the last step is entered nothing will happen

Brython_Debugger.is_debugging()

return whether a debugging session is active

Brython_Debugger.is_recorded()

returns whether this debugger is in recording mode

Brython_Debugger.is_last_step()

returns whether the current step is the last step

Brython_Debugger.is_first_step()

returns whether the current step is the first step

Brython_Debugger.get_current_step()

return a number indicating the current step

Brython_Debugger.get_current_frame()

returns the current frame/state (it should be state)

Brython_Debugger.get_recorded_frames()

returns all recorded states

Brython_Debugger.set_trace_limit(Number)

The maximum number of steps executed before the debugger halts, default 10000

Brython_Debugger.set_trace(obj)

object should contain the data you want paced later to the set_trace function

do not use event names already used by the debugger
add a trace call, (which will be called on step update)

Brython_Debugger.set_trace_call(string)

Change the name of the traceCall Function that is injected in the brython generated javascript, used to record state, the default is Brython_Debugger.set_trace. To change it you would still need to call this function, so be careful and generally you don't need to.

Brython_Debugger.on_debugging_started(cb)

cb is called after debugging session has started

Brython_Debugger.on_debugging_end(cb)

cb is called after debugging session has ended

Brython_Debugger.on_debugging_error(cb)

cb is called after either a syntax or runtime error occurs

Brython_Debugger.on_step_update(cb)

cb is called whenever a state is changed using setState

Profiling scripts

To enable profiling one has to pass the "profile" option to the brython function:

brython({'profile':1})

When the profile option is > 0 the compiler adds additional code to the generated
javascript which collects profiling information. To profile module provides access
to this information. It strives to provide an interface largely similar to the profile
module from the standard python distribution.

The notable difference is that it does not allow user-defined timers and does not
do any calibration. Methods which in the standard module save the data to a file
save a JSON-serialized version of the data to the browser's local storage instead.

where each line corresponds to a function and the different columns correspond to

ncalls is the total number number of times the function was called
(if the function was called non-recursively, the second number
behind the backslash indicates how many calls were top-level calls
in the recursion)

tottime is the total time (in seconds) spent in the function not including subcalls

percall is the average time spent in the function per call, not including subcalls

cumtime is the total time spent in function including subcalls

var percall is the average time spent in function per one non-recursive call

standard name is the name of the function in the form module.function_name:line_number

Optionally one can also use the following form, taking advantage of running the code
several times and averaging it out: