blob: 93a8f6fd2e71cdbed8d0333b5e51e8af042fe77b [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):
355 self.orig_cwd = os.getcwd()
356 os.chdir(self.tempdir)
357 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
358 self.desc_mock = self.PatchObject(pre_upload, '_get_commit_desc')
359
360 def tearDown(self):
361 os.chdir(self.orig_cwd)
362
363 def _WriteAliasFile(self, filename, project):
364 """Writes a project name to a file, creating directories if needed."""
365 os.makedirs(os.path.dirname(filename))
366 osutils.WriteFile(filename, project)
367
368 def testInvalidPrefix(self):
369 """Report an error when the prefix doesn't match the base directory."""
370 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
371 self.desc_mock.return_value = 'bar: Some commit'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700372 failure = pre_upload._check_project_prefix(ProjectNamed('PROJECT'),
373 'COMMIT')
Daniel Erata350fd32014-09-29 14:02:34 -0700374 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400375 self.assertEqual('The commit title for changes affecting only foo should '
376 'start with "foo: "', failure.msg)
Daniel Erata350fd32014-09-29 14:02:34 -0700377
378 def testValidPrefix(self):
379 """Use a prefix that matches the base directory."""
380 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
381 self.desc_mock.return_value = 'foo: Change some files.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700382 self.assertFalse(
383 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700384
385 def testAliasFile(self):
386 """Use .project_alias to override the project name."""
387 self._WriteAliasFile('foo/.project_alias', 'project')
388 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/bar.cc']
389 self.desc_mock.return_value = 'project: Use an alias.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700390 self.assertFalse(
391 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700392
393 def testAliasFileWithSubdirs(self):
394 """Check that .project_alias is used when only modifying subdirectories."""
395 self._WriteAliasFile('foo/.project_alias', 'project')
396 self.file_mock.return_value = [
397 'foo/subdir/foo.cc',
398 'foo/subdir/bar.cc'
399 'foo/subdir/blah/baz.cc'
400 ]
401 self.desc_mock.return_value = 'project: Alias with subdirs.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700402 self.assertFalse(
403 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700404
405
Mike Frysingerb2496652019-09-12 23:35:46 -0400406class CheckFilePathCharTypeTest(PreUploadTestCase):
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900407 """Tests for _check_filepath_chartype."""
408
409 def setUp(self):
410 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
411
412 def testCheck(self):
413 self.PatchObject(pre_upload, '_get_affected_files', return_value=['x.cc'])
414 self.diff_mock.return_value = [
415 (1, 'base::FilePath'), # OK
416 (2, 'base::FilePath::CharType'), # NG
417 (3, 'base::FilePath::StringType'), # NG
418 (4, 'base::FilePath::StringPieceType'), # NG
Satoru Takabayashi4ca37922018-08-08 10:16:38 +0900419 (5, 'base::FilePath::FromUTF8Unsafe'), # NG
420 (6, 'FilePath::CharType'), # NG
421 (7, 'FilePath::StringType'), # NG
422 (8, 'FilePath::StringPieceType'), # NG
423 (9, 'FilePath::FromUTF8Unsafe'), # NG
424 (10, 'AsUTF8Unsafe'), # NG
425 (11, 'FILE_PATH_LITERAL'), # NG
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900426 ]
427 failure = pre_upload._check_filepath_chartype(ProjectNamed('PROJECT'),
428 'COMMIT')
429 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400430 self.assertEqual(
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900431 'Please assume FilePath::CharType is char (crbug.com/870621):',
432 failure.msg)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400433 self.assertEqual(['x.cc, line 2 has base::FilePath::CharType',
434 'x.cc, line 3 has base::FilePath::StringType',
435 'x.cc, line 4 has base::FilePath::StringPieceType',
436 'x.cc, line 5 has base::FilePath::FromUTF8Unsafe',
437 'x.cc, line 6 has FilePath::CharType',
438 'x.cc, line 7 has FilePath::StringType',
439 'x.cc, line 8 has FilePath::StringPieceType',
440 'x.cc, line 9 has FilePath::FromUTF8Unsafe',
441 'x.cc, line 10 has AsUTF8Unsafe',
442 'x.cc, line 11 has FILE_PATH_LITERAL'],
443 failure.items)
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900444
445
Mike Frysingerb2496652019-09-12 23:35:46 -0400446class CheckKernelConfig(PreUploadTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -0500447 """Tests for _kernel_configcheck."""
448
Mike Frysinger1459d362014-12-06 13:53:23 -0500449 def setUp(self):
450 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
451
452 def testMixedChanges(self):
453 """Mixing of changes should fail."""
454 self.file_mock.return_value = [
455 '/kernel/files/chromeos/config/base.config',
456 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
457 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700458 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
459 self.assertTrue(failure)
460
Mike Frysinger1459d362014-12-06 13:53:23 -0500461 def testCodeOnly(self):
462 """Code-only changes should pass."""
463 self.file_mock.return_value = [
464 '/kernel/files/Makefile',
465 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
466 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700467 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
468 self.assertFalse(failure)
469
Mike Frysinger1459d362014-12-06 13:53:23 -0500470 def testConfigOnlyChanges(self):
471 """Config-only changes should pass."""
472 self.file_mock.return_value = [
473 '/kernel/files/chromeos/config/base.config',
474 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700475 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
476 self.assertFalse(failure)
477
Jon Salz98255932012-08-18 14:48:02 +0800478
Mike Frysingerb2496652019-09-12 23:35:46 -0400479class CheckJson(PreUploadTestCase):
Mike Frysinger908be682018-01-04 02:21:50 -0500480 """Tests for _run_json_check."""
481
482 def setUp(self):
483 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
484 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
485
486 def testNoJson(self):
487 """Nothing should be checked w/no JSON files."""
488 self.file_mock.return_value = [
489 '/foo/bar.txt',
490 '/readme.md',
491 ]
492 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
493 self.assertIsNone(ret)
494
495 def testValidJson(self):
496 """We should accept valid json files."""
497 self.file_mock.return_value = [
498 '/foo/bar.txt',
499 '/data.json',
500 ]
501 self.content_mock.return_value = '{}'
502 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
503 self.assertIsNone(ret)
504 self.content_mock.assert_called_once_with('/data.json', 'COMMIT')
505
506 def testInvalidJson(self):
507 """We should reject invalid json files."""
508 self.file_mock.return_value = [
509 '/foo/bar.txt',
510 '/data.json',
511 ]
512 self.content_mock.return_value = '{'
513 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
514 self.assertIsNotNone(ret)
515 self.content_mock.assert_called_once_with('/data.json', 'COMMIT')
516
517
Mike Frysingerb2496652019-09-12 23:35:46 -0400518class CheckManifests(PreUploadTestCase):
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500519 """Tests _check_manifests."""
520
521 def setUp(self):
522 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
523 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
524
525 def testNoManifests(self):
526 """Nothing should be checked w/no Manifest files."""
527 self.file_mock.return_value = [
528 '/foo/bar.txt',
529 '/readme.md',
530 '/manifest',
531 '/Manifest.txt',
532 ]
533 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
534 self.assertIsNone(ret)
535
536 def testValidManifest(self):
537 """Accept valid Manifest files."""
538 self.file_mock.return_value = [
539 '/foo/bar.txt',
540 '/cat/pkg/Manifest',
541 ]
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400542 self.content_mock.return_value = """# Comment and blank lines.
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500543
544DIST lines
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400545"""
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500546 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
547 self.assertIsNone(ret)
548 self.content_mock.assert_called_once_with('/cat/pkg/Manifest', 'COMMIT')
549
550 def _testReject(self, content):
551 """Make sure |content| is rejected."""
552 self.file_mock.return_value = ('/Manifest',)
553 self.content_mock.reset_mock()
554 self.content_mock.return_value = content
555 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
556 self.assertIsNotNone(ret)
557 self.content_mock.assert_called_once_with('/Manifest', 'COMMIT')
558
559 def testRejectBlank(self):
560 """Reject empty manifests."""
561 self._testReject('')
562
563 def testNoTrailingNewLine(self):
564 """Reject manifests w/out trailing newline."""
565 self._testReject('DIST foo')
566
567 def testNoLeadingBlankLines(self):
568 """Reject manifests w/leading blank lines."""
569 self._testReject('\nDIST foo\n')
570
571 def testNoTrailingBlankLines(self):
572 """Reject manifests w/trailing blank lines."""
573 self._testReject('DIST foo\n\n')
574
575 def testNoLeadingWhitespace(self):
576 """Reject manifests w/lines w/leading spaces."""
577 self._testReject(' DIST foo\n')
578 self._testReject(' # Comment\n')
579
580 def testNoTrailingWhitespace(self):
581 """Reject manifests w/lines w/trailing spaces."""
582 self._testReject('DIST foo \n')
583 self._testReject('# Comment \n')
584 self._testReject(' \n')
585
586 def testOnlyDistLines(self):
587 """Only allow DIST lines in here."""
588 self._testReject('EBUILD foo\n')
589
590
Mike Frysingerb2496652019-09-12 23:35:46 -0400591class CheckPortageMakeUseVar(PreUploadTestCase):
Daniel Erat9d203ff2015-02-17 10:12:21 -0700592 """Tests for _check_portage_make_use_var."""
593
594 def setUp(self):
595 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
596 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
597
598 def testMakeConfOmitsOriginalUseValue(self):
599 """Fail for make.conf that discards the previous value of $USE."""
600 self.file_mock.return_value = ['make.conf']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400601 self.content_mock.return_value = u'USE="foo"\nUSE="${USE} bar"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700602 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
603 self.assertTrue(failure, failure)
604
605 def testMakeConfCorrectUsage(self):
606 """Succeed for make.conf that preserves the previous value of $USE."""
607 self.file_mock.return_value = ['make.conf']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400608 self.content_mock.return_value = u'USE="${USE} foo"\nUSE="${USE}" bar'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700609 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
610 self.assertFalse(failure, failure)
611
612 def testMakeDefaultsReferencesOriginalUseValue(self):
613 """Fail for make.defaults that refers to a not-yet-set $USE value."""
614 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400615 self.content_mock.return_value = u'USE="${USE} foo"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700616 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
617 self.assertTrue(failure, failure)
618
619 # Also check for "$USE" without curly brackets.
Mike Frysinger71e643e2019-09-13 17:26:39 -0400620 self.content_mock.return_value = u'USE="$USE foo"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700621 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
622 self.assertTrue(failure, failure)
623
624 def testMakeDefaultsOverwritesUseValue(self):
625 """Fail for make.defaults that discards its own $USE value."""
626 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400627 self.content_mock.return_value = u'USE="foo"\nUSE="bar"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700628 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
629 self.assertTrue(failure, failure)
630
631 def testMakeDefaultsCorrectUsage(self):
632 """Succeed for make.defaults that sets and preserves $USE."""
633 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400634 self.content_mock.return_value = u'USE="foo"\nUSE="${USE}" bar'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700635 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
636 self.assertFalse(failure, failure)
637
638
Mike Frysingerb2496652019-09-12 23:35:46 -0400639class CheckEbuildEapi(PreUploadTestCase):
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500640 """Tests for _check_ebuild_eapi."""
641
Alex Deymo643ac4c2015-09-03 10:40:50 -0700642 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500643
644 def setUp(self):
645 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
646 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
647 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff',
648 side_effect=Exception())
649
650 def testSkipUpstreamOverlays(self):
651 """Skip ebuilds found in upstream overlays."""
652 self.file_mock.side_effect = Exception()
653 ret = pre_upload._check_ebuild_eapi(self.PORTAGE_STABLE, 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400654 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500655
656 # Make sure our condition above triggers.
657 self.assertRaises(Exception, pre_upload._check_ebuild_eapi, 'o', 'HEAD')
658
659 def testSkipNonEbuilds(self):
660 """Skip non-ebuild files."""
661 self.content_mock.side_effect = Exception()
662
663 self.file_mock.return_value = ['some-file', 'ebuild/dir', 'an.ebuild~']
Alex Deymo643ac4c2015-09-03 10:40:50 -0700664 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400665 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500666
667 # Make sure our condition above triggers.
668 self.file_mock.return_value.append('a/real.ebuild')
Alex Deymo643ac4c2015-09-03 10:40:50 -0700669 self.assertRaises(Exception, pre_upload._check_ebuild_eapi,
670 ProjectNamed('o'), 'HEAD')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500671
672 def testSkipSymlink(self):
673 """Skip files that are just symlinks."""
674 self.file_mock.return_value = ['a-r1.ebuild']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400675 self.content_mock.return_value = u'a.ebuild'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700676 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400677 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500678
679 def testRejectEapiImplicit0Content(self):
680 """Reject ebuilds that do not declare EAPI (so it's 0)."""
681 self.file_mock.return_value = ['a.ebuild']
682
Mike Frysinger71e643e2019-09-13 17:26:39 -0400683 self.content_mock.return_value = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500684IUSE="foo"
685src_compile() { }
686"""
Alex Deymo643ac4c2015-09-03 10:40:50 -0700687 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500688 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500689
690 def testRejectExplicitEapi1Content(self):
691 """Reject ebuilds that do declare old EAPI explicitly."""
692 self.file_mock.return_value = ['a.ebuild']
693
Mike Frysinger71e643e2019-09-13 17:26:39 -0400694 template = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500695EAPI=%s
696IUSE="foo"
697src_compile() { }
698"""
699 # Make sure we only check the first EAPI= setting.
Mike Frysinger948284a2018-02-01 15:22:56 -0500700 self.content_mock.return_value = template % '1\nEAPI=60'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700701 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500702 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500703
704 # Verify we handle double quotes too.
705 self.content_mock.return_value = template % '"1"'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700706 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500707 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500708
709 # Verify we handle single quotes too.
710 self.content_mock.return_value = template % "'1'"
Alex Deymo643ac4c2015-09-03 10:40:50 -0700711 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500712 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500713
Mike Frysinger948284a2018-02-01 15:22:56 -0500714 def testAcceptExplicitNewEapiContent(self):
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500715 """Accept ebuilds that do declare new EAPI explicitly."""
716 self.file_mock.return_value = ['a.ebuild']
717
Mike Frysinger71e643e2019-09-13 17:26:39 -0400718 template = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500719EAPI=%s
720IUSE="foo"
721src_compile() { }
722"""
723 # Make sure we only check the first EAPI= setting.
Mike Frysinger948284a2018-02-01 15:22:56 -0500724 self.content_mock.return_value = template % '6\nEAPI=1'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700725 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400726 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500727
728 # Verify we handle double quotes too.
729 self.content_mock.return_value = template % '"5"'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700730 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400731 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500732
733 # Verify we handle single quotes too.
734 self.content_mock.return_value = template % "'5-hdepend'"
Alex Deymo643ac4c2015-09-03 10:40:50 -0700735 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400736 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500737
738
Mike Frysingerb2496652019-09-12 23:35:46 -0400739class CheckEbuildKeywords(PreUploadTestCase):
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400740 """Tests for _check_ebuild_keywords."""
741
742 def setUp(self):
743 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
744 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
745
746 def testNoEbuilds(self):
747 """If no ebuilds are found, do not scan."""
748 self.file_mock.return_value = ['a.file', 'ebuild-is-not.foo']
749
Alex Deymo643ac4c2015-09-03 10:40:50 -0700750 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400751 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400752
753 self.assertEqual(self.content_mock.call_count, 0)
754
755 def testSomeEbuilds(self):
756 """If ebuilds are found, only scan them."""
757 self.file_mock.return_value = ['a.file', 'blah', 'foo.ebuild', 'cow']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400758 self.content_mock.return_value = u''
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400759
Alex Deymo643ac4c2015-09-03 10:40:50 -0700760 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400761 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400762
763 self.assertEqual(self.content_mock.call_count, 1)
764
765 def _CheckContent(self, content, fails):
766 """Test helper for inputs/outputs.
767
768 Args:
769 content: The ebuild content to test.
770 fails: Whether |content| should trigger a hook failure.
771 """
772 self.file_mock.return_value = ['a.ebuild']
773 self.content_mock.return_value = content
774
Alex Deymo643ac4c2015-09-03 10:40:50 -0700775 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400776 if fails:
Mike Frysingerb81102f2014-11-21 00:33:35 -0500777 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400778 else:
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400779 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400780
781 self.assertEqual(self.content_mock.call_count, 1)
782
783 def testEmpty(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 testEmptyQuotes(self):
788 """Check KEYWORDS="" is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400789 self._CheckContent(u'# HEADER\nKEYWORDS=" "\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400790
791 def testStableGlob(self):
792 """Check KEYWORDS=* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400793 self._CheckContent(u'# HEADER\nKEYWORDS="\t*\t"\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400794
795 def testUnstableGlob(self):
796 """Check KEYWORDS=~* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400797 self._CheckContent(u'# HEADER\nKEYWORDS="~* "\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400798
799 def testRestrictedGlob(self):
800 """Check KEYWORDS=-* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400801 self._CheckContent(u'# HEADER\nKEYWORDS="\t-* arm"\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400802
803 def testMissingGlobs(self):
804 """Reject KEYWORDS missing any globs."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400805 self._CheckContent(u'# HEADER\nKEYWORDS="~arm x86"\nblah\n', True)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400806
807
Mike Frysingerb2496652019-09-12 23:35:46 -0400808class CheckEbuildVirtualPv(PreUploadTestCase):
Mike Frysingercd363c82014-02-01 05:20:18 -0500809 """Tests for _check_ebuild_virtual_pv."""
810
Alex Deymo643ac4c2015-09-03 10:40:50 -0700811 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
812 CHROMIUMOS_OVERLAY = ProjectNamed('chromiumos/overlays/chromiumos')
813 BOARD_OVERLAY = ProjectNamed('chromiumos/overlays/board-overlays')
814 PRIVATE_OVERLAY = ProjectNamed('chromeos/overlays/overlay-link-private')
815 PRIVATE_VARIANT_OVERLAY = ProjectNamed('chromeos/overlays/'
816 'overlay-variant-daisy-spring-private')
Mike Frysingercd363c82014-02-01 05:20:18 -0500817
818 def setUp(self):
819 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
820
821 def testNoVirtuals(self):
822 """Skip non virtual packages."""
823 self.file_mock.return_value = ['some/package/package-3.ebuild']
Alex Deymo643ac4c2015-09-03 10:40:50 -0700824 ret = pre_upload._check_ebuild_virtual_pv(ProjectNamed('overlay'), 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400825 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500826
827 def testCommonVirtuals(self):
828 """Non-board overlays should use PV=1."""
829 template = 'virtual/foo/foo-%s.ebuild'
830 self.file_mock.return_value = [template % '1']
831 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400832 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500833
834 self.file_mock.return_value = [template % '2']
835 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500836 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500837
838 def testPublicBoardVirtuals(self):
839 """Public board overlays should use PV=2."""
840 template = 'overlay-lumpy/virtual/foo/foo-%s.ebuild'
841 self.file_mock.return_value = [template % '2']
842 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400843 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500844
845 self.file_mock.return_value = [template % '2.5']
846 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500847 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500848
849 def testPublicBoardVariantVirtuals(self):
850 """Public board variant overlays should use PV=2.5."""
851 template = 'overlay-variant-lumpy-foo/virtual/foo/foo-%s.ebuild'
852 self.file_mock.return_value = [template % '2.5']
853 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400854 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500855
856 self.file_mock.return_value = [template % '3']
857 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500858 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500859
860 def testPrivateBoardVirtuals(self):
861 """Private board overlays should use PV=3."""
862 template = 'virtual/foo/foo-%s.ebuild'
863 self.file_mock.return_value = [template % '3']
864 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400865 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500866
867 self.file_mock.return_value = [template % '3.5']
868 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500869 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500870
871 def testPrivateBoardVariantVirtuals(self):
872 """Private board variant overlays should use PV=3.5."""
873 template = 'virtual/foo/foo-%s.ebuild'
874 self.file_mock.return_value = [template % '3.5']
875 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400876 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500877
Bernie Thompsone5ee1822016-01-12 14:22:23 -0800878 def testSpecialVirtuals(self):
879 """Some cases require deeper versioning and can be >= 4."""
880 template = 'virtual/foo/foo-%s.ebuild'
Mike Frysingercd363c82014-02-01 05:20:18 -0500881 self.file_mock.return_value = [template % '4']
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 Frysingercd363c82014-02-01 05:20:18 -0500884
Bernie Thompsone5ee1822016-01-12 14:22:23 -0800885 self.file_mock.return_value = [template % '4.5']
886 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400887 self.assertIsNone(ret)
Mike Frysinger98638102014-08-28 00:15:08 -0400888
Mike Frysingerb2496652019-09-12 23:35:46 -0400889class CheckCrosLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -0700890 """Tests for _check_cros_license."""
Mike Frysinger98638102014-08-28 00:15:08 -0400891
892 def setUp(self):
893 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
894 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
895
896 def testOldHeaders(self):
897 """Accept old header styles."""
898 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400899 (u'#!/bin/sh\n'
900 u'# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.\n'
901 u'# 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'),
904 (u'// Copyright 2010-2013 The Chromium OS Authors. All rights reserved.'
905 u'\n// Use of this source code is governed by a BSD-style license that'
906 u' can be\n'
907 u'// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -0400908 )
909 self.file_mock.return_value = ['file']
910 for header in HEADERS:
911 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +0900912 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
913
914 def testNewFileYear(self):
915 """Added files should have the current year in license header."""
916 year = datetime.datetime.now().year
917 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400918 (u'// Copyright 2016 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'),
922 (u'// Copyright %d The Chromium OS Authors. All rights reserved.\n'
923 u'// Use of this source code is governed by a BSD-style license that'
924 u' can be\n'
925 u'// found in the LICENSE file.\n') % year,
Keigo Oka7e880ac2019-07-03 15:03:43 +0900926 )
927 want_error = (True, False)
928 def fake_get_affected_files(_, relative, include_adds=True):
929 _ = relative
930 if include_adds:
931 return ['file']
932 else:
933 return []
934
935 self.file_mock.side_effect = fake_get_affected_files
936 for i, header in enumerate(HEADERS):
937 self.content_mock.return_value = header
938 if want_error[i]:
939 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
940 else:
941 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -0400942
943 def testRejectC(self):
944 """Reject the (c) in newer headers."""
945 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400946 (u'// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.'
947 u'\n'
948 u'// Use of this source code is governed by a BSD-style license that'
949 u' can be\n'
950 u'// found in the LICENSE file.\n'),
951 (u'// Copyright (c) 2020 The Chromium OS Authors. All rights reserved.'
952 u'\n'
953 u'// Use of this source code is governed by a BSD-style license that'
954 u' can be\n'
955 u'// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -0400956 )
957 self.file_mock.return_value = ['file']
958 for header in HEADERS:
959 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +0900960 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -0700961
Brian Norris68838dd2018-09-26 18:30:24 -0700962 def testNoLeadingSpace(self):
963 """Allow headers without leading space (e.g., not a source comment)"""
964 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400965 (u'Copyright 2018 The Chromium OS Authors. All rights reserved.\n'
966 u'Use of this source code is governed by a BSD-style license that '
967 u'can be\n'
968 u'found in the LICENSE file.\n'),
Brian Norris68838dd2018-09-26 18:30:24 -0700969 )
970 self.file_mock.return_value = ['file']
971 for header in HEADERS:
972 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +0900973 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Brian Norris68838dd2018-09-26 18:30:24 -0700974
Keigo Oka9732e382019-06-28 17:44:59 +0900975 def testNoExcludedGolang(self):
976 """Don't exclude .go files for license checks."""
977 self.file_mock.return_value = ['foo/main.go']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400978 self.content_mock.return_value = u'package main\nfunc main() {}'
Keigo Oka7e880ac2019-07-03 15:03:43 +0900979 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Keigo Oka9732e382019-06-28 17:44:59 +0900980
Ken Turnerd07564b2018-02-08 17:57:59 +1100981 def testIgnoreExcludedPaths(self):
982 """Ignores excluded paths for license checks."""
983 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400984 self.content_mock.return_value = u'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +0900985 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +1100986
Tom Hughes90b7bd42020-11-10 10:31:49 -0800987 def testIgnoreMetadataFiles(self):
988 """Ignores metadata files for license checks."""
989 self.file_mock.return_value = ['foo/DIR_METADATA']
990 self.content_mock.return_value = u'team_email: "team@chromium.org"'
991 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
992
Chris McDonald7b63c8e2019-04-25 10:27:27 -0600993 def testIgnoreTopLevelExcludedPaths(self):
994 """Ignores excluded paths for license checks."""
995 self.file_mock.return_value = ['OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400996 self.content_mock.return_value = u'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +0900997 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -0700998
Mike Frysingerb2496652019-09-12 23:35:46 -0400999class CheckAOSPLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -07001000 """Tests for _check_aosp_license."""
1001
1002 def setUp(self):
1003 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1004 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1005
1006 def testHeaders(self):
1007 """Accept old header styles."""
1008 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001009 u"""//
Alex Deymof5792ce2015-08-24 22:50:08 -07001010// Copyright (C) 2011 The Android Open Source Project
1011//
1012// Licensed under the Apache License, Version 2.0 (the "License");
1013// you may not use this file except in compliance with the License.
1014// You may obtain a copy of the License at
1015//
1016// http://www.apache.org/licenses/LICENSE-2.0
1017//
1018// Unless required by applicable law or agreed to in writing, software
1019// distributed under the License is distributed on an "AS IS" BASIS,
1020// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1021// See the License for the specific language governing permissions and
1022// limitations under the License.
1023//
1024""",
Mike Frysinger71e643e2019-09-13 17:26:39 -04001025 u"""#
Alex Deymof5792ce2015-08-24 22:50:08 -07001026# Copyright (c) 2015 The Android Open Source Project
1027#
1028# Licensed under the Apache License, Version 2.0 (the "License");
1029# you may not use this file except in compliance with the License.
1030# You may obtain a copy of the License at
1031#
1032# http://www.apache.org/licenses/LICENSE-2.0
1033#
1034# Unless required by applicable law or agreed to in writing, software
1035# distributed under the License is distributed on an "AS IS" BASIS,
1036# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1037# See the License for the specific language governing permissions and
1038# limitations under the License.
1039#
1040""",
1041 )
1042 self.file_mock.return_value = ['file']
1043 for header in HEADERS:
1044 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001045 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001046
1047 def testRejectNoLinesAround(self):
1048 """Reject headers missing the empty lines before/after the license."""
1049 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001050 u"""# Copyright (c) 2015 The Android Open Source Project
Alex Deymof5792ce2015-08-24 22:50:08 -07001051#
1052# Licensed under the Apache License, Version 2.0 (the "License");
1053# you may not use this file except in compliance with the License.
1054# You may obtain a copy of the License at
1055#
1056# http://www.apache.org/licenses/LICENSE-2.0
1057#
1058# Unless required by applicable law or agreed to in writing, software
1059# distributed under the License is distributed on an "AS IS" BASIS,
1060# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1061# See the License for the specific language governing permissions and
1062# limitations under the License.
1063""",
1064 )
1065 self.file_mock.return_value = ['file']
1066 for header in HEADERS:
1067 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001068 self.assertIsNotNone(pre_upload._check_aosp_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -04001069
Ken Turnerd07564b2018-02-08 17:57:59 +11001070 def testIgnoreExcludedPaths(self):
1071 """Ignores excluded paths for license checks."""
1072 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001073 self.content_mock.return_value = u'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001074 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +11001075
Chris McDonald7b63c8e2019-04-25 10:27:27 -06001076 def testIgnoreTopLevelExcludedPaths(self):
1077 """Ignores excluded paths for license checks."""
1078 self.file_mock.return_value = ['OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001079 self.content_mock.return_value = u'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001080 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
1081
Mike Frysinger98638102014-08-28 00:15:08 -04001082
Mike Frysingerb2496652019-09-12 23:35:46 -04001083class CheckLayoutConfTestCase(PreUploadTestCase):
Mike Frysingerd7734522015-02-26 16:12:43 -05001084 """Tests for _check_layout_conf."""
1085
1086 def setUp(self):
1087 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1088 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1089
1090 def assertAccepted(self, files, project='project', commit='fake sha1'):
1091 """Assert _check_layout_conf accepts |files|."""
1092 self.file_mock.return_value = files
1093 ret = pre_upload._check_layout_conf(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001094 self.assertIsNone(ret, msg='rejected with:\n%s' % ret)
Mike Frysingerd7734522015-02-26 16:12:43 -05001095
1096 def assertRejected(self, files, project='project', commit='fake sha1'):
1097 """Assert _check_layout_conf rejects |files|."""
1098 self.file_mock.return_value = files
1099 ret = pre_upload._check_layout_conf(project, commit)
1100 self.assertTrue(isinstance(ret, errors.HookFailure))
1101
1102 def GetLayoutConf(self, filters=()):
1103 """Return a valid layout.conf with |filters| lines removed."""
1104 all_lines = [
Mike Frysinger71e643e2019-09-13 17:26:39 -04001105 u'masters = portage-stable chromiumos',
1106 u'profile-formats = portage-2 profile-default-eapi',
1107 u'profile_eapi_when_unspecified = 5-progress',
1108 u'repo-name = link',
1109 u'thin-manifests = true',
1110 u'use-manifests = strict',
Mike Frysingerd7734522015-02-26 16:12:43 -05001111 ]
1112
1113 lines = []
1114 for line in all_lines:
1115 for filt in filters:
1116 if line.startswith(filt):
1117 break
1118 else:
1119 lines.append(line)
1120
Mike Frysinger71e643e2019-09-13 17:26:39 -04001121 return u'\n'.join(lines)
Mike Frysingerd7734522015-02-26 16:12:43 -05001122
1123 def testNoFilesToCheck(self):
1124 """Don't blow up when there are no layout.conf files."""
1125 self.assertAccepted([])
1126
1127 def testRejectRepoNameFile(self):
1128 """If profiles/repo_name is set, kick it out."""
1129 self.assertRejected(['profiles/repo_name'])
1130
1131 def testAcceptValidLayoutConf(self):
1132 """Accept a fully valid layout.conf."""
1133 self.content_mock.return_value = self.GetLayoutConf()
1134 self.assertAccepted(['metadata/layout.conf'])
1135
1136 def testAcceptUnknownKeys(self):
1137 """Accept keys we don't explicitly know about."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001138 self.content_mock.return_value = self.GetLayoutConf() + u'\nzzz-top = ok'
Mike Frysingerd7734522015-02-26 16:12:43 -05001139 self.assertAccepted(['metadata/layout.conf'])
1140
1141 def testRejectUnsorted(self):
1142 """Reject an unsorted layout.conf."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001143 self.content_mock.return_value = u'zzz-top = bad\n' + self.GetLayoutConf()
Mike Frysingerd7734522015-02-26 16:12:43 -05001144 self.assertRejected(['metadata/layout.conf'])
1145
1146 def testRejectMissingThinManifests(self):
1147 """Reject a layout.conf missing thin-manifests."""
1148 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001149 filters=[u'thin-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001150 self.assertRejected(['metadata/layout.conf'])
1151
1152 def testRejectMissingUseManifests(self):
1153 """Reject a layout.conf missing use-manifests."""
1154 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001155 filters=[u'use-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001156 self.assertRejected(['metadata/layout.conf'])
1157
1158 def testRejectMissingEapiFallback(self):
1159 """Reject a layout.conf missing profile_eapi_when_unspecified."""
1160 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001161 filters=[u'profile_eapi_when_unspecified'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001162 self.assertRejected(['metadata/layout.conf'])
1163
1164 def testRejectMissingRepoName(self):
1165 """Reject a layout.conf missing repo-name."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001166 self.content_mock.return_value = self.GetLayoutConf(filters=[u'repo-name'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001167 self.assertRejected(['metadata/layout.conf'])
1168
1169
Mike Frysingerb2496652019-09-12 23:35:46 -04001170class CommitMessageTestCase(PreUploadTestCase):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001171 """Test case for funcs that check commit messages."""
1172
1173 def setUp(self):
1174 self.msg_mock = self.PatchObject(pre_upload, '_get_commit_desc')
1175
1176 @staticmethod
1177 def CheckMessage(_project, _commit):
1178 raise AssertionError('Test class must declare CheckMessage')
1179 # This dummy return is to silence pylint warning W1111 so we don't have to
1180 # enable it for all the call sites below.
1181 return 1 # pylint: disable=W0101
1182
Alex Deymo643ac4c2015-09-03 10:40:50 -07001183 def assertMessageAccepted(self, msg, project=ProjectNamed('project'),
1184 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001185 """Assert _check_change_has_bug_field accepts |msg|."""
1186 self.msg_mock.return_value = msg
1187 ret = self.CheckMessage(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001188 self.assertIsNone(ret)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001189
Alex Deymo643ac4c2015-09-03 10:40:50 -07001190 def assertMessageRejected(self, msg, project=ProjectNamed('project'),
1191 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001192 """Assert _check_change_has_bug_field rejects |msg|."""
1193 self.msg_mock.return_value = msg
1194 ret = self.CheckMessage(project, commit)
1195 self.assertTrue(isinstance(ret, errors.HookFailure))
1196
1197
1198class CheckCommitMessageBug(CommitMessageTestCase):
1199 """Tests for _check_change_has_bug_field."""
1200
Alex Deymo643ac4c2015-09-03 10:40:50 -07001201 AOSP_PROJECT = pre_upload.Project(name='overlay', dir='', remote='aosp')
1202 CROS_PROJECT = pre_upload.Project(name='overlay', dir='', remote='cros')
1203
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001204 @staticmethod
1205 def CheckMessage(project, commit):
1206 return pre_upload._check_change_has_bug_field(project, commit)
1207
1208 def testNormal(self):
1209 """Accept a commit message w/a valid BUG."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001210 self.assertMessageAccepted('\nBUG=chromium:1234\n', self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001211 self.assertMessageAccepted('\nBUG=b:1234\n', self.CROS_PROJECT)
1212
1213 self.assertMessageAccepted('\nBug: 1234\n', self.AOSP_PROJECT)
1214 self.assertMessageAccepted('\nBug:1234\n', self.AOSP_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001215
1216 def testNone(self):
1217 """Accept BUG=None."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001218 self.assertMessageAccepted('\nBUG=None\n', self.CROS_PROJECT)
1219 self.assertMessageAccepted('\nBUG=none\n', self.CROS_PROJECT)
1220 self.assertMessageAccepted('\nBug: None\n', self.AOSP_PROJECT)
1221 self.assertMessageAccepted('\nBug:none\n', self.AOSP_PROJECT)
1222
1223 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1224 self.assertMessageRejected('\nBUG=NONE\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001225
1226 def testBlank(self):
1227 """Reject blank values."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001228 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1229 self.assertMessageRejected('\nBUG=\n', project)
1230 self.assertMessageRejected('\nBUG= \n', project)
1231 self.assertMessageRejected('\nBug:\n', project)
1232 self.assertMessageRejected('\nBug: \n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001233
1234 def testNotFirstLine(self):
1235 """Reject the first line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001236 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1237 self.assertMessageRejected('BUG=None\n\n\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001238
1239 def testNotInline(self):
1240 """Reject not at the start of line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001241 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1242 self.assertMessageRejected('\n BUG=None\n', project)
1243 self.assertMessageRejected('\n\tBUG=None\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001244
1245 def testOldTrackers(self):
1246 """Reject commit messages using old trackers."""
Mike Frysingera2f28252017-10-27 22:26:14 -04001247 self.assertMessageRejected('\nBUG=chrome-os-partner:1234\n',
1248 self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001249 self.assertMessageRejected('\nBUG=chromium-os:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001250
1251 def testNoTrackers(self):
1252 """Reject commit messages w/invalid trackers."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001253 self.assertMessageRejected('\nBUG=booga:1234\n', self.CROS_PROJECT)
1254 self.assertMessageRejected('\nBUG=br:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001255
1256 def testMissing(self):
1257 """Reject commit messages w/no BUG line."""
1258 self.assertMessageRejected('foo\n')
1259
1260 def testCase(self):
1261 """Reject bug lines that are not BUG."""
1262 self.assertMessageRejected('\nbug=none\n')
1263
Cheng Yuehb707c522020-01-02 14:06:59 +08001264 def testNotAfterTest(self):
1265 """Reject any TEST line before any BUG line."""
1266 test_field = 'TEST=i did not do it\n'
1267 middle_field = 'A random between line\n'
1268 for project, bug_field in ((self.AOSP_PROJECT, 'Bug:none\n'),
1269 (self.CROS_PROJECT, 'BUG=None\n')):
1270 self.assertMessageRejected(
1271 '\n' + test_field + middle_field + bug_field, project)
1272 self.assertMessageRejected(
1273 '\n' + test_field + bug_field, project)
1274
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001275
1276class CheckCommitMessageCqDepend(CommitMessageTestCase):
1277 """Tests for _check_change_has_valid_cq_depend."""
1278
1279 @staticmethod
1280 def CheckMessage(project, commit):
1281 return pre_upload._check_change_has_valid_cq_depend(project, commit)
1282
1283 def testNormal(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001284 """Accept valid Cq-Depends line."""
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001285 self.assertMessageAccepted('\nCq-Depend: chromium:1234\nChange-Id: I123')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001286
1287 def testInvalid(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001288 """Reject invalid Cq-Depends line."""
1289 self.assertMessageRejected('\nCq-Depend=chromium=1234\n')
1290 self.assertMessageRejected('\nCq-Depend: None\n')
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001291 self.assertMessageRejected('\nCq-Depend: chromium:1234\n\nChange-Id: I123')
Mike Frysingere39d0cd2019-11-25 13:30:06 -05001292 self.assertMessageRejected('\nCQ-DEPEND=1234\n')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001293
1294
Bernie Thompsonf8fea992016-01-14 10:27:18 -08001295class CheckCommitMessageContribution(CommitMessageTestCase):
1296 """Tests for _check_change_is_contribution."""
1297
1298 @staticmethod
1299 def CheckMessage(project, commit):
1300 return pre_upload._check_change_is_contribution(project, commit)
1301
1302 def testNormal(self):
1303 """Accept a commit message which is a contribution."""
1304 self.assertMessageAccepted('\nThis is cool code I am contributing.\n')
1305
1306 def testFailureLowerCase(self):
1307 """Deny a commit message which is not a contribution."""
1308 self.assertMessageRejected('\nThis is not a contribution.\n')
1309
1310 def testFailureUpperCase(self):
1311 """Deny a commit message which is not a contribution."""
1312 self.assertMessageRejected('\nNOT A CONTRIBUTION\n')
1313
1314
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001315class CheckCommitMessageTest(CommitMessageTestCase):
1316 """Tests for _check_change_has_test_field."""
1317
1318 @staticmethod
1319 def CheckMessage(project, commit):
1320 return pre_upload._check_change_has_test_field(project, commit)
1321
1322 def testNormal(self):
1323 """Accept a commit message w/a valid TEST."""
1324 self.assertMessageAccepted('\nTEST=i did it\n')
1325
1326 def testNone(self):
1327 """Accept TEST=None."""
1328 self.assertMessageAccepted('\nTEST=None\n')
1329 self.assertMessageAccepted('\nTEST=none\n')
1330
1331 def testBlank(self):
1332 """Reject blank values."""
1333 self.assertMessageRejected('\nTEST=\n')
1334 self.assertMessageRejected('\nTEST= \n')
1335
1336 def testNotFirstLine(self):
1337 """Reject the first line."""
1338 self.assertMessageRejected('TEST=None\n\n\n')
1339
1340 def testNotInline(self):
1341 """Reject not at the start of line."""
1342 self.assertMessageRejected('\n TEST=None\n')
1343 self.assertMessageRejected('\n\tTEST=None\n')
1344
1345 def testMissing(self):
1346 """Reject commit messages w/no TEST line."""
1347 self.assertMessageRejected('foo\n')
1348
1349 def testCase(self):
1350 """Reject bug lines that are not TEST."""
1351 self.assertMessageRejected('\ntest=none\n')
1352
1353
1354class CheckCommitMessageChangeId(CommitMessageTestCase):
1355 """Tests for _check_change_has_proper_changeid."""
1356
1357 @staticmethod
1358 def CheckMessage(project, commit):
1359 return pre_upload._check_change_has_proper_changeid(project, commit)
1360
1361 def testNormal(self):
1362 """Accept a commit message w/a valid Change-Id."""
1363 self.assertMessageAccepted('foo\n\nChange-Id: I1234\n')
1364
1365 def testBlank(self):
1366 """Reject blank values."""
1367 self.assertMessageRejected('\nChange-Id:\n')
1368 self.assertMessageRejected('\nChange-Id: \n')
1369
1370 def testNotFirstLine(self):
1371 """Reject the first line."""
1372 self.assertMessageRejected('TEST=None\n\n\n')
1373
1374 def testNotInline(self):
1375 """Reject not at the start of line."""
1376 self.assertMessageRejected('\n Change-Id: I1234\n')
1377 self.assertMessageRejected('\n\tChange-Id: I1234\n')
1378
1379 def testMissing(self):
1380 """Reject commit messages missing the line."""
1381 self.assertMessageRejected('foo\n')
1382
1383 def testCase(self):
1384 """Reject bug lines that are not Change-Id."""
1385 self.assertMessageRejected('\nchange-id: I1234\n')
1386 self.assertMessageRejected('\nChange-id: I1234\n')
1387 self.assertMessageRejected('\nChange-ID: I1234\n')
1388
1389 def testEnd(self):
1390 """Reject Change-Id's that are not last."""
1391 self.assertMessageRejected('\nChange-Id: I1234\nbar\n')
1392
Mike Frysinger02b88bd2014-11-21 00:29:38 -05001393 def testSobTag(self):
1394 """Permit s-o-b tags to follow the Change-Id."""
1395 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nSigned-off-by: Hi\n')
1396
LaMont Jones237f3ef2020-01-22 10:40:52 -07001397 def testCqClTag(self):
1398 """Permit Cq-Cl-Tag tags to follow the Change-Id."""
1399 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nCq-Cl-Tag: Hi\n')
1400
LaMont Jonesfb5e8bf2020-03-03 12:50:06 -07001401 def testCqIncludeTrybotsTag(self):
1402 """Permit Cq-Include-Trybots tags to follow the Change-Id."""
1403 self.assertMessageAccepted(
1404 'foo\n\nChange-Id: I1234\nCq-Include-Trybots: chromeos/cq:foo\n')
1405
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001406
Jack Neus8edbf642019-07-10 16:08:31 -06001407class CheckCommitMessageNoOEM(CommitMessageTestCase):
1408 """Tests for _check_change_no_include_oem."""
1409
1410 @staticmethod
1411 def CheckMessage(project, commit):
1412 return pre_upload._check_change_no_include_oem(project, commit)
1413
1414 def testNormal(self):
1415 """Accept a commit message w/o reference to an OEM/ODM."""
1416 self.assertMessageAccepted('foo\n')
1417
1418 def testHasOEM(self):
1419 """Catch commit messages referencing OEMs."""
1420 self.assertMessageRejected('HP Project\n\n')
1421 self.assertMessageRejected('hewlett-packard\n')
1422 self.assertMessageRejected('Hewlett\nPackard\n')
1423 self.assertMessageRejected('Dell Chromebook\n\n\n')
1424 self.assertMessageRejected('product@acer.com\n')
1425 self.assertMessageRejected('This is related to Asus\n')
1426 self.assertMessageRejected('lenovo machine\n')
1427
1428 def testHasODM(self):
1429 """Catch commit messages referencing ODMs."""
1430 self.assertMessageRejected('new samsung laptop\n\n')
1431 self.assertMessageRejected('pegatron(ems) project\n')
1432 self.assertMessageRejected('new Wistron device\n')
1433
1434 def testContainsOEM(self):
1435 """Check that the check handles word boundaries properly."""
1436 self.assertMessageAccepted('oheahpohea')
1437 self.assertMessageAccepted('Play an Asus7 guitar chord.\n\n')
1438
1439 def testTag(self):
1440 """Check that the check ignores tags."""
1441 self.assertMessageAccepted(
1442 'Harmless project\n'
1443 'Reviewed-by: partner@asus.corp-partner.google.com\n'
1444 'Tested-by: partner@hp.corp-partner.google.com\n'
1445 'Signed-off-by: partner@dell.corp-partner.google.com\n'
1446 'Commit-Queue: partner@lenovo.corp-partner.google.com\n'
Jack Neus8edbf642019-07-10 16:08:31 -06001447 'CC: partner@acer.corp-partner.google.com\n'
1448 'Acked-by: partner@hewlett-packard.corp-partner.google.com\n')
1449 self.assertMessageRejected(
1450 'Asus project\n'
1451 'Reviewed-by: partner@asus.corp-partner.google.com')
Mike Frysingerbb34a222019-07-31 14:40:46 -04001452 self.assertMessageRejected(
1453 'my project\n'
1454 'Bad-tag: partner@asus.corp-partner.google.com')
Jack Neus8edbf642019-07-10 16:08:31 -06001455
1456
Mike Frysinger36b2ebc2014-10-31 14:02:03 -04001457class CheckCommitMessageStyle(CommitMessageTestCase):
1458 """Tests for _check_commit_message_style."""
1459
1460 @staticmethod
1461 def CheckMessage(project, commit):
1462 return pre_upload._check_commit_message_style(project, commit)
1463
1464 def testNormal(self):
1465 """Accept valid commit messages."""
1466 self.assertMessageAccepted('one sentence.\n')
1467 self.assertMessageAccepted('some.module: do it!\n')
1468 self.assertMessageAccepted('one line\n\nmore stuff here.')
1469
1470 def testNoBlankSecondLine(self):
1471 """Reject messages that have stuff on the second line."""
1472 self.assertMessageRejected('one sentence.\nbad fish!\n')
1473
1474 def testFirstLineMultipleSentences(self):
1475 """Reject messages that have more than one sentence in the summary."""
1476 self.assertMessageRejected('one sentence. two sentence!\n')
1477
1478 def testFirstLineTooLone(self):
1479 """Reject first lines that are too long."""
1480 self.assertMessageRejected('o' * 200)
1481
1482
Mike Frysinger292b45d2014-11-25 01:17:10 -05001483def DiffEntry(src_file=None, dst_file=None, src_mode=None, dst_mode='100644',
1484 status='M'):
1485 """Helper to create a stub RawDiffEntry object"""
1486 if src_mode is None:
1487 if status == 'A':
1488 src_mode = '000000'
1489 elif status == 'M':
1490 src_mode = dst_mode
1491 elif status == 'D':
1492 src_mode = dst_mode
1493 dst_mode = '000000'
1494
1495 src_sha = dst_sha = 'abc'
1496 if status == 'D':
1497 dst_sha = '000000'
1498 elif status == 'A':
1499 src_sha = '000000'
1500
1501 return git.RawDiffEntry(src_mode=src_mode, dst_mode=dst_mode, src_sha=src_sha,
1502 dst_sha=dst_sha, status=status, score=None,
1503 src_file=src_file, dst_file=dst_file)
1504
1505
Mike Frysingerb2496652019-09-12 23:35:46 -04001506class HelpersTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001507 """Various tests for utility functions."""
1508
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001509 def setUp(self):
1510 self.orig_cwd = os.getcwd()
1511 os.chdir(self.tempdir)
1512
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001513 self.PatchObject(git, 'RawDiff', return_value=[
1514 # A modified normal file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001515 DiffEntry(src_file='buildbot/constants.py', status='M'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001516 # A new symlink file.
Bob Haarman0dc1f942020-10-03 00:06:59 +00001517 DiffEntry(dst_file='scripts/cros_env_allowlist', dst_mode='120000',
Mike Frysinger292b45d2014-11-25 01:17:10 -05001518 status='A'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001519 # A deleted file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001520 DiffEntry(src_file='scripts/sync_sonic.py', status='D'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001521 ])
1522
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001523 def tearDown(self):
1524 os.chdir(self.orig_cwd)
1525
1526 def _WritePresubmitIgnoreFile(self, subdir, data):
1527 """Writes to a .presubmitignore file in the passed-in subdirectory."""
1528 directory = os.path.join(self.tempdir, subdir)
1529 if not os.path.exists(directory):
1530 os.makedirs(directory)
1531 osutils.WriteFile(os.path.join(directory, pre_upload._IGNORE_FILE), data)
1532
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001533 def testGetAffectedFilesNoDeletesNoRelative(self):
1534 """Verify _get_affected_files() works w/no delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001535 path = os.getcwd()
1536 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1537 relative=False)
1538 exp_files = [os.path.join(path, 'buildbot/constants.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001539 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001540
1541 def testGetAffectedFilesDeletesNoRelative(self):
1542 """Verify _get_affected_files() works w/delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001543 path = os.getcwd()
1544 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1545 relative=False)
1546 exp_files = [os.path.join(path, 'buildbot/constants.py'),
1547 os.path.join(path, 'scripts/sync_sonic.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001548 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001549
1550 def testGetAffectedFilesNoDeletesRelative(self):
1551 """Verify _get_affected_files() works w/no delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001552 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1553 relative=True)
1554 exp_files = ['buildbot/constants.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001555 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001556
1557 def testGetAffectedFilesDeletesRelative(self):
1558 """Verify _get_affected_files() works w/delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001559 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1560 relative=True)
1561 exp_files = ['buildbot/constants.py', 'scripts/sync_sonic.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001562 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001563
Mike Frysinger292b45d2014-11-25 01:17:10 -05001564 def testGetAffectedFilesDetails(self):
1565 """Verify _get_affected_files() works w/full_details."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001566 files = pre_upload._get_affected_files('HEAD', full_details=True,
1567 relative=True)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001568 self.assertEqual(files[0].src_file, 'buildbot/constants.py')
Mike Frysinger292b45d2014-11-25 01:17:10 -05001569
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001570 def testGetAffectedFilesPresubmitIgnoreDirectory(self):
1571 """Verify .presubmitignore can be used to exclude a directory."""
1572 self._WritePresubmitIgnoreFile('.', 'buildbot/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001573 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001574
1575 def testGetAffectedFilesPresubmitIgnoreDirectoryWildcard(self):
1576 """Verify .presubmitignore can be used with a directory wildcard."""
1577 self._WritePresubmitIgnoreFile('.', '*/constants.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001578 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001579
1580 def testGetAffectedFilesPresubmitIgnoreWithinDirectory(self):
1581 """Verify .presubmitignore can be placed in a subdirectory."""
1582 self._WritePresubmitIgnoreFile('buildbot', '*.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001583 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001584
1585 def testGetAffectedFilesPresubmitIgnoreDoesntMatch(self):
1586 """Verify .presubmitignore has no effect when it doesn't match a file."""
1587 self._WritePresubmitIgnoreFile('buildbot', '*.txt')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001588 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True),
1589 ['buildbot/constants.py'])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001590
1591 def testGetAffectedFilesPresubmitIgnoreAddedFile(self):
1592 """Verify .presubmitignore matches added files."""
1593 self._WritePresubmitIgnoreFile('.', 'buildbot/\nscripts/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001594 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True,
1595 include_symlinks=True),
1596 [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001597
1598 def testGetAffectedFilesPresubmitIgnoreSkipIgnoreFile(self):
1599 """Verify .presubmitignore files are automatically skipped."""
1600 self.PatchObject(git, 'RawDiff', return_value=[
1601 DiffEntry(src_file='.presubmitignore', status='M')
1602 ])
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001603 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Mike Frysinger292b45d2014-11-25 01:17:10 -05001604
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001605
Mike Frysingerb2496652019-09-12 23:35:46 -04001606class ExecFilesTest(PreUploadTestCase):
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001607 """Tests for _check_exec_files."""
1608
1609 def setUp(self):
1610 self.diff_mock = self.PatchObject(git, 'RawDiff')
1611
1612 def testNotExec(self):
1613 """Do not flag files that are not executable."""
1614 self.diff_mock.return_value = [
1615 DiffEntry(src_file='make.conf', dst_mode='100644', status='A'),
1616 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001617 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001618
1619 def testExec(self):
1620 """Flag files that are executable."""
1621 self.diff_mock.return_value = [
1622 DiffEntry(src_file='make.conf', dst_mode='100755', status='A'),
1623 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001624 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001625
1626 def testDeletedExec(self):
1627 """Ignore bad files that are being deleted."""
1628 self.diff_mock.return_value = [
1629 DiffEntry(src_file='make.conf', dst_mode='100755', status='D'),
1630 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001631 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001632
1633 def testModifiedExec(self):
1634 """Flag bad files that weren't exec, but now are."""
1635 self.diff_mock.return_value = [
1636 DiffEntry(src_file='make.conf', src_mode='100644', dst_mode='100755',
1637 status='M'),
1638 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001639 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001640
1641 def testNormalExec(self):
1642 """Don't flag normal files (e.g. scripts) that are executable."""
1643 self.diff_mock.return_value = [
1644 DiffEntry(src_file='foo.sh', dst_mode='100755', status='A'),
1645 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001646 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001647
1648
Mike Frysingerb2496652019-09-12 23:35:46 -04001649class CheckForUprev(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger292b45d2014-11-25 01:17:10 -05001650 """Tests for _check_for_uprev."""
1651
1652 def setUp(self):
1653 self.file_mock = self.PatchObject(git, 'RawDiff')
1654
1655 def _Files(self, files):
1656 """Create |files| in the tempdir and return full paths to them."""
1657 for obj in files:
1658 if obj.status == 'D':
1659 continue
1660 if obj.dst_file is None:
1661 f = obj.src_file
1662 else:
1663 f = obj.dst_file
1664 osutils.Touch(os.path.join(self.tempdir, f), makedirs=True)
1665 return files
1666
1667 def assertAccepted(self, files, project='project', commit='fake sha1'):
1668 """Assert _check_for_uprev accepts |files|."""
1669 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001670 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1671 project_top=self.tempdir)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001672 self.assertIsNone(ret)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001673
1674 def assertRejected(self, files, project='project', commit='fake sha1'):
1675 """Assert _check_for_uprev rejects |files|."""
1676 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001677 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1678 project_top=self.tempdir)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001679 self.assertTrue(isinstance(ret, errors.HookFailure))
1680
Bob Haarman0dc1f942020-10-03 00:06:59 +00001681 def testAllowlistOverlay(self):
1682 """Skip checks on allowed overlays."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001683 self.assertAccepted([DiffEntry(src_file='cat/pkg/pkg-0.ebuild')],
1684 project='chromiumos/overlays/portage-stable')
1685
Bob Haarman0dc1f942020-10-03 00:06:59 +00001686 def testAllowlistFiles(self):
1687 """Skip checks on allowed files."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001688 files = ['ChangeLog', 'Manifest', 'metadata.xml']
1689 self.assertAccepted([DiffEntry(src_file=os.path.join('c', 'p', x),
1690 status='M')
1691 for x in files])
1692
1693 def testRejectBasic(self):
1694 """Reject ebuilds missing uprevs."""
1695 self.assertRejected([DiffEntry(src_file='c/p/p-0.ebuild', status='M')])
1696
1697 def testNewPackage(self):
1698 """Accept new ebuilds w/out uprevs."""
1699 self.assertAccepted([DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1700 self.assertAccepted([DiffEntry(src_file='c/p/p-0-r12.ebuild', status='A')])
1701
1702 def testModifiedFilesOnly(self):
1703 """Reject ebuilds w/out uprevs and changes in files/."""
1704 osutils.Touch(os.path.join(self.tempdir, 'cat/pkg/pkg-0.ebuild'),
1705 makedirs=True)
1706 self.assertRejected([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1707 self.assertRejected([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1708
1709 def testFilesNoEbuilds(self):
1710 """Ignore changes to paths w/out ebuilds."""
1711 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1712 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1713
1714 def testModifiedFilesWithUprev(self):
1715 """Accept ebuilds w/uprevs and changes in files/."""
1716 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='A'),
1717 DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1718 self.assertAccepted([
1719 DiffEntry(src_file='c/p/files/f', status='M'),
1720 DiffEntry(src_file='c/p/p-0-r1.ebuild', src_mode='120000',
1721 dst_file='c/p/p-0-r2.ebuild', dst_mode='120000', status='R')])
1722
Gwendal Grignoua3086c32014-12-09 11:17:22 -08001723 def testModifiedFilesWith9999(self):
1724 """Accept 9999 ebuilds and changes in files/."""
1725 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M'),
1726 DiffEntry(src_file='c/p/p-9999.ebuild', status='M')])
1727
C Shapiroae157ae2017-09-18 16:24:03 -06001728 def testModifiedFilesIn9999SubDirWithout9999Change(self):
1729 """Accept changes in files/ with a parent 9999 ebuild"""
1730 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
1731 os.makedirs(os.path.dirname(ebuild_9999_file))
1732 osutils.WriteFile(ebuild_9999_file, 'fake')
1733 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M')])
1734
Stephen Boyd6bf5ea82020-10-15 00:02:07 -07001735 def testModifiedFilesAndProfilesWith9999(self):
1736 """Accept changes in files/ with a parent 9999 ebuild and profile change"""
1737 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
1738 os.makedirs(os.path.dirname(ebuild_9999_file))
1739 osutils.WriteFile(ebuild_9999_file, 'fake')
1740 self.assertAccepted([
1741 DiffEntry(src_file='c/p/files/f', status='M'),
1742 DiffEntry(src_file='c/profiles/base/make.defaults', status='M')])
1743
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001744
Mike Frysingerb2496652019-09-12 23:35:46 -04001745class DirectMainTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger55f85b52014-12-18 14:45:21 -05001746 """Tests for direct_main()"""
1747
1748 def setUp(self):
1749 self.hooks_mock = self.PatchObject(pre_upload, '_run_project_hooks',
1750 return_value=None)
1751
1752 def testNoArgs(self):
1753 """If run w/no args, should check the current dir."""
1754 ret = pre_upload.direct_main([])
1755 self.assertEqual(ret, 0)
1756 self.hooks_mock.assert_called_once_with(
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001757 mock.ANY, proj_dir=os.getcwd(), commit_list=[], presubmit=mock.ANY,
1758 config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001759
1760 def testExplicitDir(self):
1761 """Verify we can run on a diff dir."""
1762 # Use the chromite dir since we know it exists.
1763 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR])
1764 self.assertEqual(ret, 0)
1765 self.hooks_mock.assert_called_once_with(
1766 mock.ANY, proj_dir=constants.CHROMITE_DIR, commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001767 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001768
1769 def testBogusProject(self):
1770 """A bogus project name should be fine (use default settings)."""
1771 # Use the chromite dir since we know it exists.
1772 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR,
1773 '--project', 'foooooooooo'])
1774 self.assertEqual(ret, 0)
1775 self.hooks_mock.assert_called_once_with(
1776 'foooooooooo', proj_dir=constants.CHROMITE_DIR, commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001777 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001778
1779 def testBogustProjectNoDir(self):
1780 """Make sure --dir is detected even with --project."""
1781 ret = pre_upload.direct_main(['--project', 'foooooooooo'])
1782 self.assertEqual(ret, 0)
1783 self.hooks_mock.assert_called_once_with(
1784 'foooooooooo', proj_dir=os.getcwd(), commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001785 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001786
1787 def testNoGitDir(self):
1788 """We should die when run on a non-git dir."""
1789 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
1790 ['--dir', self.tempdir])
1791
1792 def testNoDir(self):
1793 """We should die when run on a missing dir."""
1794 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
1795 ['--dir', os.path.join(self.tempdir, 'foooooooo')])
1796
1797 def testCommitList(self):
1798 """Any args on the command line should be treated as commits."""
1799 commits = ['sha1', 'sha2', 'shaaaaaaaaaaaan']
1800 ret = pre_upload.direct_main(commits)
1801 self.assertEqual(ret, 0)
1802 self.hooks_mock.assert_called_once_with(
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001803 mock.ANY, proj_dir=mock.ANY, commit_list=commits, presubmit=mock.ANY,
1804 config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001805
1806
Mike Frysingerb2496652019-09-12 23:35:46 -04001807class CheckRustfmtTest(PreUploadTestCase):
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001808 """Tests for _check_rustfmt."""
1809
1810 def setUp(self):
1811 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1812
1813 def testBadRustFile(self):
1814 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
1815 # Bad because it's missing trailing newline.
Mike Frysingere85b0062019-08-20 15:10:33 -04001816 content = 'fn main() {}'
1817 self.content_mock.return_value = content
1818 self.PatchObject(pre_upload, '_run_command', return_value=content + '\n')
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001819 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1820 self.assertIsNotNone(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001821 self.assertEqual('Files not formatted with rustfmt: '
1822 "(run 'cargo fmt' to fix)",
1823 failure.msg)
1824 self.assertEqual(['a.rs'], failure.items)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001825
1826 def testGoodRustFile(self):
1827 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
Mike Frysingere85b0062019-08-20 15:10:33 -04001828 content = 'fn main() {}\n'
1829 self.content_mock.return_value = content
1830 self.PatchObject(pre_upload, '_run_command', return_value=content)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001831 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1832 self.assertIsNone(failure)
1833
1834 def testFilterNonRustFiles(self):
1835 self.PatchObject(pre_upload, '_get_affected_files',
1836 return_value=['a.cc', 'a.rsa', 'a.irs', 'rs.cc'])
1837 self.content_mock.return_value = 'fn main() {\n}'
1838 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1839 self.assertIsNone(failure)
1840
1841
Keiichi Watanabe6ab73fd2020-07-14 23:42:02 +09001842class GetCargoClippyParserTest(cros_test_lib.TestCase):
1843 """Tests for _get_cargo_clippy_parser."""
1844
1845 def testSingleProject(self):
1846 parser = pre_upload._get_cargo_clippy_parser()
1847 args = parser.parse_args(['--project', 'foo'])
1848 self.assertEqual(args.project,
1849 [pre_upload.ClippyProject(root='foo', script=None)])
1850
1851 def testMultipleProjects(self):
1852 parser = pre_upload._get_cargo_clippy_parser()
1853 args = parser.parse_args(['--project', 'foo:bar',
1854 '--project', 'baz'])
1855 self.assertCountEqual(args.project,
1856 [pre_upload.ClippyProject(root='foo', script='bar'),
1857 pre_upload.ClippyProject(root='baz', script=None)])
1858
1859
1860class CheckCargoClippyTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
1861 """Tests for _check_cargo_clippy."""
1862
1863 def setUp(self):
1864 self.project = pre_upload.Project(name='PROJECT', dir=self.tempdir,
1865 remote=None)
1866
1867 def testClippy(self):
1868 """Verify clippy is called when a monitored file was changed."""
1869 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
1870
1871 self.PatchObject(pre_upload, '_get_affected_files',
1872 return_value=[f'{self.project.dir}/repo_a/a.rs'])
1873
1874 ret = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
1875 options=['--project=repo_a',
1876 '--project=repo_b:foo'])
1877 self.assertFalse(ret)
1878
1879 # Check if `cargo clippy` ran.
1880 called = False
1881 for args, _ in rc_mock.call_args_list:
1882 cmd = args[0]
1883 if len(cmd) > 1 and cmd[0] == 'cargo' and cmd[1] == 'clippy':
1884 called = True
1885 break
1886
1887 self.assertTrue(called)
1888
1889 def testDontRun(self):
1890 """Skip checks when no monitored files are modified."""
1891 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
1892
1893 # A file under `repo_a` was monitored.
1894 self.PatchObject(pre_upload, '_get_affected_files',
1895 return_value=[f'{self.project.dir}/repo_a/a.rs'])
1896 # But, we only care about files under `repo_b`.
1897 errs = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
1898 options=['--project=repo_b:foo'])
1899
1900 self.assertFalse(errs)
1901
1902 rc_mock.assert_not_called()
1903
1904 def testCustomScript(self):
1905 """Verify project-specific script is used."""
1906 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
1907
1908 self.PatchObject(pre_upload, '_get_affected_files',
1909 return_value=[f'{self.project.dir}/repo_b/b.rs'])
1910
1911 errs = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
1912 options=['--project=repo_a',
1913 '--project=repo_b:foo'])
1914 self.assertFalse(errs)
1915
1916 # Check if the script `foo` ran.
1917 called = False
1918 for args, _ in rc_mock.call_args_list:
1919 cmd = args[0]
1920 if len(cmd) > 0 and cmd[0] == os.path.join(self.project.dir, 'foo'):
1921 called = True
1922 break
1923
1924 self.assertTrue(called)
1925
1926
Mike Frysinger180ecd62020-08-19 00:41:51 -04001927class OverrideHooksProcessing(PreUploadTestCase):
1928 """Verify _get_override_hooks processing."""
1929
1930 @staticmethod
1931 def parse(data):
1932 """Helper to create a config & parse it."""
1933 cfg = configparser.ConfigParser()
1934 cfg.read_string(data)
1935 return pre_upload._get_override_hooks(cfg)
1936
1937 def testHooks(self):
1938 """Verify we reject unknown hook names (e.g. typos)."""
1939 with self.assertRaises(ValueError) as e:
1940 self.parse("""
1941[Hook Overrides]
1942foobar: true
1943""")
1944 self.assertIn('foobar', str(e.exception))
1945
1946 def testImplicitDisable(self):
1947 """Verify non-common hooks aren't enabled by default."""
1948 enabled, _ = self.parse('')
1949 self.assertNotIn(pre_upload._run_checkpatch, enabled)
1950
1951 def testExplicitDisable(self):
1952 """Verify hooks disabled are disabled."""
1953 _, disabled = self.parse("""
1954[Hook Overrides]
1955tab_check: false
1956""")
1957 self.assertIn(pre_upload._check_no_tabs, disabled)
1958
1959 def testExplicitEnable(self):
1960 """Verify hooks enabled are enabled."""
1961 enabled, _ = self.parse("""
1962[Hook Overrides]
1963tab_check: true
1964""")
1965 self.assertIn(pre_upload._check_no_tabs, enabled)
1966
1967 def testOptions(self):
1968 """Verify hook options are loaded."""
1969 enabled, _ = self.parse("""
1970[Hook Overrides Options]
1971keyword_check: --kw
1972""")
1973 for func in enabled:
1974 if func.__name__ == 'keyword_check':
1975 self.assertIn('options', func.keywords)
1976 self.assertEqual(func.keywords['options'], ['--kw'])
1977 break
1978 else:
1979 self.fail('could not find "keyword_check" enabled hook')
1980
1981 def testSignOffField(self):
1982 """Verify signoff field handling."""
1983 # Enforce no s-o-b by default.
1984 enabled, disabled = self.parse('')
1985 self.assertIn(pre_upload._check_change_has_no_signoff_field, enabled)
1986 self.assertNotIn(pre_upload._check_change_has_signoff_field, enabled)
1987 self.assertNotIn(pre_upload._check_change_has_signoff_field, disabled)
1988
1989 # If disabled, don't enforce either policy.
1990 enabled, disabled = self.parse("""
1991[Hook Overrides]
1992signoff_check: false
1993""")
1994 self.assertNotIn(pre_upload._check_change_has_no_signoff_field, enabled)
1995 self.assertNotIn(pre_upload._check_change_has_signoff_field, enabled)
1996 self.assertIn(pre_upload._check_change_has_signoff_field, disabled)
1997
1998 # If enabled, require s-o-b.
1999 enabled, disabled = self.parse("""
2000[Hook Overrides]
2001signoff_check: true
2002""")
2003 self.assertNotIn(pre_upload._check_change_has_no_signoff_field, enabled)
2004 self.assertIn(pre_upload._check_change_has_signoff_field, enabled)
2005 self.assertNotIn(pre_upload._check_change_has_signoff_field, disabled)
2006
2007 def testBranchField(self):
2008 """Verify branch field enabling."""
2009 # Should be disabled by default.
2010 enabled, disabled = self.parse('')
2011 self.assertIn(pre_upload._check_change_has_no_branch_field, enabled)
2012 self.assertNotIn(pre_upload._check_change_has_branch_field, enabled)
2013 self.assertNotIn(pre_upload._check_change_has_branch_field, disabled)
2014
2015 # Should be disabled if requested.
2016 enabled, disabled = self.parse("""
2017[Hook Overrides]
2018branch_check: false
2019""")
2020 self.assertIn(pre_upload._check_change_has_no_branch_field, enabled)
2021 self.assertNotIn(pre_upload._check_change_has_branch_field, enabled)
2022 self.assertIn(pre_upload._check_change_has_branch_field, disabled)
2023
2024 # Should be enabled if requested.
2025 enabled, disabled = self.parse("""
2026[Hook Overrides]
2027branch_check: true
2028""")
2029 self.assertNotIn(pre_upload._check_change_has_no_branch_field, enabled)
2030 self.assertIn(pre_upload._check_change_has_branch_field, enabled)
2031 self.assertNotIn(pre_upload._check_change_has_branch_field, disabled)
2032
2033
Tom Hughes1ed799d2020-09-25 14:37:28 -07002034class ProjectHooksProcessing(PreUploadTestCase, cros_test_lib.TempDirTestCase):
2035 """Verify _get_project_hooks processing."""
2036
2037 def parse(self, data):
2038 """Helper to write config and parse it."""
2039 filename = os.path.join(self.tempdir, 'config')
2040 osutils.WriteFile(filename, data)
2041 return pre_upload._get_project_hooks(project='test', presubmit=True,
2042 config_file=filename)
2043
2044 def testClangFormatCheckDefault(self):
2045 """Verify clang-format check disabled by default."""
2046 hooks = self.parse('')
2047 for func in hooks:
2048 self.assertNotEqual(func.__name__, '_check_clang_format')
2049 self.assertNotEqual(func.__name__, 'clang_format_check')
2050
2051 def testClangFormatCheckDisabled(self):
2052 """Verify clang-format check disabled when requested."""
2053 hooks = self.parse("""
2054[Hook Overrides]
2055clang_format_check: false
2056""")
2057 for func in hooks:
2058 self.assertNotEqual(func.__name__, '_check_clang_format')
2059 self.assertNotEqual(func.__name__, 'clang_format_check')
2060
2061 def testClangFormatCheckEnabled(self):
2062 """Verify clang-format check enabled when requested."""
2063 hooks = self.parse("""
2064[Hook Overrides]
2065clang_format_check: true
2066""")
2067 for func in hooks:
2068 if func.__name__ == '_check_clang_format':
2069 self.assertFalse(hasattr(func, 'keywords'))
2070 break
2071 else:
2072 self.fail('could not find "_check_clang_format" enabled hook')
2073
2074 def testClangFormatCheckEnabledWithOptions(self):
2075 """Verify clang-format check has options when provided."""
2076 hooks = self.parse("""
2077[Hook Overrides]
2078clang_format_check: true
2079
2080[Hook Overrides Options]
2081clang_format_check:
2082 some_dir/
2083""")
2084 for func in hooks:
2085 if func.__name__ == 'clang_format_check':
2086 self.assertIn('options', func.keywords)
2087 self.assertEqual(func.keywords['options'], ['some_dir/'])
2088 break
2089 else:
2090 self.fail('could not find "clang_format_check" enabled hook')
2091
2092
Jon Salz98255932012-08-18 14:48:02 +08002093if __name__ == '__main__':
Mike Frysinger884a8dd2015-05-17 03:43:43 -04002094 cros_test_lib.main(module=__name__)