Commit bc11731a authored by ilevy@chromium.org's avatar ilevy@chromium.org

Add support for parallel presubmit unit testing.

Enable parallel tests on depot_tools.
On Z620 presubmit times: 3m -> 35s.

Review URL: https://codereview.chromium.org/14247012

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@195377 0039d316-1c4b-4281-b951-d872f2087c98
parent 0a753d6b
...@@ -23,21 +23,21 @@ def CommonChecks(input_api, output_api, tests_to_black_list): ...@@ -23,21 +23,21 @@ def CommonChecks(input_api, output_api, tests_to_black_list):
'R0401', # Cyclic import 'R0401', # Cyclic import
'W0613', # Unused argument 'W0613', # Unused argument
] ]
results.extend(input_api.canned_checks.RunPylint( results.extend(input_api.RunTests(
input_api, input_api.canned_checks.GetPylint(
output_api, input_api,
white_list=[r'.*\.py$'], output_api,
black_list=black_list, white_list=[r'.*\.py$'],
disabled_warnings=disabled_warnings)) black_list=black_list,
disabled_warnings=disabled_warnings) +
# TODO(maruel): Make sure at least one file is modified first. # TODO(maruel): Make sure at least one file is modified first.
# TODO(maruel): If only tests are modified, only run them. # TODO(maruel): If only tests are modified, only run them.
results.extend(input_api.canned_checks.RunUnitTestsInDirectory( input_api.canned_checks.GetUnitTestsInDirectory(
input_api, input_api,
output_api, output_api,
'tests', 'tests',
whitelist=[r'.*test\.py$'], whitelist=[r'.*test\.py$'],
blacklist=tests_to_black_list)) blacklist=tests_to_black_list)))
return results return results
......
...@@ -483,8 +483,7 @@ def CheckTreeIsOpen(input_api, output_api, ...@@ -483,8 +483,7 @@ def CheckTreeIsOpen(input_api, output_api,
long_text=str(e))] long_text=str(e))]
return [] return []
def GetUnitTestsInDirectory(
def RunUnitTestsInDirectory(
input_api, output_api, directory, whitelist=None, blacklist=None): input_api, output_api, directory, whitelist=None, blacklist=None):
"""Lists all files in a directory and runs them. Doesn't recurse. """Lists all files in a directory and runs them. Doesn't recurse.
...@@ -517,10 +516,10 @@ def RunUnitTestsInDirectory( ...@@ -517,10 +516,10 @@ def RunUnitTestsInDirectory(
'Out of %d files, found none that matched w=%r, b=%r in directory %s' 'Out of %d files, found none that matched w=%r, b=%r in directory %s'
% (found, whitelist, blacklist, directory)) % (found, whitelist, blacklist, directory))
] ]
return RunUnitTests(input_api, output_api, unit_tests) return GetUnitTests(input_api, output_api, unit_tests)
def RunUnitTests(input_api, output_api, unit_tests): def GetUnitTests(input_api, output_api, unit_tests):
"""Runs all unit tests in a directory. """Runs all unit tests in a directory.
On Windows, sys.executable is used for unit tests ending with ".py". On Windows, sys.executable is used for unit tests ending with ".py".
...@@ -541,20 +540,14 @@ def RunUnitTests(input_api, output_api, unit_tests): ...@@ -541,20 +540,14 @@ def RunUnitTests(input_api, output_api, unit_tests):
if input_api.verbose: if input_api.verbose:
print('Running %s' % unit_test) print('Running %s' % unit_test)
cmd.append('--verbose') cmd.append('--verbose')
try: results.append(input_api.Command(
if input_api.verbose: name=unit_test,
input_api.subprocess.check_call(cmd, cwd=input_api.PresubmitLocalPath()) cmd=cmd,
else: kwargs={'cwd': input_api.PresubmitLocalPath()},
input_api.subprocess.check_output( message=message_type))
cmd,
stderr=input_api.subprocess.STDOUT,
cwd=input_api.PresubmitLocalPath())
except (OSError, input_api.subprocess.CalledProcessError), e:
results.append(message_type('%s failed!\n%s' % (unit_test, e)))
return results return results
def GetPythonUnitTests(input_api, output_api, unit_tests):
def RunPythonUnitTests(input_api, output_api, unit_tests):
"""Run the unit tests out of process, capture the output and use the result """Run the unit tests out of process, capture the output and use the result
code to determine success. code to determine success.
...@@ -590,14 +583,42 @@ def RunPythonUnitTests(input_api, output_api, unit_tests): ...@@ -590,14 +583,42 @@ def RunPythonUnitTests(input_api, output_api, unit_tests):
backpath.append(env.get('PYTHONPATH')) backpath.append(env.get('PYTHONPATH'))
env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath)) env['PYTHONPATH'] = input_api.os_path.pathsep.join((backpath))
cmd = [input_api.python_executable, '-m', '%s' % unit_test] cmd = [input_api.python_executable, '-m', '%s' % unit_test]
try: results.append(input_api.Command(
input_api.subprocess.check_output( name=unit_test_name,
cmd, stderr=input_api.subprocess.STDOUT, cwd=cwd, env=env) cmd=cmd,
except (OSError, input_api.subprocess.CalledProcessError), e: kwargs={'env': env, 'cwd': cwd},
results.append(message_type('%s failed!\n%s' % (unit_test_name, e))) message=message_type))
return results return results
def RunUnitTestsInDirectory(input_api, *args, **kwargs):
"""Run tests in a directory serially.
For better performance, use GetUnitTestsInDirectory and then
pass to input_api.RunTests.
"""
return input_api.RunTests(
GetUnitTestsInDirectory(input_api, *args, **kwargs), False)
def RunUnitTests(input_api, *args, **kwargs):
"""Run tests serially.
For better performance, use GetUnitTests and then pass to
input_api.RunTests.
"""
return input_api.RunTests(GetUnitTests(input_api, *args, **kwargs), False)
def RunPythonUnitTests(input_api, *args, **kwargs):
"""Run python tests in a directory serially.
DEPRECATED
"""
return input_api.RunTests(
GetPythonUnitTests(input_api, *args, **kwargs), False)
def _FetchAllFiles(input_api, white_list, black_list): def _FetchAllFiles(input_api, white_list, black_list):
"""Hack to fetch all files.""" """Hack to fetch all files."""
# We cannot use AffectedFiles here because we want to test every python # We cannot use AffectedFiles here because we want to test every python
...@@ -626,7 +647,7 @@ def _FetchAllFiles(input_api, white_list, black_list): ...@@ -626,7 +647,7 @@ def _FetchAllFiles(input_api, white_list, black_list):
return files return files
def RunPylint(input_api, output_api, white_list=None, black_list=None, def GetPylint(input_api, output_api, white_list=None, black_list=None,
disabled_warnings=None, extra_paths_list=None): disabled_warnings=None, extra_paths_list=None):
"""Run pylint on python files. """Run pylint on python files.
...@@ -669,6 +690,7 @@ def RunPylint(input_api, output_api, white_list=None, black_list=None, ...@@ -669,6 +690,7 @@ def RunPylint(input_api, output_api, white_list=None, black_list=None,
files = _FetchAllFiles(input_api, white_list, black_list) files = _FetchAllFiles(input_api, white_list, black_list)
if not files: if not files:
return [] return []
files.sort()
input_api.logging.info('Running pylint on %d files', len(files)) input_api.logging.info('Running pylint on %d files', len(files))
input_api.logging.debug('Running pylint on: %s', files) input_api.logging.debug('Running pylint on: %s', files)
...@@ -679,31 +701,23 @@ def RunPylint(input_api, output_api, white_list=None, black_list=None, ...@@ -679,31 +701,23 @@ def RunPylint(input_api, output_api, white_list=None, black_list=None,
env['PYTHONPATH'] = input_api.os_path.pathsep.join( env['PYTHONPATH'] = input_api.os_path.pathsep.join(
extra_paths_list + sys.path).encode('utf8') extra_paths_list + sys.path).encode('utf8')
def run_lint(files): def GetPylintCmd(files):
# We can't import pylint directly due to licensing issues, so we run # Windows needs help running python files so we explicitly specify
# it in another process. Windows needs help running python files so we # the interpreter to use. It also has limitations on the size of
# explicitly specify the interpreter to use. It also has limitations on # the command-line, so we pass arguments via a pipe.
# the size of the command-line, so we pass arguments via a pipe. if len(files) == 1:
command = [input_api.python_executable, description = files[0]
input_api.os_path.join(_HERE, 'third_party', 'pylint.py'), else:
'--args-on-stdin'] description = '%s files' % len(files)
try:
child = input_api.subprocess.Popen(command, env=env, return input_api.Command(
stdin=input_api.subprocess.PIPE) name='Pylint (%s)' % description,
cmd=[input_api.python_executable,
# Dump the arguments to the child process via a pipe. input_api.os_path.join(_HERE, 'third_party', 'pylint.py'),
for filename in files: '--args-on-stdin'],
child.stdin.write(filename + '\n') kwargs={'env': env, 'stdin': '\n'.join(files + extra_args)},
for arg in extra_args: message=error_type)
child.stdin.write(arg + '\n')
child.stdin.close()
child.communicate()
return child.returncode
except OSError:
return 'Pylint failed!'
result = None
# Always run pylint and pass it all the py files at once. # Always run pylint and pass it all the py files at once.
# Passing py files one at time is slower and can produce # Passing py files one at time is slower and can produce
# different results. input_api.verbose used to be used # different results. input_api.verbose used to be used
...@@ -713,17 +727,18 @@ def RunPylint(input_api, output_api, white_list=None, black_list=None, ...@@ -713,17 +727,18 @@ def RunPylint(input_api, output_api, white_list=None, black_list=None,
# a quick local edit to diagnose pylint issues more # a quick local edit to diagnose pylint issues more
# easily. # easily.
if True: if True:
print('Running pylint on %d files.' % len(files)) return [GetPylintCmd(files)]
result = run_lint(sorted(files))
else: else:
for filename in sorted(files): return map(GetPylintCmd, files)
print('Running pylint on %s' % filename)
result = run_lint([filename]) or result
if isinstance(result, basestring): def RunPylint(input_api, *args, **kwargs):
return [error_type(result)] """Legacy presubmit function.
elif result:
return [error_type('Fix pylint errors first.')] For better performance, get all tests and then pass to
return [] input_api.RunTests.
"""
return input_api.RunTests(GetPylint(input_api, *args, **kwargs), False)
# TODO(dpranke): Get the host_url from the input_api instead # TODO(dpranke): Get the host_url from the input_api instead
......
...@@ -15,6 +15,7 @@ __version__ = '1.6.2' ...@@ -15,6 +15,7 @@ __version__ = '1.6.2'
import cpplint import cpplint
import cPickle # Exposed through the API. import cPickle # Exposed through the API.
import cStringIO # Exposed through the API. import cStringIO # Exposed through the API.
import collections
import contextlib import contextlib
import fnmatch import fnmatch
import glob import glob
...@@ -22,6 +23,7 @@ import inspect ...@@ -22,6 +23,7 @@ import inspect
import json # Exposed through the API. import json # Exposed through the API.
import logging import logging
import marshal # Exposed through the API. import marshal # Exposed through the API.
import multiprocessing
import optparse import optparse
import os # Somewhat exposed through the API. import os # Somewhat exposed through the API.
import pickle # Exposed through the API. import pickle # Exposed through the API.
...@@ -54,6 +56,9 @@ class PresubmitFailure(Exception): ...@@ -54,6 +56,9 @@ class PresubmitFailure(Exception):
pass pass
CommandData = collections.namedtuple('CommandData',
['name', 'cmd', 'kwargs', 'message'])
def normpath(path): def normpath(path):
'''Version of os.path.normpath that also changes backward slashes to '''Version of os.path.normpath that also changes backward slashes to
forward slashes when not running on Windows. forward slashes when not running on Windows.
...@@ -104,68 +109,99 @@ class PresubmitOutput(object): ...@@ -104,68 +109,99 @@ class PresubmitOutput(object):
return ''.join(self.written_output) return ''.join(self.written_output)
class OutputApi(object): # Top level object so multiprocessing can pickle
"""An instance of OutputApi gets passed to presubmit scripts so that they # Public access through OutputApi object.
can output various types of results. class _PresubmitResult(object):
""" """Base class for result objects."""
def __init__(self, is_committing): fatal = False
self.is_committing = is_committing should_prompt = False
class PresubmitResult(object):
"""Base class for result objects."""
fatal = False
should_prompt = False
def __init__(self, message, items=None, long_text=''):
"""
message: A short one-line message to indicate errors.
items: A list of short strings to indicate where errors occurred.
long_text: multi-line text output, e.g. from another tool
"""
self._message = message
self._items = []
if items:
self._items = items
self._long_text = long_text.rstrip()
def handle(self, output): def __init__(self, message, items=None, long_text=''):
output.write(self._message) """
message: A short one-line message to indicate errors.
items: A list of short strings to indicate where errors occurred.
long_text: multi-line text output, e.g. from another tool
"""
self._message = message
self._items = items or []
if items:
self._items = items
self._long_text = long_text.rstrip()
def handle(self, output):
output.write(self._message)
output.write('\n')
for index, item in enumerate(self._items):
output.write(' ')
# Write separately in case it's unicode.
output.write(str(item))
if index < len(self._items) - 1:
output.write(' \\')
output.write('\n') output.write('\n')
for index, item in enumerate(self._items): if self._long_text:
output.write(' ') output.write('\n***************\n')
# Write separately in case it's unicode. # Write separately in case it's unicode.
output.write(str(item)) output.write(self._long_text)
if index < len(self._items) - 1: output.write('\n***************\n')
output.write(' \\') if self.fatal:
output.write('\n') output.fail()
if self._long_text:
output.write('\n***************\n')
# Write separately in case it's unicode. # Top level object so multiprocessing can pickle
output.write(self._long_text) # Public access through OutputApi object.
output.write('\n***************\n') class _PresubmitAddReviewers(_PresubmitResult):
if self.fatal: """Add some suggested reviewers to the change."""
output.fail() def __init__(self, reviewers):
super(_PresubmitAddReviewers, self).__init__('')
self.reviewers = reviewers
class PresubmitAddReviewers(PresubmitResult): def handle(self, output):
"""Add some suggested reviewers to the change.""" output.reviewers.extend(self.reviewers)
def __init__(self, reviewers):
super(OutputApi.PresubmitAddReviewers, self).__init__('')
self.reviewers = reviewers
def handle(self, output):
output.reviewers.extend(self.reviewers)
class PresubmitError(PresubmitResult): # Top level object so multiprocessing can pickle
"""A hard presubmit error.""" # Public access through OutputApi object.
fatal = True class _PresubmitError(_PresubmitResult):
"""A hard presubmit error."""
fatal = True
class PresubmitPromptWarning(PresubmitResult):
"""An warning that prompts the user if they want to continue."""
should_prompt = True
class PresubmitNotifyResult(PresubmitResult): # Top level object so multiprocessing can pickle
"""Just print something to the screen -- but it's not even a warning.""" # Public access through OutputApi object.
pass class _PresubmitPromptWarning(_PresubmitResult):
"""An warning that prompts the user if they want to continue."""
should_prompt = True
# Top level object so multiprocessing can pickle
# Public access through OutputApi object.
class _PresubmitNotifyResult(_PresubmitResult):
"""Just print something to the screen -- but it's not even a warning."""
pass
# Top level object so multiprocessing can pickle
# Public access through OutputApi object.
class _MailTextResult(_PresubmitResult):
"""A warning that should be included in the review request email."""
def __init__(self, *args, **kwargs):
super(_MailTextResult, self).__init__()
raise NotImplementedError()
class OutputApi(object):
"""An instance of OutputApi gets passed to presubmit scripts so that they
can output various types of results.
"""
PresubmitResult = _PresubmitResult
PresubmitAddReviewers = _PresubmitAddReviewers
PresubmitError = _PresubmitError
PresubmitPromptWarning = _PresubmitPromptWarning
PresubmitNotifyResult = _PresubmitNotifyResult
MailTextResult = _MailTextResult
def __init__(self, is_committing):
self.is_committing = is_committing
def PresubmitPromptOrNotify(self, *args, **kwargs): def PresubmitPromptOrNotify(self, *args, **kwargs):
"""Warn the user when uploading, but only notify if committing.""" """Warn the user when uploading, but only notify if committing."""
...@@ -173,12 +209,6 @@ class OutputApi(object): ...@@ -173,12 +209,6 @@ class OutputApi(object):
return self.PresubmitNotifyResult(*args, **kwargs) return self.PresubmitNotifyResult(*args, **kwargs)
return self.PresubmitPromptWarning(*args, **kwargs) return self.PresubmitPromptWarning(*args, **kwargs)
class MailTextResult(PresubmitResult):
"""A warning that should be included in the review request email."""
def __init__(self, *args, **kwargs):
super(OutputApi.MailTextResult, self).__init__()
raise NotImplementedError()
class InputApi(object): class InputApi(object):
"""An instance of this object is passed to presubmit scripts so they can """An instance of this object is passed to presubmit scripts so they can
...@@ -284,6 +314,7 @@ class InputApi(object): ...@@ -284,6 +314,7 @@ class InputApi(object):
self.owners_db = owners.Database(change.RepositoryRoot(), self.owners_db = owners.Database(change.RepositoryRoot(),
fopen=file, os_path=self.os_path, glob=self.glob) fopen=file, os_path=self.os_path, glob=self.glob)
self.verbose = verbose self.verbose = verbose
self.Command = CommandData
# Replace <hash_map> and <hash_set> as headers that need to be included # Replace <hash_map> and <hash_set> as headers that need to be included
# with "base/hash_tables.h" instead. # with "base/hash_tables.h" instead.
...@@ -437,6 +468,26 @@ class InputApi(object): ...@@ -437,6 +468,26 @@ class InputApi(object):
"""Returns if a change is TBR'ed.""" """Returns if a change is TBR'ed."""
return 'TBR' in self.change.tags return 'TBR' in self.change.tags
@staticmethod
def RunTests(tests_mix, parallel=True):
tests = []
msgs = []
for t in tests_mix:
if isinstance(t, OutputApi.PresubmitResult):
msgs.append(t)
else:
assert issubclass(t.message, _PresubmitResult)
tests.append(t)
if parallel:
pool = multiprocessing.Pool()
# async recipe works around multiprocessing bug handling Ctrl-C
msgs.extend(pool.map_async(CallCommand, tests).get(99999))
pool.close()
pool.join()
else:
msgs.extend(map(CallCommand, tests))
return [m for m in msgs if m]
class AffectedFile(object): class AffectedFile(object):
"""Representation of a file in a change.""" """Representation of a file in a change."""
...@@ -1238,6 +1289,18 @@ def canned_check_filter(method_names): ...@@ -1238,6 +1289,18 @@ def canned_check_filter(method_names):
for name, method in filtered.iteritems(): for name, method in filtered.iteritems():
setattr(presubmit_canned_checks, name, method) setattr(presubmit_canned_checks, name, method)
def CallCommand(cmd_data):
# multiprocessing needs a top level function with a single argument.
cmd_data.kwargs['stdout'] = subprocess.PIPE
cmd_data.kwargs['stderr'] = subprocess.STDOUT
try:
(out, _), code = subprocess.communicate(cmd_data.cmd, **cmd_data.kwargs)
if code != 0:
return cmd_data.message('%s failed\n%s' % (cmd_data.name, out))
except OSError as e:
return cmd_data.message(
'%s exec failure\n %s\n%s' % (cmd_data.name, e, out))
def Main(argv): def Main(argv):
parser = optparse.OptionParser(usage="%prog [options] <files...>", parser = optparse.OptionParser(usage="%prog [options] <files...>",
......
...@@ -10,9 +10,9 @@ ...@@ -10,9 +10,9 @@
import logging import logging
import os import os
import StringIO import StringIO
import subprocess
import sys import sys
import time import time
import unittest
_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) _ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, _ROOT) sys.path.insert(0, _ROOT)
...@@ -20,6 +20,7 @@ sys.path.insert(0, _ROOT) ...@@ -20,6 +20,7 @@ sys.path.insert(0, _ROOT)
from testing_support.super_mox import mox, SuperMoxTestBase from testing_support.super_mox import mox, SuperMoxTestBase
import owners import owners
import subprocess2 as subprocess
import presubmit_support as presubmit import presubmit_support as presubmit
import rietveld import rietveld
...@@ -157,7 +158,7 @@ class PresubmitUnittest(PresubmitTestsBase): ...@@ -157,7 +158,7 @@ class PresubmitUnittest(PresubmitTestsBase):
self.mox.ReplayAll() self.mox.ReplayAll()
members = [ members = [
'AffectedFile', 'Change', 'DoGetTrySlaves', 'DoPresubmitChecks', 'AffectedFile', 'Change', 'DoGetTrySlaves', 'DoPresubmitChecks',
'GetTrySlavesExecuter', 'GitAffectedFile', 'GetTrySlavesExecuter', 'GitAffectedFile', 'CallCommand', 'CommandData',
'GitChange', 'InputApi', 'ListRelevantPresubmitFiles', 'Main', 'GitChange', 'InputApi', 'ListRelevantPresubmitFiles', 'Main',
'NonexistantCannedCheckFilter', 'OutputApi', 'ParseFiles', 'NonexistantCannedCheckFilter', 'OutputApi', 'ParseFiles',
'PresubmitFailure', 'PresubmitExecuter', 'PresubmitOutput', 'ScanSubDirs', 'PresubmitFailure', 'PresubmitExecuter', 'PresubmitOutput', 'ScanSubDirs',
...@@ -167,7 +168,7 @@ class PresubmitUnittest(PresubmitTestsBase): ...@@ -167,7 +168,7 @@ class PresubmitUnittest(PresubmitTestsBase):
'marshal', 'normpath', 'optparse', 'os', 'owners', 'pickle', 'marshal', 'normpath', 'optparse', 'os', 'owners', 'pickle',
'presubmit_canned_checks', 'random', 're', 'rietveld', 'scm', 'presubmit_canned_checks', 'random', 're', 'rietveld', 'scm',
'subprocess', 'sys', 'tempfile', 'time', 'traceback', 'types', 'unittest', 'subprocess', 'sys', 'tempfile', 'time', 'traceback', 'types', 'unittest',
'urllib2', 'warn', 'urllib2', 'warn', 'collections', 'multiprocessing',
] ]
# 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)
...@@ -881,7 +882,7 @@ class InputApiUnittest(PresubmitTestsBase): ...@@ -881,7 +882,7 @@ class InputApiUnittest(PresubmitTestsBase):
'AffectedTextFiles', 'AffectedTextFiles',
'DEFAULT_BLACK_LIST', 'DEFAULT_WHITE_LIST', 'DEFAULT_BLACK_LIST', 'DEFAULT_WHITE_LIST',
'DepotToLocalPath', 'FilterSourceFile', 'LocalPaths', 'DepotToLocalPath', 'FilterSourceFile', 'LocalPaths',
'LocalToDepotPath', 'LocalToDepotPath', 'Command', 'RunTests',
'PresubmitLocalPath', 'ReadFile', 'RightHandSideLines', 'ServerPaths', 'PresubmitLocalPath', 'ReadFile', 'RightHandSideLines', 'ServerPaths',
'basename', 'cPickle', 'cpplint', 'cStringIO', 'canned_checks', 'change', 'basename', 'cPickle', 'cpplint', 'cStringIO', 'canned_checks', 'change',
'environ', 'glob', 'host_url', 'is_committing', 'json', 'logging', 'environ', 'glob', 'host_url', 'is_committing', 'json', 'logging',
...@@ -1485,6 +1486,13 @@ class ChangeUnittest(PresubmitTestsBase): ...@@ -1485,6 +1486,13 @@ class ChangeUnittest(PresubmitTestsBase):
self.assertEquals('Y', change.AffectedFiles(include_dirs=True)[0].Action()) self.assertEquals('Y', change.AffectedFiles(include_dirs=True)[0].Action())
def CommHelper(input_api, cmd, ret=None, **kwargs):
ret = ret or (('', None), 0)
input_api.subprocess.communicate(
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs
).AndReturn(ret)
class CannedChecksUnittest(PresubmitTestsBase): class CannedChecksUnittest(PresubmitTestsBase):
"""Tests presubmit_canned_checks.py.""" """Tests presubmit_canned_checks.py."""
...@@ -1502,7 +1510,8 @@ class CannedChecksUnittest(PresubmitTestsBase): ...@@ -1502,7 +1510,8 @@ class CannedChecksUnittest(PresubmitTestsBase):
input_api.traceback = presubmit.traceback 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
input_api.subprocess = self.mox.CreateMock(presubmit.subprocess) input_api.subprocess = self.mox.CreateMock(subprocess)
presubmit.subprocess = input_api.subprocess
class fake_CalledProcessError(Exception): class fake_CalledProcessError(Exception):
def __str__(self): def __str__(self):
return 'foo' return 'foo'
...@@ -1517,6 +1526,8 @@ class CannedChecksUnittest(PresubmitTestsBase): ...@@ -1517,6 +1526,8 @@ class CannedChecksUnittest(PresubmitTestsBase):
input_api.platform = sys.platform input_api.platform = sys.platform
input_api.time = time input_api.time = time
input_api.canned_checks = presubmit_canned_checks input_api.canned_checks = presubmit_canned_checks
input_api.Command = presubmit.CommandData
input_api.RunTests = presubmit.InputApi.RunTests
return input_api return input_api
def testMembersChanged(self): def testMembersChanged(self):
...@@ -1544,6 +1555,8 @@ class CannedChecksUnittest(PresubmitTestsBase): ...@@ -1544,6 +1555,8 @@ class CannedChecksUnittest(PresubmitTestsBase):
'CheckSvnForCommonMimeTypes', 'CheckSvnProperty', 'CheckSvnForCommonMimeTypes', 'CheckSvnProperty',
'RunPythonUnitTests', 'RunPylint', 'RunPythonUnitTests', 'RunPylint',
'RunUnitTests', 'RunUnitTestsInDirectory', 'RunUnitTests', 'RunUnitTestsInDirectory',
'GetPythonUnitTests', 'GetPylint',
'GetUnitTests', 'GetUnitTestsInDirectory',
] ]
# If this test fails, you should add the relevant test. # If this test fails, you should add the relevant test.
self.compareMembers(presubmit_canned_checks, members) self.compareMembers(presubmit_canned_checks, members)
...@@ -2117,10 +2130,8 @@ class CannedChecksUnittest(PresubmitTestsBase): ...@@ -2117,10 +2130,8 @@ class CannedChecksUnittest(PresubmitTestsBase):
def testRunPythonUnitTestsNonExistentUpload(self): def testRunPythonUnitTestsNonExistentUpload(self):
input_api = self.MockInputApi(None, False) input_api = self.MockInputApi(None, False)
input_api.subprocess.check_output( CommHelper(input_api, ['pyyyyython', '-m', '_non_existent_module'],
['pyyyyython', '-m', '_non_existent_module'], cwd=None, env=None, ret=(('foo', None), 1), cwd=None, env=None)
stderr=input_api.subprocess.STDOUT).AndRaise(
input_api.subprocess.CalledProcessError())
self.mox.ReplayAll() self.mox.ReplayAll()
results = presubmit_canned_checks.RunPythonUnitTests( results = presubmit_canned_checks.RunPythonUnitTests(
...@@ -2131,10 +2142,8 @@ class CannedChecksUnittest(PresubmitTestsBase): ...@@ -2131,10 +2142,8 @@ class CannedChecksUnittest(PresubmitTestsBase):
def testRunPythonUnitTestsNonExistentCommitting(self): def testRunPythonUnitTestsNonExistentCommitting(self):
input_api = self.MockInputApi(None, True) input_api = self.MockInputApi(None, True)
input_api.subprocess.check_output( CommHelper(input_api, ['pyyyyython', '-m', '_non_existent_module'],
['pyyyyython', '-m', '_non_existent_module'], cwd=None, env=None, ret=(('foo', None), 1), cwd=None, env=None)
stderr=input_api.subprocess.STDOUT).AndRaise(
input_api.subprocess.CalledProcessError())
self.mox.ReplayAll() self.mox.ReplayAll()
results = presubmit_canned_checks.RunPythonUnitTests( results = presubmit_canned_checks.RunPythonUnitTests(
...@@ -2146,10 +2155,8 @@ class CannedChecksUnittest(PresubmitTestsBase): ...@@ -2146,10 +2155,8 @@ class CannedChecksUnittest(PresubmitTestsBase):
input_api = self.MockInputApi(None, False) input_api = self.MockInputApi(None, False)
input_api.unittest = self.mox.CreateMock(unittest) input_api.unittest = self.mox.CreateMock(unittest)
input_api.cStringIO = self.mox.CreateMock(presubmit.cStringIO) input_api.cStringIO = self.mox.CreateMock(presubmit.cStringIO)
input_api.subprocess.check_output( CommHelper(input_api, ['pyyyyython', '-m', 'test_module'],
['pyyyyython', '-m', 'test_module'], cwd=None, env=None, ret=(('foo', None), 1), cwd=None, env=None)
stderr=input_api.subprocess.STDOUT).AndRaise(
input_api.subprocess.CalledProcessError())
self.mox.ReplayAll() self.mox.ReplayAll()
results = presubmit_canned_checks.RunPythonUnitTests( results = presubmit_canned_checks.RunPythonUnitTests(
...@@ -2157,29 +2164,26 @@ class CannedChecksUnittest(PresubmitTestsBase): ...@@ -2157,29 +2164,26 @@ class CannedChecksUnittest(PresubmitTestsBase):
self.assertEquals(len(results), 1) self.assertEquals(len(results), 1)
self.assertEquals(results[0].__class__, self.assertEquals(results[0].__class__,
presubmit.OutputApi.PresubmitNotifyResult) presubmit.OutputApi.PresubmitNotifyResult)
self.assertEquals('test_module failed!\nfoo', results[0]._message) self.assertEquals('test_module failed\nfoo', results[0]._message)
def testRunPythonUnitTestsFailureCommitting(self): def testRunPythonUnitTestsFailureCommitting(self):
input_api = self.MockInputApi(None, True) input_api = self.MockInputApi(None, True)
input_api.subprocess.check_output( CommHelper(input_api, ['pyyyyython', '-m', 'test_module'],
['pyyyyython', '-m', 'test_module'], cwd=None, env=None, ret=(('foo', None), 1), cwd=None, env=None)
stderr=input_api.subprocess.STDOUT).AndRaise(
input_api.subprocess.CalledProcessError())
self.mox.ReplayAll() self.mox.ReplayAll()
results = presubmit_canned_checks.RunPythonUnitTests( results = presubmit_canned_checks.RunPythonUnitTests(
input_api, presubmit.OutputApi, ['test_module']) input_api, presubmit.OutputApi, ['test_module'])
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)
self.assertEquals('test_module failed!\nfoo', results[0]._message) self.assertEquals('test_module failed\nfoo', results[0]._message)
def testRunPythonUnitTestsSuccess(self): def testRunPythonUnitTestsSuccess(self):
input_api = self.MockInputApi(None, False) input_api = self.MockInputApi(None, False)
input_api.cStringIO = self.mox.CreateMock(presubmit.cStringIO) input_api.cStringIO = self.mox.CreateMock(presubmit.cStringIO)
input_api.unittest = self.mox.CreateMock(unittest) input_api.unittest = self.mox.CreateMock(unittest)
input_api.subprocess.check_output( CommHelper(input_api, ['pyyyyython', '-m', 'test_module'],
['pyyyyython', '-m', 'test_module'], cwd=None, env=None, cwd=None, env=None)
stderr=input_api.subprocess.STDOUT)
self.mox.ReplayAll() self.mox.ReplayAll()
results = presubmit_canned_checks.RunPythonUnitTests( results = presubmit_canned_checks.RunPythonUnitTests(
...@@ -2197,23 +2201,15 @@ class CannedChecksUnittest(PresubmitTestsBase): ...@@ -2197,23 +2201,15 @@ class CannedChecksUnittest(PresubmitTestsBase):
pylint = os.path.join(_ROOT, 'third_party', 'pylint.py') pylint = os.path.join(_ROOT, 'third_party', 'pylint.py')
pylintrc = os.path.join(_ROOT, 'pylintrc') pylintrc = os.path.join(_ROOT, 'pylintrc')
# Create a mock Popen object, and set up its expectations. CommHelper(input_api,
child = self.mox.CreateMock(subprocess.Popen) ['pyyyyython', pylint, '--args-on-stdin'],
child.stdin = self.mox.CreateMock(file) env=mox.IgnoreArg(), stdin='file1.py\n--rcfile=%s' % pylintrc)
child.stdin.write('file1.py\n')
child.stdin.write('--rcfile=%s\n' % pylintrc)
child.stdin.close()
child.communicate()
child.returncode = 0
input_api.subprocess.Popen(['pyyyyython', pylint, '--args-on-stdin'],
env=mox.IgnoreArg(), stdin=subprocess.PIPE).AndReturn(child)
self.mox.ReplayAll() self.mox.ReplayAll()
results = presubmit_canned_checks.RunPylint( results = presubmit_canned_checks.RunPylint(
input_api, presubmit.OutputApi) input_api, presubmit.OutputApi)
self.assertEquals([], results) self.assertEquals([], results)
self.checkstdout('Running pylint on 1 files.\n') self.checkstdout('')
def testCheckBuildbotPendingBuildsBad(self): def testCheckBuildbotPendingBuildsBad(self):
input_api = self.MockInputApi(None, True) input_api = self.MockInputApi(None, True)
...@@ -2455,14 +2451,11 @@ class CannedChecksUnittest(PresubmitTestsBase): ...@@ -2455,14 +2451,11 @@ class CannedChecksUnittest(PresubmitTestsBase):
unit_tests = ['allo', 'bar.py'] unit_tests = ['allo', 'bar.py']
input_api.PresubmitLocalPath().AndReturn(self.fake_root_dir) input_api.PresubmitLocalPath().AndReturn(self.fake_root_dir)
input_api.PresubmitLocalPath().AndReturn(self.fake_root_dir) input_api.PresubmitLocalPath().AndReturn(self.fake_root_dir)
input_api.subprocess.check_call( CommHelper(input_api, ['allo', '--verbose'], cwd=self.fake_root_dir)
['allo', '--verbose'], cwd=self.fake_root_dir)
cmd = ['bar.py', '--verbose'] cmd = ['bar.py', '--verbose']
if input_api.platform == 'win32': if input_api.platform == 'win32':
cmd.insert(0, input_api.python_executable) cmd.insert(0, input_api.python_executable)
input_api.subprocess.check_call( CommHelper(input_api, cmd, cwd=self.fake_root_dir, ret=(('', None), 1))
cmd, cwd=self.fake_root_dir).AndRaise(
input_api.subprocess.CalledProcessError())
self.mox.ReplayAll() self.mox.ReplayAll()
results = presubmit_canned_checks.RunUnitTests( results = presubmit_canned_checks.RunUnitTests(
...@@ -2485,7 +2478,8 @@ class CannedChecksUnittest(PresubmitTestsBase): ...@@ -2485,7 +2478,8 @@ class CannedChecksUnittest(PresubmitTestsBase):
path = presubmit.os.path.join(self.fake_root_dir, 'random_directory') path = presubmit.os.path.join(self.fake_root_dir, 'random_directory')
input_api.os_listdir(path).AndReturn(['.', '..', 'a', 'b', 'c']) input_api.os_listdir(path).AndReturn(['.', '..', 'a', 'b', 'c'])
input_api.os_path.isfile = lambda x: not x.endswith('.') input_api.os_path.isfile = lambda x: not x.endswith('.')
input_api.subprocess.check_call( CommHelper(
input_api,
[presubmit.os.path.join('random_directory', 'b'), '--verbose'], [presubmit.os.path.join('random_directory', 'b'), '--verbose'],
cwd=self.fake_root_dir) cwd=self.fake_root_dir)
input_api.logging.debug('Found 5 files, running 1') input_api.logging.debug('Found 5 files, running 1')
......
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