def __init__(self):
     self.reporter = reporting.Reporter()
     self.phapi_client = self.reporter.get_bug_tracker_client()
     self.issues = None
     self.all_autofiled_query = 'ANCHOR  TestFailure'
     self.all_autofiled_label = 'autofiled'
     self.prompted = False
Ejemplo n.º 2
0
def check_bug_filed_and_deduped(old_issue_ids):
    """Confirm bug related to dummy_Fail was filed and deduped.

    @param old_issue_ids: A list of issue ids that was closed earlier. id of the
        new issue must be not in this list.
    @raise TestPushException: If auto bug file failed to create a new issue or
        dedupe multiple failures.
    """
    reporter = reporting.Reporter()
    issue = reporter.find_issue_by_marker(BUG_ANCHOR)
    if not issue:
        raise TestPushException('Auto bug file failed. Unable to locate bug '
                                'with marker %s' % BUG_ANCHOR)
    if old_issue_ids and issue.id in old_issue_ids:
        raise TestPushException('Auto bug file failed to create a new issue. '
                                'id of the old issue found is %d.' % issue.id)
    if not ('%s2' % reporter.AUTOFILED_COUNT) in issue.labels:
        raise TestPushException(('Auto bug file failed to dedupe for issue %d '
                                 'with labels of %s.') %
                                (issue.id, issue.labels))
    # Close the bug, and do the search again, which should return None.
    reporter.modify_bug_report(issue.id,
                               comment='Issue closed by test_push script.',
                               label_update='',
                               status='WontFix')
    second_issue = reporter.find_issue_by_marker(BUG_ANCHOR)
    if second_issue:
        ids = '%d, %d' % (issue.id, second_issue.id)
        raise TestPushException(('Auto bug file failed. Multiple issues (%s) '
                                 'filed with marker %s') % (ids, BUG_ANCHOR))
    print 'Issue %d was filed and deduped successfully.' % issue.id
    def testDuplicateIssue(self):
        """Dedupe to an existing issue when one is found.

        Confirms that we call AppendTrackerIssueById with the same issue
        returned by the issue search.
        """
        self.mox.StubOutWithMock(reporting.Reporter, 'find_issue_by_marker')
        self.mox.StubOutWithMock(reporting.TestBug, 'summary')

        issue = self.mox.CreateMock(phapi_lib.Issue)
        issue.id = self._FAKE_ISSUE_ID
        issue.labels = []
        issue.state = constants.ISSUE_OPEN

        client = phapi_lib.ProjectHostingApiClient(mox.IgnoreArg(),
                                                   mox.IgnoreArg())
        client.update_issue(self._FAKE_ISSUE_ID, mox.IgnoreArg())
        reporting.Reporter.find_issue_by_marker(
            mox.IgnoreArg()).AndReturn(issue)

        reporting.TestBug.summary().AndReturn('')

        self.mox.ReplayAll()
        bug_id, bug_count = reporting.Reporter().report(self._get_failure())

        self.assertEqual(bug_id, self._FAKE_ISSUE_ID)
        self.assertEqual(bug_count, 2)
    def testProjectLabelExtraction(self):
        """Test that the project label is correctly extracted from the title."""
        TITLE_EMPTY = ''
        TITLE_NO_PROJ = '[stress] platformDevice Failure on release/47-75.0.0'
        TITLE_PROJ = '[stress] p_Device Failure on rikku-release/R44-7075.0.0'
        TITLE_PROJ2 = '[stress] p_Device Failure on ' \
                      'rikku-freon-release/R44-7075.0.0'
        TITLE_PROJ_SUBBOARD = '[stress] p_Device Failure on ' \
                              'veyron_rikku-release/R44-7075.0.0'

        client = phapi_lib.ProjectHostingApiClient(mox.IgnoreArg(),
                                                   mox.IgnoreArg())
        self.mox.ReplayAll()

        reporter = reporting.Reporter()
        self.assertEqual(reporter._get_project_label_from_title(TITLE_EMPTY),
                         '')
        self.assertEqual(reporter._get_project_label_from_title(TITLE_NO_PROJ),
                         '')
        self.assertEqual(reporter._get_project_label_from_title(TITLE_PROJ),
                         'Proj-rikku')
        self.assertEqual(reporter._get_project_label_from_title(TITLE_PROJ2),
                         'Proj-rikku')
        self.assertEqual(
            reporter._get_project_label_from_title(TITLE_PROJ_SUBBOARD),
            'Proj-rikku')
    def testReturnNoneIfMarkerIsNone(self):
        """Test that we do not look up an issue if the search marker is None."""
        mock_host = phapi_lib.ProjectHostingApiClient(mox.IgnoreArg(),
                                                      mox.IgnoreArg())

        self.mox.ReplayAll()
        result = reporting.Reporter().find_issue_by_marker(None)
        self.assertTrue(result is None)
Ejemplo n.º 6
0
    def test(self):
        """Does a basic test of as much of the system as possible."""
        afe_models.Test(name='test1', test_type=0, path='test1',
            experimental=True).save()
        afe_models.Test(name='test2', test_type=0, path='test2',
            experimental=True).save()

        tko_models.Status(status_idx=6, word='GOOD').save()

        job = tko_models.Job(job_idx=1)
        kernel = tko_models.Kernel(kernel_idx=1)
        machine = tko_models.Machine(machine_idx=1)
        success_status = tko_models.Status(status_idx=GOOD_STATUS_IDX)
        fail_status = tko_models.Status(status_idx=FAIL_STATUS_IDX)

        tko_test1 = tko_models.Test(job=job, status=success_status,
                                    kernel=kernel, machine=machine,
                                    test='test1',
                                    started_time=self.datetime(2012, 1, 20))
        tko_test1.save()
        tko_test2 = tko_models.Test(job=job, status=success_status,
                                    kernel=kernel, machine=machine,
                                    test='test2',
                                    started_time=self.datetime(2012, 1, 20))
        tko_test2.save()

        passing_experimental._MAX_DAYS_SINCE_LAST_PASS = 10
        passing_experimental._MIN_DAYS_SINCE_FAILURE = 10

        MockDatetime.today().AndReturn(self.datetime(2012, 1, 21))
        MockDatetime.today().AndReturn(self.datetime(2012, 1, 21))
        reporter1 = reporting.Reporter()
        bug1 = reporting.Bug(
                title=u'test1 should be promoted to non-experimental.',
                summary=mox.IgnoreArg(),
                search_marker=u'PassingExperimental(test1)')
        reporter1.report(bug1).AndReturn((11, 1))
        reporter2 = reporting.Reporter()
        bug2 = reporting.Bug(
                title=u'test2 should be promoted to non-experimental.',
                summary=mox.IgnoreArg(),
                search_marker=u'PassingExperimental(test2)')
        reporter2.report(bug2).AndReturn((11, 1))

        self.mox.ReplayAll()
        passing_experimental.main()
    def check_dut_availability(self, board, pool, minimum_duts=0):
        """Check if DUT availability for a given board and pool is less than
        minimum.

        @param board: The board to check DUT availability.
        @param pool: The pool to check DUT availability.
        @param minimum_duts: Minimum Number of available machines required to
                             run the suite. Default is set to 0, which means do
                             not force the check of available machines before
                             running the suite.
        @raise: NotEnoughDutsError if DUT availability is lower than minimum.
        @raise: BoardNotAvailableError if no host found for requested
                board/pool.
        """
        if minimum_duts == 0:
            return

        hosts = self.rpc_interface.get_hosts(
            invalid=False,
            multiple_labels=('pool:%s' % pool, 'board:%s' % board))
        if not hosts:
            raise BoardNotAvailableError(
                'No hosts found for board:%s in pool:%s. The test lab '
                'currently does not cover test for this board and pool.' %
                (board, pool))

        if len(hosts) < minimum_duts:
            logging.debug(
                'The total number of DUTs for %s in pool:%s is %d, '
                'which is no more than the required minimum number of'
                ' available DUTS of %d. Minimum available DUT rule is'
                ' not enforced.', board, pool, len(hosts), minimum_duts)
            return

        # TODO(dshi): Replace the hard coded string with enum value,
        # models.Host.Status.REPAIRING and REPAIR_FAILED
        # setup_django_environment can't be imported now as paygen server does
        # not have django package.
        bad_statuses = ('Repair Failed', 'Repairing', 'Verifying')
        unusable_hosts = []
        available_hosts = []
        for host in hosts:
            if host.status in bad_statuses or host.locked:
                unusable_hosts.append(host.hostname)
            else:
                available_hosts.append(host)
        logging.debug('%d of %d DUTs are available for board %s pool %s.',
                      len(available_hosts), len(hosts), board, pool)
        if len(available_hosts) < minimum_duts:
            if unusable_hosts:
                pool_health_bug = reporting.PoolHealthBug(
                    pool, board, unusable_hosts)
                self.bug = reporting.Reporter().report(pool_health_bug)[0]
            raise NotEnoughDutsError(
                'Number of available DUTs for board %s pool %s is %d, which'
                ' is less than the minimum value %d. '
                'Filed https://crbug.com/%s' %
                (board, pool, len(available_hosts), minimum_duts, self.bug))
    def test_summary_returned_untouched_if_no_search_maker(self):
        """Test that we just return the summary if we have no search marker."""
        mock_host = phapi_lib.ProjectHostingApiClient(mox.IgnoreArg(),
                                                      mox.IgnoreArg())

        bug = reporting.Bug('title', 'summary', None)

        self.mox.ReplayAll()
        result = reporting.Reporter()._anchor_summary(bug)

        self.assertEqual(result, 'summary')
    def __init__(self, autocommit=False):
        """Initialize update manager.

        @param autocommit: If False just print out the update instead
            of committing it.
        """
        self.history = {}
        self.present = {}
        self.reporter = reporting.Reporter()
        self.phapi_lib = self.reporter.get_bug_tracker_client()
        self.autocommit = autocommit
    def test_accepts_key_word_arguments(self):
        """
        Test that the functions accepts the key_word arguments.

        This basically tests that no exceptions are thrown.

        """
        reporter = reporting.Reporter()
        reporter.report(mox.IgnoreArg()).AndReturn((11, 1))

        self.mox.ReplayAll()
        reporting.submit_generic_bug_report('test', 'summary', labels=[])
    def test_append_anchor_to_summary_if_search_marker(self):
        """Test that we add an anchor to the search marker."""
        mock_host = phapi_lib.ProjectHostingApiClient(mox.IgnoreArg(),
                                                      mox.IgnoreArg())

        bug = reporting.Bug('title', 'summary', 'marker')

        self.mox.ReplayAll()
        result = reporting.Reporter()._anchor_summary(bug)

        self.assertEqual(
            result,
            'summary\n\n%smarker\n' % reporting.Reporter._SEARCH_MARKER)
    def testWithSearchMarkerSetToNoneIsNotDeduped(self):
        """Test that we do not dedupe bugs that have no search marker."""

        bug = reporting.Bug('title', 'summary', search_marker=None)

        mock_host = phapi_lib.ProjectHostingApiClient(mox.IgnoreArg(),
                                                      mox.IgnoreArg())
        mock_host.create_issue(mox.IgnoreArg()).AndReturn(
            {'id': self._FAKE_ISSUE_ID})

        self.mox.ReplayAll()
        bug_id, bug_count = reporting.Reporter().report(bug)

        self.assertEqual(bug_id, self._FAKE_ISSUE_ID)
        self.assertEqual(bug_count, 1)
Ejemplo n.º 13
0
def close_bug():
    """Close all existing bugs filed for dummy_Fail.

    @return: A list of issue ids to be used in check_bug_filed_and_deduped.
    """
    old_issue_ids = []
    reporter = reporting.Reporter()
    while True:
        issue = reporter.find_issue_by_marker(BUG_ANCHOR)
        if not issue:
            return old_issue_ids
        if issue.id in old_issue_ids:
            raise TestPushException('Failed to close issue %d' % issue.id)
        old_issue_ids.append(issue.id)
        reporter.modify_bug_report(issue.id,
                                   comment='Issue closed by test_push script.',
                                   label_update='',
                                   status='WontFix')
    def testGenericBugCanBeFiled(self):
        """Test that we can use a Bug object to file a bug report."""
        self.mox.StubOutWithMock(reporting.Reporter, 'find_issue_by_marker')

        bug = reporting.Bug('title', 'summary', 'marker')

        reporting.Reporter.find_issue_by_marker(
            mox.IgnoreArg()).AndReturn(None)

        mock_host = phapi_lib.ProjectHostingApiClient(mox.IgnoreArg(),
                                                      mox.IgnoreArg())
        mock_host.create_issue(mox.IgnoreArg()).AndReturn(
            {'id': self._FAKE_ISSUE_ID})

        self.mox.ReplayAll()
        bug_id, bug_count = reporting.Reporter().report(bug)

        self.assertEqual(bug_id, self._FAKE_ISSUE_ID)
        self.assertEqual(bug_count, 1)
    def _test_count_label_update(self, labels, remove, expected_count):
        """Utility to test _create_autofiled_count_update().

        @param labels         Input list of labels.
        @param remove         List of labels expected to be removed
                              in the result.
        @param expected_count Count value expected to be returned
                              from the call.
        """
        client = phapi_lib.ProjectHostingApiClient(mox.IgnoreArg(),
                                                   mox.IgnoreArg())
        self.mox.ReplayAll()
        issue = self.mox.CreateMock(gdata_lib.Issue)
        issue.labels = labels

        reporter = reporting.Reporter()
        new_labels, count = reporter._create_autofiled_count_update(issue)
        expected = map(lambda l: '-' + l, remove)
        expected.append(self._create_count_label(expected_count))
        self.assertEqual(new_labels, expected)
        self.assertEqual(count, expected_count)
def flag_problem_test(machine):
    """
    Notify people about the last job that ran on a machine.

    This method is invoked everytime a machine fails to repair, and attempts
    to identify the last test that ran on the machine. If successfull, it files
    a bug, or sends out an email, or just logs the fact.

    @param machine: The hostname (e.g. IP address) of the machine to find the
        last job ran on it.

    """
    rpc = frontend.AFE()
    logger = MachineDeathLogger()

    try:
        problem_test = _find_problem_test(machine, rpc)
    except (urllib2.URLError, xmlrpclib.ProtocolError):
        logger.logger.error('%s | ERROR: Could not contact RPC server'
                            % machine)
        return

    if problem_test:
        job_id = problem_test['job']['id']
        job_name = problem_test['job']['name']
        bug = reporting.MachineKillerBug(job_id=job_id,
                                         job_name=job_name,
                                         machine=machine)
        reporter = reporting.Reporter()
        bug_id = reporter.report(bug)[0]

        if bug_id is None:
            try:
                email_prefix = ('The following test is killing a machine, '
                                'could not file a bug to report this:\n\n')
                mail.send(_SENDER_ADDRESS, _NOTIFY_ADDRESS, '',
                          bug.title(), email_prefix + bug.summary())
            except smtplib.SMTPDataError:
                logger.logger.error('%s | %d | %s'
                                    % (machine, job_id, job_name))
    def testNewIssue(self):
        """Add a new issue to the tracker when a matching issue isn't found.

        Confirms that we call CreateTrackerIssue when an Issue search
        returns None.
        """
        self.mox.StubOutWithMock(reporting.Reporter, 'find_issue_by_marker')
        self.mox.StubOutWithMock(reporting.TestBug, 'summary')

        client = phapi_lib.ProjectHostingApiClient(mox.IgnoreArg(),
                                                   mox.IgnoreArg())
        client.create_issue(mox.IgnoreArg()).AndReturn(
            {'id': self._FAKE_ISSUE_ID})
        reporting.Reporter.find_issue_by_marker(
            mox.IgnoreArg()).AndReturn(None)
        reporting.TestBug.summary().AndReturn('')

        self.mox.ReplayAll()
        bug_id, bug_count = reporting.Reporter().report(self._get_failure())

        self.assertEqual(bug_id, self._FAKE_ISSUE_ID)
        self.assertEqual(bug_count, 1)
    def testSuiteIssueConfig(self):
        """Test that the suite bug template values are not overridden."""
        def check_suite_options(issue):
            """
            Checks to see if the options specified in bug_template reflect in
            the issue we're about to file, and that the autofiled label was not
            lost in the process.

            @param issue: issue to check labels on.
            """
            assert ('autofiled' in issue.labels)
            for k, v in self.bug_template.iteritems():
                if (isinstance(v, list)
                        and all(item in getattr(issue, k) for item in v)):
                    continue
                if v and getattr(issue, k) is not v:
                    return False
            return True

        self.mox.StubOutWithMock(reporting.Reporter, '_find_issue_by_marker')
        self.mox.StubOutWithMock(reporting.TestBug, 'summary')

        reporting.Reporter._find_issue_by_marker(
            mox.IgnoreArg()).AndReturn(None)
        reporting.TestBug.summary().AndReturn('Summary')

        mock_host = phapi_lib.ProjectHostingApiClient(mox.IgnoreArg(),
                                                      mox.IgnoreArg(),
                                                      mox.IgnoreArg())
        mock_host.create_issue(mox.IgnoreArg()).AndReturn(
            {'id': self._FAKE_ISSUE_ID})

        self.mox.ReplayAll()
        bug_id, bug_count = reporting.Reporter().report(
            self._get_failure(), self.bug_template)

        self.assertEqual(bug_id, self._FAKE_ISSUE_ID)
        self.assertEqual(bug_count, 1)
Ejemplo n.º 19
0
    def _Schedule(self, suite, board, build, pool, num, priority, timeout,
                  file_bugs=False, firmware_rw_build=None,
                  firmware_ro_build=None, test_source_build=None,
                  job_retry=False, launch_control_build=None,
                  run_prod_code=False, testbed_dut_count=None, no_delay=False):
        """Schedule |suite|, if it hasn't already been run.

        @param suite: the name of the suite to run, e.g. 'bvt'
        @param board: the board to run the suite on, e.g. x86-alex
        @param build: the ChromeOS build to install e.g.
                      x86-alex-release/R18-1655.0.0-a1-b1584.
        @param pool: the pool of machines to use for scheduling purposes.
                     Default: None
        @param num: the number of devices across which to shard the test suite.
                    Type: integer or None
                    Default: None (uses sharding factor in global_config.ini).
        @param priority: One of the values from
                         client.common_lib.priorities.Priority.
        @param timeout: The max lifetime of the suite in hours.
        @param file_bugs: True if bug filing is desired for this suite.
        @param firmware_rw_build: Firmware build to update RW firmware. Default
                                  to None.
        @param firmware_ro_build: Firmware build to update RO firmware. Default
                                  to None.
        @param test_source_build: Build that contains the server-side test code.
                                  Default to None to use the ChromeOS build
                                  (defined by `build`).
        @param job_retry: Set to True to enable job-level retry. Default is
                          False.
        @param launch_control_build: Name of a Launch Control build, e.g.,
                                     'git_mnc_release/shamu-eng/123'
        @param run_prod_code: If True, the suite will run the test code that
                              lives in prod aka the test code currently on the
                              lab servers. If False, the control files and test
                              code for this suite run will be retrieved from the
                              build artifacts. Default is False.
        @param testbed_dut_count: Number of duts to test when using a testbed.
        @param no_delay: Set to True to allow suite to be created without
                         configuring delay_minutes. Default is False.

        @return True if the suite got scheduled
        @raise ScheduleException if an error occurs while scheduling.

        """
        try:
            if build:
                builds = {provision.CROS_VERSION_PREFIX: build}
            if firmware_rw_build:
                builds[provision.FW_RW_VERSION_PREFIX] = firmware_rw_build
            if firmware_ro_build:
                builds[provision.FW_RO_VERSION_PREFIX] = firmware_ro_build
            if launch_control_build:
                if testbed_dut_count is None:
                    builds = {provision.ANDROID_BUILD_VERSION_PREFIX:
                              launch_control_build}
                else:
                    builds = {provision.TESTBED_BUILD_VERSION_PREFIX:
                              launch_control_build}

            # Suite scheduler handles all boards in parallel, to guarantee each
            # call of `create_suite_job` use different value of delay_minutes,
            # we need a lock around get/set attempts of self.delay_minutes.
            # To prevent suite jobs from running too long, the value for
            # self.delay_minutes is limited between 0 and MAX_DELAY_MINUTES (4
            # hours). The value starts at 0 and is increased by
            # DELAY_MINUTES_INTERVAL, when it reaches MAX_DELAY_MINUTES, the
            # logic here allows its value to step back by DELAY_MINUTES_INTERVAL
            # at each call of this method. When the value drops back to 0, it
            # will increase again in the next call of this method.
            # Such logic allows the values of delay_minutes for all calls
            # of `create_suite_job` running in parallel to be evenly distributed
            # between 0 and MAX_DELAY_MINUTES.
            delay_minutes = 0
            if not no_delay:
                with self._lock:
                    delay_minutes = self.delay_minutes
                    if ((self.delay_minutes < MAX_DELAY_MINUTES and
                         self.delay_minutes_interval > 0) or
                        (self.delay_minutes >= DELAY_MINUTES_INTERVAL and
                         self.delay_minutes_interval < 0)):
                        self.delay_minutes += self.delay_minutes_interval
                    else:
                        limit = ('Maximum' if self.delay_minutes_interval > 0
                                 else 'Minimum')
                        logging.info(
                                '%s delay minutes reached when scheduling '
                                '%s on %s against %s (pool: %s)',
                                limit, suite, builds, board, pool)
                        self.delay_minutes_interval = (
                                -self.delay_minutes_interval)

            # Update timeout settings for the suite job with delay_minutes.
            # `timeout` is in hours.
            if not timeout:
                timeout = JOB_MAX_RUNTIME_MINS_DEFAULT / 60.0
            timeout += delay_minutes / 60.0
            max_runtime_mins = JOB_MAX_RUNTIME_MINS_DEFAULT + delay_minutes
            timeout_mins = JOB_MAX_RUNTIME_MINS_DEFAULT + delay_minutes

            logging.info('Scheduling %s on %s against %s (pool: %s)...',
                         suite, builds, board, pool)
            job_id = self._afe.run('create_suite_job', name=suite, board=board,
                                   builds=builds, check_hosts=False, num=num,
                                   pool=pool, priority=priority,
                                   timeout=timeout,
                                   max_runtime_mins=max_runtime_mins,
                                   timeout_mins=timeout_mins,
                                   file_bugs=file_bugs,
                                   wait_for_results=file_bugs,
                                   test_source_build=test_source_build,
                                   job_retry=job_retry,
                                   delay_minutes=delay_minutes,
                                   run_prod_code=run_prod_code,
                                   min_rpc_timeout=_MIN_RPC_TIMEOUT)
            if job_id is not None:
                logging.info('... created as suite job id %s', job_id)
                # Report data to metrics.
                fields = {'suite': suite,
                          'board': board,
                          'pool': pool,
                          'priority': str(priority)}
                self._SUITE_SCHEDULER_SUITE_COUNT.increment(fields=fields)
                return True
            else:
                raise ScheduleException(
                        "Can't schedule %s for %s." % (suite, builds))
        except (error.ControlFileNotFound, error.ControlFileEmpty,
                error.ControlFileMalformed, error.NoControlFileList) as e:
            if self._file_bug:
                # File bug on test_source_build if it's specified.
                b = reporting.SuiteSchedulerBug(
                        suite, test_source_build or build, board, e)
                # If a bug has filed with the same <suite, build, error type>
                # will not file again, but simply gets the existing bug id.
                bid, _ = reporting.Reporter().report(
                        b, ignore_duplicate=True)
                if bid is not None:
                    return False
            # Raise the exception if not filing a bug or failed to file bug.
            raise ScheduleException(e)
        except Exception as e:
            raise ScheduleException(e)
    def _Schedule(self, suite, board, build, pool, num, priority, timeout,
                  file_bugs=False, firmware_rw_build=None,
                  test_source_build=None, job_retry=False):
        """Schedule |suite|, if it hasn't already been run.

        @param suite: the name of the suite to run, e.g. 'bvt'
        @param board: the board to run the suite on, e.g. x86-alex
        @param build: the build to install e.g.
                      x86-alex-release/R18-1655.0.0-a1-b1584.
        @param pool: the pool of machines to use for scheduling purposes.
                     Default: None
        @param num: the number of devices across which to shard the test suite.
                    Type: integer or None
                    Default: None (uses sharding factor in global_config.ini).
        @param priority: One of the values from
                         client.common_lib.priorities.Priority.
        @param timeout: The max lifetime of the suite in hours.
        @param file_bugs: True if bug filing is desired for this suite.
        @param firmware_rw_build: Firmware build to update RW firmware. Default
                                  to None.
        @param test_source_build: Build that contains the server-side test code.
                                  Default to None to use the ChromeOS build
                                  (defined by `build`).
        @param job_retry: Set to True to enable job-level retry. Default is
                          False.

        @return True if the suite got scheduled
        @raise ScheduleException if an error occurs while scheduling.

        """
        try:
            builds = {provision.CROS_VERSION_PREFIX: build}
            if firmware_rw_build:
                builds[provision.FW_RW_VERSION_PREFIX] = firmware_rw_build
            logging.info('Scheduling %s on %s against %s (pool: %s)',
                         suite, builds, board, pool)
            if self._afe.run(
                        'create_suite_job', name=suite, board=board,
                        builds=builds, check_hosts=False, num=num, pool=pool,
                        priority=priority, timeout=timeout, file_bugs=file_bugs,
                        wait_for_results=file_bugs,
                        test_source_build=test_source_build,
                        job_retry=job_retry) is not None:
                return True
            else:
                raise ScheduleException(
                    "Can't schedule %s for %s." % (suite, builds))
        except (error.ControlFileNotFound, error.ControlFileEmpty,
                error.ControlFileMalformed, error.NoControlFileList) as e:
            if self._file_bug:
                # File bug on test_source_build if it's specified.
                b = reporting.SuiteSchedulerBug(
                        suite, test_source_build or build, board, e)
                # If a bug has filed with the same <suite, build, error type>
                # will not file again, but simply gets the existing bug id.
                bid, _ = reporting.Reporter().report(
                        b, ignore_duplicate=True)
                if bid is not None:
                    return False
            # Raise the exception if not filing a bug or failed to file bug.
            raise ScheduleException(e)
        except Exception as e:
            raise ScheduleException(e)