blob: 769d57808ae5d0a27025385f50620431626dd521 [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
Sergey Frolovc1bd8782021-01-20 19:35:44 -0700804class CheckEbuildLicense(PreUploadTestCase):
805 """Tests for _check_ebuild_licenses."""
806
807 def setUp(self):
808 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
809 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
810
811 def testNoEbuilds(self):
812 """If no ebuilds are found, do not scan."""
813 self.file_mock.return_value = ['a.file', 'ebuild-is-not.foo']
814
815 ret = pre_upload._check_ebuild_licenses(ProjectNamed('overlay'), 'HEAD')
816 self.assertIsNone(ret)
817
818 self.assertEqual(self.content_mock.call_count, 0)
819
820 def testSomeEbuilds(self):
821 """If ebuilds are found, only scan them."""
822 self.file_mock.return_value = ['a.file', 'blah', 'cow',
823 'overlay/category/pkg/pkg.ebuild']
824 self.content_mock.return_value = '# HEADER\nLICENSE="GPL-3"\nblah\n'
825
826 ret = pre_upload._check_ebuild_licenses(ProjectNamed('overlay'), 'HEAD')
827 self.assertIsNone(ret)
828
829 self.assertEqual(self.content_mock.call_count, 1)
830
831 def _CheckContent(self, license_field, ebuild_path, fails):
832 """Test helper for inputs/outputs.
833
834 Args:
835 license_field: Contents of LICENSE variable in the tested ebuild.
836 ebuild_path: The path to the tested ebuild.
837 fails: Whether inputs should trigger a hook failure.
838 """
839 self.file_mock.return_value = [ebuild_path]
840 self.content_mock.return_value = f'# blah\nLICENSE="{license_field}"\nbla\n'
841
842 ret = pre_upload._check_ebuild_licenses(ProjectNamed('overlay'), 'HEAD')
843 if fails:
844 self.assertIsInstance(ret, errors.HookFailure)
845 else:
846 self.assertIsNone(ret)
847
848 self.assertEqual(self.content_mock.call_count, 1)
849
850 def testEmpty(self):
851 """Check empty license is not accepted."""
852 self._CheckContent('', 'overlay/category/pkg/pkg.ebuild', True)
853
854 def testValid(self):
855 """Check valid license is accepted."""
856 self._CheckContent('GPL-3', 'overlay/category/pkg/pkg.ebuild', False)
857
858 def testVirtualNotMetapackage(self):
859 """Check virtual package not using metapackage is not accepted."""
860 self._CheckContent('GPL-3', 'overlay/virtual/pkg/pkg.ebuild', True)
861
862 def testVirtualMetapackage(self):
863 """Check virtual package using metapackage is accepted."""
864 self._CheckContent('metapackage', 'overlay/virtual/pkg/pkg.ebuild', False)
865
866
Mike Frysingerb04778f2020-11-30 02:41:14 -0500867class CheckEbuildOwners(PreUploadTestCase):
868 """Tests for _check_ebuild_owners."""
869
870 def setUp(self):
871 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
872 self.content_mock = self.PatchObject(
873 pre_upload, '_get_file_content', return_value=None)
874
875 def testNoMatches(self):
876 """Handle no matching files."""
877 self.file_mock.return_value = []
878 ret = pre_upload._check_ebuild_owners('project', 'HEAD')
879 self.assertIsNone(ret)
880
881 def testNoEbuilds(self):
882 """Handle CLs w/no ebuilds."""
883 self.file_mock.return_value = [
884 DiffEntry(src_file='profiles/package.mask', status='M'),
885 DiffEntry(src_file='dev-util/pkg/README.md', status='A'),
886 DiffEntry(src_file='scripts/sync_sonic.py', status='D'),
887 ]
888 ret = pre_upload._check_ebuild_owners('project', 'HEAD')
889 self.assertIsNone(ret)
890
891 def testMissingOwnersFailure(self):
892 """Test cases that should flag missing OWNERS."""
893 TESTS = (
894 [
895 DiffEntry(src_file='dev-util/pkg/foo.ebuild', status='A'),
896 DiffEntry(src_file='dev-util/pkg/README.md', status='A'),
897 DiffEntry(src_file='dev-util/pkg/Manifest', status='A'),
898 ],
899 )
900 for test in TESTS:
901 self.file_mock.return_value = test
902 ret = pre_upload._check_ebuild_owners('project', 'HEAD')
903 self.assertIsNotNone(ret)
904
905 def testMissingOwnersIgnore(self):
906 """Test cases that should ignore missing OWNERS."""
907 TESTS = (
908 [
909 DiffEntry(src_file='dev-util/pkg/foo-9999.ebuild', status='M'),
910 ],
911 [
912 DiffEntry(src_file='dev-util/pkg/foo-0.ebuild', status='M'),
913 DiffEntry(src_file='dev-util/pkg/foo-0-r1.ebuild', status='A'),
914 ],
915 [
916 DiffEntry(src_file='dev-util/pkg/foo-0-r1.ebuild', status='D'),
917 DiffEntry(src_file='dev-util/pkg/foo-0-r2.ebuild', status='A'),
918 ],
919 )
920 for test in TESTS:
921 self.file_mock.return_value = test
922 ret = pre_upload._check_ebuild_owners('project', 'HEAD')
923 self.assertIsNone(ret)
924
925 def testOwnersExist(self):
926 """Test cases where OWNERS exist."""
927 TESTS = (
928 [
929 DiffEntry(src_file='dev-util/pkg/foo-9999.ebuild', status='A'),
930 ],
931 )
932 self.content_mock.return_value = 'foo'
933 for test in TESTS:
934 self.file_mock.return_value = test
935 ret = pre_upload._check_ebuild_owners('project', 'HEAD')
936 self.assertIsNone(ret)
937 # This should be the # of package dirs across all tests. This makes sure
938 # we actually called the owners check logic and didn't return early.
939 self.assertEqual(1, self.content_mock.call_count)
940
941
Mike Frysinger6ee76b82020-11-20 01:16:06 -0500942class CheckEbuildR0(PreUploadTestCase):
943 """Tests for _check_ebuild_r0."""
944
945 def setUp(self):
946 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
947
948 def testNoMatches(self):
949 """Handle no matching files."""
950 self.file_mock.return_value = []
951 ret = pre_upload._check_ebuild_r0('project', 'HEAD')
952 self.assertIsNone(ret)
953
954 def testBadEbuilds(self):
955 """Handle matching r0 files."""
956 self.file_mock.return_value = ['foo-1-r0.ebuild']
957 ret = pre_upload._check_ebuild_r0('project', 'HEAD')
958 self.assertIsNotNone(ret)
959
960
Mike Frysingerb2496652019-09-12 23:35:46 -0400961class CheckEbuildVirtualPv(PreUploadTestCase):
Mike Frysingercd363c82014-02-01 05:20:18 -0500962 """Tests for _check_ebuild_virtual_pv."""
963
Alex Deymo643ac4c2015-09-03 10:40:50 -0700964 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
965 CHROMIUMOS_OVERLAY = ProjectNamed('chromiumos/overlays/chromiumos')
966 BOARD_OVERLAY = ProjectNamed('chromiumos/overlays/board-overlays')
967 PRIVATE_OVERLAY = ProjectNamed('chromeos/overlays/overlay-link-private')
968 PRIVATE_VARIANT_OVERLAY = ProjectNamed('chromeos/overlays/'
969 'overlay-variant-daisy-spring-private')
Mike Frysingercd363c82014-02-01 05:20:18 -0500970
971 def setUp(self):
972 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
973
974 def testNoVirtuals(self):
975 """Skip non virtual packages."""
976 self.file_mock.return_value = ['some/package/package-3.ebuild']
Alex Deymo643ac4c2015-09-03 10:40:50 -0700977 ret = pre_upload._check_ebuild_virtual_pv(ProjectNamed('overlay'), 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400978 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500979
980 def testCommonVirtuals(self):
981 """Non-board overlays should use PV=1."""
982 template = 'virtual/foo/foo-%s.ebuild'
983 self.file_mock.return_value = [template % '1']
984 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400985 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500986
987 self.file_mock.return_value = [template % '2']
988 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500989 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500990
991 def testPublicBoardVirtuals(self):
992 """Public board overlays should use PV=2."""
993 template = 'overlay-lumpy/virtual/foo/foo-%s.ebuild'
994 self.file_mock.return_value = [template % '2']
995 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400996 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500997
998 self.file_mock.return_value = [template % '2.5']
999 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -05001000 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -05001001
1002 def testPublicBoardVariantVirtuals(self):
1003 """Public board variant overlays should use PV=2.5."""
1004 template = 'overlay-variant-lumpy-foo/virtual/foo/foo-%s.ebuild'
1005 self.file_mock.return_value = [template % '2.5']
1006 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001007 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001008
1009 self.file_mock.return_value = [template % '3']
1010 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -05001011 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -05001012
1013 def testPrivateBoardVirtuals(self):
1014 """Private board overlays should use PV=3."""
1015 template = 'virtual/foo/foo-%s.ebuild'
1016 self.file_mock.return_value = [template % '3']
1017 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001018 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001019
1020 self.file_mock.return_value = [template % '3.5']
1021 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -05001022 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -05001023
1024 def testPrivateBoardVariantVirtuals(self):
1025 """Private board variant overlays should use PV=3.5."""
1026 template = 'virtual/foo/foo-%s.ebuild'
1027 self.file_mock.return_value = [template % '3.5']
1028 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001029 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001030
Bernie Thompsone5ee1822016-01-12 14:22:23 -08001031 def testSpecialVirtuals(self):
1032 """Some cases require deeper versioning and can be >= 4."""
1033 template = 'virtual/foo/foo-%s.ebuild'
Mike Frysingercd363c82014-02-01 05:20:18 -05001034 self.file_mock.return_value = [template % '4']
1035 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001036 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001037
Bernie Thompsone5ee1822016-01-12 14:22:23 -08001038 self.file_mock.return_value = [template % '4.5']
1039 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001040 self.assertIsNone(ret)
Mike Frysinger98638102014-08-28 00:15:08 -04001041
Mike Frysingerb2496652019-09-12 23:35:46 -04001042class CheckCrosLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -07001043 """Tests for _check_cros_license."""
Mike Frysinger98638102014-08-28 00:15:08 -04001044
1045 def setUp(self):
1046 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1047 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1048
1049 def testOldHeaders(self):
1050 """Accept old header styles."""
1051 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001052 (u'#!/bin/sh\n'
1053 u'# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.\n'
1054 u'# Use of this source code is governed by a BSD-style license that'
1055 u' can be\n'
1056 u'# found in the LICENSE file.\n'),
1057 (u'// Copyright 2010-2013 The Chromium OS Authors. All rights reserved.'
1058 u'\n// Use of this source code is governed by a BSD-style license that'
1059 u' can be\n'
1060 u'// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -04001061 )
1062 self.file_mock.return_value = ['file']
1063 for header in HEADERS:
1064 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +09001065 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
1066
1067 def testNewFileYear(self):
1068 """Added files should have the current year in license header."""
1069 year = datetime.datetime.now().year
1070 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001071 (u'// Copyright 2016 The Chromium OS Authors. All rights reserved.\n'
1072 u'// Use of this source code is governed by a BSD-style license that'
1073 u' can be\n'
1074 u'// found in the LICENSE file.\n'),
1075 (u'// Copyright %d The Chromium OS Authors. All rights reserved.\n'
1076 u'// Use of this source code is governed by a BSD-style license that'
1077 u' can be\n'
1078 u'// found in the LICENSE file.\n') % year,
Keigo Oka7e880ac2019-07-03 15:03:43 +09001079 )
1080 want_error = (True, False)
1081 def fake_get_affected_files(_, relative, include_adds=True):
1082 _ = relative
1083 if include_adds:
1084 return ['file']
1085 else:
1086 return []
1087
1088 self.file_mock.side_effect = fake_get_affected_files
1089 for i, header in enumerate(HEADERS):
1090 self.content_mock.return_value = header
1091 if want_error[i]:
1092 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
1093 else:
1094 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -04001095
1096 def testRejectC(self):
1097 """Reject the (c) in newer headers."""
1098 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001099 (u'// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.'
1100 u'\n'
1101 u'// Use of this source code is governed by a BSD-style license that'
1102 u' can be\n'
1103 u'// found in the LICENSE file.\n'),
1104 (u'// Copyright (c) 2020 The Chromium OS Authors. All rights reserved.'
1105 u'\n'
1106 u'// Use of this source code is governed by a BSD-style license that'
1107 u' can be\n'
1108 u'// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -04001109 )
1110 self.file_mock.return_value = ['file']
1111 for header in HEADERS:
1112 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +09001113 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001114
Brian Norris68838dd2018-09-26 18:30:24 -07001115 def testNoLeadingSpace(self):
1116 """Allow headers without leading space (e.g., not a source comment)"""
1117 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001118 (u'Copyright 2018 The Chromium OS Authors. All rights reserved.\n'
1119 u'Use of this source code is governed by a BSD-style license that '
1120 u'can be\n'
1121 u'found in the LICENSE file.\n'),
Brian Norris68838dd2018-09-26 18:30:24 -07001122 )
1123 self.file_mock.return_value = ['file']
1124 for header in HEADERS:
1125 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +09001126 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Brian Norris68838dd2018-09-26 18:30:24 -07001127
Keigo Oka9732e382019-06-28 17:44:59 +09001128 def testNoExcludedGolang(self):
1129 """Don't exclude .go files for license checks."""
1130 self.file_mock.return_value = ['foo/main.go']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001131 self.content_mock.return_value = u'package main\nfunc main() {}'
Keigo Oka7e880ac2019-07-03 15:03:43 +09001132 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Keigo Oka9732e382019-06-28 17:44:59 +09001133
Ken Turnerd07564b2018-02-08 17:57:59 +11001134 def testIgnoreExcludedPaths(self):
1135 """Ignores excluded paths for license checks."""
1136 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001137 self.content_mock.return_value = u'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +09001138 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +11001139
Tom Hughes90b7bd42020-11-10 10:31:49 -08001140 def testIgnoreMetadataFiles(self):
1141 """Ignores metadata files for license checks."""
1142 self.file_mock.return_value = ['foo/DIR_METADATA']
1143 self.content_mock.return_value = u'team_email: "team@chromium.org"'
1144 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
1145
Chris McDonald7b63c8e2019-04-25 10:27:27 -06001146 def testIgnoreTopLevelExcludedPaths(self):
1147 """Ignores excluded paths for license checks."""
1148 self.file_mock.return_value = ['OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001149 self.content_mock.return_value = u'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +09001150 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001151
Mike Frysingerb2496652019-09-12 23:35:46 -04001152class CheckAOSPLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -07001153 """Tests for _check_aosp_license."""
1154
1155 def setUp(self):
1156 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1157 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1158
1159 def testHeaders(self):
1160 """Accept old header styles."""
1161 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001162 u"""//
Alex Deymof5792ce2015-08-24 22:50:08 -07001163// Copyright (C) 2011 The Android Open Source Project
1164//
1165// Licensed under the Apache License, Version 2.0 (the "License");
1166// you may not use this file except in compliance with the License.
1167// You may obtain a copy of the License at
1168//
1169// http://www.apache.org/licenses/LICENSE-2.0
1170//
1171// Unless required by applicable law or agreed to in writing, software
1172// distributed under the License is distributed on an "AS IS" BASIS,
1173// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1174// See the License for the specific language governing permissions and
1175// limitations under the License.
1176//
1177""",
Mike Frysinger71e643e2019-09-13 17:26:39 -04001178 u"""#
Alex Deymof5792ce2015-08-24 22:50:08 -07001179# Copyright (c) 2015 The Android Open Source Project
1180#
1181# Licensed under the Apache License, Version 2.0 (the "License");
1182# you may not use this file except in compliance with the License.
1183# You may obtain a copy of the License at
1184#
1185# http://www.apache.org/licenses/LICENSE-2.0
1186#
1187# Unless required by applicable law or agreed to in writing, software
1188# distributed under the License is distributed on an "AS IS" BASIS,
1189# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1190# See the License for the specific language governing permissions and
1191# limitations under the License.
1192#
1193""",
1194 )
1195 self.file_mock.return_value = ['file']
1196 for header in HEADERS:
1197 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001198 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001199
1200 def testRejectNoLinesAround(self):
1201 """Reject headers missing the empty lines before/after the license."""
1202 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001203 u"""# Copyright (c) 2015 The Android Open Source Project
Alex Deymof5792ce2015-08-24 22:50:08 -07001204#
1205# Licensed under the Apache License, Version 2.0 (the "License");
1206# you may not use this file except in compliance with the License.
1207# You may obtain a copy of the License at
1208#
1209# http://www.apache.org/licenses/LICENSE-2.0
1210#
1211# Unless required by applicable law or agreed to in writing, software
1212# distributed under the License is distributed on an "AS IS" BASIS,
1213# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1214# See the License for the specific language governing permissions and
1215# limitations under the License.
1216""",
1217 )
1218 self.file_mock.return_value = ['file']
1219 for header in HEADERS:
1220 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001221 self.assertIsNotNone(pre_upload._check_aosp_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -04001222
Ken Turnerd07564b2018-02-08 17:57:59 +11001223 def testIgnoreExcludedPaths(self):
1224 """Ignores excluded paths for license checks."""
1225 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001226 self.content_mock.return_value = u'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001227 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +11001228
Chris McDonald7b63c8e2019-04-25 10:27:27 -06001229 def testIgnoreTopLevelExcludedPaths(self):
1230 """Ignores excluded paths for license checks."""
1231 self.file_mock.return_value = ['OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001232 self.content_mock.return_value = u'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001233 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
1234
Mike Frysinger98638102014-08-28 00:15:08 -04001235
Mike Frysingerb2496652019-09-12 23:35:46 -04001236class CheckLayoutConfTestCase(PreUploadTestCase):
Mike Frysingerd7734522015-02-26 16:12:43 -05001237 """Tests for _check_layout_conf."""
1238
1239 def setUp(self):
1240 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1241 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1242
1243 def assertAccepted(self, files, project='project', commit='fake sha1'):
1244 """Assert _check_layout_conf accepts |files|."""
1245 self.file_mock.return_value = files
1246 ret = pre_upload._check_layout_conf(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001247 self.assertIsNone(ret, msg='rejected with:\n%s' % ret)
Mike Frysingerd7734522015-02-26 16:12:43 -05001248
1249 def assertRejected(self, files, project='project', commit='fake sha1'):
1250 """Assert _check_layout_conf rejects |files|."""
1251 self.file_mock.return_value = files
1252 ret = pre_upload._check_layout_conf(project, commit)
1253 self.assertTrue(isinstance(ret, errors.HookFailure))
1254
1255 def GetLayoutConf(self, filters=()):
1256 """Return a valid layout.conf with |filters| lines removed."""
1257 all_lines = [
Mike Frysinger71e643e2019-09-13 17:26:39 -04001258 u'masters = portage-stable chromiumos',
1259 u'profile-formats = portage-2 profile-default-eapi',
1260 u'profile_eapi_when_unspecified = 5-progress',
1261 u'repo-name = link',
1262 u'thin-manifests = true',
1263 u'use-manifests = strict',
Mike Frysingerd7734522015-02-26 16:12:43 -05001264 ]
1265
1266 lines = []
1267 for line in all_lines:
1268 for filt in filters:
1269 if line.startswith(filt):
1270 break
1271 else:
1272 lines.append(line)
1273
Mike Frysinger71e643e2019-09-13 17:26:39 -04001274 return u'\n'.join(lines)
Mike Frysingerd7734522015-02-26 16:12:43 -05001275
1276 def testNoFilesToCheck(self):
1277 """Don't blow up when there are no layout.conf files."""
1278 self.assertAccepted([])
1279
1280 def testRejectRepoNameFile(self):
1281 """If profiles/repo_name is set, kick it out."""
1282 self.assertRejected(['profiles/repo_name'])
1283
1284 def testAcceptValidLayoutConf(self):
1285 """Accept a fully valid layout.conf."""
1286 self.content_mock.return_value = self.GetLayoutConf()
1287 self.assertAccepted(['metadata/layout.conf'])
1288
1289 def testAcceptUnknownKeys(self):
1290 """Accept keys we don't explicitly know about."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001291 self.content_mock.return_value = self.GetLayoutConf() + u'\nzzz-top = ok'
Mike Frysingerd7734522015-02-26 16:12:43 -05001292 self.assertAccepted(['metadata/layout.conf'])
1293
1294 def testRejectUnsorted(self):
1295 """Reject an unsorted layout.conf."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001296 self.content_mock.return_value = u'zzz-top = bad\n' + self.GetLayoutConf()
Mike Frysingerd7734522015-02-26 16:12:43 -05001297 self.assertRejected(['metadata/layout.conf'])
1298
1299 def testRejectMissingThinManifests(self):
1300 """Reject a layout.conf missing thin-manifests."""
1301 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001302 filters=[u'thin-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001303 self.assertRejected(['metadata/layout.conf'])
1304
1305 def testRejectMissingUseManifests(self):
1306 """Reject a layout.conf missing use-manifests."""
1307 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001308 filters=[u'use-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001309 self.assertRejected(['metadata/layout.conf'])
1310
1311 def testRejectMissingEapiFallback(self):
1312 """Reject a layout.conf missing profile_eapi_when_unspecified."""
1313 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001314 filters=[u'profile_eapi_when_unspecified'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001315 self.assertRejected(['metadata/layout.conf'])
1316
1317 def testRejectMissingRepoName(self):
1318 """Reject a layout.conf missing repo-name."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001319 self.content_mock.return_value = self.GetLayoutConf(filters=[u'repo-name'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001320 self.assertRejected(['metadata/layout.conf'])
1321
1322
Mike Frysingerb2496652019-09-12 23:35:46 -04001323class CommitMessageTestCase(PreUploadTestCase):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001324 """Test case for funcs that check commit messages."""
1325
1326 def setUp(self):
1327 self.msg_mock = self.PatchObject(pre_upload, '_get_commit_desc')
1328
1329 @staticmethod
1330 def CheckMessage(_project, _commit):
1331 raise AssertionError('Test class must declare CheckMessage')
1332 # This dummy return is to silence pylint warning W1111 so we don't have to
1333 # enable it for all the call sites below.
1334 return 1 # pylint: disable=W0101
1335
Alex Deymo643ac4c2015-09-03 10:40:50 -07001336 def assertMessageAccepted(self, msg, project=ProjectNamed('project'),
1337 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001338 """Assert _check_change_has_bug_field accepts |msg|."""
1339 self.msg_mock.return_value = msg
1340 ret = self.CheckMessage(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001341 self.assertIsNone(ret)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001342
Alex Deymo643ac4c2015-09-03 10:40:50 -07001343 def assertMessageRejected(self, msg, project=ProjectNamed('project'),
1344 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001345 """Assert _check_change_has_bug_field rejects |msg|."""
1346 self.msg_mock.return_value = msg
1347 ret = self.CheckMessage(project, commit)
1348 self.assertTrue(isinstance(ret, errors.HookFailure))
1349
1350
1351class CheckCommitMessageBug(CommitMessageTestCase):
1352 """Tests for _check_change_has_bug_field."""
1353
Alex Deymo643ac4c2015-09-03 10:40:50 -07001354 AOSP_PROJECT = pre_upload.Project(name='overlay', dir='', remote='aosp')
1355 CROS_PROJECT = pre_upload.Project(name='overlay', dir='', remote='cros')
1356
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001357 @staticmethod
1358 def CheckMessage(project, commit):
1359 return pre_upload._check_change_has_bug_field(project, commit)
1360
1361 def testNormal(self):
1362 """Accept a commit message w/a valid BUG."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001363 self.assertMessageAccepted('\nBUG=chromium:1234\n', self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001364 self.assertMessageAccepted('\nBUG=b:1234\n', self.CROS_PROJECT)
1365
1366 self.assertMessageAccepted('\nBug: 1234\n', self.AOSP_PROJECT)
1367 self.assertMessageAccepted('\nBug:1234\n', self.AOSP_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001368
1369 def testNone(self):
1370 """Accept BUG=None."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001371 self.assertMessageAccepted('\nBUG=None\n', self.CROS_PROJECT)
1372 self.assertMessageAccepted('\nBUG=none\n', self.CROS_PROJECT)
1373 self.assertMessageAccepted('\nBug: None\n', self.AOSP_PROJECT)
1374 self.assertMessageAccepted('\nBug:none\n', self.AOSP_PROJECT)
1375
1376 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1377 self.assertMessageRejected('\nBUG=NONE\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001378
1379 def testBlank(self):
1380 """Reject blank values."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001381 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1382 self.assertMessageRejected('\nBUG=\n', project)
1383 self.assertMessageRejected('\nBUG= \n', project)
1384 self.assertMessageRejected('\nBug:\n', project)
1385 self.assertMessageRejected('\nBug: \n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001386
1387 def testNotFirstLine(self):
1388 """Reject the first line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001389 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1390 self.assertMessageRejected('BUG=None\n\n\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001391
1392 def testNotInline(self):
1393 """Reject not at the start of line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001394 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1395 self.assertMessageRejected('\n BUG=None\n', project)
1396 self.assertMessageRejected('\n\tBUG=None\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001397
1398 def testOldTrackers(self):
1399 """Reject commit messages using old trackers."""
Mike Frysingera2f28252017-10-27 22:26:14 -04001400 self.assertMessageRejected('\nBUG=chrome-os-partner:1234\n',
1401 self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001402 self.assertMessageRejected('\nBUG=chromium-os:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001403
1404 def testNoTrackers(self):
1405 """Reject commit messages w/invalid trackers."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001406 self.assertMessageRejected('\nBUG=booga:1234\n', self.CROS_PROJECT)
1407 self.assertMessageRejected('\nBUG=br:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001408
1409 def testMissing(self):
1410 """Reject commit messages w/no BUG line."""
1411 self.assertMessageRejected('foo\n')
1412
1413 def testCase(self):
1414 """Reject bug lines that are not BUG."""
1415 self.assertMessageRejected('\nbug=none\n')
1416
Cheng Yuehb707c522020-01-02 14:06:59 +08001417 def testNotAfterTest(self):
1418 """Reject any TEST line before any BUG line."""
1419 test_field = 'TEST=i did not do it\n'
1420 middle_field = 'A random between line\n'
1421 for project, bug_field in ((self.AOSP_PROJECT, 'Bug:none\n'),
1422 (self.CROS_PROJECT, 'BUG=None\n')):
1423 self.assertMessageRejected(
1424 '\n' + test_field + middle_field + bug_field, project)
1425 self.assertMessageRejected(
1426 '\n' + test_field + bug_field, project)
1427
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001428
1429class CheckCommitMessageCqDepend(CommitMessageTestCase):
1430 """Tests for _check_change_has_valid_cq_depend."""
1431
1432 @staticmethod
1433 def CheckMessage(project, commit):
1434 return pre_upload._check_change_has_valid_cq_depend(project, commit)
1435
1436 def testNormal(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001437 """Accept valid Cq-Depends line."""
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001438 self.assertMessageAccepted('\nCq-Depend: chromium:1234\nChange-Id: I123')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001439
1440 def testInvalid(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001441 """Reject invalid Cq-Depends line."""
1442 self.assertMessageRejected('\nCq-Depend=chromium=1234\n')
1443 self.assertMessageRejected('\nCq-Depend: None\n')
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001444 self.assertMessageRejected('\nCq-Depend: chromium:1234\n\nChange-Id: I123')
Mike Frysingere39d0cd2019-11-25 13:30:06 -05001445 self.assertMessageRejected('\nCQ-DEPEND=1234\n')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001446
1447
Bernie Thompsonf8fea992016-01-14 10:27:18 -08001448class CheckCommitMessageContribution(CommitMessageTestCase):
1449 """Tests for _check_change_is_contribution."""
1450
1451 @staticmethod
1452 def CheckMessage(project, commit):
1453 return pre_upload._check_change_is_contribution(project, commit)
1454
1455 def testNormal(self):
1456 """Accept a commit message which is a contribution."""
1457 self.assertMessageAccepted('\nThis is cool code I am contributing.\n')
1458
1459 def testFailureLowerCase(self):
1460 """Deny a commit message which is not a contribution."""
1461 self.assertMessageRejected('\nThis is not a contribution.\n')
1462
1463 def testFailureUpperCase(self):
1464 """Deny a commit message which is not a contribution."""
1465 self.assertMessageRejected('\nNOT A CONTRIBUTION\n')
1466
1467
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001468class CheckCommitMessageTest(CommitMessageTestCase):
1469 """Tests for _check_change_has_test_field."""
1470
1471 @staticmethod
1472 def CheckMessage(project, commit):
1473 return pre_upload._check_change_has_test_field(project, commit)
1474
1475 def testNormal(self):
1476 """Accept a commit message w/a valid TEST."""
1477 self.assertMessageAccepted('\nTEST=i did it\n')
1478
1479 def testNone(self):
1480 """Accept TEST=None."""
1481 self.assertMessageAccepted('\nTEST=None\n')
1482 self.assertMessageAccepted('\nTEST=none\n')
1483
1484 def testBlank(self):
1485 """Reject blank values."""
1486 self.assertMessageRejected('\nTEST=\n')
1487 self.assertMessageRejected('\nTEST= \n')
1488
1489 def testNotFirstLine(self):
1490 """Reject the first line."""
1491 self.assertMessageRejected('TEST=None\n\n\n')
1492
1493 def testNotInline(self):
1494 """Reject not at the start of line."""
1495 self.assertMessageRejected('\n TEST=None\n')
1496 self.assertMessageRejected('\n\tTEST=None\n')
1497
1498 def testMissing(self):
1499 """Reject commit messages w/no TEST line."""
1500 self.assertMessageRejected('foo\n')
1501
1502 def testCase(self):
1503 """Reject bug lines that are not TEST."""
1504 self.assertMessageRejected('\ntest=none\n')
1505
1506
1507class CheckCommitMessageChangeId(CommitMessageTestCase):
1508 """Tests for _check_change_has_proper_changeid."""
1509
1510 @staticmethod
1511 def CheckMessage(project, commit):
1512 return pre_upload._check_change_has_proper_changeid(project, commit)
1513
1514 def testNormal(self):
1515 """Accept a commit message w/a valid Change-Id."""
1516 self.assertMessageAccepted('foo\n\nChange-Id: I1234\n')
1517
1518 def testBlank(self):
1519 """Reject blank values."""
1520 self.assertMessageRejected('\nChange-Id:\n')
1521 self.assertMessageRejected('\nChange-Id: \n')
1522
1523 def testNotFirstLine(self):
1524 """Reject the first line."""
1525 self.assertMessageRejected('TEST=None\n\n\n')
1526
1527 def testNotInline(self):
1528 """Reject not at the start of line."""
1529 self.assertMessageRejected('\n Change-Id: I1234\n')
1530 self.assertMessageRejected('\n\tChange-Id: I1234\n')
1531
1532 def testMissing(self):
1533 """Reject commit messages missing the line."""
1534 self.assertMessageRejected('foo\n')
1535
1536 def testCase(self):
1537 """Reject bug lines that are not Change-Id."""
1538 self.assertMessageRejected('\nchange-id: I1234\n')
1539 self.assertMessageRejected('\nChange-id: I1234\n')
1540 self.assertMessageRejected('\nChange-ID: I1234\n')
1541
1542 def testEnd(self):
1543 """Reject Change-Id's that are not last."""
1544 self.assertMessageRejected('\nChange-Id: I1234\nbar\n')
1545
Mike Frysinger02b88bd2014-11-21 00:29:38 -05001546 def testSobTag(self):
1547 """Permit s-o-b tags to follow the Change-Id."""
1548 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nSigned-off-by: Hi\n')
1549
LaMont Jones237f3ef2020-01-22 10:40:52 -07001550 def testCqClTag(self):
1551 """Permit Cq-Cl-Tag tags to follow the Change-Id."""
1552 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nCq-Cl-Tag: Hi\n')
1553
LaMont Jonesfb5e8bf2020-03-03 12:50:06 -07001554 def testCqIncludeTrybotsTag(self):
1555 """Permit Cq-Include-Trybots tags to follow the Change-Id."""
1556 self.assertMessageAccepted(
1557 'foo\n\nChange-Id: I1234\nCq-Include-Trybots: chromeos/cq:foo\n')
1558
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001559
Jack Neus8edbf642019-07-10 16:08:31 -06001560class CheckCommitMessageNoOEM(CommitMessageTestCase):
1561 """Tests for _check_change_no_include_oem."""
1562
1563 @staticmethod
1564 def CheckMessage(project, commit):
1565 return pre_upload._check_change_no_include_oem(project, commit)
1566
1567 def testNormal(self):
1568 """Accept a commit message w/o reference to an OEM/ODM."""
1569 self.assertMessageAccepted('foo\n')
1570
1571 def testHasOEM(self):
1572 """Catch commit messages referencing OEMs."""
1573 self.assertMessageRejected('HP Project\n\n')
1574 self.assertMessageRejected('hewlett-packard\n')
1575 self.assertMessageRejected('Hewlett\nPackard\n')
1576 self.assertMessageRejected('Dell Chromebook\n\n\n')
1577 self.assertMessageRejected('product@acer.com\n')
1578 self.assertMessageRejected('This is related to Asus\n')
1579 self.assertMessageRejected('lenovo machine\n')
1580
1581 def testHasODM(self):
1582 """Catch commit messages referencing ODMs."""
1583 self.assertMessageRejected('new samsung laptop\n\n')
1584 self.assertMessageRejected('pegatron(ems) project\n')
1585 self.assertMessageRejected('new Wistron device\n')
1586
1587 def testContainsOEM(self):
1588 """Check that the check handles word boundaries properly."""
1589 self.assertMessageAccepted('oheahpohea')
1590 self.assertMessageAccepted('Play an Asus7 guitar chord.\n\n')
1591
1592 def testTag(self):
1593 """Check that the check ignores tags."""
1594 self.assertMessageAccepted(
1595 'Harmless project\n'
1596 'Reviewed-by: partner@asus.corp-partner.google.com\n'
1597 'Tested-by: partner@hp.corp-partner.google.com\n'
1598 'Signed-off-by: partner@dell.corp-partner.google.com\n'
1599 'Commit-Queue: partner@lenovo.corp-partner.google.com\n'
Jack Neus8edbf642019-07-10 16:08:31 -06001600 'CC: partner@acer.corp-partner.google.com\n'
1601 'Acked-by: partner@hewlett-packard.corp-partner.google.com\n')
1602 self.assertMessageRejected(
1603 'Asus project\n'
1604 'Reviewed-by: partner@asus.corp-partner.google.com')
Mike Frysingerbb34a222019-07-31 14:40:46 -04001605 self.assertMessageRejected(
1606 'my project\n'
1607 'Bad-tag: partner@asus.corp-partner.google.com')
Jack Neus8edbf642019-07-10 16:08:31 -06001608
1609
Mike Frysinger36b2ebc2014-10-31 14:02:03 -04001610class CheckCommitMessageStyle(CommitMessageTestCase):
1611 """Tests for _check_commit_message_style."""
1612
1613 @staticmethod
1614 def CheckMessage(project, commit):
1615 return pre_upload._check_commit_message_style(project, commit)
1616
1617 def testNormal(self):
1618 """Accept valid commit messages."""
1619 self.assertMessageAccepted('one sentence.\n')
1620 self.assertMessageAccepted('some.module: do it!\n')
1621 self.assertMessageAccepted('one line\n\nmore stuff here.')
1622
1623 def testNoBlankSecondLine(self):
1624 """Reject messages that have stuff on the second line."""
1625 self.assertMessageRejected('one sentence.\nbad fish!\n')
1626
1627 def testFirstLineMultipleSentences(self):
1628 """Reject messages that have more than one sentence in the summary."""
1629 self.assertMessageRejected('one sentence. two sentence!\n')
1630
1631 def testFirstLineTooLone(self):
1632 """Reject first lines that are too long."""
1633 self.assertMessageRejected('o' * 200)
1634
1635
Mike Frysinger292b45d2014-11-25 01:17:10 -05001636def DiffEntry(src_file=None, dst_file=None, src_mode=None, dst_mode='100644',
1637 status='M'):
1638 """Helper to create a stub RawDiffEntry object"""
1639 if src_mode is None:
1640 if status == 'A':
1641 src_mode = '000000'
1642 elif status == 'M':
1643 src_mode = dst_mode
1644 elif status == 'D':
1645 src_mode = dst_mode
1646 dst_mode = '000000'
1647
1648 src_sha = dst_sha = 'abc'
1649 if status == 'D':
1650 dst_sha = '000000'
1651 elif status == 'A':
1652 src_sha = '000000'
1653
1654 return git.RawDiffEntry(src_mode=src_mode, dst_mode=dst_mode, src_sha=src_sha,
1655 dst_sha=dst_sha, status=status, score=None,
1656 src_file=src_file, dst_file=dst_file)
1657
1658
Mike Frysingerb2496652019-09-12 23:35:46 -04001659class HelpersTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001660 """Various tests for utility functions."""
1661
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001662 def setUp(self):
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001663 os.chdir(self.tempdir)
1664
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001665 self.PatchObject(git, 'RawDiff', return_value=[
1666 # A modified normal file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001667 DiffEntry(src_file='buildbot/constants.py', status='M'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001668 # A new symlink file.
Bob Haarman0dc1f942020-10-03 00:06:59 +00001669 DiffEntry(dst_file='scripts/cros_env_allowlist', dst_mode='120000',
Mike Frysinger292b45d2014-11-25 01:17:10 -05001670 status='A'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001671 # A deleted file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001672 DiffEntry(src_file='scripts/sync_sonic.py', status='D'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001673 ])
1674
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001675 def _WritePresubmitIgnoreFile(self, subdir, data):
1676 """Writes to a .presubmitignore file in the passed-in subdirectory."""
1677 directory = os.path.join(self.tempdir, subdir)
1678 if not os.path.exists(directory):
1679 os.makedirs(directory)
1680 osutils.WriteFile(os.path.join(directory, pre_upload._IGNORE_FILE), data)
1681
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001682 def testGetAffectedFilesNoDeletesNoRelative(self):
1683 """Verify _get_affected_files() works w/no delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001684 path = os.getcwd()
1685 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1686 relative=False)
1687 exp_files = [os.path.join(path, 'buildbot/constants.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001688 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001689
1690 def testGetAffectedFilesDeletesNoRelative(self):
1691 """Verify _get_affected_files() works w/delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001692 path = os.getcwd()
1693 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1694 relative=False)
1695 exp_files = [os.path.join(path, 'buildbot/constants.py'),
1696 os.path.join(path, 'scripts/sync_sonic.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001697 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001698
1699 def testGetAffectedFilesNoDeletesRelative(self):
1700 """Verify _get_affected_files() works w/no delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001701 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1702 relative=True)
1703 exp_files = ['buildbot/constants.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001704 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001705
1706 def testGetAffectedFilesDeletesRelative(self):
1707 """Verify _get_affected_files() works w/delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001708 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1709 relative=True)
1710 exp_files = ['buildbot/constants.py', 'scripts/sync_sonic.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001711 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001712
Mike Frysinger292b45d2014-11-25 01:17:10 -05001713 def testGetAffectedFilesDetails(self):
1714 """Verify _get_affected_files() works w/full_details."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001715 files = pre_upload._get_affected_files('HEAD', full_details=True,
1716 relative=True)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001717 self.assertEqual(files[0].src_file, 'buildbot/constants.py')
Mike Frysinger292b45d2014-11-25 01:17:10 -05001718
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001719 def testGetAffectedFilesPresubmitIgnoreDirectory(self):
1720 """Verify .presubmitignore can be used to exclude a directory."""
1721 self._WritePresubmitIgnoreFile('.', 'buildbot/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001722 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001723
1724 def testGetAffectedFilesPresubmitIgnoreDirectoryWildcard(self):
1725 """Verify .presubmitignore can be used with a directory wildcard."""
1726 self._WritePresubmitIgnoreFile('.', '*/constants.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001727 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001728
1729 def testGetAffectedFilesPresubmitIgnoreWithinDirectory(self):
1730 """Verify .presubmitignore can be placed in a subdirectory."""
1731 self._WritePresubmitIgnoreFile('buildbot', '*.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001732 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001733
1734 def testGetAffectedFilesPresubmitIgnoreDoesntMatch(self):
1735 """Verify .presubmitignore has no effect when it doesn't match a file."""
1736 self._WritePresubmitIgnoreFile('buildbot', '*.txt')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001737 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True),
1738 ['buildbot/constants.py'])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001739
1740 def testGetAffectedFilesPresubmitIgnoreAddedFile(self):
1741 """Verify .presubmitignore matches added files."""
1742 self._WritePresubmitIgnoreFile('.', 'buildbot/\nscripts/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001743 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True,
1744 include_symlinks=True),
1745 [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001746
1747 def testGetAffectedFilesPresubmitIgnoreSkipIgnoreFile(self):
1748 """Verify .presubmitignore files are automatically skipped."""
1749 self.PatchObject(git, 'RawDiff', return_value=[
1750 DiffEntry(src_file='.presubmitignore', status='M')
1751 ])
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001752 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Mike Frysinger292b45d2014-11-25 01:17:10 -05001753
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001754
Mike Frysingerb2496652019-09-12 23:35:46 -04001755class ExecFilesTest(PreUploadTestCase):
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001756 """Tests for _check_exec_files."""
1757
1758 def setUp(self):
1759 self.diff_mock = self.PatchObject(git, 'RawDiff')
1760
1761 def testNotExec(self):
1762 """Do not flag files that are not executable."""
1763 self.diff_mock.return_value = [
1764 DiffEntry(src_file='make.conf', dst_mode='100644', status='A'),
1765 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001766 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001767
1768 def testExec(self):
1769 """Flag files that are executable."""
1770 self.diff_mock.return_value = [
1771 DiffEntry(src_file='make.conf', dst_mode='100755', status='A'),
1772 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001773 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001774
1775 def testDeletedExec(self):
1776 """Ignore bad files that are being deleted."""
1777 self.diff_mock.return_value = [
1778 DiffEntry(src_file='make.conf', dst_mode='100755', status='D'),
1779 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001780 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001781
1782 def testModifiedExec(self):
1783 """Flag bad files that weren't exec, but now are."""
1784 self.diff_mock.return_value = [
1785 DiffEntry(src_file='make.conf', src_mode='100644', dst_mode='100755',
1786 status='M'),
1787 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001788 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001789
1790 def testNormalExec(self):
1791 """Don't flag normal files (e.g. scripts) that are executable."""
1792 self.diff_mock.return_value = [
1793 DiffEntry(src_file='foo.sh', dst_mode='100755', status='A'),
1794 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001795 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001796
1797
Mike Frysingerb2496652019-09-12 23:35:46 -04001798class CheckForUprev(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger292b45d2014-11-25 01:17:10 -05001799 """Tests for _check_for_uprev."""
1800
1801 def setUp(self):
1802 self.file_mock = self.PatchObject(git, 'RawDiff')
1803
1804 def _Files(self, files):
1805 """Create |files| in the tempdir and return full paths to them."""
1806 for obj in files:
1807 if obj.status == 'D':
1808 continue
1809 if obj.dst_file is None:
1810 f = obj.src_file
1811 else:
1812 f = obj.dst_file
1813 osutils.Touch(os.path.join(self.tempdir, f), makedirs=True)
1814 return files
1815
1816 def assertAccepted(self, files, project='project', commit='fake sha1'):
1817 """Assert _check_for_uprev accepts |files|."""
1818 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001819 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1820 project_top=self.tempdir)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001821 self.assertIsNone(ret)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001822
1823 def assertRejected(self, files, project='project', commit='fake sha1'):
1824 """Assert _check_for_uprev rejects |files|."""
1825 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001826 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1827 project_top=self.tempdir)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001828 self.assertTrue(isinstance(ret, errors.HookFailure))
1829
Bob Haarman0dc1f942020-10-03 00:06:59 +00001830 def testAllowlistOverlay(self):
1831 """Skip checks on allowed overlays."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001832 self.assertAccepted([DiffEntry(src_file='cat/pkg/pkg-0.ebuild')],
1833 project='chromiumos/overlays/portage-stable')
1834
Bob Haarman0dc1f942020-10-03 00:06:59 +00001835 def testAllowlistFiles(self):
1836 """Skip checks on allowed files."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001837 files = ['ChangeLog', 'Manifest', 'metadata.xml']
1838 self.assertAccepted([DiffEntry(src_file=os.path.join('c', 'p', x),
1839 status='M')
1840 for x in files])
1841
1842 def testRejectBasic(self):
1843 """Reject ebuilds missing uprevs."""
1844 self.assertRejected([DiffEntry(src_file='c/p/p-0.ebuild', status='M')])
1845
1846 def testNewPackage(self):
1847 """Accept new ebuilds w/out uprevs."""
1848 self.assertAccepted([DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1849 self.assertAccepted([DiffEntry(src_file='c/p/p-0-r12.ebuild', status='A')])
1850
1851 def testModifiedFilesOnly(self):
1852 """Reject ebuilds w/out uprevs and changes in files/."""
1853 osutils.Touch(os.path.join(self.tempdir, 'cat/pkg/pkg-0.ebuild'),
1854 makedirs=True)
1855 self.assertRejected([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1856 self.assertRejected([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1857
1858 def testFilesNoEbuilds(self):
1859 """Ignore changes to paths w/out ebuilds."""
1860 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1861 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1862
1863 def testModifiedFilesWithUprev(self):
1864 """Accept ebuilds w/uprevs and changes in files/."""
1865 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='A'),
1866 DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1867 self.assertAccepted([
1868 DiffEntry(src_file='c/p/files/f', status='M'),
1869 DiffEntry(src_file='c/p/p-0-r1.ebuild', src_mode='120000',
1870 dst_file='c/p/p-0-r2.ebuild', dst_mode='120000', status='R')])
1871
Gwendal Grignoua3086c32014-12-09 11:17:22 -08001872 def testModifiedFilesWith9999(self):
1873 """Accept 9999 ebuilds and changes in files/."""
1874 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M'),
1875 DiffEntry(src_file='c/p/p-9999.ebuild', status='M')])
1876
C Shapiroae157ae2017-09-18 16:24:03 -06001877 def testModifiedFilesIn9999SubDirWithout9999Change(self):
1878 """Accept changes in files/ with a parent 9999 ebuild"""
1879 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
1880 os.makedirs(os.path.dirname(ebuild_9999_file))
1881 osutils.WriteFile(ebuild_9999_file, 'fake')
1882 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M')])
1883
Stephen Boyd6bf5ea82020-10-15 00:02:07 -07001884 def testModifiedFilesAndProfilesWith9999(self):
1885 """Accept changes in files/ with a parent 9999 ebuild and profile change"""
1886 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
1887 os.makedirs(os.path.dirname(ebuild_9999_file))
1888 osutils.WriteFile(ebuild_9999_file, 'fake')
1889 self.assertAccepted([
1890 DiffEntry(src_file='c/p/files/f', status='M'),
1891 DiffEntry(src_file='c/profiles/base/make.defaults', status='M')])
1892
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001893
Mike Frysingerb2496652019-09-12 23:35:46 -04001894class DirectMainTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger55f85b52014-12-18 14:45:21 -05001895 """Tests for direct_main()"""
1896
1897 def setUp(self):
1898 self.hooks_mock = self.PatchObject(pre_upload, '_run_project_hooks',
1899 return_value=None)
1900
1901 def testNoArgs(self):
1902 """If run w/no args, should check the current dir."""
1903 ret = pre_upload.direct_main([])
1904 self.assertEqual(ret, 0)
1905 self.hooks_mock.assert_called_once_with(
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001906 mock.ANY, proj_dir=os.getcwd(), commit_list=[], presubmit=mock.ANY,
1907 config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001908
1909 def testExplicitDir(self):
1910 """Verify we can run on a diff dir."""
1911 # Use the chromite dir since we know it exists.
1912 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR])
1913 self.assertEqual(ret, 0)
1914 self.hooks_mock.assert_called_once_with(
1915 mock.ANY, proj_dir=constants.CHROMITE_DIR, commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001916 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001917
1918 def testBogusProject(self):
1919 """A bogus project name should be fine (use default settings)."""
1920 # Use the chromite dir since we know it exists.
1921 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR,
1922 '--project', 'foooooooooo'])
1923 self.assertEqual(ret, 0)
1924 self.hooks_mock.assert_called_once_with(
1925 'foooooooooo', proj_dir=constants.CHROMITE_DIR, commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001926 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001927
1928 def testBogustProjectNoDir(self):
1929 """Make sure --dir is detected even with --project."""
1930 ret = pre_upload.direct_main(['--project', 'foooooooooo'])
1931 self.assertEqual(ret, 0)
1932 self.hooks_mock.assert_called_once_with(
Mike Frysinger9ba001a2020-11-20 01:02:11 -05001933 'foooooooooo', proj_dir=os.path.dirname(os.path.abspath(__file__)),
1934 commit_list=[], presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001935
1936 def testNoGitDir(self):
1937 """We should die when run on a non-git dir."""
1938 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
1939 ['--dir', self.tempdir])
1940
1941 def testNoDir(self):
1942 """We should die when run on a missing dir."""
1943 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
1944 ['--dir', os.path.join(self.tempdir, 'foooooooo')])
1945
1946 def testCommitList(self):
1947 """Any args on the command line should be treated as commits."""
1948 commits = ['sha1', 'sha2', 'shaaaaaaaaaaaan']
1949 ret = pre_upload.direct_main(commits)
1950 self.assertEqual(ret, 0)
1951 self.hooks_mock.assert_called_once_with(
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001952 mock.ANY, proj_dir=mock.ANY, commit_list=commits, presubmit=mock.ANY,
1953 config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001954
1955
Mike Frysingerb2496652019-09-12 23:35:46 -04001956class CheckRustfmtTest(PreUploadTestCase):
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001957 """Tests for _check_rustfmt."""
1958
1959 def setUp(self):
1960 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1961
1962 def testBadRustFile(self):
1963 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
1964 # Bad because it's missing trailing newline.
Mike Frysingere85b0062019-08-20 15:10:33 -04001965 content = 'fn main() {}'
1966 self.content_mock.return_value = content
1967 self.PatchObject(pre_upload, '_run_command', return_value=content + '\n')
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001968 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1969 self.assertIsNotNone(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001970 self.assertEqual('Files not formatted with rustfmt: '
1971 "(run 'cargo fmt' to fix)",
1972 failure.msg)
1973 self.assertEqual(['a.rs'], failure.items)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001974
1975 def testGoodRustFile(self):
1976 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
Mike Frysingere85b0062019-08-20 15:10:33 -04001977 content = 'fn main() {}\n'
1978 self.content_mock.return_value = content
1979 self.PatchObject(pre_upload, '_run_command', return_value=content)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001980 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1981 self.assertIsNone(failure)
1982
1983 def testFilterNonRustFiles(self):
1984 self.PatchObject(pre_upload, '_get_affected_files',
1985 return_value=['a.cc', 'a.rsa', 'a.irs', 'rs.cc'])
1986 self.content_mock.return_value = 'fn main() {\n}'
1987 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1988 self.assertIsNone(failure)
1989
1990
Keiichi Watanabe6ab73fd2020-07-14 23:42:02 +09001991class GetCargoClippyParserTest(cros_test_lib.TestCase):
1992 """Tests for _get_cargo_clippy_parser."""
1993
1994 def testSingleProject(self):
1995 parser = pre_upload._get_cargo_clippy_parser()
1996 args = parser.parse_args(['--project', 'foo'])
1997 self.assertEqual(args.project,
1998 [pre_upload.ClippyProject(root='foo', script=None)])
1999
2000 def testMultipleProjects(self):
2001 parser = pre_upload._get_cargo_clippy_parser()
2002 args = parser.parse_args(['--project', 'foo:bar',
2003 '--project', 'baz'])
2004 self.assertCountEqual(args.project,
2005 [pre_upload.ClippyProject(root='foo', script='bar'),
2006 pre_upload.ClippyProject(root='baz', script=None)])
2007
2008
2009class CheckCargoClippyTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
2010 """Tests for _check_cargo_clippy."""
2011
2012 def setUp(self):
2013 self.project = pre_upload.Project(name='PROJECT', dir=self.tempdir,
2014 remote=None)
2015
2016 def testClippy(self):
2017 """Verify clippy is called when a monitored file was changed."""
2018 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
2019
2020 self.PatchObject(pre_upload, '_get_affected_files',
2021 return_value=[f'{self.project.dir}/repo_a/a.rs'])
2022
2023 ret = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
2024 options=['--project=repo_a',
2025 '--project=repo_b:foo'])
2026 self.assertFalse(ret)
2027
2028 # Check if `cargo clippy` ran.
2029 called = False
2030 for args, _ in rc_mock.call_args_list:
2031 cmd = args[0]
2032 if len(cmd) > 1 and cmd[0] == 'cargo' and cmd[1] == 'clippy':
2033 called = True
2034 break
2035
2036 self.assertTrue(called)
2037
2038 def testDontRun(self):
2039 """Skip checks when no monitored files are modified."""
2040 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
2041
2042 # A file under `repo_a` was monitored.
2043 self.PatchObject(pre_upload, '_get_affected_files',
2044 return_value=[f'{self.project.dir}/repo_a/a.rs'])
2045 # But, we only care about files under `repo_b`.
2046 errs = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
2047 options=['--project=repo_b:foo'])
2048
2049 self.assertFalse(errs)
2050
2051 rc_mock.assert_not_called()
2052
2053 def testCustomScript(self):
2054 """Verify project-specific script is used."""
2055 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
2056
2057 self.PatchObject(pre_upload, '_get_affected_files',
2058 return_value=[f'{self.project.dir}/repo_b/b.rs'])
2059
2060 errs = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
2061 options=['--project=repo_a',
2062 '--project=repo_b:foo'])
2063 self.assertFalse(errs)
2064
2065 # Check if the script `foo` ran.
2066 called = False
2067 for args, _ in rc_mock.call_args_list:
2068 cmd = args[0]
2069 if len(cmd) > 0 and cmd[0] == os.path.join(self.project.dir, 'foo'):
2070 called = True
2071 break
2072
2073 self.assertTrue(called)
2074
2075
Mike Frysinger180ecd62020-08-19 00:41:51 -04002076class OverrideHooksProcessing(PreUploadTestCase):
2077 """Verify _get_override_hooks processing."""
2078
2079 @staticmethod
2080 def parse(data):
2081 """Helper to create a config & parse it."""
2082 cfg = configparser.ConfigParser()
2083 cfg.read_string(data)
2084 return pre_upload._get_override_hooks(cfg)
2085
2086 def testHooks(self):
2087 """Verify we reject unknown hook names (e.g. typos)."""
2088 with self.assertRaises(ValueError) as e:
2089 self.parse("""
2090[Hook Overrides]
2091foobar: true
2092""")
2093 self.assertIn('foobar', str(e.exception))
2094
2095 def testImplicitDisable(self):
2096 """Verify non-common hooks aren't enabled by default."""
2097 enabled, _ = self.parse('')
2098 self.assertNotIn(pre_upload._run_checkpatch, enabled)
2099
2100 def testExplicitDisable(self):
2101 """Verify hooks disabled are disabled."""
2102 _, disabled = self.parse("""
2103[Hook Overrides]
2104tab_check: false
2105""")
2106 self.assertIn(pre_upload._check_no_tabs, disabled)
2107
2108 def testExplicitEnable(self):
2109 """Verify hooks enabled are enabled."""
2110 enabled, _ = self.parse("""
2111[Hook Overrides]
2112tab_check: true
2113""")
2114 self.assertIn(pre_upload._check_no_tabs, enabled)
2115
2116 def testOptions(self):
2117 """Verify hook options are loaded."""
2118 enabled, _ = self.parse("""
2119[Hook Overrides Options]
2120keyword_check: --kw
2121""")
2122 for func in enabled:
2123 if func.__name__ == 'keyword_check':
2124 self.assertIn('options', func.keywords)
2125 self.assertEqual(func.keywords['options'], ['--kw'])
2126 break
2127 else:
2128 self.fail('could not find "keyword_check" enabled hook')
2129
2130 def testSignOffField(self):
2131 """Verify signoff field handling."""
2132 # Enforce no s-o-b by default.
2133 enabled, disabled = self.parse('')
2134 self.assertIn(pre_upload._check_change_has_no_signoff_field, enabled)
2135 self.assertNotIn(pre_upload._check_change_has_signoff_field, enabled)
2136 self.assertNotIn(pre_upload._check_change_has_signoff_field, disabled)
2137
2138 # If disabled, don't enforce either policy.
2139 enabled, disabled = self.parse("""
2140[Hook Overrides]
2141signoff_check: false
2142""")
2143 self.assertNotIn(pre_upload._check_change_has_no_signoff_field, enabled)
2144 self.assertNotIn(pre_upload._check_change_has_signoff_field, enabled)
2145 self.assertIn(pre_upload._check_change_has_signoff_field, disabled)
2146
2147 # If enabled, require s-o-b.
2148 enabled, disabled = self.parse("""
2149[Hook Overrides]
2150signoff_check: true
2151""")
2152 self.assertNotIn(pre_upload._check_change_has_no_signoff_field, enabled)
2153 self.assertIn(pre_upload._check_change_has_signoff_field, enabled)
2154 self.assertNotIn(pre_upload._check_change_has_signoff_field, disabled)
2155
2156 def testBranchField(self):
2157 """Verify branch field enabling."""
2158 # Should be disabled by default.
2159 enabled, disabled = self.parse('')
2160 self.assertIn(pre_upload._check_change_has_no_branch_field, enabled)
2161 self.assertNotIn(pre_upload._check_change_has_branch_field, enabled)
2162 self.assertNotIn(pre_upload._check_change_has_branch_field, disabled)
2163
2164 # Should be disabled if requested.
2165 enabled, disabled = self.parse("""
2166[Hook Overrides]
2167branch_check: false
2168""")
2169 self.assertIn(pre_upload._check_change_has_no_branch_field, enabled)
2170 self.assertNotIn(pre_upload._check_change_has_branch_field, enabled)
2171 self.assertIn(pre_upload._check_change_has_branch_field, disabled)
2172
2173 # Should be enabled if requested.
2174 enabled, disabled = self.parse("""
2175[Hook Overrides]
2176branch_check: true
2177""")
2178 self.assertNotIn(pre_upload._check_change_has_no_branch_field, enabled)
2179 self.assertIn(pre_upload._check_change_has_branch_field, enabled)
2180 self.assertNotIn(pre_upload._check_change_has_branch_field, disabled)
2181
2182
Tom Hughes1ed799d2020-09-25 14:37:28 -07002183class ProjectHooksProcessing(PreUploadTestCase, cros_test_lib.TempDirTestCase):
2184 """Verify _get_project_hooks processing."""
2185
2186 def parse(self, data):
2187 """Helper to write config and parse it."""
2188 filename = os.path.join(self.tempdir, 'config')
2189 osutils.WriteFile(filename, data)
Mike Frysingerff916c62020-12-18 01:58:08 -05002190 return pre_upload._get_project_hooks(presubmit=True, config_file=filename)
Tom Hughes1ed799d2020-09-25 14:37:28 -07002191
2192 def testClangFormatCheckDefault(self):
2193 """Verify clang-format check disabled by default."""
2194 hooks = self.parse('')
2195 for func in hooks:
2196 self.assertNotEqual(func.__name__, '_check_clang_format')
2197 self.assertNotEqual(func.__name__, 'clang_format_check')
2198
2199 def testClangFormatCheckDisabled(self):
2200 """Verify clang-format check disabled when requested."""
2201 hooks = self.parse("""
2202[Hook Overrides]
2203clang_format_check: false
2204""")
2205 for func in hooks:
2206 self.assertNotEqual(func.__name__, '_check_clang_format')
2207 self.assertNotEqual(func.__name__, 'clang_format_check')
2208
2209 def testClangFormatCheckEnabled(self):
2210 """Verify clang-format check enabled when requested."""
2211 hooks = self.parse("""
2212[Hook Overrides]
2213clang_format_check: true
2214""")
2215 for func in hooks:
2216 if func.__name__ == '_check_clang_format':
2217 self.assertFalse(hasattr(func, 'keywords'))
2218 break
2219 else:
2220 self.fail('could not find "_check_clang_format" enabled hook')
2221
2222 def testClangFormatCheckEnabledWithOptions(self):
2223 """Verify clang-format check has options when provided."""
2224 hooks = self.parse("""
2225[Hook Overrides]
2226clang_format_check: true
2227
2228[Hook Overrides Options]
2229clang_format_check:
2230 some_dir/
2231""")
2232 for func in hooks:
2233 if func.__name__ == 'clang_format_check':
2234 self.assertIn('options', func.keywords)
2235 self.assertEqual(func.keywords['options'], ['some_dir/'])
2236 break
2237 else:
2238 self.fail('could not find "clang_format_check" enabled hook')
2239
2240
Jon Salz98255932012-08-18 14:48:02 +08002241if __name__ == '__main__':
Mike Frysinger884a8dd2015-05-17 03:43:43 -04002242 cros_test_lib.main(module=__name__)