Commit fc03e67e authored by Aaron Gable's avatar Aaron Gable Committed by Commit Bot

Refactor PRESUBMIT support for tags

We want PRESUBMIT to be able to equally support
BUG= tags (old style) and Git-Footer: footers
(new style). This change refactors the way that
the presubmit api gives access to those properties
so that it is easier to add support for equivalent
footers.

It also limits the scope of tags/footers that it
exposes, as code search shows no PRESUBMIT files
that take advantage of any of the more esoteric
ones.

Bug: 710327, 710803
Change-Id: I86f1d6cb2e1f0aff9653ef3fb455e0a6f47acf5d
Reviewed-on: https://chromium-review.googlesource.com/506450
Commit-Queue: Aaron Gable <agable@chromium.org>
Reviewed-by: 's avatarAndrii Shyshkalov <tandrii@chromium.org>
parent 84a6e9c5
...@@ -39,39 +39,13 @@ BLACKLIST_LINT_FILTERS = [ ...@@ -39,39 +39,13 @@ BLACKLIST_LINT_FILTERS = [
### Description checks ### Description checks
def CheckChangeHasTestField(input_api, output_api):
"""Requires that the changelist have a TEST= field."""
if input_api.change.TEST:
return []
else:
return [output_api.PresubmitNotifyResult(
'If this change requires manual test instructions to QA team, add '
'TEST=[instructions].')]
def CheckChangeHasBugField(input_api, output_api): def CheckChangeHasBugField(input_api, output_api):
"""Requires that the changelist have a BUG= field.""" """Requires that the changelist have a Bug: field."""
if input_api.change.BUG: if input_api.change.BugsFromDescription():
return [] return []
else: else:
return [output_api.PresubmitNotifyResult( return [output_api.PresubmitNotifyResult(
'If this change has an associated bug, add BUG=[bug number].')] 'If this change has an associated bug, add Bug: [bug number].')]
def CheckChangeHasTestedField(input_api, output_api):
"""Requires that the changelist have a TESTED= field."""
if input_api.change.TESTED:
return []
else:
return [output_api.PresubmitError('Changelist must have a TESTED= field.')]
def CheckChangeHasQaField(input_api, output_api):
"""Requires that the changelist have a QA= field."""
if input_api.change.QA:
return []
else:
return [output_api.PresubmitError('Changelist must have a QA= field.')]
def CheckDoNotSubmitInDescription(input_api, output_api): def CheckDoNotSubmitInDescription(input_api, output_api):
...@@ -948,10 +922,8 @@ def _GetRietveldIssueProps(input_api, messages): ...@@ -948,10 +922,8 @@ def _GetRietveldIssueProps(input_api, messages):
def _ReviewersFromChange(change): def _ReviewersFromChange(change):
"""Return the reviewers specified in the |change|, if any.""" """Return the reviewers specified in the |change|, if any."""
reviewers = set() reviewers = set()
if change.R: reviewers.update(change.ReviewersFromDescription())
reviewers.update(set([r.strip() for r in change.R.split(',')])) reviewers.update(change.TBRsFromDescription())
if change.TBR:
reviewers.update(set([r.strip() for r in change.TBR.split(',')]))
# Drop reviewers that aren't specified in email address format. # Drop reviewers that aren't specified in email address format.
return set(reviewer for reviewer in reviewers if '@' in reviewer) return set(reviewer for reviewer in reviewers if '@' in reviewer)
......
...@@ -873,6 +873,30 @@ class Change(object): ...@@ -873,6 +873,30 @@ class Change(object):
raise AttributeError(self, attr) raise AttributeError(self, attr)
return self.tags.get(attr) return self.tags.get(attr)
# TODO(agable): Update these to also get "Git-Footer: Foo"-style footers.
def BugsFromDescription(self):
"""Returns all bugs referenced in the commit description."""
return [b.strip() for b in self.tags.get('BUG', '').split(',') if b.strip()]
def ReviewersFromDescription(self):
"""Returns all reviewers listed in the commit description."""
return [r.strip() for r in self.tags.get('R', '').split(',') if r.strip()]
def TBRsFromDescription(self):
"""Returns all TBR reviewers listed in the commit description."""
return [r.strip() for r in self.tags.get('TBR', '').split(',') if r.strip()]
# TODO(agable): Delete these once we're sure they're unused.
@property
def BUG(self):
return ','.join(self.BugsFromDescription())
@property
def R(self):
return ','.join(self.ReviewersFromDescription())
@property
def TBR(self):
return ','.join(self.TBRsFromDescription())
def AllFiles(self, root=None): def AllFiles(self, root=None):
"""List all files under source control in the repo.""" """List all files under source control in the repo."""
raise NotImplementedError() raise NotImplementedError()
......
...@@ -40,9 +40,9 @@ class PresubmitTestsBase(SuperMoxTestBase): ...@@ -40,9 +40,9 @@ class PresubmitTestsBase(SuperMoxTestBase):
"""Sets up and tears down the mocks but doesn't test anything as-is.""" """Sets up and tears down the mocks but doesn't test anything as-is."""
presubmit_text = """ presubmit_text = """
def CheckChangeOnUpload(input_api, output_api): def CheckChangeOnUpload(input_api, output_api):
if input_api.change.ERROR: if input_api.change.tags.get('ERROR'):
return [output_api.PresubmitError("!!")] return [output_api.PresubmitError("!!")]
if input_api.change.PROMPT_WARNING: if input_api.change.tags.get('PROMPT_WARNING'):
return [output_api.PresubmitPromptWarning("??")] return [output_api.PresubmitPromptWarning("??")]
else: else:
return () return ()
...@@ -304,7 +304,6 @@ class PresubmitUnittest(PresubmitTestsBase): ...@@ -304,7 +304,6 @@ class PresubmitUnittest(PresubmitTestsBase):
description_lines = ('Hello there', description_lines = ('Hello there',
'this is a change', 'this is a change',
'BUG=123', 'BUG=123',
' STORY =http://foo/ \t',
'and some more regular text \t') 'and some more regular text \t')
unified_diff = [ unified_diff = [
'diff --git binary_a.png binary_a.png', 'diff --git binary_a.png binary_a.png',
...@@ -430,9 +429,7 @@ class PresubmitUnittest(PresubmitTestsBase): ...@@ -430,9 +429,7 @@ class PresubmitUnittest(PresubmitTestsBase):
self.failUnless(change.FullDescriptionText() == self.failUnless(change.FullDescriptionText() ==
'\n'.join(description_lines)) '\n'.join(description_lines))
self.failUnless(change.BUG == '123') self.failUnless(change.BugsFromDescription() == ['123'])
self.failUnless(change.STORY == 'http://foo/')
self.failUnless(change.BLEH == None)
self.failUnless(len(change.AffectedFiles()) == 7) self.failUnless(len(change.AffectedFiles()) == 7)
self.failUnless(len(change.AffectedFiles()) == 7) self.failUnless(len(change.AffectedFiles()) == 7)
...@@ -496,7 +493,7 @@ class PresubmitUnittest(PresubmitTestsBase): ...@@ -496,7 +493,7 @@ class PresubmitUnittest(PresubmitTestsBase):
def testExecPresubmitScript(self): def testExecPresubmitScript(self):
description_lines = ('Hello there', description_lines = ('Hello there',
'this is a change', 'this is a change',
'STORY=http://tracker/123') 'BUG=123')
files = [ files = [
['A', 'foo\\blat.cc'], ['A', 'foo\\blat.cc'],
] ]
...@@ -530,22 +527,13 @@ class PresubmitUnittest(PresubmitTestsBase): ...@@ -530,22 +527,13 @@ class PresubmitUnittest(PresubmitTestsBase):
self.failIf(executer.ExecPresubmitScript( self.failIf(executer.ExecPresubmitScript(
('def CheckChangeOnUpload(input_api, output_api):\n' ('def CheckChangeOnUpload(input_api, output_api):\n'
' if not input_api.change.STORY:\n' ' if not input_api.change.BugsFromDescription():\n'
' return (output_api.PresubmitError("!!"))\n' ' return (output_api.PresubmitError("!!"))\n'
' else:\n' ' else:\n'
' return ()'), ' return ()'),
fake_presubmit fake_presubmit
)) ))
self.failUnless(executer.ExecPresubmitScript(
('def CheckChangeOnCommit(input_api, output_api):\n'
' if not input_api.change.ERROR:\n'
' return [output_api.PresubmitError("!!")]\n'
' else:\n'
' return ()'),
fake_presubmit
))
self.assertRaises(presubmit.PresubmitFailure, self.assertRaises(presubmit.PresubmitFailure,
executer.ExecPresubmitScript, executer.ExecPresubmitScript,
'def CheckChangeOnCommit(input_api, output_api):\n' 'def CheckChangeOnCommit(input_api, output_api):\n'
...@@ -741,10 +729,6 @@ def CheckChangeOnUpload(input_api, output_api): ...@@ -741,10 +729,6 @@ def CheckChangeOnUpload(input_api, output_api):
return [output_api.PresubmitError('Tag parsing failed. 1')] return [output_api.PresubmitError('Tag parsing failed. 1')]
if input_api.change.tags['STORY'] != 'http://tracker.com/42': if input_api.change.tags['STORY'] != 'http://tracker.com/42':
return [output_api.PresubmitError('Tag parsing failed. 2')] return [output_api.PresubmitError('Tag parsing failed. 2')]
if input_api.change.BUG != 'boo':
return [output_api.PresubmitError('Tag parsing failed. 6')]
if input_api.change.STORY != 'http://tracker.com/42':
return [output_api.PresubmitError('Tag parsing failed. 7')]
try: try:
y = False y = False
x = input_api.change.invalid x = input_api.change.invalid
...@@ -1588,6 +1572,8 @@ class ChangeUnittest(PresubmitTestsBase): ...@@ -1588,6 +1572,8 @@ class ChangeUnittest(PresubmitTestsBase):
'AllFiles', 'DescriptionText', 'FullDescriptionText', 'AllFiles', 'DescriptionText', 'FullDescriptionText',
'LocalPaths', 'Name', 'OriginalOwnersFiles', 'RepositoryRoot', 'LocalPaths', 'Name', 'OriginalOwnersFiles', 'RepositoryRoot',
'RightHandSideLines', 'SetDescriptionText', 'TAG_LINE_RE', 'RightHandSideLines', 'SetDescriptionText', 'TAG_LINE_RE',
'BUG', 'R', 'TBR', 'BugsFromDescription',
'ReviewersFromDescription', 'TBRsFromDescription',
'author_email', 'issue', 'patchset', 'scm', 'tags', 'author_email', 'issue', 'patchset', 'scm', 'tags',
] ]
# If this test fails, you should add the relevant test. # If this test fails, you should add the relevant test.
...@@ -1603,7 +1589,6 @@ class ChangeUnittest(PresubmitTestsBase): ...@@ -1603,7 +1589,6 @@ class ChangeUnittest(PresubmitTestsBase):
self.assertEquals('foo1', change.Name()) self.assertEquals('foo1', change.Name())
self.assertEquals('foo2', change.DescriptionText()) self.assertEquals('foo2', change.DescriptionText())
self.assertEquals('foo3', change.author_email) self.assertEquals('foo3', change.author_email)
self.assertEquals('ro', change.DRU)
self.assertEquals(3, change.issue) self.assertEquals(3, change.issue)
self.assertEquals(5, change.patchset) self.assertEquals(5, change.patchset)
self.assertEquals(self.fake_root_dir, change.RepositoryRoot()) self.assertEquals(self.fake_root_dir, change.RepositoryRoot())
...@@ -1615,13 +1600,10 @@ class ChangeUnittest(PresubmitTestsBase): ...@@ -1615,13 +1600,10 @@ class ChangeUnittest(PresubmitTestsBase):
'', 'foo\nDRU=ro', self.fake_root_dir, [], 3, 5, '') '', 'foo\nDRU=ro', self.fake_root_dir, [], 3, 5, '')
self.assertEquals('foo', change.DescriptionText()) self.assertEquals('foo', change.DescriptionText())
self.assertEquals('foo\nDRU=ro', change.FullDescriptionText()) self.assertEquals('foo\nDRU=ro', change.FullDescriptionText())
self.assertEquals('ro', change.DRU)
change.SetDescriptionText('bar\nWHIZ=bang') change.SetDescriptionText('bar\nWHIZ=bang')
self.assertEquals('bar', change.DescriptionText()) self.assertEquals('bar', change.DescriptionText())
self.assertEquals('bar\nWHIZ=bang', change.FullDescriptionText()) self.assertEquals('bar\nWHIZ=bang', change.FullDescriptionText())
self.assertEquals('bang', change.WHIZ)
self.assertFalse(change.DRU)
def CommHelper(input_api, cmd, ret=None, **kwargs): def CommHelper(input_api, cmd, ret=None, **kwargs):
...@@ -1684,8 +1666,6 @@ class CannedChecksUnittest(PresubmitTestsBase): ...@@ -1684,8 +1666,6 @@ class CannedChecksUnittest(PresubmitTestsBase):
'CheckChangeHasOnlyOneEol', 'CheckChangeHasNoCR', 'CheckChangeHasOnlyOneEol', 'CheckChangeHasNoCR',
'CheckChangeHasNoCrAndHasOnlyOneEol', 'CheckChangeHasNoTabs', 'CheckChangeHasNoCrAndHasOnlyOneEol', 'CheckChangeHasNoTabs',
'CheckChangeTodoHasOwner', 'CheckChangeTodoHasOwner',
'CheckChangeHasQaField', 'CheckChangeHasTestedField',
'CheckChangeHasTestField',
'CheckChangeLintsClean', 'CheckChangeLintsClean',
'CheckChangeWasUploaded', 'CheckChangeWasUploaded',
'CheckDoNotSubmit', 'CheckDoNotSubmit',
...@@ -1820,24 +1800,6 @@ class CannedChecksUnittest(PresubmitTestsBase): ...@@ -1820,24 +1800,6 @@ class CannedChecksUnittest(PresubmitTestsBase):
presubmit.OutputApi.PresubmitError, presubmit.OutputApi.PresubmitError,
True) True)
def testCannedCheckChangeHasTestField(self):
self.DescriptionTest(presubmit_canned_checks.CheckChangeHasTestField,
'Foo\nTEST=did some stuff', 'Foo\n',
presubmit.OutputApi.PresubmitNotifyResult,
False)
def testCannedCheckChangeHasTestedField(self):
self.DescriptionTest(presubmit_canned_checks.CheckChangeHasTestedField,
'Foo\nTESTED=did some stuff', 'Foo\n',
presubmit.OutputApi.PresubmitError,
False)
def testCannedCheckChangeHasQAField(self):
self.DescriptionTest(presubmit_canned_checks.CheckChangeHasQaField,
'Foo\nQA=BSOD your machine', 'Foo\n',
presubmit.OutputApi.PresubmitError,
False)
def testCannedCheckDoNotSubmitInDescription(self): def testCannedCheckDoNotSubmitInDescription(self):
self.DescriptionTest(presubmit_canned_checks.CheckDoNotSubmitInDescription, self.DescriptionTest(presubmit_canned_checks.CheckDoNotSubmitInDescription,
'Foo\nDO NOTSUBMIT', 'Foo\nDO NOT ' + 'SUBMIT', 'Foo\nDO NOTSUBMIT', 'Foo\nDO NOT ' + 'SUBMIT',
...@@ -2298,8 +2260,8 @@ class CannedChecksUnittest(PresubmitTestsBase): ...@@ -2298,8 +2260,8 @@ class CannedChecksUnittest(PresubmitTestsBase):
change = self.mox.CreateMock(presubmit.Change) change = self.mox.CreateMock(presubmit.Change)
change.issue = issue change.issue = issue
change.author_email = 'john@example.com' change.author_email = 'john@example.com'
change.R = ','.join(manually_specified_reviewers) change.ReviewersFromDescription = lambda: manually_specified_reviewers
change.TBR = '' change.TBRsFromDescription = lambda: []
change.RepositoryRoot = lambda: None change.RepositoryRoot = lambda: None
affected_file = self.mox.CreateMock(presubmit.GitAffectedFile) affected_file = self.mox.CreateMock(presubmit.GitAffectedFile)
input_api = self.MockInputApi(change, False) input_api = self.MockInputApi(change, False)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment