blob: 35549467ab03267c910f3350d17a4e3968b9e989 [file] [log] [blame]
Mike Frysinger4f994402019-09-13 17:40:45 -04001#!/usr/bin/env python3
Jon Salz98255932012-08-18 14:48:02 +08002# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Mike Frysingerae409522014-02-01 03:16:11 -05006"""Unittests for pre-upload.py."""
7
Mike Frysinger180ecd62020-08-19 00:41:51 -04008import configparser
Keigo Oka7e880ac2019-07-03 15:03:43 +09009import datetime
David Jamesc3b68b32013-04-03 09:17:03 -070010import os
11import sys
Mike Frysingerf6a29772020-08-22 03:57:08 -040012from unittest import mock
Mike Frysingerfd481ce2019-09-13 18:14:48 -040013
Mike Frysingerbf8b91c2014-02-01 02:50:27 -050014import errors
15
Mike Frysinger1c05e5f2021-03-23 17:14:44 -040016# We access private members of the pre_upload module.
17# pylint: disable=protected-access
Mike Frysinger65d93c12014-02-01 02:59:46 -050018
Mike Frysinger55f85b52014-12-18 14:45:21 -050019# Make sure we can find the chromite paths.
20sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)),
21 '..', '..'))
Jon Salz98255932012-08-18 14:48:02 +080022
Mike Frysingerfd481ce2019-09-13 18:14:48 -040023# The sys.path monkey patching confuses the linter.
24# pylint: disable=wrong-import-position
Mike Frysinger71e643e2019-09-13 17:26:39 -040025from chromite.lib import constants
26from chromite.lib import cros_build_lib
Mike Frysinger65d93c12014-02-01 02:59:46 -050027from chromite.lib import cros_test_lib
Mike Frysingerd3bd32c2014-11-24 23:34:29 -050028from chromite.lib import git
Daniel Erata350fd32014-09-29 14:02:34 -070029from chromite.lib import osutils
Mike Frysinger65d93c12014-02-01 02:59:46 -050030
31
Mike Frysingerff4768e2020-02-27 18:48:13 -050032assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
33
34
Jon Salz98255932012-08-18 14:48:02 +080035pre_upload = __import__('pre-upload')
36
37
Alex Deymo643ac4c2015-09-03 10:40:50 -070038def ProjectNamed(project_name):
39 """Wrapper to create a Project with just the name"""
40 return pre_upload.Project(project_name, None, None)
41
42
Mike Frysingerb2496652019-09-12 23:35:46 -040043class PreUploadTestCase(cros_test_lib.MockTestCase):
44 """Common test case base."""
45
46 def setUp(self):
47 pre_upload.CACHE.clear()
48
49
50class TryUTF8DecodeTest(PreUploadTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -050051 """Verify we sanely handle unicode content."""
52
Mike Frysinger71e643e2019-09-13 17:26:39 -040053 def setUp(self):
Mike Frysinger7bb709f2019-09-29 23:20:12 -040054 self.rc_mock = self.PatchObject(cros_build_lib, 'run')
Mike Frysinger71e643e2019-09-13 17:26:39 -040055
56 def _run(self, content):
57 """Helper for round tripping through _run_command."""
58 self.rc_mock.return_value = cros_build_lib.CommandResult(
59 output=content, returncode=0)
60 return pre_upload._run_command([])
61
62 def testEmpty(self):
63 """Check empty output."""
64 ret = self._run(b'')
65 self.assertEqual('', ret)
66
67 if sys.version_info.major < 3:
68 ret = self._run('')
Mike Frysinger1c05e5f2021-03-23 17:14:44 -040069 self.assertEqual('', ret)
Mike Frysinger71e643e2019-09-13 17:26:39 -040070
71 def testAscii(self):
72 """Check ascii output."""
73 ret = self._run(b'abc')
74 self.assertEqual('abc', ret)
75
76 if sys.version_info.major < 3:
77 ret = self._run('abc')
Mike Frysinger1c05e5f2021-03-23 17:14:44 -040078 self.assertEqual('abc', ret)
Mike Frysinger71e643e2019-09-13 17:26:39 -040079
80 def testUtf8(self):
81 """Check valid UTF-8 output."""
Mike Frysinger1c05e5f2021-03-23 17:14:44 -040082 text = '你好布萊恩'
Mike Frysinger71e643e2019-09-13 17:26:39 -040083 ret = self._run(text.encode('utf-8'))
84 self.assertEqual(text, ret)
85
86 def testBinary(self):
87 """Check binary (invalid UTF-8) output."""
88 ret = self._run(b'hi \x80 there')
Mike Frysinger1c05e5f2021-03-23 17:14:44 -040089 self.assertEqual('hi \ufffd there', ret)
Jon Salz98255932012-08-18 14:48:02 +080090
91
Daisuke Nojiri2089e012020-08-20 15:12:36 -070092class CheckKeywordsTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Bernie Thompson8e26f742020-07-23 14:32:31 -070093 """Tests for _check_keywords."""
94
95 def setUp(self):
96 self.PatchObject(pre_upload, '_get_affected_files',
97 return_value=['x.ebuild'])
98 self.PatchObject(pre_upload, '_filter_files', return_value=['x.ebuild'])
Daisuke Nojiri062a0a92021-02-14 15:38:16 -080099 # First call reads blocked_terms.txt, second call reads unblocked_terms.txt
100 # for _get_file_diff, and third call reads unblocked_terms.txt for
101 # _get_commit_desc.
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700102 self.rf_mock = self.PatchObject(
103 osutils, 'ReadFile',
Daisuke Nojiri062a0a92021-02-14 15:38:16 -0800104 side_effect=['scruffy\nmangy\ndog.?pile\ncat.?circle', 'fox', 'fox'])
Bernie Thompson8e26f742020-07-23 14:32:31 -0700105 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
106 self.desc_mock = self.PatchObject(pre_upload, '_get_commit_desc')
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700107 self.project = pre_upload.Project(name='PROJECT', dir=self.tempdir,
108 remote=None)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700109
110 def test_good_cases(self):
111 self.desc_mock.return_value = 'Commit Message.\nLine 2'
112 self.diff_mock.return_value = [
113 (1, 'Some text without keywords.'),
114 (2, 'The dog is black has a partial keyword that does not count.'),
115 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700116 failures = pre_upload._check_keywords(self.project, 'COMMIT')
Bernie Thompson8e26f742020-07-23 14:32:31 -0700117 self.assertEqual(failures, [])
118
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700119 self.rf_mock.assert_has_calls([
120 mock.call(os.path.join(pre_upload._get_hooks_dir(),
121 pre_upload.BLOCKED_TERMS_FILE)),
Daisuke Nojiri062a0a92021-02-14 15:38:16 -0800122 mock.call(pre_upload.UNBLOCKED_TERMS_FILE),
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700123 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 Frysinger1c05e5f2021-03-23 17:14:44 -0400224 (1, 'x' * 80), # OK
225 (2, '\x80' * 80), # OK
226 (3, 'x' * 81), # Too long
227 (4, '\x80' * 81), # Too long
228 (5, 'See http://' + ('x' * 80)), # OK (URL)
229 (6, 'See https://' + ('x' * 80)), # OK (URL)
230 (7, '# define ' + ('x' * 80)), # OK (compiler directive)
231 (8, '#define' + ('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 = (
Mike Frysinger1c05e5f2021-03-23 17:14:44 -0400252 ('x' * 81, False),
253 ('foo file:' + 'x' * 80, True),
254 ('include ' + 'x' * 80, True),
George Burgess IVf9f79eb2019-07-09 20:12:55 -0700255 )
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 Frysinger1c05e5f2021-03-23 17:14:44 -0400277 self.diff_mock.return_value = [(1, '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 Frysinger1c05e5f2021-03-23 17:14:44 -0400287 self.diff_mock.return_value = [(1, '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 = (
Mike Frysinger1c05e5f2021-03-23 17:14:44 -0400295 ('x' * 101, True),
296 ('x' * 100, False),
297 ('x' * 81, False),
298 ('x' * 80, False),
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900299 )
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 = [
Mike Frysinger1c05e5f2021-03-23 17:14:44 -0400324 (1, 'no_tabs_anywhere'),
325 (2, '\tleading_tab_only'),
326 (3, '\tleading_tab another_tab'),
327 (4, '\tleading_tab trailing_too\t'),
328 (5, '\tleading_tab then_spaces_trailing '),
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800329 ]
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 = [
Mike Frysinger1c05e5f2021-03-23 17:14:44 -0400336 (1, ' leading_space'),
337 (2, '\t tab_followed_by_space'),
338 (3, ' \t\t\tspace_followed_by_tab'),
339 (4, ' \t\t\t mix_em_up'),
340 (5, '\t \t\t \t\t\tmix_on_both_sides\t\t\t '),
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800341 ]
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 Frysinger1c05e5f2021-03-23 17:14:44 -0400597 self.content_mock.return_value = '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 Frysinger1c05e5f2021-03-23 17:14:44 -0400604 self.content_mock.return_value = '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 Frysinger1c05e5f2021-03-23 17:14:44 -0400611 self.content_mock.return_value = '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 Frysinger1c05e5f2021-03-23 17:14:44 -0400616 self.content_mock.return_value = '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 Frysinger1c05e5f2021-03-23 17:14:44 -0400623 self.content_mock.return_value = '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 Frysinger1c05e5f2021-03-23 17:14:44 -0400630 self.content_mock.return_value = '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 Frysinger1c05e5f2021-03-23 17:14:44 -0400671 self.content_mock.return_value = '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 Frysinger1c05e5f2021-03-23 17:14:44 -0400679 self.content_mock.return_value = """# 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 Frysinger1c05e5f2021-03-23 17:14:44 -0400690 template = """# 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 Frysinger1c05e5f2021-03-23 17:14:44 -0400714 template = """# 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 Frysinger1c05e5f2021-03-23 17:14:44 -0400754 self.content_mock.return_value = ''
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 Frysinger1c05e5f2021-03-23 17:14:44 -0400781 self._CheckContent('# HEADER\nKEYWORDS=\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400782
783 def testEmptyQuotes(self):
784 """Check KEYWORDS="" is accepted."""
Mike Frysinger1c05e5f2021-03-23 17:14:44 -0400785 self._CheckContent('# HEADER\nKEYWORDS=" "\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400786
787 def testStableGlob(self):
788 """Check KEYWORDS=* is accepted."""
Mike Frysinger1c05e5f2021-03-23 17:14:44 -0400789 self._CheckContent('# 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 Frysinger1c05e5f2021-03-23 17:14:44 -0400793 self._CheckContent('# HEADER\nKEYWORDS="~* "\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400794
795 def testRestrictedGlob(self):
796 """Check KEYWORDS=-* is accepted."""
Mike Frysinger1c05e5f2021-03-23 17:14:44 -0400797 self._CheckContent('# 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 Frysinger1c05e5f2021-03-23 17:14:44 -0400801 self._CheckContent('# 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 = []
Mike Frysingerd9ed85f2021-02-03 12:38:24 -0500878 ret = pre_upload._check_ebuild_owners(ProjectNamed('project'), 'HEAD')
Mike Frysingerb04778f2020-11-30 02:41:14 -0500879 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 ]
Mike Frysingerd9ed85f2021-02-03 12:38:24 -0500888 ret = pre_upload._check_ebuild_owners(ProjectNamed('project'), 'HEAD')
Mike Frysingerb04778f2020-11-30 02:41:14 -0500889 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
Mike Frysingerd9ed85f2021-02-03 12:38:24 -0500902 ret = pre_upload._check_ebuild_owners(ProjectNamed('project'), 'HEAD')
Mike Frysingerb04778f2020-11-30 02:41:14 -0500903 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
Mike Frysingerd9ed85f2021-02-03 12:38:24 -0500922 ret = pre_upload._check_ebuild_owners(ProjectNamed('project'), 'HEAD')
Mike Frysingerb04778f2020-11-30 02:41:14 -0500923 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
Mike Frysingerd9ed85f2021-02-03 12:38:24 -0500935 ret = pre_upload._check_ebuild_owners(ProjectNamed('project'), 'HEAD')
Mike Frysingerb04778f2020-11-30 02:41:14 -0500936 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
Mike Frysingerd9ed85f2021-02-03 12:38:24 -0500941 def testCommonPublicBoardOverlays(self):
942 """Allow an OWNERS in the top of the overlay to satisfy requirements."""
943 # The public repo that has all public overlays.
944 project = ProjectNamed('chromiumos/overlays/board-overlays')
945
946 def _get_content(path, _commit):
947 if path == 'overlay-oak/dev-util/pkg/OWNERS':
948 return None
949 elif path == 'overlay-oak/OWNERS':
950 return overlay_owners
951 else:
952 raise AssertionError(f'Unhandled test path: {path}')
953
954 self.content_mock.side_effect = _get_content
955 self.file_mock.return_value = [
956 DiffEntry(src_file='overlay-oak/dev-util/pkg/pkg-0-r1.ebuild',
957 status='A'),
958 ]
959
960 # OWNERS doesn't exist.
961 overlay_owners = None
962 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
963 self.assertIsNotNone(ret)
964
965 # OWNERS exists, but is empty.
966 overlay_owners = ''
967 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
968 self.assertIsNotNone(ret)
969
970 # OWNERS exists, but is too permissive.
971 overlay_owners = '# Everyone is an owner!\n*\n'
972 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
973 self.assertIsNotNone(ret)
974
975 # OWNERS exists, and is good.
976 overlay_owners = 'foo@chromium.org'
977 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
978 self.assertIsNone(ret)
979
980 def testCommonPrivateBoardOverlays(self):
981 """Allow an OWNERS in the top of the overlay to satisfy requirements."""
982 # A private repo that holds one board.
983 project = ProjectNamed('chromeos/overlays/baseboard-boo')
984
985 def _get_content(path, _commit):
986 if path == 'dev-util/pkg/OWNERS':
987 return None
988 elif path == 'OWNERS':
989 return overlay_owners
990 else:
991 raise AssertionError(f'Unhandled test path: {path}')
992
993 self.content_mock.side_effect = _get_content
994 self.file_mock.return_value = [
995 DiffEntry(src_file='dev-util/pkg/pkg-0-r1.ebuild', status='A'),
996 ]
997
998 # OWNERS doesn't exist.
999 overlay_owners = None
1000 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
1001 self.assertIsNotNone(ret)
1002
1003 # OWNERS exists, but is empty.
1004 overlay_owners = ''
1005 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
1006 self.assertIsNotNone(ret)
1007
1008 # OWNERS exists, but is too permissive.
1009 overlay_owners = '# Everyone is an owner!\n*\n'
1010 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
1011 self.assertIsNotNone(ret)
1012
1013 # OWNERS exists, and is good.
1014 overlay_owners = 'foo@chromium.org'
1015 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
1016 self.assertIsNone(ret)
1017
1018 def testSharedOverlays(self):
1019 """Do not allow top-level OWNERS for shared overlays."""
1020 project = ProjectNamed('chromiumos/overlays/portage-stable')
1021
1022 def _get_content(path, _commit):
1023 if path == 'dev-util/pkg/OWNERS':
1024 return None
1025 elif path == 'OWNERS':
1026 return '# Global owners.\nfoo@bar\n'
1027 else:
1028 raise AssertionError(f'Unhandled test path: {path}')
1029
1030 self.content_mock.side_effect = _get_content
1031 self.file_mock.return_value = [
1032 DiffEntry(src_file='dev-util/pkg/pkg-0-r1.ebuild', status='A'),
1033 ]
1034
1035 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
1036 self.assertIsNotNone(ret)
1037
Mike Frysingerb04778f2020-11-30 02:41:14 -05001038
Mike Frysinger6ee76b82020-11-20 01:16:06 -05001039class CheckEbuildR0(PreUploadTestCase):
1040 """Tests for _check_ebuild_r0."""
1041
1042 def setUp(self):
1043 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1044
1045 def testNoMatches(self):
1046 """Handle no matching files."""
1047 self.file_mock.return_value = []
Mike Frysingerd9ed85f2021-02-03 12:38:24 -05001048 ret = pre_upload._check_ebuild_r0(ProjectNamed('project'), 'HEAD')
Mike Frysinger6ee76b82020-11-20 01:16:06 -05001049 self.assertIsNone(ret)
1050
1051 def testBadEbuilds(self):
1052 """Handle matching r0 files."""
1053 self.file_mock.return_value = ['foo-1-r0.ebuild']
Mike Frysingerd9ed85f2021-02-03 12:38:24 -05001054 ret = pre_upload._check_ebuild_r0(ProjectNamed('project'), 'HEAD')
Mike Frysinger6ee76b82020-11-20 01:16:06 -05001055 self.assertIsNotNone(ret)
1056
1057
Mike Frysingerb2496652019-09-12 23:35:46 -04001058class CheckEbuildVirtualPv(PreUploadTestCase):
Mike Frysingercd363c82014-02-01 05:20:18 -05001059 """Tests for _check_ebuild_virtual_pv."""
1060
Alex Deymo643ac4c2015-09-03 10:40:50 -07001061 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
1062 CHROMIUMOS_OVERLAY = ProjectNamed('chromiumos/overlays/chromiumos')
1063 BOARD_OVERLAY = ProjectNamed('chromiumos/overlays/board-overlays')
1064 PRIVATE_OVERLAY = ProjectNamed('chromeos/overlays/overlay-link-private')
1065 PRIVATE_VARIANT_OVERLAY = ProjectNamed('chromeos/overlays/'
1066 'overlay-variant-daisy-spring-private')
Mike Frysingercd363c82014-02-01 05:20:18 -05001067
1068 def setUp(self):
1069 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1070
1071 def testNoVirtuals(self):
1072 """Skip non virtual packages."""
1073 self.file_mock.return_value = ['some/package/package-3.ebuild']
Alex Deymo643ac4c2015-09-03 10:40:50 -07001074 ret = pre_upload._check_ebuild_virtual_pv(ProjectNamed('overlay'), 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001075 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001076
1077 def testCommonVirtuals(self):
1078 """Non-board overlays should use PV=1."""
1079 template = 'virtual/foo/foo-%s.ebuild'
1080 self.file_mock.return_value = [template % '1']
1081 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001082 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001083
1084 self.file_mock.return_value = [template % '2']
1085 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -05001086 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -05001087
1088 def testPublicBoardVirtuals(self):
1089 """Public board overlays should use PV=2."""
1090 template = 'overlay-lumpy/virtual/foo/foo-%s.ebuild'
1091 self.file_mock.return_value = [template % '2']
1092 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001093 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001094
1095 self.file_mock.return_value = [template % '2.5']
1096 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -05001097 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -05001098
1099 def testPublicBoardVariantVirtuals(self):
1100 """Public board variant overlays should use PV=2.5."""
1101 template = 'overlay-variant-lumpy-foo/virtual/foo/foo-%s.ebuild'
1102 self.file_mock.return_value = [template % '2.5']
1103 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001104 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001105
1106 self.file_mock.return_value = [template % '3']
1107 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -05001108 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -05001109
1110 def testPrivateBoardVirtuals(self):
1111 """Private board overlays should use PV=3."""
1112 template = 'virtual/foo/foo-%s.ebuild'
1113 self.file_mock.return_value = [template % '3']
1114 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001115 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001116
1117 self.file_mock.return_value = [template % '3.5']
1118 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -05001119 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -05001120
1121 def testPrivateBoardVariantVirtuals(self):
1122 """Private board variant overlays should use PV=3.5."""
1123 template = 'virtual/foo/foo-%s.ebuild'
1124 self.file_mock.return_value = [template % '3.5']
1125 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001126 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001127
Bernie Thompsone5ee1822016-01-12 14:22:23 -08001128 def testSpecialVirtuals(self):
1129 """Some cases require deeper versioning and can be >= 4."""
1130 template = 'virtual/foo/foo-%s.ebuild'
Mike Frysingercd363c82014-02-01 05:20:18 -05001131 self.file_mock.return_value = [template % '4']
1132 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001133 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001134
Bernie Thompsone5ee1822016-01-12 14:22:23 -08001135 self.file_mock.return_value = [template % '4.5']
1136 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001137 self.assertIsNone(ret)
Mike Frysinger98638102014-08-28 00:15:08 -04001138
Mike Frysingerb2496652019-09-12 23:35:46 -04001139class CheckCrosLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -07001140 """Tests for _check_cros_license."""
Mike Frysinger98638102014-08-28 00:15:08 -04001141
1142 def setUp(self):
1143 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1144 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1145
1146 def testOldHeaders(self):
1147 """Accept old header styles."""
1148 HEADERS = (
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001149 ('#!/bin/sh\n'
1150 '# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.\n'
1151 '# Use of this source code is governed by a BSD-style license that'
1152 ' can be\n'
1153 '# found in the LICENSE file.\n'),
1154 ('// Copyright 2010-2013 The Chromium OS Authors. All rights reserved.'
1155 '\n// Use of this source code is governed by a BSD-style license that'
1156 ' can be\n'
1157 '// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -04001158 )
1159 self.file_mock.return_value = ['file']
1160 for header in HEADERS:
1161 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +09001162 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
1163
1164 def testNewFileYear(self):
1165 """Added files should have the current year in license header."""
1166 year = datetime.datetime.now().year
1167 HEADERS = (
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001168 ('// Copyright 2016 The Chromium OS Authors. All rights reserved.\n'
1169 '// Use of this source code is governed by a BSD-style license that'
1170 ' can be\n'
1171 '// found in the LICENSE file.\n'),
1172 ('// Copyright %d The Chromium OS Authors. All rights reserved.\n'
1173 '// Use of this source code is governed by a BSD-style license that'
1174 ' can be\n'
1175 '// found in the LICENSE file.\n') % year,
Keigo Oka7e880ac2019-07-03 15:03:43 +09001176 )
1177 want_error = (True, False)
1178 def fake_get_affected_files(_, relative, include_adds=True):
1179 _ = relative
1180 if include_adds:
1181 return ['file']
1182 else:
1183 return []
1184
1185 self.file_mock.side_effect = fake_get_affected_files
1186 for i, header in enumerate(HEADERS):
1187 self.content_mock.return_value = header
1188 if want_error[i]:
1189 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
1190 else:
1191 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -04001192
1193 def testRejectC(self):
1194 """Reject the (c) in newer headers."""
1195 HEADERS = (
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001196 ('// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.'
1197 '\n'
1198 '// Use of this source code is governed by a BSD-style license that'
1199 ' can be\n'
1200 '// found in the LICENSE file.\n'),
1201 ('// Copyright (c) 2020 The Chromium OS Authors. All rights reserved.'
1202 '\n'
1203 '// Use of this source code is governed by a BSD-style license that'
1204 ' can be\n'
1205 '// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -04001206 )
1207 self.file_mock.return_value = ['file']
1208 for header in HEADERS:
1209 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +09001210 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001211
Brian Norris68838dd2018-09-26 18:30:24 -07001212 def testNoLeadingSpace(self):
1213 """Allow headers without leading space (e.g., not a source comment)"""
1214 HEADERS = (
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001215 ('Copyright 2018 The Chromium OS Authors. All rights reserved.\n'
1216 'Use of this source code is governed by a BSD-style license that '
1217 'can be\n'
1218 'found in the LICENSE file.\n'),
Brian Norris68838dd2018-09-26 18:30:24 -07001219 )
1220 self.file_mock.return_value = ['file']
1221 for header in HEADERS:
1222 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +09001223 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Brian Norris68838dd2018-09-26 18:30:24 -07001224
Keigo Oka9732e382019-06-28 17:44:59 +09001225 def testNoExcludedGolang(self):
1226 """Don't exclude .go files for license checks."""
1227 self.file_mock.return_value = ['foo/main.go']
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001228 self.content_mock.return_value = 'package main\nfunc main() {}'
Keigo Oka7e880ac2019-07-03 15:03:43 +09001229 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Keigo Oka9732e382019-06-28 17:44:59 +09001230
Ken Turnerd07564b2018-02-08 17:57:59 +11001231 def testIgnoreExcludedPaths(self):
1232 """Ignores excluded paths for license checks."""
1233 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001234 self.content_mock.return_value = 'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +09001235 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +11001236
Tom Hughes90b7bd42020-11-10 10:31:49 -08001237 def testIgnoreMetadataFiles(self):
1238 """Ignores metadata files for license checks."""
1239 self.file_mock.return_value = ['foo/DIR_METADATA']
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001240 self.content_mock.return_value = 'team_email: "team@chromium.org"'
Tom Hughes90b7bd42020-11-10 10:31:49 -08001241 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
1242
Chris McDonald7b63c8e2019-04-25 10:27:27 -06001243 def testIgnoreTopLevelExcludedPaths(self):
1244 """Ignores excluded paths for license checks."""
1245 self.file_mock.return_value = ['OWNERS']
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001246 self.content_mock.return_value = 'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +09001247 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001248
Mike Frysingerb2496652019-09-12 23:35:46 -04001249class CheckAOSPLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -07001250 """Tests for _check_aosp_license."""
1251
1252 def setUp(self):
1253 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1254 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1255
1256 def testHeaders(self):
1257 """Accept old header styles."""
1258 HEADERS = (
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001259 """//
Alex Deymof5792ce2015-08-24 22:50:08 -07001260// Copyright (C) 2011 The Android Open Source Project
1261//
1262// Licensed under the Apache License, Version 2.0 (the "License");
1263// you may not use this file except in compliance with the License.
1264// You may obtain a copy of the License at
1265//
1266// http://www.apache.org/licenses/LICENSE-2.0
1267//
1268// Unless required by applicable law or agreed to in writing, software
1269// distributed under the License is distributed on an "AS IS" BASIS,
1270// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1271// See the License for the specific language governing permissions and
1272// limitations under the License.
1273//
1274""",
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001275 """#
Alex Deymof5792ce2015-08-24 22:50:08 -07001276# Copyright (c) 2015 The Android Open Source Project
1277#
1278# Licensed under the Apache License, Version 2.0 (the "License");
1279# you may not use this file except in compliance with the License.
1280# You may obtain a copy of the License at
1281#
1282# http://www.apache.org/licenses/LICENSE-2.0
1283#
1284# Unless required by applicable law or agreed to in writing, software
1285# distributed under the License is distributed on an "AS IS" BASIS,
1286# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1287# See the License for the specific language governing permissions and
1288# limitations under the License.
1289#
1290""",
1291 )
1292 self.file_mock.return_value = ['file']
1293 for header in HEADERS:
1294 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001295 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001296
1297 def testRejectNoLinesAround(self):
1298 """Reject headers missing the empty lines before/after the license."""
1299 HEADERS = (
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001300 """# Copyright (c) 2015 The Android Open Source Project
Alex Deymof5792ce2015-08-24 22:50:08 -07001301#
1302# Licensed under the Apache License, Version 2.0 (the "License");
1303# you may not use this file except in compliance with the License.
1304# You may obtain a copy of the License at
1305#
1306# http://www.apache.org/licenses/LICENSE-2.0
1307#
1308# Unless required by applicable law or agreed to in writing, software
1309# distributed under the License is distributed on an "AS IS" BASIS,
1310# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1311# See the License for the specific language governing permissions and
1312# limitations under the License.
1313""",
1314 )
1315 self.file_mock.return_value = ['file']
1316 for header in HEADERS:
1317 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001318 self.assertIsNotNone(pre_upload._check_aosp_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -04001319
Ken Turnerd07564b2018-02-08 17:57:59 +11001320 def testIgnoreExcludedPaths(self):
1321 """Ignores excluded paths for license checks."""
1322 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001323 self.content_mock.return_value = 'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001324 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +11001325
Chris McDonald7b63c8e2019-04-25 10:27:27 -06001326 def testIgnoreTopLevelExcludedPaths(self):
1327 """Ignores excluded paths for license checks."""
1328 self.file_mock.return_value = ['OWNERS']
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001329 self.content_mock.return_value = 'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001330 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
1331
Mike Frysinger98638102014-08-28 00:15:08 -04001332
Mike Frysingerb2496652019-09-12 23:35:46 -04001333class CheckLayoutConfTestCase(PreUploadTestCase):
Mike Frysingerd7734522015-02-26 16:12:43 -05001334 """Tests for _check_layout_conf."""
1335
1336 def setUp(self):
1337 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1338 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1339
Mike Frysingerd9ed85f2021-02-03 12:38:24 -05001340 def assertAccepted(self, files, project=ProjectNamed('project'),
1341 commit='fake sha1'):
Mike Frysingerd7734522015-02-26 16:12:43 -05001342 """Assert _check_layout_conf accepts |files|."""
1343 self.file_mock.return_value = files
1344 ret = pre_upload._check_layout_conf(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001345 self.assertIsNone(ret, msg='rejected with:\n%s' % ret)
Mike Frysingerd7734522015-02-26 16:12:43 -05001346
Mike Frysingerd9ed85f2021-02-03 12:38:24 -05001347 def assertRejected(self, files, project=ProjectNamed('project'),
1348 commit='fake sha1'):
Mike Frysingerd7734522015-02-26 16:12:43 -05001349 """Assert _check_layout_conf rejects |files|."""
1350 self.file_mock.return_value = files
1351 ret = pre_upload._check_layout_conf(project, commit)
1352 self.assertTrue(isinstance(ret, errors.HookFailure))
1353
1354 def GetLayoutConf(self, filters=()):
1355 """Return a valid layout.conf with |filters| lines removed."""
1356 all_lines = [
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001357 'masters = portage-stable chromiumos',
1358 'profile-formats = portage-2 profile-default-eapi',
1359 'profile_eapi_when_unspecified = 5-progress',
1360 'repo-name = link',
1361 'thin-manifests = true',
1362 'use-manifests = strict',
Mike Frysingerd7734522015-02-26 16:12:43 -05001363 ]
1364
1365 lines = []
1366 for line in all_lines:
1367 for filt in filters:
1368 if line.startswith(filt):
1369 break
1370 else:
1371 lines.append(line)
1372
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001373 return '\n'.join(lines)
Mike Frysingerd7734522015-02-26 16:12:43 -05001374
1375 def testNoFilesToCheck(self):
1376 """Don't blow up when there are no layout.conf files."""
1377 self.assertAccepted([])
1378
1379 def testRejectRepoNameFile(self):
1380 """If profiles/repo_name is set, kick it out."""
1381 self.assertRejected(['profiles/repo_name'])
1382
1383 def testAcceptValidLayoutConf(self):
1384 """Accept a fully valid layout.conf."""
1385 self.content_mock.return_value = self.GetLayoutConf()
1386 self.assertAccepted(['metadata/layout.conf'])
1387
1388 def testAcceptUnknownKeys(self):
1389 """Accept keys we don't explicitly know about."""
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001390 self.content_mock.return_value = self.GetLayoutConf() + '\nzzz-top = ok'
Mike Frysingerd7734522015-02-26 16:12:43 -05001391 self.assertAccepted(['metadata/layout.conf'])
1392
1393 def testRejectUnsorted(self):
1394 """Reject an unsorted layout.conf."""
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001395 self.content_mock.return_value = 'zzz-top = bad\n' + self.GetLayoutConf()
Mike Frysingerd7734522015-02-26 16:12:43 -05001396 self.assertRejected(['metadata/layout.conf'])
1397
1398 def testRejectMissingThinManifests(self):
1399 """Reject a layout.conf missing thin-manifests."""
1400 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001401 filters=['thin-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001402 self.assertRejected(['metadata/layout.conf'])
1403
1404 def testRejectMissingUseManifests(self):
1405 """Reject a layout.conf missing use-manifests."""
1406 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001407 filters=['use-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001408 self.assertRejected(['metadata/layout.conf'])
1409
1410 def testRejectMissingEapiFallback(self):
1411 """Reject a layout.conf missing profile_eapi_when_unspecified."""
1412 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001413 filters=['profile_eapi_when_unspecified'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001414 self.assertRejected(['metadata/layout.conf'])
1415
1416 def testRejectMissingRepoName(self):
1417 """Reject a layout.conf missing repo-name."""
Mike Frysinger1c05e5f2021-03-23 17:14:44 -04001418 self.content_mock.return_value = self.GetLayoutConf(filters=['repo-name'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001419 self.assertRejected(['metadata/layout.conf'])
1420
1421
Mike Frysingerb2496652019-09-12 23:35:46 -04001422class CommitMessageTestCase(PreUploadTestCase):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001423 """Test case for funcs that check commit messages."""
1424
1425 def setUp(self):
1426 self.msg_mock = self.PatchObject(pre_upload, '_get_commit_desc')
1427
1428 @staticmethod
1429 def CheckMessage(_project, _commit):
1430 raise AssertionError('Test class must declare CheckMessage')
1431 # This dummy return is to silence pylint warning W1111 so we don't have to
1432 # enable it for all the call sites below.
1433 return 1 # pylint: disable=W0101
1434
Alex Deymo643ac4c2015-09-03 10:40:50 -07001435 def assertMessageAccepted(self, msg, project=ProjectNamed('project'),
1436 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001437 """Assert _check_change_has_bug_field accepts |msg|."""
1438 self.msg_mock.return_value = msg
1439 ret = self.CheckMessage(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001440 self.assertIsNone(ret)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001441
Alex Deymo643ac4c2015-09-03 10:40:50 -07001442 def assertMessageRejected(self, msg, project=ProjectNamed('project'),
1443 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001444 """Assert _check_change_has_bug_field rejects |msg|."""
1445 self.msg_mock.return_value = msg
1446 ret = self.CheckMessage(project, commit)
1447 self.assertTrue(isinstance(ret, errors.HookFailure))
1448
1449
1450class CheckCommitMessageBug(CommitMessageTestCase):
1451 """Tests for _check_change_has_bug_field."""
1452
Alex Deymo643ac4c2015-09-03 10:40:50 -07001453 AOSP_PROJECT = pre_upload.Project(name='overlay', dir='', remote='aosp')
1454 CROS_PROJECT = pre_upload.Project(name='overlay', dir='', remote='cros')
1455
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001456 @staticmethod
1457 def CheckMessage(project, commit):
1458 return pre_upload._check_change_has_bug_field(project, commit)
1459
1460 def testNormal(self):
1461 """Accept a commit message w/a valid BUG."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001462 self.assertMessageAccepted('\nBUG=chromium:1234\n', self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001463 self.assertMessageAccepted('\nBUG=b:1234\n', self.CROS_PROJECT)
1464
1465 self.assertMessageAccepted('\nBug: 1234\n', self.AOSP_PROJECT)
1466 self.assertMessageAccepted('\nBug:1234\n', self.AOSP_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001467
1468 def testNone(self):
1469 """Accept BUG=None."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001470 self.assertMessageAccepted('\nBUG=None\n', self.CROS_PROJECT)
1471 self.assertMessageAccepted('\nBUG=none\n', self.CROS_PROJECT)
1472 self.assertMessageAccepted('\nBug: None\n', self.AOSP_PROJECT)
1473 self.assertMessageAccepted('\nBug:none\n', self.AOSP_PROJECT)
1474
1475 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1476 self.assertMessageRejected('\nBUG=NONE\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001477
1478 def testBlank(self):
1479 """Reject blank values."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001480 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1481 self.assertMessageRejected('\nBUG=\n', project)
1482 self.assertMessageRejected('\nBUG= \n', project)
1483 self.assertMessageRejected('\nBug:\n', project)
1484 self.assertMessageRejected('\nBug: \n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001485
1486 def testNotFirstLine(self):
1487 """Reject the first line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001488 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1489 self.assertMessageRejected('BUG=None\n\n\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001490
1491 def testNotInline(self):
1492 """Reject not at the start of line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001493 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1494 self.assertMessageRejected('\n BUG=None\n', project)
1495 self.assertMessageRejected('\n\tBUG=None\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001496
1497 def testOldTrackers(self):
1498 """Reject commit messages using old trackers."""
Mike Frysingera2f28252017-10-27 22:26:14 -04001499 self.assertMessageRejected('\nBUG=chrome-os-partner:1234\n',
1500 self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001501 self.assertMessageRejected('\nBUG=chromium-os:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001502
1503 def testNoTrackers(self):
1504 """Reject commit messages w/invalid trackers."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001505 self.assertMessageRejected('\nBUG=booga:1234\n', self.CROS_PROJECT)
1506 self.assertMessageRejected('\nBUG=br:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001507
1508 def testMissing(self):
1509 """Reject commit messages w/no BUG line."""
1510 self.assertMessageRejected('foo\n')
1511
1512 def testCase(self):
1513 """Reject bug lines that are not BUG."""
1514 self.assertMessageRejected('\nbug=none\n')
1515
Cheng Yuehb707c522020-01-02 14:06:59 +08001516 def testNotAfterTest(self):
1517 """Reject any TEST line before any BUG line."""
Cheng Yuehb707c522020-01-02 14:06:59 +08001518 middle_field = 'A random between line\n'
Allen Webb00c74ba2021-04-14 10:20:56 -05001519 for project, bug_field, test_field in (
1520 (self.AOSP_PROJECT, 'Bug:none\n', 'Test: i did not do it\n'),
1521 (self.CROS_PROJECT, 'BUG=None\n', 'TEST=i did not do it\n')):
Cheng Yuehb707c522020-01-02 14:06:59 +08001522 self.assertMessageRejected(
1523 '\n' + test_field + middle_field + bug_field, project)
1524 self.assertMessageRejected(
1525 '\n' + test_field + bug_field, project)
1526
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001527
1528class CheckCommitMessageCqDepend(CommitMessageTestCase):
1529 """Tests for _check_change_has_valid_cq_depend."""
1530
1531 @staticmethod
1532 def CheckMessage(project, commit):
1533 return pre_upload._check_change_has_valid_cq_depend(project, commit)
1534
1535 def testNormal(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001536 """Accept valid Cq-Depends line."""
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001537 self.assertMessageAccepted('\nCq-Depend: chromium:1234\nChange-Id: I123')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001538
1539 def testInvalid(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001540 """Reject invalid Cq-Depends line."""
1541 self.assertMessageRejected('\nCq-Depend=chromium=1234\n')
1542 self.assertMessageRejected('\nCq-Depend: None\n')
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001543 self.assertMessageRejected('\nCq-Depend: chromium:1234\n\nChange-Id: I123')
Mike Frysingere39d0cd2019-11-25 13:30:06 -05001544 self.assertMessageRejected('\nCQ-DEPEND=1234\n')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001545
1546
Bernie Thompsonf8fea992016-01-14 10:27:18 -08001547class CheckCommitMessageContribution(CommitMessageTestCase):
1548 """Tests for _check_change_is_contribution."""
1549
1550 @staticmethod
1551 def CheckMessage(project, commit):
1552 return pre_upload._check_change_is_contribution(project, commit)
1553
1554 def testNormal(self):
1555 """Accept a commit message which is a contribution."""
1556 self.assertMessageAccepted('\nThis is cool code I am contributing.\n')
1557
1558 def testFailureLowerCase(self):
1559 """Deny a commit message which is not a contribution."""
1560 self.assertMessageRejected('\nThis is not a contribution.\n')
1561
1562 def testFailureUpperCase(self):
1563 """Deny a commit message which is not a contribution."""
1564 self.assertMessageRejected('\nNOT A CONTRIBUTION\n')
1565
1566
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001567class CheckCommitMessageTest(CommitMessageTestCase):
1568 """Tests for _check_change_has_test_field."""
1569
1570 @staticmethod
1571 def CheckMessage(project, commit):
1572 return pre_upload._check_change_has_test_field(project, commit)
1573
1574 def testNormal(self):
1575 """Accept a commit message w/a valid TEST."""
1576 self.assertMessageAccepted('\nTEST=i did it\n')
1577
1578 def testNone(self):
1579 """Accept TEST=None."""
1580 self.assertMessageAccepted('\nTEST=None\n')
1581 self.assertMessageAccepted('\nTEST=none\n')
1582
1583 def testBlank(self):
1584 """Reject blank values."""
1585 self.assertMessageRejected('\nTEST=\n')
1586 self.assertMessageRejected('\nTEST= \n')
1587
1588 def testNotFirstLine(self):
1589 """Reject the first line."""
1590 self.assertMessageRejected('TEST=None\n\n\n')
1591
1592 def testNotInline(self):
1593 """Reject not at the start of line."""
1594 self.assertMessageRejected('\n TEST=None\n')
1595 self.assertMessageRejected('\n\tTEST=None\n')
1596
1597 def testMissing(self):
1598 """Reject commit messages w/no TEST line."""
1599 self.assertMessageRejected('foo\n')
1600
1601 def testCase(self):
1602 """Reject bug lines that are not TEST."""
1603 self.assertMessageRejected('\ntest=none\n')
1604
1605
1606class CheckCommitMessageChangeId(CommitMessageTestCase):
1607 """Tests for _check_change_has_proper_changeid."""
1608
1609 @staticmethod
1610 def CheckMessage(project, commit):
1611 return pre_upload._check_change_has_proper_changeid(project, commit)
1612
1613 def testNormal(self):
1614 """Accept a commit message w/a valid Change-Id."""
1615 self.assertMessageAccepted('foo\n\nChange-Id: I1234\n')
1616
1617 def testBlank(self):
1618 """Reject blank values."""
1619 self.assertMessageRejected('\nChange-Id:\n')
1620 self.assertMessageRejected('\nChange-Id: \n')
1621
1622 def testNotFirstLine(self):
1623 """Reject the first line."""
1624 self.assertMessageRejected('TEST=None\n\n\n')
1625
1626 def testNotInline(self):
1627 """Reject not at the start of line."""
1628 self.assertMessageRejected('\n Change-Id: I1234\n')
1629 self.assertMessageRejected('\n\tChange-Id: I1234\n')
1630
1631 def testMissing(self):
1632 """Reject commit messages missing the line."""
1633 self.assertMessageRejected('foo\n')
1634
1635 def testCase(self):
1636 """Reject bug lines that are not Change-Id."""
1637 self.assertMessageRejected('\nchange-id: I1234\n')
1638 self.assertMessageRejected('\nChange-id: I1234\n')
1639 self.assertMessageRejected('\nChange-ID: I1234\n')
1640
1641 def testEnd(self):
1642 """Reject Change-Id's that are not last."""
1643 self.assertMessageRejected('\nChange-Id: I1234\nbar\n')
1644
Mike Frysinger02b88bd2014-11-21 00:29:38 -05001645 def testSobTag(self):
1646 """Permit s-o-b tags to follow the Change-Id."""
1647 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nSigned-off-by: Hi\n')
1648
LaMont Jones237f3ef2020-01-22 10:40:52 -07001649 def testCqClTag(self):
1650 """Permit Cq-Cl-Tag tags to follow the Change-Id."""
1651 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nCq-Cl-Tag: Hi\n')
1652
LaMont Jonesfb5e8bf2020-03-03 12:50:06 -07001653 def testCqIncludeTrybotsTag(self):
1654 """Permit Cq-Include-Trybots tags to follow the Change-Id."""
1655 self.assertMessageAccepted(
1656 'foo\n\nChange-Id: I1234\nCq-Include-Trybots: chromeos/cq:foo\n')
1657
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001658
Jack Neus8edbf642019-07-10 16:08:31 -06001659class CheckCommitMessageNoOEM(CommitMessageTestCase):
1660 """Tests for _check_change_no_include_oem."""
1661
1662 @staticmethod
1663 def CheckMessage(project, commit):
1664 return pre_upload._check_change_no_include_oem(project, commit)
1665
1666 def testNormal(self):
1667 """Accept a commit message w/o reference to an OEM/ODM."""
1668 self.assertMessageAccepted('foo\n')
1669
1670 def testHasOEM(self):
1671 """Catch commit messages referencing OEMs."""
1672 self.assertMessageRejected('HP Project\n\n')
1673 self.assertMessageRejected('hewlett-packard\n')
1674 self.assertMessageRejected('Hewlett\nPackard\n')
1675 self.assertMessageRejected('Dell Chromebook\n\n\n')
1676 self.assertMessageRejected('product@acer.com\n')
1677 self.assertMessageRejected('This is related to Asus\n')
1678 self.assertMessageRejected('lenovo machine\n')
1679
1680 def testHasODM(self):
1681 """Catch commit messages referencing ODMs."""
1682 self.assertMessageRejected('new samsung laptop\n\n')
1683 self.assertMessageRejected('pegatron(ems) project\n')
1684 self.assertMessageRejected('new Wistron device\n')
1685
1686 def testContainsOEM(self):
1687 """Check that the check handles word boundaries properly."""
1688 self.assertMessageAccepted('oheahpohea')
1689 self.assertMessageAccepted('Play an Asus7 guitar chord.\n\n')
1690
1691 def testTag(self):
1692 """Check that the check ignores tags."""
1693 self.assertMessageAccepted(
1694 'Harmless project\n'
1695 'Reviewed-by: partner@asus.corp-partner.google.com\n'
1696 'Tested-by: partner@hp.corp-partner.google.com\n'
1697 'Signed-off-by: partner@dell.corp-partner.google.com\n'
1698 'Commit-Queue: partner@lenovo.corp-partner.google.com\n'
Jack Neus8edbf642019-07-10 16:08:31 -06001699 'CC: partner@acer.corp-partner.google.com\n'
1700 'Acked-by: partner@hewlett-packard.corp-partner.google.com\n')
1701 self.assertMessageRejected(
1702 'Asus project\n'
1703 'Reviewed-by: partner@asus.corp-partner.google.com')
Mike Frysingerbb34a222019-07-31 14:40:46 -04001704 self.assertMessageRejected(
1705 'my project\n'
1706 'Bad-tag: partner@asus.corp-partner.google.com')
Jack Neus8edbf642019-07-10 16:08:31 -06001707
1708
Mike Frysinger36b2ebc2014-10-31 14:02:03 -04001709class CheckCommitMessageStyle(CommitMessageTestCase):
1710 """Tests for _check_commit_message_style."""
1711
1712 @staticmethod
1713 def CheckMessage(project, commit):
1714 return pre_upload._check_commit_message_style(project, commit)
1715
1716 def testNormal(self):
1717 """Accept valid commit messages."""
1718 self.assertMessageAccepted('one sentence.\n')
1719 self.assertMessageAccepted('some.module: do it!\n')
1720 self.assertMessageAccepted('one line\n\nmore stuff here.')
1721
1722 def testNoBlankSecondLine(self):
1723 """Reject messages that have stuff on the second line."""
1724 self.assertMessageRejected('one sentence.\nbad fish!\n')
1725
1726 def testFirstLineMultipleSentences(self):
1727 """Reject messages that have more than one sentence in the summary."""
1728 self.assertMessageRejected('one sentence. two sentence!\n')
1729
1730 def testFirstLineTooLone(self):
1731 """Reject first lines that are too long."""
1732 self.assertMessageRejected('o' * 200)
1733
Mike Frysingerd2451822021-02-17 13:52:19 -05001734 def testGitKeywords(self):
1735 """Reject git special keywords."""
1736 BAD_MESSAGES = (
1737 'fixup!',
1738 'fixup! that old commit',
1739 'squash!',
1740 'squash! that old commit',
1741 )
1742 GOOD_MESSAGES = (
1743 'this change needs a fixup!',
1744 'you should eat your squash!',
1745 )
1746 for msg in BAD_MESSAGES:
1747 self.assertMessageRejected(msg)
1748 for msg in GOOD_MESSAGES:
1749 self.assertMessageAccepted(msg)
1750
Mike Frysinger36b2ebc2014-10-31 14:02:03 -04001751
Mike Frysinger292b45d2014-11-25 01:17:10 -05001752def DiffEntry(src_file=None, dst_file=None, src_mode=None, dst_mode='100644',
1753 status='M'):
1754 """Helper to create a stub RawDiffEntry object"""
1755 if src_mode is None:
1756 if status == 'A':
1757 src_mode = '000000'
1758 elif status == 'M':
1759 src_mode = dst_mode
1760 elif status == 'D':
1761 src_mode = dst_mode
1762 dst_mode = '000000'
1763
1764 src_sha = dst_sha = 'abc'
1765 if status == 'D':
1766 dst_sha = '000000'
1767 elif status == 'A':
1768 src_sha = '000000'
1769
1770 return git.RawDiffEntry(src_mode=src_mode, dst_mode=dst_mode, src_sha=src_sha,
1771 dst_sha=dst_sha, status=status, score=None,
1772 src_file=src_file, dst_file=dst_file)
1773
1774
Mike Frysingerb2496652019-09-12 23:35:46 -04001775class HelpersTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001776 """Various tests for utility functions."""
1777
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001778 def setUp(self):
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001779 os.chdir(self.tempdir)
1780
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001781 self.PatchObject(git, 'RawDiff', return_value=[
1782 # A modified normal file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001783 DiffEntry(src_file='buildbot/constants.py', status='M'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001784 # A new symlink file.
Bob Haarman0dc1f942020-10-03 00:06:59 +00001785 DiffEntry(dst_file='scripts/cros_env_allowlist', dst_mode='120000',
Mike Frysinger292b45d2014-11-25 01:17:10 -05001786 status='A'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001787 # A deleted file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001788 DiffEntry(src_file='scripts/sync_sonic.py', status='D'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001789 ])
1790
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001791 def _WritePresubmitIgnoreFile(self, subdir, data):
1792 """Writes to a .presubmitignore file in the passed-in subdirectory."""
1793 directory = os.path.join(self.tempdir, subdir)
1794 if not os.path.exists(directory):
1795 os.makedirs(directory)
1796 osutils.WriteFile(os.path.join(directory, pre_upload._IGNORE_FILE), data)
1797
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001798 def testGetAffectedFilesNoDeletesNoRelative(self):
1799 """Verify _get_affected_files() works w/no delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001800 path = os.getcwd()
1801 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1802 relative=False)
1803 exp_files = [os.path.join(path, 'buildbot/constants.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001804 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001805
1806 def testGetAffectedFilesDeletesNoRelative(self):
1807 """Verify _get_affected_files() works w/delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001808 path = os.getcwd()
1809 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1810 relative=False)
1811 exp_files = [os.path.join(path, 'buildbot/constants.py'),
1812 os.path.join(path, 'scripts/sync_sonic.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001813 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001814
1815 def testGetAffectedFilesNoDeletesRelative(self):
1816 """Verify _get_affected_files() works w/no delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001817 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1818 relative=True)
1819 exp_files = ['buildbot/constants.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001820 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001821
1822 def testGetAffectedFilesDeletesRelative(self):
1823 """Verify _get_affected_files() works w/delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001824 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1825 relative=True)
1826 exp_files = ['buildbot/constants.py', 'scripts/sync_sonic.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001827 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001828
Mike Frysinger292b45d2014-11-25 01:17:10 -05001829 def testGetAffectedFilesDetails(self):
1830 """Verify _get_affected_files() works w/full_details."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001831 files = pre_upload._get_affected_files('HEAD', full_details=True,
1832 relative=True)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001833 self.assertEqual(files[0].src_file, 'buildbot/constants.py')
Mike Frysinger292b45d2014-11-25 01:17:10 -05001834
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001835 def testGetAffectedFilesPresubmitIgnoreDirectory(self):
1836 """Verify .presubmitignore can be used to exclude a directory."""
1837 self._WritePresubmitIgnoreFile('.', 'buildbot/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001838 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001839
1840 def testGetAffectedFilesPresubmitIgnoreDirectoryWildcard(self):
1841 """Verify .presubmitignore can be used with a directory wildcard."""
1842 self._WritePresubmitIgnoreFile('.', '*/constants.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001843 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001844
1845 def testGetAffectedFilesPresubmitIgnoreWithinDirectory(self):
1846 """Verify .presubmitignore can be placed in a subdirectory."""
1847 self._WritePresubmitIgnoreFile('buildbot', '*.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001848 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001849
1850 def testGetAffectedFilesPresubmitIgnoreDoesntMatch(self):
1851 """Verify .presubmitignore has no effect when it doesn't match a file."""
1852 self._WritePresubmitIgnoreFile('buildbot', '*.txt')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001853 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True),
1854 ['buildbot/constants.py'])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001855
1856 def testGetAffectedFilesPresubmitIgnoreAddedFile(self):
1857 """Verify .presubmitignore matches added files."""
1858 self._WritePresubmitIgnoreFile('.', 'buildbot/\nscripts/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001859 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True,
1860 include_symlinks=True),
1861 [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001862
1863 def testGetAffectedFilesPresubmitIgnoreSkipIgnoreFile(self):
1864 """Verify .presubmitignore files are automatically skipped."""
1865 self.PatchObject(git, 'RawDiff', return_value=[
1866 DiffEntry(src_file='.presubmitignore', status='M')
1867 ])
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001868 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Mike Frysinger292b45d2014-11-25 01:17:10 -05001869
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001870
Mike Frysingerb2496652019-09-12 23:35:46 -04001871class ExecFilesTest(PreUploadTestCase):
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001872 """Tests for _check_exec_files."""
1873
1874 def setUp(self):
1875 self.diff_mock = self.PatchObject(git, 'RawDiff')
1876
1877 def testNotExec(self):
1878 """Do not flag files that are not executable."""
1879 self.diff_mock.return_value = [
1880 DiffEntry(src_file='make.conf', dst_mode='100644', status='A'),
1881 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001882 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001883
1884 def testExec(self):
1885 """Flag files that are executable."""
1886 self.diff_mock.return_value = [
1887 DiffEntry(src_file='make.conf', dst_mode='100755', status='A'),
1888 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001889 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001890
1891 def testDeletedExec(self):
1892 """Ignore bad files that are being deleted."""
1893 self.diff_mock.return_value = [
1894 DiffEntry(src_file='make.conf', dst_mode='100755', status='D'),
1895 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001896 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001897
1898 def testModifiedExec(self):
1899 """Flag bad files that weren't exec, but now are."""
1900 self.diff_mock.return_value = [
1901 DiffEntry(src_file='make.conf', src_mode='100644', dst_mode='100755',
1902 status='M'),
1903 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001904 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001905
1906 def testNormalExec(self):
1907 """Don't flag normal files (e.g. scripts) that are executable."""
1908 self.diff_mock.return_value = [
1909 DiffEntry(src_file='foo.sh', dst_mode='100755', status='A'),
1910 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001911 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001912
1913
Mike Frysingerb2496652019-09-12 23:35:46 -04001914class CheckForUprev(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger292b45d2014-11-25 01:17:10 -05001915 """Tests for _check_for_uprev."""
1916
1917 def setUp(self):
1918 self.file_mock = self.PatchObject(git, 'RawDiff')
1919
1920 def _Files(self, files):
1921 """Create |files| in the tempdir and return full paths to them."""
1922 for obj in files:
1923 if obj.status == 'D':
1924 continue
1925 if obj.dst_file is None:
1926 f = obj.src_file
1927 else:
1928 f = obj.dst_file
1929 osutils.Touch(os.path.join(self.tempdir, f), makedirs=True)
1930 return files
1931
1932 def assertAccepted(self, files, project='project', commit='fake sha1'):
1933 """Assert _check_for_uprev accepts |files|."""
1934 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001935 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1936 project_top=self.tempdir)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001937 self.assertIsNone(ret)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001938
1939 def assertRejected(self, files, project='project', commit='fake sha1'):
1940 """Assert _check_for_uprev rejects |files|."""
1941 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001942 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1943 project_top=self.tempdir)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001944 self.assertTrue(isinstance(ret, errors.HookFailure))
1945
Bob Haarman0dc1f942020-10-03 00:06:59 +00001946 def testAllowlistOverlay(self):
1947 """Skip checks on allowed overlays."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001948 self.assertAccepted([DiffEntry(src_file='cat/pkg/pkg-0.ebuild')],
1949 project='chromiumos/overlays/portage-stable')
1950
Bob Haarman0dc1f942020-10-03 00:06:59 +00001951 def testAllowlistFiles(self):
1952 """Skip checks on allowed files."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001953 files = ['ChangeLog', 'Manifest', 'metadata.xml']
1954 self.assertAccepted([DiffEntry(src_file=os.path.join('c', 'p', x),
1955 status='M')
1956 for x in files])
1957
1958 def testRejectBasic(self):
1959 """Reject ebuilds missing uprevs."""
1960 self.assertRejected([DiffEntry(src_file='c/p/p-0.ebuild', status='M')])
1961
1962 def testNewPackage(self):
1963 """Accept new ebuilds w/out uprevs."""
1964 self.assertAccepted([DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1965 self.assertAccepted([DiffEntry(src_file='c/p/p-0-r12.ebuild', status='A')])
1966
1967 def testModifiedFilesOnly(self):
1968 """Reject ebuilds w/out uprevs and changes in files/."""
1969 osutils.Touch(os.path.join(self.tempdir, 'cat/pkg/pkg-0.ebuild'),
1970 makedirs=True)
1971 self.assertRejected([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1972 self.assertRejected([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1973
1974 def testFilesNoEbuilds(self):
1975 """Ignore changes to paths w/out ebuilds."""
1976 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1977 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1978
1979 def testModifiedFilesWithUprev(self):
1980 """Accept ebuilds w/uprevs and changes in files/."""
1981 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='A'),
1982 DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1983 self.assertAccepted([
1984 DiffEntry(src_file='c/p/files/f', status='M'),
1985 DiffEntry(src_file='c/p/p-0-r1.ebuild', src_mode='120000',
1986 dst_file='c/p/p-0-r2.ebuild', dst_mode='120000', status='R')])
1987
Gwendal Grignoua3086c32014-12-09 11:17:22 -08001988 def testModifiedFilesWith9999(self):
1989 """Accept 9999 ebuilds and changes in files/."""
1990 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M'),
1991 DiffEntry(src_file='c/p/p-9999.ebuild', status='M')])
1992
C Shapiroae157ae2017-09-18 16:24:03 -06001993 def testModifiedFilesIn9999SubDirWithout9999Change(self):
1994 """Accept changes in files/ with a parent 9999 ebuild"""
1995 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
1996 os.makedirs(os.path.dirname(ebuild_9999_file))
1997 osutils.WriteFile(ebuild_9999_file, 'fake')
1998 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M')])
1999
Stephen Boyd6bf5ea82020-10-15 00:02:07 -07002000 def testModifiedFilesAndProfilesWith9999(self):
2001 """Accept changes in files/ with a parent 9999 ebuild and profile change"""
2002 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
2003 os.makedirs(os.path.dirname(ebuild_9999_file))
2004 osutils.WriteFile(ebuild_9999_file, 'fake')
2005 self.assertAccepted([
2006 DiffEntry(src_file='c/p/files/f', status='M'),
2007 DiffEntry(src_file='c/profiles/base/make.defaults', status='M')])
2008
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05002009
Mike Frysingerb2496652019-09-12 23:35:46 -04002010class DirectMainTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger55f85b52014-12-18 14:45:21 -05002011 """Tests for direct_main()"""
2012
2013 def setUp(self):
2014 self.hooks_mock = self.PatchObject(pre_upload, '_run_project_hooks',
2015 return_value=None)
2016
2017 def testNoArgs(self):
2018 """If run w/no args, should check the current dir."""
2019 ret = pre_upload.direct_main([])
2020 self.assertEqual(ret, 0)
2021 self.hooks_mock.assert_called_once_with(
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07002022 mock.ANY, proj_dir=os.getcwd(), commit_list=[], presubmit=mock.ANY,
2023 config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05002024
2025 def testExplicitDir(self):
2026 """Verify we can run on a diff dir."""
2027 # Use the chromite dir since we know it exists.
2028 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR])
2029 self.assertEqual(ret, 0)
2030 self.hooks_mock.assert_called_once_with(
2031 mock.ANY, proj_dir=constants.CHROMITE_DIR, commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07002032 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05002033
2034 def testBogusProject(self):
2035 """A bogus project name should be fine (use default settings)."""
2036 # Use the chromite dir since we know it exists.
2037 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR,
2038 '--project', 'foooooooooo'])
2039 self.assertEqual(ret, 0)
2040 self.hooks_mock.assert_called_once_with(
2041 'foooooooooo', proj_dir=constants.CHROMITE_DIR, commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07002042 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05002043
2044 def testBogustProjectNoDir(self):
2045 """Make sure --dir is detected even with --project."""
2046 ret = pre_upload.direct_main(['--project', 'foooooooooo'])
2047 self.assertEqual(ret, 0)
2048 self.hooks_mock.assert_called_once_with(
Mike Frysinger9ba001a2020-11-20 01:02:11 -05002049 'foooooooooo', proj_dir=os.path.dirname(os.path.abspath(__file__)),
2050 commit_list=[], presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05002051
2052 def testNoGitDir(self):
2053 """We should die when run on a non-git dir."""
2054 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
2055 ['--dir', self.tempdir])
2056
2057 def testNoDir(self):
2058 """We should die when run on a missing dir."""
2059 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
2060 ['--dir', os.path.join(self.tempdir, 'foooooooo')])
2061
2062 def testCommitList(self):
2063 """Any args on the command line should be treated as commits."""
2064 commits = ['sha1', 'sha2', 'shaaaaaaaaaaaan']
2065 ret = pre_upload.direct_main(commits)
2066 self.assertEqual(ret, 0)
2067 self.hooks_mock.assert_called_once_with(
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07002068 mock.ANY, proj_dir=mock.ANY, commit_list=commits, presubmit=mock.ANY,
2069 config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05002070
2071
Mike Frysingerb2496652019-09-12 23:35:46 -04002072class CheckRustfmtTest(PreUploadTestCase):
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06002073 """Tests for _check_rustfmt."""
2074
2075 def setUp(self):
2076 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
2077
2078 def testBadRustFile(self):
2079 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
2080 # Bad because it's missing trailing newline.
Mike Frysingere85b0062019-08-20 15:10:33 -04002081 content = 'fn main() {}'
2082 self.content_mock.return_value = content
2083 self.PatchObject(pre_upload, '_run_command', return_value=content + '\n')
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06002084 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
2085 self.assertIsNotNone(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04002086 self.assertEqual('Files not formatted with rustfmt: '
2087 "(run 'cargo fmt' to fix)",
2088 failure.msg)
2089 self.assertEqual(['a.rs'], failure.items)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06002090
2091 def testGoodRustFile(self):
2092 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
Mike Frysingere85b0062019-08-20 15:10:33 -04002093 content = 'fn main() {}\n'
2094 self.content_mock.return_value = content
2095 self.PatchObject(pre_upload, '_run_command', return_value=content)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06002096 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
2097 self.assertIsNone(failure)
2098
2099 def testFilterNonRustFiles(self):
2100 self.PatchObject(pre_upload, '_get_affected_files',
2101 return_value=['a.cc', 'a.rsa', 'a.irs', 'rs.cc'])
2102 self.content_mock.return_value = 'fn main() {\n}'
2103 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
2104 self.assertIsNone(failure)
2105
2106
Keiichi Watanabe6ab73fd2020-07-14 23:42:02 +09002107class GetCargoClippyParserTest(cros_test_lib.TestCase):
2108 """Tests for _get_cargo_clippy_parser."""
2109
2110 def testSingleProject(self):
2111 parser = pre_upload._get_cargo_clippy_parser()
2112 args = parser.parse_args(['--project', 'foo'])
2113 self.assertEqual(args.project,
2114 [pre_upload.ClippyProject(root='foo', script=None)])
2115
2116 def testMultipleProjects(self):
2117 parser = pre_upload._get_cargo_clippy_parser()
2118 args = parser.parse_args(['--project', 'foo:bar',
2119 '--project', 'baz'])
2120 self.assertCountEqual(args.project,
2121 [pre_upload.ClippyProject(root='foo', script='bar'),
2122 pre_upload.ClippyProject(root='baz', script=None)])
2123
2124
2125class CheckCargoClippyTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
2126 """Tests for _check_cargo_clippy."""
2127
2128 def setUp(self):
2129 self.project = pre_upload.Project(name='PROJECT', dir=self.tempdir,
2130 remote=None)
2131
2132 def testClippy(self):
2133 """Verify clippy is called when a monitored file was changed."""
2134 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
2135
2136 self.PatchObject(pre_upload, '_get_affected_files',
2137 return_value=[f'{self.project.dir}/repo_a/a.rs'])
2138
2139 ret = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
2140 options=['--project=repo_a',
2141 '--project=repo_b:foo'])
2142 self.assertFalse(ret)
2143
2144 # Check if `cargo clippy` ran.
2145 called = False
2146 for args, _ in rc_mock.call_args_list:
2147 cmd = args[0]
2148 if len(cmd) > 1 and cmd[0] == 'cargo' and cmd[1] == 'clippy':
2149 called = True
2150 break
2151
2152 self.assertTrue(called)
2153
2154 def testDontRun(self):
2155 """Skip checks when no monitored files are modified."""
2156 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
2157
2158 # A file under `repo_a` was monitored.
2159 self.PatchObject(pre_upload, '_get_affected_files',
2160 return_value=[f'{self.project.dir}/repo_a/a.rs'])
2161 # But, we only care about files under `repo_b`.
2162 errs = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
2163 options=['--project=repo_b:foo'])
2164
2165 self.assertFalse(errs)
2166
2167 rc_mock.assert_not_called()
2168
2169 def testCustomScript(self):
2170 """Verify project-specific script is used."""
2171 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
2172
2173 self.PatchObject(pre_upload, '_get_affected_files',
2174 return_value=[f'{self.project.dir}/repo_b/b.rs'])
2175
2176 errs = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
2177 options=['--project=repo_a',
2178 '--project=repo_b:foo'])
2179 self.assertFalse(errs)
2180
2181 # Check if the script `foo` ran.
2182 called = False
2183 for args, _ in rc_mock.call_args_list:
2184 cmd = args[0]
2185 if len(cmd) > 0 and cmd[0] == os.path.join(self.project.dir, 'foo'):
2186 called = True
2187 break
2188
2189 self.assertTrue(called)
2190
2191
Mike Frysinger180ecd62020-08-19 00:41:51 -04002192class OverrideHooksProcessing(PreUploadTestCase):
2193 """Verify _get_override_hooks processing."""
2194
2195 @staticmethod
2196 def parse(data):
2197 """Helper to create a config & parse it."""
2198 cfg = configparser.ConfigParser()
2199 cfg.read_string(data)
2200 return pre_upload._get_override_hooks(cfg)
2201
2202 def testHooks(self):
2203 """Verify we reject unknown hook names (e.g. typos)."""
2204 with self.assertRaises(ValueError) as e:
2205 self.parse("""
2206[Hook Overrides]
2207foobar: true
2208""")
2209 self.assertIn('foobar', str(e.exception))
2210
2211 def testImplicitDisable(self):
2212 """Verify non-common hooks aren't enabled by default."""
2213 enabled, _ = self.parse('')
2214 self.assertNotIn(pre_upload._run_checkpatch, enabled)
2215
2216 def testExplicitDisable(self):
2217 """Verify hooks disabled are disabled."""
2218 _, disabled = self.parse("""
2219[Hook Overrides]
2220tab_check: false
2221""")
2222 self.assertIn(pre_upload._check_no_tabs, disabled)
2223
2224 def testExplicitEnable(self):
2225 """Verify hooks enabled are enabled."""
2226 enabled, _ = self.parse("""
2227[Hook Overrides]
2228tab_check: true
2229""")
2230 self.assertIn(pre_upload._check_no_tabs, enabled)
2231
2232 def testOptions(self):
2233 """Verify hook options are loaded."""
2234 enabled, _ = self.parse("""
2235[Hook Overrides Options]
2236keyword_check: --kw
2237""")
2238 for func in enabled:
2239 if func.__name__ == 'keyword_check':
2240 self.assertIn('options', func.keywords)
2241 self.assertEqual(func.keywords['options'], ['--kw'])
2242 break
2243 else:
2244 self.fail('could not find "keyword_check" enabled hook')
2245
2246 def testSignOffField(self):
2247 """Verify signoff field handling."""
2248 # Enforce no s-o-b by default.
2249 enabled, disabled = self.parse('')
2250 self.assertIn(pre_upload._check_change_has_no_signoff_field, enabled)
2251 self.assertNotIn(pre_upload._check_change_has_signoff_field, enabled)
2252 self.assertNotIn(pre_upload._check_change_has_signoff_field, disabled)
2253
2254 # If disabled, don't enforce either policy.
2255 enabled, disabled = self.parse("""
2256[Hook Overrides]
2257signoff_check: false
2258""")
2259 self.assertNotIn(pre_upload._check_change_has_no_signoff_field, enabled)
2260 self.assertNotIn(pre_upload._check_change_has_signoff_field, enabled)
2261 self.assertIn(pre_upload._check_change_has_signoff_field, disabled)
2262
2263 # If enabled, require s-o-b.
2264 enabled, disabled = self.parse("""
2265[Hook Overrides]
2266signoff_check: true
2267""")
2268 self.assertNotIn(pre_upload._check_change_has_no_signoff_field, enabled)
2269 self.assertIn(pre_upload._check_change_has_signoff_field, enabled)
2270 self.assertNotIn(pre_upload._check_change_has_signoff_field, disabled)
2271
2272 def testBranchField(self):
2273 """Verify branch field enabling."""
2274 # Should be disabled by default.
2275 enabled, disabled = self.parse('')
2276 self.assertIn(pre_upload._check_change_has_no_branch_field, enabled)
2277 self.assertNotIn(pre_upload._check_change_has_branch_field, enabled)
2278 self.assertNotIn(pre_upload._check_change_has_branch_field, disabled)
2279
2280 # Should be disabled if requested.
2281 enabled, disabled = self.parse("""
2282[Hook Overrides]
2283branch_check: false
2284""")
2285 self.assertIn(pre_upload._check_change_has_no_branch_field, enabled)
2286 self.assertNotIn(pre_upload._check_change_has_branch_field, enabled)
2287 self.assertIn(pre_upload._check_change_has_branch_field, disabled)
2288
2289 # Should be enabled if requested.
2290 enabled, disabled = self.parse("""
2291[Hook Overrides]
2292branch_check: true
2293""")
2294 self.assertNotIn(pre_upload._check_change_has_no_branch_field, enabled)
2295 self.assertIn(pre_upload._check_change_has_branch_field, enabled)
2296 self.assertNotIn(pre_upload._check_change_has_branch_field, disabled)
2297
2298
Tom Hughes1ed799d2020-09-25 14:37:28 -07002299class ProjectHooksProcessing(PreUploadTestCase, cros_test_lib.TempDirTestCase):
2300 """Verify _get_project_hooks processing."""
2301
2302 def parse(self, data):
2303 """Helper to write config and parse it."""
2304 filename = os.path.join(self.tempdir, 'config')
2305 osutils.WriteFile(filename, data)
Mike Frysingerff916c62020-12-18 01:58:08 -05002306 return pre_upload._get_project_hooks(presubmit=True, config_file=filename)
Tom Hughes1ed799d2020-09-25 14:37:28 -07002307
2308 def testClangFormatCheckDefault(self):
2309 """Verify clang-format check disabled by default."""
2310 hooks = self.parse('')
2311 for func in hooks:
2312 self.assertNotEqual(func.__name__, '_check_clang_format')
2313 self.assertNotEqual(func.__name__, 'clang_format_check')
2314
2315 def testClangFormatCheckDisabled(self):
2316 """Verify clang-format check disabled when requested."""
2317 hooks = self.parse("""
2318[Hook Overrides]
2319clang_format_check: false
2320""")
2321 for func in hooks:
2322 self.assertNotEqual(func.__name__, '_check_clang_format')
2323 self.assertNotEqual(func.__name__, 'clang_format_check')
2324
2325 def testClangFormatCheckEnabled(self):
2326 """Verify clang-format check enabled when requested."""
2327 hooks = self.parse("""
2328[Hook Overrides]
2329clang_format_check: true
2330""")
2331 for func in hooks:
2332 if func.__name__ == '_check_clang_format':
2333 self.assertFalse(hasattr(func, 'keywords'))
2334 break
2335 else:
2336 self.fail('could not find "_check_clang_format" enabled hook')
2337
2338 def testClangFormatCheckEnabledWithOptions(self):
2339 """Verify clang-format check has options when provided."""
2340 hooks = self.parse("""
2341[Hook Overrides]
2342clang_format_check: true
2343
2344[Hook Overrides Options]
2345clang_format_check:
2346 some_dir/
2347""")
2348 for func in hooks:
2349 if func.__name__ == 'clang_format_check':
2350 self.assertIn('options', func.keywords)
2351 self.assertEqual(func.keywords['options'], ['some_dir/'])
2352 break
2353 else:
2354 self.fail('could not find "clang_format_check" enabled hook')
2355
2356
Jon Salz98255932012-08-18 14:48:02 +08002357if __name__ == '__main__':
Mike Frysinger884a8dd2015-05-17 03:43:43 -04002358 cros_test_lib.main(module=__name__)