blob: 8aeeee702a9f36b0ddf4f3f3f757e2f78cdfb981 [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 Frysinger6ee76b82020-11-20 01:16:06 -0500804class CheckEbuildR0(PreUploadTestCase):
805 """Tests for _check_ebuild_r0."""
806
807 def setUp(self):
808 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
809
810 def testNoMatches(self):
811 """Handle no matching files."""
812 self.file_mock.return_value = []
813 ret = pre_upload._check_ebuild_r0('project', 'HEAD')
814 self.assertIsNone(ret)
815
816 def testBadEbuilds(self):
817 """Handle matching r0 files."""
818 self.file_mock.return_value = ['foo-1-r0.ebuild']
819 ret = pre_upload._check_ebuild_r0('project', 'HEAD')
820 self.assertIsNotNone(ret)
821
822
Mike Frysingerb2496652019-09-12 23:35:46 -0400823class CheckEbuildVirtualPv(PreUploadTestCase):
Mike Frysingercd363c82014-02-01 05:20:18 -0500824 """Tests for _check_ebuild_virtual_pv."""
825
Alex Deymo643ac4c2015-09-03 10:40:50 -0700826 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
827 CHROMIUMOS_OVERLAY = ProjectNamed('chromiumos/overlays/chromiumos')
828 BOARD_OVERLAY = ProjectNamed('chromiumos/overlays/board-overlays')
829 PRIVATE_OVERLAY = ProjectNamed('chromeos/overlays/overlay-link-private')
830 PRIVATE_VARIANT_OVERLAY = ProjectNamed('chromeos/overlays/'
831 'overlay-variant-daisy-spring-private')
Mike Frysingercd363c82014-02-01 05:20:18 -0500832
833 def setUp(self):
834 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
835
836 def testNoVirtuals(self):
837 """Skip non virtual packages."""
838 self.file_mock.return_value = ['some/package/package-3.ebuild']
Alex Deymo643ac4c2015-09-03 10:40:50 -0700839 ret = pre_upload._check_ebuild_virtual_pv(ProjectNamed('overlay'), 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400840 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500841
842 def testCommonVirtuals(self):
843 """Non-board overlays should use PV=1."""
844 template = 'virtual/foo/foo-%s.ebuild'
845 self.file_mock.return_value = [template % '1']
846 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400847 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500848
849 self.file_mock.return_value = [template % '2']
850 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500851 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500852
853 def testPublicBoardVirtuals(self):
854 """Public board overlays should use PV=2."""
855 template = 'overlay-lumpy/virtual/foo/foo-%s.ebuild'
856 self.file_mock.return_value = [template % '2']
857 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400858 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500859
860 self.file_mock.return_value = [template % '2.5']
861 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500862 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500863
864 def testPublicBoardVariantVirtuals(self):
865 """Public board variant overlays should use PV=2.5."""
866 template = 'overlay-variant-lumpy-foo/virtual/foo/foo-%s.ebuild'
867 self.file_mock.return_value = [template % '2.5']
868 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400869 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500870
871 self.file_mock.return_value = [template % '3']
872 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500873 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500874
875 def testPrivateBoardVirtuals(self):
876 """Private board overlays should use PV=3."""
877 template = 'virtual/foo/foo-%s.ebuild'
878 self.file_mock.return_value = [template % '3']
879 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400880 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500881
882 self.file_mock.return_value = [template % '3.5']
883 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500884 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500885
886 def testPrivateBoardVariantVirtuals(self):
887 """Private board variant overlays should use PV=3.5."""
888 template = 'virtual/foo/foo-%s.ebuild'
889 self.file_mock.return_value = [template % '3.5']
890 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400891 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500892
Bernie Thompsone5ee1822016-01-12 14:22:23 -0800893 def testSpecialVirtuals(self):
894 """Some cases require deeper versioning and can be >= 4."""
895 template = 'virtual/foo/foo-%s.ebuild'
Mike Frysingercd363c82014-02-01 05:20:18 -0500896 self.file_mock.return_value = [template % '4']
897 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400898 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500899
Bernie Thompsone5ee1822016-01-12 14:22:23 -0800900 self.file_mock.return_value = [template % '4.5']
901 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400902 self.assertIsNone(ret)
Mike Frysinger98638102014-08-28 00:15:08 -0400903
Mike Frysingerb2496652019-09-12 23:35:46 -0400904class CheckCrosLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -0700905 """Tests for _check_cros_license."""
Mike Frysinger98638102014-08-28 00:15:08 -0400906
907 def setUp(self):
908 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
909 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
910
911 def testOldHeaders(self):
912 """Accept old header styles."""
913 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400914 (u'#!/bin/sh\n'
915 u'# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.\n'
916 u'# Use of this source code is governed by a BSD-style license that'
917 u' can be\n'
918 u'# found in the LICENSE file.\n'),
919 (u'// Copyright 2010-2013 The Chromium OS Authors. All rights reserved.'
920 u'\n// Use of this source code is governed by a BSD-style license that'
921 u' can be\n'
922 u'// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -0400923 )
924 self.file_mock.return_value = ['file']
925 for header in HEADERS:
926 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +0900927 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
928
929 def testNewFileYear(self):
930 """Added files should have the current year in license header."""
931 year = datetime.datetime.now().year
932 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400933 (u'// Copyright 2016 The Chromium OS Authors. All rights reserved.\n'
934 u'// Use of this source code is governed by a BSD-style license that'
935 u' can be\n'
936 u'// found in the LICENSE file.\n'),
937 (u'// Copyright %d The Chromium OS Authors. All rights reserved.\n'
938 u'// Use of this source code is governed by a BSD-style license that'
939 u' can be\n'
940 u'// found in the LICENSE file.\n') % year,
Keigo Oka7e880ac2019-07-03 15:03:43 +0900941 )
942 want_error = (True, False)
943 def fake_get_affected_files(_, relative, include_adds=True):
944 _ = relative
945 if include_adds:
946 return ['file']
947 else:
948 return []
949
950 self.file_mock.side_effect = fake_get_affected_files
951 for i, header in enumerate(HEADERS):
952 self.content_mock.return_value = header
953 if want_error[i]:
954 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
955 else:
956 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -0400957
958 def testRejectC(self):
959 """Reject the (c) in newer headers."""
960 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400961 (u'// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.'
962 u'\n'
963 u'// Use of this source code is governed by a BSD-style license that'
964 u' can be\n'
965 u'// found in the LICENSE file.\n'),
966 (u'// Copyright (c) 2020 The Chromium OS Authors. All rights reserved.'
967 u'\n'
968 u'// Use of this source code is governed by a BSD-style license that'
969 u' can be\n'
970 u'// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -0400971 )
972 self.file_mock.return_value = ['file']
973 for header in HEADERS:
974 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +0900975 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -0700976
Brian Norris68838dd2018-09-26 18:30:24 -0700977 def testNoLeadingSpace(self):
978 """Allow headers without leading space (e.g., not a source comment)"""
979 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400980 (u'Copyright 2018 The Chromium OS Authors. All rights reserved.\n'
981 u'Use of this source code is governed by a BSD-style license that '
982 u'can be\n'
983 u'found in the LICENSE file.\n'),
Brian Norris68838dd2018-09-26 18:30:24 -0700984 )
985 self.file_mock.return_value = ['file']
986 for header in HEADERS:
987 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +0900988 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Brian Norris68838dd2018-09-26 18:30:24 -0700989
Keigo Oka9732e382019-06-28 17:44:59 +0900990 def testNoExcludedGolang(self):
991 """Don't exclude .go files for license checks."""
992 self.file_mock.return_value = ['foo/main.go']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400993 self.content_mock.return_value = u'package main\nfunc main() {}'
Keigo Oka7e880ac2019-07-03 15:03:43 +0900994 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Keigo Oka9732e382019-06-28 17:44:59 +0900995
Ken Turnerd07564b2018-02-08 17:57:59 +1100996 def testIgnoreExcludedPaths(self):
997 """Ignores excluded paths for license checks."""
998 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400999 self.content_mock.return_value = u'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +09001000 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +11001001
Tom Hughes90b7bd42020-11-10 10:31:49 -08001002 def testIgnoreMetadataFiles(self):
1003 """Ignores metadata files for license checks."""
1004 self.file_mock.return_value = ['foo/DIR_METADATA']
1005 self.content_mock.return_value = u'team_email: "team@chromium.org"'
1006 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
1007
Chris McDonald7b63c8e2019-04-25 10:27:27 -06001008 def testIgnoreTopLevelExcludedPaths(self):
1009 """Ignores excluded paths for license checks."""
1010 self.file_mock.return_value = ['OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001011 self.content_mock.return_value = u'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +09001012 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001013
Mike Frysingerb2496652019-09-12 23:35:46 -04001014class CheckAOSPLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -07001015 """Tests for _check_aosp_license."""
1016
1017 def setUp(self):
1018 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1019 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1020
1021 def testHeaders(self):
1022 """Accept old header styles."""
1023 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001024 u"""//
Alex Deymof5792ce2015-08-24 22:50:08 -07001025// Copyright (C) 2011 The Android Open Source Project
1026//
1027// Licensed under the Apache License, Version 2.0 (the "License");
1028// you may not use this file except in compliance with the License.
1029// You may obtain a copy of the License at
1030//
1031// http://www.apache.org/licenses/LICENSE-2.0
1032//
1033// Unless required by applicable law or agreed to in writing, software
1034// distributed under the License is distributed on an "AS IS" BASIS,
1035// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1036// See the License for the specific language governing permissions and
1037// limitations under the License.
1038//
1039""",
Mike Frysinger71e643e2019-09-13 17:26:39 -04001040 u"""#
Alex Deymof5792ce2015-08-24 22:50:08 -07001041# Copyright (c) 2015 The Android Open Source Project
1042#
1043# Licensed under the Apache License, Version 2.0 (the "License");
1044# you may not use this file except in compliance with the License.
1045# You may obtain a copy of the License at
1046#
1047# http://www.apache.org/licenses/LICENSE-2.0
1048#
1049# Unless required by applicable law or agreed to in writing, software
1050# distributed under the License is distributed on an "AS IS" BASIS,
1051# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1052# See the License for the specific language governing permissions and
1053# limitations under the License.
1054#
1055""",
1056 )
1057 self.file_mock.return_value = ['file']
1058 for header in HEADERS:
1059 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001060 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001061
1062 def testRejectNoLinesAround(self):
1063 """Reject headers missing the empty lines before/after the license."""
1064 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001065 u"""# Copyright (c) 2015 The Android Open Source Project
Alex Deymof5792ce2015-08-24 22:50:08 -07001066#
1067# Licensed under the Apache License, Version 2.0 (the "License");
1068# you may not use this file except in compliance with the License.
1069# You may obtain a copy of the License at
1070#
1071# http://www.apache.org/licenses/LICENSE-2.0
1072#
1073# Unless required by applicable law or agreed to in writing, software
1074# distributed under the License is distributed on an "AS IS" BASIS,
1075# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1076# See the License for the specific language governing permissions and
1077# limitations under the License.
1078""",
1079 )
1080 self.file_mock.return_value = ['file']
1081 for header in HEADERS:
1082 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001083 self.assertIsNotNone(pre_upload._check_aosp_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -04001084
Ken Turnerd07564b2018-02-08 17:57:59 +11001085 def testIgnoreExcludedPaths(self):
1086 """Ignores excluded paths for license checks."""
1087 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001088 self.content_mock.return_value = u'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001089 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +11001090
Chris McDonald7b63c8e2019-04-25 10:27:27 -06001091 def testIgnoreTopLevelExcludedPaths(self):
1092 """Ignores excluded paths for license checks."""
1093 self.file_mock.return_value = ['OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001094 self.content_mock.return_value = u'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001095 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
1096
Mike Frysinger98638102014-08-28 00:15:08 -04001097
Mike Frysingerb2496652019-09-12 23:35:46 -04001098class CheckLayoutConfTestCase(PreUploadTestCase):
Mike Frysingerd7734522015-02-26 16:12:43 -05001099 """Tests for _check_layout_conf."""
1100
1101 def setUp(self):
1102 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1103 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1104
1105 def assertAccepted(self, files, project='project', commit='fake sha1'):
1106 """Assert _check_layout_conf accepts |files|."""
1107 self.file_mock.return_value = files
1108 ret = pre_upload._check_layout_conf(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001109 self.assertIsNone(ret, msg='rejected with:\n%s' % ret)
Mike Frysingerd7734522015-02-26 16:12:43 -05001110
1111 def assertRejected(self, files, project='project', commit='fake sha1'):
1112 """Assert _check_layout_conf rejects |files|."""
1113 self.file_mock.return_value = files
1114 ret = pre_upload._check_layout_conf(project, commit)
1115 self.assertTrue(isinstance(ret, errors.HookFailure))
1116
1117 def GetLayoutConf(self, filters=()):
1118 """Return a valid layout.conf with |filters| lines removed."""
1119 all_lines = [
Mike Frysinger71e643e2019-09-13 17:26:39 -04001120 u'masters = portage-stable chromiumos',
1121 u'profile-formats = portage-2 profile-default-eapi',
1122 u'profile_eapi_when_unspecified = 5-progress',
1123 u'repo-name = link',
1124 u'thin-manifests = true',
1125 u'use-manifests = strict',
Mike Frysingerd7734522015-02-26 16:12:43 -05001126 ]
1127
1128 lines = []
1129 for line in all_lines:
1130 for filt in filters:
1131 if line.startswith(filt):
1132 break
1133 else:
1134 lines.append(line)
1135
Mike Frysinger71e643e2019-09-13 17:26:39 -04001136 return u'\n'.join(lines)
Mike Frysingerd7734522015-02-26 16:12:43 -05001137
1138 def testNoFilesToCheck(self):
1139 """Don't blow up when there are no layout.conf files."""
1140 self.assertAccepted([])
1141
1142 def testRejectRepoNameFile(self):
1143 """If profiles/repo_name is set, kick it out."""
1144 self.assertRejected(['profiles/repo_name'])
1145
1146 def testAcceptValidLayoutConf(self):
1147 """Accept a fully valid layout.conf."""
1148 self.content_mock.return_value = self.GetLayoutConf()
1149 self.assertAccepted(['metadata/layout.conf'])
1150
1151 def testAcceptUnknownKeys(self):
1152 """Accept keys we don't explicitly know about."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001153 self.content_mock.return_value = self.GetLayoutConf() + u'\nzzz-top = ok'
Mike Frysingerd7734522015-02-26 16:12:43 -05001154 self.assertAccepted(['metadata/layout.conf'])
1155
1156 def testRejectUnsorted(self):
1157 """Reject an unsorted layout.conf."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001158 self.content_mock.return_value = u'zzz-top = bad\n' + self.GetLayoutConf()
Mike Frysingerd7734522015-02-26 16:12:43 -05001159 self.assertRejected(['metadata/layout.conf'])
1160
1161 def testRejectMissingThinManifests(self):
1162 """Reject a layout.conf missing thin-manifests."""
1163 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001164 filters=[u'thin-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001165 self.assertRejected(['metadata/layout.conf'])
1166
1167 def testRejectMissingUseManifests(self):
1168 """Reject a layout.conf missing use-manifests."""
1169 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001170 filters=[u'use-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001171 self.assertRejected(['metadata/layout.conf'])
1172
1173 def testRejectMissingEapiFallback(self):
1174 """Reject a layout.conf missing profile_eapi_when_unspecified."""
1175 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001176 filters=[u'profile_eapi_when_unspecified'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001177 self.assertRejected(['metadata/layout.conf'])
1178
1179 def testRejectMissingRepoName(self):
1180 """Reject a layout.conf missing repo-name."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001181 self.content_mock.return_value = self.GetLayoutConf(filters=[u'repo-name'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001182 self.assertRejected(['metadata/layout.conf'])
1183
1184
Mike Frysingerb2496652019-09-12 23:35:46 -04001185class CommitMessageTestCase(PreUploadTestCase):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001186 """Test case for funcs that check commit messages."""
1187
1188 def setUp(self):
1189 self.msg_mock = self.PatchObject(pre_upload, '_get_commit_desc')
1190
1191 @staticmethod
1192 def CheckMessage(_project, _commit):
1193 raise AssertionError('Test class must declare CheckMessage')
1194 # This dummy return is to silence pylint warning W1111 so we don't have to
1195 # enable it for all the call sites below.
1196 return 1 # pylint: disable=W0101
1197
Alex Deymo643ac4c2015-09-03 10:40:50 -07001198 def assertMessageAccepted(self, msg, project=ProjectNamed('project'),
1199 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001200 """Assert _check_change_has_bug_field accepts |msg|."""
1201 self.msg_mock.return_value = msg
1202 ret = self.CheckMessage(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001203 self.assertIsNone(ret)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001204
Alex Deymo643ac4c2015-09-03 10:40:50 -07001205 def assertMessageRejected(self, msg, project=ProjectNamed('project'),
1206 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001207 """Assert _check_change_has_bug_field rejects |msg|."""
1208 self.msg_mock.return_value = msg
1209 ret = self.CheckMessage(project, commit)
1210 self.assertTrue(isinstance(ret, errors.HookFailure))
1211
1212
1213class CheckCommitMessageBug(CommitMessageTestCase):
1214 """Tests for _check_change_has_bug_field."""
1215
Alex Deymo643ac4c2015-09-03 10:40:50 -07001216 AOSP_PROJECT = pre_upload.Project(name='overlay', dir='', remote='aosp')
1217 CROS_PROJECT = pre_upload.Project(name='overlay', dir='', remote='cros')
1218
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001219 @staticmethod
1220 def CheckMessage(project, commit):
1221 return pre_upload._check_change_has_bug_field(project, commit)
1222
1223 def testNormal(self):
1224 """Accept a commit message w/a valid BUG."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001225 self.assertMessageAccepted('\nBUG=chromium:1234\n', self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001226 self.assertMessageAccepted('\nBUG=b:1234\n', self.CROS_PROJECT)
1227
1228 self.assertMessageAccepted('\nBug: 1234\n', self.AOSP_PROJECT)
1229 self.assertMessageAccepted('\nBug:1234\n', self.AOSP_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001230
1231 def testNone(self):
1232 """Accept BUG=None."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001233 self.assertMessageAccepted('\nBUG=None\n', self.CROS_PROJECT)
1234 self.assertMessageAccepted('\nBUG=none\n', self.CROS_PROJECT)
1235 self.assertMessageAccepted('\nBug: None\n', self.AOSP_PROJECT)
1236 self.assertMessageAccepted('\nBug:none\n', self.AOSP_PROJECT)
1237
1238 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1239 self.assertMessageRejected('\nBUG=NONE\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001240
1241 def testBlank(self):
1242 """Reject blank values."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001243 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1244 self.assertMessageRejected('\nBUG=\n', project)
1245 self.assertMessageRejected('\nBUG= \n', project)
1246 self.assertMessageRejected('\nBug:\n', project)
1247 self.assertMessageRejected('\nBug: \n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001248
1249 def testNotFirstLine(self):
1250 """Reject the first line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001251 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1252 self.assertMessageRejected('BUG=None\n\n\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001253
1254 def testNotInline(self):
1255 """Reject not at the start of line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001256 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1257 self.assertMessageRejected('\n BUG=None\n', project)
1258 self.assertMessageRejected('\n\tBUG=None\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001259
1260 def testOldTrackers(self):
1261 """Reject commit messages using old trackers."""
Mike Frysingera2f28252017-10-27 22:26:14 -04001262 self.assertMessageRejected('\nBUG=chrome-os-partner:1234\n',
1263 self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001264 self.assertMessageRejected('\nBUG=chromium-os:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001265
1266 def testNoTrackers(self):
1267 """Reject commit messages w/invalid trackers."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001268 self.assertMessageRejected('\nBUG=booga:1234\n', self.CROS_PROJECT)
1269 self.assertMessageRejected('\nBUG=br:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001270
1271 def testMissing(self):
1272 """Reject commit messages w/no BUG line."""
1273 self.assertMessageRejected('foo\n')
1274
1275 def testCase(self):
1276 """Reject bug lines that are not BUG."""
1277 self.assertMessageRejected('\nbug=none\n')
1278
Cheng Yuehb707c522020-01-02 14:06:59 +08001279 def testNotAfterTest(self):
1280 """Reject any TEST line before any BUG line."""
1281 test_field = 'TEST=i did not do it\n'
1282 middle_field = 'A random between line\n'
1283 for project, bug_field in ((self.AOSP_PROJECT, 'Bug:none\n'),
1284 (self.CROS_PROJECT, 'BUG=None\n')):
1285 self.assertMessageRejected(
1286 '\n' + test_field + middle_field + bug_field, project)
1287 self.assertMessageRejected(
1288 '\n' + test_field + bug_field, project)
1289
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001290
1291class CheckCommitMessageCqDepend(CommitMessageTestCase):
1292 """Tests for _check_change_has_valid_cq_depend."""
1293
1294 @staticmethod
1295 def CheckMessage(project, commit):
1296 return pre_upload._check_change_has_valid_cq_depend(project, commit)
1297
1298 def testNormal(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001299 """Accept valid Cq-Depends line."""
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001300 self.assertMessageAccepted('\nCq-Depend: chromium:1234\nChange-Id: I123')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001301
1302 def testInvalid(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001303 """Reject invalid Cq-Depends line."""
1304 self.assertMessageRejected('\nCq-Depend=chromium=1234\n')
1305 self.assertMessageRejected('\nCq-Depend: None\n')
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001306 self.assertMessageRejected('\nCq-Depend: chromium:1234\n\nChange-Id: I123')
Mike Frysingere39d0cd2019-11-25 13:30:06 -05001307 self.assertMessageRejected('\nCQ-DEPEND=1234\n')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001308
1309
Bernie Thompsonf8fea992016-01-14 10:27:18 -08001310class CheckCommitMessageContribution(CommitMessageTestCase):
1311 """Tests for _check_change_is_contribution."""
1312
1313 @staticmethod
1314 def CheckMessage(project, commit):
1315 return pre_upload._check_change_is_contribution(project, commit)
1316
1317 def testNormal(self):
1318 """Accept a commit message which is a contribution."""
1319 self.assertMessageAccepted('\nThis is cool code I am contributing.\n')
1320
1321 def testFailureLowerCase(self):
1322 """Deny a commit message which is not a contribution."""
1323 self.assertMessageRejected('\nThis is not a contribution.\n')
1324
1325 def testFailureUpperCase(self):
1326 """Deny a commit message which is not a contribution."""
1327 self.assertMessageRejected('\nNOT A CONTRIBUTION\n')
1328
1329
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001330class CheckCommitMessageTest(CommitMessageTestCase):
1331 """Tests for _check_change_has_test_field."""
1332
1333 @staticmethod
1334 def CheckMessage(project, commit):
1335 return pre_upload._check_change_has_test_field(project, commit)
1336
1337 def testNormal(self):
1338 """Accept a commit message w/a valid TEST."""
1339 self.assertMessageAccepted('\nTEST=i did it\n')
1340
1341 def testNone(self):
1342 """Accept TEST=None."""
1343 self.assertMessageAccepted('\nTEST=None\n')
1344 self.assertMessageAccepted('\nTEST=none\n')
1345
1346 def testBlank(self):
1347 """Reject blank values."""
1348 self.assertMessageRejected('\nTEST=\n')
1349 self.assertMessageRejected('\nTEST= \n')
1350
1351 def testNotFirstLine(self):
1352 """Reject the first line."""
1353 self.assertMessageRejected('TEST=None\n\n\n')
1354
1355 def testNotInline(self):
1356 """Reject not at the start of line."""
1357 self.assertMessageRejected('\n TEST=None\n')
1358 self.assertMessageRejected('\n\tTEST=None\n')
1359
1360 def testMissing(self):
1361 """Reject commit messages w/no TEST line."""
1362 self.assertMessageRejected('foo\n')
1363
1364 def testCase(self):
1365 """Reject bug lines that are not TEST."""
1366 self.assertMessageRejected('\ntest=none\n')
1367
1368
1369class CheckCommitMessageChangeId(CommitMessageTestCase):
1370 """Tests for _check_change_has_proper_changeid."""
1371
1372 @staticmethod
1373 def CheckMessage(project, commit):
1374 return pre_upload._check_change_has_proper_changeid(project, commit)
1375
1376 def testNormal(self):
1377 """Accept a commit message w/a valid Change-Id."""
1378 self.assertMessageAccepted('foo\n\nChange-Id: I1234\n')
1379
1380 def testBlank(self):
1381 """Reject blank values."""
1382 self.assertMessageRejected('\nChange-Id:\n')
1383 self.assertMessageRejected('\nChange-Id: \n')
1384
1385 def testNotFirstLine(self):
1386 """Reject the first line."""
1387 self.assertMessageRejected('TEST=None\n\n\n')
1388
1389 def testNotInline(self):
1390 """Reject not at the start of line."""
1391 self.assertMessageRejected('\n Change-Id: I1234\n')
1392 self.assertMessageRejected('\n\tChange-Id: I1234\n')
1393
1394 def testMissing(self):
1395 """Reject commit messages missing the line."""
1396 self.assertMessageRejected('foo\n')
1397
1398 def testCase(self):
1399 """Reject bug lines that are not Change-Id."""
1400 self.assertMessageRejected('\nchange-id: I1234\n')
1401 self.assertMessageRejected('\nChange-id: I1234\n')
1402 self.assertMessageRejected('\nChange-ID: I1234\n')
1403
1404 def testEnd(self):
1405 """Reject Change-Id's that are not last."""
1406 self.assertMessageRejected('\nChange-Id: I1234\nbar\n')
1407
Mike Frysinger02b88bd2014-11-21 00:29:38 -05001408 def testSobTag(self):
1409 """Permit s-o-b tags to follow the Change-Id."""
1410 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nSigned-off-by: Hi\n')
1411
LaMont Jones237f3ef2020-01-22 10:40:52 -07001412 def testCqClTag(self):
1413 """Permit Cq-Cl-Tag tags to follow the Change-Id."""
1414 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nCq-Cl-Tag: Hi\n')
1415
LaMont Jonesfb5e8bf2020-03-03 12:50:06 -07001416 def testCqIncludeTrybotsTag(self):
1417 """Permit Cq-Include-Trybots tags to follow the Change-Id."""
1418 self.assertMessageAccepted(
1419 'foo\n\nChange-Id: I1234\nCq-Include-Trybots: chromeos/cq:foo\n')
1420
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001421
Jack Neus8edbf642019-07-10 16:08:31 -06001422class CheckCommitMessageNoOEM(CommitMessageTestCase):
1423 """Tests for _check_change_no_include_oem."""
1424
1425 @staticmethod
1426 def CheckMessage(project, commit):
1427 return pre_upload._check_change_no_include_oem(project, commit)
1428
1429 def testNormal(self):
1430 """Accept a commit message w/o reference to an OEM/ODM."""
1431 self.assertMessageAccepted('foo\n')
1432
1433 def testHasOEM(self):
1434 """Catch commit messages referencing OEMs."""
1435 self.assertMessageRejected('HP Project\n\n')
1436 self.assertMessageRejected('hewlett-packard\n')
1437 self.assertMessageRejected('Hewlett\nPackard\n')
1438 self.assertMessageRejected('Dell Chromebook\n\n\n')
1439 self.assertMessageRejected('product@acer.com\n')
1440 self.assertMessageRejected('This is related to Asus\n')
1441 self.assertMessageRejected('lenovo machine\n')
1442
1443 def testHasODM(self):
1444 """Catch commit messages referencing ODMs."""
1445 self.assertMessageRejected('new samsung laptop\n\n')
1446 self.assertMessageRejected('pegatron(ems) project\n')
1447 self.assertMessageRejected('new Wistron device\n')
1448
1449 def testContainsOEM(self):
1450 """Check that the check handles word boundaries properly."""
1451 self.assertMessageAccepted('oheahpohea')
1452 self.assertMessageAccepted('Play an Asus7 guitar chord.\n\n')
1453
1454 def testTag(self):
1455 """Check that the check ignores tags."""
1456 self.assertMessageAccepted(
1457 'Harmless project\n'
1458 'Reviewed-by: partner@asus.corp-partner.google.com\n'
1459 'Tested-by: partner@hp.corp-partner.google.com\n'
1460 'Signed-off-by: partner@dell.corp-partner.google.com\n'
1461 'Commit-Queue: partner@lenovo.corp-partner.google.com\n'
Jack Neus8edbf642019-07-10 16:08:31 -06001462 'CC: partner@acer.corp-partner.google.com\n'
1463 'Acked-by: partner@hewlett-packard.corp-partner.google.com\n')
1464 self.assertMessageRejected(
1465 'Asus project\n'
1466 'Reviewed-by: partner@asus.corp-partner.google.com')
Mike Frysingerbb34a222019-07-31 14:40:46 -04001467 self.assertMessageRejected(
1468 'my project\n'
1469 'Bad-tag: partner@asus.corp-partner.google.com')
Jack Neus8edbf642019-07-10 16:08:31 -06001470
1471
Mike Frysinger36b2ebc2014-10-31 14:02:03 -04001472class CheckCommitMessageStyle(CommitMessageTestCase):
1473 """Tests for _check_commit_message_style."""
1474
1475 @staticmethod
1476 def CheckMessage(project, commit):
1477 return pre_upload._check_commit_message_style(project, commit)
1478
1479 def testNormal(self):
1480 """Accept valid commit messages."""
1481 self.assertMessageAccepted('one sentence.\n')
1482 self.assertMessageAccepted('some.module: do it!\n')
1483 self.assertMessageAccepted('one line\n\nmore stuff here.')
1484
1485 def testNoBlankSecondLine(self):
1486 """Reject messages that have stuff on the second line."""
1487 self.assertMessageRejected('one sentence.\nbad fish!\n')
1488
1489 def testFirstLineMultipleSentences(self):
1490 """Reject messages that have more than one sentence in the summary."""
1491 self.assertMessageRejected('one sentence. two sentence!\n')
1492
1493 def testFirstLineTooLone(self):
1494 """Reject first lines that are too long."""
1495 self.assertMessageRejected('o' * 200)
1496
1497
Mike Frysinger292b45d2014-11-25 01:17:10 -05001498def DiffEntry(src_file=None, dst_file=None, src_mode=None, dst_mode='100644',
1499 status='M'):
1500 """Helper to create a stub RawDiffEntry object"""
1501 if src_mode is None:
1502 if status == 'A':
1503 src_mode = '000000'
1504 elif status == 'M':
1505 src_mode = dst_mode
1506 elif status == 'D':
1507 src_mode = dst_mode
1508 dst_mode = '000000'
1509
1510 src_sha = dst_sha = 'abc'
1511 if status == 'D':
1512 dst_sha = '000000'
1513 elif status == 'A':
1514 src_sha = '000000'
1515
1516 return git.RawDiffEntry(src_mode=src_mode, dst_mode=dst_mode, src_sha=src_sha,
1517 dst_sha=dst_sha, status=status, score=None,
1518 src_file=src_file, dst_file=dst_file)
1519
1520
Mike Frysingerb2496652019-09-12 23:35:46 -04001521class HelpersTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001522 """Various tests for utility functions."""
1523
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001524 def setUp(self):
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001525 os.chdir(self.tempdir)
1526
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001527 self.PatchObject(git, 'RawDiff', return_value=[
1528 # A modified normal file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001529 DiffEntry(src_file='buildbot/constants.py', status='M'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001530 # A new symlink file.
Bob Haarman0dc1f942020-10-03 00:06:59 +00001531 DiffEntry(dst_file='scripts/cros_env_allowlist', dst_mode='120000',
Mike Frysinger292b45d2014-11-25 01:17:10 -05001532 status='A'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001533 # A deleted file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001534 DiffEntry(src_file='scripts/sync_sonic.py', status='D'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001535 ])
1536
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001537 def _WritePresubmitIgnoreFile(self, subdir, data):
1538 """Writes to a .presubmitignore file in the passed-in subdirectory."""
1539 directory = os.path.join(self.tempdir, subdir)
1540 if not os.path.exists(directory):
1541 os.makedirs(directory)
1542 osutils.WriteFile(os.path.join(directory, pre_upload._IGNORE_FILE), data)
1543
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001544 def testGetAffectedFilesNoDeletesNoRelative(self):
1545 """Verify _get_affected_files() works w/no delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001546 path = os.getcwd()
1547 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1548 relative=False)
1549 exp_files = [os.path.join(path, 'buildbot/constants.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001550 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001551
1552 def testGetAffectedFilesDeletesNoRelative(self):
1553 """Verify _get_affected_files() works w/delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001554 path = os.getcwd()
1555 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1556 relative=False)
1557 exp_files = [os.path.join(path, 'buildbot/constants.py'),
1558 os.path.join(path, 'scripts/sync_sonic.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001559 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001560
1561 def testGetAffectedFilesNoDeletesRelative(self):
1562 """Verify _get_affected_files() works w/no delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001563 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1564 relative=True)
1565 exp_files = ['buildbot/constants.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001566 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001567
1568 def testGetAffectedFilesDeletesRelative(self):
1569 """Verify _get_affected_files() works w/delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001570 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1571 relative=True)
1572 exp_files = ['buildbot/constants.py', 'scripts/sync_sonic.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001573 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001574
Mike Frysinger292b45d2014-11-25 01:17:10 -05001575 def testGetAffectedFilesDetails(self):
1576 """Verify _get_affected_files() works w/full_details."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001577 files = pre_upload._get_affected_files('HEAD', full_details=True,
1578 relative=True)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001579 self.assertEqual(files[0].src_file, 'buildbot/constants.py')
Mike Frysinger292b45d2014-11-25 01:17:10 -05001580
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001581 def testGetAffectedFilesPresubmitIgnoreDirectory(self):
1582 """Verify .presubmitignore can be used to exclude a directory."""
1583 self._WritePresubmitIgnoreFile('.', 'buildbot/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001584 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001585
1586 def testGetAffectedFilesPresubmitIgnoreDirectoryWildcard(self):
1587 """Verify .presubmitignore can be used with a directory wildcard."""
1588 self._WritePresubmitIgnoreFile('.', '*/constants.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001589 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001590
1591 def testGetAffectedFilesPresubmitIgnoreWithinDirectory(self):
1592 """Verify .presubmitignore can be placed in a subdirectory."""
1593 self._WritePresubmitIgnoreFile('buildbot', '*.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001594 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001595
1596 def testGetAffectedFilesPresubmitIgnoreDoesntMatch(self):
1597 """Verify .presubmitignore has no effect when it doesn't match a file."""
1598 self._WritePresubmitIgnoreFile('buildbot', '*.txt')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001599 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True),
1600 ['buildbot/constants.py'])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001601
1602 def testGetAffectedFilesPresubmitIgnoreAddedFile(self):
1603 """Verify .presubmitignore matches added files."""
1604 self._WritePresubmitIgnoreFile('.', 'buildbot/\nscripts/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001605 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True,
1606 include_symlinks=True),
1607 [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001608
1609 def testGetAffectedFilesPresubmitIgnoreSkipIgnoreFile(self):
1610 """Verify .presubmitignore files are automatically skipped."""
1611 self.PatchObject(git, 'RawDiff', return_value=[
1612 DiffEntry(src_file='.presubmitignore', status='M')
1613 ])
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001614 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Mike Frysinger292b45d2014-11-25 01:17:10 -05001615
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001616
Mike Frysingerb2496652019-09-12 23:35:46 -04001617class ExecFilesTest(PreUploadTestCase):
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001618 """Tests for _check_exec_files."""
1619
1620 def setUp(self):
1621 self.diff_mock = self.PatchObject(git, 'RawDiff')
1622
1623 def testNotExec(self):
1624 """Do not flag files that are not executable."""
1625 self.diff_mock.return_value = [
1626 DiffEntry(src_file='make.conf', dst_mode='100644', status='A'),
1627 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001628 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001629
1630 def testExec(self):
1631 """Flag files that are executable."""
1632 self.diff_mock.return_value = [
1633 DiffEntry(src_file='make.conf', dst_mode='100755', status='A'),
1634 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001635 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001636
1637 def testDeletedExec(self):
1638 """Ignore bad files that are being deleted."""
1639 self.diff_mock.return_value = [
1640 DiffEntry(src_file='make.conf', dst_mode='100755', status='D'),
1641 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001642 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001643
1644 def testModifiedExec(self):
1645 """Flag bad files that weren't exec, but now are."""
1646 self.diff_mock.return_value = [
1647 DiffEntry(src_file='make.conf', src_mode='100644', dst_mode='100755',
1648 status='M'),
1649 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001650 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001651
1652 def testNormalExec(self):
1653 """Don't flag normal files (e.g. scripts) that are executable."""
1654 self.diff_mock.return_value = [
1655 DiffEntry(src_file='foo.sh', dst_mode='100755', status='A'),
1656 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001657 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001658
1659
Mike Frysingerb2496652019-09-12 23:35:46 -04001660class CheckForUprev(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger292b45d2014-11-25 01:17:10 -05001661 """Tests for _check_for_uprev."""
1662
1663 def setUp(self):
1664 self.file_mock = self.PatchObject(git, 'RawDiff')
1665
1666 def _Files(self, files):
1667 """Create |files| in the tempdir and return full paths to them."""
1668 for obj in files:
1669 if obj.status == 'D':
1670 continue
1671 if obj.dst_file is None:
1672 f = obj.src_file
1673 else:
1674 f = obj.dst_file
1675 osutils.Touch(os.path.join(self.tempdir, f), makedirs=True)
1676 return files
1677
1678 def assertAccepted(self, files, project='project', commit='fake sha1'):
1679 """Assert _check_for_uprev accepts |files|."""
1680 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001681 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1682 project_top=self.tempdir)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001683 self.assertIsNone(ret)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001684
1685 def assertRejected(self, files, project='project', commit='fake sha1'):
1686 """Assert _check_for_uprev rejects |files|."""
1687 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001688 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1689 project_top=self.tempdir)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001690 self.assertTrue(isinstance(ret, errors.HookFailure))
1691
Bob Haarman0dc1f942020-10-03 00:06:59 +00001692 def testAllowlistOverlay(self):
1693 """Skip checks on allowed overlays."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001694 self.assertAccepted([DiffEntry(src_file='cat/pkg/pkg-0.ebuild')],
1695 project='chromiumos/overlays/portage-stable')
1696
Bob Haarman0dc1f942020-10-03 00:06:59 +00001697 def testAllowlistFiles(self):
1698 """Skip checks on allowed files."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001699 files = ['ChangeLog', 'Manifest', 'metadata.xml']
1700 self.assertAccepted([DiffEntry(src_file=os.path.join('c', 'p', x),
1701 status='M')
1702 for x in files])
1703
1704 def testRejectBasic(self):
1705 """Reject ebuilds missing uprevs."""
1706 self.assertRejected([DiffEntry(src_file='c/p/p-0.ebuild', status='M')])
1707
1708 def testNewPackage(self):
1709 """Accept new ebuilds w/out uprevs."""
1710 self.assertAccepted([DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1711 self.assertAccepted([DiffEntry(src_file='c/p/p-0-r12.ebuild', status='A')])
1712
1713 def testModifiedFilesOnly(self):
1714 """Reject ebuilds w/out uprevs and changes in files/."""
1715 osutils.Touch(os.path.join(self.tempdir, 'cat/pkg/pkg-0.ebuild'),
1716 makedirs=True)
1717 self.assertRejected([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1718 self.assertRejected([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1719
1720 def testFilesNoEbuilds(self):
1721 """Ignore changes to paths w/out ebuilds."""
1722 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1723 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1724
1725 def testModifiedFilesWithUprev(self):
1726 """Accept ebuilds w/uprevs and changes in files/."""
1727 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='A'),
1728 DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1729 self.assertAccepted([
1730 DiffEntry(src_file='c/p/files/f', status='M'),
1731 DiffEntry(src_file='c/p/p-0-r1.ebuild', src_mode='120000',
1732 dst_file='c/p/p-0-r2.ebuild', dst_mode='120000', status='R')])
1733
Gwendal Grignoua3086c32014-12-09 11:17:22 -08001734 def testModifiedFilesWith9999(self):
1735 """Accept 9999 ebuilds and changes in files/."""
1736 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M'),
1737 DiffEntry(src_file='c/p/p-9999.ebuild', status='M')])
1738
C Shapiroae157ae2017-09-18 16:24:03 -06001739 def testModifiedFilesIn9999SubDirWithout9999Change(self):
1740 """Accept changes in files/ with a parent 9999 ebuild"""
1741 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
1742 os.makedirs(os.path.dirname(ebuild_9999_file))
1743 osutils.WriteFile(ebuild_9999_file, 'fake')
1744 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M')])
1745
Stephen Boyd6bf5ea82020-10-15 00:02:07 -07001746 def testModifiedFilesAndProfilesWith9999(self):
1747 """Accept changes in files/ with a parent 9999 ebuild and profile change"""
1748 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
1749 os.makedirs(os.path.dirname(ebuild_9999_file))
1750 osutils.WriteFile(ebuild_9999_file, 'fake')
1751 self.assertAccepted([
1752 DiffEntry(src_file='c/p/files/f', status='M'),
1753 DiffEntry(src_file='c/profiles/base/make.defaults', status='M')])
1754
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001755
Mike Frysingerb2496652019-09-12 23:35:46 -04001756class DirectMainTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger55f85b52014-12-18 14:45:21 -05001757 """Tests for direct_main()"""
1758
1759 def setUp(self):
1760 self.hooks_mock = self.PatchObject(pre_upload, '_run_project_hooks',
1761 return_value=None)
1762
1763 def testNoArgs(self):
1764 """If run w/no args, should check the current dir."""
1765 ret = pre_upload.direct_main([])
1766 self.assertEqual(ret, 0)
1767 self.hooks_mock.assert_called_once_with(
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001768 mock.ANY, proj_dir=os.getcwd(), commit_list=[], presubmit=mock.ANY,
1769 config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001770
1771 def testExplicitDir(self):
1772 """Verify we can run on a diff dir."""
1773 # Use the chromite dir since we know it exists.
1774 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR])
1775 self.assertEqual(ret, 0)
1776 self.hooks_mock.assert_called_once_with(
1777 mock.ANY, proj_dir=constants.CHROMITE_DIR, commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001778 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001779
1780 def testBogusProject(self):
1781 """A bogus project name should be fine (use default settings)."""
1782 # Use the chromite dir since we know it exists.
1783 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR,
1784 '--project', 'foooooooooo'])
1785 self.assertEqual(ret, 0)
1786 self.hooks_mock.assert_called_once_with(
1787 'foooooooooo', proj_dir=constants.CHROMITE_DIR, commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001788 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001789
1790 def testBogustProjectNoDir(self):
1791 """Make sure --dir is detected even with --project."""
1792 ret = pre_upload.direct_main(['--project', 'foooooooooo'])
1793 self.assertEqual(ret, 0)
1794 self.hooks_mock.assert_called_once_with(
Mike Frysinger9ba001a2020-11-20 01:02:11 -05001795 'foooooooooo', proj_dir=os.path.dirname(os.path.abspath(__file__)),
1796 commit_list=[], presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001797
1798 def testNoGitDir(self):
1799 """We should die when run on a non-git dir."""
1800 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
1801 ['--dir', self.tempdir])
1802
1803 def testNoDir(self):
1804 """We should die when run on a missing dir."""
1805 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
1806 ['--dir', os.path.join(self.tempdir, 'foooooooo')])
1807
1808 def testCommitList(self):
1809 """Any args on the command line should be treated as commits."""
1810 commits = ['sha1', 'sha2', 'shaaaaaaaaaaaan']
1811 ret = pre_upload.direct_main(commits)
1812 self.assertEqual(ret, 0)
1813 self.hooks_mock.assert_called_once_with(
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001814 mock.ANY, proj_dir=mock.ANY, commit_list=commits, presubmit=mock.ANY,
1815 config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001816
1817
Mike Frysingerb2496652019-09-12 23:35:46 -04001818class CheckRustfmtTest(PreUploadTestCase):
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001819 """Tests for _check_rustfmt."""
1820
1821 def setUp(self):
1822 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1823
1824 def testBadRustFile(self):
1825 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
1826 # Bad because it's missing trailing newline.
Mike Frysingere85b0062019-08-20 15:10:33 -04001827 content = 'fn main() {}'
1828 self.content_mock.return_value = content
1829 self.PatchObject(pre_upload, '_run_command', return_value=content + '\n')
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001830 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1831 self.assertIsNotNone(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001832 self.assertEqual('Files not formatted with rustfmt: '
1833 "(run 'cargo fmt' to fix)",
1834 failure.msg)
1835 self.assertEqual(['a.rs'], failure.items)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001836
1837 def testGoodRustFile(self):
1838 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
Mike Frysingere85b0062019-08-20 15:10:33 -04001839 content = 'fn main() {}\n'
1840 self.content_mock.return_value = content
1841 self.PatchObject(pre_upload, '_run_command', return_value=content)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001842 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1843 self.assertIsNone(failure)
1844
1845 def testFilterNonRustFiles(self):
1846 self.PatchObject(pre_upload, '_get_affected_files',
1847 return_value=['a.cc', 'a.rsa', 'a.irs', 'rs.cc'])
1848 self.content_mock.return_value = 'fn main() {\n}'
1849 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1850 self.assertIsNone(failure)
1851
1852
Keiichi Watanabe6ab73fd2020-07-14 23:42:02 +09001853class GetCargoClippyParserTest(cros_test_lib.TestCase):
1854 """Tests for _get_cargo_clippy_parser."""
1855
1856 def testSingleProject(self):
1857 parser = pre_upload._get_cargo_clippy_parser()
1858 args = parser.parse_args(['--project', 'foo'])
1859 self.assertEqual(args.project,
1860 [pre_upload.ClippyProject(root='foo', script=None)])
1861
1862 def testMultipleProjects(self):
1863 parser = pre_upload._get_cargo_clippy_parser()
1864 args = parser.parse_args(['--project', 'foo:bar',
1865 '--project', 'baz'])
1866 self.assertCountEqual(args.project,
1867 [pre_upload.ClippyProject(root='foo', script='bar'),
1868 pre_upload.ClippyProject(root='baz', script=None)])
1869
1870
1871class CheckCargoClippyTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
1872 """Tests for _check_cargo_clippy."""
1873
1874 def setUp(self):
1875 self.project = pre_upload.Project(name='PROJECT', dir=self.tempdir,
1876 remote=None)
1877
1878 def testClippy(self):
1879 """Verify clippy is called when a monitored file was changed."""
1880 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
1881
1882 self.PatchObject(pre_upload, '_get_affected_files',
1883 return_value=[f'{self.project.dir}/repo_a/a.rs'])
1884
1885 ret = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
1886 options=['--project=repo_a',
1887 '--project=repo_b:foo'])
1888 self.assertFalse(ret)
1889
1890 # Check if `cargo clippy` ran.
1891 called = False
1892 for args, _ in rc_mock.call_args_list:
1893 cmd = args[0]
1894 if len(cmd) > 1 and cmd[0] == 'cargo' and cmd[1] == 'clippy':
1895 called = True
1896 break
1897
1898 self.assertTrue(called)
1899
1900 def testDontRun(self):
1901 """Skip checks when no monitored files are modified."""
1902 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
1903
1904 # A file under `repo_a` was monitored.
1905 self.PatchObject(pre_upload, '_get_affected_files',
1906 return_value=[f'{self.project.dir}/repo_a/a.rs'])
1907 # But, we only care about files under `repo_b`.
1908 errs = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
1909 options=['--project=repo_b:foo'])
1910
1911 self.assertFalse(errs)
1912
1913 rc_mock.assert_not_called()
1914
1915 def testCustomScript(self):
1916 """Verify project-specific script is used."""
1917 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
1918
1919 self.PatchObject(pre_upload, '_get_affected_files',
1920 return_value=[f'{self.project.dir}/repo_b/b.rs'])
1921
1922 errs = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
1923 options=['--project=repo_a',
1924 '--project=repo_b:foo'])
1925 self.assertFalse(errs)
1926
1927 # Check if the script `foo` ran.
1928 called = False
1929 for args, _ in rc_mock.call_args_list:
1930 cmd = args[0]
1931 if len(cmd) > 0 and cmd[0] == os.path.join(self.project.dir, 'foo'):
1932 called = True
1933 break
1934
1935 self.assertTrue(called)
1936
1937
Mike Frysinger180ecd62020-08-19 00:41:51 -04001938class OverrideHooksProcessing(PreUploadTestCase):
1939 """Verify _get_override_hooks processing."""
1940
1941 @staticmethod
1942 def parse(data):
1943 """Helper to create a config & parse it."""
1944 cfg = configparser.ConfigParser()
1945 cfg.read_string(data)
1946 return pre_upload._get_override_hooks(cfg)
1947
1948 def testHooks(self):
1949 """Verify we reject unknown hook names (e.g. typos)."""
1950 with self.assertRaises(ValueError) as e:
1951 self.parse("""
1952[Hook Overrides]
1953foobar: true
1954""")
1955 self.assertIn('foobar', str(e.exception))
1956
1957 def testImplicitDisable(self):
1958 """Verify non-common hooks aren't enabled by default."""
1959 enabled, _ = self.parse('')
1960 self.assertNotIn(pre_upload._run_checkpatch, enabled)
1961
1962 def testExplicitDisable(self):
1963 """Verify hooks disabled are disabled."""
1964 _, disabled = self.parse("""
1965[Hook Overrides]
1966tab_check: false
1967""")
1968 self.assertIn(pre_upload._check_no_tabs, disabled)
1969
1970 def testExplicitEnable(self):
1971 """Verify hooks enabled are enabled."""
1972 enabled, _ = self.parse("""
1973[Hook Overrides]
1974tab_check: true
1975""")
1976 self.assertIn(pre_upload._check_no_tabs, enabled)
1977
1978 def testOptions(self):
1979 """Verify hook options are loaded."""
1980 enabled, _ = self.parse("""
1981[Hook Overrides Options]
1982keyword_check: --kw
1983""")
1984 for func in enabled:
1985 if func.__name__ == 'keyword_check':
1986 self.assertIn('options', func.keywords)
1987 self.assertEqual(func.keywords['options'], ['--kw'])
1988 break
1989 else:
1990 self.fail('could not find "keyword_check" enabled hook')
1991
1992 def testSignOffField(self):
1993 """Verify signoff field handling."""
1994 # Enforce no s-o-b by default.
1995 enabled, disabled = self.parse('')
1996 self.assertIn(pre_upload._check_change_has_no_signoff_field, enabled)
1997 self.assertNotIn(pre_upload._check_change_has_signoff_field, enabled)
1998 self.assertNotIn(pre_upload._check_change_has_signoff_field, disabled)
1999
2000 # If disabled, don't enforce either policy.
2001 enabled, disabled = self.parse("""
2002[Hook Overrides]
2003signoff_check: false
2004""")
2005 self.assertNotIn(pre_upload._check_change_has_no_signoff_field, enabled)
2006 self.assertNotIn(pre_upload._check_change_has_signoff_field, enabled)
2007 self.assertIn(pre_upload._check_change_has_signoff_field, disabled)
2008
2009 # If enabled, require s-o-b.
2010 enabled, disabled = self.parse("""
2011[Hook Overrides]
2012signoff_check: true
2013""")
2014 self.assertNotIn(pre_upload._check_change_has_no_signoff_field, enabled)
2015 self.assertIn(pre_upload._check_change_has_signoff_field, enabled)
2016 self.assertNotIn(pre_upload._check_change_has_signoff_field, disabled)
2017
2018 def testBranchField(self):
2019 """Verify branch field enabling."""
2020 # Should be disabled by default.
2021 enabled, disabled = self.parse('')
2022 self.assertIn(pre_upload._check_change_has_no_branch_field, enabled)
2023 self.assertNotIn(pre_upload._check_change_has_branch_field, enabled)
2024 self.assertNotIn(pre_upload._check_change_has_branch_field, disabled)
2025
2026 # Should be disabled if requested.
2027 enabled, disabled = self.parse("""
2028[Hook Overrides]
2029branch_check: false
2030""")
2031 self.assertIn(pre_upload._check_change_has_no_branch_field, enabled)
2032 self.assertNotIn(pre_upload._check_change_has_branch_field, enabled)
2033 self.assertIn(pre_upload._check_change_has_branch_field, disabled)
2034
2035 # Should be enabled if requested.
2036 enabled, disabled = self.parse("""
2037[Hook Overrides]
2038branch_check: true
2039""")
2040 self.assertNotIn(pre_upload._check_change_has_no_branch_field, enabled)
2041 self.assertIn(pre_upload._check_change_has_branch_field, enabled)
2042 self.assertNotIn(pre_upload._check_change_has_branch_field, disabled)
2043
2044
Tom Hughes1ed799d2020-09-25 14:37:28 -07002045class ProjectHooksProcessing(PreUploadTestCase, cros_test_lib.TempDirTestCase):
2046 """Verify _get_project_hooks processing."""
2047
2048 def parse(self, data):
2049 """Helper to write config and parse it."""
2050 filename = os.path.join(self.tempdir, 'config')
2051 osutils.WriteFile(filename, data)
2052 return pre_upload._get_project_hooks(project='test', presubmit=True,
2053 config_file=filename)
2054
2055 def testClangFormatCheckDefault(self):
2056 """Verify clang-format check disabled by default."""
2057 hooks = self.parse('')
2058 for func in hooks:
2059 self.assertNotEqual(func.__name__, '_check_clang_format')
2060 self.assertNotEqual(func.__name__, 'clang_format_check')
2061
2062 def testClangFormatCheckDisabled(self):
2063 """Verify clang-format check disabled when requested."""
2064 hooks = self.parse("""
2065[Hook Overrides]
2066clang_format_check: false
2067""")
2068 for func in hooks:
2069 self.assertNotEqual(func.__name__, '_check_clang_format')
2070 self.assertNotEqual(func.__name__, 'clang_format_check')
2071
2072 def testClangFormatCheckEnabled(self):
2073 """Verify clang-format check enabled when requested."""
2074 hooks = self.parse("""
2075[Hook Overrides]
2076clang_format_check: true
2077""")
2078 for func in hooks:
2079 if func.__name__ == '_check_clang_format':
2080 self.assertFalse(hasattr(func, 'keywords'))
2081 break
2082 else:
2083 self.fail('could not find "_check_clang_format" enabled hook')
2084
2085 def testClangFormatCheckEnabledWithOptions(self):
2086 """Verify clang-format check has options when provided."""
2087 hooks = self.parse("""
2088[Hook Overrides]
2089clang_format_check: true
2090
2091[Hook Overrides Options]
2092clang_format_check:
2093 some_dir/
2094""")
2095 for func in hooks:
2096 if func.__name__ == 'clang_format_check':
2097 self.assertIn('options', func.keywords)
2098 self.assertEqual(func.keywords['options'], ['some_dir/'])
2099 break
2100 else:
2101 self.fail('could not find "clang_format_check" enabled hook')
2102
2103
Jon Salz98255932012-08-18 14:48:02 +08002104if __name__ == '__main__':
Mike Frysinger884a8dd2015-05-17 03:43:43 -04002105 cros_test_lib.main(module=__name__)