def launch(self):
        """Launches tests using xcodebuild."""
        overall_launch_command_result = ResultCollection()
        shards = self.shards
        running_tests = set(self.egtests_app.get_all_tests())
        # total number of attempts is self.retries+1
        for attempt in range(self.retries + 1):
            # Erase all simulators per each attempt
            if iossim_util.is_device_with_udid_simulator(self.udid):
                # kill all running simulators to prevent possible memory leaks
                test_runner.SimulatorTestRunner.kill_simulators()
                shutdown_all_simulators()
                shutdown_all_simulators(XTDEVICE_FOLDER)
                erase_all_simulators()
                erase_all_simulators(XTDEVICE_FOLDER)
            outdir_attempt = os.path.join(self.out_dir, 'attempt_%d' % attempt)
            cmd_list = self.egtests_app.command(outdir_attempt,
                                                'id=%s' % self.udid, shards)
            # TODO(crbug.com/914878): add heartbeat logging to xcodebuild_runner.
            LOGGER.info('Start test attempt #%d for command [%s]' %
                        (attempt, ' '.join(cmd_list)))
            output = self.launch_attempt(cmd_list)

            if hasattr(self, 'use_clang_coverage') and self.use_clang_coverage:
                # out_dir of LaunchCommand object is the TestRunner out_dir joined with
                # UDID. Use os.path.dirname to retrieve the TestRunner out_dir.
                file_util.move_raw_coverage_data(self.udid,
                                                 os.path.dirname(self.out_dir))

            result = self._log_parser.collect_test_results(
                outdir_attempt, output)

            tests_selected_at_runtime = _tests_decided_at_runtime(
                self.egtests_app.test_app_path)
            # For most suites, only keep crash status from last attempt since retries
            # will cover any missing tests. For these decided at runtime, retain
            # crashes from all attempts and a dummy "crashed" result will be reported
            # to indicate some tests might never ran.
            # TODO(crbug.com/1235871): Switch back to excluded tests and set
            # |overall_crash| to always True.
            overall_launch_command_result.add_result_collection(
                result, overwrite_crash=not tests_selected_at_runtime)
            result.report_to_result_sink()

            tests_to_include = set()
            # |running_tests| are compiled tests in target intersecting with swarming
            # sharding. For some suites, they are more than what's needed to run.
            if not tests_selected_at_runtime:
                tests_to_include = tests_to_include | (
                    running_tests -
                    overall_launch_command_result.expected_tests())
            # Add failed tests from last rounds for runtime decided suites and device
            # suites.
            tests_to_include = (
                tests_to_include
                | overall_launch_command_result.never_expected_tests())
            self.egtests_app.included_tests = list(tests_to_include)

            # Nothing to run in retry.
            if not self.egtests_app.included_tests:
                break

            # If tests are not completed(interrupted or did not start) and there are
            # >= 20 remaining tests, run them with the same number of shards.
            # otherwise re-run with shards=1.
            if (not result.crashed
                    # If need to re-run less than 20 tests, 1 shard should be enough.
                    or (len(running_tests) -
                        len(overall_launch_command_result.expected_tests()) <=
                        MAXIMUM_TESTS_PER_SHARD_FOR_RERUN)):
                shards = 1

        return overall_launch_command_result
Exemple #2
0
    def launch(self):
        """Launches the test app."""
        self.set_up()
        # The overall ResultCorrection object holding all runs of all tests in the
        # runner run. It will be updated with each test application launch.
        overall_result = ResultCollection()
        destination = 'id=%s' % self.udid
        test_app = self.get_launch_test_app()
        out_dir = os.path.join(self.out_dir, 'TestResults')
        cmd = self.get_launch_command(test_app, out_dir, destination,
                                      self.shards)
        try:
            result = self._run(cmd=cmd, shards=self.shards or 1)
            if result.crashed and not result.crashed_tests():
                # If the app crashed but not during any particular test case, assume
                # it crashed on startup. Try one more time.
                self.shutdown_and_restart()
                LOGGER.warning('Crashed on startup, retrying...\n')
                out_dir = os.path.join(self.out_dir,
                                       'retry_after_crash_on_startup')
                cmd = self.get_launch_command(test_app, out_dir, destination,
                                              self.shards)
                result = self._run(cmd)

            result.report_to_result_sink()

            if result.crashed and not result.crashed_tests():
                raise AppLaunchError

            overall_result.add_result_collection(result)

            try:
                while result.crashed and result.crashed_tests():
                    # If the app crashes during a specific test case, then resume at the
                    # next test case. This is achieved by filtering out every test case
                    # which has already run.
                    LOGGER.warning('Crashed during %s, resuming...\n',
                                   list(result.crashed_tests()))
                    test_app.excluded_tests = list(
                        overall_result.all_test_names())
                    retry_out_dir = os.path.join(
                        self.out_dir,
                        'retry_after_crash_%d' % int(time.time()))
                    result = self._run(
                        self.get_launch_command(
                            test_app,
                            os.path.join(retry_out_dir, str(int(time.time()))),
                            destination))
                    result.report_to_result_sink()
                    # Only keep the last crash status in crash retries in overall crash
                    # status.
                    overall_result.add_result_collection(result,
                                                         overwrite_crash=True)

            except OSError as e:
                if e.errno == errno.E2BIG:
                    LOGGER.error('Too many test cases to resume.')
                else:
                    raise

            # Retry failed test cases.
            test_app.excluded_tests = []
            never_expected_tests = overall_result.never_expected_tests()
            if self.retries and never_expected_tests:
                LOGGER.warning('%s tests failed and will be retried.\n',
                               len(never_expected_tests))
                for i in xrange(self.retries):
                    tests_to_retry = list(
                        overall_result.never_expected_tests())
                    for test in tests_to_retry:
                        LOGGER.info('Retry #%s for %s.\n', i + 1, test)
                        test_app.included_tests = [test]
                        retry_out_dir = os.path.join(self.out_dir,
                                                     test + '_failed',
                                                     'retry_%d' % i)
                        retry_result = self._run(
                            self.get_launch_command(test_app, retry_out_dir,
                                                    destination))

                        if not retry_result.all_test_names():
                            retry_result.add_test_result(
                                TestResult(
                                    test,
                                    TestStatus.SKIP,
                                    test_log=
                                    'In single test retry, result of this test '
                                    'didn\'t appear in log.'))
                        retry_result.report_to_result_sink()
                        # No unknown tests might be skipped so do not change
                        # |overall_result|'s crash status.
                        overall_result.add_result_collection(retry_result,
                                                             ignore_crash=True)

            interrupted = overall_result.crashed

            if interrupted:
                overall_result.add_and_report_crash(
                    crash_message_prefix_line=
                    'Test application crashed when running '
                    'tests which might have caused some tests never ran or finished.'
                )

            self.test_results = overall_result.standard_json_output()
            self.logs.update(overall_result.test_runner_logs())

            return not overall_result.never_expected_tests(
            ) and not interrupted
        finally:
            self.tear_down()