def _test_run(state):
    '''Execute a single test suite.

    Args:
        state: test suite state collection, instance of TestState.

    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

    test_failures = state.test.run(state.lldb, state.pid, state.lldb_module)

    if test_failures:
        log = util_log.get_logger()
        for test, err in test_failures:
            log.error('test %s:%s failed: %r' % (state.name, test, err))

        return False

    return True
def _test_pre_run(state):
    '''This function is called before a test is executed (setup).

    Args:
        state: Test suite state collection, instance of TestState.

    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
    # First try the legacy behaviour
    try:
        target_name = state.test.get_bundle_target()
        warnings.warn(
            "get_bundle_target() is deprecated and will be removed soon"
            " - use the `bundle_target` dictionary attribute instead")
    except AttributeError:
        try:
            target_name = state.test.bundle_target[state.bundle_type]
        except KeyError:
            raise TestIgnoredException()

    if target_name is None:
        # 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 _get_test_case_class(module):
    '''Inspect a test case module and return the test case class.

    Args:
        module: A loaded test case module.
    '''
    # We consider only subclasses of TestCase that have `test_` methods`
    log = util_log.get_logger()
    log.debug("loading test suites from %r", module)
    for name, klass in inspect.getmembers(module, inspect.isclass):
        for attr in dir(klass):
            if attr.startswith('test_'):
                log.info("Found test class %r", name)
                return klass
        else:
            log.debug("class %r has no test_ methods", name)
    return None
def _execute_test(state):
    '''Execute a test suite.

    Args:
        state: The current TestState object.
    '''
    log = util_log.get_logger()

    state.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.teardown(state.android)
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

    # parse the command line (positional arguments only)
    truthy = lambda x: x.lower() in ('true', '1')
    parser = argparse.ArgumentParser(
        "Run a single RenderScript TestSuite against lldb")
    for name, formatter in (
        ('test_name', str),
        ('log_file_path', str),
        ('adb_path', str),
        ('lldb_server_path_device', str),
        ('aosp_product_path', str),
        ('device_port', int),
        ('device', str),
        ('print_to_stdout', truthy),
        ('verbose', truthy),
        ('wimpy', truthy),
        ('timeout', int),
        ('bundle_type', str),
    ):
        parser.add_argument(name, type=formatter)

    args = parser.parse_args()

    try:
        # create utility classes
        harness.util_log.initialise(
            '%s(%s)' % (args.test_name, args.bundle_type),
            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))

        # inspect the test module and locate our test case class
        test_class = _get_test_case_class(test_module)

        # 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,
                               args.bundle_type,
                               wimpy=args.wimpy)

        # 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 = TestState(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,
                                      bundle_type=args.bundle_type)

                    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 TestIgnoredException:
        if log:
            log.warn("test ignored")
        _quit_test(util_constants.RC_TEST_IGNORED, timer)

    except TestSuiteException as error:
        if log:
            log.exception(str(error))
        else:
            print(error, file=sys.stderr)
        _quit_test(util_constants.RC_TEST_FAIL, 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()