#!/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 '## 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 '' ]) ] 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()