Files
chromium_depot_tools/tests/presubmit_canned_checks_test.py
Josiah Kiehl 4244c70855 Handle Windows command line limits in CheckForCommitObjects.
On Windows, the command line has a character limit of 8191. When checking for commit objects in submodules, the `git ls-tree` command can become very long if many files are affected, potentially exceeding this limit.

This change modifies `CheckForCommitObjects` to check the estimated length of the `git ls-tree` command when running on Windows. If the command line with all affected files would exceed the limit, it falls back to using a recursive `ls-tree` (`-r`) instead of listing each file individually. This prevents command line overflow errors on Windows.

Change-Id: I6a340baefee57f5933473add0601a42ff1e61bb6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/7254474
Auto-Submit: Josiah Kiehl <kiehl@google.com>
Commit-Queue: Josiah Kiehl <kiehl@google.com>
Reviewed-by: Yiwei Zhang <yiwzhang@google.com>
2025-12-11 18:23:44 -08:00

954 lines
38 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright (c) 2021 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os.path
import subprocess
import sys
import unittest
from unittest import mock
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, ROOT_DIR)
from testing_support.presubmit_canned_checks_test_mocks import (
MockFile, MockAffectedFile, MockInputApi, MockOutputApi, MockChange)
import presubmit_canned_checks
# TODO: Should fix these warnings.
# pylint: disable=line-too-long
class InclusiveLanguageCheckTest(unittest.TestCase):
def testBlockedTerms(self):
input_api = MockInputApi()
input_api.change.RepositoryRoot = lambda: ''
input_api.presubmit_local_path = ''
input_api.files = [
MockFile(
os.path.normpath(
'infra/inclusive_language_presubmit_exempt_dirs.txt'), [
'some/dir 2 1',
'some/other/dir 2 1',
]),
MockFile(
os.path.normpath('some/ios/file.mm'),
[
'TEST(SomeClassTest, SomeInteraction, blacklist) {', # nocheck
'}'
]),
MockFile(
os.path.normpath('some/mac/file.mm'),
[
'TEST(SomeClassTest, SomeInteraction, BlackList) {', # nocheck
'}'
]),
MockFile(os.path.normpath('another/ios_file.mm'),
['class SomeTest : public testing::Test blocklist {};']),
MockFile(os.path.normpath('some/ios/file_egtest.mm'),
['- (void)testSomething { V(whitelist); }']), # nocheck
MockFile(
os.path.normpath('some/ios/file_unittest.mm'),
['TEST_F(SomeTest, Whitelist) { V(allowlist); }']), # nocheck
MockFile(
os.path.normpath('some/doc/file.md'),
[
'# Title',
'Some markdown text includes master.', # nocheck
]),
MockFile(
os.path.normpath('some/doc/ok_file.md'),
[
'# Title',
# This link contains a '//' which the matcher thinks is a
# C-style comment, and the 'master' term appears after the
# '//' in the URL, so it gets ignored as a side-effect.
'[Ignored](https://git/project.git/+/master/foo)', # nocheck
]),
MockFile(
os.path.normpath('some/doc/branch_name_file.md'),
[
'# Title',
# Matches appearing before `//` still trigger the check.
'[src/master](https://git/p.git/+/master/foo)', # nocheck
]),
MockFile(
os.path.normpath('some/java/file/TestJavaDoc.java'),
[
'/**',
' * This line contains the word master,', # nocheck
'* ignored because this is a comment. See {@link',
' * https://s/src/+/master:tools/README.md}', # nocheck
' */'
]),
MockFile(
os.path.normpath('some/java/file/TestJava.java'),
[
'class TestJava {',
' public String master;', # nocheck
'}'
]),
MockFile(
os.path.normpath('some/html/file.html'),
[
'<-- an existing html multiline comment',
'says "master" here', # nocheck
'in the comment -->'
])
]
errors = presubmit_canned_checks.CheckInclusiveLanguage(
input_api, MockOutputApi())
self.assertEqual(1, len(errors))
self.assertTrue(
os.path.normpath('some/ios/file.mm') in errors[0].message)
self.assertTrue(
os.path.normpath('another/ios_file.mm') not in errors[0].message)
self.assertTrue(
os.path.normpath('some/mac/file.mm') in errors[0].message)
self.assertTrue(
os.path.normpath('some/ios/file_egtest.mm') in errors[0].message)
self.assertTrue(
os.path.normpath('some/ios/file_unittest.mm') in errors[0].message)
self.assertTrue(
os.path.normpath('some/doc/file.md') not in errors[0].message)
self.assertTrue(
os.path.normpath('some/doc/ok_file.md') not in errors[0].message)
self.assertTrue(
os.path.normpath('some/doc/branch_name_file.md') not in
errors[0].message)
self.assertTrue(
os.path.normpath('some/java/file/TestJavaDoc.java') not in
errors[0].message)
self.assertTrue(
os.path.normpath('some/java/file/TestJava.java') not in
errors[0].message)
self.assertTrue(
os.path.normpath('some/html/file.html') not in errors[0].message)
def testBlockedTermsWithLegacy(self):
input_api = MockInputApi()
input_api.change.RepositoryRoot = lambda: ''
input_api.presubmit_local_path = ''
input_api.files = [
MockFile(
os.path.normpath(
'infra/inclusive_language_presubmit_exempt_dirs.txt'), [
'some/ios 2 1',
'some/other/dir 2 1',
]),
MockFile(
os.path.normpath('some/ios/file.mm'),
[
'TEST(SomeClassTest, SomeInteraction, blacklist) {', # nocheck
'}'
]),
MockFile(
os.path.normpath('some/ios/subdir/file.mm'),
[
'TEST(SomeClassTest, SomeInteraction, blacklist) {', # nocheck
'}'
]),
MockFile(
os.path.normpath('some/mac/file.mm'),
[
'TEST(SomeClassTest, SomeInteraction, BlackList) {', # nocheck
'}'
]),
MockFile(os.path.normpath('another/ios_file.mm'),
['class SomeTest : public testing::Test blocklist {};']),
MockFile(os.path.normpath('some/ios/file_egtest.mm'),
['- (void)testSomething { V(whitelist); }']), # nocheck
MockFile(
os.path.normpath('some/ios/file_unittest.mm'),
['TEST_F(SomeTest, Whitelist) { V(allowlist); }']), # nocheck
]
errors = presubmit_canned_checks.CheckInclusiveLanguage(
input_api, MockOutputApi())
self.assertEqual(1, len(errors))
self.assertTrue(
os.path.normpath('some/ios/file.mm') not in errors[0].message)
self.assertTrue(
os.path.normpath('some/ios/subdir/file.mm') in errors[0].message)
self.assertTrue(
os.path.normpath('another/ios_file.mm') not in errors[0].message)
self.assertTrue(
os.path.normpath('some/mac/file.mm') in errors[0].message)
self.assertTrue(
os.path.normpath('some/ios/file_egtest.mm') not in
errors[0].message)
self.assertTrue(
os.path.normpath('some/ios/file_unittest.mm') not in
errors[0].message)
def testBlockedTermsWithNocheck(self):
input_api = MockInputApi()
input_api.change.RepositoryRoot = lambda: ''
input_api.presubmit_local_path = ''
input_api.files = [
MockFile(
os.path.normpath(
'infra/inclusive_language_presubmit_exempt_dirs.txt'), [
'some/dir 2 1',
'some/other/dir 2 1',
]),
MockFile(
os.path.normpath('some/ios/file.mm'),
[
'TEST(SomeClassTest, SomeInteraction, ',
' blacklist) { // nocheck', # nocheck
'}'
]),
MockFile(
os.path.normpath('some/mac/file.mm'),
[
'TEST(SomeClassTest, SomeInteraction, ',
'BlackList) { // nocheck', # nocheck
'}'
]),
MockFile(os.path.normpath('another/ios_file.mm'),
['class SomeTest : public testing::Test blocklist {};']),
MockFile(os.path.normpath('some/ios/file_egtest.mm'),
['- (void)testSomething { ', 'V(whitelist); } // nocheck'
]), # nocheck
MockFile(
os.path.normpath('some/ios/file_unittest.mm'),
[
'TEST_F(SomeTest, Whitelist) // nocheck', # nocheck
' { V(allowlist); }'
]),
MockFile(
os.path.normpath('some/doc/file.md'),
[
'Master in markdown <!-- nocheck -->', # nocheck
'## Subheading is okay'
]),
MockFile(
os.path.normpath('some/java/file/TestJava.java'),
[
'class TestJava {',
' public String master; // nocheck', # nocheck
'}'
]),
MockFile(
os.path.normpath('some/html/file.html'),
[
'<-- an existing html multiline comment',
'says "master" here --><!-- nocheck -->', # nocheck
'<!-- in the comment -->'
])
]
errors = presubmit_canned_checks.CheckInclusiveLanguage(
input_api, MockOutputApi())
self.assertEqual(0, len(errors))
def testTopLevelDirExcempt(self):
input_api = MockInputApi()
input_api.change.RepositoryRoot = lambda: ''
input_api.presubmit_local_path = ''
input_api.files = [
MockFile(
os.path.normpath(
'infra/inclusive_language_presubmit_exempt_dirs.txt'), [
'. 2 1',
'some/other/dir 2 1',
]),
MockFile(
os.path.normpath('presubmit_canned_checks_test.py'),
[
'TEST(SomeClassTest, SomeInteraction, blacklist) {', # nocheck
'}'
]),
MockFile(
os.path.normpath('presubmit_canned_checks.py'),
['- (void)testSth { V(whitelist); } // nocheck']), # nocheck
]
errors = presubmit_canned_checks.CheckInclusiveLanguage(
input_api, MockOutputApi())
self.assertEqual(1, len(errors))
self.assertTrue(
os.path.normpath('presubmit_canned_checks_test.py') in
errors[0].message)
self.assertTrue(
os.path.normpath('presubmit_canned_checks.py') not in
errors[0].message)
def testChangeIsForSomeOtherRepo(self):
input_api = MockInputApi()
input_api.change.RepositoryRoot = lambda: 'v8'
input_api.presubmit_local_path = ''
input_api.files = [
MockFile(
os.path.normpath('some_file'),
[
'# this is a blacklist', # nocheck
]),
]
errors = presubmit_canned_checks.CheckInclusiveLanguage(
input_api, MockOutputApi())
self.assertEqual([], errors)
def testDirExemptWithComment(self):
input_api = MockInputApi()
input_api.change.RepositoryRoot = lambda: ''
input_api.presubmit_local_path = ''
input_api.files = [
MockFile(
os.path.normpath(
'infra/inclusive_language_presubmit_exempt_dirs.txt'), [
'# this is a comment',
'dir1',
'# dir2',
]),
# this should be excluded
MockFile(
os.path.normpath('dir1/1.py'),
[
'TEST(SomeClassTest, SomeInteraction, blacklist) {', # nocheck
'}'
]),
# this should not be excluded
MockFile(os.path.normpath('dir2/2.py'),
['- (void)testSth { V(whitelist); }']), # nocheck
]
errors = presubmit_canned_checks.CheckInclusiveLanguage(
input_api, MockOutputApi())
self.assertEqual(1, len(errors))
self.assertTrue(os.path.normpath('dir1/1.py') not in errors[0].message)
self.assertTrue(os.path.normpath('dir2/2.py') in errors[0].message)
class DescriptionChecksTest(unittest.TestCase):
def testCheckDescriptionUsesColonInsteadOfEquals(self):
input_api = MockInputApi()
input_api.change.RepositoryRoot = lambda: ''
input_api.presubmit_local_path = ''
# Verify error in case of the attempt to use "Bug=".
input_api.change = MockChange([], 'Broken description\nBug=123')
errors = presubmit_canned_checks.CheckDescriptionUsesColonInsteadOfEquals(
input_api, MockOutputApi())
self.assertEqual(1, len(errors))
self.assertTrue('Bug=' in errors[0].message)
# Verify error in case of the attempt to use "Fixed=".
input_api.change = MockChange([], 'Broken description\nFixed=123')
errors = presubmit_canned_checks.CheckDescriptionUsesColonInsteadOfEquals(
input_api, MockOutputApi())
self.assertEqual(1, len(errors))
self.assertTrue('Fixed=' in errors[0].message)
# Verify error in case of the attempt to use the lower case "bug=".
input_api.change = MockChange([],
'Broken description lowercase\nbug=123')
errors = presubmit_canned_checks.CheckDescriptionUsesColonInsteadOfEquals(
input_api, MockOutputApi())
self.assertEqual(1, len(errors))
self.assertTrue('Bug=' in errors[0].message)
# Verify no error in case of "Bug:"
input_api.change = MockChange([], 'Correct description\nBug: 123')
errors = presubmit_canned_checks.CheckDescriptionUsesColonInsteadOfEquals(
input_api, MockOutputApi())
self.assertEqual(0, len(errors))
# Verify no error in case of "Fixed:"
input_api.change = MockChange([], 'Correct description\nFixed: 123')
errors = presubmit_canned_checks.CheckDescriptionUsesColonInsteadOfEquals(
input_api, MockOutputApi())
self.assertEqual(0, len(errors))
class ChromiumDependencyMetadataCheckTest(unittest.TestCase):
def testDefaultFileFilter(self):
"""Checks the default file filter limits the scope to Chromium dependency
metadata files.
"""
input_api = MockInputApi()
input_api.change.RepositoryRoot = lambda: ''
input_api.files = [
MockFile(os.path.normpath('foo/README.md'), ['Shipped: no?']),
MockFile(os.path.normpath('foo/main.py'), ['Shipped: yes?']),
]
results = presubmit_canned_checks.CheckChromiumDependencyMetadata(
input_api, MockOutputApi())
self.assertEqual(len(results), 0)
def testSkipDeletedFiles(self):
"""Checks validation is skipped for deleted files."""
input_api = MockInputApi()
input_api.change.RepositoryRoot = lambda: ''
input_api.files = [
MockFile(os.path.normpath('foo/README.chromium'), ['No fields'],
action='D'),
]
results = presubmit_canned_checks.CheckChromiumDependencyMetadata(
input_api, MockOutputApi())
self.assertEqual(len(results), 0)
def testFeedbackForNoMetadata(self):
"""Checks presubmit results are returned for files without any metadata."""
input_api = MockInputApi()
input_api.change.RepositoryRoot = lambda: ''
input_api.files = [
MockFile(os.path.normpath('foo/README.chromium'), ['No fields']),
]
results = presubmit_canned_checks.CheckChromiumDependencyMetadata(
input_api, MockOutputApi())
self.assertEqual(len(results), 1)
self.assertTrue("No dependency metadata" in results[0].message)
def testFeedbackForInvalidMetadata(self):
"""Checks presubmit results are returned for files with invalid metadata."""
input_api = MockInputApi()
input_api.change.RepositoryRoot = lambda: ''
test_file = MockFile(os.path.normpath('foo/README.chromium'),
['Shipped: yes?'])
input_api.files = [test_file]
results = presubmit_canned_checks.CheckChromiumDependencyMetadata(
input_api, MockOutputApi())
# There should be 9 results due to
# - missing 5 mandatory fields: Name, URL, Version, License, and
# Security Critical
# - 1 error for insufficent versioning info
# - missing 2 required fields: License File, and
# License Android Compatible
# - Shipped should be only 'yes' or 'no'.
self.assertEqual(len(results), 9)
# Check each presubmit result is associated with the test file.
for result in results:
self.assertEqual(len(result.items), 1)
self.assertEqual(result.items[0], test_file)
class CheckUpdateOwnersFileReferences(unittest.TestCase):
def testShowsWarningIfDeleting(self):
input_api = MockInputApi()
input_api.files = [
MockFile(os.path.normpath('foo/OWNERS'), [], [], action='D'),
]
results = presubmit_canned_checks.CheckUpdateOwnersFileReferences(
input_api, MockOutputApi())
self.assertEqual(1, len(results))
self.assertEqual('warning', results[0].type)
self.assertEqual(1, len(results[0].items))
def testShowsWarningIfMoving(self):
input_api = MockInputApi()
input_api.files = [
MockFile(os.path.normpath('new_directory/OWNERS'), [], [],
action='A'),
MockFile(os.path.normpath('old_directory/OWNERS'), [], [],
action='D'),
]
results = presubmit_canned_checks.CheckUpdateOwnersFileReferences(
input_api, MockOutputApi())
self.assertEqual(1, len(results))
self.assertEqual('warning', results[0].type)
self.assertEqual(1, len(results[0].items))
def testNoWarningIfAdding(self):
input_api = MockInputApi()
input_api.files = [
MockFile(os.path.normpath('foo/OWNERS'), [], [], action='A'),
]
results = presubmit_canned_checks.CheckUpdateOwnersFileReferences(
input_api, MockOutputApi())
self.assertEqual(0, len(results))
class CheckNoNewGitFilesAddedInDependenciesTest(unittest.TestCase):
@mock.patch('presubmit_canned_checks._readDeps')
def testNonNested(self, readDeps):
readDeps.return_value = '''deps = {
'src/foo': {'url': 'bar', 'condition': 'non_git_source'},
'src/components/foo/bar': {'url': 'bar', 'condition': 'non_git_source'},
}'''
input_api = MockInputApi()
input_api.files = [
MockFile('components/foo/file1.java', ['otherFunction']),
MockFile('components/foo/file2.java', ['hasSyncConsent']),
MockFile('chrome/foo/file3.java', ['canSyncFeatureStart']),
MockFile('chrome/foo/file4.java', ['isSyncFeatureEnabled']),
MockFile('chrome/foo/file5.java', ['isSyncFeatureActive']),
]
results = presubmit_canned_checks.CheckNoNewGitFilesAddedInDependencies(
input_api, MockOutputApi())
self.assertEqual(0, len(results))
@mock.patch('presubmit_canned_checks._readDeps')
def testCollision(self, readDeps):
readDeps.return_value = '''deps = {
'src/foo': {'url': 'bar', 'condition': 'non_git_source'},
'src/baz': {'url': 'baz'},
}'''
input_api = MockInputApi()
input_api.files = [
MockAffectedFile('fo', 'content'), # no conflict
MockAffectedFile('foo', 'content'), # conflict
MockAffectedFile('foo/bar', 'content'), # conflict
MockAffectedFile('baz/qux', 'content'), # conflict, but ignored
]
results = presubmit_canned_checks.CheckNoNewGitFilesAddedInDependencies(
input_api, MockOutputApi())
self.assertEqual(2, len(results))
self.assertIn('File: foo', str(results))
self.assertIn('File: foo/bar', str(results))
@mock.patch('presubmit_canned_checks._readDeps')
def testNoDeps(self, readDeps):
readDeps.return_value = '' # Empty deps
input_api = MockInputApi()
input_api.files = [
MockAffectedFile('fo', 'content'), # no conflict
MockAffectedFile('foo', 'content'), # conflict
MockAffectedFile('foo/bar', 'content'), # conflict
MockAffectedFile('baz/qux', 'content'), # conflict, but ignored
]
results = presubmit_canned_checks.CheckNoNewGitFilesAddedInDependencies(
input_api, MockOutputApi())
self.assertEqual(0, len(results))
class CheckNewDEPSHooksHasRequiredReviewersTest(unittest.TestCase):
def setUp(self):
self.input_api = MockInputApi()
self.input_api.change = MockChange([], issue=123)
self.input_api.change.RepositoryRoot = lambda: ''
def test_no_gerrit_cl(self):
self.input_api.change = MockChange([], issue=None)
results = presubmit_canned_checks.CheckNewDEPSHooksHasRequiredReviewers(
self.input_api, MockOutputApi())
self.assertEqual(0, len(results))
def test_no_deps_file_change(self):
self.input_api.files = [
MockAffectedFile('foo.py', 'content'),
]
results = presubmit_canned_checks.CheckNewDEPSHooksHasRequiredReviewers(
self.input_api, MockOutputApi())
self.assertEqual(0, len(results))
def test_new_deps_hook(self):
gerrit_mock = mock.Mock()
self.input_api.gerrit = gerrit_mock
test_cases = [
{
'name': 'no new hooks',
'old_contents': ['hooks = []'],
'new_contents': ['hooks = []'],
'reviewers': [],
},
{
'name':
'add new hook and require review',
'old_contents': ['hooks = [{"name": "old_hook"}]'],
'new_contents': [
'hooks = [{"name": "old_hook"}, {"name": "new_hook"}, {"name": "new_hook_2"}]'
],
'reviewers': [],
'expected_error_msg':
'New DEPS hooks (new_hook, new_hook_2) are found. Please '
'request review from one of the following reviewers:\n '
'* foo@chromium.org\n * bar@chromium.org\n * baz@chromium.org'
},
{
'name':
'add new hook and require approval',
'old_contents': ['hooks = [{"name": "old_hook"}]'],
'new_contents': [
'hooks = [{"name": "old_hook"}, {"name": "new_hook"}, {"name": "new_hook_2"}]'
],
'submitting':
True,
'reviewers': ['not_relevant@chromium.org'],
'expected_error_msg':
'New DEPS hooks (new_hook, new_hook_2) are found. The CL must '
'be approved by one of the following reviewers:\n'
' * foo@chromium.org\n * bar@chromium.org\n * baz@chromium.org'
},
{
'name':
'add new hook and reviewer is already added',
'old_contents': ['hooks = [{"name": "old_hook"}]'],
'new_contents': [
'hooks = [{"name": "old_hook"}, {"name": "new_hook"}, {"name": "new_hook_2"}]'
],
'reviewers': ['baz@chromium.org'],
},
{
'name':
'add new hook and reviewer already approves',
'old_contents': ['hooks = [{"name": "old_hook"}]'],
'new_contents': [
'hooks = [{"name": "old_hook"}, {"name": "new_hook"}, {"name": "new_hook_2"}]'
],
'submitting':
True,
'reviewers': ['foo@chromium.org'],
},
{
'name':
'change existing hook',
'old_contents': [
'hooks = [{"name": "existing_hook", "action": ["run", "./test.sh"]}]'
],
'new_contents': [
'hooks = [{"name": "existing_hook", "action": ["run", "./test_v2.sh"]}]'
],
'reviewers': [],
},
{
'name':
'remove hook',
'old_contents':
['hooks = [{"name": "old_hook"}, {"name": "hook_to_remove"}]'],
'new_contents': ['hooks = [{"name": "old_hook"}]'],
'reviewers': [],
},
]
for case in test_cases:
with self.subTest(case_name=case['name']):
self.input_api.files = [
MockFile('OWNERS', [
'per-file DEPS=foo@chromium.org # For new DEPS hook',
'per-file DEPS=bar@chromium.org, baz@chromium.org # For new DEPS hook'
]),
MockAffectedFile('DEPS',
old_contents=case['old_contents'],
new_contents=case['new_contents']),
]
if case.get('submitting', False):
self.input_api.is_committing = True
self.input_api.dry_run = False
gerrit_mock.GetChangeReviewers.return_value = case['reviewers']
results = presubmit_canned_checks.CheckNewDEPSHooksHasRequiredReviewers(
self.input_api,
MockOutputApi(),
)
if 'expected_error_msg' in case:
self.assertEqual(1, len(results))
self.assertEqual(case['expected_error_msg'],
results[0].message)
else:
self.assertEqual(0, len(results))
class CheckAyeAyeTest(unittest.TestCase):
def setUp(self):
super(CheckAyeAyeTest, self).setUp()
self.addCleanup(mock.patch.stopall)
self.input_api = MockInputApi()
self.output_api = MockOutputApi()
self.mock_repo_root = mock.patch.object(self.input_api.change,
'RepositoryRoot',
create=True).start()
self.mock_repo_root.return_value = '/fake/repo/root'
self.mock_popen = mock.patch.object(self.input_api.subprocess,
'Popen',
autospec=True).start()
self.mock_proc = mock.Mock()
self.mock_popen.return_value = self.mock_proc
self.input_api.subprocess.PIPE = subprocess.PIPE
self.input_api.subprocess.STDOUT = subprocess.STDOUT
self.mock_exists = mock.patch.object(presubmit_canned_checks._os.path,
'exists',
autospec=True).start()
self.mock_exists.return_value = True
def test_ayeaye_findings(self):
# Simulate alint output with color codes
alint_output = (
"\x1b[31mERROR:\x1b[0m This is an error.\n"
"Some other info line\n"
"\x1b[33mWARNING:\x1b[0m This is a warning.\n"
"\x1b[94mINFO:\x1b[0m This is an info.\n"
"\x1b[31mERROR:\x1b[0m Another error.\n"
"\x1b[33mWARNING:\x1b[0m Another warning.").encode('utf-8')
self.mock_proc.communicate.return_value = (alint_output, b'')
self.mock_proc.returncode = 0
results = presubmit_canned_checks.CheckAyeAye(self.input_api,
self.output_api)
self.assertEqual(len(results), 4)
result_types = sorted([r.type for r in results])
self.assertEqual(result_types, ['error', 'error', 'warning', 'warning'])
messages = sorted([r.message for r in results])
expected_messages = sorted([
"This is an error.",
"Another error.",
"This is a warning.",
"Another warning.",
])
self.assertEqual(messages, expected_messages)
self.mock_popen.assert_called_once_with(
['/google/bin/releases/alint/alint'],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
cwd='/fake/repo/root')
def test_ayeaye_no_findings(self):
self.mock_proc.communicate.return_value = (
b"\x1b[94mINFO:\x1b[0m All good", b'')
self.mock_proc.returncode = 0
results = presubmit_canned_checks.CheckAyeAye(self.input_api,
self.output_api)
self.assertEqual(len(results), 0)
def test_ayeaye_alint_not_found(self):
self.mock_exists.return_value = False
results = presubmit_canned_checks.CheckAyeAye(self.input_api,
self.output_api)
self.assertEqual(len(results), 0)
def test_ayeaye_subprocess_exception(self):
self.mock_popen.side_effect = Exception("BOOM")
results = presubmit_canned_checks.CheckAyeAye(self.input_api,
self.output_api)
self.assertEqual(len(results), 1)
self.assertEqual(results[0].type, 'error')
# Exact message depends on Exception type, so check for key parts
self.assertIn("Unexpected error in CheckAyeAye:", results[0].message)
self.assertIn("BOOM", results[0].message)
def test_ayeaye_alint_fails(self):
alint_output = (
"\x1b[31mERROR:\x1b[0m Failed to run.\n").encode('utf-8')
self.mock_proc.communicate.return_value = (alint_output, b'')
self.mock_proc.returncode = 1 # Non-zero return code
results = presubmit_canned_checks.CheckAyeAye(self.input_api,
self.output_api)
self.assertEqual(len(results), 1)
self.assertEqual(results[0].type, 'error')
self.assertIn("Failed to run.", results[0].message)
class CheckForCommitObjectsTest(unittest.TestCase):
def setUp(self):
self.input_api = MockInputApi()
self.input_api.change.scm = 'git'
self.input_api.subprocess = mock.Mock()
self.output_api = MockOutputApi()
self.patcher = mock.patch('presubmit_canned_checks._ParseDeps')
self.mock_parse_deps = self.patcher.start()
self.mock_parse_deps.return_value = {'git_dependencies': 'DEPS'}
self.input_api.change.RepositoryRoot = lambda: ''
def tearDown(self):
self.patcher.stop()
def testNoGitlinks(self):
# No gitlinks at all.
self.input_api.subprocess.check_output.side_effect = [
b'', # git show HEAD:DEPS
b'100644 blob 1234\tfile.txt\0'
]
results = presubmit_canned_checks.CheckForCommitObjects(
self.input_api, self.output_api)
self.assertEqual(0, len(results))
def testGitlinkFound(self):
# One gitlink found.
self.input_api.subprocess.check_output.side_effect = [
b'', # git show HEAD:DEPS
b'160000 commit 1234\tsubmodule\0'
]
results = presubmit_canned_checks.CheckForCommitObjects(
self.input_api, self.output_api)
self.assertEqual(1, len(results))
self.assertEqual('submodule', results[0].items[0])
def testGitlinkMiddle(self):
# Gitlink in the middle of other files.
self.input_api.subprocess.check_output.side_effect = [
b'', # git show HEAD:DEPS
b'100644 blob 1111\tfile1\0'
b'160000 commit 2222\tsubmodule\0'
b'100644 blob 3333\tfile2\0'
]
results = presubmit_canned_checks.CheckForCommitObjects(
self.input_api, self.output_api)
self.assertEqual(1, len(results))
self.assertEqual('submodule', results[0].items[0])
def testGitlinkStart(self):
# Gitlink at the very start.
self.input_api.subprocess.check_output.side_effect = [
b'', # git show HEAD:DEPS
b'160000 commit 2222\tsubmodule\0'
b'100644 blob 3333\tfile2\0'
]
results = presubmit_canned_checks.CheckForCommitObjects(
self.input_api, self.output_api)
self.assertEqual(1, len(results))
self.assertEqual('submodule', results[0].items[0])
def testGitlinkEnd(self):
# Gitlink at the very end.
self.input_api.subprocess.check_output.side_effect = [
b'', # git show HEAD:DEPS
b'100644 blob 3333\tfile2\0'
b'160000 commit 2222\tsubmodule\0'
]
results = presubmit_canned_checks.CheckForCommitObjects(
self.input_api, self.output_api)
self.assertEqual(1, len(results))
self.assertEqual('submodule', results[0].items[0])
def testMultipleGitlinks(self):
# Multiple gitlinks.
self.input_api.subprocess.check_output.side_effect = [
b'', # git show HEAD:DEPS
b'160000 commit 1111\tsub1\0'
b'100644 blob 2222\tfile\0'
b'160000 commit 3333\tsub2\0'
]
results = presubmit_canned_checks.CheckForCommitObjects(
self.input_api, self.output_api)
self.assertEqual(1, len(results))
self.assertEqual(2, len(results[0].items))
self.assertIn('sub1', results[0].items)
self.assertIn('sub2', results[0].items)
def testFalsePositiveText(self):
# "160000" in filename but not mode.
self.input_api.subprocess.check_output.side_effect = [
b'', # git show HEAD:DEPS
b'100644 blob 1234\t160000_file.txt\0'
]
results = presubmit_canned_checks.CheckForCommitObjects(
self.input_api, self.output_api)
self.assertEqual(0, len(results))
def testRunFromSubdir_SmallFiles_NoSubmodules(self):
self.input_api.presubmit_local_path = os.path.join(ROOT_DIR, 'subdir')
self.input_api.change.RepositoryRoot = lambda: ROOT_DIR
self.input_api.files = [MockAffectedFile('foo.txt', 'content')]
self.input_api.subprocess.check_output.return_value = b''
results = presubmit_canned_checks.CheckForCommitObjects(
self.input_api, self.output_api)
self.assertEqual(0, len(results))
def testRunFromSubdir_SmallFiles_WithSubmodules(self):
self.input_api.presubmit_local_path = os.path.join(ROOT_DIR, 'subdir')
self.input_api.change.RepositoryRoot = lambda: ROOT_DIR
self.input_api.files = [MockAffectedFile('foo.txt', 'content')]
self.input_api.subprocess.check_output.return_value = b'160000 commit 1234\tsubmodule\0'
results = presubmit_canned_checks.CheckForCommitObjects(
self.input_api, self.output_api)
self.assertEqual(1, len(results))
self.assertIn('submodule', results[0].items)
def testRunFromSubdir_LargeFiles_NoSubmodules(self):
self.input_api.presubmit_local_path = os.path.join(ROOT_DIR, 'subdir')
self.input_api.change.RepositoryRoot = lambda: ROOT_DIR
self.input_api.files = [
MockAffectedFile(f'f{i}', '') for i in range(1001)
]
self.input_api.subprocess.check_output.return_value = b''
results = presubmit_canned_checks.CheckForCommitObjects(
self.input_api, self.output_api)
self.assertEqual(0, len(results))
def testRunFromSubdir_LargeFiles_WithSubmodules(self):
self.input_api.presubmit_local_path = os.path.join(ROOT_DIR, 'subdir')
self.input_api.change.RepositoryRoot = lambda: ROOT_DIR
self.input_api.files = [
MockAffectedFile(f'f{i}', '') for i in range(1001)
]
self.input_api.subprocess.check_output.return_value = b'160000 commit 1234\tsubmodule\0'
results = presubmit_canned_checks.CheckForCommitObjects(
self.input_api, self.output_api)
self.assertEqual(1, len(results))
self.assertIn('submodule', results[0].items)
def testWindowsCommandLineLimit(self):
# On Windows, if the command line is too long, we should fall back to a
# recursive ls-tree.
self.input_api.platform = 'win32'
self.input_api.files = [
MockAffectedFile('a' * 100, '') for i in range(100)
]
self.input_api.subprocess.check_output.return_value = b''
presubmit_canned_checks.CheckForCommitObjects(
self.input_api, self.output_api)
# The first call is to `git show HEAD:DEPS`.
# The second call is to `git ls-tree`.
self.assertEqual(2, self.input_api.subprocess.check_output.call_count)
ls_tree_cmd = self.input_api.subprocess.check_output.call_args_list[1][0][0]
self.assertIn('-r', ls_tree_cmd)
def testWindowsCommandLineNotTooLong(self):
# On Windows, if the command line is not too long, we should pass the
# file list.
self.input_api.platform = 'win32'
self.input_api.files = [
MockAffectedFile('foo.txt', '')
]
self.input_api.subprocess.check_output.return_value = b''
presubmit_canned_checks.CheckForCommitObjects(
self.input_api, self.output_api)
# The first call is to `git show HEAD:DEPS`.
# The second call is to `git ls-tree`.
self.assertEqual(2, self.input_api.subprocess.check_output.call_count)
ls_tree_cmd = self.input_api.subprocess.check_output.call_args_list[1][0][0]
self.assertNotIn('-r', ls_tree_cmd)
self.assertIn('--', ls_tree_cmd)
self.assertIn('foo.txt', ls_tree_cmd)
if __name__ == '__main__':
unittest.main()