[autotest]: Refactor bug filing.
This creates a new Bug class that TestFailure now derives from. This allows us
to file bugs for issues that are not test failures.
BUG=chromium:254256
TEST=Unit tests and ran the dummy suite and saw that the tests were deduped.
Change-Id: I104489e9627a64506fc9c206392f1297fa538c44
Reviewed-on: https://gerrit.chromium.org/gerrit/65898
Reviewed-by: Prashanth Balasubramanian <beeps@chromium.org>
Reviewed-by: Keyar Hood <keyar@chromium.org>
Tested-by: Keyar Hood <keyar@chromium.org>
Commit-Queue: Keyar Hood <keyar@chromium.org>
diff --git a/global_config.ini b/global_config.ini
index e618a5d..85f7b39 100644
--- a/global_config.ini
+++ b/global_config.ini
@@ -193,6 +193,8 @@
build_prefix: build/
tracker_url: https://code.google.com/p/chromium/issues/detail?id=
gs_file_prefix: gs://
+chromium_email_address = @chromium.org
+test_failure_owner = beeps@chromium.org
credentials: USE SHADOW CREDENTIALS
diff --git a/server/cros/dynamic_suite/reporting.py b/server/cros/dynamic_suite/reporting.py
index c8caba9..b06541b 100644
--- a/server/cros/dynamic_suite/reporting.py
+++ b/server/cros/dynamic_suite/reporting.py
@@ -34,8 +34,53 @@
BUG_CONFIG_SECTION = 'BUG_REPORTING'
+CHROMIUM_EMAIL_ADDRESS = global_config.global_config.get_config_value(
+ BUG_CONFIG_SECTION, 'chromium_email_address', default='')
-class TestFailure(object):
+
+class Bug(object):
+ """Holds the minimum information needed to make a dedupable bug report."""
+
+ def __init__(self, title, summary, search_marker=None, labels=None,
+ owner='', cc=None):
+ """
+ Initializes Bug object.
+
+ @param title: The title of the bug.
+ @param summary: The summary of the bug.
+ @param search_marker: The string used to determine if a bug is a
+ duplicate report or not. All Bugs with the same
+ search_marker are considered to be for the same
+ bug. Make this None if you do not want to dedupe.
+ @param labels: The labels that the filed bug will have.
+ @param owner: The owner/asignee of this bug. Typically left blank.
+ @param cc: Who to cc'd for this bug.
+ """
+ self._title = title
+ self._summary = summary
+ self._search_marker = search_marker
+ self.owner = owner
+
+ self.labels = labels if labels is not None else []
+ self.cc = cc if cc is not None else []
+
+
+ def title(self):
+ """Combines information about this bug into a title string."""
+ return self._title
+
+
+ def summary(self):
+ """Combines information about this bug into a summary string."""
+ return self._summary
+
+
+ def search_marker(self):
+ """Return an Anchor that we can use to dedupe this exact bug."""
+ return self._search_marker
+
+
+class TestFailure(Bug):
"""
Wrap up all information needed to make an intelligent report about a
test failure. Each TestFailure has a search marker associated with it
@@ -68,7 +113,8 @@
_build_prefix = global_config.global_config.get_config_value(
BUG_CONFIG_SECTION, 'build_prefix', default='')
-
+ OWNER = global_config.global_config.get_config_value(
+ BUG_CONFIG_SECTION, 'test_failure_owner', default='')
# Number of times to retry if a gs command fails. Defaults to 10,
# which is far too long given that we already wait on these files
# before starting HWTests.
@@ -91,9 +137,10 @@
self.build = build
self.chrome_version = chrome_version
self.suite = suite
- self.test = result.test_name
+ self.name = result.test_name
self.reason = result.reason
- self.owner = result.owner
+ # The result_owner is used to find results and logs.
+ self.result_owner = result.owner
self.hostname = result.hostname
self.job_id = result.id
@@ -102,17 +149,21 @@
# are disabled till crbug.com/188217 is resolved.
self.lab_error = job_status.is_for_infrastructure_fail(result)
+ # The owner is who the bug is assigned to.
+ self.owner = ''
+ self.cc = [self.OWNER]
+ self.labels = []
- def bug_title(self):
+ def title(self):
"""Combines information about this failure into a title string."""
- return '[%s] %s failed on %s' % (self.suite, self.test, self.build)
+ return '[%s] %s failed on %s' % (self.suite, self.name, self.build)
- def bug_summary(self):
+ def summary(self):
"""Combines information about this failure into a summary string."""
links = self._get_links_for_failure()
- summary = ('This bug has been automatically filed to track the '
+ template = ('This bug has been automatically filed to track the '
'following failure:\nTest: %(test)s.\nSuite: %(suite)s.\n'
'Chrome Version: %(chrome_version)s.\n'
'Build: %(build)s.\n\nReason:\n%(reason)s.\n'
@@ -121,7 +172,7 @@
'buildbot stages: %(buildbot_stages)s.\n')
specifics = {
- 'test': self.test,
+ 'test': self.name,
'suite': self.suite,
'build': self.build,
'chrome_version': self.chrome_version,
@@ -130,13 +181,14 @@
'results_log': links.results,
'buildbot_stages': links.buildbot,
}
- return summary % specifics
+
+ return template % specifics
def search_marker(self):
"""Return an Anchor that we can use to dedupe this exact failure."""
return "%s(%s,%s,%s)" % ('TestFailure', self.suite,
- self.test, self.reason)
+ self.name, self.reason)
def _link_build_artifacts(self):
@@ -147,8 +199,8 @@
def _link_result_logs(self):
"""Returns an url to test logs on google storage."""
- if self.job_id and self.owner and self.hostname:
- path_to_object = '%s-%s/%s/%s' % (self.job_id, self.owner,
+ if self.job_id and self.result_owner and self.hostname:
+ path_to_object = '%s-%s/%s/%s' % (self.job_id, self.result_owner,
self.hostname, self._debug_dir)
return (self._retrieve_logs_cgi + self._generic_results_bin +
path_to_object)
@@ -209,8 +261,7 @@
class Reporter(object):
"""
- Files external reports about bug failures that happened inside
- autotest.
+ Files external reports about bugs that happened inside autotest.
"""
_project_name = global_config.global_config.get_config_value(
BUG_CONFIG_SECTION, 'project_name', default='')
@@ -218,7 +269,6 @@
BUG_CONFIG_SECTION, 'username', default='')
_password = global_config.global_config.get_config_value(
BUG_CONFIG_SECTION, 'password', default='')
- _SEARCH_MARKER = 'ANCHOR '
# Credentials for access to the project hosting api
_oauth_credentials = global_config.global_config.get_config_value(
@@ -226,16 +276,16 @@
_PREDEFINED_LABELS = ['autofiled', 'OS-Chrome',
'Type-Bug', 'Restrict-View-Google']
- _CHROMIUM_EMAIL_ADDRESS = '@chromium.org'
- _OWNER = 'beeps%s' % _CHROMIUM_EMAIL_ADDRESS
_LAB_ERROR_TEMPLATE = {
'labels': ['Bug-Filer-Bug'],
- 'owner': _OWNER,
+ 'owner': TestFailure.OWNER,
# Set the status to Invalid so we don't dedupe against these bugs.
'status': 'Invalid',
}
+ _SEARCH_MARKER = 'ANCHOR '
+
def __init__(self):
if not fundamental_libs:
@@ -257,56 +307,24 @@
return fundamental_libs and self._phapi_client
- def _get_owner(self, failure):
+ def _get_owner(self, bug):
"""
- Returns an owner for the given failure.
+ Returns an owner for the given bug.
- @param failure: A failure object for which a bug is about to get filed.
- @return: A string with the email address of the owner of this failure.
- The issue associated with the failure will get assigned to the
+ @param bug: A Bug object for which a bug is about to get filed.
+ @return: A string with the email address of the owner of this bug.
+ The issue associated with the bug will get assigned to the
owner and they will receive an email from the bug tracker. If
- there is no obvious owner for the failure an empty string is
+ there is no obvious owner for the bug an empty string is
returned.
"""
- if failure.lab_error:
- return self._OWNER
- return ''
+ try:
+ if bug.lab_error:
+ return TestFailure.OWNER
+ except AttributeError:
+ pass
-
- def _get_labels(self, test_name):
- """
- Creates labels for an issue.
-
- Does a simple check to see if any of the areas listed in the
- projects pre-defined labels are embedded in the name of the
- failing test.
-
- @param test_name: name of the failing test.
- @return: a list of labels.
- """
- def match_area(test_name, test_area):
- """
- Matches the prefix of a test name to an area, and then the suffix
- of the area (if any) to the test name. Both parts of the test name
- don't need to be in the area, this function prioritizes the first
- half of the test name. A helical match is needed to allow situations
- like the third example:
-
- kernel_Video matches kernel, kernel-video but not kernel-audio.
- network_Ping matches Systems-Network.
- kernel_ConfigVerify matches kernel, but would also match both
- kernel-config and kernel-verify.
-
- @param test_name: lower case test name.
- @param test_area: lower case Cr-OS area from tracker.
- """
- return (test_name and test_name[:test_name.find('_')] in test_area
- and (not '-' in test_area or
- test_area[test_area.find('-')+1:] in test_name))
-
- cros_areas = self._phapi_client.get_areas()
- return ['Cr-OS-%s' % area for area in cros_areas
- if match_area(test_name, area.lower())]
+ return bug.owner
def _format_issue_options(self, override, **kwargs):
@@ -332,23 +350,36 @@
# The existence of an owner key will cause the api to try and match
# the value under the key to a member of the project, resulting in a
# 404 or 500 Http response when the owner is invalid.
- if (self._CHROMIUM_EMAIL_ADDRESS not in kwargs['owner']):
+ if (CHROMIUM_EMAIL_ADDRESS not in kwargs['owner']):
del(kwargs['owner'])
else:
kwargs['owner'] = {'name': kwargs['owner']}
return kwargs
- # TODO(beeps):crbug.com/254256
- def create_bug_report(self, description, title, name, owner,
- bug_template={}, sheriffs=[]):
+ def _anchor_summary(self, bug):
+ """
+ Creates the summary that can be used for bug deduplication.
+
+ Only attaches the anchor if the search_marker on the bug is not None.
+
+ @param: The bug to create the anchored summary for.
+
+ @return the summary with the anchor appened if the search marker is not
+ None, otherwise return the summary.
+ """
+ if bug.search_marker() is None:
+ return bug.summary()
+ else:
+ return '%s\n\n%s%s\n' % (bug.summary(), self._SEARCH_MARKER,
+ bug.search_marker())
+
+
+ def create_bug_report(self, bug, bug_template={}, sheriffs=[]):
"""
Creates a new bug report.
- @param description: A summary of the failure.
- @param title: Title of the bug.
- @param name: Failing Test name, used to assigning labels.
- @param owner: The owner of the new bug.
+ @param bug: The Bug instance to create the report for.
@param bug_template: A template of options to use for filing bugs.
@param sheriffs: A list of chromium email addresses (of sheriffs)
to cc on this bug. Since the list of sheriffs is
@@ -363,20 +394,23 @@
logging.error("Can't file: %s", title)
return None
- issue = self._format_issue_options(bug_template, title=title,
- description=description, labels=self._get_labels(name.lower()),
- status='Untriaged', owner=owner, cc=[self._OWNER],
+ anchored_summary = self._anchor_summary(bug)
+
+ issue = self._format_issue_options(bug_template, title=bug.title(),
+ description=anchored_summary, labels=bug.labels,
+ status='Untriaged', owner=self._get_owner(bug), cc=bug.cc,
sheriffs=sheriffs)
try:
- bug = self._phapi_client.create_issue(issue)
+ filed_bug = self._phapi_client.create_issue(issue)
except phapi_lib.ProjectHostingApiException as e:
logging.error('Unable to create a bug for issue with title: %s and '
- 'description %s', title, description)
+ 'description %s', bug.title(),
+ anchored_summary)
else:
logging.info('Filing new bug %s, with description %s',
- bug.get('id'), description)
- return bug.get('id')
+ filed_bug.get('id'), anchored_summary)
+ return filed_bug.get('id')
def _modify_bug_report(self, issue_id, comment=''):
@@ -418,9 +452,14 @@
@param marker The marker string to search for to find a duplicate of
this issue.
@return A gdata_lib.Issue instance of the issue that was found, or
- None if no issue was found.
+ None if no issue was found. Also returns None if the marker
+ is None.
"""
+ if marker is None:
+ logging.info('No search marker specified, will create new issue.')
+ return None
+
# Note that this method cannot handle markers which have already been
# html escaped, as it will try and unescape them by converting the &
# to & again, thereby failing deduplication.
@@ -462,7 +501,7 @@
if not issues:
return
- # Breadth first, since open issues/failure probably < comments/issue.
+ # Breadth first, since open issues/bugs probably < comments/issue.
# If we find more than one issue matching a particular anchor assign
# a mystery bug with all relevent information on the owner and return
# the first matching issue.
@@ -485,20 +524,21 @@
return issue
- def report(self, failure, bug_template={}):
+ def report(self, bug, bug_template={}):
"""
- Report a failure to the bug tracker. If this failure has already
+ Report a bug to the bug tracker. If this bug has already
happened, post a comment on the existing bug about it occurring again.
- If this is a new failure, create a new bug about it.
+ If this is a new bug, create a new bug about it.
- @param failure A TestFailure instance about the failure.
+ @param bug: A Bug instance about the bug.
@param bug_template: A template dictionary specifying the default bug
- filing options for failures in this suite.
+ filing options for the bug or failures in this
+ suite.
@return: The issue id of the issue that was either created or modified.
"""
if not self._check_tracker():
- logging.error("Can't file %s", failure.bug_title())
+ logging.error("Can't file %s", bug.title())
return None
# If our search string sends pythons xml module into a state which it
@@ -508,28 +548,28 @@
# issue than fail in dedulicating such cases.
issue = None
try:
- issue = self._find_issue_by_marker(failure.search_marker())
+ issue = self._find_issue_by_marker(bug.search_marker())
except expat.ExpatError as e:
logging.warning('Unable to deduplicate, creating new issue: %s',
str(e))
- summary = '%s\n\n%s%s\n' % (failure.bug_summary(),
- self._SEARCH_MARKER,
- failure.search_marker())
-
if issue:
- comment = '%s\n\n%s' % (failure.bug_title(), summary)
+ comment = '%s\n\n%s' % (bug.title(), self._anchor_summary(bug))
self._modify_bug_report(issue.id, comment)
return issue.id
sheriffs = []
- if failure.lab_error:
- if bug_template.get('labels'):
- self._LAB_ERROR_TEMPLATE['labels'] += bug_template.get('labels')
- bug_template = self._LAB_ERROR_TEMPLATE
- elif failure.suite == 'bvt':
- sheriffs = site_utils.get_sheriffs()
- return self.create_bug_report(summary, failure.bug_title(),
- failure.test, self._get_owner(failure),
- bug_template, sheriffs)
+ # TODO(beeps): move this to classify_bug
+ try:
+ if bug.lab_error:
+ if bug_template.get('labels'):
+ self._LAB_ERROR_TEMPLATE['labels'] += bug_template.get(
+ 'labels')
+ bug_template = self._LAB_ERROR_TEMPLATE
+ elif bug.suite == 'bvt':
+ sheriffs = site_utils.get_sheriffs()
+ except AttributeError:
+ pass
+
+ return self.create_bug_report(bug, bug_template, sheriffs)
diff --git a/server/cros/dynamic_suite/reporting_unittest.py b/server/cros/dynamic_suite/reporting_unittest.py
index b9f8bb5..e6fefc7 100755
--- a/server/cros/dynamic_suite/reporting_unittest.py
+++ b/server/cros/dynamic_suite/reporting_unittest.py
@@ -65,7 +65,6 @@
def setUp(self):
super(ReportingTest, self).setUp()
self.mox.StubOutClassWithMocks(phapi_lib, 'ProjectHostingApiClient')
- self.mox.StubOutClassWithMocks(gdata_lib, 'TrackerComm')
self._orig_project_name = reporting.Reporter._project_name
self._orig_username = reporting.Reporter._username
self._orig_password = reporting.Reporter._password
@@ -91,17 +90,15 @@
Confirms that we call CreateTrackerIssue when an Issue search returns None.
"""
self.mox.StubOutWithMock(reporting.Reporter, '_find_issue_by_marker')
- self.mox.StubOutWithMock(reporting.Reporter, '_get_labels')
- self.mox.StubOutWithMock(reporting.TestFailure, 'bug_summary')
+ self.mox.StubOutWithMock(reporting.TestFailure, 'summary')
client = phapi_lib.ProjectHostingApiClient(mox.IgnoreArg(),
mox.IgnoreArg())
client.create_issue(mox.IgnoreArg()).AndReturn(
{'id': self._FAKE_ISSUE_ID})
- reporting.Reporter._get_labels(mox.IgnoreArg()).AndReturn([])
reporting.Reporter._find_issue_by_marker(mox.IgnoreArg()).AndReturn(
None)
- reporting.TestFailure.bug_summary().AndReturn('')
+ reporting.TestFailure.summary().AndReturn('')
self.mox.ReplayAll()
bug_id = reporting.Reporter().report(self._get_failure())
@@ -117,7 +114,7 @@
returned by the issue search.
"""
self.mox.StubOutWithMock(reporting.Reporter, '_find_issue_by_marker')
- self.mox.StubOutWithMock(reporting.TestFailure, 'bug_summary')
+ self.mox.StubOutWithMock(reporting.TestFailure, 'summary')
issue = self.mox.CreateMock(gdata_lib.Issue)
issue.id = self._FAKE_ISSUE_ID
@@ -128,7 +125,7 @@
reporting.Reporter._find_issue_by_marker(mox.IgnoreArg()).AndReturn(
issue)
- reporting.TestFailure.bug_summary().AndReturn('')
+ reporting.TestFailure.summary().AndReturn('')
self.mox.ReplayAll()
bug_id = reporting.Reporter().report(self._get_failure())
@@ -157,13 +154,11 @@
return True
self.mox.StubOutWithMock(reporting.Reporter, '_find_issue_by_marker')
- self.mox.StubOutWithMock(reporting.Reporter, '_get_labels')
- self.mox.StubOutWithMock(reporting.TestFailure, 'bug_summary')
+ self.mox.StubOutWithMock(reporting.TestFailure, 'summary')
reporting.Reporter._find_issue_by_marker(mox.IgnoreArg()).AndReturn(
None)
- reporting.Reporter._get_labels(mox.IgnoreArg()).AndReturn(['Test'])
- reporting.TestFailure.bug_summary().AndReturn('Summary')
+ reporting.TestFailure.summary().AndReturn('Summary')
mock_host = phapi_lib.ProjectHostingApiClient(mox.IgnoreArg(),
mox.IgnoreArg())
@@ -178,5 +173,126 @@
self.assertEqual(bug_id, self._FAKE_ISSUE_ID)
+ 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 = reporting.Reporter().report(bug)
+
+ self.assertEqual(bug_id, self._FAKE_ISSUE_ID)
+
+
+ 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 = reporting.Reporter().report(bug)
+
+ self.assertEqual(bug_id, self._FAKE_ISSUE_ID)
+
+
+class FindIssueByMarkerTests(mox.MoxTestBase):
+ """Tests the _find_issue_by_marker function."""
+
+ def setUp(self):
+ super(FindIssueByMarkerTests, self).setUp()
+ self.mox.StubOutClassWithMocks(phapi_lib, 'ProjectHostingApiClient')
+ self._orig_project_name = reporting.Reporter._project_name
+ self._orig_username = reporting.Reporter._username
+ self._orig_password = reporting.Reporter._password
+
+ # We want to have some data so that the Reporter doesn't fail at
+ # initialization.
+ reporting.Reporter._project_name = 'project'
+ reporting.Reporter._username = 'username'
+ reporting.Reporter._password = 'password'
+
+
+ def tearDown(self):
+ reporting.Reporter._project_name = self._orig_project_name
+ reporting.Reporter._username = self._orig_username
+ reporting.Reporter._password = self._orig_password
+ super(FindIssueByMarkerTests, self).tearDown()
+
+
+ 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)
+
+
+class AnchorSummaryTests(mox.MoxTestBase):
+ """Tests the _anchor_summary function."""
+
+ def setUp(self):
+ super(AnchorSummaryTests, self).setUp()
+ self.mox.StubOutClassWithMocks(phapi_lib, 'ProjectHostingApiClient')
+ self._orig_project_name = reporting.Reporter._project_name
+ self._orig_username = reporting.Reporter._username
+ self._orig_password = reporting.Reporter._password
+
+ # We want to have some data so that the Reporter doesn't fail at
+ # initialization.
+ reporting.Reporter._project_name = 'project'
+ reporting.Reporter._username = 'username'
+ reporting.Reporter._password = 'password'
+
+
+ def tearDown(self):
+ reporting.Reporter._project_name = self._orig_project_name
+ reporting.Reporter._username = self._orig_username
+ reporting.Reporter._password = self._orig_password
+ super(AnchorSummaryTests, self).tearDown()
+
+
+ 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 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)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/site_utils/suite_scheduler/deduping_scheduler.py b/site_utils/suite_scheduler/deduping_scheduler.py
index 1865fde..fe49cf8 100644
--- a/site_utils/suite_scheduler/deduping_scheduler.py
+++ b/site_utils/suite_scheduler/deduping_scheduler.py
@@ -85,10 +85,10 @@
sheriffs = site_utils.get_sheriffs(lab_only=True)
owner = sheriffs[0] if sheriffs else ''
logging.info('Filing a bug: %s', title)
- return bug_reporter.create_bug_report(description=description,
- title=title,
- name='',
- owner=owner,
+ bug = reporting.Bug(title=title,
+ summary=description,
+ owner=owner)
+ return bug_reporter.create_bug_report(bug,
bug_template=template,
sheriffs=[])
diff --git a/site_utils/suite_scheduler/deduping_scheduler_unittest.py b/site_utils/suite_scheduler/deduping_scheduler_unittest.py
index 3b32212..b56a07a 100644
--- a/site_utils/suite_scheduler/deduping_scheduler_unittest.py
+++ b/site_utils/suite_scheduler/deduping_scheduler_unittest.py
@@ -160,6 +160,7 @@
self.mox.StubOutWithMock(reporting.Reporter, '__init__')
self.mox.StubOutWithMock(reporting.Reporter, 'create_bug_report')
self.mox.StubOutWithMock(site_utils, 'get_sheriffs')
+ self.mox.StubOutClassWithMocks(reporting, 'Bug')
self.scheduler._file_bug = True
# A similar suite has not already been scheduled.
self.afe.get_jobs(name__startswith=self._BUILD,
@@ -180,11 +181,11 @@
self._SUITE, self._BUILD, self._BOARD, self._POOL))
site_utils.get_sheriffs(
lab_only=True).AndReturn(['dummy@chromium.org'])
+ bug = reporting.Bug(title=title,
+ summary=mox.IgnoreArg(),
+ owner='dummy@chromium.org')
reporting.Reporter.create_bug_report(
- description=mox.IgnoreArg(),
- title=title,
- name='',
- owner='dummy@chromium.org',
+ bug,
bug_template = {'labels': ['Suite-Scheduler-Bug'],
'status': 'Available'},
sheriffs=[]).AndReturn(1158)