def main():
    '''The lldb-renderscript test suite entry point.'''
    log = None

    try:
        # parse the command line
        state = State()
        assert state

        # logging is initialised in State()
        log = util_log.get_logger()

        # if we can, set PYTHONPATH for lldb bindings
        if not _deduce_python_path(state):
            log.log_and_print('Unable to deduce PYTHONPATH', logging.WARN)

        # pre run step
        if not _suite_pre_run(state):
            raise TestSuiteException('Test suite pre-run step failed')
        # discover all tests and execute them
        tests = _discover_tests(state)
        log.log_and_print('Found {0} tests'.format(len(tests)))
        if state.install_only:
            log.log_and_print(
                'Test applications installed. Terminating due to '
                '--install-only option')
        else:
            # run the tests
            for bundle_type in state.bundle_types:
                log.info("Running bundle type '%s'", bundle_type)
                for item in tests:
                    _run_test(state, item, bundle_type)
                # post run step
            quit(0 if _suite_post_run(state) == 0 else 1)

    except AssertionError:
        if log:
            log.exception('Internal test suite error')

        print('Internal test suite error')
        quit(1)

    except FailFastException:
        log.exception('Early exit after first test failure')
        quit(1)

    except TestSuiteException as error:
        if log:
            log.exception('Test suite exception')

        print('{0}'.format(str(error)))
        quit(2)

    finally:
        _kill_emulator()
        logging.shutdown()
def _kill_emulator():
    ''' Kill the emulator process. '''
    global EMU_PROC
    if EMU_PROC:
        try:
            EMU_PROC.terminate()
        except OSError:
            # can't kill a dead proc
            log = util_log.get_logger()
            log.debug('Trying to kill an emulator but it is already dead.')
def _suite_post_run(state):
    '''This function is executed after the test cases have run (teardown).

    Args:
        state: Test suite state collection, instance of State.
    Returns:
        Number of failures
    '''
    log = util_log.get_logger()

    if not state.noinstall and not state.nouninstall:
        if state.wimpy:
            state.bundle.uninstall_all_apk()
        else:
            state.bundle.uninstall_all()
        log.log_and_print('Uninstalled/Deleted all tests')

    total = 0
    passes = 0
    failures = 0

    results = ET.Element('testsuite')
    results.attrib['name'] = 'LLDB RS Test Suite'

    for key, value in state.results.items():
        total += 1
        if value == 'pass':
            passes += 1
        else:
            failures += 1

        # test case name, followed by pass, failure or error elements
        testcase = ET.Element('testcase')
        testcase.attrib['name'] = "%s:%s" % key
        result_element = ET.Element(value)
        result_element.text = "%s:%s" % key
        testcase.append(result_element)
        results.append(testcase)

    assert passes + failures == total, 'Invalid test results status'
    if failures:
        log.log_and_print(
            'The following failures occurred:\n%s\n' %
            '\n'.join('failed: %s:%s' % test_spec
                      for test_spec, result in state.results.items()
                      if result != 'pass'))

    log.log_and_print('{0} of {1} passed'.format(passes, total))
    if total:
        log.log_and_print('{0}% rate'.format((passes * 100) / total))

    results.attrib['tests'] = str(total)
    state.results_file.write(ET.tostring(results, encoding='iso-8859-1'))

    return failures
def _suite_pre_run(state):
    '''This function is executed before the test cases are run (setup).

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

    Return:
        True if the pre_run step completes without error.
        Checks made:
            - Validating that adb exists and runs.
            - Validating that a device is attached.
            - We have root access to the device.
            - All test binaries were pushed to the device.
            - The port for lldb-server was forwarded correctly.

    Raises:
        AssertionError: When assertions fail.
    '''
    assert state
    log = util_log.get_logger()

    try:
        android = state.get_android()
        bundle = state.get_bundle()
        assert android
        assert bundle

        # validate ADB helper class
        android.validate_adb()
        log.log_and_print('Located ADB')

        if state.run_emu:
            log.log_and_print('Launching emulator...')
            _launch_emulator(state)
            log.log_and_print('Started emulator ' + android.device)
        else:
            android.validate_device()
            log.log_and_print('Located device ' + android.device)

        if state.noinstall and not state.single_test:
            bundle.check_apps_installed(state.wimpy)

        # elevate to root user
        android.adb_root()
        android.wait_for_device()
        # check that lldb-server exists on device
        android.kill_servers()
        _check_lldbserver_exists(state)

        if not state.noinstall:
            # push all tests to the device
            log.log_and_print('Pushing all tests...')
            bundle.push_all()
            log.log_and_print('Pushed all tests')
        log.log_and_print('Pre run complete')

    except TestSuiteException as expt:
        log.exception('Test suite pre run failure')

        # Even if we are logging the error, it may be helpful and more
        # immediate to find out the error into the terminal
        log.log_and_print(
            'ERROR: Unable to set up the test suite: %s\n' % expt.message,
            logging.ERROR)

        return False
    return True
def _run_test(state, name, bundle_type):
    '''Execute a single test case.

    Args:
        state: Test suite state collection, instance of State.
        name: String file name of the test to execute.
        bundle_type: string for the installed app type (cpp|jni|java)

    Raises:
        AssertionError: When assertion fails.
    '''
    assert isinstance(name, str)

    try:
        state.android.check_adb_alive()
    except TestSuiteException as expt:
        global EMU_PROC
        if EMU_PROC:
            _restart_emulator(state)
        else:
            raise expt

    log = util_log.get_logger()
    sys.stdout.write('Running {0}\r'.format(name))
    sys.stdout.flush()
    log.info('Running {0}'.format(name))

    run_tests_dir = os.path.dirname(os.path.realpath(__file__))
    run_test_path = os.path.join(run_tests_dir, 'tests', 'run_test.py')

    # Forward port for lldb-server on the device to our host
    hport = int(state.host_port) + state.port_mod
    dport = int(state.device_port) + state.port_mod
    state.android.forward_port(hport, dport)
    state.port_mod += 1

    log.debug('Giving up control to {0}...'.format(name))

    params = map(str, [
        sys.executable, run_test_path, name, state.log_file_path,
        state.adb_path, state.lldb_server_path_device, state.aosp_product_path,
        dport,
        state.android.get_device_id(), state.print_to_stdout, state.verbose,
        state.wimpy, state.timeout, bundle_type
    ])

    return_code = subprocess.call(params)
    state.test_count += 1
    state.android.remove_port_forwarding()
    log.seek_to_end()

    # report in sys.stdout the result
    success = return_code == util_constants.RC_TEST_OK
    status_handlers = collections.defaultdict(
        lambda: ('error', log.error),
        ((util_constants.RC_TEST_OK, ('pass', log.info)),
         (util_constants.RC_TEST_TIMEOUT, ('timeout', log.error)),
         (util_constants.RC_TEST_IGNORED, ('ignored', log.info)),
         (util_constants.RC_TEST_FAIL, ('fail', log.critical))))
    status_name, status_logger = status_handlers[return_code]
    log.info('Running %s: %s', name, status_name.upper())
    status_logger("Test %r: %s", name, status_name)

    # Special case for ignored tests - just return now
    if return_code == util_constants.RC_TEST_IGNORED:
        return

    state.add_result(name, bundle_type, status_name)

    if state.fail_fast and not success:
        raise FailFastException(name)

    # print a running total pass rate
    passes = sum(1 for key, value in state.results.items() if value == 'pass')
    log.info('Current pass rate: %s of %s executed.', passes,
             len(state.results))
def _launch_emulator(state):
    '''Launch the emulator and wait for it to boot.

    Args:
        emu_cmd: The command line to run the emulator.

    Raises:
        TestSuiteException: If an emulator already exists or the emulator
                            process terminated before we could connect to it, or
                            we failed to copy lldb-server to the emulator.
    '''
    global EMU_PROC
    android = state.android
    if state.user_specified_device:
        if android.device_with_substring_exists(state.user_specified_device):
            raise TestSuiteException('A device with name {0} already exists.',
                                     state.user_specified_device)
    else:
        if android.device_with_substring_exists('emulator'):
            raise TestSuiteException('An emulator already exists.')

    assert state.emu_cmd
    EMU_PROC = subprocess.Popen(state.emu_cmd.split(),
                                stdout=None,
                                stderr=subprocess.STDOUT)

    log = util_log.get_logger()
    log.info('Launching emulator with command line {0}'.format(state.emu_cmd))

    tries_number = 180
    tries = tries_number
    found_device = False
    while not found_device:
        try:
            android.validate_device(False, 'emulator')
            found_device = True
        except TestSuiteException as ex:
            tries -= 1
            if tries == 0:
                # Avoid infinitely looping if the emulator won't boot
                log.warning(
                    'Giving up trying to validate device after {0} tries.'.
                    format(tries_number))
                raise ex
            _check_emulator_terminated()
            # wait a bit and try again, maybe it has now booted
            time.sleep(10)

    tries = 500
    while not android.is_booted():
        tries -= 1
        if tries == 0:
            # Avoid infinitely looping if the emulator won't boot
            raise TestSuiteException('The emulator has failed to boot.')
        _check_emulator_terminated()
        time.sleep(5)

    # Need to be root before we can push lldb-server
    android.adb_root()
    android.wait_for_device()

    # Push the lldb-server executable to the device.
    output = android.adb('push {0} {1}'.format(state.lldb_server_path_host,
                                               state.lldb_server_path_device))

    if 'failed to copy' in output or 'No such file or directory' in output:
        raise TestSuiteException(
            'unable to push lldb-server to the emulator: {0}.'.format(output))

    output = android.shell('chmod a+x {0}'.format(
        state.lldb_server_path_device))

    if 'No such file or directory' in output:
        raise TestSuiteException('Failed to copy lldb-server to the emulator.')
    def __init__(self):
        '''State constructor.

        Raises:
            TestSuiteException: When unable to load config file.

            AssertionError: When assertions fail.
        '''

        # Parse the command line options
        args = _parse_args()

        # create a config instance
        if args.config:
            # use the user supplied
            config = State.load_user_configuration(args.config)
        else:
            # use the default configuration
            config = Config()

        # save the test blacklist
        self.blacklist = _choice(args.blacklist, config.blacklist)

        # Allow any of the command line arguments to override the
        # values in the config file.
        self.adb_path = _choice(args.adb_path, config.adb_path)

        self.host_port = int(_choice(args.host_port, config.host_port))

        self.device = _choice(args.device, config.device)

        self.user_specified_device = self.device

        self.device_port = int(_choice(args.device_port, config.device_port))

        self.lldb_server_path_device = _choice(args.lldb_server_path_device,
                                               config.lldb_server_path_device)

        self.lldb_server_path_host = _choice(args.lldb_server_path_host,
                                             config.lldb_server_path_host)

        self.aosp_product_path = _choice(args.aosp_product_path,
                                         config.aosp_product_path)

        self.log_file_path = _choice(args.log_file_path, config.log_file_path)

        self.results_file_path = _choice(args.results_file_path,
                                         config.results_file_path)

        self.lldb_path = _choice(args.lldb_path, config.lldb_path)
        self.print_to_stdout = args.print_to_stdout
        self.verbose = _choice(args.verbose, config.verbose)
        self.timeout = int(_choice(args.timeout, config.timeout))
        self.emu_cmd = _choice(args.emu_cmd, config.emu_cmd)
        self.run_emu = args.run_emu
        self.wimpy = args.wimpy
        self.bundle_types = args.bundle_types if not self.wimpy else ['java']
        self.fail_fast = args.fail_fast

        # validate the param "verbose"
        if not isinstance(self.verbose, bool):
            raise TestSuiteException('The parameter "verbose" should be a '
                                     'boolean: {0}'.format(self.verbose))

        # create result array
        self.results = dict()
        self.single_test = args.test

        # initialise the logging facility
        log_level = logging.INFO if not self.verbose else logging.DEBUG
        util_log.initialise(
            "driver",
            print_to_stdout=self.print_to_stdout,
            level=log_level,
            file_mode='w',  # open for write
            file_path=self.log_file_path)
        log = util_log.get_logger()

        if self.run_emu and not self.emu_cmd:
            log.TestSuiteException(
                'Need to specify --emu-cmd (or specify a'
                ' value in the config file) if using --run-emu.')

        # create a results file
        self.results_file = open(self.results_file_path, 'w')

        # create an android helper object
        self.android = UtilAndroid(self.adb_path, self.lldb_server_path_device,
                                   self.device)
        assert self.android

        # create a test bundle
        self.bundle = UtilBundle(self.android, self.aosp_product_path)
        assert self.bundle

        # save the no pushing option
        assert isinstance(args.noinstall, bool)
        self.noinstall = args.noinstall

        assert isinstance(args.nouninstall, bool)
        self.nouninstall = args.nouninstall

        # install only option
        assert type(args.install_only) is bool
        self.install_only = args.install_only
        if self.install_only:
            log.log_and_print('Option --install-only set. The test APKs will '
                              'be installed on the device but the tests will '
                              'not be executed.')
            if self.noinstall:
                raise TestSuiteException('Conflicting options given: '
                                         '--install-only and --no-install')

        # TCP port modifier which is used to increment the port number used for
        # each test case to avoid collisions.
        self.port_mod = 0

        # total number of test files that have been executed
        self.test_count = 0