blob: 278f9c2169db08b3f3377429845a7138b217f4ea [file] [log] [blame]
Mike Frysinger4f994402019-09-13 17:40:45 -04001#!/usr/bin/env python3
Jon Salz98255932012-08-18 14:48:02 +08002# -*- coding: utf-8 -*-
3# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
Mike Frysingerae409522014-02-01 03:16:11 -05007"""Unittests for pre-upload.py."""
8
Mike Frysinger09d6a3d2013-10-08 22:21:03 -04009from __future__ import print_function
10
Mike Frysinger180ecd62020-08-19 00:41:51 -040011import configparser
Keigo Oka7e880ac2019-07-03 15:03:43 +090012import datetime
David Jamesc3b68b32013-04-03 09:17:03 -070013import os
14import sys
Mike Frysingerf6a29772020-08-22 03:57:08 -040015from unittest import mock
Mike Frysingerfd481ce2019-09-13 18:14:48 -040016
Mike Frysingerbf8b91c2014-02-01 02:50:27 -050017import errors
18
Jon Salz98255932012-08-18 14:48:02 +080019# pylint: disable=W0212
Mike Frysinger65d93c12014-02-01 02:59:46 -050020# We access private members of the pre_upload module all over the place.
21
Mike Frysinger55f85b52014-12-18 14:45:21 -050022# Make sure we can find the chromite paths.
23sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)),
24 '..', '..'))
Jon Salz98255932012-08-18 14:48:02 +080025
Mike Frysingerfd481ce2019-09-13 18:14:48 -040026# The sys.path monkey patching confuses the linter.
27# pylint: disable=wrong-import-position
Mike Frysinger71e643e2019-09-13 17:26:39 -040028from chromite.lib import constants
29from chromite.lib import cros_build_lib
Mike Frysinger65d93c12014-02-01 02:59:46 -050030from chromite.lib import cros_test_lib
Mike Frysingerd3bd32c2014-11-24 23:34:29 -050031from chromite.lib import git
Daniel Erata350fd32014-09-29 14:02:34 -070032from chromite.lib import osutils
Mike Frysinger65d93c12014-02-01 02:59:46 -050033
34
Mike Frysingerff4768e2020-02-27 18:48:13 -050035assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
36
37
Jon Salz98255932012-08-18 14:48:02 +080038pre_upload = __import__('pre-upload')
39
40
Alex Deymo643ac4c2015-09-03 10:40:50 -070041def ProjectNamed(project_name):
42 """Wrapper to create a Project with just the name"""
43 return pre_upload.Project(project_name, None, None)
44
45
Mike Frysingerb2496652019-09-12 23:35:46 -040046class PreUploadTestCase(cros_test_lib.MockTestCase):
47 """Common test case base."""
48
49 def setUp(self):
50 pre_upload.CACHE.clear()
51
52
53class TryUTF8DecodeTest(PreUploadTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -050054 """Verify we sanely handle unicode content."""
55
Mike Frysinger71e643e2019-09-13 17:26:39 -040056 def setUp(self):
Mike Frysinger7bb709f2019-09-29 23:20:12 -040057 self.rc_mock = self.PatchObject(cros_build_lib, 'run')
Mike Frysinger71e643e2019-09-13 17:26:39 -040058
59 def _run(self, content):
60 """Helper for round tripping through _run_command."""
61 self.rc_mock.return_value = cros_build_lib.CommandResult(
62 output=content, returncode=0)
63 return pre_upload._run_command([])
64
65 def testEmpty(self):
66 """Check empty output."""
67 ret = self._run(b'')
68 self.assertEqual('', ret)
69
70 if sys.version_info.major < 3:
71 ret = self._run('')
72 self.assertEqual(u'', ret)
73
74 def testAscii(self):
75 """Check ascii output."""
76 ret = self._run(b'abc')
77 self.assertEqual('abc', ret)
78
79 if sys.version_info.major < 3:
80 ret = self._run('abc')
81 self.assertEqual(u'abc', ret)
82
83 def testUtf8(self):
84 """Check valid UTF-8 output."""
85 text = u'你好布萊恩'
86 ret = self._run(text.encode('utf-8'))
87 self.assertEqual(text, ret)
88
89 def testBinary(self):
90 """Check binary (invalid UTF-8) output."""
91 ret = self._run(b'hi \x80 there')
Mike Frysinger8a4e8942019-09-16 23:43:49 -040092 self.assertEqual(u'hi \ufffd there', ret)
Jon Salz98255932012-08-18 14:48:02 +080093
94
Daisuke Nojiri2089e012020-08-20 15:12:36 -070095class CheckKeywordsTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Bernie Thompson8e26f742020-07-23 14:32:31 -070096 """Tests for _check_keywords."""
97
98 def setUp(self):
99 self.PatchObject(pre_upload, '_get_affected_files',
100 return_value=['x.ebuild'])
101 self.PatchObject(pre_upload, '_filter_files', return_value=['x.ebuild'])
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700102 # First call for blocked_terms.txt and second call for unblocked_terms.txt.
103 self.rf_mock = self.PatchObject(
104 osutils, 'ReadFile',
105 side_effect=['scruffy\nmangy\ndog.?pile\ncat.?circle', 'fox'])
Bernie Thompson8e26f742020-07-23 14:32:31 -0700106 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
107 self.desc_mock = self.PatchObject(pre_upload, '_get_commit_desc')
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700108 self.project = pre_upload.Project(name='PROJECT', dir=self.tempdir,
109 remote=None)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700110
111 def test_good_cases(self):
112 self.desc_mock.return_value = 'Commit Message.\nLine 2'
113 self.diff_mock.return_value = [
114 (1, 'Some text without keywords.'),
115 (2, 'The dog is black has a partial keyword that does not count.'),
116 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700117 failures = pre_upload._check_keywords(self.project, 'COMMIT')
Bernie Thompson8e26f742020-07-23 14:32:31 -0700118 self.assertEqual(failures, [])
119
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700120 self.rf_mock.assert_has_calls([
121 mock.call(os.path.join(pre_upload._get_hooks_dir(),
122 pre_upload.BLOCKED_TERMS_FILE)),
123 mock.call(os.path.join(pre_upload._get_hooks_dir(),
124 pre_upload.UNBLOCKED_TERMS_FILE)),
125 ])
126
Bernie Thompson8e26f742020-07-23 14:32:31 -0700127 def test_bad_cases(self):
128 self.desc_mock.return_value = 'Commit Message.\nLine 2\nLine 3 scruffy'
129 self.diff_mock.return_value = [
130 (1, 'Scruffy plain catch'),
131 (2, 'dog-pile hyphenated catch'),
132 (3, 'cat_circle underscored catch'),
133 (4, 'dog pile space catch'),
134 (5, 'dogpiled substring catch'),
135 (6, 'scruffy mangy dog, multiple in a line catch'),
136 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700137 failures = pre_upload._check_keywords(self.project, 'COMMIT')
Bernie Thompson8e26f742020-07-23 14:32:31 -0700138 self.assertNotEqual(failures, [])
139 self.assertEqual('Found a blocked keyword in:', failures[0].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700140 self.assertEqual(
141 ['x.ebuild, line 1: Matched "Scruffy" with regex of "scruffy"',
142 'x.ebuild, line 2: Matched "dog-pile" with regex of "dog.?pile"',
143 'x.ebuild, line 3: Matched "cat_circle" with regex of "cat.?circle"',
144 'x.ebuild, line 4: Matched "dog pile" with regex of "dog.?pile"',
145 'x.ebuild, line 5: Matched "dogpile" with regex of "dog.?pile"',
146 'x.ebuild, line 6: Matched "mangy" with regex of "mangy"'],
147 failures[0].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700148 self.assertEqual('Found a blocked keyword in:', failures[1].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700149 self.assertEqual(
150 ['Commit message, line 3: Matched "scruffy" with regex of "scruffy"'],
151 failures[1].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700152
153 def test_block_option_cases(self):
154 self.desc_mock.return_value = 'Commit Message.\nLine 2 voldemort'
155 self.diff_mock.return_value = [
156 (1, 'Line with a new term voldemort.'),
157 (2, 'Line with only they who shall not be named.'),
158 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700159 failures = pre_upload._check_keywords(self.project,
Bernie Thompson8e26f742020-07-23 14:32:31 -0700160 'COMMIT', ['--block', 'voldemort'])
161 self.assertNotEqual(failures, [])
162 self.assertEqual('Found a blocked keyword in:', failures[0].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700163 self.assertEqual(
164 ['x.ebuild, line 1: Matched "voldemort" with regex of "voldemort"'],
165 failures[0].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700166 self.assertEqual('Found a blocked keyword in:', failures[1].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700167 self.assertEqual(
168 ['Commit message, line 2: '
169 'Matched "voldemort" with regex of "voldemort"'], failures[1].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700170
171 def test_unblock_option_cases(self):
172 self.desc_mock.return_value = 'Commit message with scruffy'
173 self.diff_mock.return_value = [
Bernie Thompson4e362922020-09-02 16:17:50 -0700174 (1, 'Line with two unblocked terms scruffy big dog-pile'),
175 (2, 'Line with without any blocked terms'),
176 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700177 # scruffy matches regex of 'scruffy' in block list but excluded by
178 # different regex of 'scru.?fy' in unblock list.
179 failures = pre_upload._check_keywords(self.project,
Bernie Thompson4e362922020-09-02 16:17:50 -0700180 'COMMIT', ['--unblock', 'dog.?pile',
181 '--unblock', 'scru.?fy'])
182 self.assertEqual(failures, [])
Bernie Thompson8e26f742020-07-23 14:32:31 -0700183
Laurent Chavey434af9a2020-09-28 22:25:16 +0900184 def test_unblock_and_block_option_cases(self):
185 self.desc_mock.return_value = 'Commit message with scruffy'
186 self.diff_mock.return_value = [
187 (1, 'Two unblocked terms scruffy and dog-pile'),
188 (2, 'Without any blocked terms'),
189 (3, 'Blocked dogpile'),
190 (4, 'Unblocked m.dogpile'),
191 (5, 'Blocked dogpile and unblocked m.dogpile'),
192 (6, 'Unlocked m.dogpile and blocked dogpile'),
193 (7, 'Unlocked m.dogpile and unblocked dog-pile'),
194 ]
195 # scruffy matches regex of 'scruffy' in block list but excluded by
196 # a different regex of 'scru.?fy' in unblock list.
197 # dogpile, dog.pile matches regex of 'dog.?pile' in block list.
198 # m.dogpile and dog-pile matches regex of 'dog.?pile' in block list but
199 # excluded by different regex '\.dog.?pile' and 'dog-pile' in unblock list.
200 failures = pre_upload._check_keywords(self.project,
201 'COMMIT',
202 ['--unblock', r'dog-pile',
203 '--unblock', r'scru.?fy',
204 '--unblock', r'\.dog.?pile'])
205 self.assertNotEqual(failures, [])
206 self.assertEqual('Found a blocked keyword in:', failures[0].msg)
207 self.assertEqual(
208 [r'x.ebuild, line 3: Matched "dogpile" with regex of "dog.?pile"',
209 r'x.ebuild, line 5: Matched "dogpile" with regex of "dog.?pile"',
210 r'x.ebuild, line 6: Matched "dogpile" with regex of "dog.?pile"'],
211 failures[0].items)
212
Bernie Thompson8e26f742020-07-23 14:32:31 -0700213
Mike Frysingerb2496652019-09-12 23:35:46 -0400214class CheckNoLongLinesTest(PreUploadTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -0500215 """Tests for _check_no_long_lines."""
216
Jon Salz98255932012-08-18 14:48:02 +0800217 def setUp(self):
Mike Frysinger1459d362014-12-06 13:53:23 -0500218 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
Jon Salz98255932012-08-18 14:48:02 +0800219
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900220 def testCheck(self):
Mike Frysingerf8961942020-05-15 00:36:31 -0400221 path = 'x.cc'
222 self.PatchObject(pre_upload, '_get_affected_files', return_value=[path])
Mike Frysinger1459d362014-12-06 13:53:23 -0500223 self.diff_mock.return_value = [
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400224 (1, u'x' * 80), # OK
225 (2, '\x80' * 80), # OK
226 (3, u'x' * 81), # Too long
227 (4, '\x80' * 81), # Too long
228 (5, u'See http://' + (u'x' * 80)), # OK (URL)
229 (6, u'See https://' + (u'x' * 80)), # OK (URL)
230 (7, u'# define ' + (u'x' * 80)), # OK (compiler directive)
231 (8, u'#define' + (u'x' * 74)), # Too long
Mike Frysinger1459d362014-12-06 13:53:23 -0500232 ]
Alex Deymo643ac4c2015-09-03 10:40:50 -0700233 failure = pre_upload._check_no_long_lines(ProjectNamed('PROJECT'), 'COMMIT')
Jon Salz98255932012-08-18 14:48:02 +0800234 self.assertTrue(failure)
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900235 self.assertEqual('Found lines longer than the limit (first 5 shown):',
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400236 failure.msg)
Mike Frysingerf8961942020-05-15 00:36:31 -0400237 self.assertEqual([path + ', line %d, 81 chars, over 80 chars' %
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900238 x for x in [3, 4, 8]],
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400239 failure.items)
Jon Salz98255932012-08-18 14:48:02 +0800240
George Burgess IVf9f79eb2019-07-09 20:12:55 -0700241 def testCheckTreatsOwnersFilesSpecially(self):
242 affected_files = self.PatchObject(pre_upload, '_get_affected_files')
243
244 mock_files = (
245 ('foo-OWNERS', False),
246 ('OWNERS', True),
247 ('/path/to/OWNERS', True),
248 ('/path/to/OWNERS.foo', True),
249 )
250
251 mock_lines = (
252 (u'x' * 81, False),
253 (u'foo file:' + u'x' * 80, True),
254 (u'include ' + u'x' * 80, True),
255 )
256 assert all(len(line) > 80 for line, _ in mock_lines)
257
258 for file_name, is_owners in mock_files:
259 affected_files.return_value = [file_name]
260 for line, is_ok in mock_lines:
261 self.diff_mock.return_value = [(1, line)]
262 failure = pre_upload._check_no_long_lines(ProjectNamed('PROJECT'),
263 'COMMIT')
264
265 assert_msg = 'file: %r; line: %r' % (file_name, line)
266 if is_owners and is_ok:
267 self.assertFalse(failure, assert_msg)
268 else:
269 self.assertTrue(failure, assert_msg)
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900270 self.assertIn('Found lines longer than the limit', failure.msg,
George Burgess IVf9f79eb2019-07-09 20:12:55 -0700271 assert_msg)
272
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900273 def testIncludeOptions(self):
274 self.PatchObject(pre_upload,
275 '_get_affected_files',
276 return_value=['foo.txt'])
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400277 self.diff_mock.return_value = [(1, u'x' * 81)]
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900278 self.assertFalse(pre_upload._check_no_long_lines(
279 ProjectNamed('PROJECT'), 'COMMIT'))
280 self.assertTrue(pre_upload._check_no_long_lines(
281 ProjectNamed('PROJECT'), 'COMMIT', options=['--include_regex=foo']))
282
283 def testExcludeOptions(self):
284 self.PatchObject(pre_upload,
285 '_get_affected_files',
Mike Frysingerf8961942020-05-15 00:36:31 -0400286 return_value=['foo.cc'])
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400287 self.diff_mock.return_value = [(1, u'x' * 81)]
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900288 self.assertTrue(pre_upload._check_no_long_lines(
289 ProjectNamed('PROJECT'), 'COMMIT'))
290 self.assertFalse(pre_upload._check_no_long_lines(
291 ProjectNamed('PROJECT'), 'COMMIT', options=['--exclude_regex=foo']))
292
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900293 def testSpecialLineLength(self):
294 mock_lines = (
295 (u'x' * 101, True),
296 (u'x' * 100, False),
297 (u'x' * 81, False),
298 (u'x' * 80, False),
299 )
300 self.PatchObject(pre_upload,
301 '_get_affected_files',
302 return_value=['foo.java'])
303 for line, is_ok in mock_lines:
304 self.diff_mock.return_value = [(1, line)]
305 if is_ok:
306 self.assertTrue(pre_upload._check_no_long_lines(
307 ProjectNamed('PROJECT'), 'COMMIT'))
308 else:
309 self.assertFalse(pre_upload._check_no_long_lines(
310 ProjectNamed('PROJECT'), 'COMMIT'))
311
Mike Frysingerae409522014-02-01 03:16:11 -0500312
Mike Frysingerb2496652019-09-12 23:35:46 -0400313class CheckTabbedIndentsTest(PreUploadTestCase):
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800314 """Tests for _check_tabbed_indents."""
Mike Frysingerb2496652019-09-12 23:35:46 -0400315
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800316 def setUp(self):
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900317 self.PatchObject(pre_upload,
318 '_get_affected_files',
319 return_value=['x.ebuild'])
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800320 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
321
322 def test_good_cases(self):
323 self.diff_mock.return_value = [
324 (1, u'no_tabs_anywhere'),
325 (2, u' leading_tab_only'),
326 (3, u' leading_tab another_tab'),
327 (4, u' leading_tab trailing_too '),
328 (5, u' leading_tab then_spaces_trailing '),
329 ]
330 failure = pre_upload._check_tabbed_indents(ProjectNamed('PROJECT'),
331 'COMMIT')
332 self.assertIsNone(failure)
333
334 def test_bad_cases(self):
335 self.diff_mock.return_value = [
336 (1, u' leading_space'),
337 (2, u' tab_followed_by_space'),
338 (3, u' space_followed_by_tab'),
339 (4, u' mix_em_up'),
340 (5, u' mix_on_both_sides '),
341 ]
342 failure = pre_upload._check_tabbed_indents(ProjectNamed('PROJECT'),
343 'COMMIT')
344 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400345 self.assertEqual('Found a space in indentation (must be all tabs):',
346 failure.msg)
347 self.assertEqual(['x.ebuild, line %d' % x for x in range(1, 6)],
348 failure.items)
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800349
350
Mike Frysingerb2496652019-09-12 23:35:46 -0400351class CheckProjectPrefix(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Daniel Erata350fd32014-09-29 14:02:34 -0700352 """Tests for _check_project_prefix."""
353
354 def setUp(self):
Daniel Erata350fd32014-09-29 14:02:34 -0700355 os.chdir(self.tempdir)
356 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
357 self.desc_mock = self.PatchObject(pre_upload, '_get_commit_desc')
358
Daniel Erata350fd32014-09-29 14:02:34 -0700359 def _WriteAliasFile(self, filename, project):
360 """Writes a project name to a file, creating directories if needed."""
361 os.makedirs(os.path.dirname(filename))
362 osutils.WriteFile(filename, project)
363
364 def testInvalidPrefix(self):
365 """Report an error when the prefix doesn't match the base directory."""
366 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
367 self.desc_mock.return_value = 'bar: Some commit'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700368 failure = pre_upload._check_project_prefix(ProjectNamed('PROJECT'),
369 'COMMIT')
Daniel Erata350fd32014-09-29 14:02:34 -0700370 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400371 self.assertEqual('The commit title for changes affecting only foo should '
372 'start with "foo: "', failure.msg)
Daniel Erata350fd32014-09-29 14:02:34 -0700373
374 def testValidPrefix(self):
375 """Use a prefix that matches the base directory."""
376 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
377 self.desc_mock.return_value = 'foo: Change some files.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700378 self.assertFalse(
379 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700380
381 def testAliasFile(self):
382 """Use .project_alias to override the project name."""
383 self._WriteAliasFile('foo/.project_alias', 'project')
384 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/bar.cc']
385 self.desc_mock.return_value = 'project: Use an alias.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700386 self.assertFalse(
387 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700388
389 def testAliasFileWithSubdirs(self):
390 """Check that .project_alias is used when only modifying subdirectories."""
391 self._WriteAliasFile('foo/.project_alias', 'project')
392 self.file_mock.return_value = [
393 'foo/subdir/foo.cc',
394 'foo/subdir/bar.cc'
395 'foo/subdir/blah/baz.cc'
396 ]
397 self.desc_mock.return_value = 'project: Alias with subdirs.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700398 self.assertFalse(
399 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700400
401
Mike Frysingerb2496652019-09-12 23:35:46 -0400402class CheckFilePathCharTypeTest(PreUploadTestCase):
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900403 """Tests for _check_filepath_chartype."""
404
405 def setUp(self):
406 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
407
408 def testCheck(self):
409 self.PatchObject(pre_upload, '_get_affected_files', return_value=['x.cc'])
410 self.diff_mock.return_value = [
411 (1, 'base::FilePath'), # OK
412 (2, 'base::FilePath::CharType'), # NG
413 (3, 'base::FilePath::StringType'), # NG
414 (4, 'base::FilePath::StringPieceType'), # NG
Satoru Takabayashi4ca37922018-08-08 10:16:38 +0900415 (5, 'base::FilePath::FromUTF8Unsafe'), # NG
416 (6, 'FilePath::CharType'), # NG
417 (7, 'FilePath::StringType'), # NG
418 (8, 'FilePath::StringPieceType'), # NG
419 (9, 'FilePath::FromUTF8Unsafe'), # NG
420 (10, 'AsUTF8Unsafe'), # NG
421 (11, 'FILE_PATH_LITERAL'), # NG
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900422 ]
423 failure = pre_upload._check_filepath_chartype(ProjectNamed('PROJECT'),
424 'COMMIT')
425 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400426 self.assertEqual(
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900427 'Please assume FilePath::CharType is char (crbug.com/870621):',
428 failure.msg)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400429 self.assertEqual(['x.cc, line 2 has base::FilePath::CharType',
430 'x.cc, line 3 has base::FilePath::StringType',
431 'x.cc, line 4 has base::FilePath::StringPieceType',
432 'x.cc, line 5 has base::FilePath::FromUTF8Unsafe',
433 'x.cc, line 6 has FilePath::CharType',
434 'x.cc, line 7 has FilePath::StringType',
435 'x.cc, line 8 has FilePath::StringPieceType',
436 'x.cc, line 9 has FilePath::FromUTF8Unsafe',
437 'x.cc, line 10 has AsUTF8Unsafe',
438 'x.cc, line 11 has FILE_PATH_LITERAL'],
439 failure.items)
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900440
441
Mike Frysingerb2496652019-09-12 23:35:46 -0400442class CheckKernelConfig(PreUploadTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -0500443 """Tests for _kernel_configcheck."""
444
Mike Frysinger1459d362014-12-06 13:53:23 -0500445 def setUp(self):
446 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
447
448 def testMixedChanges(self):
449 """Mixing of changes should fail."""
450 self.file_mock.return_value = [
451 '/kernel/files/chromeos/config/base.config',
452 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
453 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700454 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
455 self.assertTrue(failure)
456
Mike Frysinger1459d362014-12-06 13:53:23 -0500457 def testCodeOnly(self):
458 """Code-only changes should pass."""
459 self.file_mock.return_value = [
460 '/kernel/files/Makefile',
461 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
462 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700463 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
464 self.assertFalse(failure)
465
Mike Frysinger1459d362014-12-06 13:53:23 -0500466 def testConfigOnlyChanges(self):
467 """Config-only changes should pass."""
468 self.file_mock.return_value = [
469 '/kernel/files/chromeos/config/base.config',
470 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700471 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
472 self.assertFalse(failure)
473
Jon Salz98255932012-08-18 14:48:02 +0800474
Mike Frysingerb2496652019-09-12 23:35:46 -0400475class CheckJson(PreUploadTestCase):
Mike Frysinger908be682018-01-04 02:21:50 -0500476 """Tests for _run_json_check."""
477
478 def setUp(self):
479 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
480 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
481
482 def testNoJson(self):
483 """Nothing should be checked w/no JSON files."""
484 self.file_mock.return_value = [
485 '/foo/bar.txt',
486 '/readme.md',
487 ]
488 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
489 self.assertIsNone(ret)
490
491 def testValidJson(self):
492 """We should accept valid json files."""
493 self.file_mock.return_value = [
494 '/foo/bar.txt',
495 '/data.json',
496 ]
497 self.content_mock.return_value = '{}'
498 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
499 self.assertIsNone(ret)
500 self.content_mock.assert_called_once_with('/data.json', 'COMMIT')
501
502 def testInvalidJson(self):
503 """We should reject invalid json files."""
504 self.file_mock.return_value = [
505 '/foo/bar.txt',
506 '/data.json',
507 ]
508 self.content_mock.return_value = '{'
509 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
510 self.assertIsNotNone(ret)
511 self.content_mock.assert_called_once_with('/data.json', 'COMMIT')
512
513
Mike Frysingerb2496652019-09-12 23:35:46 -0400514class CheckManifests(PreUploadTestCase):
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500515 """Tests _check_manifests."""
516
517 def setUp(self):
518 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
519 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
520
521 def testNoManifests(self):
522 """Nothing should be checked w/no Manifest files."""
523 self.file_mock.return_value = [
524 '/foo/bar.txt',
525 '/readme.md',
526 '/manifest',
527 '/Manifest.txt',
528 ]
529 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
530 self.assertIsNone(ret)
531
532 def testValidManifest(self):
533 """Accept valid Manifest files."""
534 self.file_mock.return_value = [
535 '/foo/bar.txt',
536 '/cat/pkg/Manifest',
537 ]
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400538 self.content_mock.return_value = """# Comment and blank lines.
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500539
540DIST lines
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400541"""
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500542 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
543 self.assertIsNone(ret)
544 self.content_mock.assert_called_once_with('/cat/pkg/Manifest', 'COMMIT')
545
546 def _testReject(self, content):
547 """Make sure |content| is rejected."""
548 self.file_mock.return_value = ('/Manifest',)
549 self.content_mock.reset_mock()
550 self.content_mock.return_value = content
551 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
552 self.assertIsNotNone(ret)
553 self.content_mock.assert_called_once_with('/Manifest', 'COMMIT')
554
555 def testRejectBlank(self):
556 """Reject empty manifests."""
557 self._testReject('')
558
559 def testNoTrailingNewLine(self):
560 """Reject manifests w/out trailing newline."""
561 self._testReject('DIST foo')
562
563 def testNoLeadingBlankLines(self):
564 """Reject manifests w/leading blank lines."""
565 self._testReject('\nDIST foo\n')
566
567 def testNoTrailingBlankLines(self):
568 """Reject manifests w/trailing blank lines."""
569 self._testReject('DIST foo\n\n')
570
571 def testNoLeadingWhitespace(self):
572 """Reject manifests w/lines w/leading spaces."""
573 self._testReject(' DIST foo\n')
574 self._testReject(' # Comment\n')
575
576 def testNoTrailingWhitespace(self):
577 """Reject manifests w/lines w/trailing spaces."""
578 self._testReject('DIST foo \n')
579 self._testReject('# Comment \n')
580 self._testReject(' \n')
581
582 def testOnlyDistLines(self):
583 """Only allow DIST lines in here."""
584 self._testReject('EBUILD foo\n')
585
586
Mike Frysingerb2496652019-09-12 23:35:46 -0400587class CheckPortageMakeUseVar(PreUploadTestCase):
Daniel Erat9d203ff2015-02-17 10:12:21 -0700588 """Tests for _check_portage_make_use_var."""
589
590 def setUp(self):
591 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
592 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
593
594 def testMakeConfOmitsOriginalUseValue(self):
595 """Fail for make.conf that discards the previous value of $USE."""
596 self.file_mock.return_value = ['make.conf']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400597 self.content_mock.return_value = u'USE="foo"\nUSE="${USE} bar"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700598 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
599 self.assertTrue(failure, failure)
600
601 def testMakeConfCorrectUsage(self):
602 """Succeed for make.conf that preserves the previous value of $USE."""
603 self.file_mock.return_value = ['make.conf']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400604 self.content_mock.return_value = u'USE="${USE} foo"\nUSE="${USE}" bar'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700605 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
606 self.assertFalse(failure, failure)
607
608 def testMakeDefaultsReferencesOriginalUseValue(self):
609 """Fail for make.defaults that refers to a not-yet-set $USE value."""
610 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400611 self.content_mock.return_value = u'USE="${USE} foo"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700612 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
613 self.assertTrue(failure, failure)
614
615 # Also check for "$USE" without curly brackets.
Mike Frysinger71e643e2019-09-13 17:26:39 -0400616 self.content_mock.return_value = u'USE="$USE foo"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700617 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
618 self.assertTrue(failure, failure)
619
620 def testMakeDefaultsOverwritesUseValue(self):
621 """Fail for make.defaults that discards its own $USE value."""
622 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400623 self.content_mock.return_value = u'USE="foo"\nUSE="bar"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700624 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
625 self.assertTrue(failure, failure)
626
627 def testMakeDefaultsCorrectUsage(self):
628 """Succeed for make.defaults that sets and preserves $USE."""
629 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400630 self.content_mock.return_value = u'USE="foo"\nUSE="${USE}" bar'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700631 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
632 self.assertFalse(failure, failure)
633
634
Mike Frysingerb2496652019-09-12 23:35:46 -0400635class CheckEbuildEapi(PreUploadTestCase):
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500636 """Tests for _check_ebuild_eapi."""
637
Alex Deymo643ac4c2015-09-03 10:40:50 -0700638 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500639
640 def setUp(self):
641 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
642 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
643 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff',
644 side_effect=Exception())
645
646 def testSkipUpstreamOverlays(self):
647 """Skip ebuilds found in upstream overlays."""
648 self.file_mock.side_effect = Exception()
649 ret = pre_upload._check_ebuild_eapi(self.PORTAGE_STABLE, 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400650 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500651
652 # Make sure our condition above triggers.
653 self.assertRaises(Exception, pre_upload._check_ebuild_eapi, 'o', 'HEAD')
654
655 def testSkipNonEbuilds(self):
656 """Skip non-ebuild files."""
657 self.content_mock.side_effect = Exception()
658
659 self.file_mock.return_value = ['some-file', 'ebuild/dir', 'an.ebuild~']
Alex Deymo643ac4c2015-09-03 10:40:50 -0700660 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400661 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500662
663 # Make sure our condition above triggers.
664 self.file_mock.return_value.append('a/real.ebuild')
Alex Deymo643ac4c2015-09-03 10:40:50 -0700665 self.assertRaises(Exception, pre_upload._check_ebuild_eapi,
666 ProjectNamed('o'), 'HEAD')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500667
668 def testSkipSymlink(self):
669 """Skip files that are just symlinks."""
670 self.file_mock.return_value = ['a-r1.ebuild']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400671 self.content_mock.return_value = u'a.ebuild'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700672 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400673 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500674
675 def testRejectEapiImplicit0Content(self):
676 """Reject ebuilds that do not declare EAPI (so it's 0)."""
677 self.file_mock.return_value = ['a.ebuild']
678
Mike Frysinger71e643e2019-09-13 17:26:39 -0400679 self.content_mock.return_value = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500680IUSE="foo"
681src_compile() { }
682"""
Alex Deymo643ac4c2015-09-03 10:40:50 -0700683 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500684 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500685
686 def testRejectExplicitEapi1Content(self):
687 """Reject ebuilds that do declare old EAPI explicitly."""
688 self.file_mock.return_value = ['a.ebuild']
689
Mike Frysinger71e643e2019-09-13 17:26:39 -0400690 template = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500691EAPI=%s
692IUSE="foo"
693src_compile() { }
694"""
695 # Make sure we only check the first EAPI= setting.
Mike Frysinger948284a2018-02-01 15:22:56 -0500696 self.content_mock.return_value = template % '1\nEAPI=60'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700697 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500698 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500699
700 # Verify we handle double quotes too.
701 self.content_mock.return_value = template % '"1"'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700702 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500703 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500704
705 # Verify we handle single quotes too.
706 self.content_mock.return_value = template % "'1'"
Alex Deymo643ac4c2015-09-03 10:40:50 -0700707 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500708 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500709
Mike Frysinger948284a2018-02-01 15:22:56 -0500710 def testAcceptExplicitNewEapiContent(self):
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500711 """Accept ebuilds that do declare new EAPI explicitly."""
712 self.file_mock.return_value = ['a.ebuild']
713
Mike Frysinger71e643e2019-09-13 17:26:39 -0400714 template = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500715EAPI=%s
716IUSE="foo"
717src_compile() { }
718"""
719 # Make sure we only check the first EAPI= setting.
Mike Frysinger948284a2018-02-01 15:22:56 -0500720 self.content_mock.return_value = template % '6\nEAPI=1'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700721 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400722 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500723
724 # Verify we handle double quotes too.
725 self.content_mock.return_value = template % '"5"'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700726 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400727 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500728
729 # Verify we handle single quotes too.
730 self.content_mock.return_value = template % "'5-hdepend'"
Alex Deymo643ac4c2015-09-03 10:40:50 -0700731 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400732 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500733
734
Mike Frysingerb2496652019-09-12 23:35:46 -0400735class CheckEbuildKeywords(PreUploadTestCase):
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400736 """Tests for _check_ebuild_keywords."""
737
738 def setUp(self):
739 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
740 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
741
742 def testNoEbuilds(self):
743 """If no ebuilds are found, do not scan."""
744 self.file_mock.return_value = ['a.file', 'ebuild-is-not.foo']
745
Alex Deymo643ac4c2015-09-03 10:40:50 -0700746 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400747 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400748
749 self.assertEqual(self.content_mock.call_count, 0)
750
751 def testSomeEbuilds(self):
752 """If ebuilds are found, only scan them."""
753 self.file_mock.return_value = ['a.file', 'blah', 'foo.ebuild', 'cow']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400754 self.content_mock.return_value = u''
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400755
Alex Deymo643ac4c2015-09-03 10:40:50 -0700756 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400757 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400758
759 self.assertEqual(self.content_mock.call_count, 1)
760
761 def _CheckContent(self, content, fails):
762 """Test helper for inputs/outputs.
763
764 Args:
765 content: The ebuild content to test.
766 fails: Whether |content| should trigger a hook failure.
767 """
768 self.file_mock.return_value = ['a.ebuild']
769 self.content_mock.return_value = content
770
Alex Deymo643ac4c2015-09-03 10:40:50 -0700771 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400772 if fails:
Mike Frysingerb81102f2014-11-21 00:33:35 -0500773 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400774 else:
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400775 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400776
777 self.assertEqual(self.content_mock.call_count, 1)
778
779 def testEmpty(self):
780 """Check KEYWORDS= is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400781 self._CheckContent(u'# HEADER\nKEYWORDS=\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400782
783 def testEmptyQuotes(self):
784 """Check KEYWORDS="" is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400785 self._CheckContent(u'# HEADER\nKEYWORDS=" "\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400786
787 def testStableGlob(self):
788 """Check KEYWORDS=* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400789 self._CheckContent(u'# HEADER\nKEYWORDS="\t*\t"\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400790
791 def testUnstableGlob(self):
792 """Check KEYWORDS=~* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400793 self._CheckContent(u'# HEADER\nKEYWORDS="~* "\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400794
795 def testRestrictedGlob(self):
796 """Check KEYWORDS=-* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400797 self._CheckContent(u'# HEADER\nKEYWORDS="\t-* arm"\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400798
799 def testMissingGlobs(self):
800 """Reject KEYWORDS missing any globs."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400801 self._CheckContent(u'# HEADER\nKEYWORDS="~arm x86"\nblah\n', True)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400802
803
Sergey Frolovc1bd8782021-01-20 19:35:44 -0700804class CheckEbuildLicense(PreUploadTestCase):
805 """Tests for _check_ebuild_licenses."""
806
807 def setUp(self):
808 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
809 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
810
811 def testNoEbuilds(self):
812 """If no ebuilds are found, do not scan."""
813 self.file_mock.return_value = ['a.file', 'ebuild-is-not.foo']
814
815 ret = pre_upload._check_ebuild_licenses(ProjectNamed('overlay'), 'HEAD')
816 self.assertIsNone(ret)
817
818 self.assertEqual(self.content_mock.call_count, 0)
819
820 def testSomeEbuilds(self):
821 """If ebuilds are found, only scan them."""
822 self.file_mock.return_value = ['a.file', 'blah', 'cow',
823 'overlay/category/pkg/pkg.ebuild']
824 self.content_mock.return_value = '# HEADER\nLICENSE="GPL-3"\nblah\n'
825
826 ret = pre_upload._check_ebuild_licenses(ProjectNamed('overlay'), 'HEAD')
827 self.assertIsNone(ret)
828
829 self.assertEqual(self.content_mock.call_count, 1)
830
831 def _CheckContent(self, license_field, ebuild_path, fails):
832 """Test helper for inputs/outputs.
833
834 Args:
835 license_field: Contents of LICENSE variable in the tested ebuild.
836 ebuild_path: The path to the tested ebuild.
837 fails: Whether inputs should trigger a hook failure.
838 """
839 self.file_mock.return_value = [ebuild_path]
840 self.content_mock.return_value = f'# blah\nLICENSE="{license_field}"\nbla\n'
841
842 ret = pre_upload._check_ebuild_licenses(ProjectNamed('overlay'), 'HEAD')
843 if fails:
844 self.assertIsInstance(ret, errors.HookFailure)
845 else:
846 self.assertIsNone(ret)
847
848 self.assertEqual(self.content_mock.call_count, 1)
849
850 def testEmpty(self):
851 """Check empty license is not accepted."""
852 self._CheckContent('', 'overlay/category/pkg/pkg.ebuild', True)
853
854 def testValid(self):
855 """Check valid license is accepted."""
856 self._CheckContent('GPL-3', 'overlay/category/pkg/pkg.ebuild', False)
857
858 def testVirtualNotMetapackage(self):
859 """Check virtual package not using metapackage is not accepted."""
860 self._CheckContent('GPL-3', 'overlay/virtual/pkg/pkg.ebuild', True)
861
862 def testVirtualMetapackage(self):
863 """Check virtual package using metapackage is accepted."""
864 self._CheckContent('metapackage', 'overlay/virtual/pkg/pkg.ebuild', False)
865
866
Mike Frysingerb04778f2020-11-30 02:41:14 -0500867class CheckEbuildOwners(PreUploadTestCase):
868 """Tests for _check_ebuild_owners."""
869
870 def setUp(self):
871 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
872 self.content_mock = self.PatchObject(
873 pre_upload, '_get_file_content', return_value=None)
874
875 def testNoMatches(self):
876 """Handle no matching files."""
877 self.file_mock.return_value = []
Mike Frysingerd9ed85f2021-02-03 12:38:24 -0500878 ret = pre_upload._check_ebuild_owners(ProjectNamed('project'), 'HEAD')
Mike Frysingerb04778f2020-11-30 02:41:14 -0500879 self.assertIsNone(ret)
880
881 def testNoEbuilds(self):
882 """Handle CLs w/no ebuilds."""
883 self.file_mock.return_value = [
884 DiffEntry(src_file='profiles/package.mask', status='M'),
885 DiffEntry(src_file='dev-util/pkg/README.md', status='A'),
886 DiffEntry(src_file='scripts/sync_sonic.py', status='D'),
887 ]
Mike Frysingerd9ed85f2021-02-03 12:38:24 -0500888 ret = pre_upload._check_ebuild_owners(ProjectNamed('project'), 'HEAD')
Mike Frysingerb04778f2020-11-30 02:41:14 -0500889 self.assertIsNone(ret)
890
891 def testMissingOwnersFailure(self):
892 """Test cases that should flag missing OWNERS."""
893 TESTS = (
894 [
895 DiffEntry(src_file='dev-util/pkg/foo.ebuild', status='A'),
896 DiffEntry(src_file='dev-util/pkg/README.md', status='A'),
897 DiffEntry(src_file='dev-util/pkg/Manifest', status='A'),
898 ],
899 )
900 for test in TESTS:
901 self.file_mock.return_value = test
Mike Frysingerd9ed85f2021-02-03 12:38:24 -0500902 ret = pre_upload._check_ebuild_owners(ProjectNamed('project'), 'HEAD')
Mike Frysingerb04778f2020-11-30 02:41:14 -0500903 self.assertIsNotNone(ret)
904
905 def testMissingOwnersIgnore(self):
906 """Test cases that should ignore missing OWNERS."""
907 TESTS = (
908 [
909 DiffEntry(src_file='dev-util/pkg/foo-9999.ebuild', status='M'),
910 ],
911 [
912 DiffEntry(src_file='dev-util/pkg/foo-0.ebuild', status='M'),
913 DiffEntry(src_file='dev-util/pkg/foo-0-r1.ebuild', status='A'),
914 ],
915 [
916 DiffEntry(src_file='dev-util/pkg/foo-0-r1.ebuild', status='D'),
917 DiffEntry(src_file='dev-util/pkg/foo-0-r2.ebuild', status='A'),
918 ],
919 )
920 for test in TESTS:
921 self.file_mock.return_value = test
Mike Frysingerd9ed85f2021-02-03 12:38:24 -0500922 ret = pre_upload._check_ebuild_owners(ProjectNamed('project'), 'HEAD')
Mike Frysingerb04778f2020-11-30 02:41:14 -0500923 self.assertIsNone(ret)
924
925 def testOwnersExist(self):
926 """Test cases where OWNERS exist."""
927 TESTS = (
928 [
929 DiffEntry(src_file='dev-util/pkg/foo-9999.ebuild', status='A'),
930 ],
931 )
932 self.content_mock.return_value = 'foo'
933 for test in TESTS:
934 self.file_mock.return_value = test
Mike Frysingerd9ed85f2021-02-03 12:38:24 -0500935 ret = pre_upload._check_ebuild_owners(ProjectNamed('project'), 'HEAD')
Mike Frysingerb04778f2020-11-30 02:41:14 -0500936 self.assertIsNone(ret)
937 # This should be the # of package dirs across all tests. This makes sure
938 # we actually called the owners check logic and didn't return early.
939 self.assertEqual(1, self.content_mock.call_count)
940
Mike Frysingerd9ed85f2021-02-03 12:38:24 -0500941 def testCommonPublicBoardOverlays(self):
942 """Allow an OWNERS in the top of the overlay to satisfy requirements."""
943 # The public repo that has all public overlays.
944 project = ProjectNamed('chromiumos/overlays/board-overlays')
945
946 def _get_content(path, _commit):
947 if path == 'overlay-oak/dev-util/pkg/OWNERS':
948 return None
949 elif path == 'overlay-oak/OWNERS':
950 return overlay_owners
951 else:
952 raise AssertionError(f'Unhandled test path: {path}')
953
954 self.content_mock.side_effect = _get_content
955 self.file_mock.return_value = [
956 DiffEntry(src_file='overlay-oak/dev-util/pkg/pkg-0-r1.ebuild',
957 status='A'),
958 ]
959
960 # OWNERS doesn't exist.
961 overlay_owners = None
962 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
963 self.assertIsNotNone(ret)
964
965 # OWNERS exists, but is empty.
966 overlay_owners = ''
967 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
968 self.assertIsNotNone(ret)
969
970 # OWNERS exists, but is too permissive.
971 overlay_owners = '# Everyone is an owner!\n*\n'
972 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
973 self.assertIsNotNone(ret)
974
975 # OWNERS exists, and is good.
976 overlay_owners = 'foo@chromium.org'
977 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
978 self.assertIsNone(ret)
979
980 def testCommonPrivateBoardOverlays(self):
981 """Allow an OWNERS in the top of the overlay to satisfy requirements."""
982 # A private repo that holds one board.
983 project = ProjectNamed('chromeos/overlays/baseboard-boo')
984
985 def _get_content(path, _commit):
986 if path == 'dev-util/pkg/OWNERS':
987 return None
988 elif path == 'OWNERS':
989 return overlay_owners
990 else:
991 raise AssertionError(f'Unhandled test path: {path}')
992
993 self.content_mock.side_effect = _get_content
994 self.file_mock.return_value = [
995 DiffEntry(src_file='dev-util/pkg/pkg-0-r1.ebuild', status='A'),
996 ]
997
998 # OWNERS doesn't exist.
999 overlay_owners = None
1000 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
1001 self.assertIsNotNone(ret)
1002
1003 # OWNERS exists, but is empty.
1004 overlay_owners = ''
1005 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
1006 self.assertIsNotNone(ret)
1007
1008 # OWNERS exists, but is too permissive.
1009 overlay_owners = '# Everyone is an owner!\n*\n'
1010 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
1011 self.assertIsNotNone(ret)
1012
1013 # OWNERS exists, and is good.
1014 overlay_owners = 'foo@chromium.org'
1015 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
1016 self.assertIsNone(ret)
1017
1018 def testSharedOverlays(self):
1019 """Do not allow top-level OWNERS for shared overlays."""
1020 project = ProjectNamed('chromiumos/overlays/portage-stable')
1021
1022 def _get_content(path, _commit):
1023 if path == 'dev-util/pkg/OWNERS':
1024 return None
1025 elif path == 'OWNERS':
1026 return '# Global owners.\nfoo@bar\n'
1027 else:
1028 raise AssertionError(f'Unhandled test path: {path}')
1029
1030 self.content_mock.side_effect = _get_content
1031 self.file_mock.return_value = [
1032 DiffEntry(src_file='dev-util/pkg/pkg-0-r1.ebuild', status='A'),
1033 ]
1034
1035 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
1036 self.assertIsNotNone(ret)
1037
Mike Frysingerb04778f2020-11-30 02:41:14 -05001038
Mike Frysinger6ee76b82020-11-20 01:16:06 -05001039class CheckEbuildR0(PreUploadTestCase):
1040 """Tests for _check_ebuild_r0."""
1041
1042 def setUp(self):
1043 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1044
1045 def testNoMatches(self):
1046 """Handle no matching files."""
1047 self.file_mock.return_value = []
Mike Frysingerd9ed85f2021-02-03 12:38:24 -05001048 ret = pre_upload._check_ebuild_r0(ProjectNamed('project'), 'HEAD')
Mike Frysinger6ee76b82020-11-20 01:16:06 -05001049 self.assertIsNone(ret)
1050
1051 def testBadEbuilds(self):
1052 """Handle matching r0 files."""
1053 self.file_mock.return_value = ['foo-1-r0.ebuild']
Mike Frysingerd9ed85f2021-02-03 12:38:24 -05001054 ret = pre_upload._check_ebuild_r0(ProjectNamed('project'), 'HEAD')
Mike Frysinger6ee76b82020-11-20 01:16:06 -05001055 self.assertIsNotNone(ret)
1056
1057
Mike Frysingerb2496652019-09-12 23:35:46 -04001058class CheckEbuildVirtualPv(PreUploadTestCase):
Mike Frysingercd363c82014-02-01 05:20:18 -05001059 """Tests for _check_ebuild_virtual_pv."""
1060
Alex Deymo643ac4c2015-09-03 10:40:50 -07001061 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
1062 CHROMIUMOS_OVERLAY = ProjectNamed('chromiumos/overlays/chromiumos')
1063 BOARD_OVERLAY = ProjectNamed('chromiumos/overlays/board-overlays')
1064 PRIVATE_OVERLAY = ProjectNamed('chromeos/overlays/overlay-link-private')
1065 PRIVATE_VARIANT_OVERLAY = ProjectNamed('chromeos/overlays/'
1066 'overlay-variant-daisy-spring-private')
Mike Frysingercd363c82014-02-01 05:20:18 -05001067
1068 def setUp(self):
1069 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1070
1071 def testNoVirtuals(self):
1072 """Skip non virtual packages."""
1073 self.file_mock.return_value = ['some/package/package-3.ebuild']
Alex Deymo643ac4c2015-09-03 10:40:50 -07001074 ret = pre_upload._check_ebuild_virtual_pv(ProjectNamed('overlay'), 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001075 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001076
1077 def testCommonVirtuals(self):
1078 """Non-board overlays should use PV=1."""
1079 template = 'virtual/foo/foo-%s.ebuild'
1080 self.file_mock.return_value = [template % '1']
1081 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001082 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001083
1084 self.file_mock.return_value = [template % '2']
1085 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -05001086 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -05001087
1088 def testPublicBoardVirtuals(self):
1089 """Public board overlays should use PV=2."""
1090 template = 'overlay-lumpy/virtual/foo/foo-%s.ebuild'
1091 self.file_mock.return_value = [template % '2']
1092 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001093 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001094
1095 self.file_mock.return_value = [template % '2.5']
1096 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -05001097 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -05001098
1099 def testPublicBoardVariantVirtuals(self):
1100 """Public board variant overlays should use PV=2.5."""
1101 template = 'overlay-variant-lumpy-foo/virtual/foo/foo-%s.ebuild'
1102 self.file_mock.return_value = [template % '2.5']
1103 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001104 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001105
1106 self.file_mock.return_value = [template % '3']
1107 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -05001108 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -05001109
1110 def testPrivateBoardVirtuals(self):
1111 """Private board overlays should use PV=3."""
1112 template = 'virtual/foo/foo-%s.ebuild'
1113 self.file_mock.return_value = [template % '3']
1114 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001115 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001116
1117 self.file_mock.return_value = [template % '3.5']
1118 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -05001119 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -05001120
1121 def testPrivateBoardVariantVirtuals(self):
1122 """Private board variant overlays should use PV=3.5."""
1123 template = 'virtual/foo/foo-%s.ebuild'
1124 self.file_mock.return_value = [template % '3.5']
1125 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001126 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001127
Bernie Thompsone5ee1822016-01-12 14:22:23 -08001128 def testSpecialVirtuals(self):
1129 """Some cases require deeper versioning and can be >= 4."""
1130 template = 'virtual/foo/foo-%s.ebuild'
Mike Frysingercd363c82014-02-01 05:20:18 -05001131 self.file_mock.return_value = [template % '4']
1132 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001133 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001134
Bernie Thompsone5ee1822016-01-12 14:22:23 -08001135 self.file_mock.return_value = [template % '4.5']
1136 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001137 self.assertIsNone(ret)
Mike Frysinger98638102014-08-28 00:15:08 -04001138
Mike Frysingerb2496652019-09-12 23:35:46 -04001139class CheckCrosLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -07001140 """Tests for _check_cros_license."""
Mike Frysinger98638102014-08-28 00:15:08 -04001141
1142 def setUp(self):
1143 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1144 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1145
1146 def testOldHeaders(self):
1147 """Accept old header styles."""
1148 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001149 (u'#!/bin/sh\n'
1150 u'# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.\n'
1151 u'# Use of this source code is governed by a BSD-style license that'
1152 u' can be\n'
1153 u'# found in the LICENSE file.\n'),
1154 (u'// Copyright 2010-2013 The Chromium OS Authors. All rights reserved.'
1155 u'\n// Use of this source code is governed by a BSD-style license that'
1156 u' can be\n'
1157 u'// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -04001158 )
1159 self.file_mock.return_value = ['file']
1160 for header in HEADERS:
1161 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +09001162 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
1163
1164 def testNewFileYear(self):
1165 """Added files should have the current year in license header."""
1166 year = datetime.datetime.now().year
1167 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001168 (u'// Copyright 2016 The Chromium OS Authors. All rights reserved.\n'
1169 u'// Use of this source code is governed by a BSD-style license that'
1170 u' can be\n'
1171 u'// found in the LICENSE file.\n'),
1172 (u'// Copyright %d The Chromium OS Authors. All rights reserved.\n'
1173 u'// Use of this source code is governed by a BSD-style license that'
1174 u' can be\n'
1175 u'// found in the LICENSE file.\n') % year,
Keigo Oka7e880ac2019-07-03 15:03:43 +09001176 )
1177 want_error = (True, False)
1178 def fake_get_affected_files(_, relative, include_adds=True):
1179 _ = relative
1180 if include_adds:
1181 return ['file']
1182 else:
1183 return []
1184
1185 self.file_mock.side_effect = fake_get_affected_files
1186 for i, header in enumerate(HEADERS):
1187 self.content_mock.return_value = header
1188 if want_error[i]:
1189 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
1190 else:
1191 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -04001192
1193 def testRejectC(self):
1194 """Reject the (c) in newer headers."""
1195 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001196 (u'// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.'
1197 u'\n'
1198 u'// Use of this source code is governed by a BSD-style license that'
1199 u' can be\n'
1200 u'// found in the LICENSE file.\n'),
1201 (u'// Copyright (c) 2020 The Chromium OS Authors. All rights reserved.'
1202 u'\n'
1203 u'// Use of this source code is governed by a BSD-style license that'
1204 u' can be\n'
1205 u'// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -04001206 )
1207 self.file_mock.return_value = ['file']
1208 for header in HEADERS:
1209 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +09001210 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001211
Brian Norris68838dd2018-09-26 18:30:24 -07001212 def testNoLeadingSpace(self):
1213 """Allow headers without leading space (e.g., not a source comment)"""
1214 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001215 (u'Copyright 2018 The Chromium OS Authors. All rights reserved.\n'
1216 u'Use of this source code is governed by a BSD-style license that '
1217 u'can be\n'
1218 u'found in the LICENSE file.\n'),
Brian Norris68838dd2018-09-26 18:30:24 -07001219 )
1220 self.file_mock.return_value = ['file']
1221 for header in HEADERS:
1222 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +09001223 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Brian Norris68838dd2018-09-26 18:30:24 -07001224
Keigo Oka9732e382019-06-28 17:44:59 +09001225 def testNoExcludedGolang(self):
1226 """Don't exclude .go files for license checks."""
1227 self.file_mock.return_value = ['foo/main.go']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001228 self.content_mock.return_value = u'package main\nfunc main() {}'
Keigo Oka7e880ac2019-07-03 15:03:43 +09001229 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Keigo Oka9732e382019-06-28 17:44:59 +09001230
Ken Turnerd07564b2018-02-08 17:57:59 +11001231 def testIgnoreExcludedPaths(self):
1232 """Ignores excluded paths for license checks."""
1233 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001234 self.content_mock.return_value = u'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +09001235 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +11001236
Tom Hughes90b7bd42020-11-10 10:31:49 -08001237 def testIgnoreMetadataFiles(self):
1238 """Ignores metadata files for license checks."""
1239 self.file_mock.return_value = ['foo/DIR_METADATA']
1240 self.content_mock.return_value = u'team_email: "team@chromium.org"'
1241 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
1242
Chris McDonald7b63c8e2019-04-25 10:27:27 -06001243 def testIgnoreTopLevelExcludedPaths(self):
1244 """Ignores excluded paths for license checks."""
1245 self.file_mock.return_value = ['OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001246 self.content_mock.return_value = u'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +09001247 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001248
Mike Frysingerb2496652019-09-12 23:35:46 -04001249class CheckAOSPLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -07001250 """Tests for _check_aosp_license."""
1251
1252 def setUp(self):
1253 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1254 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1255
1256 def testHeaders(self):
1257 """Accept old header styles."""
1258 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001259 u"""//
Alex Deymof5792ce2015-08-24 22:50:08 -07001260// Copyright (C) 2011 The Android Open Source Project
1261//
1262// Licensed under the Apache License, Version 2.0 (the "License");
1263// you may not use this file except in compliance with the License.
1264// You may obtain a copy of the License at
1265//
1266// http://www.apache.org/licenses/LICENSE-2.0
1267//
1268// Unless required by applicable law or agreed to in writing, software
1269// distributed under the License is distributed on an "AS IS" BASIS,
1270// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1271// See the License for the specific language governing permissions and
1272// limitations under the License.
1273//
1274""",
Mike Frysinger71e643e2019-09-13 17:26:39 -04001275 u"""#
Alex Deymof5792ce2015-08-24 22:50:08 -07001276# Copyright (c) 2015 The Android Open Source Project
1277#
1278# Licensed under the Apache License, Version 2.0 (the "License");
1279# you may not use this file except in compliance with the License.
1280# You may obtain a copy of the License at
1281#
1282# http://www.apache.org/licenses/LICENSE-2.0
1283#
1284# Unless required by applicable law or agreed to in writing, software
1285# distributed under the License is distributed on an "AS IS" BASIS,
1286# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1287# See the License for the specific language governing permissions and
1288# limitations under the License.
1289#
1290""",
1291 )
1292 self.file_mock.return_value = ['file']
1293 for header in HEADERS:
1294 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001295 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001296
1297 def testRejectNoLinesAround(self):
1298 """Reject headers missing the empty lines before/after the license."""
1299 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001300 u"""# Copyright (c) 2015 The Android Open Source Project
Alex Deymof5792ce2015-08-24 22:50:08 -07001301#
1302# Licensed under the Apache License, Version 2.0 (the "License");
1303# you may not use this file except in compliance with the License.
1304# You may obtain a copy of the License at
1305#
1306# http://www.apache.org/licenses/LICENSE-2.0
1307#
1308# Unless required by applicable law or agreed to in writing, software
1309# distributed under the License is distributed on an "AS IS" BASIS,
1310# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1311# See the License for the specific language governing permissions and
1312# limitations under the License.
1313""",
1314 )
1315 self.file_mock.return_value = ['file']
1316 for header in HEADERS:
1317 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001318 self.assertIsNotNone(pre_upload._check_aosp_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -04001319
Ken Turnerd07564b2018-02-08 17:57:59 +11001320 def testIgnoreExcludedPaths(self):
1321 """Ignores excluded paths for license checks."""
1322 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001323 self.content_mock.return_value = u'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001324 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +11001325
Chris McDonald7b63c8e2019-04-25 10:27:27 -06001326 def testIgnoreTopLevelExcludedPaths(self):
1327 """Ignores excluded paths for license checks."""
1328 self.file_mock.return_value = ['OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001329 self.content_mock.return_value = u'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001330 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
1331
Mike Frysinger98638102014-08-28 00:15:08 -04001332
Mike Frysingerb2496652019-09-12 23:35:46 -04001333class CheckLayoutConfTestCase(PreUploadTestCase):
Mike Frysingerd7734522015-02-26 16:12:43 -05001334 """Tests for _check_layout_conf."""
1335
1336 def setUp(self):
1337 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1338 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1339
Mike Frysingerd9ed85f2021-02-03 12:38:24 -05001340 def assertAccepted(self, files, project=ProjectNamed('project'),
1341 commit='fake sha1'):
Mike Frysingerd7734522015-02-26 16:12:43 -05001342 """Assert _check_layout_conf accepts |files|."""
1343 self.file_mock.return_value = files
1344 ret = pre_upload._check_layout_conf(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001345 self.assertIsNone(ret, msg='rejected with:\n%s' % ret)
Mike Frysingerd7734522015-02-26 16:12:43 -05001346
Mike Frysingerd9ed85f2021-02-03 12:38:24 -05001347 def assertRejected(self, files, project=ProjectNamed('project'),
1348 commit='fake sha1'):
Mike Frysingerd7734522015-02-26 16:12:43 -05001349 """Assert _check_layout_conf rejects |files|."""
1350 self.file_mock.return_value = files
1351 ret = pre_upload._check_layout_conf(project, commit)
1352 self.assertTrue(isinstance(ret, errors.HookFailure))
1353
1354 def GetLayoutConf(self, filters=()):
1355 """Return a valid layout.conf with |filters| lines removed."""
1356 all_lines = [
Mike Frysinger71e643e2019-09-13 17:26:39 -04001357 u'masters = portage-stable chromiumos',
1358 u'profile-formats = portage-2 profile-default-eapi',
1359 u'profile_eapi_when_unspecified = 5-progress',
1360 u'repo-name = link',
1361 u'thin-manifests = true',
1362 u'use-manifests = strict',
Mike Frysingerd7734522015-02-26 16:12:43 -05001363 ]
1364
1365 lines = []
1366 for line in all_lines:
1367 for filt in filters:
1368 if line.startswith(filt):
1369 break
1370 else:
1371 lines.append(line)
1372
Mike Frysinger71e643e2019-09-13 17:26:39 -04001373 return u'\n'.join(lines)
Mike Frysingerd7734522015-02-26 16:12:43 -05001374
1375 def testNoFilesToCheck(self):
1376 """Don't blow up when there are no layout.conf files."""
1377 self.assertAccepted([])
1378
1379 def testRejectRepoNameFile(self):
1380 """If profiles/repo_name is set, kick it out."""
1381 self.assertRejected(['profiles/repo_name'])
1382
1383 def testAcceptValidLayoutConf(self):
1384 """Accept a fully valid layout.conf."""
1385 self.content_mock.return_value = self.GetLayoutConf()
1386 self.assertAccepted(['metadata/layout.conf'])
1387
1388 def testAcceptUnknownKeys(self):
1389 """Accept keys we don't explicitly know about."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001390 self.content_mock.return_value = self.GetLayoutConf() + u'\nzzz-top = ok'
Mike Frysingerd7734522015-02-26 16:12:43 -05001391 self.assertAccepted(['metadata/layout.conf'])
1392
1393 def testRejectUnsorted(self):
1394 """Reject an unsorted layout.conf."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001395 self.content_mock.return_value = u'zzz-top = bad\n' + self.GetLayoutConf()
Mike Frysingerd7734522015-02-26 16:12:43 -05001396 self.assertRejected(['metadata/layout.conf'])
1397
1398 def testRejectMissingThinManifests(self):
1399 """Reject a layout.conf missing thin-manifests."""
1400 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001401 filters=[u'thin-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001402 self.assertRejected(['metadata/layout.conf'])
1403
1404 def testRejectMissingUseManifests(self):
1405 """Reject a layout.conf missing use-manifests."""
1406 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001407 filters=[u'use-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001408 self.assertRejected(['metadata/layout.conf'])
1409
1410 def testRejectMissingEapiFallback(self):
1411 """Reject a layout.conf missing profile_eapi_when_unspecified."""
1412 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001413 filters=[u'profile_eapi_when_unspecified'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001414 self.assertRejected(['metadata/layout.conf'])
1415
1416 def testRejectMissingRepoName(self):
1417 """Reject a layout.conf missing repo-name."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001418 self.content_mock.return_value = self.GetLayoutConf(filters=[u'repo-name'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001419 self.assertRejected(['metadata/layout.conf'])
1420
1421
Mike Frysingerb2496652019-09-12 23:35:46 -04001422class CommitMessageTestCase(PreUploadTestCase):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001423 """Test case for funcs that check commit messages."""
1424
1425 def setUp(self):
1426 self.msg_mock = self.PatchObject(pre_upload, '_get_commit_desc')
1427
1428 @staticmethod
1429 def CheckMessage(_project, _commit):
1430 raise AssertionError('Test class must declare CheckMessage')
1431 # This dummy return is to silence pylint warning W1111 so we don't have to
1432 # enable it for all the call sites below.
1433 return 1 # pylint: disable=W0101
1434
Alex Deymo643ac4c2015-09-03 10:40:50 -07001435 def assertMessageAccepted(self, msg, project=ProjectNamed('project'),
1436 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001437 """Assert _check_change_has_bug_field accepts |msg|."""
1438 self.msg_mock.return_value = msg
1439 ret = self.CheckMessage(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001440 self.assertIsNone(ret)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001441
Alex Deymo643ac4c2015-09-03 10:40:50 -07001442 def assertMessageRejected(self, msg, project=ProjectNamed('project'),
1443 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001444 """Assert _check_change_has_bug_field rejects |msg|."""
1445 self.msg_mock.return_value = msg
1446 ret = self.CheckMessage(project, commit)
1447 self.assertTrue(isinstance(ret, errors.HookFailure))
1448
1449
1450class CheckCommitMessageBug(CommitMessageTestCase):
1451 """Tests for _check_change_has_bug_field."""
1452
Alex Deymo643ac4c2015-09-03 10:40:50 -07001453 AOSP_PROJECT = pre_upload.Project(name='overlay', dir='', remote='aosp')
1454 CROS_PROJECT = pre_upload.Project(name='overlay', dir='', remote='cros')
1455
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001456 @staticmethod
1457 def CheckMessage(project, commit):
1458 return pre_upload._check_change_has_bug_field(project, commit)
1459
1460 def testNormal(self):
1461 """Accept a commit message w/a valid BUG."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001462 self.assertMessageAccepted('\nBUG=chromium:1234\n', self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001463 self.assertMessageAccepted('\nBUG=b:1234\n', self.CROS_PROJECT)
1464
1465 self.assertMessageAccepted('\nBug: 1234\n', self.AOSP_PROJECT)
1466 self.assertMessageAccepted('\nBug:1234\n', self.AOSP_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001467
1468 def testNone(self):
1469 """Accept BUG=None."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001470 self.assertMessageAccepted('\nBUG=None\n', self.CROS_PROJECT)
1471 self.assertMessageAccepted('\nBUG=none\n', self.CROS_PROJECT)
1472 self.assertMessageAccepted('\nBug: None\n', self.AOSP_PROJECT)
1473 self.assertMessageAccepted('\nBug:none\n', self.AOSP_PROJECT)
1474
1475 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1476 self.assertMessageRejected('\nBUG=NONE\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001477
1478 def testBlank(self):
1479 """Reject blank values."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001480 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1481 self.assertMessageRejected('\nBUG=\n', project)
1482 self.assertMessageRejected('\nBUG= \n', project)
1483 self.assertMessageRejected('\nBug:\n', project)
1484 self.assertMessageRejected('\nBug: \n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001485
1486 def testNotFirstLine(self):
1487 """Reject the first line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001488 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1489 self.assertMessageRejected('BUG=None\n\n\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001490
1491 def testNotInline(self):
1492 """Reject not at the start of line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001493 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1494 self.assertMessageRejected('\n BUG=None\n', project)
1495 self.assertMessageRejected('\n\tBUG=None\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001496
1497 def testOldTrackers(self):
1498 """Reject commit messages using old trackers."""
Mike Frysingera2f28252017-10-27 22:26:14 -04001499 self.assertMessageRejected('\nBUG=chrome-os-partner:1234\n',
1500 self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001501 self.assertMessageRejected('\nBUG=chromium-os:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001502
1503 def testNoTrackers(self):
1504 """Reject commit messages w/invalid trackers."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001505 self.assertMessageRejected('\nBUG=booga:1234\n', self.CROS_PROJECT)
1506 self.assertMessageRejected('\nBUG=br:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001507
1508 def testMissing(self):
1509 """Reject commit messages w/no BUG line."""
1510 self.assertMessageRejected('foo\n')
1511
1512 def testCase(self):
1513 """Reject bug lines that are not BUG."""
1514 self.assertMessageRejected('\nbug=none\n')
1515
Cheng Yuehb707c522020-01-02 14:06:59 +08001516 def testNotAfterTest(self):
1517 """Reject any TEST line before any BUG line."""
1518 test_field = 'TEST=i did not do it\n'
1519 middle_field = 'A random between line\n'
1520 for project, bug_field in ((self.AOSP_PROJECT, 'Bug:none\n'),
1521 (self.CROS_PROJECT, 'BUG=None\n')):
1522 self.assertMessageRejected(
1523 '\n' + test_field + middle_field + bug_field, project)
1524 self.assertMessageRejected(
1525 '\n' + test_field + bug_field, project)
1526
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001527
1528class CheckCommitMessageCqDepend(CommitMessageTestCase):
1529 """Tests for _check_change_has_valid_cq_depend."""
1530
1531 @staticmethod
1532 def CheckMessage(project, commit):
1533 return pre_upload._check_change_has_valid_cq_depend(project, commit)
1534
1535 def testNormal(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001536 """Accept valid Cq-Depends line."""
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001537 self.assertMessageAccepted('\nCq-Depend: chromium:1234\nChange-Id: I123')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001538
1539 def testInvalid(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001540 """Reject invalid Cq-Depends line."""
1541 self.assertMessageRejected('\nCq-Depend=chromium=1234\n')
1542 self.assertMessageRejected('\nCq-Depend: None\n')
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001543 self.assertMessageRejected('\nCq-Depend: chromium:1234\n\nChange-Id: I123')
Mike Frysingere39d0cd2019-11-25 13:30:06 -05001544 self.assertMessageRejected('\nCQ-DEPEND=1234\n')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001545
1546
Bernie Thompsonf8fea992016-01-14 10:27:18 -08001547class CheckCommitMessageContribution(CommitMessageTestCase):
1548 """Tests for _check_change_is_contribution."""
1549
1550 @staticmethod
1551 def CheckMessage(project, commit):
1552 return pre_upload._check_change_is_contribution(project, commit)
1553
1554 def testNormal(self):
1555 """Accept a commit message which is a contribution."""
1556 self.assertMessageAccepted('\nThis is cool code I am contributing.\n')
1557
1558 def testFailureLowerCase(self):
1559 """Deny a commit message which is not a contribution."""
1560 self.assertMessageRejected('\nThis is not a contribution.\n')
1561
1562 def testFailureUpperCase(self):
1563 """Deny a commit message which is not a contribution."""
1564 self.assertMessageRejected('\nNOT A CONTRIBUTION\n')
1565
1566
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001567class CheckCommitMessageTest(CommitMessageTestCase):
1568 """Tests for _check_change_has_test_field."""
1569
1570 @staticmethod
1571 def CheckMessage(project, commit):
1572 return pre_upload._check_change_has_test_field(project, commit)
1573
1574 def testNormal(self):
1575 """Accept a commit message w/a valid TEST."""
1576 self.assertMessageAccepted('\nTEST=i did it\n')
1577
1578 def testNone(self):
1579 """Accept TEST=None."""
1580 self.assertMessageAccepted('\nTEST=None\n')
1581 self.assertMessageAccepted('\nTEST=none\n')
1582
1583 def testBlank(self):
1584 """Reject blank values."""
1585 self.assertMessageRejected('\nTEST=\n')
1586 self.assertMessageRejected('\nTEST= \n')
1587
1588 def testNotFirstLine(self):
1589 """Reject the first line."""
1590 self.assertMessageRejected('TEST=None\n\n\n')
1591
1592 def testNotInline(self):
1593 """Reject not at the start of line."""
1594 self.assertMessageRejected('\n TEST=None\n')
1595 self.assertMessageRejected('\n\tTEST=None\n')
1596
1597 def testMissing(self):
1598 """Reject commit messages w/no TEST line."""
1599 self.assertMessageRejected('foo\n')
1600
1601 def testCase(self):
1602 """Reject bug lines that are not TEST."""
1603 self.assertMessageRejected('\ntest=none\n')
1604
1605
1606class CheckCommitMessageChangeId(CommitMessageTestCase):
1607 """Tests for _check_change_has_proper_changeid."""
1608
1609 @staticmethod
1610 def CheckMessage(project, commit):
1611 return pre_upload._check_change_has_proper_changeid(project, commit)
1612
1613 def testNormal(self):
1614 """Accept a commit message w/a valid Change-Id."""
1615 self.assertMessageAccepted('foo\n\nChange-Id: I1234\n')
1616
1617 def testBlank(self):
1618 """Reject blank values."""
1619 self.assertMessageRejected('\nChange-Id:\n')
1620 self.assertMessageRejected('\nChange-Id: \n')
1621
1622 def testNotFirstLine(self):
1623 """Reject the first line."""
1624 self.assertMessageRejected('TEST=None\n\n\n')
1625
1626 def testNotInline(self):
1627 """Reject not at the start of line."""
1628 self.assertMessageRejected('\n Change-Id: I1234\n')
1629 self.assertMessageRejected('\n\tChange-Id: I1234\n')
1630
1631 def testMissing(self):
1632 """Reject commit messages missing the line."""
1633 self.assertMessageRejected('foo\n')
1634
1635 def testCase(self):
1636 """Reject bug lines that are not Change-Id."""
1637 self.assertMessageRejected('\nchange-id: I1234\n')
1638 self.assertMessageRejected('\nChange-id: I1234\n')
1639 self.assertMessageRejected('\nChange-ID: I1234\n')
1640
1641 def testEnd(self):
1642 """Reject Change-Id's that are not last."""
1643 self.assertMessageRejected('\nChange-Id: I1234\nbar\n')
1644
Mike Frysinger02b88bd2014-11-21 00:29:38 -05001645 def testSobTag(self):
1646 """Permit s-o-b tags to follow the Change-Id."""
1647 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nSigned-off-by: Hi\n')
1648
LaMont Jones237f3ef2020-01-22 10:40:52 -07001649 def testCqClTag(self):
1650 """Permit Cq-Cl-Tag tags to follow the Change-Id."""
1651 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nCq-Cl-Tag: Hi\n')
1652
LaMont Jonesfb5e8bf2020-03-03 12:50:06 -07001653 def testCqIncludeTrybotsTag(self):
1654 """Permit Cq-Include-Trybots tags to follow the Change-Id."""
1655 self.assertMessageAccepted(
1656 'foo\n\nChange-Id: I1234\nCq-Include-Trybots: chromeos/cq:foo\n')
1657
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001658
Jack Neus8edbf642019-07-10 16:08:31 -06001659class CheckCommitMessageNoOEM(CommitMessageTestCase):
1660 """Tests for _check_change_no_include_oem."""
1661
1662 @staticmethod
1663 def CheckMessage(project, commit):
1664 return pre_upload._check_change_no_include_oem(project, commit)
1665
1666 def testNormal(self):
1667 """Accept a commit message w/o reference to an OEM/ODM."""
1668 self.assertMessageAccepted('foo\n')
1669
1670 def testHasOEM(self):
1671 """Catch commit messages referencing OEMs."""
1672 self.assertMessageRejected('HP Project\n\n')
1673 self.assertMessageRejected('hewlett-packard\n')
1674 self.assertMessageRejected('Hewlett\nPackard\n')
1675 self.assertMessageRejected('Dell Chromebook\n\n\n')
1676 self.assertMessageRejected('product@acer.com\n')
1677 self.assertMessageRejected('This is related to Asus\n')
1678 self.assertMessageRejected('lenovo machine\n')
1679
1680 def testHasODM(self):
1681 """Catch commit messages referencing ODMs."""
1682 self.assertMessageRejected('new samsung laptop\n\n')
1683 self.assertMessageRejected('pegatron(ems) project\n')
1684 self.assertMessageRejected('new Wistron device\n')
1685
1686 def testContainsOEM(self):
1687 """Check that the check handles word boundaries properly."""
1688 self.assertMessageAccepted('oheahpohea')
1689 self.assertMessageAccepted('Play an Asus7 guitar chord.\n\n')
1690
1691 def testTag(self):
1692 """Check that the check ignores tags."""
1693 self.assertMessageAccepted(
1694 'Harmless project\n'
1695 'Reviewed-by: partner@asus.corp-partner.google.com\n'
1696 'Tested-by: partner@hp.corp-partner.google.com\n'
1697 'Signed-off-by: partner@dell.corp-partner.google.com\n'
1698 'Commit-Queue: partner@lenovo.corp-partner.google.com\n'
Jack Neus8edbf642019-07-10 16:08:31 -06001699 'CC: partner@acer.corp-partner.google.com\n'
1700 'Acked-by: partner@hewlett-packard.corp-partner.google.com\n')
1701 self.assertMessageRejected(
1702 'Asus project\n'
1703 'Reviewed-by: partner@asus.corp-partner.google.com')
Mike Frysingerbb34a222019-07-31 14:40:46 -04001704 self.assertMessageRejected(
1705 'my project\n'
1706 'Bad-tag: partner@asus.corp-partner.google.com')
Jack Neus8edbf642019-07-10 16:08:31 -06001707
1708
Mike Frysinger36b2ebc2014-10-31 14:02:03 -04001709class CheckCommitMessageStyle(CommitMessageTestCase):
1710 """Tests for _check_commit_message_style."""
1711
1712 @staticmethod
1713 def CheckMessage(project, commit):
1714 return pre_upload._check_commit_message_style(project, commit)
1715
1716 def testNormal(self):
1717 """Accept valid commit messages."""
1718 self.assertMessageAccepted('one sentence.\n')
1719 self.assertMessageAccepted('some.module: do it!\n')
1720 self.assertMessageAccepted('one line\n\nmore stuff here.')
1721
1722 def testNoBlankSecondLine(self):
1723 """Reject messages that have stuff on the second line."""
1724 self.assertMessageRejected('one sentence.\nbad fish!\n')
1725
1726 def testFirstLineMultipleSentences(self):
1727 """Reject messages that have more than one sentence in the summary."""
1728 self.assertMessageRejected('one sentence. two sentence!\n')
1729
1730 def testFirstLineTooLone(self):
1731 """Reject first lines that are too long."""
1732 self.assertMessageRejected('o' * 200)
1733
1734
Mike Frysinger292b45d2014-11-25 01:17:10 -05001735def DiffEntry(src_file=None, dst_file=None, src_mode=None, dst_mode='100644',
1736 status='M'):
1737 """Helper to create a stub RawDiffEntry object"""
1738 if src_mode is None:
1739 if status == 'A':
1740 src_mode = '000000'
1741 elif status == 'M':
1742 src_mode = dst_mode
1743 elif status == 'D':
1744 src_mode = dst_mode
1745 dst_mode = '000000'
1746
1747 src_sha = dst_sha = 'abc'
1748 if status == 'D':
1749 dst_sha = '000000'
1750 elif status == 'A':
1751 src_sha = '000000'
1752
1753 return git.RawDiffEntry(src_mode=src_mode, dst_mode=dst_mode, src_sha=src_sha,
1754 dst_sha=dst_sha, status=status, score=None,
1755 src_file=src_file, dst_file=dst_file)
1756
1757
Mike Frysingerb2496652019-09-12 23:35:46 -04001758class HelpersTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001759 """Various tests for utility functions."""
1760
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001761 def setUp(self):
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001762 os.chdir(self.tempdir)
1763
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001764 self.PatchObject(git, 'RawDiff', return_value=[
1765 # A modified normal file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001766 DiffEntry(src_file='buildbot/constants.py', status='M'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001767 # A new symlink file.
Bob Haarman0dc1f942020-10-03 00:06:59 +00001768 DiffEntry(dst_file='scripts/cros_env_allowlist', dst_mode='120000',
Mike Frysinger292b45d2014-11-25 01:17:10 -05001769 status='A'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001770 # A deleted file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001771 DiffEntry(src_file='scripts/sync_sonic.py', status='D'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001772 ])
1773
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001774 def _WritePresubmitIgnoreFile(self, subdir, data):
1775 """Writes to a .presubmitignore file in the passed-in subdirectory."""
1776 directory = os.path.join(self.tempdir, subdir)
1777 if not os.path.exists(directory):
1778 os.makedirs(directory)
1779 osutils.WriteFile(os.path.join(directory, pre_upload._IGNORE_FILE), data)
1780
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001781 def testGetAffectedFilesNoDeletesNoRelative(self):
1782 """Verify _get_affected_files() works w/no delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001783 path = os.getcwd()
1784 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1785 relative=False)
1786 exp_files = [os.path.join(path, 'buildbot/constants.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001787 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001788
1789 def testGetAffectedFilesDeletesNoRelative(self):
1790 """Verify _get_affected_files() works w/delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001791 path = os.getcwd()
1792 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1793 relative=False)
1794 exp_files = [os.path.join(path, 'buildbot/constants.py'),
1795 os.path.join(path, 'scripts/sync_sonic.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001796 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001797
1798 def testGetAffectedFilesNoDeletesRelative(self):
1799 """Verify _get_affected_files() works w/no delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001800 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1801 relative=True)
1802 exp_files = ['buildbot/constants.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001803 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001804
1805 def testGetAffectedFilesDeletesRelative(self):
1806 """Verify _get_affected_files() works w/delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001807 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1808 relative=True)
1809 exp_files = ['buildbot/constants.py', 'scripts/sync_sonic.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001810 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001811
Mike Frysinger292b45d2014-11-25 01:17:10 -05001812 def testGetAffectedFilesDetails(self):
1813 """Verify _get_affected_files() works w/full_details."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001814 files = pre_upload._get_affected_files('HEAD', full_details=True,
1815 relative=True)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001816 self.assertEqual(files[0].src_file, 'buildbot/constants.py')
Mike Frysinger292b45d2014-11-25 01:17:10 -05001817
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001818 def testGetAffectedFilesPresubmitIgnoreDirectory(self):
1819 """Verify .presubmitignore can be used to exclude a directory."""
1820 self._WritePresubmitIgnoreFile('.', 'buildbot/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001821 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001822
1823 def testGetAffectedFilesPresubmitIgnoreDirectoryWildcard(self):
1824 """Verify .presubmitignore can be used with a directory wildcard."""
1825 self._WritePresubmitIgnoreFile('.', '*/constants.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001826 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001827
1828 def testGetAffectedFilesPresubmitIgnoreWithinDirectory(self):
1829 """Verify .presubmitignore can be placed in a subdirectory."""
1830 self._WritePresubmitIgnoreFile('buildbot', '*.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001831 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001832
1833 def testGetAffectedFilesPresubmitIgnoreDoesntMatch(self):
1834 """Verify .presubmitignore has no effect when it doesn't match a file."""
1835 self._WritePresubmitIgnoreFile('buildbot', '*.txt')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001836 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True),
1837 ['buildbot/constants.py'])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001838
1839 def testGetAffectedFilesPresubmitIgnoreAddedFile(self):
1840 """Verify .presubmitignore matches added files."""
1841 self._WritePresubmitIgnoreFile('.', 'buildbot/\nscripts/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001842 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True,
1843 include_symlinks=True),
1844 [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001845
1846 def testGetAffectedFilesPresubmitIgnoreSkipIgnoreFile(self):
1847 """Verify .presubmitignore files are automatically skipped."""
1848 self.PatchObject(git, 'RawDiff', return_value=[
1849 DiffEntry(src_file='.presubmitignore', status='M')
1850 ])
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001851 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Mike Frysinger292b45d2014-11-25 01:17:10 -05001852
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001853
Mike Frysingerb2496652019-09-12 23:35:46 -04001854class ExecFilesTest(PreUploadTestCase):
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001855 """Tests for _check_exec_files."""
1856
1857 def setUp(self):
1858 self.diff_mock = self.PatchObject(git, 'RawDiff')
1859
1860 def testNotExec(self):
1861 """Do not flag files that are not executable."""
1862 self.diff_mock.return_value = [
1863 DiffEntry(src_file='make.conf', dst_mode='100644', status='A'),
1864 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001865 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001866
1867 def testExec(self):
1868 """Flag files that are executable."""
1869 self.diff_mock.return_value = [
1870 DiffEntry(src_file='make.conf', dst_mode='100755', status='A'),
1871 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001872 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001873
1874 def testDeletedExec(self):
1875 """Ignore bad files that are being deleted."""
1876 self.diff_mock.return_value = [
1877 DiffEntry(src_file='make.conf', dst_mode='100755', status='D'),
1878 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001879 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001880
1881 def testModifiedExec(self):
1882 """Flag bad files that weren't exec, but now are."""
1883 self.diff_mock.return_value = [
1884 DiffEntry(src_file='make.conf', src_mode='100644', dst_mode='100755',
1885 status='M'),
1886 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001887 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001888
1889 def testNormalExec(self):
1890 """Don't flag normal files (e.g. scripts) that are executable."""
1891 self.diff_mock.return_value = [
1892 DiffEntry(src_file='foo.sh', dst_mode='100755', status='A'),
1893 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001894 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001895
1896
Mike Frysingerb2496652019-09-12 23:35:46 -04001897class CheckForUprev(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger292b45d2014-11-25 01:17:10 -05001898 """Tests for _check_for_uprev."""
1899
1900 def setUp(self):
1901 self.file_mock = self.PatchObject(git, 'RawDiff')
1902
1903 def _Files(self, files):
1904 """Create |files| in the tempdir and return full paths to them."""
1905 for obj in files:
1906 if obj.status == 'D':
1907 continue
1908 if obj.dst_file is None:
1909 f = obj.src_file
1910 else:
1911 f = obj.dst_file
1912 osutils.Touch(os.path.join(self.tempdir, f), makedirs=True)
1913 return files
1914
1915 def assertAccepted(self, files, project='project', commit='fake sha1'):
1916 """Assert _check_for_uprev accepts |files|."""
1917 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001918 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1919 project_top=self.tempdir)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001920 self.assertIsNone(ret)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001921
1922 def assertRejected(self, files, project='project', commit='fake sha1'):
1923 """Assert _check_for_uprev rejects |files|."""
1924 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001925 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1926 project_top=self.tempdir)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001927 self.assertTrue(isinstance(ret, errors.HookFailure))
1928
Bob Haarman0dc1f942020-10-03 00:06:59 +00001929 def testAllowlistOverlay(self):
1930 """Skip checks on allowed overlays."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001931 self.assertAccepted([DiffEntry(src_file='cat/pkg/pkg-0.ebuild')],
1932 project='chromiumos/overlays/portage-stable')
1933
Bob Haarman0dc1f942020-10-03 00:06:59 +00001934 def testAllowlistFiles(self):
1935 """Skip checks on allowed files."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001936 files = ['ChangeLog', 'Manifest', 'metadata.xml']
1937 self.assertAccepted([DiffEntry(src_file=os.path.join('c', 'p', x),
1938 status='M')
1939 for x in files])
1940
1941 def testRejectBasic(self):
1942 """Reject ebuilds missing uprevs."""
1943 self.assertRejected([DiffEntry(src_file='c/p/p-0.ebuild', status='M')])
1944
1945 def testNewPackage(self):
1946 """Accept new ebuilds w/out uprevs."""
1947 self.assertAccepted([DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1948 self.assertAccepted([DiffEntry(src_file='c/p/p-0-r12.ebuild', status='A')])
1949
1950 def testModifiedFilesOnly(self):
1951 """Reject ebuilds w/out uprevs and changes in files/."""
1952 osutils.Touch(os.path.join(self.tempdir, 'cat/pkg/pkg-0.ebuild'),
1953 makedirs=True)
1954 self.assertRejected([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1955 self.assertRejected([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1956
1957 def testFilesNoEbuilds(self):
1958 """Ignore changes to paths w/out ebuilds."""
1959 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1960 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1961
1962 def testModifiedFilesWithUprev(self):
1963 """Accept ebuilds w/uprevs and changes in files/."""
1964 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='A'),
1965 DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1966 self.assertAccepted([
1967 DiffEntry(src_file='c/p/files/f', status='M'),
1968 DiffEntry(src_file='c/p/p-0-r1.ebuild', src_mode='120000',
1969 dst_file='c/p/p-0-r2.ebuild', dst_mode='120000', status='R')])
1970
Gwendal Grignoua3086c32014-12-09 11:17:22 -08001971 def testModifiedFilesWith9999(self):
1972 """Accept 9999 ebuilds and changes in files/."""
1973 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M'),
1974 DiffEntry(src_file='c/p/p-9999.ebuild', status='M')])
1975
C Shapiroae157ae2017-09-18 16:24:03 -06001976 def testModifiedFilesIn9999SubDirWithout9999Change(self):
1977 """Accept changes in files/ with a parent 9999 ebuild"""
1978 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
1979 os.makedirs(os.path.dirname(ebuild_9999_file))
1980 osutils.WriteFile(ebuild_9999_file, 'fake')
1981 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M')])
1982
Stephen Boyd6bf5ea82020-10-15 00:02:07 -07001983 def testModifiedFilesAndProfilesWith9999(self):
1984 """Accept changes in files/ with a parent 9999 ebuild and profile change"""
1985 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
1986 os.makedirs(os.path.dirname(ebuild_9999_file))
1987 osutils.WriteFile(ebuild_9999_file, 'fake')
1988 self.assertAccepted([
1989 DiffEntry(src_file='c/p/files/f', status='M'),
1990 DiffEntry(src_file='c/profiles/base/make.defaults', status='M')])
1991
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001992
Mike Frysingerb2496652019-09-12 23:35:46 -04001993class DirectMainTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger55f85b52014-12-18 14:45:21 -05001994 """Tests for direct_main()"""
1995
1996 def setUp(self):
1997 self.hooks_mock = self.PatchObject(pre_upload, '_run_project_hooks',
1998 return_value=None)
1999
2000 def testNoArgs(self):
2001 """If run w/no args, should check the current dir."""
2002 ret = pre_upload.direct_main([])
2003 self.assertEqual(ret, 0)
2004 self.hooks_mock.assert_called_once_with(
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07002005 mock.ANY, proj_dir=os.getcwd(), commit_list=[], presubmit=mock.ANY,
2006 config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05002007
2008 def testExplicitDir(self):
2009 """Verify we can run on a diff dir."""
2010 # Use the chromite dir since we know it exists.
2011 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR])
2012 self.assertEqual(ret, 0)
2013 self.hooks_mock.assert_called_once_with(
2014 mock.ANY, proj_dir=constants.CHROMITE_DIR, commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07002015 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05002016
2017 def testBogusProject(self):
2018 """A bogus project name should be fine (use default settings)."""
2019 # Use the chromite dir since we know it exists.
2020 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR,
2021 '--project', 'foooooooooo'])
2022 self.assertEqual(ret, 0)
2023 self.hooks_mock.assert_called_once_with(
2024 'foooooooooo', proj_dir=constants.CHROMITE_DIR, commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07002025 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05002026
2027 def testBogustProjectNoDir(self):
2028 """Make sure --dir is detected even with --project."""
2029 ret = pre_upload.direct_main(['--project', 'foooooooooo'])
2030 self.assertEqual(ret, 0)
2031 self.hooks_mock.assert_called_once_with(
Mike Frysinger9ba001a2020-11-20 01:02:11 -05002032 'foooooooooo', proj_dir=os.path.dirname(os.path.abspath(__file__)),
2033 commit_list=[], presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05002034
2035 def testNoGitDir(self):
2036 """We should die when run on a non-git dir."""
2037 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
2038 ['--dir', self.tempdir])
2039
2040 def testNoDir(self):
2041 """We should die when run on a missing dir."""
2042 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
2043 ['--dir', os.path.join(self.tempdir, 'foooooooo')])
2044
2045 def testCommitList(self):
2046 """Any args on the command line should be treated as commits."""
2047 commits = ['sha1', 'sha2', 'shaaaaaaaaaaaan']
2048 ret = pre_upload.direct_main(commits)
2049 self.assertEqual(ret, 0)
2050 self.hooks_mock.assert_called_once_with(
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07002051 mock.ANY, proj_dir=mock.ANY, commit_list=commits, presubmit=mock.ANY,
2052 config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05002053
2054
Mike Frysingerb2496652019-09-12 23:35:46 -04002055class CheckRustfmtTest(PreUploadTestCase):
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06002056 """Tests for _check_rustfmt."""
2057
2058 def setUp(self):
2059 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
2060
2061 def testBadRustFile(self):
2062 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
2063 # Bad because it's missing trailing newline.
Mike Frysingere85b0062019-08-20 15:10:33 -04002064 content = 'fn main() {}'
2065 self.content_mock.return_value = content
2066 self.PatchObject(pre_upload, '_run_command', return_value=content + '\n')
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06002067 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
2068 self.assertIsNotNone(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04002069 self.assertEqual('Files not formatted with rustfmt: '
2070 "(run 'cargo fmt' to fix)",
2071 failure.msg)
2072 self.assertEqual(['a.rs'], failure.items)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06002073
2074 def testGoodRustFile(self):
2075 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
Mike Frysingere85b0062019-08-20 15:10:33 -04002076 content = 'fn main() {}\n'
2077 self.content_mock.return_value = content
2078 self.PatchObject(pre_upload, '_run_command', return_value=content)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06002079 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
2080 self.assertIsNone(failure)
2081
2082 def testFilterNonRustFiles(self):
2083 self.PatchObject(pre_upload, '_get_affected_files',
2084 return_value=['a.cc', 'a.rsa', 'a.irs', 'rs.cc'])
2085 self.content_mock.return_value = 'fn main() {\n}'
2086 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
2087 self.assertIsNone(failure)
2088
2089
Keiichi Watanabe6ab73fd2020-07-14 23:42:02 +09002090class GetCargoClippyParserTest(cros_test_lib.TestCase):
2091 """Tests for _get_cargo_clippy_parser."""
2092
2093 def testSingleProject(self):
2094 parser = pre_upload._get_cargo_clippy_parser()
2095 args = parser.parse_args(['--project', 'foo'])
2096 self.assertEqual(args.project,
2097 [pre_upload.ClippyProject(root='foo', script=None)])
2098
2099 def testMultipleProjects(self):
2100 parser = pre_upload._get_cargo_clippy_parser()
2101 args = parser.parse_args(['--project', 'foo:bar',
2102 '--project', 'baz'])
2103 self.assertCountEqual(args.project,
2104 [pre_upload.ClippyProject(root='foo', script='bar'),
2105 pre_upload.ClippyProject(root='baz', script=None)])
2106
2107
2108class CheckCargoClippyTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
2109 """Tests for _check_cargo_clippy."""
2110
2111 def setUp(self):
2112 self.project = pre_upload.Project(name='PROJECT', dir=self.tempdir,
2113 remote=None)
2114
2115 def testClippy(self):
2116 """Verify clippy is called when a monitored file was changed."""
2117 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
2118
2119 self.PatchObject(pre_upload, '_get_affected_files',
2120 return_value=[f'{self.project.dir}/repo_a/a.rs'])
2121
2122 ret = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
2123 options=['--project=repo_a',
2124 '--project=repo_b:foo'])
2125 self.assertFalse(ret)
2126
2127 # Check if `cargo clippy` ran.
2128 called = False
2129 for args, _ in rc_mock.call_args_list:
2130 cmd = args[0]
2131 if len(cmd) > 1 and cmd[0] == 'cargo' and cmd[1] == 'clippy':
2132 called = True
2133 break
2134
2135 self.assertTrue(called)
2136
2137 def testDontRun(self):
2138 """Skip checks when no monitored files are modified."""
2139 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
2140
2141 # A file under `repo_a` was monitored.
2142 self.PatchObject(pre_upload, '_get_affected_files',
2143 return_value=[f'{self.project.dir}/repo_a/a.rs'])
2144 # But, we only care about files under `repo_b`.
2145 errs = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
2146 options=['--project=repo_b:foo'])
2147
2148 self.assertFalse(errs)
2149
2150 rc_mock.assert_not_called()
2151
2152 def testCustomScript(self):
2153 """Verify project-specific script is used."""
2154 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
2155
2156 self.PatchObject(pre_upload, '_get_affected_files',
2157 return_value=[f'{self.project.dir}/repo_b/b.rs'])
2158
2159 errs = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
2160 options=['--project=repo_a',
2161 '--project=repo_b:foo'])
2162 self.assertFalse(errs)
2163
2164 # Check if the script `foo` ran.
2165 called = False
2166 for args, _ in rc_mock.call_args_list:
2167 cmd = args[0]
2168 if len(cmd) > 0 and cmd[0] == os.path.join(self.project.dir, 'foo'):
2169 called = True
2170 break
2171
2172 self.assertTrue(called)
2173
2174
Mike Frysinger180ecd62020-08-19 00:41:51 -04002175class OverrideHooksProcessing(PreUploadTestCase):
2176 """Verify _get_override_hooks processing."""
2177
2178 @staticmethod
2179 def parse(data):
2180 """Helper to create a config & parse it."""
2181 cfg = configparser.ConfigParser()
2182 cfg.read_string(data)
2183 return pre_upload._get_override_hooks(cfg)
2184
2185 def testHooks(self):
2186 """Verify we reject unknown hook names (e.g. typos)."""
2187 with self.assertRaises(ValueError) as e:
2188 self.parse("""
2189[Hook Overrides]
2190foobar: true
2191""")
2192 self.assertIn('foobar', str(e.exception))
2193
2194 def testImplicitDisable(self):
2195 """Verify non-common hooks aren't enabled by default."""
2196 enabled, _ = self.parse('')
2197 self.assertNotIn(pre_upload._run_checkpatch, enabled)
2198
2199 def testExplicitDisable(self):
2200 """Verify hooks disabled are disabled."""
2201 _, disabled = self.parse("""
2202[Hook Overrides]
2203tab_check: false
2204""")
2205 self.assertIn(pre_upload._check_no_tabs, disabled)
2206
2207 def testExplicitEnable(self):
2208 """Verify hooks enabled are enabled."""
2209 enabled, _ = self.parse("""
2210[Hook Overrides]
2211tab_check: true
2212""")
2213 self.assertIn(pre_upload._check_no_tabs, enabled)
2214
2215 def testOptions(self):
2216 """Verify hook options are loaded."""
2217 enabled, _ = self.parse("""
2218[Hook Overrides Options]
2219keyword_check: --kw
2220""")
2221 for func in enabled:
2222 if func.__name__ == 'keyword_check':
2223 self.assertIn('options', func.keywords)
2224 self.assertEqual(func.keywords['options'], ['--kw'])
2225 break
2226 else:
2227 self.fail('could not find "keyword_check" enabled hook')
2228
2229 def testSignOffField(self):
2230 """Verify signoff field handling."""
2231 # Enforce no s-o-b by default.
2232 enabled, disabled = self.parse('')
2233 self.assertIn(pre_upload._check_change_has_no_signoff_field, enabled)
2234 self.assertNotIn(pre_upload._check_change_has_signoff_field, enabled)
2235 self.assertNotIn(pre_upload._check_change_has_signoff_field, disabled)
2236
2237 # If disabled, don't enforce either policy.
2238 enabled, disabled = self.parse("""
2239[Hook Overrides]
2240signoff_check: false
2241""")
2242 self.assertNotIn(pre_upload._check_change_has_no_signoff_field, enabled)
2243 self.assertNotIn(pre_upload._check_change_has_signoff_field, enabled)
2244 self.assertIn(pre_upload._check_change_has_signoff_field, disabled)
2245
2246 # If enabled, require s-o-b.
2247 enabled, disabled = self.parse("""
2248[Hook Overrides]
2249signoff_check: true
2250""")
2251 self.assertNotIn(pre_upload._check_change_has_no_signoff_field, enabled)
2252 self.assertIn(pre_upload._check_change_has_signoff_field, enabled)
2253 self.assertNotIn(pre_upload._check_change_has_signoff_field, disabled)
2254
2255 def testBranchField(self):
2256 """Verify branch field enabling."""
2257 # Should be disabled by default.
2258 enabled, disabled = self.parse('')
2259 self.assertIn(pre_upload._check_change_has_no_branch_field, enabled)
2260 self.assertNotIn(pre_upload._check_change_has_branch_field, enabled)
2261 self.assertNotIn(pre_upload._check_change_has_branch_field, disabled)
2262
2263 # Should be disabled if requested.
2264 enabled, disabled = self.parse("""
2265[Hook Overrides]
2266branch_check: false
2267""")
2268 self.assertIn(pre_upload._check_change_has_no_branch_field, enabled)
2269 self.assertNotIn(pre_upload._check_change_has_branch_field, enabled)
2270 self.assertIn(pre_upload._check_change_has_branch_field, disabled)
2271
2272 # Should be enabled if requested.
2273 enabled, disabled = self.parse("""
2274[Hook Overrides]
2275branch_check: true
2276""")
2277 self.assertNotIn(pre_upload._check_change_has_no_branch_field, enabled)
2278 self.assertIn(pre_upload._check_change_has_branch_field, enabled)
2279 self.assertNotIn(pre_upload._check_change_has_branch_field, disabled)
2280
2281
Tom Hughes1ed799d2020-09-25 14:37:28 -07002282class ProjectHooksProcessing(PreUploadTestCase, cros_test_lib.TempDirTestCase):
2283 """Verify _get_project_hooks processing."""
2284
2285 def parse(self, data):
2286 """Helper to write config and parse it."""
2287 filename = os.path.join(self.tempdir, 'config')
2288 osutils.WriteFile(filename, data)
Mike Frysingerff916c62020-12-18 01:58:08 -05002289 return pre_upload._get_project_hooks(presubmit=True, config_file=filename)
Tom Hughes1ed799d2020-09-25 14:37:28 -07002290
2291 def testClangFormatCheckDefault(self):
2292 """Verify clang-format check disabled by default."""
2293 hooks = self.parse('')
2294 for func in hooks:
2295 self.assertNotEqual(func.__name__, '_check_clang_format')
2296 self.assertNotEqual(func.__name__, 'clang_format_check')
2297
2298 def testClangFormatCheckDisabled(self):
2299 """Verify clang-format check disabled when requested."""
2300 hooks = self.parse("""
2301[Hook Overrides]
2302clang_format_check: false
2303""")
2304 for func in hooks:
2305 self.assertNotEqual(func.__name__, '_check_clang_format')
2306 self.assertNotEqual(func.__name__, 'clang_format_check')
2307
2308 def testClangFormatCheckEnabled(self):
2309 """Verify clang-format check enabled when requested."""
2310 hooks = self.parse("""
2311[Hook Overrides]
2312clang_format_check: true
2313""")
2314 for func in hooks:
2315 if func.__name__ == '_check_clang_format':
2316 self.assertFalse(hasattr(func, 'keywords'))
2317 break
2318 else:
2319 self.fail('could not find "_check_clang_format" enabled hook')
2320
2321 def testClangFormatCheckEnabledWithOptions(self):
2322 """Verify clang-format check has options when provided."""
2323 hooks = self.parse("""
2324[Hook Overrides]
2325clang_format_check: true
2326
2327[Hook Overrides Options]
2328clang_format_check:
2329 some_dir/
2330""")
2331 for func in hooks:
2332 if func.__name__ == 'clang_format_check':
2333 self.assertIn('options', func.keywords)
2334 self.assertEqual(func.keywords['options'], ['some_dir/'])
2335 break
2336 else:
2337 self.fail('could not find "clang_format_check" enabled hook')
2338
2339
Jon Salz98255932012-08-18 14:48:02 +08002340if __name__ == '__main__':
Mike Frysinger884a8dd2015-05-17 03:43:43 -04002341 cros_test_lib.main(module=__name__)