Writing clean scripts
Contents
A recurring issue when developing scripts using ANDi is how to ensure that after the scripts are executed or stopped, all their references have to be managed and cleaned out correctly.
Unmanaged references can cause incremental executions of scripts to yield wrong or duplicate results (example: event function callback executing more than once and with old implementation).
Note
- Unmanaged references in a script can refer to:
Events callback functions not removed after script execution (-= operator not used).
Unhandled exception in script ending script prematurely and preventing script clean up code block to be executed.
Inaccessible objects not cleaned in scripts.
ANDi will attempt to cleanup all object referenced by variables (at the end of the script execution, ANDi loops all global variables).
If for some reason the object is not accessible (wrapped inside a python class, not referenced by python code or no longer referenced…) then ANDi won’t be able to clean it.
So it is important to write clean scripts to avoid these issues.
Dereferencing event callback functions
Events are a powerful tool to execute a specific code when a certain event occurs, this is done through adding an event callback function to the event (using += operator to the event).
However, when an event function callback is not dereferenced when no longer needed, it will always keep executing with the event (because the event itself keeps referencing this function).
Therefor a script must always dereference the callback functions that it adds to events to ensure that scripts execute normally (using -= operator to the event).
How to use
# adding callback function to event
obj.event_name += callback_function
# other code to execute
# Dereferencing callback function from event
obj.event_name -= callback_function
Important
There is a possibility that code execution will stop before dereferencing a callback function either because the code executed before it raises an exception or because script was stopped.
So dereferencing callback functions needs to be done in a code block that will always execute (using try/except/finally or atexit library).
Using try/except/finally blocks
An exception is an error that happens during execution of a program.
When that error occurs, an exception will be generated that can be handled.
This is done using try/except/finally python blocks.
How to use
try:
# code to execute
except:
# code to execute in case an exception is thrown
finally:
# code that will always execute after the try/except block
# this is where cleaning code happens
Note
It is possible to use except with specific exception type (for example except ValueError).
More about try/except/finally can be found in python exceptions doc.
Sample script
from time import sleep
def on_elapsed(source, event_args):
# execute timer related code here
print(event_args.time)
l_timer = andi.create_timer()
try:
# do some code
l_timer.on_time_elapsed += on_elapsed
l_timer.start()
sleep(5)
print("this will print if code executes normally")
except:
# in case of exception do this
print("this will print if an exception is caught")
# rethrow exception to be caught and displayed/handled by ANDi
raise
finally:
# this will always run after the try catch function
# even when a code is stopped by the user
# so cleaning up the code should be done here
print("this will always run after try/except block even if script is stopped")
l_timer.stop()
l_timer.on_time_elapsed -= on_elapsed
Using atexit library
atexit
module defines functions to register and unregister cleanup functions, these functions are automatically executed upon normal script termination.atexit
will run these functions in the reverse ordre in which they were registered.How to use
import atexit
def function_to_register(first, second):
# clean up code to execute
atexit.register(function_to_register, 'first_value', 'second_value')
# or:
atexit.register(function_to_register, first='first_value', second='second_value')
Note
It is possible to use
@atexit.register
decorator for functions that have no arguments.More about atexit library can be found in python atexit doc.
Sample script using decorator
from time import sleep
import atexit
def on_elapsed(source, event_args):
# execute timer related code here
print(event_args.time)
@atexit.register
def clean_up():
global l_timer
l_timer.on_time_elapsed -= on_elapsed
l_timer.stop()
# clean up function is already registered with the decorator
# keep in mind the decorator only works with parameter-less functions
l_timer = andi.create_timer()
l_timer.on_time_elapsed += on_elapsed
l_timer.start()
sleep(5)
Sample script using register function
from time import sleep
import atexit
def on_elapsed(source, event_args):
# execute timer related code here
print(event_args.time)
def clean_up():
global l_timer
l_timer.on_time_elapsed -= on_elapsed
l_timer.stop()
def clean_up_args(local_timer):
local_timer.on_time_elapsed -= on_elapsed
local_timer.stop()
l_timer = andi.create_timer()
l_timer_2 = andi.create_timer()
# register clean up functions
# register function with argument
atexit.register(clean_up_args, l_timer_2)
# register function without argument
atexit.register(clean_up)
# code to run
l_timer.on_time_elapsed += on_elapsed
l_timer_2.on_time_elapsed += on_elapsed
l_timer.start()
l_timer_2.start()
sleep(5)
Using tc_return_foo methods
tc_return_success()
: informs about the success of the test case.tc_return_failure()
: informs about the failure of the test case.tc_wait_for_return(optional_timeout)
: blocks execution until a tc_return_
function is called or the timeout, if mentioned, is elapsed.tc_return_continue()
: unblocks tc_wait_for_return
without setting any status.tc_return_skipped()
: marks the script as skipped. To mark the script as skipped and stop it immediately, use current_script.skip()
.Sample script
import random
RANDOM_NB = 0
# Create SOME/IP message
msg_someip = message_builder.create_someip_message("channel_1" , "channel_1")
# Callback function, invoked on each incoming SOME/IP message
def on_message_received(msg):
global RANDOM_NB
# A random number (takes 1 or 2) to test both results *Success* and *Failure*
RANDOM_NB = random.randint(1, 2)
# Unblock the execution caused by tc_wait_for_return
tc_return_continue()
# Register callback function
msg_someip.on_message_received += on_message_received
# Start live capturing for SOME/IP message
msg_someip.start_capture()
# Send one message
msg_someip.send()
# Block the test case execution until the message is received
# since on_message_received function calls tc_return_continue()
tc_wait_for_return()
## test result
print("-- RESULT --")
if RANDOM_NB == 1:
# Inform about the success of the test case.
tc_return_success('** Success **')
else:
# Inform about the failure of the test case.
tc_return_failure('** Failure **')
# De-register callback function
msg_someip.on_message_received -= on_message_received
# Stop capturing
msg_someip.stop_capture()