blob: 15b91ad8d44cf589337a7ec8c6342ad761829f5e [file] [log] [blame]
Mike Frysinger4f994402019-09-13 17:40:45 -04001#!/usr/bin/env python3
Jon Salz98255932012-08-18 14:48:02 +08002# -*- coding: utf-8 -*-
3# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
Mike Frysingerae409522014-02-01 03:16:11 -05007"""Unittests for pre-upload.py."""
8
Mike Frysinger09d6a3d2013-10-08 22:21:03 -04009from __future__ import print_function
10
Mike Frysinger180ecd62020-08-19 00:41:51 -040011import configparser
Keigo Oka7e880ac2019-07-03 15:03:43 +090012import datetime
David Jamesc3b68b32013-04-03 09:17:03 -070013import os
14import sys
Mike Frysingerf6a29772020-08-22 03:57:08 -040015from unittest import mock
Mike Frysingerfd481ce2019-09-13 18:14:48 -040016
Mike Frysingerbf8b91c2014-02-01 02:50:27 -050017import errors
18
Jon Salz98255932012-08-18 14:48:02 +080019# pylint: disable=W0212
Mike Frysinger65d93c12014-02-01 02:59:46 -050020# We access private members of the pre_upload module all over the place.
21
Mike Frysinger55f85b52014-12-18 14:45:21 -050022# Make sure we can find the chromite paths.
23sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)),
24 '..', '..'))
Jon Salz98255932012-08-18 14:48:02 +080025
Mike Frysingerfd481ce2019-09-13 18:14:48 -040026# The sys.path monkey patching confuses the linter.
27# pylint: disable=wrong-import-position
Mike Frysinger71e643e2019-09-13 17:26:39 -040028from chromite.lib import constants
29from chromite.lib import cros_build_lib
Mike Frysinger65d93c12014-02-01 02:59:46 -050030from chromite.lib import cros_test_lib
Mike Frysingerd3bd32c2014-11-24 23:34:29 -050031from chromite.lib import git
Daniel Erata350fd32014-09-29 14:02:34 -070032from chromite.lib import osutils
Mike Frysinger65d93c12014-02-01 02:59:46 -050033
34
Mike Frysingerff4768e2020-02-27 18:48:13 -050035assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
36
37
Jon Salz98255932012-08-18 14:48:02 +080038pre_upload = __import__('pre-upload')
39
40
Alex Deymo643ac4c2015-09-03 10:40:50 -070041def ProjectNamed(project_name):
42 """Wrapper to create a Project with just the name"""
43 return pre_upload.Project(project_name, None, None)
44
45
Mike Frysingerb2496652019-09-12 23:35:46 -040046class PreUploadTestCase(cros_test_lib.MockTestCase):
47 """Common test case base."""
48
49 def setUp(self):
50 pre_upload.CACHE.clear()
51
52
53class TryUTF8DecodeTest(PreUploadTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -050054 """Verify we sanely handle unicode content."""
55
Mike Frysinger71e643e2019-09-13 17:26:39 -040056 def setUp(self):
Mike Frysinger7bb709f2019-09-29 23:20:12 -040057 self.rc_mock = self.PatchObject(cros_build_lib, 'run')
Mike Frysinger71e643e2019-09-13 17:26:39 -040058
59 def _run(self, content):
60 """Helper for round tripping through _run_command."""
61 self.rc_mock.return_value = cros_build_lib.CommandResult(
62 output=content, returncode=0)
63 return pre_upload._run_command([])
64
65 def testEmpty(self):
66 """Check empty output."""
67 ret = self._run(b'')
68 self.assertEqual('', ret)
69
70 if sys.version_info.major < 3:
71 ret = self._run('')
72 self.assertEqual(u'', ret)
73
74 def testAscii(self):
75 """Check ascii output."""
76 ret = self._run(b'abc')
77 self.assertEqual('abc', ret)
78
79 if sys.version_info.major < 3:
80 ret = self._run('abc')
81 self.assertEqual(u'abc', ret)
82
83 def testUtf8(self):
84 """Check valid UTF-8 output."""
85 text = u'你好布萊恩'
86 ret = self._run(text.encode('utf-8'))
87 self.assertEqual(text, ret)
88
89 def testBinary(self):
90 """Check binary (invalid UTF-8) output."""
91 ret = self._run(b'hi \x80 there')
Mike Frysinger8a4e8942019-09-16 23:43:49 -040092 self.assertEqual(u'hi \ufffd there', ret)
Jon Salz98255932012-08-18 14:48:02 +080093
94
Daisuke Nojiri2089e012020-08-20 15:12:36 -070095class CheckKeywordsTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Bernie Thompson8e26f742020-07-23 14:32:31 -070096 """Tests for _check_keywords."""
97
98 def setUp(self):
99 self.PatchObject(pre_upload, '_get_affected_files',
100 return_value=['x.ebuild'])
101 self.PatchObject(pre_upload, '_filter_files', return_value=['x.ebuild'])
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700102 # First call for blocked_terms.txt and second call for unblocked_terms.txt.
103 self.rf_mock = self.PatchObject(
104 osutils, 'ReadFile',
105 side_effect=['scruffy\nmangy\ndog.?pile\ncat.?circle', 'fox'])
Bernie Thompson8e26f742020-07-23 14:32:31 -0700106 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
107 self.desc_mock = self.PatchObject(pre_upload, '_get_commit_desc')
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700108 self.project = pre_upload.Project(name='PROJECT', dir=self.tempdir,
109 remote=None)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700110
111 def test_good_cases(self):
112 self.desc_mock.return_value = 'Commit Message.\nLine 2'
113 self.diff_mock.return_value = [
114 (1, 'Some text without keywords.'),
115 (2, 'The dog is black has a partial keyword that does not count.'),
116 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700117 failures = pre_upload._check_keywords(self.project, 'COMMIT')
Bernie Thompson8e26f742020-07-23 14:32:31 -0700118 self.assertEqual(failures, [])
119
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700120 self.rf_mock.assert_has_calls([
121 mock.call(os.path.join(pre_upload._get_hooks_dir(),
122 pre_upload.BLOCKED_TERMS_FILE)),
123 mock.call(os.path.join(pre_upload._get_hooks_dir(),
124 pre_upload.UNBLOCKED_TERMS_FILE)),
125 ])
126
Bernie Thompson8e26f742020-07-23 14:32:31 -0700127 def test_bad_cases(self):
128 self.desc_mock.return_value = 'Commit Message.\nLine 2\nLine 3 scruffy'
129 self.diff_mock.return_value = [
130 (1, 'Scruffy plain catch'),
131 (2, 'dog-pile hyphenated catch'),
132 (3, 'cat_circle underscored catch'),
133 (4, 'dog pile space catch'),
134 (5, 'dogpiled substring catch'),
135 (6, 'scruffy mangy dog, multiple in a line catch'),
136 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700137 failures = pre_upload._check_keywords(self.project, 'COMMIT')
Bernie Thompson8e26f742020-07-23 14:32:31 -0700138 self.assertNotEqual(failures, [])
139 self.assertEqual('Found a blocked keyword in:', failures[0].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700140 self.assertEqual(
141 ['x.ebuild, line 1: Matched "Scruffy" with regex of "scruffy"',
142 'x.ebuild, line 2: Matched "dog-pile" with regex of "dog.?pile"',
143 'x.ebuild, line 3: Matched "cat_circle" with regex of "cat.?circle"',
144 'x.ebuild, line 4: Matched "dog pile" with regex of "dog.?pile"',
145 'x.ebuild, line 5: Matched "dogpile" with regex of "dog.?pile"',
146 'x.ebuild, line 6: Matched "mangy" with regex of "mangy"'],
147 failures[0].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700148 self.assertEqual('Found a blocked keyword in:', failures[1].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700149 self.assertEqual(
150 ['Commit message, line 3: Matched "scruffy" with regex of "scruffy"'],
151 failures[1].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700152
153 def test_block_option_cases(self):
154 self.desc_mock.return_value = 'Commit Message.\nLine 2 voldemort'
155 self.diff_mock.return_value = [
156 (1, 'Line with a new term voldemort.'),
157 (2, 'Line with only they who shall not be named.'),
158 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700159 failures = pre_upload._check_keywords(self.project,
Bernie Thompson8e26f742020-07-23 14:32:31 -0700160 'COMMIT', ['--block', 'voldemort'])
161 self.assertNotEqual(failures, [])
162 self.assertEqual('Found a blocked keyword in:', failures[0].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700163 self.assertEqual(
164 ['x.ebuild, line 1: Matched "voldemort" with regex of "voldemort"'],
165 failures[0].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700166 self.assertEqual('Found a blocked keyword in:', failures[1].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700167 self.assertEqual(
168 ['Commit message, line 2: '
169 'Matched "voldemort" with regex of "voldemort"'], failures[1].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700170
171 def test_unblock_option_cases(self):
172 self.desc_mock.return_value = 'Commit message with scruffy'
173 self.diff_mock.return_value = [
Bernie Thompson4e362922020-09-02 16:17:50 -0700174 (1, 'Line with two unblocked terms scruffy big dog-pile'),
175 (2, 'Line with without any blocked terms'),
176 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700177 # scruffy matches regex of 'scruffy' in block list but excluded by
178 # different regex of 'scru.?fy' in unblock list.
179 failures = pre_upload._check_keywords(self.project,
Bernie Thompson4e362922020-09-02 16:17:50 -0700180 'COMMIT', ['--unblock', 'dog.?pile',
181 '--unblock', 'scru.?fy'])
182 self.assertEqual(failures, [])
Bernie Thompson8e26f742020-07-23 14:32:31 -0700183
Laurent Chavey434af9a2020-09-28 22:25:16 +0900184 def test_unblock_and_block_option_cases(self):
185 self.desc_mock.return_value = 'Commit message with scruffy'
186 self.diff_mock.return_value = [
187 (1, 'Two unblocked terms scruffy and dog-pile'),
188 (2, 'Without any blocked terms'),
189 (3, 'Blocked dogpile'),
190 (4, 'Unblocked m.dogpile'),
191 (5, 'Blocked dogpile and unblocked m.dogpile'),
192 (6, 'Unlocked m.dogpile and blocked dogpile'),
193 (7, 'Unlocked m.dogpile and unblocked dog-pile'),
194 ]
195 # scruffy matches regex of 'scruffy' in block list but excluded by
196 # a different regex of 'scru.?fy' in unblock list.
197 # dogpile, dog.pile matches regex of 'dog.?pile' in block list.
198 # m.dogpile and dog-pile matches regex of 'dog.?pile' in block list but
199 # excluded by different regex '\.dog.?pile' and 'dog-pile' in unblock list.
200 failures = pre_upload._check_keywords(self.project,
201 'COMMIT',
202 ['--unblock', r'dog-pile',
203 '--unblock', r'scru.?fy',
204 '--unblock', r'\.dog.?pile'])
205 self.assertNotEqual(failures, [])
206 self.assertEqual('Found a blocked keyword in:', failures[0].msg)
207 self.assertEqual(
208 [r'x.ebuild, line 3: Matched "dogpile" with regex of "dog.?pile"',
209 r'x.ebuild, line 5: Matched "dogpile" with regex of "dog.?pile"',
210 r'x.ebuild, line 6: Matched "dogpile" with regex of "dog.?pile"'],
211 failures[0].items)
212
Bernie Thompson8e26f742020-07-23 14:32:31 -0700213
Mike Frysingerb2496652019-09-12 23:35:46 -0400214class CheckNoLongLinesTest(PreUploadTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -0500215 """Tests for _check_no_long_lines."""
216
Jon Salz98255932012-08-18 14:48:02 +0800217 def setUp(self):
Mike Frysinger1459d362014-12-06 13:53:23 -0500218 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
Jon Salz98255932012-08-18 14:48:02 +0800219
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900220 def testCheck(self):
Mike Frysingerf8961942020-05-15 00:36:31 -0400221 path = 'x.cc'
222 self.PatchObject(pre_upload, '_get_affected_files', return_value=[path])
Mike Frysinger1459d362014-12-06 13:53:23 -0500223 self.diff_mock.return_value = [
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400224 (1, u'x' * 80), # OK
225 (2, '\x80' * 80), # OK
226 (3, u'x' * 81), # Too long
227 (4, '\x80' * 81), # Too long
228 (5, u'See http://' + (u'x' * 80)), # OK (URL)
229 (6, u'See https://' + (u'x' * 80)), # OK (URL)
230 (7, u'# define ' + (u'x' * 80)), # OK (compiler directive)
231 (8, u'#define' + (u'x' * 74)), # Too long
Mike Frysinger1459d362014-12-06 13:53:23 -0500232 ]
Alex Deymo643ac4c2015-09-03 10:40:50 -0700233 failure = pre_upload._check_no_long_lines(ProjectNamed('PROJECT'), 'COMMIT')
Jon Salz98255932012-08-18 14:48:02 +0800234 self.assertTrue(failure)
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900235 self.assertEqual('Found lines longer than the limit (first 5 shown):',
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400236 failure.msg)
Mike Frysingerf8961942020-05-15 00:36:31 -0400237 self.assertEqual([path + ', line %d, 81 chars, over 80 chars' %
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900238 x for x in [3, 4, 8]],
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400239 failure.items)
Jon Salz98255932012-08-18 14:48:02 +0800240
George Burgess IVf9f79eb2019-07-09 20:12:55 -0700241 def testCheckTreatsOwnersFilesSpecially(self):
242 affected_files = self.PatchObject(pre_upload, '_get_affected_files')
243
244 mock_files = (
245 ('foo-OWNERS', False),
246 ('OWNERS', True),
247 ('/path/to/OWNERS', True),
248 ('/path/to/OWNERS.foo', True),
249 )
250
251 mock_lines = (
252 (u'x' * 81, False),
253 (u'foo file:' + u'x' * 80, True),
254 (u'include ' + u'x' * 80, True),
255 )
256 assert all(len(line) > 80 for line, _ in mock_lines)
257
258 for file_name, is_owners in mock_files:
259 affected_files.return_value = [file_name]
260 for line, is_ok in mock_lines:
261 self.diff_mock.return_value = [(1, line)]
262 failure = pre_upload._check_no_long_lines(ProjectNamed('PROJECT'),
263 'COMMIT')
264
265 assert_msg = 'file: %r; line: %r' % (file_name, line)
266 if is_owners and is_ok:
267 self.assertFalse(failure, assert_msg)
268 else:
269 self.assertTrue(failure, assert_msg)
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900270 self.assertIn('Found lines longer than the limit', failure.msg,
George Burgess IVf9f79eb2019-07-09 20:12:55 -0700271 assert_msg)
272
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900273 def testIncludeOptions(self):
274 self.PatchObject(pre_upload,
275 '_get_affected_files',
276 return_value=['foo.txt'])
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400277 self.diff_mock.return_value = [(1, u'x' * 81)]
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900278 self.assertFalse(pre_upload._check_no_long_lines(
279 ProjectNamed('PROJECT'), 'COMMIT'))
280 self.assertTrue(pre_upload._check_no_long_lines(
281 ProjectNamed('PROJECT'), 'COMMIT', options=['--include_regex=foo']))
282
283 def testExcludeOptions(self):
284 self.PatchObject(pre_upload,
285 '_get_affected_files',
Mike Frysingerf8961942020-05-15 00:36:31 -0400286 return_value=['foo.cc'])
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400287 self.diff_mock.return_value = [(1, u'x' * 81)]
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900288 self.assertTrue(pre_upload._check_no_long_lines(
289 ProjectNamed('PROJECT'), 'COMMIT'))
290 self.assertFalse(pre_upload._check_no_long_lines(
291 ProjectNamed('PROJECT'), 'COMMIT', options=['--exclude_regex=foo']))
292
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900293 def testSpecialLineLength(self):
294 mock_lines = (
295 (u'x' * 101, True),
296 (u'x' * 100, False),
297 (u'x' * 81, False),
298 (u'x' * 80, False),
299 )
300 self.PatchObject(pre_upload,
301 '_get_affected_files',
302 return_value=['foo.java'])
303 for line, is_ok in mock_lines:
304 self.diff_mock.return_value = [(1, line)]
305 if is_ok:
306 self.assertTrue(pre_upload._check_no_long_lines(
307 ProjectNamed('PROJECT'), 'COMMIT'))
308 else:
309 self.assertFalse(pre_upload._check_no_long_lines(
310 ProjectNamed('PROJECT'), 'COMMIT'))
311
Mike Frysingerae409522014-02-01 03:16:11 -0500312
Mike Frysingerb2496652019-09-12 23:35:46 -0400313class CheckTabbedIndentsTest(PreUploadTestCase):
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800314 """Tests for _check_tabbed_indents."""
Mike Frysingerb2496652019-09-12 23:35:46 -0400315
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800316 def setUp(self):
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900317 self.PatchObject(pre_upload,
318 '_get_affected_files',
319 return_value=['x.ebuild'])
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800320 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
321
322 def test_good_cases(self):
323 self.diff_mock.return_value = [
324 (1, u'no_tabs_anywhere'),
325 (2, u' leading_tab_only'),
326 (3, u' leading_tab another_tab'),
327 (4, u' leading_tab trailing_too '),
328 (5, u' leading_tab then_spaces_trailing '),
329 ]
330 failure = pre_upload._check_tabbed_indents(ProjectNamed('PROJECT'),
331 'COMMIT')
332 self.assertIsNone(failure)
333
334 def test_bad_cases(self):
335 self.diff_mock.return_value = [
336 (1, u' leading_space'),
337 (2, u' tab_followed_by_space'),
338 (3, u' space_followed_by_tab'),
339 (4, u' mix_em_up'),
340 (5, u' mix_on_both_sides '),
341 ]
342 failure = pre_upload._check_tabbed_indents(ProjectNamed('PROJECT'),
343 'COMMIT')
344 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400345 self.assertEqual('Found a space in indentation (must be all tabs):',
346 failure.msg)
347 self.assertEqual(['x.ebuild, line %d' % x for x in range(1, 6)],
348 failure.items)
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800349
350
Mike Frysingerb2496652019-09-12 23:35:46 -0400351class CheckProjectPrefix(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Daniel Erata350fd32014-09-29 14:02:34 -0700352 """Tests for _check_project_prefix."""
353
354 def setUp(self):
Daniel Erata350fd32014-09-29 14:02:34 -0700355 os.chdir(self.tempdir)
356 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
357 self.desc_mock = self.PatchObject(pre_upload, '_get_commit_desc')
358
Daniel Erata350fd32014-09-29 14:02:34 -0700359 def _WriteAliasFile(self, filename, project):
360 """Writes a project name to a file, creating directories if needed."""
361 os.makedirs(os.path.dirname(filename))
362 osutils.WriteFile(filename, project)
363
364 def testInvalidPrefix(self):
365 """Report an error when the prefix doesn't match the base directory."""
366 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
367 self.desc_mock.return_value = 'bar: Some commit'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700368 failure = pre_upload._check_project_prefix(ProjectNamed('PROJECT'),
369 'COMMIT')
Daniel Erata350fd32014-09-29 14:02:34 -0700370 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400371 self.assertEqual('The commit title for changes affecting only foo should '
372 'start with "foo: "', failure.msg)
Daniel Erata350fd32014-09-29 14:02:34 -0700373
374 def testValidPrefix(self):
375 """Use a prefix that matches the base directory."""
376 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
377 self.desc_mock.return_value = 'foo: Change some files.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700378 self.assertFalse(
379 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700380
381 def testAliasFile(self):
382 """Use .project_alias to override the project name."""
383 self._WriteAliasFile('foo/.project_alias', 'project')
384 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/bar.cc']
385 self.desc_mock.return_value = 'project: Use an alias.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700386 self.assertFalse(
387 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700388
389 def testAliasFileWithSubdirs(self):
390 """Check that .project_alias is used when only modifying subdirectories."""
391 self._WriteAliasFile('foo/.project_alias', 'project')
392 self.file_mock.return_value = [
393 'foo/subdir/foo.cc',
394 'foo/subdir/bar.cc'
395 'foo/subdir/blah/baz.cc'
396 ]
397 self.desc_mock.return_value = 'project: Alias with subdirs.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700398 self.assertFalse(
399 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700400
401
Mike Frysingerb2496652019-09-12 23:35:46 -0400402class CheckFilePathCharTypeTest(PreUploadTestCase):
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900403 """Tests for _check_filepath_chartype."""
404
405 def setUp(self):
406 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
407
408 def testCheck(self):
409 self.PatchObject(pre_upload, '_get_affected_files', return_value=['x.cc'])
410 self.diff_mock.return_value = [
411 (1, 'base::FilePath'), # OK
412 (2, 'base::FilePath::CharType'), # NG
413 (3, 'base::FilePath::StringType'), # NG
414 (4, 'base::FilePath::StringPieceType'), # NG
Satoru Takabayashi4ca37922018-08-08 10:16:38 +0900415 (5, 'base::FilePath::FromUTF8Unsafe'), # NG
416 (6, 'FilePath::CharType'), # NG
417 (7, 'FilePath::StringType'), # NG
418 (8, 'FilePath::StringPieceType'), # NG
419 (9, 'FilePath::FromUTF8Unsafe'), # NG
420 (10, 'AsUTF8Unsafe'), # NG
421 (11, 'FILE_PATH_LITERAL'), # NG
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900422 ]
423 failure = pre_upload._check_filepath_chartype(ProjectNamed('PROJECT'),
424 'COMMIT')
425 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400426 self.assertEqual(
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900427 'Please assume FilePath::CharType is char (crbug.com/870621):',
428 failure.msg)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400429 self.assertEqual(['x.cc, line 2 has base::FilePath::CharType',
430 'x.cc, line 3 has base::FilePath::StringType',
431 'x.cc, line 4 has base::FilePath::StringPieceType',
432 'x.cc, line 5 has base::FilePath::FromUTF8Unsafe',
433 'x.cc, line 6 has FilePath::CharType',
434 'x.cc, line 7 has FilePath::StringType',
435 'x.cc, line 8 has FilePath::StringPieceType',
436 'x.cc, line 9 has FilePath::FromUTF8Unsafe',
437 'x.cc, line 10 has AsUTF8Unsafe',
438 'x.cc, line 11 has FILE_PATH_LITERAL'],
439 failure.items)
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900440
441
Mike Frysingerb2496652019-09-12 23:35:46 -0400442class CheckKernelConfig(PreUploadTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -0500443 """Tests for _kernel_configcheck."""
444
Mike Frysinger1459d362014-12-06 13:53:23 -0500445 def setUp(self):
446 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
447
448 def testMixedChanges(self):
449 """Mixing of changes should fail."""
450 self.file_mock.return_value = [
451 '/kernel/files/chromeos/config/base.config',
452 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
453 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700454 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
455 self.assertTrue(failure)
456
Mike Frysinger1459d362014-12-06 13:53:23 -0500457 def testCodeOnly(self):
458 """Code-only changes should pass."""
459 self.file_mock.return_value = [
460 '/kernel/files/Makefile',
461 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
462 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700463 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
464 self.assertFalse(failure)
465
Mike Frysinger1459d362014-12-06 13:53:23 -0500466 def testConfigOnlyChanges(self):
467 """Config-only changes should pass."""
468 self.file_mock.return_value = [
469 '/kernel/files/chromeos/config/base.config',
470 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700471 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
472 self.assertFalse(failure)
473
Jon Salz98255932012-08-18 14:48:02 +0800474
Mike Frysingerb2496652019-09-12 23:35:46 -0400475class CheckJson(PreUploadTestCase):
Mike Frysinger908be682018-01-04 02:21:50 -0500476 """Tests for _run_json_check."""
477
478 def setUp(self):
479 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
480 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
481
482 def testNoJson(self):
483 """Nothing should be checked w/no JSON files."""
484 self.file_mock.return_value = [
485 '/foo/bar.txt',
486 '/readme.md',
487 ]
488 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
489 self.assertIsNone(ret)
490
491 def testValidJson(self):
492 """We should accept valid json files."""
493 self.file_mock.return_value = [
494 '/foo/bar.txt',
495 '/data.json',
496 ]
497 self.content_mock.return_value = '{}'
498 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
499 self.assertIsNone(ret)
500 self.content_mock.assert_called_once_with('/data.json', 'COMMIT')
501
502 def testInvalidJson(self):
503 """We should reject invalid json files."""
504 self.file_mock.return_value = [
505 '/foo/bar.txt',
506 '/data.json',
507 ]
508 self.content_mock.return_value = '{'
509 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
510 self.assertIsNotNone(ret)
511 self.content_mock.assert_called_once_with('/data.json', 'COMMIT')
512
513
Mike Frysingerb2496652019-09-12 23:35:46 -0400514class CheckManifests(PreUploadTestCase):
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500515 """Tests _check_manifests."""
516
517 def setUp(self):
518 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
519 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
520
521 def testNoManifests(self):
522 """Nothing should be checked w/no Manifest files."""
523 self.file_mock.return_value = [
524 '/foo/bar.txt',
525 '/readme.md',
526 '/manifest',
527 '/Manifest.txt',
528 ]
529 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
530 self.assertIsNone(ret)
531
532 def testValidManifest(self):
533 """Accept valid Manifest files."""
534 self.file_mock.return_value = [
535 '/foo/bar.txt',
536 '/cat/pkg/Manifest',
537 ]
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400538 self.content_mock.return_value = """# Comment and blank lines.
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500539
540DIST lines
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400541"""
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500542 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
543 self.assertIsNone(ret)
544 self.content_mock.assert_called_once_with('/cat/pkg/Manifest', 'COMMIT')
545
546 def _testReject(self, content):
547 """Make sure |content| is rejected."""
548 self.file_mock.return_value = ('/Manifest',)
549 self.content_mock.reset_mock()
550 self.content_mock.return_value = content
551 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
552 self.assertIsNotNone(ret)
553 self.content_mock.assert_called_once_with('/Manifest', 'COMMIT')
554
555 def testRejectBlank(self):
556 """Reject empty manifests."""
557 self._testReject('')
558
559 def testNoTrailingNewLine(self):
560 """Reject manifests w/out trailing newline."""
561 self._testReject('DIST foo')
562
563 def testNoLeadingBlankLines(self):
564 """Reject manifests w/leading blank lines."""
565 self._testReject('\nDIST foo\n')
566
567 def testNoTrailingBlankLines(self):
568 """Reject manifests w/trailing blank lines."""
569 self._testReject('DIST foo\n\n')
570
571 def testNoLeadingWhitespace(self):
572 """Reject manifests w/lines w/leading spaces."""
573 self._testReject(' DIST foo\n')
574 self._testReject(' # Comment\n')
575
576 def testNoTrailingWhitespace(self):
577 """Reject manifests w/lines w/trailing spaces."""
578 self._testReject('DIST foo \n')
579 self._testReject('# Comment \n')
580 self._testReject(' \n')
581
582 def testOnlyDistLines(self):
583 """Only allow DIST lines in here."""
584 self._testReject('EBUILD foo\n')
585
586
Mike Frysingerb2496652019-09-12 23:35:46 -0400587class CheckPortageMakeUseVar(PreUploadTestCase):
Daniel Erat9d203ff2015-02-17 10:12:21 -0700588 """Tests for _check_portage_make_use_var."""
589
590 def setUp(self):
591 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
592 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
593
594 def testMakeConfOmitsOriginalUseValue(self):
595 """Fail for make.conf that discards the previous value of $USE."""
596 self.file_mock.return_value = ['make.conf']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400597 self.content_mock.return_value = u'USE="foo"\nUSE="${USE} bar"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700598 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
599 self.assertTrue(failure, failure)
600
601 def testMakeConfCorrectUsage(self):
602 """Succeed for make.conf that preserves the previous value of $USE."""
603 self.file_mock.return_value = ['make.conf']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400604 self.content_mock.return_value = u'USE="${USE} foo"\nUSE="${USE}" bar'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700605 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
606 self.assertFalse(failure, failure)
607
608 def testMakeDefaultsReferencesOriginalUseValue(self):
609 """Fail for make.defaults that refers to a not-yet-set $USE value."""
610 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400611 self.content_mock.return_value = u'USE="${USE} foo"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700612 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
613 self.assertTrue(failure, failure)
614
615 # Also check for "$USE" without curly brackets.
Mike Frysinger71e643e2019-09-13 17:26:39 -0400616 self.content_mock.return_value = u'USE="$USE foo"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700617 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
618 self.assertTrue(failure, failure)
619
620 def testMakeDefaultsOverwritesUseValue(self):
621 """Fail for make.defaults that discards its own $USE value."""
622 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400623 self.content_mock.return_value = u'USE="foo"\nUSE="bar"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700624 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
625 self.assertTrue(failure, failure)
626
627 def testMakeDefaultsCorrectUsage(self):
628 """Succeed for make.defaults that sets and preserves $USE."""
629 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400630 self.content_mock.return_value = u'USE="foo"\nUSE="${USE}" bar'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700631 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
632 self.assertFalse(failure, failure)
633
634
Mike Frysingerb2496652019-09-12 23:35:46 -0400635class CheckEbuildEapi(PreUploadTestCase):
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500636 """Tests for _check_ebuild_eapi."""
637
Alex Deymo643ac4c2015-09-03 10:40:50 -0700638 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500639
640 def setUp(self):
641 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
642 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
643 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff',
644 side_effect=Exception())
645
646 def testSkipUpstreamOverlays(self):
647 """Skip ebuilds found in upstream overlays."""
648 self.file_mock.side_effect = Exception()
649 ret = pre_upload._check_ebuild_eapi(self.PORTAGE_STABLE, 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400650 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500651
652 # Make sure our condition above triggers.
653 self.assertRaises(Exception, pre_upload._check_ebuild_eapi, 'o', 'HEAD')
654
655 def testSkipNonEbuilds(self):
656 """Skip non-ebuild files."""
657 self.content_mock.side_effect = Exception()
658
659 self.file_mock.return_value = ['some-file', 'ebuild/dir', 'an.ebuild~']
Alex Deymo643ac4c2015-09-03 10:40:50 -0700660 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400661 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500662
663 # Make sure our condition above triggers.
664 self.file_mock.return_value.append('a/real.ebuild')
Alex Deymo643ac4c2015-09-03 10:40:50 -0700665 self.assertRaises(Exception, pre_upload._check_ebuild_eapi,
666 ProjectNamed('o'), 'HEAD')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500667
668 def testSkipSymlink(self):
669 """Skip files that are just symlinks."""
670 self.file_mock.return_value = ['a-r1.ebuild']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400671 self.content_mock.return_value = u'a.ebuild'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700672 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400673 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500674
675 def testRejectEapiImplicit0Content(self):
676 """Reject ebuilds that do not declare EAPI (so it's 0)."""
677 self.file_mock.return_value = ['a.ebuild']
678
Mike Frysinger71e643e2019-09-13 17:26:39 -0400679 self.content_mock.return_value = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500680IUSE="foo"
681src_compile() { }
682"""
Alex Deymo643ac4c2015-09-03 10:40:50 -0700683 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500684 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500685
686 def testRejectExplicitEapi1Content(self):
687 """Reject ebuilds that do declare old EAPI explicitly."""
688 self.file_mock.return_value = ['a.ebuild']
689
Mike Frysinger71e643e2019-09-13 17:26:39 -0400690 template = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500691EAPI=%s
692IUSE="foo"
693src_compile() { }
694"""
695 # Make sure we only check the first EAPI= setting.
Mike Frysinger948284a2018-02-01 15:22:56 -0500696 self.content_mock.return_value = template % '1\nEAPI=60'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700697 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500698 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500699
700 # Verify we handle double quotes too.
701 self.content_mock.return_value = template % '"1"'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700702 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500703 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500704
705 # Verify we handle single quotes too.
706 self.content_mock.return_value = template % "'1'"
Alex Deymo643ac4c2015-09-03 10:40:50 -0700707 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500708 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500709
Mike Frysinger948284a2018-02-01 15:22:56 -0500710 def testAcceptExplicitNewEapiContent(self):
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500711 """Accept ebuilds that do declare new EAPI explicitly."""
712 self.file_mock.return_value = ['a.ebuild']
713
Mike Frysinger71e643e2019-09-13 17:26:39 -0400714 template = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500715EAPI=%s
716IUSE="foo"
717src_compile() { }
718"""
719 # Make sure we only check the first EAPI= setting.
Mike Frysinger948284a2018-02-01 15:22:56 -0500720 self.content_mock.return_value = template % '6\nEAPI=1'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700721 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400722 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500723
724 # Verify we handle double quotes too.
725 self.content_mock.return_value = template % '"5"'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700726 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400727 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500728
729 # Verify we handle single quotes too.
730 self.content_mock.return_value = template % "'5-hdepend'"
Alex Deymo643ac4c2015-09-03 10:40:50 -0700731 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400732 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500733
734
Mike Frysingerb2496652019-09-12 23:35:46 -0400735class CheckEbuildKeywords(PreUploadTestCase):
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400736 """Tests for _check_ebuild_keywords."""
737
738 def setUp(self):
739 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
740 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
741
742 def testNoEbuilds(self):
743 """If no ebuilds are found, do not scan."""
744 self.file_mock.return_value = ['a.file', 'ebuild-is-not.foo']
745
Alex Deymo643ac4c2015-09-03 10:40:50 -0700746 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400747 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400748
749 self.assertEqual(self.content_mock.call_count, 0)
750
751 def testSomeEbuilds(self):
752 """If ebuilds are found, only scan them."""
753 self.file_mock.return_value = ['a.file', 'blah', 'foo.ebuild', 'cow']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400754 self.content_mock.return_value = u''
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400755
Alex Deymo643ac4c2015-09-03 10:40:50 -0700756 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400757 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400758
759 self.assertEqual(self.content_mock.call_count, 1)
760
761 def _CheckContent(self, content, fails):
762 """Test helper for inputs/outputs.
763
764 Args:
765 content: The ebuild content to test.
766 fails: Whether |content| should trigger a hook failure.
767 """
768 self.file_mock.return_value = ['a.ebuild']
769 self.content_mock.return_value = content
770
Alex Deymo643ac4c2015-09-03 10:40:50 -0700771 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400772 if fails:
Mike Frysingerb81102f2014-11-21 00:33:35 -0500773 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400774 else:
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400775 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400776
777 self.assertEqual(self.content_mock.call_count, 1)
778
779 def testEmpty(self):
780 """Check KEYWORDS= is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400781 self._CheckContent(u'# HEADER\nKEYWORDS=\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400782
783 def testEmptyQuotes(self):
784 """Check KEYWORDS="" is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400785 self._CheckContent(u'# HEADER\nKEYWORDS=" "\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400786
787 def testStableGlob(self):
788 """Check KEYWORDS=* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400789 self._CheckContent(u'# HEADER\nKEYWORDS="\t*\t"\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400790
791 def testUnstableGlob(self):
792 """Check KEYWORDS=~* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400793 self._CheckContent(u'# HEADER\nKEYWORDS="~* "\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400794
795 def testRestrictedGlob(self):
796 """Check KEYWORDS=-* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400797 self._CheckContent(u'# HEADER\nKEYWORDS="\t-* arm"\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400798
799 def testMissingGlobs(self):
800 """Reject KEYWORDS missing any globs."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400801 self._CheckContent(u'# HEADER\nKEYWORDS="~arm x86"\nblah\n', True)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400802
803
Mike Frysingerb04778f2020-11-30 02:41:14 -0500804class CheckEbuildOwners(PreUploadTestCase):
805 """Tests for _check_ebuild_owners."""
806
807 def setUp(self):
808 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
809 self.content_mock = self.PatchObject(
810 pre_upload, '_get_file_content', return_value=None)
811
812 def testNoMatches(self):
813 """Handle no matching files."""
814 self.file_mock.return_value = []
815 ret = pre_upload._check_ebuild_owners('project', 'HEAD')
816 self.assertIsNone(ret)
817
818 def testNoEbuilds(self):
819 """Handle CLs w/no ebuilds."""
820 self.file_mock.return_value = [
821 DiffEntry(src_file='profiles/package.mask', status='M'),
822 DiffEntry(src_file='dev-util/pkg/README.md', status='A'),
823 DiffEntry(src_file='scripts/sync_sonic.py', status='D'),
824 ]
825 ret = pre_upload._check_ebuild_owners('project', 'HEAD')
826 self.assertIsNone(ret)
827
828 def testMissingOwnersFailure(self):
829 """Test cases that should flag missing OWNERS."""
830 TESTS = (
831 [
832 DiffEntry(src_file='dev-util/pkg/foo.ebuild', status='A'),
833 DiffEntry(src_file='dev-util/pkg/README.md', status='A'),
834 DiffEntry(src_file='dev-util/pkg/Manifest', status='A'),
835 ],
836 )
837 for test in TESTS:
838 self.file_mock.return_value = test
839 ret = pre_upload._check_ebuild_owners('project', 'HEAD')
840 self.assertIsNotNone(ret)
841
842 def testMissingOwnersIgnore(self):
843 """Test cases that should ignore missing OWNERS."""
844 TESTS = (
845 [
846 DiffEntry(src_file='dev-util/pkg/foo-9999.ebuild', status='M'),
847 ],
848 [
849 DiffEntry(src_file='dev-util/pkg/foo-0.ebuild', status='M'),
850 DiffEntry(src_file='dev-util/pkg/foo-0-r1.ebuild', status='A'),
851 ],
852 [
853 DiffEntry(src_file='dev-util/pkg/foo-0-r1.ebuild', status='D'),
854 DiffEntry(src_file='dev-util/pkg/foo-0-r2.ebuild', status='A'),
855 ],
856 )
857 for test in TESTS:
858 self.file_mock.return_value = test
859 ret = pre_upload._check_ebuild_owners('project', 'HEAD')
860 self.assertIsNone(ret)
861
862 def testOwnersExist(self):
863 """Test cases where OWNERS exist."""
864 TESTS = (
865 [
866 DiffEntry(src_file='dev-util/pkg/foo-9999.ebuild', status='A'),
867 ],
868 )
869 self.content_mock.return_value = 'foo'
870 for test in TESTS:
871 self.file_mock.return_value = test
872 ret = pre_upload._check_ebuild_owners('project', 'HEAD')
873 self.assertIsNone(ret)
874 # This should be the # of package dirs across all tests. This makes sure
875 # we actually called the owners check logic and didn't return early.
876 self.assertEqual(1, self.content_mock.call_count)
877
878
Mike Frysinger6ee76b82020-11-20 01:16:06 -0500879class CheckEbuildR0(PreUploadTestCase):
880 """Tests for _check_ebuild_r0."""
881
882 def setUp(self):
883 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
884
885 def testNoMatches(self):
886 """Handle no matching files."""
887 self.file_mock.return_value = []
888 ret = pre_upload._check_ebuild_r0('project', 'HEAD')
889 self.assertIsNone(ret)
890
891 def testBadEbuilds(self):
892 """Handle matching r0 files."""
893 self.file_mock.return_value = ['foo-1-r0.ebuild']
894 ret = pre_upload._check_ebuild_r0('project', 'HEAD')
895 self.assertIsNotNone(ret)
896
897
Mike Frysingerb2496652019-09-12 23:35:46 -0400898class CheckEbuildVirtualPv(PreUploadTestCase):
Mike Frysingercd363c82014-02-01 05:20:18 -0500899 """Tests for _check_ebuild_virtual_pv."""
900
Alex Deymo643ac4c2015-09-03 10:40:50 -0700901 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
902 CHROMIUMOS_OVERLAY = ProjectNamed('chromiumos/overlays/chromiumos')
903 BOARD_OVERLAY = ProjectNamed('chromiumos/overlays/board-overlays')
904 PRIVATE_OVERLAY = ProjectNamed('chromeos/overlays/overlay-link-private')
905 PRIVATE_VARIANT_OVERLAY = ProjectNamed('chromeos/overlays/'
906 'overlay-variant-daisy-spring-private')
Mike Frysingercd363c82014-02-01 05:20:18 -0500907
908 def setUp(self):
909 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
910
911 def testNoVirtuals(self):
912 """Skip non virtual packages."""
913 self.file_mock.return_value = ['some/package/package-3.ebuild']
Alex Deymo643ac4c2015-09-03 10:40:50 -0700914 ret = pre_upload._check_ebuild_virtual_pv(ProjectNamed('overlay'), 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400915 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500916
917 def testCommonVirtuals(self):
918 """Non-board overlays should use PV=1."""
919 template = 'virtual/foo/foo-%s.ebuild'
920 self.file_mock.return_value = [template % '1']
921 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400922 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500923
924 self.file_mock.return_value = [template % '2']
925 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500926 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500927
928 def testPublicBoardVirtuals(self):
929 """Public board overlays should use PV=2."""
930 template = 'overlay-lumpy/virtual/foo/foo-%s.ebuild'
931 self.file_mock.return_value = [template % '2']
932 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400933 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500934
935 self.file_mock.return_value = [template % '2.5']
936 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500937 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500938
939 def testPublicBoardVariantVirtuals(self):
940 """Public board variant overlays should use PV=2.5."""
941 template = 'overlay-variant-lumpy-foo/virtual/foo/foo-%s.ebuild'
942 self.file_mock.return_value = [template % '2.5']
943 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400944 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500945
946 self.file_mock.return_value = [template % '3']
947 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500948 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500949
950 def testPrivateBoardVirtuals(self):
951 """Private board overlays should use PV=3."""
952 template = 'virtual/foo/foo-%s.ebuild'
953 self.file_mock.return_value = [template % '3']
954 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400955 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500956
957 self.file_mock.return_value = [template % '3.5']
958 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500959 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500960
961 def testPrivateBoardVariantVirtuals(self):
962 """Private board variant overlays should use PV=3.5."""
963 template = 'virtual/foo/foo-%s.ebuild'
964 self.file_mock.return_value = [template % '3.5']
965 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400966 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500967
Bernie Thompsone5ee1822016-01-12 14:22:23 -0800968 def testSpecialVirtuals(self):
969 """Some cases require deeper versioning and can be >= 4."""
970 template = 'virtual/foo/foo-%s.ebuild'
Mike Frysingercd363c82014-02-01 05:20:18 -0500971 self.file_mock.return_value = [template % '4']
972 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400973 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500974
Bernie Thompsone5ee1822016-01-12 14:22:23 -0800975 self.file_mock.return_value = [template % '4.5']
976 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400977 self.assertIsNone(ret)
Mike Frysinger98638102014-08-28 00:15:08 -0400978
Mike Frysingerb2496652019-09-12 23:35:46 -0400979class CheckCrosLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -0700980 """Tests for _check_cros_license."""
Mike Frysinger98638102014-08-28 00:15:08 -0400981
982 def setUp(self):
983 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
984 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
985
986 def testOldHeaders(self):
987 """Accept old header styles."""
988 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400989 (u'#!/bin/sh\n'
990 u'# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.\n'
991 u'# Use of this source code is governed by a BSD-style license that'
992 u' can be\n'
993 u'# found in the LICENSE file.\n'),
994 (u'// Copyright 2010-2013 The Chromium OS Authors. All rights reserved.'
995 u'\n// Use of this source code is governed by a BSD-style license that'
996 u' can be\n'
997 u'// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -0400998 )
999 self.file_mock.return_value = ['file']
1000 for header in HEADERS:
1001 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +09001002 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
1003
1004 def testNewFileYear(self):
1005 """Added files should have the current year in license header."""
1006 year = datetime.datetime.now().year
1007 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001008 (u'// Copyright 2016 The Chromium OS Authors. All rights reserved.\n'
1009 u'// Use of this source code is governed by a BSD-style license that'
1010 u' can be\n'
1011 u'// found in the LICENSE file.\n'),
1012 (u'// Copyright %d The Chromium OS Authors. All rights reserved.\n'
1013 u'// Use of this source code is governed by a BSD-style license that'
1014 u' can be\n'
1015 u'// found in the LICENSE file.\n') % year,
Keigo Oka7e880ac2019-07-03 15:03:43 +09001016 )
1017 want_error = (True, False)
1018 def fake_get_affected_files(_, relative, include_adds=True):
1019 _ = relative
1020 if include_adds:
1021 return ['file']
1022 else:
1023 return []
1024
1025 self.file_mock.side_effect = fake_get_affected_files
1026 for i, header in enumerate(HEADERS):
1027 self.content_mock.return_value = header
1028 if want_error[i]:
1029 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
1030 else:
1031 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -04001032
1033 def testRejectC(self):
1034 """Reject the (c) in newer headers."""
1035 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001036 (u'// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.'
1037 u'\n'
1038 u'// Use of this source code is governed by a BSD-style license that'
1039 u' can be\n'
1040 u'// found in the LICENSE file.\n'),
1041 (u'// Copyright (c) 2020 The Chromium OS Authors. All rights reserved.'
1042 u'\n'
1043 u'// Use of this source code is governed by a BSD-style license that'
1044 u' can be\n'
1045 u'// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -04001046 )
1047 self.file_mock.return_value = ['file']
1048 for header in HEADERS:
1049 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +09001050 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001051
Brian Norris68838dd2018-09-26 18:30:24 -07001052 def testNoLeadingSpace(self):
1053 """Allow headers without leading space (e.g., not a source comment)"""
1054 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001055 (u'Copyright 2018 The Chromium OS Authors. All rights reserved.\n'
1056 u'Use of this source code is governed by a BSD-style license that '
1057 u'can be\n'
1058 u'found in the LICENSE file.\n'),
Brian Norris68838dd2018-09-26 18:30:24 -07001059 )
1060 self.file_mock.return_value = ['file']
1061 for header in HEADERS:
1062 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +09001063 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Brian Norris68838dd2018-09-26 18:30:24 -07001064
Keigo Oka9732e382019-06-28 17:44:59 +09001065 def testNoExcludedGolang(self):
1066 """Don't exclude .go files for license checks."""
1067 self.file_mock.return_value = ['foo/main.go']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001068 self.content_mock.return_value = u'package main\nfunc main() {}'
Keigo Oka7e880ac2019-07-03 15:03:43 +09001069 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Keigo Oka9732e382019-06-28 17:44:59 +09001070
Ken Turnerd07564b2018-02-08 17:57:59 +11001071 def testIgnoreExcludedPaths(self):
1072 """Ignores excluded paths for license checks."""
1073 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001074 self.content_mock.return_value = u'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +09001075 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +11001076
Tom Hughes90b7bd42020-11-10 10:31:49 -08001077 def testIgnoreMetadataFiles(self):
1078 """Ignores metadata files for license checks."""
1079 self.file_mock.return_value = ['foo/DIR_METADATA']
1080 self.content_mock.return_value = u'team_email: "team@chromium.org"'
1081 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
1082
Chris McDonald7b63c8e2019-04-25 10:27:27 -06001083 def testIgnoreTopLevelExcludedPaths(self):
1084 """Ignores excluded paths for license checks."""
1085 self.file_mock.return_value = ['OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001086 self.content_mock.return_value = u'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +09001087 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001088
Mike Frysingerb2496652019-09-12 23:35:46 -04001089class CheckAOSPLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -07001090 """Tests for _check_aosp_license."""
1091
1092 def setUp(self):
1093 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1094 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1095
1096 def testHeaders(self):
1097 """Accept old header styles."""
1098 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001099 u"""//
Alex Deymof5792ce2015-08-24 22:50:08 -07001100// Copyright (C) 2011 The Android Open Source Project
1101//
1102// Licensed under the Apache License, Version 2.0 (the "License");
1103// you may not use this file except in compliance with the License.
1104// You may obtain a copy of the License at
1105//
1106// http://www.apache.org/licenses/LICENSE-2.0
1107//
1108// Unless required by applicable law or agreed to in writing, software
1109// distributed under the License is distributed on an "AS IS" BASIS,
1110// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1111// See the License for the specific language governing permissions and
1112// limitations under the License.
1113//
1114""",
Mike Frysinger71e643e2019-09-13 17:26:39 -04001115 u"""#
Alex Deymof5792ce2015-08-24 22:50:08 -07001116# Copyright (c) 2015 The Android Open Source Project
1117#
1118# Licensed under the Apache License, Version 2.0 (the "License");
1119# you may not use this file except in compliance with the License.
1120# You may obtain a copy of the License at
1121#
1122# http://www.apache.org/licenses/LICENSE-2.0
1123#
1124# Unless required by applicable law or agreed to in writing, software
1125# distributed under the License is distributed on an "AS IS" BASIS,
1126# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1127# See the License for the specific language governing permissions and
1128# limitations under the License.
1129#
1130""",
1131 )
1132 self.file_mock.return_value = ['file']
1133 for header in HEADERS:
1134 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001135 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001136
1137 def testRejectNoLinesAround(self):
1138 """Reject headers missing the empty lines before/after the license."""
1139 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001140 u"""# Copyright (c) 2015 The Android Open Source Project
Alex Deymof5792ce2015-08-24 22:50:08 -07001141#
1142# Licensed under the Apache License, Version 2.0 (the "License");
1143# you may not use this file except in compliance with the License.
1144# You may obtain a copy of the License at
1145#
1146# http://www.apache.org/licenses/LICENSE-2.0
1147#
1148# Unless required by applicable law or agreed to in writing, software
1149# distributed under the License is distributed on an "AS IS" BASIS,
1150# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1151# See the License for the specific language governing permissions and
1152# limitations under the License.
1153""",
1154 )
1155 self.file_mock.return_value = ['file']
1156 for header in HEADERS:
1157 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001158 self.assertIsNotNone(pre_upload._check_aosp_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -04001159
Ken Turnerd07564b2018-02-08 17:57:59 +11001160 def testIgnoreExcludedPaths(self):
1161 """Ignores excluded paths for license checks."""
1162 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001163 self.content_mock.return_value = u'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001164 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +11001165
Chris McDonald7b63c8e2019-04-25 10:27:27 -06001166 def testIgnoreTopLevelExcludedPaths(self):
1167 """Ignores excluded paths for license checks."""
1168 self.file_mock.return_value = ['OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001169 self.content_mock.return_value = u'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001170 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
1171
Mike Frysinger98638102014-08-28 00:15:08 -04001172
Mike Frysingerb2496652019-09-12 23:35:46 -04001173class CheckLayoutConfTestCase(PreUploadTestCase):
Mike Frysingerd7734522015-02-26 16:12:43 -05001174 """Tests for _check_layout_conf."""
1175
1176 def setUp(self):
1177 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1178 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1179
1180 def assertAccepted(self, files, project='project', commit='fake sha1'):
1181 """Assert _check_layout_conf accepts |files|."""
1182 self.file_mock.return_value = files
1183 ret = pre_upload._check_layout_conf(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001184 self.assertIsNone(ret, msg='rejected with:\n%s' % ret)
Mike Frysingerd7734522015-02-26 16:12:43 -05001185
1186 def assertRejected(self, files, project='project', commit='fake sha1'):
1187 """Assert _check_layout_conf rejects |files|."""
1188 self.file_mock.return_value = files
1189 ret = pre_upload._check_layout_conf(project, commit)
1190 self.assertTrue(isinstance(ret, errors.HookFailure))
1191
1192 def GetLayoutConf(self, filters=()):
1193 """Return a valid layout.conf with |filters| lines removed."""
1194 all_lines = [
Mike Frysinger71e643e2019-09-13 17:26:39 -04001195 u'masters = portage-stable chromiumos',
1196 u'profile-formats = portage-2 profile-default-eapi',
1197 u'profile_eapi_when_unspecified = 5-progress',
1198 u'repo-name = link',
1199 u'thin-manifests = true',
1200 u'use-manifests = strict',
Mike Frysingerd7734522015-02-26 16:12:43 -05001201 ]
1202
1203 lines = []
1204 for line in all_lines:
1205 for filt in filters:
1206 if line.startswith(filt):
1207 break
1208 else:
1209 lines.append(line)
1210
Mike Frysinger71e643e2019-09-13 17:26:39 -04001211 return u'\n'.join(lines)
Mike Frysingerd7734522015-02-26 16:12:43 -05001212
1213 def testNoFilesToCheck(self):
1214 """Don't blow up when there are no layout.conf files."""
1215 self.assertAccepted([])
1216
1217 def testRejectRepoNameFile(self):
1218 """If profiles/repo_name is set, kick it out."""
1219 self.assertRejected(['profiles/repo_name'])
1220
1221 def testAcceptValidLayoutConf(self):
1222 """Accept a fully valid layout.conf."""
1223 self.content_mock.return_value = self.GetLayoutConf()
1224 self.assertAccepted(['metadata/layout.conf'])
1225
1226 def testAcceptUnknownKeys(self):
1227 """Accept keys we don't explicitly know about."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001228 self.content_mock.return_value = self.GetLayoutConf() + u'\nzzz-top = ok'
Mike Frysingerd7734522015-02-26 16:12:43 -05001229 self.assertAccepted(['metadata/layout.conf'])
1230
1231 def testRejectUnsorted(self):
1232 """Reject an unsorted layout.conf."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001233 self.content_mock.return_value = u'zzz-top = bad\n' + self.GetLayoutConf()
Mike Frysingerd7734522015-02-26 16:12:43 -05001234 self.assertRejected(['metadata/layout.conf'])
1235
1236 def testRejectMissingThinManifests(self):
1237 """Reject a layout.conf missing thin-manifests."""
1238 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001239 filters=[u'thin-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001240 self.assertRejected(['metadata/layout.conf'])
1241
1242 def testRejectMissingUseManifests(self):
1243 """Reject a layout.conf missing use-manifests."""
1244 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001245 filters=[u'use-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001246 self.assertRejected(['metadata/layout.conf'])
1247
1248 def testRejectMissingEapiFallback(self):
1249 """Reject a layout.conf missing profile_eapi_when_unspecified."""
1250 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001251 filters=[u'profile_eapi_when_unspecified'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001252 self.assertRejected(['metadata/layout.conf'])
1253
1254 def testRejectMissingRepoName(self):
1255 """Reject a layout.conf missing repo-name."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001256 self.content_mock.return_value = self.GetLayoutConf(filters=[u'repo-name'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001257 self.assertRejected(['metadata/layout.conf'])
1258
1259
Mike Frysingerb2496652019-09-12 23:35:46 -04001260class CommitMessageTestCase(PreUploadTestCase):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001261 """Test case for funcs that check commit messages."""
1262
1263 def setUp(self):
1264 self.msg_mock = self.PatchObject(pre_upload, '_get_commit_desc')
1265
1266 @staticmethod
1267 def CheckMessage(_project, _commit):
1268 raise AssertionError('Test class must declare CheckMessage')
1269 # This dummy return is to silence pylint warning W1111 so we don't have to
1270 # enable it for all the call sites below.
1271 return 1 # pylint: disable=W0101
1272
Alex Deymo643ac4c2015-09-03 10:40:50 -07001273 def assertMessageAccepted(self, msg, project=ProjectNamed('project'),
1274 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001275 """Assert _check_change_has_bug_field accepts |msg|."""
1276 self.msg_mock.return_value = msg
1277 ret = self.CheckMessage(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001278 self.assertIsNone(ret)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001279
Alex Deymo643ac4c2015-09-03 10:40:50 -07001280 def assertMessageRejected(self, msg, project=ProjectNamed('project'),
1281 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001282 """Assert _check_change_has_bug_field rejects |msg|."""
1283 self.msg_mock.return_value = msg
1284 ret = self.CheckMessage(project, commit)
1285 self.assertTrue(isinstance(ret, errors.HookFailure))
1286
1287
1288class CheckCommitMessageBug(CommitMessageTestCase):
1289 """Tests for _check_change_has_bug_field."""
1290
Alex Deymo643ac4c2015-09-03 10:40:50 -07001291 AOSP_PROJECT = pre_upload.Project(name='overlay', dir='', remote='aosp')
1292 CROS_PROJECT = pre_upload.Project(name='overlay', dir='', remote='cros')
1293
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001294 @staticmethod
1295 def CheckMessage(project, commit):
1296 return pre_upload._check_change_has_bug_field(project, commit)
1297
1298 def testNormal(self):
1299 """Accept a commit message w/a valid BUG."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001300 self.assertMessageAccepted('\nBUG=chromium:1234\n', self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001301 self.assertMessageAccepted('\nBUG=b:1234\n', self.CROS_PROJECT)
1302
1303 self.assertMessageAccepted('\nBug: 1234\n', self.AOSP_PROJECT)
1304 self.assertMessageAccepted('\nBug:1234\n', self.AOSP_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001305
1306 def testNone(self):
1307 """Accept BUG=None."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001308 self.assertMessageAccepted('\nBUG=None\n', self.CROS_PROJECT)
1309 self.assertMessageAccepted('\nBUG=none\n', self.CROS_PROJECT)
1310 self.assertMessageAccepted('\nBug: None\n', self.AOSP_PROJECT)
1311 self.assertMessageAccepted('\nBug:none\n', self.AOSP_PROJECT)
1312
1313 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1314 self.assertMessageRejected('\nBUG=NONE\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001315
1316 def testBlank(self):
1317 """Reject blank values."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001318 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1319 self.assertMessageRejected('\nBUG=\n', project)
1320 self.assertMessageRejected('\nBUG= \n', project)
1321 self.assertMessageRejected('\nBug:\n', project)
1322 self.assertMessageRejected('\nBug: \n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001323
1324 def testNotFirstLine(self):
1325 """Reject the first line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001326 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1327 self.assertMessageRejected('BUG=None\n\n\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001328
1329 def testNotInline(self):
1330 """Reject not at the start of line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001331 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1332 self.assertMessageRejected('\n BUG=None\n', project)
1333 self.assertMessageRejected('\n\tBUG=None\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001334
1335 def testOldTrackers(self):
1336 """Reject commit messages using old trackers."""
Mike Frysingera2f28252017-10-27 22:26:14 -04001337 self.assertMessageRejected('\nBUG=chrome-os-partner:1234\n',
1338 self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001339 self.assertMessageRejected('\nBUG=chromium-os:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001340
1341 def testNoTrackers(self):
1342 """Reject commit messages w/invalid trackers."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001343 self.assertMessageRejected('\nBUG=booga:1234\n', self.CROS_PROJECT)
1344 self.assertMessageRejected('\nBUG=br:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001345
1346 def testMissing(self):
1347 """Reject commit messages w/no BUG line."""
1348 self.assertMessageRejected('foo\n')
1349
1350 def testCase(self):
1351 """Reject bug lines that are not BUG."""
1352 self.assertMessageRejected('\nbug=none\n')
1353
Cheng Yuehb707c522020-01-02 14:06:59 +08001354 def testNotAfterTest(self):
1355 """Reject any TEST line before any BUG line."""
1356 test_field = 'TEST=i did not do it\n'
1357 middle_field = 'A random between line\n'
1358 for project, bug_field in ((self.AOSP_PROJECT, 'Bug:none\n'),
1359 (self.CROS_PROJECT, 'BUG=None\n')):
1360 self.assertMessageRejected(
1361 '\n' + test_field + middle_field + bug_field, project)
1362 self.assertMessageRejected(
1363 '\n' + test_field + bug_field, project)
1364
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001365
1366class CheckCommitMessageCqDepend(CommitMessageTestCase):
1367 """Tests for _check_change_has_valid_cq_depend."""
1368
1369 @staticmethod
1370 def CheckMessage(project, commit):
1371 return pre_upload._check_change_has_valid_cq_depend(project, commit)
1372
1373 def testNormal(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001374 """Accept valid Cq-Depends line."""
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001375 self.assertMessageAccepted('\nCq-Depend: chromium:1234\nChange-Id: I123')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001376
1377 def testInvalid(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001378 """Reject invalid Cq-Depends line."""
1379 self.assertMessageRejected('\nCq-Depend=chromium=1234\n')
1380 self.assertMessageRejected('\nCq-Depend: None\n')
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001381 self.assertMessageRejected('\nCq-Depend: chromium:1234\n\nChange-Id: I123')
Mike Frysingere39d0cd2019-11-25 13:30:06 -05001382 self.assertMessageRejected('\nCQ-DEPEND=1234\n')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001383
1384
Bernie Thompsonf8fea992016-01-14 10:27:18 -08001385class CheckCommitMessageContribution(CommitMessageTestCase):
1386 """Tests for _check_change_is_contribution."""
1387
1388 @staticmethod
1389 def CheckMessage(project, commit):
1390 return pre_upload._check_change_is_contribution(project, commit)
1391
1392 def testNormal(self):
1393 """Accept a commit message which is a contribution."""
1394 self.assertMessageAccepted('\nThis is cool code I am contributing.\n')
1395
1396 def testFailureLowerCase(self):
1397 """Deny a commit message which is not a contribution."""
1398 self.assertMessageRejected('\nThis is not a contribution.\n')
1399
1400 def testFailureUpperCase(self):
1401 """Deny a commit message which is not a contribution."""
1402 self.assertMessageRejected('\nNOT A CONTRIBUTION\n')
1403
1404
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001405class CheckCommitMessageTest(CommitMessageTestCase):
1406 """Tests for _check_change_has_test_field."""
1407
1408 @staticmethod
1409 def CheckMessage(project, commit):
1410 return pre_upload._check_change_has_test_field(project, commit)
1411
1412 def testNormal(self):
1413 """Accept a commit message w/a valid TEST."""
1414 self.assertMessageAccepted('\nTEST=i did it\n')
1415
1416 def testNone(self):
1417 """Accept TEST=None."""
1418 self.assertMessageAccepted('\nTEST=None\n')
1419 self.assertMessageAccepted('\nTEST=none\n')
1420
1421 def testBlank(self):
1422 """Reject blank values."""
1423 self.assertMessageRejected('\nTEST=\n')
1424 self.assertMessageRejected('\nTEST= \n')
1425
1426 def testNotFirstLine(self):
1427 """Reject the first line."""
1428 self.assertMessageRejected('TEST=None\n\n\n')
1429
1430 def testNotInline(self):
1431 """Reject not at the start of line."""
1432 self.assertMessageRejected('\n TEST=None\n')
1433 self.assertMessageRejected('\n\tTEST=None\n')
1434
1435 def testMissing(self):
1436 """Reject commit messages w/no TEST line."""
1437 self.assertMessageRejected('foo\n')
1438
1439 def testCase(self):
1440 """Reject bug lines that are not TEST."""
1441 self.assertMessageRejected('\ntest=none\n')
1442
1443
1444class CheckCommitMessageChangeId(CommitMessageTestCase):
1445 """Tests for _check_change_has_proper_changeid."""
1446
1447 @staticmethod
1448 def CheckMessage(project, commit):
1449 return pre_upload._check_change_has_proper_changeid(project, commit)
1450
1451 def testNormal(self):
1452 """Accept a commit message w/a valid Change-Id."""
1453 self.assertMessageAccepted('foo\n\nChange-Id: I1234\n')
1454
1455 def testBlank(self):
1456 """Reject blank values."""
1457 self.assertMessageRejected('\nChange-Id:\n')
1458 self.assertMessageRejected('\nChange-Id: \n')
1459
1460 def testNotFirstLine(self):
1461 """Reject the first line."""
1462 self.assertMessageRejected('TEST=None\n\n\n')
1463
1464 def testNotInline(self):
1465 """Reject not at the start of line."""
1466 self.assertMessageRejected('\n Change-Id: I1234\n')
1467 self.assertMessageRejected('\n\tChange-Id: I1234\n')
1468
1469 def testMissing(self):
1470 """Reject commit messages missing the line."""
1471 self.assertMessageRejected('foo\n')
1472
1473 def testCase(self):
1474 """Reject bug lines that are not Change-Id."""
1475 self.assertMessageRejected('\nchange-id: I1234\n')
1476 self.assertMessageRejected('\nChange-id: I1234\n')
1477 self.assertMessageRejected('\nChange-ID: I1234\n')
1478
1479 def testEnd(self):
1480 """Reject Change-Id's that are not last."""
1481 self.assertMessageRejected('\nChange-Id: I1234\nbar\n')
1482
Mike Frysinger02b88bd2014-11-21 00:29:38 -05001483 def testSobTag(self):
1484 """Permit s-o-b tags to follow the Change-Id."""
1485 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nSigned-off-by: Hi\n')
1486
LaMont Jones237f3ef2020-01-22 10:40:52 -07001487 def testCqClTag(self):
1488 """Permit Cq-Cl-Tag tags to follow the Change-Id."""
1489 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nCq-Cl-Tag: Hi\n')
1490
LaMont Jonesfb5e8bf2020-03-03 12:50:06 -07001491 def testCqIncludeTrybotsTag(self):
1492 """Permit Cq-Include-Trybots tags to follow the Change-Id."""
1493 self.assertMessageAccepted(
1494 'foo\n\nChange-Id: I1234\nCq-Include-Trybots: chromeos/cq:foo\n')
1495
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001496
Jack Neus8edbf642019-07-10 16:08:31 -06001497class CheckCommitMessageNoOEM(CommitMessageTestCase):
1498 """Tests for _check_change_no_include_oem."""
1499
1500 @staticmethod
1501 def CheckMessage(project, commit):
1502 return pre_upload._check_change_no_include_oem(project, commit)
1503
1504 def testNormal(self):
1505 """Accept a commit message w/o reference to an OEM/ODM."""
1506 self.assertMessageAccepted('foo\n')
1507
1508 def testHasOEM(self):
1509 """Catch commit messages referencing OEMs."""
1510 self.assertMessageRejected('HP Project\n\n')
1511 self.assertMessageRejected('hewlett-packard\n')
1512 self.assertMessageRejected('Hewlett\nPackard\n')
1513 self.assertMessageRejected('Dell Chromebook\n\n\n')
1514 self.assertMessageRejected('product@acer.com\n')
1515 self.assertMessageRejected('This is related to Asus\n')
1516 self.assertMessageRejected('lenovo machine\n')
1517
1518 def testHasODM(self):
1519 """Catch commit messages referencing ODMs."""
1520 self.assertMessageRejected('new samsung laptop\n\n')
1521 self.assertMessageRejected('pegatron(ems) project\n')
1522 self.assertMessageRejected('new Wistron device\n')
1523
1524 def testContainsOEM(self):
1525 """Check that the check handles word boundaries properly."""
1526 self.assertMessageAccepted('oheahpohea')
1527 self.assertMessageAccepted('Play an Asus7 guitar chord.\n\n')
1528
1529 def testTag(self):
1530 """Check that the check ignores tags."""
1531 self.assertMessageAccepted(
1532 'Harmless project\n'
1533 'Reviewed-by: partner@asus.corp-partner.google.com\n'
1534 'Tested-by: partner@hp.corp-partner.google.com\n'
1535 'Signed-off-by: partner@dell.corp-partner.google.com\n'
1536 'Commit-Queue: partner@lenovo.corp-partner.google.com\n'
Jack Neus8edbf642019-07-10 16:08:31 -06001537 'CC: partner@acer.corp-partner.google.com\n'
1538 'Acked-by: partner@hewlett-packard.corp-partner.google.com\n')
1539 self.assertMessageRejected(
1540 'Asus project\n'
1541 'Reviewed-by: partner@asus.corp-partner.google.com')
Mike Frysingerbb34a222019-07-31 14:40:46 -04001542 self.assertMessageRejected(
1543 'my project\n'
1544 'Bad-tag: partner@asus.corp-partner.google.com')
Jack Neus8edbf642019-07-10 16:08:31 -06001545
1546
Mike Frysinger36b2ebc2014-10-31 14:02:03 -04001547class CheckCommitMessageStyle(CommitMessageTestCase):
1548 """Tests for _check_commit_message_style."""
1549
1550 @staticmethod
1551 def CheckMessage(project, commit):
1552 return pre_upload._check_commit_message_style(project, commit)
1553
1554 def testNormal(self):
1555 """Accept valid commit messages."""
1556 self.assertMessageAccepted('one sentence.\n')
1557 self.assertMessageAccepted('some.module: do it!\n')
1558 self.assertMessageAccepted('one line\n\nmore stuff here.')
1559
1560 def testNoBlankSecondLine(self):
1561 """Reject messages that have stuff on the second line."""
1562 self.assertMessageRejected('one sentence.\nbad fish!\n')
1563
1564 def testFirstLineMultipleSentences(self):
1565 """Reject messages that have more than one sentence in the summary."""
1566 self.assertMessageRejected('one sentence. two sentence!\n')
1567
1568 def testFirstLineTooLone(self):
1569 """Reject first lines that are too long."""
1570 self.assertMessageRejected('o' * 200)
1571
1572
Mike Frysinger292b45d2014-11-25 01:17:10 -05001573def DiffEntry(src_file=None, dst_file=None, src_mode=None, dst_mode='100644',
1574 status='M'):
1575 """Helper to create a stub RawDiffEntry object"""
1576 if src_mode is None:
1577 if status == 'A':
1578 src_mode = '000000'
1579 elif status == 'M':
1580 src_mode = dst_mode
1581 elif status == 'D':
1582 src_mode = dst_mode
1583 dst_mode = '000000'
1584
1585 src_sha = dst_sha = 'abc'
1586 if status == 'D':
1587 dst_sha = '000000'
1588 elif status == 'A':
1589 src_sha = '000000'
1590
1591 return git.RawDiffEntry(src_mode=src_mode, dst_mode=dst_mode, src_sha=src_sha,
1592 dst_sha=dst_sha, status=status, score=None,
1593 src_file=src_file, dst_file=dst_file)
1594
1595
Mike Frysingerb2496652019-09-12 23:35:46 -04001596class HelpersTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001597 """Various tests for utility functions."""
1598
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001599 def setUp(self):
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001600 os.chdir(self.tempdir)
1601
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001602 self.PatchObject(git, 'RawDiff', return_value=[
1603 # A modified normal file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001604 DiffEntry(src_file='buildbot/constants.py', status='M'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001605 # A new symlink file.
Bob Haarman0dc1f942020-10-03 00:06:59 +00001606 DiffEntry(dst_file='scripts/cros_env_allowlist', dst_mode='120000',
Mike Frysinger292b45d2014-11-25 01:17:10 -05001607 status='A'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001608 # A deleted file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001609 DiffEntry(src_file='scripts/sync_sonic.py', status='D'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001610 ])
1611
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001612 def _WritePresubmitIgnoreFile(self, subdir, data):
1613 """Writes to a .presubmitignore file in the passed-in subdirectory."""
1614 directory = os.path.join(self.tempdir, subdir)
1615 if not os.path.exists(directory):
1616 os.makedirs(directory)
1617 osutils.WriteFile(os.path.join(directory, pre_upload._IGNORE_FILE), data)
1618
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001619 def testGetAffectedFilesNoDeletesNoRelative(self):
1620 """Verify _get_affected_files() works w/no delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001621 path = os.getcwd()
1622 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1623 relative=False)
1624 exp_files = [os.path.join(path, 'buildbot/constants.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001625 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001626
1627 def testGetAffectedFilesDeletesNoRelative(self):
1628 """Verify _get_affected_files() works w/delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001629 path = os.getcwd()
1630 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1631 relative=False)
1632 exp_files = [os.path.join(path, 'buildbot/constants.py'),
1633 os.path.join(path, 'scripts/sync_sonic.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001634 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001635
1636 def testGetAffectedFilesNoDeletesRelative(self):
1637 """Verify _get_affected_files() works w/no delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001638 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1639 relative=True)
1640 exp_files = ['buildbot/constants.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001641 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001642
1643 def testGetAffectedFilesDeletesRelative(self):
1644 """Verify _get_affected_files() works w/delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001645 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1646 relative=True)
1647 exp_files = ['buildbot/constants.py', 'scripts/sync_sonic.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001648 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001649
Mike Frysinger292b45d2014-11-25 01:17:10 -05001650 def testGetAffectedFilesDetails(self):
1651 """Verify _get_affected_files() works w/full_details."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001652 files = pre_upload._get_affected_files('HEAD', full_details=True,
1653 relative=True)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001654 self.assertEqual(files[0].src_file, 'buildbot/constants.py')
Mike Frysinger292b45d2014-11-25 01:17:10 -05001655
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001656 def testGetAffectedFilesPresubmitIgnoreDirectory(self):
1657 """Verify .presubmitignore can be used to exclude a directory."""
1658 self._WritePresubmitIgnoreFile('.', 'buildbot/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001659 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001660
1661 def testGetAffectedFilesPresubmitIgnoreDirectoryWildcard(self):
1662 """Verify .presubmitignore can be used with a directory wildcard."""
1663 self._WritePresubmitIgnoreFile('.', '*/constants.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001664 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001665
1666 def testGetAffectedFilesPresubmitIgnoreWithinDirectory(self):
1667 """Verify .presubmitignore can be placed in a subdirectory."""
1668 self._WritePresubmitIgnoreFile('buildbot', '*.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001669 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001670
1671 def testGetAffectedFilesPresubmitIgnoreDoesntMatch(self):
1672 """Verify .presubmitignore has no effect when it doesn't match a file."""
1673 self._WritePresubmitIgnoreFile('buildbot', '*.txt')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001674 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True),
1675 ['buildbot/constants.py'])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001676
1677 def testGetAffectedFilesPresubmitIgnoreAddedFile(self):
1678 """Verify .presubmitignore matches added files."""
1679 self._WritePresubmitIgnoreFile('.', 'buildbot/\nscripts/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001680 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True,
1681 include_symlinks=True),
1682 [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001683
1684 def testGetAffectedFilesPresubmitIgnoreSkipIgnoreFile(self):
1685 """Verify .presubmitignore files are automatically skipped."""
1686 self.PatchObject(git, 'RawDiff', return_value=[
1687 DiffEntry(src_file='.presubmitignore', status='M')
1688 ])
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001689 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Mike Frysinger292b45d2014-11-25 01:17:10 -05001690
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001691
Mike Frysingerb2496652019-09-12 23:35:46 -04001692class ExecFilesTest(PreUploadTestCase):
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001693 """Tests for _check_exec_files."""
1694
1695 def setUp(self):
1696 self.diff_mock = self.PatchObject(git, 'RawDiff')
1697
1698 def testNotExec(self):
1699 """Do not flag files that are not executable."""
1700 self.diff_mock.return_value = [
1701 DiffEntry(src_file='make.conf', dst_mode='100644', status='A'),
1702 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001703 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001704
1705 def testExec(self):
1706 """Flag files that are executable."""
1707 self.diff_mock.return_value = [
1708 DiffEntry(src_file='make.conf', dst_mode='100755', status='A'),
1709 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001710 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001711
1712 def testDeletedExec(self):
1713 """Ignore bad files that are being deleted."""
1714 self.diff_mock.return_value = [
1715 DiffEntry(src_file='make.conf', dst_mode='100755', status='D'),
1716 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001717 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001718
1719 def testModifiedExec(self):
1720 """Flag bad files that weren't exec, but now are."""
1721 self.diff_mock.return_value = [
1722 DiffEntry(src_file='make.conf', src_mode='100644', dst_mode='100755',
1723 status='M'),
1724 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001725 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001726
1727 def testNormalExec(self):
1728 """Don't flag normal files (e.g. scripts) that are executable."""
1729 self.diff_mock.return_value = [
1730 DiffEntry(src_file='foo.sh', dst_mode='100755', status='A'),
1731 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001732 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001733
1734
Mike Frysingerb2496652019-09-12 23:35:46 -04001735class CheckForUprev(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger292b45d2014-11-25 01:17:10 -05001736 """Tests for _check_for_uprev."""
1737
1738 def setUp(self):
1739 self.file_mock = self.PatchObject(git, 'RawDiff')
1740
1741 def _Files(self, files):
1742 """Create |files| in the tempdir and return full paths to them."""
1743 for obj in files:
1744 if obj.status == 'D':
1745 continue
1746 if obj.dst_file is None:
1747 f = obj.src_file
1748 else:
1749 f = obj.dst_file
1750 osutils.Touch(os.path.join(self.tempdir, f), makedirs=True)
1751 return files
1752
1753 def assertAccepted(self, files, project='project', commit='fake sha1'):
1754 """Assert _check_for_uprev accepts |files|."""
1755 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001756 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1757 project_top=self.tempdir)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001758 self.assertIsNone(ret)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001759
1760 def assertRejected(self, files, project='project', commit='fake sha1'):
1761 """Assert _check_for_uprev rejects |files|."""
1762 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001763 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1764 project_top=self.tempdir)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001765 self.assertTrue(isinstance(ret, errors.HookFailure))
1766
Bob Haarman0dc1f942020-10-03 00:06:59 +00001767 def testAllowlistOverlay(self):
1768 """Skip checks on allowed overlays."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001769 self.assertAccepted([DiffEntry(src_file='cat/pkg/pkg-0.ebuild')],
1770 project='chromiumos/overlays/portage-stable')
1771
Bob Haarman0dc1f942020-10-03 00:06:59 +00001772 def testAllowlistFiles(self):
1773 """Skip checks on allowed files."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001774 files = ['ChangeLog', 'Manifest', 'metadata.xml']
1775 self.assertAccepted([DiffEntry(src_file=os.path.join('c', 'p', x),
1776 status='M')
1777 for x in files])
1778
1779 def testRejectBasic(self):
1780 """Reject ebuilds missing uprevs."""
1781 self.assertRejected([DiffEntry(src_file='c/p/p-0.ebuild', status='M')])
1782
1783 def testNewPackage(self):
1784 """Accept new ebuilds w/out uprevs."""
1785 self.assertAccepted([DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1786 self.assertAccepted([DiffEntry(src_file='c/p/p-0-r12.ebuild', status='A')])
1787
1788 def testModifiedFilesOnly(self):
1789 """Reject ebuilds w/out uprevs and changes in files/."""
1790 osutils.Touch(os.path.join(self.tempdir, 'cat/pkg/pkg-0.ebuild'),
1791 makedirs=True)
1792 self.assertRejected([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1793 self.assertRejected([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1794
1795 def testFilesNoEbuilds(self):
1796 """Ignore changes to paths w/out ebuilds."""
1797 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1798 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1799
1800 def testModifiedFilesWithUprev(self):
1801 """Accept ebuilds w/uprevs and changes in files/."""
1802 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='A'),
1803 DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1804 self.assertAccepted([
1805 DiffEntry(src_file='c/p/files/f', status='M'),
1806 DiffEntry(src_file='c/p/p-0-r1.ebuild', src_mode='120000',
1807 dst_file='c/p/p-0-r2.ebuild', dst_mode='120000', status='R')])
1808
Gwendal Grignoua3086c32014-12-09 11:17:22 -08001809 def testModifiedFilesWith9999(self):
1810 """Accept 9999 ebuilds and changes in files/."""
1811 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M'),
1812 DiffEntry(src_file='c/p/p-9999.ebuild', status='M')])
1813
C Shapiroae157ae2017-09-18 16:24:03 -06001814 def testModifiedFilesIn9999SubDirWithout9999Change(self):
1815 """Accept changes in files/ with a parent 9999 ebuild"""
1816 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
1817 os.makedirs(os.path.dirname(ebuild_9999_file))
1818 osutils.WriteFile(ebuild_9999_file, 'fake')
1819 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M')])
1820
Stephen Boyd6bf5ea82020-10-15 00:02:07 -07001821 def testModifiedFilesAndProfilesWith9999(self):
1822 """Accept changes in files/ with a parent 9999 ebuild and profile change"""
1823 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
1824 os.makedirs(os.path.dirname(ebuild_9999_file))
1825 osutils.WriteFile(ebuild_9999_file, 'fake')
1826 self.assertAccepted([
1827 DiffEntry(src_file='c/p/files/f', status='M'),
1828 DiffEntry(src_file='c/profiles/base/make.defaults', status='M')])
1829
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001830
Mike Frysingerb2496652019-09-12 23:35:46 -04001831class DirectMainTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger55f85b52014-12-18 14:45:21 -05001832 """Tests for direct_main()"""
1833
1834 def setUp(self):
1835 self.hooks_mock = self.PatchObject(pre_upload, '_run_project_hooks',
1836 return_value=None)
1837
1838 def testNoArgs(self):
1839 """If run w/no args, should check the current dir."""
1840 ret = pre_upload.direct_main([])
1841 self.assertEqual(ret, 0)
1842 self.hooks_mock.assert_called_once_with(
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001843 mock.ANY, proj_dir=os.getcwd(), commit_list=[], presubmit=mock.ANY,
1844 config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001845
1846 def testExplicitDir(self):
1847 """Verify we can run on a diff dir."""
1848 # Use the chromite dir since we know it exists.
1849 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR])
1850 self.assertEqual(ret, 0)
1851 self.hooks_mock.assert_called_once_with(
1852 mock.ANY, proj_dir=constants.CHROMITE_DIR, commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001853 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001854
1855 def testBogusProject(self):
1856 """A bogus project name should be fine (use default settings)."""
1857 # Use the chromite dir since we know it exists.
1858 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR,
1859 '--project', 'foooooooooo'])
1860 self.assertEqual(ret, 0)
1861 self.hooks_mock.assert_called_once_with(
1862 'foooooooooo', proj_dir=constants.CHROMITE_DIR, commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001863 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001864
1865 def testBogustProjectNoDir(self):
1866 """Make sure --dir is detected even with --project."""
1867 ret = pre_upload.direct_main(['--project', 'foooooooooo'])
1868 self.assertEqual(ret, 0)
1869 self.hooks_mock.assert_called_once_with(
Mike Frysinger9ba001a2020-11-20 01:02:11 -05001870 'foooooooooo', proj_dir=os.path.dirname(os.path.abspath(__file__)),
1871 commit_list=[], presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001872
1873 def testNoGitDir(self):
1874 """We should die when run on a non-git dir."""
1875 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
1876 ['--dir', self.tempdir])
1877
1878 def testNoDir(self):
1879 """We should die when run on a missing dir."""
1880 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
1881 ['--dir', os.path.join(self.tempdir, 'foooooooo')])
1882
1883 def testCommitList(self):
1884 """Any args on the command line should be treated as commits."""
1885 commits = ['sha1', 'sha2', 'shaaaaaaaaaaaan']
1886 ret = pre_upload.direct_main(commits)
1887 self.assertEqual(ret, 0)
1888 self.hooks_mock.assert_called_once_with(
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001889 mock.ANY, proj_dir=mock.ANY, commit_list=commits, presubmit=mock.ANY,
1890 config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001891
1892
Mike Frysingerb2496652019-09-12 23:35:46 -04001893class CheckRustfmtTest(PreUploadTestCase):
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001894 """Tests for _check_rustfmt."""
1895
1896 def setUp(self):
1897 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1898
1899 def testBadRustFile(self):
1900 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
1901 # Bad because it's missing trailing newline.
Mike Frysingere85b0062019-08-20 15:10:33 -04001902 content = 'fn main() {}'
1903 self.content_mock.return_value = content
1904 self.PatchObject(pre_upload, '_run_command', return_value=content + '\n')
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001905 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1906 self.assertIsNotNone(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001907 self.assertEqual('Files not formatted with rustfmt: '
1908 "(run 'cargo fmt' to fix)",
1909 failure.msg)
1910 self.assertEqual(['a.rs'], failure.items)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001911
1912 def testGoodRustFile(self):
1913 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
Mike Frysingere85b0062019-08-20 15:10:33 -04001914 content = 'fn main() {}\n'
1915 self.content_mock.return_value = content
1916 self.PatchObject(pre_upload, '_run_command', return_value=content)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001917 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1918 self.assertIsNone(failure)
1919
1920 def testFilterNonRustFiles(self):
1921 self.PatchObject(pre_upload, '_get_affected_files',
1922 return_value=['a.cc', 'a.rsa', 'a.irs', 'rs.cc'])
1923 self.content_mock.return_value = 'fn main() {\n}'
1924 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1925 self.assertIsNone(failure)
1926
1927
Keiichi Watanabe6ab73fd2020-07-14 23:42:02 +09001928class GetCargoClippyParserTest(cros_test_lib.TestCase):
1929 """Tests for _get_cargo_clippy_parser."""
1930
1931 def testSingleProject(self):
1932 parser = pre_upload._get_cargo_clippy_parser()
1933 args = parser.parse_args(['--project', 'foo'])
1934 self.assertEqual(args.project,
1935 [pre_upload.ClippyProject(root='foo', script=None)])
1936
1937 def testMultipleProjects(self):
1938 parser = pre_upload._get_cargo_clippy_parser()
1939 args = parser.parse_args(['--project', 'foo:bar',
1940 '--project', 'baz'])
1941 self.assertCountEqual(args.project,
1942 [pre_upload.ClippyProject(root='foo', script='bar'),
1943 pre_upload.ClippyProject(root='baz', script=None)])
1944
1945
1946class CheckCargoClippyTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
1947 """Tests for _check_cargo_clippy."""
1948
1949 def setUp(self):
1950 self.project = pre_upload.Project(name='PROJECT', dir=self.tempdir,
1951 remote=None)
1952
1953 def testClippy(self):
1954 """Verify clippy is called when a monitored file was changed."""
1955 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
1956
1957 self.PatchObject(pre_upload, '_get_affected_files',
1958 return_value=[f'{self.project.dir}/repo_a/a.rs'])
1959
1960 ret = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
1961 options=['--project=repo_a',
1962 '--project=repo_b:foo'])
1963 self.assertFalse(ret)
1964
1965 # Check if `cargo clippy` ran.
1966 called = False
1967 for args, _ in rc_mock.call_args_list:
1968 cmd = args[0]
1969 if len(cmd) > 1 and cmd[0] == 'cargo' and cmd[1] == 'clippy':
1970 called = True
1971 break
1972
1973 self.assertTrue(called)
1974
1975 def testDontRun(self):
1976 """Skip checks when no monitored files are modified."""
1977 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
1978
1979 # A file under `repo_a` was monitored.
1980 self.PatchObject(pre_upload, '_get_affected_files',
1981 return_value=[f'{self.project.dir}/repo_a/a.rs'])
1982 # But, we only care about files under `repo_b`.
1983 errs = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
1984 options=['--project=repo_b:foo'])
1985
1986 self.assertFalse(errs)
1987
1988 rc_mock.assert_not_called()
1989
1990 def testCustomScript(self):
1991 """Verify project-specific script is used."""
1992 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
1993
1994 self.PatchObject(pre_upload, '_get_affected_files',
1995 return_value=[f'{self.project.dir}/repo_b/b.rs'])
1996
1997 errs = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
1998 options=['--project=repo_a',
1999 '--project=repo_b:foo'])
2000 self.assertFalse(errs)
2001
2002 # Check if the script `foo` ran.
2003 called = False
2004 for args, _ in rc_mock.call_args_list:
2005 cmd = args[0]
2006 if len(cmd) > 0 and cmd[0] == os.path.join(self.project.dir, 'foo'):
2007 called = True
2008 break
2009
2010 self.assertTrue(called)
2011
2012
Mike Frysinger180ecd62020-08-19 00:41:51 -04002013class OverrideHooksProcessing(PreUploadTestCase):
2014 """Verify _get_override_hooks processing."""
2015
2016 @staticmethod
2017 def parse(data):
2018 """Helper to create a config & parse it."""
2019 cfg = configparser.ConfigParser()
2020 cfg.read_string(data)
2021 return pre_upload._get_override_hooks(cfg)
2022
2023 def testHooks(self):
2024 """Verify we reject unknown hook names (e.g. typos)."""
2025 with self.assertRaises(ValueError) as e:
2026 self.parse("""
2027[Hook Overrides]
2028foobar: true
2029""")
2030 self.assertIn('foobar', str(e.exception))
2031
2032 def testImplicitDisable(self):
2033 """Verify non-common hooks aren't enabled by default."""
2034 enabled, _ = self.parse('')
2035 self.assertNotIn(pre_upload._run_checkpatch, enabled)
2036
2037 def testExplicitDisable(self):
2038 """Verify hooks disabled are disabled."""
2039 _, disabled = self.parse("""
2040[Hook Overrides]
2041tab_check: false
2042""")
2043 self.assertIn(pre_upload._check_no_tabs, disabled)
2044
2045 def testExplicitEnable(self):
2046 """Verify hooks enabled are enabled."""
2047 enabled, _ = self.parse("""
2048[Hook Overrides]
2049tab_check: true
2050""")
2051 self.assertIn(pre_upload._check_no_tabs, enabled)
2052
2053 def testOptions(self):
2054 """Verify hook options are loaded."""
2055 enabled, _ = self.parse("""
2056[Hook Overrides Options]
2057keyword_check: --kw
2058""")
2059 for func in enabled:
2060 if func.__name__ == 'keyword_check':
2061 self.assertIn('options', func.keywords)
2062 self.assertEqual(func.keywords['options'], ['--kw'])
2063 break
2064 else:
2065 self.fail('could not find "keyword_check" enabled hook')
2066
2067 def testSignOffField(self):
2068 """Verify signoff field handling."""
2069 # Enforce no s-o-b by default.
2070 enabled, disabled = self.parse('')
2071 self.assertIn(pre_upload._check_change_has_no_signoff_field, enabled)
2072 self.assertNotIn(pre_upload._check_change_has_signoff_field, enabled)
2073 self.assertNotIn(pre_upload._check_change_has_signoff_field, disabled)
2074
2075 # If disabled, don't enforce either policy.
2076 enabled, disabled = self.parse("""
2077[Hook Overrides]
2078signoff_check: false
2079""")
2080 self.assertNotIn(pre_upload._check_change_has_no_signoff_field, enabled)
2081 self.assertNotIn(pre_upload._check_change_has_signoff_field, enabled)
2082 self.assertIn(pre_upload._check_change_has_signoff_field, disabled)
2083
2084 # If enabled, require s-o-b.
2085 enabled, disabled = self.parse("""
2086[Hook Overrides]
2087signoff_check: true
2088""")
2089 self.assertNotIn(pre_upload._check_change_has_no_signoff_field, enabled)
2090 self.assertIn(pre_upload._check_change_has_signoff_field, enabled)
2091 self.assertNotIn(pre_upload._check_change_has_signoff_field, disabled)
2092
2093 def testBranchField(self):
2094 """Verify branch field enabling."""
2095 # Should be disabled by default.
2096 enabled, disabled = self.parse('')
2097 self.assertIn(pre_upload._check_change_has_no_branch_field, enabled)
2098 self.assertNotIn(pre_upload._check_change_has_branch_field, enabled)
2099 self.assertNotIn(pre_upload._check_change_has_branch_field, disabled)
2100
2101 # Should be disabled if requested.
2102 enabled, disabled = self.parse("""
2103[Hook Overrides]
2104branch_check: false
2105""")
2106 self.assertIn(pre_upload._check_change_has_no_branch_field, enabled)
2107 self.assertNotIn(pre_upload._check_change_has_branch_field, enabled)
2108 self.assertIn(pre_upload._check_change_has_branch_field, disabled)
2109
2110 # Should be enabled if requested.
2111 enabled, disabled = self.parse("""
2112[Hook Overrides]
2113branch_check: true
2114""")
2115 self.assertNotIn(pre_upload._check_change_has_no_branch_field, enabled)
2116 self.assertIn(pre_upload._check_change_has_branch_field, enabled)
2117 self.assertNotIn(pre_upload._check_change_has_branch_field, disabled)
2118
2119
Tom Hughes1ed799d2020-09-25 14:37:28 -07002120class ProjectHooksProcessing(PreUploadTestCase, cros_test_lib.TempDirTestCase):
2121 """Verify _get_project_hooks processing."""
2122
2123 def parse(self, data):
2124 """Helper to write config and parse it."""
2125 filename = os.path.join(self.tempdir, 'config')
2126 osutils.WriteFile(filename, data)
2127 return pre_upload._get_project_hooks(project='test', presubmit=True,
2128 config_file=filename)
2129
2130 def testClangFormatCheckDefault(self):
2131 """Verify clang-format check disabled by default."""
2132 hooks = self.parse('')
2133 for func in hooks:
2134 self.assertNotEqual(func.__name__, '_check_clang_format')
2135 self.assertNotEqual(func.__name__, 'clang_format_check')
2136
2137 def testClangFormatCheckDisabled(self):
2138 """Verify clang-format check disabled when requested."""
2139 hooks = self.parse("""
2140[Hook Overrides]
2141clang_format_check: false
2142""")
2143 for func in hooks:
2144 self.assertNotEqual(func.__name__, '_check_clang_format')
2145 self.assertNotEqual(func.__name__, 'clang_format_check')
2146
2147 def testClangFormatCheckEnabled(self):
2148 """Verify clang-format check enabled when requested."""
2149 hooks = self.parse("""
2150[Hook Overrides]
2151clang_format_check: true
2152""")
2153 for func in hooks:
2154 if func.__name__ == '_check_clang_format':
2155 self.assertFalse(hasattr(func, 'keywords'))
2156 break
2157 else:
2158 self.fail('could not find "_check_clang_format" enabled hook')
2159
2160 def testClangFormatCheckEnabledWithOptions(self):
2161 """Verify clang-format check has options when provided."""
2162 hooks = self.parse("""
2163[Hook Overrides]
2164clang_format_check: true
2165
2166[Hook Overrides Options]
2167clang_format_check:
2168 some_dir/
2169""")
2170 for func in hooks:
2171 if func.__name__ == 'clang_format_check':
2172 self.assertIn('options', func.keywords)
2173 self.assertEqual(func.keywords['options'], ['some_dir/'])
2174 break
2175 else:
2176 self.fail('could not find "clang_format_check" enabled hook')
2177
2178
Jon Salz98255932012-08-18 14:48:02 +08002179if __name__ == '__main__':
Mike Frysinger884a8dd2015-05-17 03:43:43 -04002180 cros_test_lib.main(module=__name__)