diff --git a/presubmit_canned_checks.py b/presubmit_canned_checks.py index d72c78715..f12bd1802 100644 --- a/presubmit_canned_checks.py +++ b/presubmit_canned_checks.py @@ -576,7 +576,8 @@ def CheckTreeIsOpen(input_api, output_api, return [] def GetUnitTestsInDirectory( - input_api, output_api, directory, whitelist=None, blacklist=None, env=None): + input_api, output_api, directory, whitelist=None, blacklist=None, env=None, + run_on_python2=True, run_on_python3=True): """Lists all files in a directory and runs them. Doesn't recurse. It's mainly a wrapper for RunUnitTests. Use whitelist and blacklist to filter @@ -609,14 +610,24 @@ def GetUnitTestsInDirectory( 'Out of %d files, found none that matched w=%r, b=%r in directory %s' % (found, whitelist, blacklist, directory)) ] - return GetUnitTests(input_api, output_api, unit_tests, env) + return GetUnitTests( + input_api, output_api, unit_tests, env, run_on_python2, run_on_python3) -def GetUnitTests(input_api, output_api, unit_tests, env=None): +def GetUnitTests( + input_api, output_api, unit_tests, env=None, run_on_python2=True, + run_on_python3=True): """Runs all unit tests in a directory. On Windows, sys.executable is used for unit tests ending with ".py". """ + assert run_on_python3 or run_on_python2, ( + 'At least one of "run_on_python2" or "run_on_python3" must be set.') + def has_py3_shebang(test): + with open(test) as f: + maybe_shebang = f.readline() + return maybe_shebang.startswith('#!') and 'python3' in maybe_shebang + # We don't want to hinder users from uploading incomplete patches. if input_api.is_committing: message_type = output_api.PresubmitError @@ -631,11 +642,26 @@ def GetUnitTests(input_api, output_api, unit_tests, env=None): kwargs = {'cwd': input_api.PresubmitLocalPath()} if env: kwargs['env'] = env - results.append(input_api.Command( - name=unit_test, - cmd=cmd, - kwargs=kwargs, - message=message_type)) + if not unit_test.endswith('.py'): + results.append(input_api.Command( + name=unit_test, + cmd=cmd, + kwargs=kwargs, + message=message_type)) + else: + if has_py3_shebang(unit_test) and run_on_python3: + results.append(input_api.Command( + name=unit_test, + cmd=cmd, + kwargs=kwargs, + message=message_type, + python3=True)) + if run_on_python2: + results.append(input_api.Command( + name=unit_test, + cmd=cmd, + kwargs=kwargs, + message=message_type)) return results diff --git a/presubmit_support.py b/presubmit_support.py index f945d718d..22ace3f40 100755 --- a/presubmit_support.py +++ b/presubmit_support.py @@ -66,7 +66,7 @@ class PresubmitFailure(Exception): class CommandData(object): - def __init__(self, name, cmd, kwargs, message): + def __init__(self, name, cmd, kwargs, message, python3=False): self.name = name self.cmd = cmd self.stdin = kwargs.get('stdin', None) @@ -76,6 +76,7 @@ class CommandData(object): self.kwargs['stdin'] = subprocess.PIPE self.message = message self.info = None + self.python3 = python3 # Adapted from @@ -152,7 +153,11 @@ class ThreadPool(object): This function converts invocation of .py files and invocations of "python" to vpython invocations. """ - vpython = 'vpython.bat' if sys.platform == 'win32' else 'vpython' + vpython = 'vpython' + if test.python3: + vpython += '3' + if sys.platform == 'win32': + vpython += '.bat' cmd = test.cmd if cmd[0] == 'python': diff --git a/tests/presubmit_unittest.py b/tests/presubmit_unittest.py index 21818695f..d17cbad29 100755 --- a/tests/presubmit_unittest.py +++ b/tests/presubmit_unittest.py @@ -58,6 +58,11 @@ class MockTemporaryFile(object): pass +class MockProcess(object): + def __init__(self, returncode): + self.returncode = returncode + + class PresubmitTestsBase(TestCaseUtils, unittest.TestCase): """Sets up and tears down the mocks but doesn't test anything as-is.""" presubmit_text = """ @@ -2521,6 +2526,7 @@ the current line as well! is_committing=False, uncovered_files=set()) + @mock.patch('__builtin__.open', mock.mock_open(read_data='')) def testCannedRunUnitTests(self): change = presubmit.Change( 'foo1', 'description1', self.fake_root_dir, None, 0, 0, None) @@ -2563,6 +2569,143 @@ the current line as well! self.checkstdout('') + @mock.patch('__builtin__.open', mock.mock_open()) + def testCannedRunUnitTestsPython3(self): + open().readline.return_value = '#!/usr/bin/env python3' + change = presubmit.Change( + 'foo1', 'description1', self.fake_root_dir, None, 0, 0, None) + input_api = self.MockInputApi(change, False) + input_api.verbose = True + input_api.PresubmitLocalPath.return_value = self.fake_root_dir + presubmit.sigint_handler.wait.return_value = ('', None) + + subprocess.Popen.side_effect = [ + MockProcess(1), + MockProcess(0), + MockProcess(0), + ] + + unit_tests = ['allo', 'bar.py'] + results = presubmit_canned_checks.RunUnitTests( + input_api, + presubmit.OutputApi, + unit_tests) + self.assertEqual([result.__class__ for result in results], [ + presubmit.OutputApi.PresubmitPromptWarning, + presubmit.OutputApi.PresubmitNotifyResult, + presubmit.OutputApi.PresubmitNotifyResult, + ]) + + cmd = ['bar.py', '--verbose'] + vpython = 'vpython' + vpython3 = 'vpython3' + if input_api.platform == 'win32': + vpython += '.bat' + vpython3 += '.bat' + + self.assertEqual(subprocess.Popen.mock_calls, [ + mock.call( + [vpython] + cmd, cwd=self.fake_root_dir, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, stdin=subprocess.PIPE), + mock.call( + [vpython3] + cmd, cwd=self.fake_root_dir, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, stdin=subprocess.PIPE), + mock.call( + ['allo', '--verbose'], cwd=self.fake_root_dir, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + stdin=subprocess.PIPE), + ]) + + self.checkstdout('') + + @mock.patch('__builtin__.open', mock.mock_open()) + def testCannedRunUnitTestsDontRunOnPython2(self): + open().readline.return_value = '#!/usr/bin/env python3' + change = presubmit.Change( + 'foo1', 'description1', self.fake_root_dir, None, 0, 0, None) + input_api = self.MockInputApi(change, False) + input_api.verbose = True + input_api.PresubmitLocalPath.return_value = self.fake_root_dir + presubmit.sigint_handler.wait.return_value = ('', None) + + subprocess.Popen.side_effect = [ + MockProcess(1), + MockProcess(0), + MockProcess(0), + ] + + unit_tests = ['allo', 'bar.py'] + results = presubmit_canned_checks.RunUnitTests( + input_api, + presubmit.OutputApi, + unit_tests, + run_on_python2=False) + self.assertEqual([result.__class__ for result in results], [ + presubmit.OutputApi.PresubmitPromptWarning, + presubmit.OutputApi.PresubmitNotifyResult, + ]) + + cmd = ['bar.py', '--verbose'] + vpython3 = 'vpython3' + if input_api.platform == 'win32': + vpython3 += '.bat' + + self.assertEqual(subprocess.Popen.mock_calls, [ + mock.call( + [vpython3] + cmd, cwd=self.fake_root_dir, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, stdin=subprocess.PIPE), + mock.call( + ['allo', '--verbose'], cwd=self.fake_root_dir, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + stdin=subprocess.PIPE), + ]) + + self.checkstdout('') + + @mock.patch('__builtin__.open', mock.mock_open()) + def testCannedRunUnitTestsDontRunOnPython3(self): + open().readline.return_value = '#!/usr/bin/env python3' + change = presubmit.Change( + 'foo1', 'description1', self.fake_root_dir, None, 0, 0, None) + input_api = self.MockInputApi(change, False) + input_api.verbose = True + input_api.PresubmitLocalPath.return_value = self.fake_root_dir + presubmit.sigint_handler.wait.return_value = ('', None) + + subprocess.Popen.side_effect = [ + MockProcess(1), + MockProcess(0), + MockProcess(0), + ] + + unit_tests = ['allo', 'bar.py'] + results = presubmit_canned_checks.RunUnitTests( + input_api, + presubmit.OutputApi, + unit_tests, + run_on_python3=False) + self.assertEqual([result.__class__ for result in results], [ + presubmit.OutputApi.PresubmitPromptWarning, + presubmit.OutputApi.PresubmitNotifyResult, + ]) + + cmd = ['bar.py', '--verbose'] + vpython = 'vpython' + if input_api.platform == 'win32': + vpython += '.bat' + + self.assertEqual(subprocess.Popen.mock_calls, [ + mock.call( + [vpython] + cmd, cwd=self.fake_root_dir, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, stdin=subprocess.PIPE), + mock.call( + ['allo', '--verbose'], cwd=self.fake_root_dir, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + stdin=subprocess.PIPE), + ]) + + self.checkstdout('') + def testCannedRunUnitTestsInDirectory(self): change = presubmit.Change( 'foo1', 'description1', self.fake_root_dir, None, 0, 0, None)