blob: b9e214547d45f1f7bcfd106fbe4d944f1d5946b3 [file] [log] [blame]
#!/usr/bin/env python2.7
'''This script will run one specific test.'''
from __future__ import print_function
import sys
import atexit
import inspect
import logging
import os
import harness
from harness import util_constants
from harness import util_log
from harness import util_warnings
from harness.util_functions import load_py_module
from harness.util_lldb import UtilLLDB
from harness.exception import DisconnectedException
from harness.exception import TestSuiteException
from harness.util_timer import Timer
def _test_pre_run(state):
'''This function is called before a test is executed (setup).
Args:
state: Test suite state collection, instance of State.
Returns:
True if the pre_run step completed without error. Currently the pre-run
will launch the target test binary on the device and attach an
lldb-server to it in platform mode.
Raises:
AssertionError: If an assertion fails.
TestSuiteException: Previous processes of this apk required for this
test could not be killed.
'''
assert state.test
assert state.bundle
log = util_log.get_logger()
log.info('running: {0}'.format(state.name))
# Remove any cached NDK scripts between tests
state.bundle.delete_ndk_cache()
# query our test case for the remote target app it needs
target_name = state.test.get_bundle_target()
if not target_name:
# test case doesn't require a remote process to debug
return True
else:
# find the pid of our remote test process
state.pid = state.bundle.launch(target_name)
if not state.pid:
log.error('unable to get pid of target')
return False
state.android.kill_servers()
# spawn lldb platform on the target device
state.android.launch_lldb_platform(state.device_port)
return True
def _test_post_run(state):
'''This function is called after a test is executed (cleanup).
Args:
state: Test suite state collection, instance of State.
Raises:
AssertionError: If an assertion fails.
'''
assert state.test
assert state.bundle
target_name = state.test.get_bundle_target()
if target_name:
if state.bundle.is_apk(target_name):
state.android.stop_app(state.bundle.get_package(target_name))
else:
state.android.kill_process(target_name)
def _test_run(state):
'''Execute a single test case.
Args:
state: test suite state collection, instance of State.
Returns:
True: if the test case ran successfully and passed.
False: if the test case failed or suffered an error.
Raises:
AssertionError: If an assertion fails.
'''
assert state.lldb
assert state.lldb_module
assert state.test
if not state.test.run(state.lldb, state.pid, state.lldb_module,
state.wimpy):
util_log.get_logger().error('test {0} failed'.format(state.name))
return False
return True
def _initialise_timer(android, interval):
'''Start a 'timeout' timer, to catch stalled execution.
This function will start a timer that will act as a timeout killing this
test session if a test becomes un-responsive.
Args:
android: current instance of harness.UtilAndroid
interval: the interval for the timeout, in seconds
Returns:
The instance of the Timer class that was created.
'''
def on_timeout():
'''This is a callback function that will fire if a test takes longer
then a threshold time to complete.'''
# Clean up the android properties
android.reset_all_props()
# pylint: disable=protected-access
sys.stdout.flush()
# hard exit to force kill all threads that may block our exit
os._exit(util_constants.RC_TEST_TIMEOUT)
timer = Timer(interval, on_timeout)
timer.start()
atexit.register(Timer.stop, timer)
return timer
def _quit_test(num, timer):
'''This function will exit making sure the timeout thread is killed.
Args:
num: An integer specifying the exit status, 0 meaning "successful
termination".
timer: The current Timer instance.
'''
if timer:
timer.stop()
sys.stdout.flush()
sys.exit(num)
def _execute_test(state):
'''Execute a test case.
Args:
state: The current State object.
log: The current Log object.
'''
log = util_log.get_logger()
state.test.test_setup(state.android)
try:
if not _test_pre_run(state):
raise TestSuiteException('test_pre_run() failed')
if not _test_run(state):
raise TestSuiteException('test_run() failed')
_test_post_run(state)
log.info('Test passed')
finally:
state.test.post_run()
state.test.test_shutdown(state.android)
def _get_test_case_class(module):
'''Inspect a test case module and return the test case class.
Args:
module: A loaded test case module.
'''
for _, obj in inspect.getmembers(module):
if inspect.isclass(obj):
if (hasattr(module, "TestBase") and
issubclass(obj, module.TestBase) and
obj is not module.TestBase):
return obj
if (hasattr(module, "TestBaseRemote") and
issubclass(obj, module.TestBaseRemote) and
obj is not module.TestBaseRemote):
return obj
return None
def get_test_dir(test_name):
''' Get the directory that contains a test with a given name.
Returns:
A string that is the directory containing the test.
Raises:
TestSuiteException: If a test with this name does not exist.
'''
tests_dir = os.path.dirname(os.path.realpath(__file__))
for sub_dir in os.listdir(tests_dir):
current_test_dir = os.path.join(tests_dir, sub_dir)
if (os.path.isdir(current_test_dir) and
test_name in os.listdir(current_test_dir)):
return current_test_dir
raise TestSuiteException(
'unable to find test: {0}'.format(test_name))
def main():
'''Test runner entry point.'''
# re-open stdout with no buffering
sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
android = None
timer = None
log = None
try:
# parse the command line
if len(sys.argv) < 12:
raise TestSuiteException('Invalid number of arguments')
assert sys.argv[8] in ('True', 'False')
args = dict(test_name=sys.argv[1],
log_file_path=sys.argv[2],
adb_path=sys.argv[3],
lldb_server_path_device=sys.argv[4],
aosp_product_path=sys.argv[5],
device_port=int(sys.argv[6]),
device=sys.argv[7],
print_to_stdout=sys.argv[8] == 'True',
verbose=sys.argv[9] == 'True',
wimpy=sys.argv[10] == 'True',
timeout=int(sys.argv[11]))
# create utility classes
harness.util_log.initialise(args['test_name'],
print_to_stdout=args['print_to_stdout'],
level=logging.INFO if not args['verbose'] else logging.DEBUG,
file_path=args['log_file_path'],
file_mode='a'
)
log = util_log.get_logger()
log.debug('Logger initialised')
android = harness.UtilAndroid(args['adb_path'],
args['lldb_server_path_device'],
args['device'])
# start the timeout counter
timer = _initialise_timer(android, args['timeout'])
# startup lldb and register teardown handler
atexit.register(UtilLLDB.stop)
UtilLLDB.start()
current_test_dir = get_test_dir(args['test_name'])
# load a test case module
test_module = load_py_module(os.path.join(current_test_dir,
args['test_name']))
assert test_module
assert (hasattr(test_module, "TestBase") or
hasattr(test_module, "TestBaseRemote"))
# inspect the test module and locate our test case class
test_class = _get_test_case_class(test_module)
assert test_class
# if our test inherits from TestBaseRemote, check we have a valid device
if (hasattr(test_module, "TestBaseRemote") and
issubclass(test_class, test_module.TestBaseRemote)):
android.validate_device()
# create an instance of our test case
test_inst = test_class(args['device_port'], args['device'], timer)
assert test_inst
# instantiate a test target bundle
bundle = harness.UtilBundle(android, args['aosp_product_path'])
# execute the test case
try:
for _ in range(2):
try:
# create an lldb instance
lldb = UtilLLDB.create_debugger()
# create state object to encapsulate instances
state = type('State',
(object,),
dict(
android=android,
bundle=bundle,
lldb=lldb,
lldb_module=UtilLLDB.get_module(),
test=test_inst,
pid=None,
name=args['test_name'],
device_port=args['device_port'],
wimpy=args['wimpy'])
)
util_warnings.redirect_warnings()
_execute_test(state)
# tear down the lldb instance
UtilLLDB.destroy_debugger(lldb)
break
except DisconnectedException as error:
log.warning(error)
log.warning('Trying again.')
else:
log.fatal('Not trying again, maximum retries exceeded.')
raise TestSuiteException('Lost connection to lldb-server')
finally:
util_warnings.restore_warnings()
_quit_test(util_constants.RC_TEST_OK, timer)
except AssertionError:
if log:
log.critical('Internal test suite error', exc_info=1)
print('Internal test suite error', file=sys.stderr)
_quit_test(util_constants.RC_TEST_FATAL, timer)
except TestSuiteException as error:
if log:
log.exception(str(error))
else:
print(str(error), file=sys.stderr)
_quit_test(util_constants.RC_TEST_ERROR, timer)
# use a global exception handler to be sure that we will
# exit safely and correctly
except Exception:
if log:
log.exception('INTERNAL ERROR')
else:
import traceback
print('Exception {0}'.format(traceback.format_exc()),
file=sys.stderr)
_quit_test(util_constants.RC_TEST_FATAL, timer)
finally:
if android:
android.reset_all_props()
if timer:
timer.stop()
# execution trampoline
if __name__ == '__main__':
main()