blob: b5902aadb4c63237afb5b3f268a20062ab9c951e [file] [log] [blame]
Mike Frysinger4f994402019-09-13 17:40:45 -04001#!/usr/bin/env python3
Jon Salz98255932012-08-18 14:48:02 +08002# -*- coding: utf-8 -*-
3# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
Mike Frysingerae409522014-02-01 03:16:11 -05007"""Unittests for pre-upload.py."""
8
Mike Frysinger09d6a3d2013-10-08 22:21:03 -04009from __future__ import print_function
10
Mike Frysinger180ecd62020-08-19 00:41:51 -040011import configparser
Keigo Oka7e880ac2019-07-03 15:03:43 +090012import datetime
David Jamesc3b68b32013-04-03 09:17:03 -070013import os
14import sys
Mike Frysingerf6a29772020-08-22 03:57:08 -040015from unittest import mock
Mike Frysingerfd481ce2019-09-13 18:14:48 -040016
Mike Frysingerbf8b91c2014-02-01 02:50:27 -050017import errors
18
Jon Salz98255932012-08-18 14:48:02 +080019# pylint: disable=W0212
Mike Frysinger65d93c12014-02-01 02:59:46 -050020# We access private members of the pre_upload module all over the place.
21
Mike Frysinger55f85b52014-12-18 14:45:21 -050022# Make sure we can find the chromite paths.
23sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)),
24 '..', '..'))
Jon Salz98255932012-08-18 14:48:02 +080025
Mike Frysingerfd481ce2019-09-13 18:14:48 -040026# The sys.path monkey patching confuses the linter.
27# pylint: disable=wrong-import-position
Mike Frysinger71e643e2019-09-13 17:26:39 -040028from chromite.lib import constants
29from chromite.lib import cros_build_lib
Mike Frysinger65d93c12014-02-01 02:59:46 -050030from chromite.lib import cros_test_lib
Mike Frysingerd3bd32c2014-11-24 23:34:29 -050031from chromite.lib import git
Daniel Erata350fd32014-09-29 14:02:34 -070032from chromite.lib import osutils
Mike Frysinger65d93c12014-02-01 02:59:46 -050033
34
Mike Frysingerff4768e2020-02-27 18:48:13 -050035assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
36
37
Jon Salz98255932012-08-18 14:48:02 +080038pre_upload = __import__('pre-upload')
39
40
Alex Deymo643ac4c2015-09-03 10:40:50 -070041def ProjectNamed(project_name):
42 """Wrapper to create a Project with just the name"""
43 return pre_upload.Project(project_name, None, None)
44
45
Mike Frysingerb2496652019-09-12 23:35:46 -040046class PreUploadTestCase(cros_test_lib.MockTestCase):
47 """Common test case base."""
48
49 def setUp(self):
50 pre_upload.CACHE.clear()
51
52
53class TryUTF8DecodeTest(PreUploadTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -050054 """Verify we sanely handle unicode content."""
55
Mike Frysinger71e643e2019-09-13 17:26:39 -040056 def setUp(self):
Mike Frysinger7bb709f2019-09-29 23:20:12 -040057 self.rc_mock = self.PatchObject(cros_build_lib, 'run')
Mike Frysinger71e643e2019-09-13 17:26:39 -040058
59 def _run(self, content):
60 """Helper for round tripping through _run_command."""
61 self.rc_mock.return_value = cros_build_lib.CommandResult(
62 output=content, returncode=0)
63 return pre_upload._run_command([])
64
65 def testEmpty(self):
66 """Check empty output."""
67 ret = self._run(b'')
68 self.assertEqual('', ret)
69
70 if sys.version_info.major < 3:
71 ret = self._run('')
72 self.assertEqual(u'', ret)
73
74 def testAscii(self):
75 """Check ascii output."""
76 ret = self._run(b'abc')
77 self.assertEqual('abc', ret)
78
79 if sys.version_info.major < 3:
80 ret = self._run('abc')
81 self.assertEqual(u'abc', ret)
82
83 def testUtf8(self):
84 """Check valid UTF-8 output."""
85 text = u'你好布萊恩'
86 ret = self._run(text.encode('utf-8'))
87 self.assertEqual(text, ret)
88
89 def testBinary(self):
90 """Check binary (invalid UTF-8) output."""
91 ret = self._run(b'hi \x80 there')
Mike Frysinger8a4e8942019-09-16 23:43:49 -040092 self.assertEqual(u'hi \ufffd there', ret)
Jon Salz98255932012-08-18 14:48:02 +080093
94
Daisuke Nojiri2089e012020-08-20 15:12:36 -070095class CheckKeywordsTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Bernie Thompson8e26f742020-07-23 14:32:31 -070096 """Tests for _check_keywords."""
97
98 def setUp(self):
99 self.PatchObject(pre_upload, '_get_affected_files',
100 return_value=['x.ebuild'])
101 self.PatchObject(pre_upload, '_filter_files', return_value=['x.ebuild'])
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700102 # First call for blocked_terms.txt and second call for unblocked_terms.txt.
103 self.rf_mock = self.PatchObject(
104 osutils, 'ReadFile',
105 side_effect=['scruffy\nmangy\ndog.?pile\ncat.?circle', 'fox'])
Bernie Thompson8e26f742020-07-23 14:32:31 -0700106 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
107 self.desc_mock = self.PatchObject(pre_upload, '_get_commit_desc')
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700108 self.project = pre_upload.Project(name='PROJECT', dir=self.tempdir,
109 remote=None)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700110
111 def test_good_cases(self):
112 self.desc_mock.return_value = 'Commit Message.\nLine 2'
113 self.diff_mock.return_value = [
114 (1, 'Some text without keywords.'),
115 (2, 'The dog is black has a partial keyword that does not count.'),
116 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700117 failures = pre_upload._check_keywords(self.project, 'COMMIT')
Bernie Thompson8e26f742020-07-23 14:32:31 -0700118 self.assertEqual(failures, [])
119
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700120 self.rf_mock.assert_has_calls([
121 mock.call(os.path.join(pre_upload._get_hooks_dir(),
122 pre_upload.BLOCKED_TERMS_FILE)),
123 mock.call(os.path.join(pre_upload._get_hooks_dir(),
124 pre_upload.UNBLOCKED_TERMS_FILE)),
125 ])
126
Bernie Thompson8e26f742020-07-23 14:32:31 -0700127 def test_bad_cases(self):
128 self.desc_mock.return_value = 'Commit Message.\nLine 2\nLine 3 scruffy'
129 self.diff_mock.return_value = [
130 (1, 'Scruffy plain catch'),
131 (2, 'dog-pile hyphenated catch'),
132 (3, 'cat_circle underscored catch'),
133 (4, 'dog pile space catch'),
134 (5, 'dogpiled substring catch'),
135 (6, 'scruffy mangy dog, multiple in a line catch'),
136 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700137 failures = pre_upload._check_keywords(self.project, 'COMMIT')
Bernie Thompson8e26f742020-07-23 14:32:31 -0700138 self.assertNotEqual(failures, [])
139 self.assertEqual('Found a blocked keyword in:', failures[0].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700140 self.assertEqual(
141 ['x.ebuild, line 1: Matched "Scruffy" with regex of "scruffy"',
142 'x.ebuild, line 2: Matched "dog-pile" with regex of "dog.?pile"',
143 'x.ebuild, line 3: Matched "cat_circle" with regex of "cat.?circle"',
144 'x.ebuild, line 4: Matched "dog pile" with regex of "dog.?pile"',
145 'x.ebuild, line 5: Matched "dogpile" with regex of "dog.?pile"',
146 'x.ebuild, line 6: Matched "mangy" with regex of "mangy"'],
147 failures[0].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700148 self.assertEqual('Found a blocked keyword in:', failures[1].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700149 self.assertEqual(
150 ['Commit message, line 3: Matched "scruffy" with regex of "scruffy"'],
151 failures[1].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700152
153 def test_block_option_cases(self):
154 self.desc_mock.return_value = 'Commit Message.\nLine 2 voldemort'
155 self.diff_mock.return_value = [
156 (1, 'Line with a new term voldemort.'),
157 (2, 'Line with only they who shall not be named.'),
158 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700159 failures = pre_upload._check_keywords(self.project,
Bernie Thompson8e26f742020-07-23 14:32:31 -0700160 'COMMIT', ['--block', 'voldemort'])
161 self.assertNotEqual(failures, [])
162 self.assertEqual('Found a blocked keyword in:', failures[0].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700163 self.assertEqual(
164 ['x.ebuild, line 1: Matched "voldemort" with regex of "voldemort"'],
165 failures[0].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700166 self.assertEqual('Found a blocked keyword in:', failures[1].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700167 self.assertEqual(
168 ['Commit message, line 2: '
169 'Matched "voldemort" with regex of "voldemort"'], failures[1].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700170
171 def test_unblock_option_cases(self):
172 self.desc_mock.return_value = 'Commit message with scruffy'
173 self.diff_mock.return_value = [
Bernie Thompson4e362922020-09-02 16:17:50 -0700174 (1, 'Line with two unblocked terms scruffy big dog-pile'),
175 (2, 'Line with without any blocked terms'),
176 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700177 # scruffy matches regex of 'scruffy' in block list but excluded by
178 # different regex of 'scru.?fy' in unblock list.
179 failures = pre_upload._check_keywords(self.project,
Bernie Thompson4e362922020-09-02 16:17:50 -0700180 'COMMIT', ['--unblock', 'dog.?pile',
181 '--unblock', 'scru.?fy'])
182 self.assertEqual(failures, [])
Bernie Thompson8e26f742020-07-23 14:32:31 -0700183
Laurent Chavey434af9a2020-09-28 22:25:16 +0900184 def test_unblock_and_block_option_cases(self):
185 self.desc_mock.return_value = 'Commit message with scruffy'
186 self.diff_mock.return_value = [
187 (1, 'Two unblocked terms scruffy and dog-pile'),
188 (2, 'Without any blocked terms'),
189 (3, 'Blocked dogpile'),
190 (4, 'Unblocked m.dogpile'),
191 (5, 'Blocked dogpile and unblocked m.dogpile'),
192 (6, 'Unlocked m.dogpile and blocked dogpile'),
193 (7, 'Unlocked m.dogpile and unblocked dog-pile'),
194 ]
195 # scruffy matches regex of 'scruffy' in block list but excluded by
196 # a different regex of 'scru.?fy' in unblock list.
197 # dogpile, dog.pile matches regex of 'dog.?pile' in block list.
198 # m.dogpile and dog-pile matches regex of 'dog.?pile' in block list but
199 # excluded by different regex '\.dog.?pile' and 'dog-pile' in unblock list.
200 failures = pre_upload._check_keywords(self.project,
201 'COMMIT',
202 ['--unblock', r'dog-pile',
203 '--unblock', r'scru.?fy',
204 '--unblock', r'\.dog.?pile'])
205 self.assertNotEqual(failures, [])
206 self.assertEqual('Found a blocked keyword in:', failures[0].msg)
207 self.assertEqual(
208 [r'x.ebuild, line 3: Matched "dogpile" with regex of "dog.?pile"',
209 r'x.ebuild, line 5: Matched "dogpile" with regex of "dog.?pile"',
210 r'x.ebuild, line 6: Matched "dogpile" with regex of "dog.?pile"'],
211 failures[0].items)
212
Bernie Thompson8e26f742020-07-23 14:32:31 -0700213
Mike Frysingerb2496652019-09-12 23:35:46 -0400214class CheckNoLongLinesTest(PreUploadTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -0500215 """Tests for _check_no_long_lines."""
216
Jon Salz98255932012-08-18 14:48:02 +0800217 def setUp(self):
Mike Frysinger1459d362014-12-06 13:53:23 -0500218 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
Jon Salz98255932012-08-18 14:48:02 +0800219
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900220 def testCheck(self):
Mike Frysingerf8961942020-05-15 00:36:31 -0400221 path = 'x.cc'
222 self.PatchObject(pre_upload, '_get_affected_files', return_value=[path])
Mike Frysinger1459d362014-12-06 13:53:23 -0500223 self.diff_mock.return_value = [
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400224 (1, u'x' * 80), # OK
225 (2, '\x80' * 80), # OK
226 (3, u'x' * 81), # Too long
227 (4, '\x80' * 81), # Too long
228 (5, u'See http://' + (u'x' * 80)), # OK (URL)
229 (6, u'See https://' + (u'x' * 80)), # OK (URL)
230 (7, u'# define ' + (u'x' * 80)), # OK (compiler directive)
231 (8, u'#define' + (u'x' * 74)), # Too long
Mike Frysinger1459d362014-12-06 13:53:23 -0500232 ]
Alex Deymo643ac4c2015-09-03 10:40:50 -0700233 failure = pre_upload._check_no_long_lines(ProjectNamed('PROJECT'), 'COMMIT')
Jon Salz98255932012-08-18 14:48:02 +0800234 self.assertTrue(failure)
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900235 self.assertEqual('Found lines longer than the limit (first 5 shown):',
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400236 failure.msg)
Mike Frysingerf8961942020-05-15 00:36:31 -0400237 self.assertEqual([path + ', line %d, 81 chars, over 80 chars' %
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900238 x for x in [3, 4, 8]],
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400239 failure.items)
Jon Salz98255932012-08-18 14:48:02 +0800240
George Burgess IVf9f79eb2019-07-09 20:12:55 -0700241 def testCheckTreatsOwnersFilesSpecially(self):
242 affected_files = self.PatchObject(pre_upload, '_get_affected_files')
243
244 mock_files = (
245 ('foo-OWNERS', False),
246 ('OWNERS', True),
247 ('/path/to/OWNERS', True),
248 ('/path/to/OWNERS.foo', True),
249 )
250
251 mock_lines = (
252 (u'x' * 81, False),
253 (u'foo file:' + u'x' * 80, True),
254 (u'include ' + u'x' * 80, True),
255 )
256 assert all(len(line) > 80 for line, _ in mock_lines)
257
258 for file_name, is_owners in mock_files:
259 affected_files.return_value = [file_name]
260 for line, is_ok in mock_lines:
261 self.diff_mock.return_value = [(1, line)]
262 failure = pre_upload._check_no_long_lines(ProjectNamed('PROJECT'),
263 'COMMIT')
264
265 assert_msg = 'file: %r; line: %r' % (file_name, line)
266 if is_owners and is_ok:
267 self.assertFalse(failure, assert_msg)
268 else:
269 self.assertTrue(failure, assert_msg)
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900270 self.assertIn('Found lines longer than the limit', failure.msg,
George Burgess IVf9f79eb2019-07-09 20:12:55 -0700271 assert_msg)
272
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900273 def testIncludeOptions(self):
274 self.PatchObject(pre_upload,
275 '_get_affected_files',
276 return_value=['foo.txt'])
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400277 self.diff_mock.return_value = [(1, u'x' * 81)]
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900278 self.assertFalse(pre_upload._check_no_long_lines(
279 ProjectNamed('PROJECT'), 'COMMIT'))
280 self.assertTrue(pre_upload._check_no_long_lines(
281 ProjectNamed('PROJECT'), 'COMMIT', options=['--include_regex=foo']))
282
283 def testExcludeOptions(self):
284 self.PatchObject(pre_upload,
285 '_get_affected_files',
Mike Frysingerf8961942020-05-15 00:36:31 -0400286 return_value=['foo.cc'])
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400287 self.diff_mock.return_value = [(1, u'x' * 81)]
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900288 self.assertTrue(pre_upload._check_no_long_lines(
289 ProjectNamed('PROJECT'), 'COMMIT'))
290 self.assertFalse(pre_upload._check_no_long_lines(
291 ProjectNamed('PROJECT'), 'COMMIT', options=['--exclude_regex=foo']))
292
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900293 def testSpecialLineLength(self):
294 mock_lines = (
295 (u'x' * 101, True),
296 (u'x' * 100, False),
297 (u'x' * 81, False),
298 (u'x' * 80, False),
299 )
300 self.PatchObject(pre_upload,
301 '_get_affected_files',
302 return_value=['foo.java'])
303 for line, is_ok in mock_lines:
304 self.diff_mock.return_value = [(1, line)]
305 if is_ok:
306 self.assertTrue(pre_upload._check_no_long_lines(
307 ProjectNamed('PROJECT'), 'COMMIT'))
308 else:
309 self.assertFalse(pre_upload._check_no_long_lines(
310 ProjectNamed('PROJECT'), 'COMMIT'))
311
Mike Frysingerae409522014-02-01 03:16:11 -0500312
Mike Frysingerb2496652019-09-12 23:35:46 -0400313class CheckTabbedIndentsTest(PreUploadTestCase):
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800314 """Tests for _check_tabbed_indents."""
Mike Frysingerb2496652019-09-12 23:35:46 -0400315
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800316 def setUp(self):
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900317 self.PatchObject(pre_upload,
318 '_get_affected_files',
319 return_value=['x.ebuild'])
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800320 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
321
322 def test_good_cases(self):
323 self.diff_mock.return_value = [
324 (1, u'no_tabs_anywhere'),
325 (2, u' leading_tab_only'),
326 (3, u' leading_tab another_tab'),
327 (4, u' leading_tab trailing_too '),
328 (5, u' leading_tab then_spaces_trailing '),
329 ]
330 failure = pre_upload._check_tabbed_indents(ProjectNamed('PROJECT'),
331 'COMMIT')
332 self.assertIsNone(failure)
333
334 def test_bad_cases(self):
335 self.diff_mock.return_value = [
336 (1, u' leading_space'),
337 (2, u' tab_followed_by_space'),
338 (3, u' space_followed_by_tab'),
339 (4, u' mix_em_up'),
340 (5, u' mix_on_both_sides '),
341 ]
342 failure = pre_upload._check_tabbed_indents(ProjectNamed('PROJECT'),
343 'COMMIT')
344 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400345 self.assertEqual('Found a space in indentation (must be all tabs):',
346 failure.msg)
347 self.assertEqual(['x.ebuild, line %d' % x for x in range(1, 6)],
348 failure.items)
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800349
350
Mike Frysingerb2496652019-09-12 23:35:46 -0400351class CheckProjectPrefix(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Daniel Erata350fd32014-09-29 14:02:34 -0700352 """Tests for _check_project_prefix."""
353
354 def setUp(self):
Daniel Erata350fd32014-09-29 14:02:34 -0700355 os.chdir(self.tempdir)
356 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
357 self.desc_mock = self.PatchObject(pre_upload, '_get_commit_desc')
358
Daniel Erata350fd32014-09-29 14:02:34 -0700359 def _WriteAliasFile(self, filename, project):
360 """Writes a project name to a file, creating directories if needed."""
361 os.makedirs(os.path.dirname(filename))
362 osutils.WriteFile(filename, project)
363
364 def testInvalidPrefix(self):
365 """Report an error when the prefix doesn't match the base directory."""
366 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
367 self.desc_mock.return_value = 'bar: Some commit'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700368 failure = pre_upload._check_project_prefix(ProjectNamed('PROJECT'),
369 'COMMIT')
Daniel Erata350fd32014-09-29 14:02:34 -0700370 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400371 self.assertEqual('The commit title for changes affecting only foo should '
372 'start with "foo: "', failure.msg)
Daniel Erata350fd32014-09-29 14:02:34 -0700373
374 def testValidPrefix(self):
375 """Use a prefix that matches the base directory."""
376 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
377 self.desc_mock.return_value = 'foo: Change some files.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700378 self.assertFalse(
379 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700380
381 def testAliasFile(self):
382 """Use .project_alias to override the project name."""
383 self._WriteAliasFile('foo/.project_alias', 'project')
384 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/bar.cc']
385 self.desc_mock.return_value = 'project: Use an alias.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700386 self.assertFalse(
387 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700388
389 def testAliasFileWithSubdirs(self):
390 """Check that .project_alias is used when only modifying subdirectories."""
391 self._WriteAliasFile('foo/.project_alias', 'project')
392 self.file_mock.return_value = [
393 'foo/subdir/foo.cc',
394 'foo/subdir/bar.cc'
395 'foo/subdir/blah/baz.cc'
396 ]
397 self.desc_mock.return_value = 'project: Alias with subdirs.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700398 self.assertFalse(
399 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700400
401
Mike Frysingerb2496652019-09-12 23:35:46 -0400402class CheckFilePathCharTypeTest(PreUploadTestCase):
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900403 """Tests for _check_filepath_chartype."""
404
405 def setUp(self):
406 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
407
408 def testCheck(self):
409 self.PatchObject(pre_upload, '_get_affected_files', return_value=['x.cc'])
410 self.diff_mock.return_value = [
411 (1, 'base::FilePath'), # OK
412 (2, 'base::FilePath::CharType'), # NG
413 (3, 'base::FilePath::StringType'), # NG
414 (4, 'base::FilePath::StringPieceType'), # NG
Satoru Takabayashi4ca37922018-08-08 10:16:38 +0900415 (5, 'base::FilePath::FromUTF8Unsafe'), # NG
416 (6, 'FilePath::CharType'), # NG
417 (7, 'FilePath::StringType'), # NG
418 (8, 'FilePath::StringPieceType'), # NG
419 (9, 'FilePath::FromUTF8Unsafe'), # NG
420 (10, 'AsUTF8Unsafe'), # NG
421 (11, 'FILE_PATH_LITERAL'), # NG
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900422 ]
423 failure = pre_upload._check_filepath_chartype(ProjectNamed('PROJECT'),
424 'COMMIT')
425 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400426 self.assertEqual(
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900427 'Please assume FilePath::CharType is char (crbug.com/870621):',
428 failure.msg)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400429 self.assertEqual(['x.cc, line 2 has base::FilePath::CharType',
430 'x.cc, line 3 has base::FilePath::StringType',
431 'x.cc, line 4 has base::FilePath::StringPieceType',
432 'x.cc, line 5 has base::FilePath::FromUTF8Unsafe',
433 'x.cc, line 6 has FilePath::CharType',
434 'x.cc, line 7 has FilePath::StringType',
435 'x.cc, line 8 has FilePath::StringPieceType',
436 'x.cc, line 9 has FilePath::FromUTF8Unsafe',
437 'x.cc, line 10 has AsUTF8Unsafe',
438 'x.cc, line 11 has FILE_PATH_LITERAL'],
439 failure.items)
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900440
441
Mike Frysingerb2496652019-09-12 23:35:46 -0400442class CheckKernelConfig(PreUploadTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -0500443 """Tests for _kernel_configcheck."""
444
Mike Frysinger1459d362014-12-06 13:53:23 -0500445 def setUp(self):
446 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
447
448 def testMixedChanges(self):
449 """Mixing of changes should fail."""
450 self.file_mock.return_value = [
451 '/kernel/files/chromeos/config/base.config',
452 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
453 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700454 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
455 self.assertTrue(failure)
456
Mike Frysinger1459d362014-12-06 13:53:23 -0500457 def testCodeOnly(self):
458 """Code-only changes should pass."""
459 self.file_mock.return_value = [
460 '/kernel/files/Makefile',
461 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
462 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700463 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
464 self.assertFalse(failure)
465
Mike Frysinger1459d362014-12-06 13:53:23 -0500466 def testConfigOnlyChanges(self):
467 """Config-only changes should pass."""
468 self.file_mock.return_value = [
469 '/kernel/files/chromeos/config/base.config',
470 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700471 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
472 self.assertFalse(failure)
473
Jon Salz98255932012-08-18 14:48:02 +0800474
Mike Frysingerb2496652019-09-12 23:35:46 -0400475class CheckJson(PreUploadTestCase):
Mike Frysinger908be682018-01-04 02:21:50 -0500476 """Tests for _run_json_check."""
477
478 def setUp(self):
479 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
480 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
481
482 def testNoJson(self):
483 """Nothing should be checked w/no JSON files."""
484 self.file_mock.return_value = [
485 '/foo/bar.txt',
486 '/readme.md',
487 ]
488 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
489 self.assertIsNone(ret)
490
491 def testValidJson(self):
492 """We should accept valid json files."""
493 self.file_mock.return_value = [
494 '/foo/bar.txt',
495 '/data.json',
496 ]
497 self.content_mock.return_value = '{}'
498 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
499 self.assertIsNone(ret)
500 self.content_mock.assert_called_once_with('/data.json', 'COMMIT')
501
502 def testInvalidJson(self):
503 """We should reject invalid json files."""
504 self.file_mock.return_value = [
505 '/foo/bar.txt',
506 '/data.json',
507 ]
508 self.content_mock.return_value = '{'
509 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
510 self.assertIsNotNone(ret)
511 self.content_mock.assert_called_once_with('/data.json', 'COMMIT')
512
513
Mike Frysingerb2496652019-09-12 23:35:46 -0400514class CheckManifests(PreUploadTestCase):
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500515 """Tests _check_manifests."""
516
517 def setUp(self):
518 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
519 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
520
521 def testNoManifests(self):
522 """Nothing should be checked w/no Manifest files."""
523 self.file_mock.return_value = [
524 '/foo/bar.txt',
525 '/readme.md',
526 '/manifest',
527 '/Manifest.txt',
528 ]
529 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
530 self.assertIsNone(ret)
531
532 def testValidManifest(self):
533 """Accept valid Manifest files."""
534 self.file_mock.return_value = [
535 '/foo/bar.txt',
536 '/cat/pkg/Manifest',
537 ]
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400538 self.content_mock.return_value = """# Comment and blank lines.
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500539
540DIST lines
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400541"""
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500542 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
543 self.assertIsNone(ret)
544 self.content_mock.assert_called_once_with('/cat/pkg/Manifest', 'COMMIT')
545
546 def _testReject(self, content):
547 """Make sure |content| is rejected."""
548 self.file_mock.return_value = ('/Manifest',)
549 self.content_mock.reset_mock()
550 self.content_mock.return_value = content
551 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
552 self.assertIsNotNone(ret)
553 self.content_mock.assert_called_once_with('/Manifest', 'COMMIT')
554
555 def testRejectBlank(self):
556 """Reject empty manifests."""
557 self._testReject('')
558
559 def testNoTrailingNewLine(self):
560 """Reject manifests w/out trailing newline."""
561 self._testReject('DIST foo')
562
563 def testNoLeadingBlankLines(self):
564 """Reject manifests w/leading blank lines."""
565 self._testReject('\nDIST foo\n')
566
567 def testNoTrailingBlankLines(self):
568 """Reject manifests w/trailing blank lines."""
569 self._testReject('DIST foo\n\n')
570
571 def testNoLeadingWhitespace(self):
572 """Reject manifests w/lines w/leading spaces."""
573 self._testReject(' DIST foo\n')
574 self._testReject(' # Comment\n')
575
576 def testNoTrailingWhitespace(self):
577 """Reject manifests w/lines w/trailing spaces."""
578 self._testReject('DIST foo \n')
579 self._testReject('# Comment \n')
580 self._testReject(' \n')
581
582 def testOnlyDistLines(self):
583 """Only allow DIST lines in here."""
584 self._testReject('EBUILD foo\n')
585
586
Mike Frysingerb2496652019-09-12 23:35:46 -0400587class CheckPortageMakeUseVar(PreUploadTestCase):
Daniel Erat9d203ff2015-02-17 10:12:21 -0700588 """Tests for _check_portage_make_use_var."""
589
590 def setUp(self):
591 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
592 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
593
594 def testMakeConfOmitsOriginalUseValue(self):
595 """Fail for make.conf that discards the previous value of $USE."""
596 self.file_mock.return_value = ['make.conf']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400597 self.content_mock.return_value = u'USE="foo"\nUSE="${USE} bar"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700598 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
599 self.assertTrue(failure, failure)
600
601 def testMakeConfCorrectUsage(self):
602 """Succeed for make.conf that preserves the previous value of $USE."""
603 self.file_mock.return_value = ['make.conf']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400604 self.content_mock.return_value = u'USE="${USE} foo"\nUSE="${USE}" bar'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700605 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
606 self.assertFalse(failure, failure)
607
608 def testMakeDefaultsReferencesOriginalUseValue(self):
609 """Fail for make.defaults that refers to a not-yet-set $USE value."""
610 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400611 self.content_mock.return_value = u'USE="${USE} foo"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700612 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
613 self.assertTrue(failure, failure)
614
615 # Also check for "$USE" without curly brackets.
Mike Frysinger71e643e2019-09-13 17:26:39 -0400616 self.content_mock.return_value = u'USE="$USE foo"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700617 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
618 self.assertTrue(failure, failure)
619
620 def testMakeDefaultsOverwritesUseValue(self):
621 """Fail for make.defaults that discards its own $USE value."""
622 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400623 self.content_mock.return_value = u'USE="foo"\nUSE="bar"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700624 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
625 self.assertTrue(failure, failure)
626
627 def testMakeDefaultsCorrectUsage(self):
628 """Succeed for make.defaults that sets and preserves $USE."""
629 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400630 self.content_mock.return_value = u'USE="foo"\nUSE="${USE}" bar'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700631 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
632 self.assertFalse(failure, failure)
633
634
Mike Frysingerb2496652019-09-12 23:35:46 -0400635class CheckEbuildEapi(PreUploadTestCase):
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500636 """Tests for _check_ebuild_eapi."""
637
Alex Deymo643ac4c2015-09-03 10:40:50 -0700638 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500639
640 def setUp(self):
641 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
642 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
643 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff',
644 side_effect=Exception())
645
646 def testSkipUpstreamOverlays(self):
647 """Skip ebuilds found in upstream overlays."""
648 self.file_mock.side_effect = Exception()
649 ret = pre_upload._check_ebuild_eapi(self.PORTAGE_STABLE, 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400650 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500651
652 # Make sure our condition above triggers.
653 self.assertRaises(Exception, pre_upload._check_ebuild_eapi, 'o', 'HEAD')
654
655 def testSkipNonEbuilds(self):
656 """Skip non-ebuild files."""
657 self.content_mock.side_effect = Exception()
658
659 self.file_mock.return_value = ['some-file', 'ebuild/dir', 'an.ebuild~']
Alex Deymo643ac4c2015-09-03 10:40:50 -0700660 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400661 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500662
663 # Make sure our condition above triggers.
664 self.file_mock.return_value.append('a/real.ebuild')
Alex Deymo643ac4c2015-09-03 10:40:50 -0700665 self.assertRaises(Exception, pre_upload._check_ebuild_eapi,
666 ProjectNamed('o'), 'HEAD')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500667
668 def testSkipSymlink(self):
669 """Skip files that are just symlinks."""
670 self.file_mock.return_value = ['a-r1.ebuild']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400671 self.content_mock.return_value = u'a.ebuild'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700672 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400673 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500674
675 def testRejectEapiImplicit0Content(self):
676 """Reject ebuilds that do not declare EAPI (so it's 0)."""
677 self.file_mock.return_value = ['a.ebuild']
678
Mike Frysinger71e643e2019-09-13 17:26:39 -0400679 self.content_mock.return_value = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500680IUSE="foo"
681src_compile() { }
682"""
Alex Deymo643ac4c2015-09-03 10:40:50 -0700683 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500684 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500685
686 def testRejectExplicitEapi1Content(self):
687 """Reject ebuilds that do declare old EAPI explicitly."""
688 self.file_mock.return_value = ['a.ebuild']
689
Mike Frysinger71e643e2019-09-13 17:26:39 -0400690 template = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500691EAPI=%s
692IUSE="foo"
693src_compile() { }
694"""
695 # Make sure we only check the first EAPI= setting.
Mike Frysinger948284a2018-02-01 15:22:56 -0500696 self.content_mock.return_value = template % '1\nEAPI=60'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700697 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500698 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500699
700 # Verify we handle double quotes too.
701 self.content_mock.return_value = template % '"1"'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700702 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500703 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500704
705 # Verify we handle single quotes too.
706 self.content_mock.return_value = template % "'1'"
Alex Deymo643ac4c2015-09-03 10:40:50 -0700707 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500708 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500709
Mike Frysinger948284a2018-02-01 15:22:56 -0500710 def testAcceptExplicitNewEapiContent(self):
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500711 """Accept ebuilds that do declare new EAPI explicitly."""
712 self.file_mock.return_value = ['a.ebuild']
713
Mike Frysinger71e643e2019-09-13 17:26:39 -0400714 template = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500715EAPI=%s
716IUSE="foo"
717src_compile() { }
718"""
719 # Make sure we only check the first EAPI= setting.
Mike Frysinger948284a2018-02-01 15:22:56 -0500720 self.content_mock.return_value = template % '6\nEAPI=1'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700721 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400722 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500723
724 # Verify we handle double quotes too.
725 self.content_mock.return_value = template % '"5"'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700726 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400727 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500728
729 # Verify we handle single quotes too.
730 self.content_mock.return_value = template % "'5-hdepend'"
Alex Deymo643ac4c2015-09-03 10:40:50 -0700731 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400732 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500733
734
Mike Frysingerb2496652019-09-12 23:35:46 -0400735class CheckEbuildKeywords(PreUploadTestCase):
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400736 """Tests for _check_ebuild_keywords."""
737
738 def setUp(self):
739 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
740 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
741
742 def testNoEbuilds(self):
743 """If no ebuilds are found, do not scan."""
744 self.file_mock.return_value = ['a.file', 'ebuild-is-not.foo']
745
Alex Deymo643ac4c2015-09-03 10:40:50 -0700746 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400747 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400748
749 self.assertEqual(self.content_mock.call_count, 0)
750
751 def testSomeEbuilds(self):
752 """If ebuilds are found, only scan them."""
753 self.file_mock.return_value = ['a.file', 'blah', 'foo.ebuild', 'cow']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400754 self.content_mock.return_value = u''
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400755
Alex Deymo643ac4c2015-09-03 10:40:50 -0700756 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400757 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400758
759 self.assertEqual(self.content_mock.call_count, 1)
760
761 def _CheckContent(self, content, fails):
762 """Test helper for inputs/outputs.
763
764 Args:
765 content: The ebuild content to test.
766 fails: Whether |content| should trigger a hook failure.
767 """
768 self.file_mock.return_value = ['a.ebuild']
769 self.content_mock.return_value = content
770
Alex Deymo643ac4c2015-09-03 10:40:50 -0700771 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400772 if fails:
Mike Frysingerb81102f2014-11-21 00:33:35 -0500773 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400774 else:
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400775 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400776
777 self.assertEqual(self.content_mock.call_count, 1)
778
779 def testEmpty(self):
780 """Check KEYWORDS= is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400781 self._CheckContent(u'# HEADER\nKEYWORDS=\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400782
783 def testEmptyQuotes(self):
784 """Check KEYWORDS="" is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400785 self._CheckContent(u'# HEADER\nKEYWORDS=" "\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400786
787 def testStableGlob(self):
788 """Check KEYWORDS=* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400789 self._CheckContent(u'# HEADER\nKEYWORDS="\t*\t"\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400790
791 def testUnstableGlob(self):
792 """Check KEYWORDS=~* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400793 self._CheckContent(u'# HEADER\nKEYWORDS="~* "\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400794
795 def testRestrictedGlob(self):
796 """Check KEYWORDS=-* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400797 self._CheckContent(u'# HEADER\nKEYWORDS="\t-* arm"\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400798
799 def testMissingGlobs(self):
800 """Reject KEYWORDS missing any globs."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400801 self._CheckContent(u'# HEADER\nKEYWORDS="~arm x86"\nblah\n', True)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400802
803
Mike Frysingerb2496652019-09-12 23:35:46 -0400804class CheckEbuildVirtualPv(PreUploadTestCase):
Mike Frysingercd363c82014-02-01 05:20:18 -0500805 """Tests for _check_ebuild_virtual_pv."""
806
Alex Deymo643ac4c2015-09-03 10:40:50 -0700807 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
808 CHROMIUMOS_OVERLAY = ProjectNamed('chromiumos/overlays/chromiumos')
809 BOARD_OVERLAY = ProjectNamed('chromiumos/overlays/board-overlays')
810 PRIVATE_OVERLAY = ProjectNamed('chromeos/overlays/overlay-link-private')
811 PRIVATE_VARIANT_OVERLAY = ProjectNamed('chromeos/overlays/'
812 'overlay-variant-daisy-spring-private')
Mike Frysingercd363c82014-02-01 05:20:18 -0500813
814 def setUp(self):
815 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
816
817 def testNoVirtuals(self):
818 """Skip non virtual packages."""
819 self.file_mock.return_value = ['some/package/package-3.ebuild']
Alex Deymo643ac4c2015-09-03 10:40:50 -0700820 ret = pre_upload._check_ebuild_virtual_pv(ProjectNamed('overlay'), 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400821 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500822
823 def testCommonVirtuals(self):
824 """Non-board overlays should use PV=1."""
825 template = 'virtual/foo/foo-%s.ebuild'
826 self.file_mock.return_value = [template % '1']
827 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400828 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500829
830 self.file_mock.return_value = [template % '2']
831 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500832 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500833
834 def testPublicBoardVirtuals(self):
835 """Public board overlays should use PV=2."""
836 template = 'overlay-lumpy/virtual/foo/foo-%s.ebuild'
837 self.file_mock.return_value = [template % '2']
838 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400839 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500840
841 self.file_mock.return_value = [template % '2.5']
842 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500843 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500844
845 def testPublicBoardVariantVirtuals(self):
846 """Public board variant overlays should use PV=2.5."""
847 template = 'overlay-variant-lumpy-foo/virtual/foo/foo-%s.ebuild'
848 self.file_mock.return_value = [template % '2.5']
849 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400850 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500851
852 self.file_mock.return_value = [template % '3']
853 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500854 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500855
856 def testPrivateBoardVirtuals(self):
857 """Private board overlays should use PV=3."""
858 template = 'virtual/foo/foo-%s.ebuild'
859 self.file_mock.return_value = [template % '3']
860 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400861 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500862
863 self.file_mock.return_value = [template % '3.5']
864 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500865 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500866
867 def testPrivateBoardVariantVirtuals(self):
868 """Private board variant overlays should use PV=3.5."""
869 template = 'virtual/foo/foo-%s.ebuild'
870 self.file_mock.return_value = [template % '3.5']
871 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400872 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500873
Bernie Thompsone5ee1822016-01-12 14:22:23 -0800874 def testSpecialVirtuals(self):
875 """Some cases require deeper versioning and can be >= 4."""
876 template = 'virtual/foo/foo-%s.ebuild'
Mike Frysingercd363c82014-02-01 05:20:18 -0500877 self.file_mock.return_value = [template % '4']
878 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400879 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500880
Bernie Thompsone5ee1822016-01-12 14:22:23 -0800881 self.file_mock.return_value = [template % '4.5']
882 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400883 self.assertIsNone(ret)
Mike Frysinger98638102014-08-28 00:15:08 -0400884
Mike Frysingerb2496652019-09-12 23:35:46 -0400885class CheckCrosLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -0700886 """Tests for _check_cros_license."""
Mike Frysinger98638102014-08-28 00:15:08 -0400887
888 def setUp(self):
889 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
890 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
891
892 def testOldHeaders(self):
893 """Accept old header styles."""
894 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400895 (u'#!/bin/sh\n'
896 u'# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.\n'
897 u'# Use of this source code is governed by a BSD-style license that'
898 u' can be\n'
899 u'# found in the LICENSE file.\n'),
900 (u'// Copyright 2010-2013 The Chromium OS Authors. All rights reserved.'
901 u'\n// Use of this source code is governed by a BSD-style license that'
902 u' can be\n'
903 u'// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -0400904 )
905 self.file_mock.return_value = ['file']
906 for header in HEADERS:
907 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +0900908 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
909
910 def testNewFileYear(self):
911 """Added files should have the current year in license header."""
912 year = datetime.datetime.now().year
913 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400914 (u'// Copyright 2016 The Chromium OS Authors. All rights reserved.\n'
915 u'// Use of this source code is governed by a BSD-style license that'
916 u' can be\n'
917 u'// found in the LICENSE file.\n'),
918 (u'// Copyright %d The Chromium OS Authors. All rights reserved.\n'
919 u'// Use of this source code is governed by a BSD-style license that'
920 u' can be\n'
921 u'// found in the LICENSE file.\n') % year,
Keigo Oka7e880ac2019-07-03 15:03:43 +0900922 )
923 want_error = (True, False)
924 def fake_get_affected_files(_, relative, include_adds=True):
925 _ = relative
926 if include_adds:
927 return ['file']
928 else:
929 return []
930
931 self.file_mock.side_effect = fake_get_affected_files
932 for i, header in enumerate(HEADERS):
933 self.content_mock.return_value = header
934 if want_error[i]:
935 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
936 else:
937 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -0400938
939 def testRejectC(self):
940 """Reject the (c) in newer headers."""
941 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400942 (u'// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.'
943 u'\n'
944 u'// Use of this source code is governed by a BSD-style license that'
945 u' can be\n'
946 u'// found in the LICENSE file.\n'),
947 (u'// Copyright (c) 2020 The Chromium OS Authors. All rights reserved.'
948 u'\n'
949 u'// Use of this source code is governed by a BSD-style license that'
950 u' can be\n'
951 u'// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -0400952 )
953 self.file_mock.return_value = ['file']
954 for header in HEADERS:
955 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +0900956 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -0700957
Brian Norris68838dd2018-09-26 18:30:24 -0700958 def testNoLeadingSpace(self):
959 """Allow headers without leading space (e.g., not a source comment)"""
960 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400961 (u'Copyright 2018 The Chromium OS Authors. All rights reserved.\n'
962 u'Use of this source code is governed by a BSD-style license that '
963 u'can be\n'
964 u'found in the LICENSE file.\n'),
Brian Norris68838dd2018-09-26 18:30:24 -0700965 )
966 self.file_mock.return_value = ['file']
967 for header in HEADERS:
968 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +0900969 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Brian Norris68838dd2018-09-26 18:30:24 -0700970
Keigo Oka9732e382019-06-28 17:44:59 +0900971 def testNoExcludedGolang(self):
972 """Don't exclude .go files for license checks."""
973 self.file_mock.return_value = ['foo/main.go']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400974 self.content_mock.return_value = u'package main\nfunc main() {}'
Keigo Oka7e880ac2019-07-03 15:03:43 +0900975 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Keigo Oka9732e382019-06-28 17:44:59 +0900976
Ken Turnerd07564b2018-02-08 17:57:59 +1100977 def testIgnoreExcludedPaths(self):
978 """Ignores excluded paths for license checks."""
979 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400980 self.content_mock.return_value = u'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +0900981 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +1100982
Tom Hughes90b7bd42020-11-10 10:31:49 -0800983 def testIgnoreMetadataFiles(self):
984 """Ignores metadata files for license checks."""
985 self.file_mock.return_value = ['foo/DIR_METADATA']
986 self.content_mock.return_value = u'team_email: "team@chromium.org"'
987 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
988
Chris McDonald7b63c8e2019-04-25 10:27:27 -0600989 def testIgnoreTopLevelExcludedPaths(self):
990 """Ignores excluded paths for license checks."""
991 self.file_mock.return_value = ['OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400992 self.content_mock.return_value = u'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +0900993 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -0700994
Mike Frysingerb2496652019-09-12 23:35:46 -0400995class CheckAOSPLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -0700996 """Tests for _check_aosp_license."""
997
998 def setUp(self):
999 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1000 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1001
1002 def testHeaders(self):
1003 """Accept old header styles."""
1004 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001005 u"""//
Alex Deymof5792ce2015-08-24 22:50:08 -07001006// Copyright (C) 2011 The Android Open Source Project
1007//
1008// Licensed under the Apache License, Version 2.0 (the "License");
1009// you may not use this file except in compliance with the License.
1010// You may obtain a copy of the License at
1011//
1012// http://www.apache.org/licenses/LICENSE-2.0
1013//
1014// Unless required by applicable law or agreed to in writing, software
1015// distributed under the License is distributed on an "AS IS" BASIS,
1016// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1017// See the License for the specific language governing permissions and
1018// limitations under the License.
1019//
1020""",
Mike Frysinger71e643e2019-09-13 17:26:39 -04001021 u"""#
Alex Deymof5792ce2015-08-24 22:50:08 -07001022# Copyright (c) 2015 The Android Open Source Project
1023#
1024# Licensed under the Apache License, Version 2.0 (the "License");
1025# you may not use this file except in compliance with the License.
1026# You may obtain a copy of the License at
1027#
1028# http://www.apache.org/licenses/LICENSE-2.0
1029#
1030# Unless required by applicable law or agreed to in writing, software
1031# distributed under the License is distributed on an "AS IS" BASIS,
1032# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1033# See the License for the specific language governing permissions and
1034# limitations under the License.
1035#
1036""",
1037 )
1038 self.file_mock.return_value = ['file']
1039 for header in HEADERS:
1040 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001041 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001042
1043 def testRejectNoLinesAround(self):
1044 """Reject headers missing the empty lines before/after the license."""
1045 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001046 u"""# Copyright (c) 2015 The Android Open Source Project
Alex Deymof5792ce2015-08-24 22:50:08 -07001047#
1048# Licensed under the Apache License, Version 2.0 (the "License");
1049# you may not use this file except in compliance with the License.
1050# You may obtain a copy of the License at
1051#
1052# http://www.apache.org/licenses/LICENSE-2.0
1053#
1054# Unless required by applicable law or agreed to in writing, software
1055# distributed under the License is distributed on an "AS IS" BASIS,
1056# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1057# See the License for the specific language governing permissions and
1058# limitations under the License.
1059""",
1060 )
1061 self.file_mock.return_value = ['file']
1062 for header in HEADERS:
1063 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001064 self.assertIsNotNone(pre_upload._check_aosp_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -04001065
Ken Turnerd07564b2018-02-08 17:57:59 +11001066 def testIgnoreExcludedPaths(self):
1067 """Ignores excluded paths for license checks."""
1068 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001069 self.content_mock.return_value = u'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001070 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +11001071
Chris McDonald7b63c8e2019-04-25 10:27:27 -06001072 def testIgnoreTopLevelExcludedPaths(self):
1073 """Ignores excluded paths for license checks."""
1074 self.file_mock.return_value = ['OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001075 self.content_mock.return_value = u'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001076 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
1077
Mike Frysinger98638102014-08-28 00:15:08 -04001078
Mike Frysingerb2496652019-09-12 23:35:46 -04001079class CheckLayoutConfTestCase(PreUploadTestCase):
Mike Frysingerd7734522015-02-26 16:12:43 -05001080 """Tests for _check_layout_conf."""
1081
1082 def setUp(self):
1083 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1084 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1085
1086 def assertAccepted(self, files, project='project', commit='fake sha1'):
1087 """Assert _check_layout_conf accepts |files|."""
1088 self.file_mock.return_value = files
1089 ret = pre_upload._check_layout_conf(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001090 self.assertIsNone(ret, msg='rejected with:\n%s' % ret)
Mike Frysingerd7734522015-02-26 16:12:43 -05001091
1092 def assertRejected(self, files, project='project', commit='fake sha1'):
1093 """Assert _check_layout_conf rejects |files|."""
1094 self.file_mock.return_value = files
1095 ret = pre_upload._check_layout_conf(project, commit)
1096 self.assertTrue(isinstance(ret, errors.HookFailure))
1097
1098 def GetLayoutConf(self, filters=()):
1099 """Return a valid layout.conf with |filters| lines removed."""
1100 all_lines = [
Mike Frysinger71e643e2019-09-13 17:26:39 -04001101 u'masters = portage-stable chromiumos',
1102 u'profile-formats = portage-2 profile-default-eapi',
1103 u'profile_eapi_when_unspecified = 5-progress',
1104 u'repo-name = link',
1105 u'thin-manifests = true',
1106 u'use-manifests = strict',
Mike Frysingerd7734522015-02-26 16:12:43 -05001107 ]
1108
1109 lines = []
1110 for line in all_lines:
1111 for filt in filters:
1112 if line.startswith(filt):
1113 break
1114 else:
1115 lines.append(line)
1116
Mike Frysinger71e643e2019-09-13 17:26:39 -04001117 return u'\n'.join(lines)
Mike Frysingerd7734522015-02-26 16:12:43 -05001118
1119 def testNoFilesToCheck(self):
1120 """Don't blow up when there are no layout.conf files."""
1121 self.assertAccepted([])
1122
1123 def testRejectRepoNameFile(self):
1124 """If profiles/repo_name is set, kick it out."""
1125 self.assertRejected(['profiles/repo_name'])
1126
1127 def testAcceptValidLayoutConf(self):
1128 """Accept a fully valid layout.conf."""
1129 self.content_mock.return_value = self.GetLayoutConf()
1130 self.assertAccepted(['metadata/layout.conf'])
1131
1132 def testAcceptUnknownKeys(self):
1133 """Accept keys we don't explicitly know about."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001134 self.content_mock.return_value = self.GetLayoutConf() + u'\nzzz-top = ok'
Mike Frysingerd7734522015-02-26 16:12:43 -05001135 self.assertAccepted(['metadata/layout.conf'])
1136
1137 def testRejectUnsorted(self):
1138 """Reject an unsorted layout.conf."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001139 self.content_mock.return_value = u'zzz-top = bad\n' + self.GetLayoutConf()
Mike Frysingerd7734522015-02-26 16:12:43 -05001140 self.assertRejected(['metadata/layout.conf'])
1141
1142 def testRejectMissingThinManifests(self):
1143 """Reject a layout.conf missing thin-manifests."""
1144 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001145 filters=[u'thin-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001146 self.assertRejected(['metadata/layout.conf'])
1147
1148 def testRejectMissingUseManifests(self):
1149 """Reject a layout.conf missing use-manifests."""
1150 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001151 filters=[u'use-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001152 self.assertRejected(['metadata/layout.conf'])
1153
1154 def testRejectMissingEapiFallback(self):
1155 """Reject a layout.conf missing profile_eapi_when_unspecified."""
1156 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001157 filters=[u'profile_eapi_when_unspecified'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001158 self.assertRejected(['metadata/layout.conf'])
1159
1160 def testRejectMissingRepoName(self):
1161 """Reject a layout.conf missing repo-name."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001162 self.content_mock.return_value = self.GetLayoutConf(filters=[u'repo-name'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001163 self.assertRejected(['metadata/layout.conf'])
1164
1165
Mike Frysingerb2496652019-09-12 23:35:46 -04001166class CommitMessageTestCase(PreUploadTestCase):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001167 """Test case for funcs that check commit messages."""
1168
1169 def setUp(self):
1170 self.msg_mock = self.PatchObject(pre_upload, '_get_commit_desc')
1171
1172 @staticmethod
1173 def CheckMessage(_project, _commit):
1174 raise AssertionError('Test class must declare CheckMessage')
1175 # This dummy return is to silence pylint warning W1111 so we don't have to
1176 # enable it for all the call sites below.
1177 return 1 # pylint: disable=W0101
1178
Alex Deymo643ac4c2015-09-03 10:40:50 -07001179 def assertMessageAccepted(self, msg, project=ProjectNamed('project'),
1180 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001181 """Assert _check_change_has_bug_field accepts |msg|."""
1182 self.msg_mock.return_value = msg
1183 ret = self.CheckMessage(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001184 self.assertIsNone(ret)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001185
Alex Deymo643ac4c2015-09-03 10:40:50 -07001186 def assertMessageRejected(self, msg, project=ProjectNamed('project'),
1187 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001188 """Assert _check_change_has_bug_field rejects |msg|."""
1189 self.msg_mock.return_value = msg
1190 ret = self.CheckMessage(project, commit)
1191 self.assertTrue(isinstance(ret, errors.HookFailure))
1192
1193
1194class CheckCommitMessageBug(CommitMessageTestCase):
1195 """Tests for _check_change_has_bug_field."""
1196
Alex Deymo643ac4c2015-09-03 10:40:50 -07001197 AOSP_PROJECT = pre_upload.Project(name='overlay', dir='', remote='aosp')
1198 CROS_PROJECT = pre_upload.Project(name='overlay', dir='', remote='cros')
1199
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001200 @staticmethod
1201 def CheckMessage(project, commit):
1202 return pre_upload._check_change_has_bug_field(project, commit)
1203
1204 def testNormal(self):
1205 """Accept a commit message w/a valid BUG."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001206 self.assertMessageAccepted('\nBUG=chromium:1234\n', self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001207 self.assertMessageAccepted('\nBUG=b:1234\n', self.CROS_PROJECT)
1208
1209 self.assertMessageAccepted('\nBug: 1234\n', self.AOSP_PROJECT)
1210 self.assertMessageAccepted('\nBug:1234\n', self.AOSP_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001211
1212 def testNone(self):
1213 """Accept BUG=None."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001214 self.assertMessageAccepted('\nBUG=None\n', self.CROS_PROJECT)
1215 self.assertMessageAccepted('\nBUG=none\n', self.CROS_PROJECT)
1216 self.assertMessageAccepted('\nBug: None\n', self.AOSP_PROJECT)
1217 self.assertMessageAccepted('\nBug:none\n', self.AOSP_PROJECT)
1218
1219 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1220 self.assertMessageRejected('\nBUG=NONE\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001221
1222 def testBlank(self):
1223 """Reject blank values."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001224 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1225 self.assertMessageRejected('\nBUG=\n', project)
1226 self.assertMessageRejected('\nBUG= \n', project)
1227 self.assertMessageRejected('\nBug:\n', project)
1228 self.assertMessageRejected('\nBug: \n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001229
1230 def testNotFirstLine(self):
1231 """Reject the first line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001232 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1233 self.assertMessageRejected('BUG=None\n\n\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001234
1235 def testNotInline(self):
1236 """Reject not at the start of line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001237 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1238 self.assertMessageRejected('\n BUG=None\n', project)
1239 self.assertMessageRejected('\n\tBUG=None\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001240
1241 def testOldTrackers(self):
1242 """Reject commit messages using old trackers."""
Mike Frysingera2f28252017-10-27 22:26:14 -04001243 self.assertMessageRejected('\nBUG=chrome-os-partner:1234\n',
1244 self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001245 self.assertMessageRejected('\nBUG=chromium-os:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001246
1247 def testNoTrackers(self):
1248 """Reject commit messages w/invalid trackers."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001249 self.assertMessageRejected('\nBUG=booga:1234\n', self.CROS_PROJECT)
1250 self.assertMessageRejected('\nBUG=br:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001251
1252 def testMissing(self):
1253 """Reject commit messages w/no BUG line."""
1254 self.assertMessageRejected('foo\n')
1255
1256 def testCase(self):
1257 """Reject bug lines that are not BUG."""
1258 self.assertMessageRejected('\nbug=none\n')
1259
Cheng Yuehb707c522020-01-02 14:06:59 +08001260 def testNotAfterTest(self):
1261 """Reject any TEST line before any BUG line."""
1262 test_field = 'TEST=i did not do it\n'
1263 middle_field = 'A random between line\n'
1264 for project, bug_field in ((self.AOSP_PROJECT, 'Bug:none\n'),
1265 (self.CROS_PROJECT, 'BUG=None\n')):
1266 self.assertMessageRejected(
1267 '\n' + test_field + middle_field + bug_field, project)
1268 self.assertMessageRejected(
1269 '\n' + test_field + bug_field, project)
1270
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001271
1272class CheckCommitMessageCqDepend(CommitMessageTestCase):
1273 """Tests for _check_change_has_valid_cq_depend."""
1274
1275 @staticmethod
1276 def CheckMessage(project, commit):
1277 return pre_upload._check_change_has_valid_cq_depend(project, commit)
1278
1279 def testNormal(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001280 """Accept valid Cq-Depends line."""
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001281 self.assertMessageAccepted('\nCq-Depend: chromium:1234\nChange-Id: I123')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001282
1283 def testInvalid(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001284 """Reject invalid Cq-Depends line."""
1285 self.assertMessageRejected('\nCq-Depend=chromium=1234\n')
1286 self.assertMessageRejected('\nCq-Depend: None\n')
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001287 self.assertMessageRejected('\nCq-Depend: chromium:1234\n\nChange-Id: I123')
Mike Frysingere39d0cd2019-11-25 13:30:06 -05001288 self.assertMessageRejected('\nCQ-DEPEND=1234\n')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001289
1290
Bernie Thompsonf8fea992016-01-14 10:27:18 -08001291class CheckCommitMessageContribution(CommitMessageTestCase):
1292 """Tests for _check_change_is_contribution."""
1293
1294 @staticmethod
1295 def CheckMessage(project, commit):
1296 return pre_upload._check_change_is_contribution(project, commit)
1297
1298 def testNormal(self):
1299 """Accept a commit message which is a contribution."""
1300 self.assertMessageAccepted('\nThis is cool code I am contributing.\n')
1301
1302 def testFailureLowerCase(self):
1303 """Deny a commit message which is not a contribution."""
1304 self.assertMessageRejected('\nThis is not a contribution.\n')
1305
1306 def testFailureUpperCase(self):
1307 """Deny a commit message which is not a contribution."""
1308 self.assertMessageRejected('\nNOT A CONTRIBUTION\n')
1309
1310
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001311class CheckCommitMessageTest(CommitMessageTestCase):
1312 """Tests for _check_change_has_test_field."""
1313
1314 @staticmethod
1315 def CheckMessage(project, commit):
1316 return pre_upload._check_change_has_test_field(project, commit)
1317
1318 def testNormal(self):
1319 """Accept a commit message w/a valid TEST."""
1320 self.assertMessageAccepted('\nTEST=i did it\n')
1321
1322 def testNone(self):
1323 """Accept TEST=None."""
1324 self.assertMessageAccepted('\nTEST=None\n')
1325 self.assertMessageAccepted('\nTEST=none\n')
1326
1327 def testBlank(self):
1328 """Reject blank values."""
1329 self.assertMessageRejected('\nTEST=\n')
1330 self.assertMessageRejected('\nTEST= \n')
1331
1332 def testNotFirstLine(self):
1333 """Reject the first line."""
1334 self.assertMessageRejected('TEST=None\n\n\n')
1335
1336 def testNotInline(self):
1337 """Reject not at the start of line."""
1338 self.assertMessageRejected('\n TEST=None\n')
1339 self.assertMessageRejected('\n\tTEST=None\n')
1340
1341 def testMissing(self):
1342 """Reject commit messages w/no TEST line."""
1343 self.assertMessageRejected('foo\n')
1344
1345 def testCase(self):
1346 """Reject bug lines that are not TEST."""
1347 self.assertMessageRejected('\ntest=none\n')
1348
1349
1350class CheckCommitMessageChangeId(CommitMessageTestCase):
1351 """Tests for _check_change_has_proper_changeid."""
1352
1353 @staticmethod
1354 def CheckMessage(project, commit):
1355 return pre_upload._check_change_has_proper_changeid(project, commit)
1356
1357 def testNormal(self):
1358 """Accept a commit message w/a valid Change-Id."""
1359 self.assertMessageAccepted('foo\n\nChange-Id: I1234\n')
1360
1361 def testBlank(self):
1362 """Reject blank values."""
1363 self.assertMessageRejected('\nChange-Id:\n')
1364 self.assertMessageRejected('\nChange-Id: \n')
1365
1366 def testNotFirstLine(self):
1367 """Reject the first line."""
1368 self.assertMessageRejected('TEST=None\n\n\n')
1369
1370 def testNotInline(self):
1371 """Reject not at the start of line."""
1372 self.assertMessageRejected('\n Change-Id: I1234\n')
1373 self.assertMessageRejected('\n\tChange-Id: I1234\n')
1374
1375 def testMissing(self):
1376 """Reject commit messages missing the line."""
1377 self.assertMessageRejected('foo\n')
1378
1379 def testCase(self):
1380 """Reject bug lines that are not Change-Id."""
1381 self.assertMessageRejected('\nchange-id: I1234\n')
1382 self.assertMessageRejected('\nChange-id: I1234\n')
1383 self.assertMessageRejected('\nChange-ID: I1234\n')
1384
1385 def testEnd(self):
1386 """Reject Change-Id's that are not last."""
1387 self.assertMessageRejected('\nChange-Id: I1234\nbar\n')
1388
Mike Frysinger02b88bd2014-11-21 00:29:38 -05001389 def testSobTag(self):
1390 """Permit s-o-b tags to follow the Change-Id."""
1391 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nSigned-off-by: Hi\n')
1392
LaMont Jones237f3ef2020-01-22 10:40:52 -07001393 def testCqClTag(self):
1394 """Permit Cq-Cl-Tag tags to follow the Change-Id."""
1395 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nCq-Cl-Tag: Hi\n')
1396
LaMont Jonesfb5e8bf2020-03-03 12:50:06 -07001397 def testCqIncludeTrybotsTag(self):
1398 """Permit Cq-Include-Trybots tags to follow the Change-Id."""
1399 self.assertMessageAccepted(
1400 'foo\n\nChange-Id: I1234\nCq-Include-Trybots: chromeos/cq:foo\n')
1401
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001402
Jack Neus8edbf642019-07-10 16:08:31 -06001403class CheckCommitMessageNoOEM(CommitMessageTestCase):
1404 """Tests for _check_change_no_include_oem."""
1405
1406 @staticmethod
1407 def CheckMessage(project, commit):
1408 return pre_upload._check_change_no_include_oem(project, commit)
1409
1410 def testNormal(self):
1411 """Accept a commit message w/o reference to an OEM/ODM."""
1412 self.assertMessageAccepted('foo\n')
1413
1414 def testHasOEM(self):
1415 """Catch commit messages referencing OEMs."""
1416 self.assertMessageRejected('HP Project\n\n')
1417 self.assertMessageRejected('hewlett-packard\n')
1418 self.assertMessageRejected('Hewlett\nPackard\n')
1419 self.assertMessageRejected('Dell Chromebook\n\n\n')
1420 self.assertMessageRejected('product@acer.com\n')
1421 self.assertMessageRejected('This is related to Asus\n')
1422 self.assertMessageRejected('lenovo machine\n')
1423
1424 def testHasODM(self):
1425 """Catch commit messages referencing ODMs."""
1426 self.assertMessageRejected('new samsung laptop\n\n')
1427 self.assertMessageRejected('pegatron(ems) project\n')
1428 self.assertMessageRejected('new Wistron device\n')
1429
1430 def testContainsOEM(self):
1431 """Check that the check handles word boundaries properly."""
1432 self.assertMessageAccepted('oheahpohea')
1433 self.assertMessageAccepted('Play an Asus7 guitar chord.\n\n')
1434
1435 def testTag(self):
1436 """Check that the check ignores tags."""
1437 self.assertMessageAccepted(
1438 'Harmless project\n'
1439 'Reviewed-by: partner@asus.corp-partner.google.com\n'
1440 'Tested-by: partner@hp.corp-partner.google.com\n'
1441 'Signed-off-by: partner@dell.corp-partner.google.com\n'
1442 'Commit-Queue: partner@lenovo.corp-partner.google.com\n'
Jack Neus8edbf642019-07-10 16:08:31 -06001443 'CC: partner@acer.corp-partner.google.com\n'
1444 'Acked-by: partner@hewlett-packard.corp-partner.google.com\n')
1445 self.assertMessageRejected(
1446 'Asus project\n'
1447 'Reviewed-by: partner@asus.corp-partner.google.com')
Mike Frysingerbb34a222019-07-31 14:40:46 -04001448 self.assertMessageRejected(
1449 'my project\n'
1450 'Bad-tag: partner@asus.corp-partner.google.com')
Jack Neus8edbf642019-07-10 16:08:31 -06001451
1452
Mike Frysinger36b2ebc2014-10-31 14:02:03 -04001453class CheckCommitMessageStyle(CommitMessageTestCase):
1454 """Tests for _check_commit_message_style."""
1455
1456 @staticmethod
1457 def CheckMessage(project, commit):
1458 return pre_upload._check_commit_message_style(project, commit)
1459
1460 def testNormal(self):
1461 """Accept valid commit messages."""
1462 self.assertMessageAccepted('one sentence.\n')
1463 self.assertMessageAccepted('some.module: do it!\n')
1464 self.assertMessageAccepted('one line\n\nmore stuff here.')
1465
1466 def testNoBlankSecondLine(self):
1467 """Reject messages that have stuff on the second line."""
1468 self.assertMessageRejected('one sentence.\nbad fish!\n')
1469
1470 def testFirstLineMultipleSentences(self):
1471 """Reject messages that have more than one sentence in the summary."""
1472 self.assertMessageRejected('one sentence. two sentence!\n')
1473
1474 def testFirstLineTooLone(self):
1475 """Reject first lines that are too long."""
1476 self.assertMessageRejected('o' * 200)
1477
1478
Mike Frysinger292b45d2014-11-25 01:17:10 -05001479def DiffEntry(src_file=None, dst_file=None, src_mode=None, dst_mode='100644',
1480 status='M'):
1481 """Helper to create a stub RawDiffEntry object"""
1482 if src_mode is None:
1483 if status == 'A':
1484 src_mode = '000000'
1485 elif status == 'M':
1486 src_mode = dst_mode
1487 elif status == 'D':
1488 src_mode = dst_mode
1489 dst_mode = '000000'
1490
1491 src_sha = dst_sha = 'abc'
1492 if status == 'D':
1493 dst_sha = '000000'
1494 elif status == 'A':
1495 src_sha = '000000'
1496
1497 return git.RawDiffEntry(src_mode=src_mode, dst_mode=dst_mode, src_sha=src_sha,
1498 dst_sha=dst_sha, status=status, score=None,
1499 src_file=src_file, dst_file=dst_file)
1500
1501
Mike Frysingerb2496652019-09-12 23:35:46 -04001502class HelpersTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001503 """Various tests for utility functions."""
1504
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001505 def setUp(self):
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001506 os.chdir(self.tempdir)
1507
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001508 self.PatchObject(git, 'RawDiff', return_value=[
1509 # A modified normal file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001510 DiffEntry(src_file='buildbot/constants.py', status='M'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001511 # A new symlink file.
Bob Haarman0dc1f942020-10-03 00:06:59 +00001512 DiffEntry(dst_file='scripts/cros_env_allowlist', dst_mode='120000',
Mike Frysinger292b45d2014-11-25 01:17:10 -05001513 status='A'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001514 # A deleted file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001515 DiffEntry(src_file='scripts/sync_sonic.py', status='D'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001516 ])
1517
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001518 def _WritePresubmitIgnoreFile(self, subdir, data):
1519 """Writes to a .presubmitignore file in the passed-in subdirectory."""
1520 directory = os.path.join(self.tempdir, subdir)
1521 if not os.path.exists(directory):
1522 os.makedirs(directory)
1523 osutils.WriteFile(os.path.join(directory, pre_upload._IGNORE_FILE), data)
1524
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001525 def testGetAffectedFilesNoDeletesNoRelative(self):
1526 """Verify _get_affected_files() works w/no delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001527 path = os.getcwd()
1528 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1529 relative=False)
1530 exp_files = [os.path.join(path, 'buildbot/constants.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001531 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001532
1533 def testGetAffectedFilesDeletesNoRelative(self):
1534 """Verify _get_affected_files() works w/delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001535 path = os.getcwd()
1536 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1537 relative=False)
1538 exp_files = [os.path.join(path, 'buildbot/constants.py'),
1539 os.path.join(path, 'scripts/sync_sonic.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001540 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001541
1542 def testGetAffectedFilesNoDeletesRelative(self):
1543 """Verify _get_affected_files() works w/no delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001544 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1545 relative=True)
1546 exp_files = ['buildbot/constants.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001547 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001548
1549 def testGetAffectedFilesDeletesRelative(self):
1550 """Verify _get_affected_files() works w/delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001551 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1552 relative=True)
1553 exp_files = ['buildbot/constants.py', 'scripts/sync_sonic.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001554 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001555
Mike Frysinger292b45d2014-11-25 01:17:10 -05001556 def testGetAffectedFilesDetails(self):
1557 """Verify _get_affected_files() works w/full_details."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001558 files = pre_upload._get_affected_files('HEAD', full_details=True,
1559 relative=True)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001560 self.assertEqual(files[0].src_file, 'buildbot/constants.py')
Mike Frysinger292b45d2014-11-25 01:17:10 -05001561
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001562 def testGetAffectedFilesPresubmitIgnoreDirectory(self):
1563 """Verify .presubmitignore can be used to exclude a directory."""
1564 self._WritePresubmitIgnoreFile('.', 'buildbot/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001565 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001566
1567 def testGetAffectedFilesPresubmitIgnoreDirectoryWildcard(self):
1568 """Verify .presubmitignore can be used with a directory wildcard."""
1569 self._WritePresubmitIgnoreFile('.', '*/constants.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001570 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001571
1572 def testGetAffectedFilesPresubmitIgnoreWithinDirectory(self):
1573 """Verify .presubmitignore can be placed in a subdirectory."""
1574 self._WritePresubmitIgnoreFile('buildbot', '*.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001575 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001576
1577 def testGetAffectedFilesPresubmitIgnoreDoesntMatch(self):
1578 """Verify .presubmitignore has no effect when it doesn't match a file."""
1579 self._WritePresubmitIgnoreFile('buildbot', '*.txt')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001580 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True),
1581 ['buildbot/constants.py'])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001582
1583 def testGetAffectedFilesPresubmitIgnoreAddedFile(self):
1584 """Verify .presubmitignore matches added files."""
1585 self._WritePresubmitIgnoreFile('.', 'buildbot/\nscripts/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001586 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True,
1587 include_symlinks=True),
1588 [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001589
1590 def testGetAffectedFilesPresubmitIgnoreSkipIgnoreFile(self):
1591 """Verify .presubmitignore files are automatically skipped."""
1592 self.PatchObject(git, 'RawDiff', return_value=[
1593 DiffEntry(src_file='.presubmitignore', status='M')
1594 ])
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001595 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Mike Frysinger292b45d2014-11-25 01:17:10 -05001596
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001597
Mike Frysingerb2496652019-09-12 23:35:46 -04001598class ExecFilesTest(PreUploadTestCase):
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001599 """Tests for _check_exec_files."""
1600
1601 def setUp(self):
1602 self.diff_mock = self.PatchObject(git, 'RawDiff')
1603
1604 def testNotExec(self):
1605 """Do not flag files that are not executable."""
1606 self.diff_mock.return_value = [
1607 DiffEntry(src_file='make.conf', dst_mode='100644', status='A'),
1608 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001609 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001610
1611 def testExec(self):
1612 """Flag files that are executable."""
1613 self.diff_mock.return_value = [
1614 DiffEntry(src_file='make.conf', dst_mode='100755', status='A'),
1615 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001616 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001617
1618 def testDeletedExec(self):
1619 """Ignore bad files that are being deleted."""
1620 self.diff_mock.return_value = [
1621 DiffEntry(src_file='make.conf', dst_mode='100755', status='D'),
1622 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001623 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001624
1625 def testModifiedExec(self):
1626 """Flag bad files that weren't exec, but now are."""
1627 self.diff_mock.return_value = [
1628 DiffEntry(src_file='make.conf', src_mode='100644', dst_mode='100755',
1629 status='M'),
1630 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001631 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001632
1633 def testNormalExec(self):
1634 """Don't flag normal files (e.g. scripts) that are executable."""
1635 self.diff_mock.return_value = [
1636 DiffEntry(src_file='foo.sh', dst_mode='100755', status='A'),
1637 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001638 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001639
1640
Mike Frysingerb2496652019-09-12 23:35:46 -04001641class CheckForUprev(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger292b45d2014-11-25 01:17:10 -05001642 """Tests for _check_for_uprev."""
1643
1644 def setUp(self):
1645 self.file_mock = self.PatchObject(git, 'RawDiff')
1646
1647 def _Files(self, files):
1648 """Create |files| in the tempdir and return full paths to them."""
1649 for obj in files:
1650 if obj.status == 'D':
1651 continue
1652 if obj.dst_file is None:
1653 f = obj.src_file
1654 else:
1655 f = obj.dst_file
1656 osutils.Touch(os.path.join(self.tempdir, f), makedirs=True)
1657 return files
1658
1659 def assertAccepted(self, files, project='project', commit='fake sha1'):
1660 """Assert _check_for_uprev accepts |files|."""
1661 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001662 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1663 project_top=self.tempdir)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001664 self.assertIsNone(ret)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001665
1666 def assertRejected(self, files, project='project', commit='fake sha1'):
1667 """Assert _check_for_uprev rejects |files|."""
1668 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001669 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1670 project_top=self.tempdir)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001671 self.assertTrue(isinstance(ret, errors.HookFailure))
1672
Bob Haarman0dc1f942020-10-03 00:06:59 +00001673 def testAllowlistOverlay(self):
1674 """Skip checks on allowed overlays."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001675 self.assertAccepted([DiffEntry(src_file='cat/pkg/pkg-0.ebuild')],
1676 project='chromiumos/overlays/portage-stable')
1677
Bob Haarman0dc1f942020-10-03 00:06:59 +00001678 def testAllowlistFiles(self):
1679 """Skip checks on allowed files."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001680 files = ['ChangeLog', 'Manifest', 'metadata.xml']
1681 self.assertAccepted([DiffEntry(src_file=os.path.join('c', 'p', x),
1682 status='M')
1683 for x in files])
1684
1685 def testRejectBasic(self):
1686 """Reject ebuilds missing uprevs."""
1687 self.assertRejected([DiffEntry(src_file='c/p/p-0.ebuild', status='M')])
1688
1689 def testNewPackage(self):
1690 """Accept new ebuilds w/out uprevs."""
1691 self.assertAccepted([DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1692 self.assertAccepted([DiffEntry(src_file='c/p/p-0-r12.ebuild', status='A')])
1693
1694 def testModifiedFilesOnly(self):
1695 """Reject ebuilds w/out uprevs and changes in files/."""
1696 osutils.Touch(os.path.join(self.tempdir, 'cat/pkg/pkg-0.ebuild'),
1697 makedirs=True)
1698 self.assertRejected([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1699 self.assertRejected([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1700
1701 def testFilesNoEbuilds(self):
1702 """Ignore changes to paths w/out ebuilds."""
1703 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1704 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1705
1706 def testModifiedFilesWithUprev(self):
1707 """Accept ebuilds w/uprevs and changes in files/."""
1708 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='A'),
1709 DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1710 self.assertAccepted([
1711 DiffEntry(src_file='c/p/files/f', status='M'),
1712 DiffEntry(src_file='c/p/p-0-r1.ebuild', src_mode='120000',
1713 dst_file='c/p/p-0-r2.ebuild', dst_mode='120000', status='R')])
1714
Gwendal Grignoua3086c32014-12-09 11:17:22 -08001715 def testModifiedFilesWith9999(self):
1716 """Accept 9999 ebuilds and changes in files/."""
1717 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M'),
1718 DiffEntry(src_file='c/p/p-9999.ebuild', status='M')])
1719
C Shapiroae157ae2017-09-18 16:24:03 -06001720 def testModifiedFilesIn9999SubDirWithout9999Change(self):
1721 """Accept changes in files/ with a parent 9999 ebuild"""
1722 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
1723 os.makedirs(os.path.dirname(ebuild_9999_file))
1724 osutils.WriteFile(ebuild_9999_file, 'fake')
1725 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M')])
1726
Stephen Boyd6bf5ea82020-10-15 00:02:07 -07001727 def testModifiedFilesAndProfilesWith9999(self):
1728 """Accept changes in files/ with a parent 9999 ebuild and profile change"""
1729 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
1730 os.makedirs(os.path.dirname(ebuild_9999_file))
1731 osutils.WriteFile(ebuild_9999_file, 'fake')
1732 self.assertAccepted([
1733 DiffEntry(src_file='c/p/files/f', status='M'),
1734 DiffEntry(src_file='c/profiles/base/make.defaults', status='M')])
1735
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001736
Mike Frysingerb2496652019-09-12 23:35:46 -04001737class DirectMainTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger55f85b52014-12-18 14:45:21 -05001738 """Tests for direct_main()"""
1739
1740 def setUp(self):
1741 self.hooks_mock = self.PatchObject(pre_upload, '_run_project_hooks',
1742 return_value=None)
1743
1744 def testNoArgs(self):
1745 """If run w/no args, should check the current dir."""
1746 ret = pre_upload.direct_main([])
1747 self.assertEqual(ret, 0)
1748 self.hooks_mock.assert_called_once_with(
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001749 mock.ANY, proj_dir=os.getcwd(), commit_list=[], presubmit=mock.ANY,
1750 config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001751
1752 def testExplicitDir(self):
1753 """Verify we can run on a diff dir."""
1754 # Use the chromite dir since we know it exists.
1755 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR])
1756 self.assertEqual(ret, 0)
1757 self.hooks_mock.assert_called_once_with(
1758 mock.ANY, proj_dir=constants.CHROMITE_DIR, commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001759 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001760
1761 def testBogusProject(self):
1762 """A bogus project name should be fine (use default settings)."""
1763 # Use the chromite dir since we know it exists.
1764 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR,
1765 '--project', 'foooooooooo'])
1766 self.assertEqual(ret, 0)
1767 self.hooks_mock.assert_called_once_with(
1768 'foooooooooo', proj_dir=constants.CHROMITE_DIR, commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001769 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001770
1771 def testBogustProjectNoDir(self):
1772 """Make sure --dir is detected even with --project."""
1773 ret = pre_upload.direct_main(['--project', 'foooooooooo'])
1774 self.assertEqual(ret, 0)
1775 self.hooks_mock.assert_called_once_with(
Mike Frysinger9ba001a2020-11-20 01:02:11 -05001776 'foooooooooo', proj_dir=os.path.dirname(os.path.abspath(__file__)),
1777 commit_list=[], presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001778
1779 def testNoGitDir(self):
1780 """We should die when run on a non-git dir."""
1781 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
1782 ['--dir', self.tempdir])
1783
1784 def testNoDir(self):
1785 """We should die when run on a missing dir."""
1786 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
1787 ['--dir', os.path.join(self.tempdir, 'foooooooo')])
1788
1789 def testCommitList(self):
1790 """Any args on the command line should be treated as commits."""
1791 commits = ['sha1', 'sha2', 'shaaaaaaaaaaaan']
1792 ret = pre_upload.direct_main(commits)
1793 self.assertEqual(ret, 0)
1794 self.hooks_mock.assert_called_once_with(
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001795 mock.ANY, proj_dir=mock.ANY, commit_list=commits, presubmit=mock.ANY,
1796 config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001797
1798
Mike Frysingerb2496652019-09-12 23:35:46 -04001799class CheckRustfmtTest(PreUploadTestCase):
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001800 """Tests for _check_rustfmt."""
1801
1802 def setUp(self):
1803 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1804
1805 def testBadRustFile(self):
1806 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
1807 # Bad because it's missing trailing newline.
Mike Frysingere85b0062019-08-20 15:10:33 -04001808 content = 'fn main() {}'
1809 self.content_mock.return_value = content
1810 self.PatchObject(pre_upload, '_run_command', return_value=content + '\n')
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001811 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1812 self.assertIsNotNone(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001813 self.assertEqual('Files not formatted with rustfmt: '
1814 "(run 'cargo fmt' to fix)",
1815 failure.msg)
1816 self.assertEqual(['a.rs'], failure.items)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001817
1818 def testGoodRustFile(self):
1819 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
Mike Frysingere85b0062019-08-20 15:10:33 -04001820 content = 'fn main() {}\n'
1821 self.content_mock.return_value = content
1822 self.PatchObject(pre_upload, '_run_command', return_value=content)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001823 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1824 self.assertIsNone(failure)
1825
1826 def testFilterNonRustFiles(self):
1827 self.PatchObject(pre_upload, '_get_affected_files',
1828 return_value=['a.cc', 'a.rsa', 'a.irs', 'rs.cc'])
1829 self.content_mock.return_value = 'fn main() {\n}'
1830 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1831 self.assertIsNone(failure)
1832
1833
Keiichi Watanabe6ab73fd2020-07-14 23:42:02 +09001834class GetCargoClippyParserTest(cros_test_lib.TestCase):
1835 """Tests for _get_cargo_clippy_parser."""
1836
1837 def testSingleProject(self):
1838 parser = pre_upload._get_cargo_clippy_parser()
1839 args = parser.parse_args(['--project', 'foo'])
1840 self.assertEqual(args.project,
1841 [pre_upload.ClippyProject(root='foo', script=None)])
1842
1843 def testMultipleProjects(self):
1844 parser = pre_upload._get_cargo_clippy_parser()
1845 args = parser.parse_args(['--project', 'foo:bar',
1846 '--project', 'baz'])
1847 self.assertCountEqual(args.project,
1848 [pre_upload.ClippyProject(root='foo', script='bar'),
1849 pre_upload.ClippyProject(root='baz', script=None)])
1850
1851
1852class CheckCargoClippyTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
1853 """Tests for _check_cargo_clippy."""
1854
1855 def setUp(self):
1856 self.project = pre_upload.Project(name='PROJECT', dir=self.tempdir,
1857 remote=None)
1858
1859 def testClippy(self):
1860 """Verify clippy is called when a monitored file was changed."""
1861 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
1862
1863 self.PatchObject(pre_upload, '_get_affected_files',
1864 return_value=[f'{self.project.dir}/repo_a/a.rs'])
1865
1866 ret = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
1867 options=['--project=repo_a',
1868 '--project=repo_b:foo'])
1869 self.assertFalse(ret)
1870
1871 # Check if `cargo clippy` ran.
1872 called = False
1873 for args, _ in rc_mock.call_args_list:
1874 cmd = args[0]
1875 if len(cmd) > 1 and cmd[0] == 'cargo' and cmd[1] == 'clippy':
1876 called = True
1877 break
1878
1879 self.assertTrue(called)
1880
1881 def testDontRun(self):
1882 """Skip checks when no monitored files are modified."""
1883 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
1884
1885 # A file under `repo_a` was monitored.
1886 self.PatchObject(pre_upload, '_get_affected_files',
1887 return_value=[f'{self.project.dir}/repo_a/a.rs'])
1888 # But, we only care about files under `repo_b`.
1889 errs = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
1890 options=['--project=repo_b:foo'])
1891
1892 self.assertFalse(errs)
1893
1894 rc_mock.assert_not_called()
1895
1896 def testCustomScript(self):
1897 """Verify project-specific script is used."""
1898 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
1899
1900 self.PatchObject(pre_upload, '_get_affected_files',
1901 return_value=[f'{self.project.dir}/repo_b/b.rs'])
1902
1903 errs = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
1904 options=['--project=repo_a',
1905 '--project=repo_b:foo'])
1906 self.assertFalse(errs)
1907
1908 # Check if the script `foo` ran.
1909 called = False
1910 for args, _ in rc_mock.call_args_list:
1911 cmd = args[0]
1912 if len(cmd) > 0 and cmd[0] == os.path.join(self.project.dir, 'foo'):
1913 called = True
1914 break
1915
1916 self.assertTrue(called)
1917
1918
Mike Frysinger180ecd62020-08-19 00:41:51 -04001919class OverrideHooksProcessing(PreUploadTestCase):
1920 """Verify _get_override_hooks processing."""
1921
1922 @staticmethod
1923 def parse(data):
1924 """Helper to create a config & parse it."""
1925 cfg = configparser.ConfigParser()
1926 cfg.read_string(data)
1927 return pre_upload._get_override_hooks(cfg)
1928
1929 def testHooks(self):
1930 """Verify we reject unknown hook names (e.g. typos)."""
1931 with self.assertRaises(ValueError) as e:
1932 self.parse("""
1933[Hook Overrides]
1934foobar: true
1935""")
1936 self.assertIn('foobar', str(e.exception))
1937
1938 def testImplicitDisable(self):
1939 """Verify non-common hooks aren't enabled by default."""
1940 enabled, _ = self.parse('')
1941 self.assertNotIn(pre_upload._run_checkpatch, enabled)
1942
1943 def testExplicitDisable(self):
1944 """Verify hooks disabled are disabled."""
1945 _, disabled = self.parse("""
1946[Hook Overrides]
1947tab_check: false
1948""")
1949 self.assertIn(pre_upload._check_no_tabs, disabled)
1950
1951 def testExplicitEnable(self):
1952 """Verify hooks enabled are enabled."""
1953 enabled, _ = self.parse("""
1954[Hook Overrides]
1955tab_check: true
1956""")
1957 self.assertIn(pre_upload._check_no_tabs, enabled)
1958
1959 def testOptions(self):
1960 """Verify hook options are loaded."""
1961 enabled, _ = self.parse("""
1962[Hook Overrides Options]
1963keyword_check: --kw
1964""")
1965 for func in enabled:
1966 if func.__name__ == 'keyword_check':
1967 self.assertIn('options', func.keywords)
1968 self.assertEqual(func.keywords['options'], ['--kw'])
1969 break
1970 else:
1971 self.fail('could not find "keyword_check" enabled hook')
1972
1973 def testSignOffField(self):
1974 """Verify signoff field handling."""
1975 # Enforce no s-o-b by default.
1976 enabled, disabled = self.parse('')
1977 self.assertIn(pre_upload._check_change_has_no_signoff_field, enabled)
1978 self.assertNotIn(pre_upload._check_change_has_signoff_field, enabled)
1979 self.assertNotIn(pre_upload._check_change_has_signoff_field, disabled)
1980
1981 # If disabled, don't enforce either policy.
1982 enabled, disabled = self.parse("""
1983[Hook Overrides]
1984signoff_check: false
1985""")
1986 self.assertNotIn(pre_upload._check_change_has_no_signoff_field, enabled)
1987 self.assertNotIn(pre_upload._check_change_has_signoff_field, enabled)
1988 self.assertIn(pre_upload._check_change_has_signoff_field, disabled)
1989
1990 # If enabled, require s-o-b.
1991 enabled, disabled = self.parse("""
1992[Hook Overrides]
1993signoff_check: true
1994""")
1995 self.assertNotIn(pre_upload._check_change_has_no_signoff_field, enabled)
1996 self.assertIn(pre_upload._check_change_has_signoff_field, enabled)
1997 self.assertNotIn(pre_upload._check_change_has_signoff_field, disabled)
1998
1999 def testBranchField(self):
2000 """Verify branch field enabling."""
2001 # Should be disabled by default.
2002 enabled, disabled = self.parse('')
2003 self.assertIn(pre_upload._check_change_has_no_branch_field, enabled)
2004 self.assertNotIn(pre_upload._check_change_has_branch_field, enabled)
2005 self.assertNotIn(pre_upload._check_change_has_branch_field, disabled)
2006
2007 # Should be disabled if requested.
2008 enabled, disabled = self.parse("""
2009[Hook Overrides]
2010branch_check: false
2011""")
2012 self.assertIn(pre_upload._check_change_has_no_branch_field, enabled)
2013 self.assertNotIn(pre_upload._check_change_has_branch_field, enabled)
2014 self.assertIn(pre_upload._check_change_has_branch_field, disabled)
2015
2016 # Should be enabled if requested.
2017 enabled, disabled = self.parse("""
2018[Hook Overrides]
2019branch_check: true
2020""")
2021 self.assertNotIn(pre_upload._check_change_has_no_branch_field, enabled)
2022 self.assertIn(pre_upload._check_change_has_branch_field, enabled)
2023 self.assertNotIn(pre_upload._check_change_has_branch_field, disabled)
2024
2025
Tom Hughes1ed799d2020-09-25 14:37:28 -07002026class ProjectHooksProcessing(PreUploadTestCase, cros_test_lib.TempDirTestCase):
2027 """Verify _get_project_hooks processing."""
2028
2029 def parse(self, data):
2030 """Helper to write config and parse it."""
2031 filename = os.path.join(self.tempdir, 'config')
2032 osutils.WriteFile(filename, data)
2033 return pre_upload._get_project_hooks(project='test', presubmit=True,
2034 config_file=filename)
2035
2036 def testClangFormatCheckDefault(self):
2037 """Verify clang-format check disabled by default."""
2038 hooks = self.parse('')
2039 for func in hooks:
2040 self.assertNotEqual(func.__name__, '_check_clang_format')
2041 self.assertNotEqual(func.__name__, 'clang_format_check')
2042
2043 def testClangFormatCheckDisabled(self):
2044 """Verify clang-format check disabled when requested."""
2045 hooks = self.parse("""
2046[Hook Overrides]
2047clang_format_check: false
2048""")
2049 for func in hooks:
2050 self.assertNotEqual(func.__name__, '_check_clang_format')
2051 self.assertNotEqual(func.__name__, 'clang_format_check')
2052
2053 def testClangFormatCheckEnabled(self):
2054 """Verify clang-format check enabled when requested."""
2055 hooks = self.parse("""
2056[Hook Overrides]
2057clang_format_check: true
2058""")
2059 for func in hooks:
2060 if func.__name__ == '_check_clang_format':
2061 self.assertFalse(hasattr(func, 'keywords'))
2062 break
2063 else:
2064 self.fail('could not find "_check_clang_format" enabled hook')
2065
2066 def testClangFormatCheckEnabledWithOptions(self):
2067 """Verify clang-format check has options when provided."""
2068 hooks = self.parse("""
2069[Hook Overrides]
2070clang_format_check: true
2071
2072[Hook Overrides Options]
2073clang_format_check:
2074 some_dir/
2075""")
2076 for func in hooks:
2077 if func.__name__ == 'clang_format_check':
2078 self.assertIn('options', func.keywords)
2079 self.assertEqual(func.keywords['options'], ['some_dir/'])
2080 break
2081 else:
2082 self.fail('could not find "clang_format_check" enabled hook')
2083
2084
Jon Salz98255932012-08-18 14:48:02 +08002085if __name__ == '__main__':
Mike Frysinger884a8dd2015-05-17 03:43:43 -04002086 cros_test_lib.main(module=__name__)