Commit d7dccf56 authored by maruel@chromium.org's avatar maruel@chromium.org

Add InputApi.is_committing to be able to return a failure or notification, depending on the check.

Add input_api.traceback, useful for dumping information.

BUG=none
TEST=none

Review URL: http://codereview.chromium.org/119252

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@17837 0039d316-1c4b-4281-b951-d872f2087c98
parent 32ba260f
...@@ -125,17 +125,23 @@ def _RunPythonUnitTests_LoadTests(input_api, module_name): ...@@ -125,17 +125,23 @@ def _RunPythonUnitTests_LoadTests(input_api, module_name):
def RunPythonUnitTests(input_api, output_api, unit_tests): def RunPythonUnitTests(input_api, output_api, unit_tests):
"""Imports the unit_tests modules and run them.""" """Imports the unit_tests modules and run them."""
# We don't want to hinder users from uploading incomplete patches.
if input_api.is_committing:
message_type = output_api.PresubmitError
else:
message_type = output_api.PresubmitNotifyResult
tests_suite = [] tests_suite = []
outputs = [] outputs = []
for unit_test in unit_tests: for unit_test in unit_tests:
try: try:
tests_suite.extend(_RunPythonUnitTests_LoadTests(unit_test)) tests_suite.extend(_RunPythonUnitTests_LoadTests(unit_test))
except ImportError: except ImportError:
outputs.append(output_api.PresubmitError("Failed to load %s" % unit_test)) outputs.append(message_type("Failed to load %s" % unit_test,
long_text=input_api.traceback.format_exc()))
results = input_api.unittest.TextTestRunner(verbosity=0).run( results = input_api.unittest.TextTestRunner(verbosity=0).run(
input_api.unittest.TestSuite(tests_suite)) input_api.unittest.TestSuite(tests_suite))
if not results.wasSuccessful(): if not results.wasSuccessful():
outputs.append(output_api.PresubmitError( outputs.append(message_type(
"%d unit tests failed." % (results.failures + results.errors))) "%d unit tests failed." % (results.failures + results.errors)))
return outputs return outputs
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
"""Enables directory-specific presubmit checks to run at upload and/or commit. """Enables directory-specific presubmit checks to run at upload and/or commit.
""" """
__version__ = '1.2' __version__ = '1.3'
# TODO(joi) Add caching where appropriate/needed. The API is designed to allow # TODO(joi) Add caching where appropriate/needed. The API is designed to allow
# caching (between all different invocations of presubmit scripts for a given # caching (between all different invocations of presubmit scripts for a given
...@@ -25,6 +25,7 @@ import re # Exposed through the API. ...@@ -25,6 +25,7 @@ import re # Exposed through the API.
import subprocess # Exposed through the API. import subprocess # Exposed through the API.
import sys # Parts exposed through API. import sys # Parts exposed through API.
import tempfile # Exposed through the API. import tempfile # Exposed through the API.
import traceback # Exposed through the API.
import types import types
import unittest # Exposed through the API. import unittest # Exposed through the API.
import urllib2 # Exposed through the API. import urllib2 # Exposed through the API.
...@@ -56,21 +57,6 @@ def normpath(path): ...@@ -56,21 +57,6 @@ def normpath(path):
return os.path.normpath(path) return os.path.normpath(path)
def deprecated(func):
"""This is a decorator which can be used to mark functions as deprecated.
It will result in a warning being emmitted when the function is used."""
def newFunc(*args, **kwargs):
warnings.warn("Call to deprecated function %s." % func.__name__,
category=DeprecationWarning,
stacklevel=2)
return func(*args, **kwargs)
newFunc.__name__ = func.__name__
newFunc.__doc__ = func.__doc__
newFunc.__dict__.update(func.__dict__)
return newFunc
class OutputApi(object): class OutputApi(object):
"""This class (more like a module) gets passed to presubmit scripts so that """This class (more like a module) gets passed to presubmit scripts so that
they can specify various types of results. they can specify various types of results.
...@@ -151,16 +137,18 @@ class InputApi(object): ...@@ -151,16 +137,18 @@ class InputApi(object):
know stuff about the change they're looking at. know stuff about the change they're looking at.
""" """
def __init__(self, change, presubmit_path): def __init__(self, change, presubmit_path, is_committing):
"""Builds an InputApi object. """Builds an InputApi object.
Args: Args:
change: A presubmit.GclChange object. change: A presubmit.GclChange object.
presubmit_path: The path to the presubmit script being processed. presubmit_path: The path to the presubmit script being processed.
is_committing: True if the change is about to be committed.
""" """
# Version number of the presubmit_support script. # Version number of the presubmit_support script.
self.version = [int(x) for x in __version__.split('.')] self.version = [int(x) for x in __version__.split('.')]
self.change = change self.change = change
self.is_committing = is_committing
# We expose various modules and functions as attributes of the input_api # We expose various modules and functions as attributes of the input_api
# so that presubmit scripts don't have to import them. # so that presubmit scripts don't have to import them.
...@@ -173,6 +161,7 @@ class InputApi(object): ...@@ -173,6 +161,7 @@ class InputApi(object):
self.re = re self.re = re
self.subprocess = subprocess self.subprocess = subprocess
self.tempfile = tempfile self.tempfile = tempfile
self.traceback = traceback
self.unittest = unittest self.unittest = unittest
self.urllib2 = urllib2 self.urllib2 = urllib2
...@@ -603,7 +592,7 @@ class PresubmitExecuter(object): ...@@ -603,7 +592,7 @@ class PresubmitExecuter(object):
Return: Return:
A list of result objects, empty if no problems. A list of result objects, empty if no problems.
""" """
input_api = InputApi(self.change, presubmit_path) input_api = InputApi(self.change, presubmit_path, self.committing)
context = {} context = {}
exec script_text in context exec script_text in context
......
...@@ -104,10 +104,10 @@ class PresubmitUnittest(PresubmitTestsBase): ...@@ -104,10 +104,10 @@ class PresubmitUnittest(PresubmitTestsBase):
'ListRelevantPresubmitFiles', 'Main', 'NotImplementedException', 'ListRelevantPresubmitFiles', 'Main', 'NotImplementedException',
'OutputApi', 'ParseFiles', 'PresubmitExecuter', 'OutputApi', 'ParseFiles', 'PresubmitExecuter',
'ScanSubDirs', 'SvnAffectedFile', 'ScanSubDirs', 'SvnAffectedFile',
'cPickle', 'cStringIO', 'deprecated', 'exceptions', 'cPickle', 'cStringIO', 'exceptions',
'fnmatch', 'gcl', 'gclient', 'glob', 'marshal', 'normpath', 'optparse', 'fnmatch', 'gcl', 'gclient', 'glob', 'marshal', 'normpath', 'optparse',
'os', 'pickle', 'presubmit_canned_checks', 're', 'subprocess', 'sys', 'os', 'pickle', 'presubmit_canned_checks', 're', 'subprocess', 'sys',
'tempfile', 'types', 'unittest', 'urllib2', 'warnings', 'tempfile', 'traceback', 'types', 'unittest', 'urllib2', 'warnings',
] ]
# If this test fails, you should add the relevant test. # If this test fails, you should add the relevant test.
self.compareMembers(presubmit, members) self.compareMembers(presubmit, members)
...@@ -476,6 +476,10 @@ def CheckChangeOnUpload(input_api, output_api): ...@@ -476,6 +476,10 @@ 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')]
if 'TEST' in input_api.change.tags: if 'TEST' in input_api.change.tags:
return [output_api.PresubmitError('Tag parsing failed. 3')] return [output_api.PresubmitError('Tag parsing failed. 3')]
if input_api.change.DescriptionText() != 'Blah Blah': if input_api.change.DescriptionText() != 'Blah Blah':
...@@ -517,11 +521,12 @@ class InputApiUnittest(PresubmitTestsBase): ...@@ -517,11 +521,12 @@ class InputApiUnittest(PresubmitTestsBase):
'DepotToLocalPath', 'LocalPaths', 'LocalToDepotPath', 'DepotToLocalPath', 'LocalPaths', 'LocalToDepotPath',
'PresubmitLocalPath', 'RightHandSideLines', 'ServerPaths', 'PresubmitLocalPath', 'RightHandSideLines', 'ServerPaths',
'basename', 'cPickle', 'cStringIO', 'canned_checks', 'change', 'basename', 'cPickle', 'cStringIO', 'canned_checks', 'change',
'marshal', 'os_path', 'pickle', 'platform', 'is_committing', 'marshal', 'os_path', 'pickle', 'platform',
're', 'subprocess', 'tempfile', 'unittest', 'urllib2', 'version', 're', 'subprocess', 'tempfile', 'traceback', 'unittest', 'urllib2',
'version',
] ]
# If this test fails, you should add the relevant test. # If this test fails, you should add the relevant test.
self.compareMembers(presubmit.InputApi(None, './.'), members) self.compareMembers(presubmit.InputApi(None, './.', False), members)
def testDepotToLocalPath(self): def testDepotToLocalPath(self):
presubmit.gclient.CaptureSVNInfo('svn://foo/smurf').AndReturn( presubmit.gclient.CaptureSVNInfo('svn://foo/smurf').AndReturn(
...@@ -529,9 +534,10 @@ class InputApiUnittest(PresubmitTestsBase): ...@@ -529,9 +534,10 @@ class InputApiUnittest(PresubmitTestsBase):
presubmit.gclient.CaptureSVNInfo('svn:/foo/notfound/burp').AndReturn({}) presubmit.gclient.CaptureSVNInfo('svn:/foo/notfound/burp').AndReturn({})
self.mox.ReplayAll() self.mox.ReplayAll()
path = presubmit.InputApi(None, './p').DepotToLocalPath('svn://foo/smurf') path = presubmit.InputApi(None, './p', False).DepotToLocalPath(
'svn://foo/smurf')
self.failUnless(path == 'prout') self.failUnless(path == 'prout')
path = presubmit.InputApi(None, './p').DepotToLocalPath( path = presubmit.InputApi(None, './p', False).DepotToLocalPath(
'svn:/foo/notfound/burp') 'svn:/foo/notfound/burp')
self.failUnless(path == None) self.failUnless(path == None)
...@@ -540,15 +546,17 @@ class InputApiUnittest(PresubmitTestsBase): ...@@ -540,15 +546,17 @@ class InputApiUnittest(PresubmitTestsBase):
presubmit.gclient.CaptureSVNInfo('notfound-food').AndReturn({}) presubmit.gclient.CaptureSVNInfo('notfound-food').AndReturn({})
self.mox.ReplayAll() self.mox.ReplayAll()
path = presubmit.InputApi(None, './p').LocalToDepotPath('smurf') path = presubmit.InputApi(None, './p', False).LocalToDepotPath('smurf')
self.assertEqual(path, 'svn://foo') self.assertEqual(path, 'svn://foo')
path = presubmit.InputApi(None, './p').LocalToDepotPath('notfound-food') path = presubmit.InputApi(None, './p', False).LocalToDepotPath(
'notfound-food')
self.failUnless(path == None) self.failUnless(path == None)
def testInputApiConstruction(self): def testInputApiConstruction(self):
self.mox.ReplayAll() self.mox.ReplayAll()
# Fudge the change object, it's not used during construction anyway # Fudge the change object, it's not used during construction anyway
api = presubmit.InputApi(change=42, presubmit_path='foo/path/PRESUBMIT.py') api = presubmit.InputApi(change=42, presubmit_path='foo/path/PRESUBMIT.py',
is_committing=False)
self.assertEquals(api.PresubmitLocalPath(), 'foo/path') self.assertEquals(api.PresubmitLocalPath(), 'foo/path')
self.assertEquals(api.change, 42) self.assertEquals(api.change, 42)
...@@ -593,7 +601,7 @@ class InputApiUnittest(PresubmitTestsBase): ...@@ -593,7 +601,7 @@ class InputApiUnittest(PresubmitTestsBase):
description='\n'.join(description_lines), description='\n'.join(description_lines),
files=files) files=files)
change = presubmit.GclChange(ci) change = presubmit.GclChange(ci)
api = presubmit.InputApi(change, 'foo/PRESUBMIT.py') api = presubmit.InputApi(change, 'foo/PRESUBMIT.py', False)
affected_files = api.AffectedFiles() affected_files = api.AffectedFiles()
self.assertEquals(len(affected_files), 3) self.assertEquals(len(affected_files), 3)
self.assertEquals(affected_files[0].LocalPath(), self.assertEquals(affected_files[0].LocalPath(),
...@@ -643,7 +651,9 @@ class InputApiUnittest(PresubmitTestsBase): ...@@ -643,7 +651,9 @@ class InputApiUnittest(PresubmitTestsBase):
paths_from_change = change.AbsoluteLocalPaths(include_dirs=True) paths_from_change = change.AbsoluteLocalPaths(include_dirs=True)
self.assertEqual(len(paths_from_change), 3) self.assertEqual(len(paths_from_change), 3)
presubmit_path = join(self.fake_root_dir, 'isdir', 'PRESUBMIT.py') presubmit_path = join(self.fake_root_dir, 'isdir', 'PRESUBMIT.py')
api = presubmit.InputApi(change=change, presubmit_path=presubmit_path) api = presubmit.InputApi(change=change,
presubmit_path=presubmit_path,
is_committing=True)
paths_from_api = api.AbsoluteLocalPaths(include_dirs=True) paths_from_api = api.AbsoluteLocalPaths(include_dirs=True)
self.assertEqual(len(paths_from_api), 2) self.assertEqual(len(paths_from_api), 2)
for absolute_paths in [paths_from_change, paths_from_api]: for absolute_paths in [paths_from_change, paths_from_api]:
...@@ -659,7 +669,7 @@ class InputApiUnittest(PresubmitTestsBase): ...@@ -659,7 +669,7 @@ class InputApiUnittest(PresubmitTestsBase):
change = presubmit.GclChange( change = presubmit.GclChange(
presubmit.gcl.ChangeInfo(name='mychange', issue=0, patchset=0, presubmit.gcl.ChangeInfo(name='mychange', issue=0, patchset=0,
description='Bleh\n', files=None)) description='Bleh\n', files=None))
api = presubmit.InputApi(change, 'foo/PRESUBMIT.py') api = presubmit.InputApi(change, 'foo/PRESUBMIT.py', True)
api.AffectedTextFiles(include_deletes=False) api.AffectedTextFiles(include_deletes=False)
...@@ -821,6 +831,7 @@ class CannedChecksUnittest(PresubmitTestsBase): ...@@ -821,6 +831,7 @@ class CannedChecksUnittest(PresubmitTestsBase):
def MockInputApi(self): def MockInputApi(self):
input_api = self.mox.CreateMock(presubmit.InputApi) input_api = self.mox.CreateMock(presubmit.InputApi)
input_api.re = presubmit.re input_api.re = presubmit.re
input_api.traceback = presubmit.traceback
input_api.urllib2 = self.mox.CreateMock(presubmit.urllib2) input_api.urllib2 = self.mox.CreateMock(presubmit.urllib2)
input_api.unittest = unittest input_api.unittest = unittest
return input_api return input_api
...@@ -945,15 +956,29 @@ class CannedChecksUnittest(PresubmitTestsBase): ...@@ -945,15 +956,29 @@ class CannedChecksUnittest(PresubmitTestsBase):
self.assertEquals(results[0].__class__, self.assertEquals(results[0].__class__,
presubmit.OutputApi.PresubmitError) presubmit.OutputApi.PresubmitError)
def testRunPythonUnitTests1(self): def testRunPythonUnitTestsNoTest(self):
input_api = self.MockInputApi() input_api = self.MockInputApi()
input_api.is_committing = False
self.mox.ReplayAll() self.mox.ReplayAll()
results = presubmit_canned_checks.RunPythonUnitTests( results = presubmit_canned_checks.RunPythonUnitTests(
input_api, presubmit.OutputApi, []) input_api, presubmit.OutputApi, [])
self.assertEquals(results, []) self.assertEquals(results, [])
def testRunPythonUnitTests2(self): def testRunPythonUnitTestsNonExistent1(self):
input_api = self.MockInputApi() input_api = self.MockInputApi()
input_api.is_committing = False
presubmit_canned_checks._RunPythonUnitTests_LoadTests('_non_existent_module'
).AndRaise(exceptions.ImportError('Blehh'))
self.mox.ReplayAll()
results = presubmit_canned_checks.RunPythonUnitTests(
input_api, presubmit.OutputApi, ['_non_existent_module'])
self.assertEquals(len(results), 1)
self.assertEquals(results[0].__class__,
presubmit.OutputApi.PresubmitNotifyResult)
def testRunPythonUnitTestsNonExistent2(self):
input_api = self.MockInputApi()
input_api.is_committing = True
presubmit_canned_checks._RunPythonUnitTests_LoadTests('_non_existent_module' presubmit_canned_checks._RunPythonUnitTests_LoadTests('_non_existent_module'
).AndRaise(exceptions.ImportError('Blehh')) ).AndRaise(exceptions.ImportError('Blehh'))
self.mox.ReplayAll() self.mox.ReplayAll()
...@@ -962,8 +987,21 @@ class CannedChecksUnittest(PresubmitTestsBase): ...@@ -962,8 +987,21 @@ class CannedChecksUnittest(PresubmitTestsBase):
self.assertEquals(len(results), 1) self.assertEquals(len(results), 1)
self.assertEquals(results[0].__class__, presubmit.OutputApi.PresubmitError) self.assertEquals(results[0].__class__, presubmit.OutputApi.PresubmitError)
def testRunPythonUnitTests3(self): def testRunPythonUnitTestsEmpty1(self):
input_api = self.MockInputApi()
input_api.is_committing = False
test_module = self.mox.CreateMockAnything()
presubmit_canned_checks._RunPythonUnitTests_LoadTests('test_module'
).AndReturn([])
self.mox.ReplayAll()
results = presubmit_canned_checks.RunPythonUnitTests(
input_api, presubmit.OutputApi, ['test_module'])
self.assertEquals(results, [])
def testRunPythonUnitTestsEmpty2(self):
input_api = self.MockInputApi() input_api = self.MockInputApi()
input_api.is_committing = True
test_module = self.mox.CreateMockAnything() test_module = self.mox.CreateMockAnything()
presubmit_canned_checks._RunPythonUnitTests_LoadTests('test_module' presubmit_canned_checks._RunPythonUnitTests_LoadTests('test_module'
).AndReturn([]) ).AndReturn([])
...@@ -973,8 +1011,33 @@ class CannedChecksUnittest(PresubmitTestsBase): ...@@ -973,8 +1011,33 @@ class CannedChecksUnittest(PresubmitTestsBase):
input_api, presubmit.OutputApi, ['test_module']) input_api, presubmit.OutputApi, ['test_module'])
self.assertEquals(results, []) self.assertEquals(results, [])
def testRunPythonUnitTests4(self): def testRunPythonUnitTestsFailure1(self):
input_api = self.MockInputApi()
input_api.is_committing = False
input_api.unittest = self.mox.CreateMock(unittest)
test = self.mox.CreateMockAnything()
presubmit_canned_checks._RunPythonUnitTests_LoadTests('test_module'
).AndReturn([test])
runner = self.mox.CreateMockAnything()
input_api.unittest.TextTestRunner(verbosity=0).AndReturn(runner)
suite = self.mox.CreateMockAnything()
input_api.unittest.TestSuite([test]).AndReturn(suite)
test_result = self.mox.CreateMockAnything()
runner.run(suite).AndReturn(test_result)
test_result.wasSuccessful().AndReturn(False)
test_result.failures = 2
test_result.errors = 3
self.mox.ReplayAll()
results = presubmit_canned_checks.RunPythonUnitTests(
input_api, presubmit.OutputApi, ['test_module'])
self.assertEquals(len(results), 1)
self.assertEquals(results[0].__class__,
presubmit.OutputApi.PresubmitNotifyResult)
def testRunPythonUnitTestsFailure2(self):
input_api = self.MockInputApi() input_api = self.MockInputApi()
input_api.is_committing = True
input_api.unittest = self.mox.CreateMock(unittest) input_api.unittest = self.mox.CreateMock(unittest)
test = self.mox.CreateMockAnything() test = self.mox.CreateMockAnything()
presubmit_canned_checks._RunPythonUnitTests_LoadTests('test_module' presubmit_canned_checks._RunPythonUnitTests_LoadTests('test_module'
...@@ -995,8 +1058,9 @@ class CannedChecksUnittest(PresubmitTestsBase): ...@@ -995,8 +1058,9 @@ class CannedChecksUnittest(PresubmitTestsBase):
self.assertEquals(len(results), 1) self.assertEquals(len(results), 1)
self.assertEquals(results[0].__class__, presubmit.OutputApi.PresubmitError) self.assertEquals(results[0].__class__, presubmit.OutputApi.PresubmitError)
def testRunPythonUnitTests5(self): def testRunPythonUnitTestsSuccess(self):
input_api = self.MockInputApi() input_api = self.MockInputApi()
input_api.is_committing = False
input_api.unittest = self.mox.CreateMock(unittest) input_api.unittest = self.mox.CreateMock(unittest)
test = self.mox.CreateMockAnything() test = self.mox.CreateMockAnything()
presubmit_canned_checks._RunPythonUnitTests_LoadTests('test_module' presubmit_canned_checks._RunPythonUnitTests_LoadTests('test_module'
......
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