blob: 1f8e6d73e11b5261807afd2de40f3138732af98d [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 Nojiri062a0a92021-02-14 15:38:16 -0800102 # First call reads blocked_terms.txt, second call reads unblocked_terms.txt
103 # for _get_file_diff, and third call reads unblocked_terms.txt for
104 # _get_commit_desc.
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700105 self.rf_mock = self.PatchObject(
106 osutils, 'ReadFile',
Daisuke Nojiri062a0a92021-02-14 15:38:16 -0800107 side_effect=['scruffy\nmangy\ndog.?pile\ncat.?circle', 'fox', 'fox'])
Bernie Thompson8e26f742020-07-23 14:32:31 -0700108 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
109 self.desc_mock = self.PatchObject(pre_upload, '_get_commit_desc')
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700110 self.project = pre_upload.Project(name='PROJECT', dir=self.tempdir,
111 remote=None)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700112
113 def test_good_cases(self):
114 self.desc_mock.return_value = 'Commit Message.\nLine 2'
115 self.diff_mock.return_value = [
116 (1, 'Some text without keywords.'),
117 (2, 'The dog is black has a partial keyword that does not count.'),
118 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700119 failures = pre_upload._check_keywords(self.project, 'COMMIT')
Bernie Thompson8e26f742020-07-23 14:32:31 -0700120 self.assertEqual(failures, [])
121
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700122 self.rf_mock.assert_has_calls([
123 mock.call(os.path.join(pre_upload._get_hooks_dir(),
124 pre_upload.BLOCKED_TERMS_FILE)),
Daisuke Nojiri062a0a92021-02-14 15:38:16 -0800125 mock.call(pre_upload.UNBLOCKED_TERMS_FILE),
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700126 mock.call(os.path.join(pre_upload._get_hooks_dir(),
127 pre_upload.UNBLOCKED_TERMS_FILE)),
128 ])
129
Bernie Thompson8e26f742020-07-23 14:32:31 -0700130 def test_bad_cases(self):
131 self.desc_mock.return_value = 'Commit Message.\nLine 2\nLine 3 scruffy'
132 self.diff_mock.return_value = [
133 (1, 'Scruffy plain catch'),
134 (2, 'dog-pile hyphenated catch'),
135 (3, 'cat_circle underscored catch'),
136 (4, 'dog pile space catch'),
137 (5, 'dogpiled substring catch'),
138 (6, 'scruffy mangy dog, multiple in a line catch'),
139 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700140 failures = pre_upload._check_keywords(self.project, 'COMMIT')
Bernie Thompson8e26f742020-07-23 14:32:31 -0700141 self.assertNotEqual(failures, [])
142 self.assertEqual('Found a blocked keyword in:', failures[0].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700143 self.assertEqual(
144 ['x.ebuild, line 1: Matched "Scruffy" with regex of "scruffy"',
145 'x.ebuild, line 2: Matched "dog-pile" with regex of "dog.?pile"',
146 'x.ebuild, line 3: Matched "cat_circle" with regex of "cat.?circle"',
147 'x.ebuild, line 4: Matched "dog pile" with regex of "dog.?pile"',
148 'x.ebuild, line 5: Matched "dogpile" with regex of "dog.?pile"',
149 'x.ebuild, line 6: Matched "mangy" with regex of "mangy"'],
150 failures[0].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700151 self.assertEqual('Found a blocked keyword in:', failures[1].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700152 self.assertEqual(
153 ['Commit message, line 3: Matched "scruffy" with regex of "scruffy"'],
154 failures[1].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700155
156 def test_block_option_cases(self):
157 self.desc_mock.return_value = 'Commit Message.\nLine 2 voldemort'
158 self.diff_mock.return_value = [
159 (1, 'Line with a new term voldemort.'),
160 (2, 'Line with only they who shall not be named.'),
161 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700162 failures = pre_upload._check_keywords(self.project,
Bernie Thompson8e26f742020-07-23 14:32:31 -0700163 'COMMIT', ['--block', 'voldemort'])
164 self.assertNotEqual(failures, [])
165 self.assertEqual('Found a blocked keyword in:', failures[0].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700166 self.assertEqual(
167 ['x.ebuild, line 1: Matched "voldemort" with regex of "voldemort"'],
168 failures[0].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700169 self.assertEqual('Found a blocked keyword in:', failures[1].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700170 self.assertEqual(
171 ['Commit message, line 2: '
172 'Matched "voldemort" with regex of "voldemort"'], failures[1].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700173
174 def test_unblock_option_cases(self):
175 self.desc_mock.return_value = 'Commit message with scruffy'
176 self.diff_mock.return_value = [
Bernie Thompson4e362922020-09-02 16:17:50 -0700177 (1, 'Line with two unblocked terms scruffy big dog-pile'),
178 (2, 'Line with without any blocked terms'),
179 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700180 # scruffy matches regex of 'scruffy' in block list but excluded by
181 # different regex of 'scru.?fy' in unblock list.
182 failures = pre_upload._check_keywords(self.project,
Bernie Thompson4e362922020-09-02 16:17:50 -0700183 'COMMIT', ['--unblock', 'dog.?pile',
184 '--unblock', 'scru.?fy'])
185 self.assertEqual(failures, [])
Bernie Thompson8e26f742020-07-23 14:32:31 -0700186
Laurent Chavey434af9a2020-09-28 22:25:16 +0900187 def test_unblock_and_block_option_cases(self):
188 self.desc_mock.return_value = 'Commit message with scruffy'
189 self.diff_mock.return_value = [
190 (1, 'Two unblocked terms scruffy and dog-pile'),
191 (2, 'Without any blocked terms'),
192 (3, 'Blocked dogpile'),
193 (4, 'Unblocked m.dogpile'),
194 (5, 'Blocked dogpile and unblocked m.dogpile'),
195 (6, 'Unlocked m.dogpile and blocked dogpile'),
196 (7, 'Unlocked m.dogpile and unblocked dog-pile'),
197 ]
198 # scruffy matches regex of 'scruffy' in block list but excluded by
199 # a different regex of 'scru.?fy' in unblock list.
200 # dogpile, dog.pile matches regex of 'dog.?pile' in block list.
201 # m.dogpile and dog-pile matches regex of 'dog.?pile' in block list but
202 # excluded by different regex '\.dog.?pile' and 'dog-pile' in unblock list.
203 failures = pre_upload._check_keywords(self.project,
204 'COMMIT',
205 ['--unblock', r'dog-pile',
206 '--unblock', r'scru.?fy',
207 '--unblock', r'\.dog.?pile'])
208 self.assertNotEqual(failures, [])
209 self.assertEqual('Found a blocked keyword in:', failures[0].msg)
210 self.assertEqual(
211 [r'x.ebuild, line 3: Matched "dogpile" with regex of "dog.?pile"',
212 r'x.ebuild, line 5: Matched "dogpile" with regex of "dog.?pile"',
213 r'x.ebuild, line 6: Matched "dogpile" with regex of "dog.?pile"'],
214 failures[0].items)
215
Bernie Thompson8e26f742020-07-23 14:32:31 -0700216
Mike Frysingerb2496652019-09-12 23:35:46 -0400217class CheckNoLongLinesTest(PreUploadTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -0500218 """Tests for _check_no_long_lines."""
219
Jon Salz98255932012-08-18 14:48:02 +0800220 def setUp(self):
Mike Frysinger1459d362014-12-06 13:53:23 -0500221 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
Jon Salz98255932012-08-18 14:48:02 +0800222
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900223 def testCheck(self):
Mike Frysingerf8961942020-05-15 00:36:31 -0400224 path = 'x.cc'
225 self.PatchObject(pre_upload, '_get_affected_files', return_value=[path])
Mike Frysinger1459d362014-12-06 13:53:23 -0500226 self.diff_mock.return_value = [
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400227 (1, u'x' * 80), # OK
228 (2, '\x80' * 80), # OK
229 (3, u'x' * 81), # Too long
230 (4, '\x80' * 81), # Too long
231 (5, u'See http://' + (u'x' * 80)), # OK (URL)
232 (6, u'See https://' + (u'x' * 80)), # OK (URL)
233 (7, u'# define ' + (u'x' * 80)), # OK (compiler directive)
234 (8, u'#define' + (u'x' * 74)), # Too long
Mike Frysinger1459d362014-12-06 13:53:23 -0500235 ]
Alex Deymo643ac4c2015-09-03 10:40:50 -0700236 failure = pre_upload._check_no_long_lines(ProjectNamed('PROJECT'), 'COMMIT')
Jon Salz98255932012-08-18 14:48:02 +0800237 self.assertTrue(failure)
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900238 self.assertEqual('Found lines longer than the limit (first 5 shown):',
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400239 failure.msg)
Mike Frysingerf8961942020-05-15 00:36:31 -0400240 self.assertEqual([path + ', line %d, 81 chars, over 80 chars' %
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900241 x for x in [3, 4, 8]],
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400242 failure.items)
Jon Salz98255932012-08-18 14:48:02 +0800243
George Burgess IVf9f79eb2019-07-09 20:12:55 -0700244 def testCheckTreatsOwnersFilesSpecially(self):
245 affected_files = self.PatchObject(pre_upload, '_get_affected_files')
246
247 mock_files = (
248 ('foo-OWNERS', False),
249 ('OWNERS', True),
250 ('/path/to/OWNERS', True),
251 ('/path/to/OWNERS.foo', True),
252 )
253
254 mock_lines = (
255 (u'x' * 81, False),
256 (u'foo file:' + u'x' * 80, True),
257 (u'include ' + u'x' * 80, True),
258 )
259 assert all(len(line) > 80 for line, _ in mock_lines)
260
261 for file_name, is_owners in mock_files:
262 affected_files.return_value = [file_name]
263 for line, is_ok in mock_lines:
264 self.diff_mock.return_value = [(1, line)]
265 failure = pre_upload._check_no_long_lines(ProjectNamed('PROJECT'),
266 'COMMIT')
267
268 assert_msg = 'file: %r; line: %r' % (file_name, line)
269 if is_owners and is_ok:
270 self.assertFalse(failure, assert_msg)
271 else:
272 self.assertTrue(failure, assert_msg)
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900273 self.assertIn('Found lines longer than the limit', failure.msg,
George Burgess IVf9f79eb2019-07-09 20:12:55 -0700274 assert_msg)
275
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900276 def testIncludeOptions(self):
277 self.PatchObject(pre_upload,
278 '_get_affected_files',
279 return_value=['foo.txt'])
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400280 self.diff_mock.return_value = [(1, u'x' * 81)]
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900281 self.assertFalse(pre_upload._check_no_long_lines(
282 ProjectNamed('PROJECT'), 'COMMIT'))
283 self.assertTrue(pre_upload._check_no_long_lines(
284 ProjectNamed('PROJECT'), 'COMMIT', options=['--include_regex=foo']))
285
286 def testExcludeOptions(self):
287 self.PatchObject(pre_upload,
288 '_get_affected_files',
Mike Frysingerf8961942020-05-15 00:36:31 -0400289 return_value=['foo.cc'])
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400290 self.diff_mock.return_value = [(1, u'x' * 81)]
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900291 self.assertTrue(pre_upload._check_no_long_lines(
292 ProjectNamed('PROJECT'), 'COMMIT'))
293 self.assertFalse(pre_upload._check_no_long_lines(
294 ProjectNamed('PROJECT'), 'COMMIT', options=['--exclude_regex=foo']))
295
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900296 def testSpecialLineLength(self):
297 mock_lines = (
298 (u'x' * 101, True),
299 (u'x' * 100, False),
300 (u'x' * 81, False),
301 (u'x' * 80, False),
302 )
303 self.PatchObject(pre_upload,
304 '_get_affected_files',
305 return_value=['foo.java'])
306 for line, is_ok in mock_lines:
307 self.diff_mock.return_value = [(1, line)]
308 if is_ok:
309 self.assertTrue(pre_upload._check_no_long_lines(
310 ProjectNamed('PROJECT'), 'COMMIT'))
311 else:
312 self.assertFalse(pre_upload._check_no_long_lines(
313 ProjectNamed('PROJECT'), 'COMMIT'))
314
Mike Frysingerae409522014-02-01 03:16:11 -0500315
Mike Frysingerb2496652019-09-12 23:35:46 -0400316class CheckTabbedIndentsTest(PreUploadTestCase):
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800317 """Tests for _check_tabbed_indents."""
Mike Frysingerb2496652019-09-12 23:35:46 -0400318
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800319 def setUp(self):
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900320 self.PatchObject(pre_upload,
321 '_get_affected_files',
322 return_value=['x.ebuild'])
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800323 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
324
325 def test_good_cases(self):
326 self.diff_mock.return_value = [
327 (1, u'no_tabs_anywhere'),
328 (2, u' leading_tab_only'),
329 (3, u' leading_tab another_tab'),
330 (4, u' leading_tab trailing_too '),
331 (5, u' leading_tab then_spaces_trailing '),
332 ]
333 failure = pre_upload._check_tabbed_indents(ProjectNamed('PROJECT'),
334 'COMMIT')
335 self.assertIsNone(failure)
336
337 def test_bad_cases(self):
338 self.diff_mock.return_value = [
339 (1, u' leading_space'),
340 (2, u' tab_followed_by_space'),
341 (3, u' space_followed_by_tab'),
342 (4, u' mix_em_up'),
343 (5, u' mix_on_both_sides '),
344 ]
345 failure = pre_upload._check_tabbed_indents(ProjectNamed('PROJECT'),
346 'COMMIT')
347 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400348 self.assertEqual('Found a space in indentation (must be all tabs):',
349 failure.msg)
350 self.assertEqual(['x.ebuild, line %d' % x for x in range(1, 6)],
351 failure.items)
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800352
353
Mike Frysingerb2496652019-09-12 23:35:46 -0400354class CheckProjectPrefix(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Daniel Erata350fd32014-09-29 14:02:34 -0700355 """Tests for _check_project_prefix."""
356
357 def setUp(self):
Daniel Erata350fd32014-09-29 14:02:34 -0700358 os.chdir(self.tempdir)
359 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
360 self.desc_mock = self.PatchObject(pre_upload, '_get_commit_desc')
361
Daniel Erata350fd32014-09-29 14:02:34 -0700362 def _WriteAliasFile(self, filename, project):
363 """Writes a project name to a file, creating directories if needed."""
364 os.makedirs(os.path.dirname(filename))
365 osutils.WriteFile(filename, project)
366
367 def testInvalidPrefix(self):
368 """Report an error when the prefix doesn't match the base directory."""
369 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
370 self.desc_mock.return_value = 'bar: Some commit'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700371 failure = pre_upload._check_project_prefix(ProjectNamed('PROJECT'),
372 'COMMIT')
Daniel Erata350fd32014-09-29 14:02:34 -0700373 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400374 self.assertEqual('The commit title for changes affecting only foo should '
375 'start with "foo: "', failure.msg)
Daniel Erata350fd32014-09-29 14:02:34 -0700376
377 def testValidPrefix(self):
378 """Use a prefix that matches the base directory."""
379 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
380 self.desc_mock.return_value = 'foo: Change some files.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700381 self.assertFalse(
382 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700383
384 def testAliasFile(self):
385 """Use .project_alias to override the project name."""
386 self._WriteAliasFile('foo/.project_alias', 'project')
387 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/bar.cc']
388 self.desc_mock.return_value = 'project: Use an alias.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700389 self.assertFalse(
390 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700391
392 def testAliasFileWithSubdirs(self):
393 """Check that .project_alias is used when only modifying subdirectories."""
394 self._WriteAliasFile('foo/.project_alias', 'project')
395 self.file_mock.return_value = [
396 'foo/subdir/foo.cc',
397 'foo/subdir/bar.cc'
398 'foo/subdir/blah/baz.cc'
399 ]
400 self.desc_mock.return_value = 'project: Alias with subdirs.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700401 self.assertFalse(
402 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700403
404
Mike Frysingerb2496652019-09-12 23:35:46 -0400405class CheckFilePathCharTypeTest(PreUploadTestCase):
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900406 """Tests for _check_filepath_chartype."""
407
408 def setUp(self):
409 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
410
411 def testCheck(self):
412 self.PatchObject(pre_upload, '_get_affected_files', return_value=['x.cc'])
413 self.diff_mock.return_value = [
414 (1, 'base::FilePath'), # OK
415 (2, 'base::FilePath::CharType'), # NG
416 (3, 'base::FilePath::StringType'), # NG
417 (4, 'base::FilePath::StringPieceType'), # NG
Satoru Takabayashi4ca37922018-08-08 10:16:38 +0900418 (5, 'base::FilePath::FromUTF8Unsafe'), # NG
419 (6, 'FilePath::CharType'), # NG
420 (7, 'FilePath::StringType'), # NG
421 (8, 'FilePath::StringPieceType'), # NG
422 (9, 'FilePath::FromUTF8Unsafe'), # NG
423 (10, 'AsUTF8Unsafe'), # NG
424 (11, 'FILE_PATH_LITERAL'), # NG
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900425 ]
426 failure = pre_upload._check_filepath_chartype(ProjectNamed('PROJECT'),
427 'COMMIT')
428 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400429 self.assertEqual(
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900430 'Please assume FilePath::CharType is char (crbug.com/870621):',
431 failure.msg)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400432 self.assertEqual(['x.cc, line 2 has base::FilePath::CharType',
433 'x.cc, line 3 has base::FilePath::StringType',
434 'x.cc, line 4 has base::FilePath::StringPieceType',
435 'x.cc, line 5 has base::FilePath::FromUTF8Unsafe',
436 'x.cc, line 6 has FilePath::CharType',
437 'x.cc, line 7 has FilePath::StringType',
438 'x.cc, line 8 has FilePath::StringPieceType',
439 'x.cc, line 9 has FilePath::FromUTF8Unsafe',
440 'x.cc, line 10 has AsUTF8Unsafe',
441 'x.cc, line 11 has FILE_PATH_LITERAL'],
442 failure.items)
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900443
444
Mike Frysingerb2496652019-09-12 23:35:46 -0400445class CheckKernelConfig(PreUploadTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -0500446 """Tests for _kernel_configcheck."""
447
Mike Frysinger1459d362014-12-06 13:53:23 -0500448 def setUp(self):
449 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
450
451 def testMixedChanges(self):
452 """Mixing of changes should fail."""
453 self.file_mock.return_value = [
454 '/kernel/files/chromeos/config/base.config',
455 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
456 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700457 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
458 self.assertTrue(failure)
459
Mike Frysinger1459d362014-12-06 13:53:23 -0500460 def testCodeOnly(self):
461 """Code-only changes should pass."""
462 self.file_mock.return_value = [
463 '/kernel/files/Makefile',
464 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
465 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700466 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
467 self.assertFalse(failure)
468
Mike Frysinger1459d362014-12-06 13:53:23 -0500469 def testConfigOnlyChanges(self):
470 """Config-only changes should pass."""
471 self.file_mock.return_value = [
472 '/kernel/files/chromeos/config/base.config',
473 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700474 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
475 self.assertFalse(failure)
476
Jon Salz98255932012-08-18 14:48:02 +0800477
Mike Frysingerb2496652019-09-12 23:35:46 -0400478class CheckJson(PreUploadTestCase):
Mike Frysinger908be682018-01-04 02:21:50 -0500479 """Tests for _run_json_check."""
480
481 def setUp(self):
482 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
483 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
484
485 def testNoJson(self):
486 """Nothing should be checked w/no JSON files."""
487 self.file_mock.return_value = [
488 '/foo/bar.txt',
489 '/readme.md',
490 ]
491 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
492 self.assertIsNone(ret)
493
494 def testValidJson(self):
495 """We should accept valid json files."""
496 self.file_mock.return_value = [
497 '/foo/bar.txt',
498 '/data.json',
499 ]
500 self.content_mock.return_value = '{}'
501 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
502 self.assertIsNone(ret)
503 self.content_mock.assert_called_once_with('/data.json', 'COMMIT')
504
505 def testInvalidJson(self):
506 """We should reject invalid json files."""
507 self.file_mock.return_value = [
508 '/foo/bar.txt',
509 '/data.json',
510 ]
511 self.content_mock.return_value = '{'
512 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
513 self.assertIsNotNone(ret)
514 self.content_mock.assert_called_once_with('/data.json', 'COMMIT')
515
516
Mike Frysingerb2496652019-09-12 23:35:46 -0400517class CheckManifests(PreUploadTestCase):
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500518 """Tests _check_manifests."""
519
520 def setUp(self):
521 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
522 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
523
524 def testNoManifests(self):
525 """Nothing should be checked w/no Manifest files."""
526 self.file_mock.return_value = [
527 '/foo/bar.txt',
528 '/readme.md',
529 '/manifest',
530 '/Manifest.txt',
531 ]
532 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
533 self.assertIsNone(ret)
534
535 def testValidManifest(self):
536 """Accept valid Manifest files."""
537 self.file_mock.return_value = [
538 '/foo/bar.txt',
539 '/cat/pkg/Manifest',
540 ]
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400541 self.content_mock.return_value = """# Comment and blank lines.
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500542
543DIST lines
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400544"""
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500545 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
546 self.assertIsNone(ret)
547 self.content_mock.assert_called_once_with('/cat/pkg/Manifest', 'COMMIT')
548
549 def _testReject(self, content):
550 """Make sure |content| is rejected."""
551 self.file_mock.return_value = ('/Manifest',)
552 self.content_mock.reset_mock()
553 self.content_mock.return_value = content
554 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
555 self.assertIsNotNone(ret)
556 self.content_mock.assert_called_once_with('/Manifest', 'COMMIT')
557
558 def testRejectBlank(self):
559 """Reject empty manifests."""
560 self._testReject('')
561
562 def testNoTrailingNewLine(self):
563 """Reject manifests w/out trailing newline."""
564 self._testReject('DIST foo')
565
566 def testNoLeadingBlankLines(self):
567 """Reject manifests w/leading blank lines."""
568 self._testReject('\nDIST foo\n')
569
570 def testNoTrailingBlankLines(self):
571 """Reject manifests w/trailing blank lines."""
572 self._testReject('DIST foo\n\n')
573
574 def testNoLeadingWhitespace(self):
575 """Reject manifests w/lines w/leading spaces."""
576 self._testReject(' DIST foo\n')
577 self._testReject(' # Comment\n')
578
579 def testNoTrailingWhitespace(self):
580 """Reject manifests w/lines w/trailing spaces."""
581 self._testReject('DIST foo \n')
582 self._testReject('# Comment \n')
583 self._testReject(' \n')
584
585 def testOnlyDistLines(self):
586 """Only allow DIST lines in here."""
587 self._testReject('EBUILD foo\n')
588
589
Mike Frysingerb2496652019-09-12 23:35:46 -0400590class CheckPortageMakeUseVar(PreUploadTestCase):
Daniel Erat9d203ff2015-02-17 10:12:21 -0700591 """Tests for _check_portage_make_use_var."""
592
593 def setUp(self):
594 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
595 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
596
597 def testMakeConfOmitsOriginalUseValue(self):
598 """Fail for make.conf that discards the previous value of $USE."""
599 self.file_mock.return_value = ['make.conf']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400600 self.content_mock.return_value = u'USE="foo"\nUSE="${USE} bar"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700601 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
602 self.assertTrue(failure, failure)
603
604 def testMakeConfCorrectUsage(self):
605 """Succeed for make.conf that preserves the previous value of $USE."""
606 self.file_mock.return_value = ['make.conf']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400607 self.content_mock.return_value = u'USE="${USE} foo"\nUSE="${USE}" bar'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700608 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
609 self.assertFalse(failure, failure)
610
611 def testMakeDefaultsReferencesOriginalUseValue(self):
612 """Fail for make.defaults that refers to a not-yet-set $USE value."""
613 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400614 self.content_mock.return_value = u'USE="${USE} foo"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700615 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
616 self.assertTrue(failure, failure)
617
618 # Also check for "$USE" without curly brackets.
Mike Frysinger71e643e2019-09-13 17:26:39 -0400619 self.content_mock.return_value = u'USE="$USE foo"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700620 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
621 self.assertTrue(failure, failure)
622
623 def testMakeDefaultsOverwritesUseValue(self):
624 """Fail for make.defaults that discards its own $USE value."""
625 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400626 self.content_mock.return_value = u'USE="foo"\nUSE="bar"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700627 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
628 self.assertTrue(failure, failure)
629
630 def testMakeDefaultsCorrectUsage(self):
631 """Succeed for make.defaults that sets and preserves $USE."""
632 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400633 self.content_mock.return_value = u'USE="foo"\nUSE="${USE}" bar'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700634 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
635 self.assertFalse(failure, failure)
636
637
Mike Frysingerb2496652019-09-12 23:35:46 -0400638class CheckEbuildEapi(PreUploadTestCase):
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500639 """Tests for _check_ebuild_eapi."""
640
Alex Deymo643ac4c2015-09-03 10:40:50 -0700641 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500642
643 def setUp(self):
644 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
645 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
646 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff',
647 side_effect=Exception())
648
649 def testSkipUpstreamOverlays(self):
650 """Skip ebuilds found in upstream overlays."""
651 self.file_mock.side_effect = Exception()
652 ret = pre_upload._check_ebuild_eapi(self.PORTAGE_STABLE, 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400653 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500654
655 # Make sure our condition above triggers.
656 self.assertRaises(Exception, pre_upload._check_ebuild_eapi, 'o', 'HEAD')
657
658 def testSkipNonEbuilds(self):
659 """Skip non-ebuild files."""
660 self.content_mock.side_effect = Exception()
661
662 self.file_mock.return_value = ['some-file', 'ebuild/dir', 'an.ebuild~']
Alex Deymo643ac4c2015-09-03 10:40:50 -0700663 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400664 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500665
666 # Make sure our condition above triggers.
667 self.file_mock.return_value.append('a/real.ebuild')
Alex Deymo643ac4c2015-09-03 10:40:50 -0700668 self.assertRaises(Exception, pre_upload._check_ebuild_eapi,
669 ProjectNamed('o'), 'HEAD')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500670
671 def testSkipSymlink(self):
672 """Skip files that are just symlinks."""
673 self.file_mock.return_value = ['a-r1.ebuild']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400674 self.content_mock.return_value = u'a.ebuild'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700675 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400676 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500677
678 def testRejectEapiImplicit0Content(self):
679 """Reject ebuilds that do not declare EAPI (so it's 0)."""
680 self.file_mock.return_value = ['a.ebuild']
681
Mike Frysinger71e643e2019-09-13 17:26:39 -0400682 self.content_mock.return_value = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500683IUSE="foo"
684src_compile() { }
685"""
Alex Deymo643ac4c2015-09-03 10:40:50 -0700686 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500687 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500688
689 def testRejectExplicitEapi1Content(self):
690 """Reject ebuilds that do declare old EAPI explicitly."""
691 self.file_mock.return_value = ['a.ebuild']
692
Mike Frysinger71e643e2019-09-13 17:26:39 -0400693 template = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500694EAPI=%s
695IUSE="foo"
696src_compile() { }
697"""
698 # Make sure we only check the first EAPI= setting.
Mike Frysinger948284a2018-02-01 15:22:56 -0500699 self.content_mock.return_value = template % '1\nEAPI=60'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700700 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500701 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500702
703 # Verify we handle double quotes too.
704 self.content_mock.return_value = template % '"1"'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700705 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500706 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500707
708 # Verify we handle single quotes too.
709 self.content_mock.return_value = template % "'1'"
Alex Deymo643ac4c2015-09-03 10:40:50 -0700710 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500711 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500712
Mike Frysinger948284a2018-02-01 15:22:56 -0500713 def testAcceptExplicitNewEapiContent(self):
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500714 """Accept ebuilds that do declare new EAPI explicitly."""
715 self.file_mock.return_value = ['a.ebuild']
716
Mike Frysinger71e643e2019-09-13 17:26:39 -0400717 template = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500718EAPI=%s
719IUSE="foo"
720src_compile() { }
721"""
722 # Make sure we only check the first EAPI= setting.
Mike Frysinger948284a2018-02-01 15:22:56 -0500723 self.content_mock.return_value = template % '6\nEAPI=1'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700724 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400725 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500726
727 # Verify we handle double quotes too.
728 self.content_mock.return_value = template % '"5"'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700729 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400730 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500731
732 # Verify we handle single quotes too.
733 self.content_mock.return_value = template % "'5-hdepend'"
Alex Deymo643ac4c2015-09-03 10:40:50 -0700734 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400735 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500736
737
Mike Frysingerb2496652019-09-12 23:35:46 -0400738class CheckEbuildKeywords(PreUploadTestCase):
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400739 """Tests for _check_ebuild_keywords."""
740
741 def setUp(self):
742 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
743 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
744
745 def testNoEbuilds(self):
746 """If no ebuilds are found, do not scan."""
747 self.file_mock.return_value = ['a.file', 'ebuild-is-not.foo']
748
Alex Deymo643ac4c2015-09-03 10:40:50 -0700749 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400750 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400751
752 self.assertEqual(self.content_mock.call_count, 0)
753
754 def testSomeEbuilds(self):
755 """If ebuilds are found, only scan them."""
756 self.file_mock.return_value = ['a.file', 'blah', 'foo.ebuild', 'cow']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400757 self.content_mock.return_value = u''
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400758
Alex Deymo643ac4c2015-09-03 10:40:50 -0700759 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400760 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400761
762 self.assertEqual(self.content_mock.call_count, 1)
763
764 def _CheckContent(self, content, fails):
765 """Test helper for inputs/outputs.
766
767 Args:
768 content: The ebuild content to test.
769 fails: Whether |content| should trigger a hook failure.
770 """
771 self.file_mock.return_value = ['a.ebuild']
772 self.content_mock.return_value = content
773
Alex Deymo643ac4c2015-09-03 10:40:50 -0700774 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400775 if fails:
Mike Frysingerb81102f2014-11-21 00:33:35 -0500776 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400777 else:
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400778 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400779
780 self.assertEqual(self.content_mock.call_count, 1)
781
782 def testEmpty(self):
783 """Check KEYWORDS= is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400784 self._CheckContent(u'# HEADER\nKEYWORDS=\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400785
786 def testEmptyQuotes(self):
787 """Check KEYWORDS="" is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400788 self._CheckContent(u'# HEADER\nKEYWORDS=" "\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400789
790 def testStableGlob(self):
791 """Check KEYWORDS=* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400792 self._CheckContent(u'# HEADER\nKEYWORDS="\t*\t"\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400793
794 def testUnstableGlob(self):
795 """Check KEYWORDS=~* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400796 self._CheckContent(u'# HEADER\nKEYWORDS="~* "\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400797
798 def testRestrictedGlob(self):
799 """Check KEYWORDS=-* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400800 self._CheckContent(u'# HEADER\nKEYWORDS="\t-* arm"\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400801
802 def testMissingGlobs(self):
803 """Reject KEYWORDS missing any globs."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400804 self._CheckContent(u'# HEADER\nKEYWORDS="~arm x86"\nblah\n', True)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400805
806
Sergey Frolovc1bd8782021-01-20 19:35:44 -0700807class CheckEbuildLicense(PreUploadTestCase):
808 """Tests for _check_ebuild_licenses."""
809
810 def setUp(self):
811 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
812 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
813
814 def testNoEbuilds(self):
815 """If no ebuilds are found, do not scan."""
816 self.file_mock.return_value = ['a.file', 'ebuild-is-not.foo']
817
818 ret = pre_upload._check_ebuild_licenses(ProjectNamed('overlay'), 'HEAD')
819 self.assertIsNone(ret)
820
821 self.assertEqual(self.content_mock.call_count, 0)
822
823 def testSomeEbuilds(self):
824 """If ebuilds are found, only scan them."""
825 self.file_mock.return_value = ['a.file', 'blah', 'cow',
826 'overlay/category/pkg/pkg.ebuild']
827 self.content_mock.return_value = '# HEADER\nLICENSE="GPL-3"\nblah\n'
828
829 ret = pre_upload._check_ebuild_licenses(ProjectNamed('overlay'), 'HEAD')
830 self.assertIsNone(ret)
831
832 self.assertEqual(self.content_mock.call_count, 1)
833
834 def _CheckContent(self, license_field, ebuild_path, fails):
835 """Test helper for inputs/outputs.
836
837 Args:
838 license_field: Contents of LICENSE variable in the tested ebuild.
839 ebuild_path: The path to the tested ebuild.
840 fails: Whether inputs should trigger a hook failure.
841 """
842 self.file_mock.return_value = [ebuild_path]
843 self.content_mock.return_value = f'# blah\nLICENSE="{license_field}"\nbla\n'
844
845 ret = pre_upload._check_ebuild_licenses(ProjectNamed('overlay'), 'HEAD')
846 if fails:
847 self.assertIsInstance(ret, errors.HookFailure)
848 else:
849 self.assertIsNone(ret)
850
851 self.assertEqual(self.content_mock.call_count, 1)
852
853 def testEmpty(self):
854 """Check empty license is not accepted."""
855 self._CheckContent('', 'overlay/category/pkg/pkg.ebuild', True)
856
857 def testValid(self):
858 """Check valid license is accepted."""
859 self._CheckContent('GPL-3', 'overlay/category/pkg/pkg.ebuild', False)
860
861 def testVirtualNotMetapackage(self):
862 """Check virtual package not using metapackage is not accepted."""
863 self._CheckContent('GPL-3', 'overlay/virtual/pkg/pkg.ebuild', True)
864
865 def testVirtualMetapackage(self):
866 """Check virtual package using metapackage is accepted."""
867 self._CheckContent('metapackage', 'overlay/virtual/pkg/pkg.ebuild', False)
868
869
Mike Frysingerb04778f2020-11-30 02:41:14 -0500870class CheckEbuildOwners(PreUploadTestCase):
871 """Tests for _check_ebuild_owners."""
872
873 def setUp(self):
874 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
875 self.content_mock = self.PatchObject(
876 pre_upload, '_get_file_content', return_value=None)
877
878 def testNoMatches(self):
879 """Handle no matching files."""
880 self.file_mock.return_value = []
Mike Frysingerd9ed85f2021-02-03 12:38:24 -0500881 ret = pre_upload._check_ebuild_owners(ProjectNamed('project'), 'HEAD')
Mike Frysingerb04778f2020-11-30 02:41:14 -0500882 self.assertIsNone(ret)
883
884 def testNoEbuilds(self):
885 """Handle CLs w/no ebuilds."""
886 self.file_mock.return_value = [
887 DiffEntry(src_file='profiles/package.mask', status='M'),
888 DiffEntry(src_file='dev-util/pkg/README.md', status='A'),
889 DiffEntry(src_file='scripts/sync_sonic.py', status='D'),
890 ]
Mike Frysingerd9ed85f2021-02-03 12:38:24 -0500891 ret = pre_upload._check_ebuild_owners(ProjectNamed('project'), 'HEAD')
Mike Frysingerb04778f2020-11-30 02:41:14 -0500892 self.assertIsNone(ret)
893
894 def testMissingOwnersFailure(self):
895 """Test cases that should flag missing OWNERS."""
896 TESTS = (
897 [
898 DiffEntry(src_file='dev-util/pkg/foo.ebuild', status='A'),
899 DiffEntry(src_file='dev-util/pkg/README.md', status='A'),
900 DiffEntry(src_file='dev-util/pkg/Manifest', status='A'),
901 ],
902 )
903 for test in TESTS:
904 self.file_mock.return_value = test
Mike Frysingerd9ed85f2021-02-03 12:38:24 -0500905 ret = pre_upload._check_ebuild_owners(ProjectNamed('project'), 'HEAD')
Mike Frysingerb04778f2020-11-30 02:41:14 -0500906 self.assertIsNotNone(ret)
907
908 def testMissingOwnersIgnore(self):
909 """Test cases that should ignore missing OWNERS."""
910 TESTS = (
911 [
912 DiffEntry(src_file='dev-util/pkg/foo-9999.ebuild', status='M'),
913 ],
914 [
915 DiffEntry(src_file='dev-util/pkg/foo-0.ebuild', status='M'),
916 DiffEntry(src_file='dev-util/pkg/foo-0-r1.ebuild', status='A'),
917 ],
918 [
919 DiffEntry(src_file='dev-util/pkg/foo-0-r1.ebuild', status='D'),
920 DiffEntry(src_file='dev-util/pkg/foo-0-r2.ebuild', status='A'),
921 ],
922 )
923 for test in TESTS:
924 self.file_mock.return_value = test
Mike Frysingerd9ed85f2021-02-03 12:38:24 -0500925 ret = pre_upload._check_ebuild_owners(ProjectNamed('project'), 'HEAD')
Mike Frysingerb04778f2020-11-30 02:41:14 -0500926 self.assertIsNone(ret)
927
928 def testOwnersExist(self):
929 """Test cases where OWNERS exist."""
930 TESTS = (
931 [
932 DiffEntry(src_file='dev-util/pkg/foo-9999.ebuild', status='A'),
933 ],
934 )
935 self.content_mock.return_value = 'foo'
936 for test in TESTS:
937 self.file_mock.return_value = test
Mike Frysingerd9ed85f2021-02-03 12:38:24 -0500938 ret = pre_upload._check_ebuild_owners(ProjectNamed('project'), 'HEAD')
Mike Frysingerb04778f2020-11-30 02:41:14 -0500939 self.assertIsNone(ret)
940 # This should be the # of package dirs across all tests. This makes sure
941 # we actually called the owners check logic and didn't return early.
942 self.assertEqual(1, self.content_mock.call_count)
943
Mike Frysingerd9ed85f2021-02-03 12:38:24 -0500944 def testCommonPublicBoardOverlays(self):
945 """Allow an OWNERS in the top of the overlay to satisfy requirements."""
946 # The public repo that has all public overlays.
947 project = ProjectNamed('chromiumos/overlays/board-overlays')
948
949 def _get_content(path, _commit):
950 if path == 'overlay-oak/dev-util/pkg/OWNERS':
951 return None
952 elif path == 'overlay-oak/OWNERS':
953 return overlay_owners
954 else:
955 raise AssertionError(f'Unhandled test path: {path}')
956
957 self.content_mock.side_effect = _get_content
958 self.file_mock.return_value = [
959 DiffEntry(src_file='overlay-oak/dev-util/pkg/pkg-0-r1.ebuild',
960 status='A'),
961 ]
962
963 # OWNERS doesn't exist.
964 overlay_owners = None
965 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
966 self.assertIsNotNone(ret)
967
968 # OWNERS exists, but is empty.
969 overlay_owners = ''
970 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
971 self.assertIsNotNone(ret)
972
973 # OWNERS exists, but is too permissive.
974 overlay_owners = '# Everyone is an owner!\n*\n'
975 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
976 self.assertIsNotNone(ret)
977
978 # OWNERS exists, and is good.
979 overlay_owners = 'foo@chromium.org'
980 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
981 self.assertIsNone(ret)
982
983 def testCommonPrivateBoardOverlays(self):
984 """Allow an OWNERS in the top of the overlay to satisfy requirements."""
985 # A private repo that holds one board.
986 project = ProjectNamed('chromeos/overlays/baseboard-boo')
987
988 def _get_content(path, _commit):
989 if path == 'dev-util/pkg/OWNERS':
990 return None
991 elif path == 'OWNERS':
992 return overlay_owners
993 else:
994 raise AssertionError(f'Unhandled test path: {path}')
995
996 self.content_mock.side_effect = _get_content
997 self.file_mock.return_value = [
998 DiffEntry(src_file='dev-util/pkg/pkg-0-r1.ebuild', status='A'),
999 ]
1000
1001 # OWNERS doesn't exist.
1002 overlay_owners = None
1003 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
1004 self.assertIsNotNone(ret)
1005
1006 # OWNERS exists, but is empty.
1007 overlay_owners = ''
1008 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
1009 self.assertIsNotNone(ret)
1010
1011 # OWNERS exists, but is too permissive.
1012 overlay_owners = '# Everyone is an owner!\n*\n'
1013 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
1014 self.assertIsNotNone(ret)
1015
1016 # OWNERS exists, and is good.
1017 overlay_owners = 'foo@chromium.org'
1018 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
1019 self.assertIsNone(ret)
1020
1021 def testSharedOverlays(self):
1022 """Do not allow top-level OWNERS for shared overlays."""
1023 project = ProjectNamed('chromiumos/overlays/portage-stable')
1024
1025 def _get_content(path, _commit):
1026 if path == 'dev-util/pkg/OWNERS':
1027 return None
1028 elif path == 'OWNERS':
1029 return '# Global owners.\nfoo@bar\n'
1030 else:
1031 raise AssertionError(f'Unhandled test path: {path}')
1032
1033 self.content_mock.side_effect = _get_content
1034 self.file_mock.return_value = [
1035 DiffEntry(src_file='dev-util/pkg/pkg-0-r1.ebuild', status='A'),
1036 ]
1037
1038 ret = pre_upload._check_ebuild_owners(project, 'HEAD')
1039 self.assertIsNotNone(ret)
1040
Mike Frysingerb04778f2020-11-30 02:41:14 -05001041
Mike Frysinger6ee76b82020-11-20 01:16:06 -05001042class CheckEbuildR0(PreUploadTestCase):
1043 """Tests for _check_ebuild_r0."""
1044
1045 def setUp(self):
1046 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1047
1048 def testNoMatches(self):
1049 """Handle no matching files."""
1050 self.file_mock.return_value = []
Mike Frysingerd9ed85f2021-02-03 12:38:24 -05001051 ret = pre_upload._check_ebuild_r0(ProjectNamed('project'), 'HEAD')
Mike Frysinger6ee76b82020-11-20 01:16:06 -05001052 self.assertIsNone(ret)
1053
1054 def testBadEbuilds(self):
1055 """Handle matching r0 files."""
1056 self.file_mock.return_value = ['foo-1-r0.ebuild']
Mike Frysingerd9ed85f2021-02-03 12:38:24 -05001057 ret = pre_upload._check_ebuild_r0(ProjectNamed('project'), 'HEAD')
Mike Frysinger6ee76b82020-11-20 01:16:06 -05001058 self.assertIsNotNone(ret)
1059
1060
Mike Frysingerb2496652019-09-12 23:35:46 -04001061class CheckEbuildVirtualPv(PreUploadTestCase):
Mike Frysingercd363c82014-02-01 05:20:18 -05001062 """Tests for _check_ebuild_virtual_pv."""
1063
Alex Deymo643ac4c2015-09-03 10:40:50 -07001064 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
1065 CHROMIUMOS_OVERLAY = ProjectNamed('chromiumos/overlays/chromiumos')
1066 BOARD_OVERLAY = ProjectNamed('chromiumos/overlays/board-overlays')
1067 PRIVATE_OVERLAY = ProjectNamed('chromeos/overlays/overlay-link-private')
1068 PRIVATE_VARIANT_OVERLAY = ProjectNamed('chromeos/overlays/'
1069 'overlay-variant-daisy-spring-private')
Mike Frysingercd363c82014-02-01 05:20:18 -05001070
1071 def setUp(self):
1072 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1073
1074 def testNoVirtuals(self):
1075 """Skip non virtual packages."""
1076 self.file_mock.return_value = ['some/package/package-3.ebuild']
Alex Deymo643ac4c2015-09-03 10:40:50 -07001077 ret = pre_upload._check_ebuild_virtual_pv(ProjectNamed('overlay'), 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001078 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001079
1080 def testCommonVirtuals(self):
1081 """Non-board overlays should use PV=1."""
1082 template = 'virtual/foo/foo-%s.ebuild'
1083 self.file_mock.return_value = [template % '1']
1084 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001085 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001086
1087 self.file_mock.return_value = [template % '2']
1088 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -05001089 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -05001090
1091 def testPublicBoardVirtuals(self):
1092 """Public board overlays should use PV=2."""
1093 template = 'overlay-lumpy/virtual/foo/foo-%s.ebuild'
1094 self.file_mock.return_value = [template % '2']
1095 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001096 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001097
1098 self.file_mock.return_value = [template % '2.5']
1099 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -05001100 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -05001101
1102 def testPublicBoardVariantVirtuals(self):
1103 """Public board variant overlays should use PV=2.5."""
1104 template = 'overlay-variant-lumpy-foo/virtual/foo/foo-%s.ebuild'
1105 self.file_mock.return_value = [template % '2.5']
1106 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001107 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001108
1109 self.file_mock.return_value = [template % '3']
1110 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -05001111 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -05001112
1113 def testPrivateBoardVirtuals(self):
1114 """Private board overlays should use PV=3."""
1115 template = 'virtual/foo/foo-%s.ebuild'
1116 self.file_mock.return_value = [template % '3']
1117 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001118 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001119
1120 self.file_mock.return_value = [template % '3.5']
1121 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -05001122 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -05001123
1124 def testPrivateBoardVariantVirtuals(self):
1125 """Private board variant overlays should use PV=3.5."""
1126 template = 'virtual/foo/foo-%s.ebuild'
1127 self.file_mock.return_value = [template % '3.5']
1128 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001129 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001130
Bernie Thompsone5ee1822016-01-12 14:22:23 -08001131 def testSpecialVirtuals(self):
1132 """Some cases require deeper versioning and can be >= 4."""
1133 template = 'virtual/foo/foo-%s.ebuild'
Mike Frysingercd363c82014-02-01 05:20:18 -05001134 self.file_mock.return_value = [template % '4']
1135 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001136 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -05001137
Bernie Thompsone5ee1822016-01-12 14:22:23 -08001138 self.file_mock.return_value = [template % '4.5']
1139 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001140 self.assertIsNone(ret)
Mike Frysinger98638102014-08-28 00:15:08 -04001141
Mike Frysingerb2496652019-09-12 23:35:46 -04001142class CheckCrosLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -07001143 """Tests for _check_cros_license."""
Mike Frysinger98638102014-08-28 00:15:08 -04001144
1145 def setUp(self):
1146 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1147 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1148
1149 def testOldHeaders(self):
1150 """Accept old header styles."""
1151 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001152 (u'#!/bin/sh\n'
1153 u'# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.\n'
1154 u'# Use of this source code is governed by a BSD-style license that'
1155 u' can be\n'
1156 u'# found in the LICENSE file.\n'),
1157 (u'// Copyright 2010-2013 The Chromium OS Authors. All rights reserved.'
1158 u'\n// Use of this source code is governed by a BSD-style license that'
1159 u' can be\n'
1160 u'// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -04001161 )
1162 self.file_mock.return_value = ['file']
1163 for header in HEADERS:
1164 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +09001165 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
1166
1167 def testNewFileYear(self):
1168 """Added files should have the current year in license header."""
1169 year = datetime.datetime.now().year
1170 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001171 (u'// Copyright 2016 The Chromium OS Authors. All rights reserved.\n'
1172 u'// Use of this source code is governed by a BSD-style license that'
1173 u' can be\n'
1174 u'// found in the LICENSE file.\n'),
1175 (u'// Copyright %d The Chromium OS Authors. All rights reserved.\n'
1176 u'// Use of this source code is governed by a BSD-style license that'
1177 u' can be\n'
1178 u'// found in the LICENSE file.\n') % year,
Keigo Oka7e880ac2019-07-03 15:03:43 +09001179 )
1180 want_error = (True, False)
1181 def fake_get_affected_files(_, relative, include_adds=True):
1182 _ = relative
1183 if include_adds:
1184 return ['file']
1185 else:
1186 return []
1187
1188 self.file_mock.side_effect = fake_get_affected_files
1189 for i, header in enumerate(HEADERS):
1190 self.content_mock.return_value = header
1191 if want_error[i]:
1192 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
1193 else:
1194 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -04001195
1196 def testRejectC(self):
1197 """Reject the (c) in newer headers."""
1198 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001199 (u'// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.'
1200 u'\n'
1201 u'// Use of this source code is governed by a BSD-style license that'
1202 u' can be\n'
1203 u'// found in the LICENSE file.\n'),
1204 (u'// Copyright (c) 2020 The Chromium OS Authors. All rights reserved.'
1205 u'\n'
1206 u'// Use of this source code is governed by a BSD-style license that'
1207 u' can be\n'
1208 u'// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -04001209 )
1210 self.file_mock.return_value = ['file']
1211 for header in HEADERS:
1212 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +09001213 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001214
Brian Norris68838dd2018-09-26 18:30:24 -07001215 def testNoLeadingSpace(self):
1216 """Allow headers without leading space (e.g., not a source comment)"""
1217 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001218 (u'Copyright 2018 The Chromium OS Authors. All rights reserved.\n'
1219 u'Use of this source code is governed by a BSD-style license that '
1220 u'can be\n'
1221 u'found in the LICENSE file.\n'),
Brian Norris68838dd2018-09-26 18:30:24 -07001222 )
1223 self.file_mock.return_value = ['file']
1224 for header in HEADERS:
1225 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +09001226 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Brian Norris68838dd2018-09-26 18:30:24 -07001227
Keigo Oka9732e382019-06-28 17:44:59 +09001228 def testNoExcludedGolang(self):
1229 """Don't exclude .go files for license checks."""
1230 self.file_mock.return_value = ['foo/main.go']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001231 self.content_mock.return_value = u'package main\nfunc main() {}'
Keigo Oka7e880ac2019-07-03 15:03:43 +09001232 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Keigo Oka9732e382019-06-28 17:44:59 +09001233
Ken Turnerd07564b2018-02-08 17:57:59 +11001234 def testIgnoreExcludedPaths(self):
1235 """Ignores excluded paths for license checks."""
1236 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001237 self.content_mock.return_value = u'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +09001238 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +11001239
Tom Hughes90b7bd42020-11-10 10:31:49 -08001240 def testIgnoreMetadataFiles(self):
1241 """Ignores metadata files for license checks."""
1242 self.file_mock.return_value = ['foo/DIR_METADATA']
1243 self.content_mock.return_value = u'team_email: "team@chromium.org"'
1244 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
1245
Chris McDonald7b63c8e2019-04-25 10:27:27 -06001246 def testIgnoreTopLevelExcludedPaths(self):
1247 """Ignores excluded paths for license checks."""
1248 self.file_mock.return_value = ['OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001249 self.content_mock.return_value = u'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +09001250 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001251
Mike Frysingerb2496652019-09-12 23:35:46 -04001252class CheckAOSPLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -07001253 """Tests for _check_aosp_license."""
1254
1255 def setUp(self):
1256 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1257 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1258
1259 def testHeaders(self):
1260 """Accept old header styles."""
1261 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001262 u"""//
Alex Deymof5792ce2015-08-24 22:50:08 -07001263// Copyright (C) 2011 The Android Open Source Project
1264//
1265// Licensed under the Apache License, Version 2.0 (the "License");
1266// you may not use this file except in compliance with the License.
1267// You may obtain a copy of the License at
1268//
1269// http://www.apache.org/licenses/LICENSE-2.0
1270//
1271// Unless required by applicable law or agreed to in writing, software
1272// distributed under the License is distributed on an "AS IS" BASIS,
1273// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1274// See the License for the specific language governing permissions and
1275// limitations under the License.
1276//
1277""",
Mike Frysinger71e643e2019-09-13 17:26:39 -04001278 u"""#
Alex Deymof5792ce2015-08-24 22:50:08 -07001279# Copyright (c) 2015 The Android Open Source Project
1280#
1281# Licensed under the Apache License, Version 2.0 (the "License");
1282# you may not use this file except in compliance with the License.
1283# You may obtain a copy of the License at
1284#
1285# http://www.apache.org/licenses/LICENSE-2.0
1286#
1287# Unless required by applicable law or agreed to in writing, software
1288# distributed under the License is distributed on an "AS IS" BASIS,
1289# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1290# See the License for the specific language governing permissions and
1291# limitations under the License.
1292#
1293""",
1294 )
1295 self.file_mock.return_value = ['file']
1296 for header in HEADERS:
1297 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001298 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001299
1300 def testRejectNoLinesAround(self):
1301 """Reject headers missing the empty lines before/after the license."""
1302 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001303 u"""# Copyright (c) 2015 The Android Open Source Project
Alex Deymof5792ce2015-08-24 22:50:08 -07001304#
1305# Licensed under the Apache License, Version 2.0 (the "License");
1306# you may not use this file except in compliance with the License.
1307# You may obtain a copy of the License at
1308#
1309# http://www.apache.org/licenses/LICENSE-2.0
1310#
1311# Unless required by applicable law or agreed to in writing, software
1312# distributed under the License is distributed on an "AS IS" BASIS,
1313# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1314# See the License for the specific language governing permissions and
1315# limitations under the License.
1316""",
1317 )
1318 self.file_mock.return_value = ['file']
1319 for header in HEADERS:
1320 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001321 self.assertIsNotNone(pre_upload._check_aosp_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -04001322
Ken Turnerd07564b2018-02-08 17:57:59 +11001323 def testIgnoreExcludedPaths(self):
1324 """Ignores excluded paths for license checks."""
1325 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001326 self.content_mock.return_value = u'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001327 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +11001328
Chris McDonald7b63c8e2019-04-25 10:27:27 -06001329 def testIgnoreTopLevelExcludedPaths(self):
1330 """Ignores excluded paths for license checks."""
1331 self.file_mock.return_value = ['OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001332 self.content_mock.return_value = u'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001333 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
1334
Mike Frysinger98638102014-08-28 00:15:08 -04001335
Mike Frysingerb2496652019-09-12 23:35:46 -04001336class CheckLayoutConfTestCase(PreUploadTestCase):
Mike Frysingerd7734522015-02-26 16:12:43 -05001337 """Tests for _check_layout_conf."""
1338
1339 def setUp(self):
1340 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1341 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1342
Mike Frysingerd9ed85f2021-02-03 12:38:24 -05001343 def assertAccepted(self, files, project=ProjectNamed('project'),
1344 commit='fake sha1'):
Mike Frysingerd7734522015-02-26 16:12:43 -05001345 """Assert _check_layout_conf accepts |files|."""
1346 self.file_mock.return_value = files
1347 ret = pre_upload._check_layout_conf(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001348 self.assertIsNone(ret, msg='rejected with:\n%s' % ret)
Mike Frysingerd7734522015-02-26 16:12:43 -05001349
Mike Frysingerd9ed85f2021-02-03 12:38:24 -05001350 def assertRejected(self, files, project=ProjectNamed('project'),
1351 commit='fake sha1'):
Mike Frysingerd7734522015-02-26 16:12:43 -05001352 """Assert _check_layout_conf rejects |files|."""
1353 self.file_mock.return_value = files
1354 ret = pre_upload._check_layout_conf(project, commit)
1355 self.assertTrue(isinstance(ret, errors.HookFailure))
1356
1357 def GetLayoutConf(self, filters=()):
1358 """Return a valid layout.conf with |filters| lines removed."""
1359 all_lines = [
Mike Frysinger71e643e2019-09-13 17:26:39 -04001360 u'masters = portage-stable chromiumos',
1361 u'profile-formats = portage-2 profile-default-eapi',
1362 u'profile_eapi_when_unspecified = 5-progress',
1363 u'repo-name = link',
1364 u'thin-manifests = true',
1365 u'use-manifests = strict',
Mike Frysingerd7734522015-02-26 16:12:43 -05001366 ]
1367
1368 lines = []
1369 for line in all_lines:
1370 for filt in filters:
1371 if line.startswith(filt):
1372 break
1373 else:
1374 lines.append(line)
1375
Mike Frysinger71e643e2019-09-13 17:26:39 -04001376 return u'\n'.join(lines)
Mike Frysingerd7734522015-02-26 16:12:43 -05001377
1378 def testNoFilesToCheck(self):
1379 """Don't blow up when there are no layout.conf files."""
1380 self.assertAccepted([])
1381
1382 def testRejectRepoNameFile(self):
1383 """If profiles/repo_name is set, kick it out."""
1384 self.assertRejected(['profiles/repo_name'])
1385
1386 def testAcceptValidLayoutConf(self):
1387 """Accept a fully valid layout.conf."""
1388 self.content_mock.return_value = self.GetLayoutConf()
1389 self.assertAccepted(['metadata/layout.conf'])
1390
1391 def testAcceptUnknownKeys(self):
1392 """Accept keys we don't explicitly know about."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001393 self.content_mock.return_value = self.GetLayoutConf() + u'\nzzz-top = ok'
Mike Frysingerd7734522015-02-26 16:12:43 -05001394 self.assertAccepted(['metadata/layout.conf'])
1395
1396 def testRejectUnsorted(self):
1397 """Reject an unsorted layout.conf."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001398 self.content_mock.return_value = u'zzz-top = bad\n' + self.GetLayoutConf()
Mike Frysingerd7734522015-02-26 16:12:43 -05001399 self.assertRejected(['metadata/layout.conf'])
1400
1401 def testRejectMissingThinManifests(self):
1402 """Reject a layout.conf missing thin-manifests."""
1403 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001404 filters=[u'thin-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001405 self.assertRejected(['metadata/layout.conf'])
1406
1407 def testRejectMissingUseManifests(self):
1408 """Reject a layout.conf missing use-manifests."""
1409 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001410 filters=[u'use-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001411 self.assertRejected(['metadata/layout.conf'])
1412
1413 def testRejectMissingEapiFallback(self):
1414 """Reject a layout.conf missing profile_eapi_when_unspecified."""
1415 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001416 filters=[u'profile_eapi_when_unspecified'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001417 self.assertRejected(['metadata/layout.conf'])
1418
1419 def testRejectMissingRepoName(self):
1420 """Reject a layout.conf missing repo-name."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001421 self.content_mock.return_value = self.GetLayoutConf(filters=[u'repo-name'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001422 self.assertRejected(['metadata/layout.conf'])
1423
1424
Mike Frysingerb2496652019-09-12 23:35:46 -04001425class CommitMessageTestCase(PreUploadTestCase):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001426 """Test case for funcs that check commit messages."""
1427
1428 def setUp(self):
1429 self.msg_mock = self.PatchObject(pre_upload, '_get_commit_desc')
1430
1431 @staticmethod
1432 def CheckMessage(_project, _commit):
1433 raise AssertionError('Test class must declare CheckMessage')
1434 # This dummy return is to silence pylint warning W1111 so we don't have to
1435 # enable it for all the call sites below.
1436 return 1 # pylint: disable=W0101
1437
Alex Deymo643ac4c2015-09-03 10:40:50 -07001438 def assertMessageAccepted(self, msg, project=ProjectNamed('project'),
1439 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001440 """Assert _check_change_has_bug_field accepts |msg|."""
1441 self.msg_mock.return_value = msg
1442 ret = self.CheckMessage(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001443 self.assertIsNone(ret)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001444
Alex Deymo643ac4c2015-09-03 10:40:50 -07001445 def assertMessageRejected(self, msg, project=ProjectNamed('project'),
1446 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001447 """Assert _check_change_has_bug_field rejects |msg|."""
1448 self.msg_mock.return_value = msg
1449 ret = self.CheckMessage(project, commit)
1450 self.assertTrue(isinstance(ret, errors.HookFailure))
1451
1452
1453class CheckCommitMessageBug(CommitMessageTestCase):
1454 """Tests for _check_change_has_bug_field."""
1455
Alex Deymo643ac4c2015-09-03 10:40:50 -07001456 AOSP_PROJECT = pre_upload.Project(name='overlay', dir='', remote='aosp')
1457 CROS_PROJECT = pre_upload.Project(name='overlay', dir='', remote='cros')
1458
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001459 @staticmethod
1460 def CheckMessage(project, commit):
1461 return pre_upload._check_change_has_bug_field(project, commit)
1462
1463 def testNormal(self):
1464 """Accept a commit message w/a valid BUG."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001465 self.assertMessageAccepted('\nBUG=chromium:1234\n', self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001466 self.assertMessageAccepted('\nBUG=b:1234\n', self.CROS_PROJECT)
1467
1468 self.assertMessageAccepted('\nBug: 1234\n', self.AOSP_PROJECT)
1469 self.assertMessageAccepted('\nBug:1234\n', self.AOSP_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001470
1471 def testNone(self):
1472 """Accept BUG=None."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001473 self.assertMessageAccepted('\nBUG=None\n', self.CROS_PROJECT)
1474 self.assertMessageAccepted('\nBUG=none\n', self.CROS_PROJECT)
1475 self.assertMessageAccepted('\nBug: None\n', self.AOSP_PROJECT)
1476 self.assertMessageAccepted('\nBug:none\n', self.AOSP_PROJECT)
1477
1478 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1479 self.assertMessageRejected('\nBUG=NONE\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001480
1481 def testBlank(self):
1482 """Reject blank values."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001483 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1484 self.assertMessageRejected('\nBUG=\n', project)
1485 self.assertMessageRejected('\nBUG= \n', project)
1486 self.assertMessageRejected('\nBug:\n', project)
1487 self.assertMessageRejected('\nBug: \n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001488
1489 def testNotFirstLine(self):
1490 """Reject the first line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001491 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1492 self.assertMessageRejected('BUG=None\n\n\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001493
1494 def testNotInline(self):
1495 """Reject not at the start of line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001496 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1497 self.assertMessageRejected('\n BUG=None\n', project)
1498 self.assertMessageRejected('\n\tBUG=None\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001499
1500 def testOldTrackers(self):
1501 """Reject commit messages using old trackers."""
Mike Frysingera2f28252017-10-27 22:26:14 -04001502 self.assertMessageRejected('\nBUG=chrome-os-partner:1234\n',
1503 self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001504 self.assertMessageRejected('\nBUG=chromium-os:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001505
1506 def testNoTrackers(self):
1507 """Reject commit messages w/invalid trackers."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001508 self.assertMessageRejected('\nBUG=booga:1234\n', self.CROS_PROJECT)
1509 self.assertMessageRejected('\nBUG=br:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001510
1511 def testMissing(self):
1512 """Reject commit messages w/no BUG line."""
1513 self.assertMessageRejected('foo\n')
1514
1515 def testCase(self):
1516 """Reject bug lines that are not BUG."""
1517 self.assertMessageRejected('\nbug=none\n')
1518
Cheng Yuehb707c522020-01-02 14:06:59 +08001519 def testNotAfterTest(self):
1520 """Reject any TEST line before any BUG line."""
1521 test_field = 'TEST=i did not do it\n'
1522 middle_field = 'A random between line\n'
1523 for project, bug_field in ((self.AOSP_PROJECT, 'Bug:none\n'),
1524 (self.CROS_PROJECT, 'BUG=None\n')):
1525 self.assertMessageRejected(
1526 '\n' + test_field + middle_field + bug_field, project)
1527 self.assertMessageRejected(
1528 '\n' + test_field + bug_field, project)
1529
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001530
1531class CheckCommitMessageCqDepend(CommitMessageTestCase):
1532 """Tests for _check_change_has_valid_cq_depend."""
1533
1534 @staticmethod
1535 def CheckMessage(project, commit):
1536 return pre_upload._check_change_has_valid_cq_depend(project, commit)
1537
1538 def testNormal(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001539 """Accept valid Cq-Depends line."""
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001540 self.assertMessageAccepted('\nCq-Depend: chromium:1234\nChange-Id: I123')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001541
1542 def testInvalid(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001543 """Reject invalid Cq-Depends line."""
1544 self.assertMessageRejected('\nCq-Depend=chromium=1234\n')
1545 self.assertMessageRejected('\nCq-Depend: None\n')
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001546 self.assertMessageRejected('\nCq-Depend: chromium:1234\n\nChange-Id: I123')
Mike Frysingere39d0cd2019-11-25 13:30:06 -05001547 self.assertMessageRejected('\nCQ-DEPEND=1234\n')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001548
1549
Bernie Thompsonf8fea992016-01-14 10:27:18 -08001550class CheckCommitMessageContribution(CommitMessageTestCase):
1551 """Tests for _check_change_is_contribution."""
1552
1553 @staticmethod
1554 def CheckMessage(project, commit):
1555 return pre_upload._check_change_is_contribution(project, commit)
1556
1557 def testNormal(self):
1558 """Accept a commit message which is a contribution."""
1559 self.assertMessageAccepted('\nThis is cool code I am contributing.\n')
1560
1561 def testFailureLowerCase(self):
1562 """Deny a commit message which is not a contribution."""
1563 self.assertMessageRejected('\nThis is not a contribution.\n')
1564
1565 def testFailureUpperCase(self):
1566 """Deny a commit message which is not a contribution."""
1567 self.assertMessageRejected('\nNOT A CONTRIBUTION\n')
1568
1569
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001570class CheckCommitMessageTest(CommitMessageTestCase):
1571 """Tests for _check_change_has_test_field."""
1572
1573 @staticmethod
1574 def CheckMessage(project, commit):
1575 return pre_upload._check_change_has_test_field(project, commit)
1576
1577 def testNormal(self):
1578 """Accept a commit message w/a valid TEST."""
1579 self.assertMessageAccepted('\nTEST=i did it\n')
1580
1581 def testNone(self):
1582 """Accept TEST=None."""
1583 self.assertMessageAccepted('\nTEST=None\n')
1584 self.assertMessageAccepted('\nTEST=none\n')
1585
1586 def testBlank(self):
1587 """Reject blank values."""
1588 self.assertMessageRejected('\nTEST=\n')
1589 self.assertMessageRejected('\nTEST= \n')
1590
1591 def testNotFirstLine(self):
1592 """Reject the first line."""
1593 self.assertMessageRejected('TEST=None\n\n\n')
1594
1595 def testNotInline(self):
1596 """Reject not at the start of line."""
1597 self.assertMessageRejected('\n TEST=None\n')
1598 self.assertMessageRejected('\n\tTEST=None\n')
1599
1600 def testMissing(self):
1601 """Reject commit messages w/no TEST line."""
1602 self.assertMessageRejected('foo\n')
1603
1604 def testCase(self):
1605 """Reject bug lines that are not TEST."""
1606 self.assertMessageRejected('\ntest=none\n')
1607
1608
1609class CheckCommitMessageChangeId(CommitMessageTestCase):
1610 """Tests for _check_change_has_proper_changeid."""
1611
1612 @staticmethod
1613 def CheckMessage(project, commit):
1614 return pre_upload._check_change_has_proper_changeid(project, commit)
1615
1616 def testNormal(self):
1617 """Accept a commit message w/a valid Change-Id."""
1618 self.assertMessageAccepted('foo\n\nChange-Id: I1234\n')
1619
1620 def testBlank(self):
1621 """Reject blank values."""
1622 self.assertMessageRejected('\nChange-Id:\n')
1623 self.assertMessageRejected('\nChange-Id: \n')
1624
1625 def testNotFirstLine(self):
1626 """Reject the first line."""
1627 self.assertMessageRejected('TEST=None\n\n\n')
1628
1629 def testNotInline(self):
1630 """Reject not at the start of line."""
1631 self.assertMessageRejected('\n Change-Id: I1234\n')
1632 self.assertMessageRejected('\n\tChange-Id: I1234\n')
1633
1634 def testMissing(self):
1635 """Reject commit messages missing the line."""
1636 self.assertMessageRejected('foo\n')
1637
1638 def testCase(self):
1639 """Reject bug lines that are not Change-Id."""
1640 self.assertMessageRejected('\nchange-id: I1234\n')
1641 self.assertMessageRejected('\nChange-id: I1234\n')
1642 self.assertMessageRejected('\nChange-ID: I1234\n')
1643
1644 def testEnd(self):
1645 """Reject Change-Id's that are not last."""
1646 self.assertMessageRejected('\nChange-Id: I1234\nbar\n')
1647
Mike Frysinger02b88bd2014-11-21 00:29:38 -05001648 def testSobTag(self):
1649 """Permit s-o-b tags to follow the Change-Id."""
1650 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nSigned-off-by: Hi\n')
1651
LaMont Jones237f3ef2020-01-22 10:40:52 -07001652 def testCqClTag(self):
1653 """Permit Cq-Cl-Tag tags to follow the Change-Id."""
1654 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nCq-Cl-Tag: Hi\n')
1655
LaMont Jonesfb5e8bf2020-03-03 12:50:06 -07001656 def testCqIncludeTrybotsTag(self):
1657 """Permit Cq-Include-Trybots tags to follow the Change-Id."""
1658 self.assertMessageAccepted(
1659 'foo\n\nChange-Id: I1234\nCq-Include-Trybots: chromeos/cq:foo\n')
1660
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001661
Jack Neus8edbf642019-07-10 16:08:31 -06001662class CheckCommitMessageNoOEM(CommitMessageTestCase):
1663 """Tests for _check_change_no_include_oem."""
1664
1665 @staticmethod
1666 def CheckMessage(project, commit):
1667 return pre_upload._check_change_no_include_oem(project, commit)
1668
1669 def testNormal(self):
1670 """Accept a commit message w/o reference to an OEM/ODM."""
1671 self.assertMessageAccepted('foo\n')
1672
1673 def testHasOEM(self):
1674 """Catch commit messages referencing OEMs."""
1675 self.assertMessageRejected('HP Project\n\n')
1676 self.assertMessageRejected('hewlett-packard\n')
1677 self.assertMessageRejected('Hewlett\nPackard\n')
1678 self.assertMessageRejected('Dell Chromebook\n\n\n')
1679 self.assertMessageRejected('product@acer.com\n')
1680 self.assertMessageRejected('This is related to Asus\n')
1681 self.assertMessageRejected('lenovo machine\n')
1682
1683 def testHasODM(self):
1684 """Catch commit messages referencing ODMs."""
1685 self.assertMessageRejected('new samsung laptop\n\n')
1686 self.assertMessageRejected('pegatron(ems) project\n')
1687 self.assertMessageRejected('new Wistron device\n')
1688
1689 def testContainsOEM(self):
1690 """Check that the check handles word boundaries properly."""
1691 self.assertMessageAccepted('oheahpohea')
1692 self.assertMessageAccepted('Play an Asus7 guitar chord.\n\n')
1693
1694 def testTag(self):
1695 """Check that the check ignores tags."""
1696 self.assertMessageAccepted(
1697 'Harmless project\n'
1698 'Reviewed-by: partner@asus.corp-partner.google.com\n'
1699 'Tested-by: partner@hp.corp-partner.google.com\n'
1700 'Signed-off-by: partner@dell.corp-partner.google.com\n'
1701 'Commit-Queue: partner@lenovo.corp-partner.google.com\n'
Jack Neus8edbf642019-07-10 16:08:31 -06001702 'CC: partner@acer.corp-partner.google.com\n'
1703 'Acked-by: partner@hewlett-packard.corp-partner.google.com\n')
1704 self.assertMessageRejected(
1705 'Asus project\n'
1706 'Reviewed-by: partner@asus.corp-partner.google.com')
Mike Frysingerbb34a222019-07-31 14:40:46 -04001707 self.assertMessageRejected(
1708 'my project\n'
1709 'Bad-tag: partner@asus.corp-partner.google.com')
Jack Neus8edbf642019-07-10 16:08:31 -06001710
1711
Mike Frysinger36b2ebc2014-10-31 14:02:03 -04001712class CheckCommitMessageStyle(CommitMessageTestCase):
1713 """Tests for _check_commit_message_style."""
1714
1715 @staticmethod
1716 def CheckMessage(project, commit):
1717 return pre_upload._check_commit_message_style(project, commit)
1718
1719 def testNormal(self):
1720 """Accept valid commit messages."""
1721 self.assertMessageAccepted('one sentence.\n')
1722 self.assertMessageAccepted('some.module: do it!\n')
1723 self.assertMessageAccepted('one line\n\nmore stuff here.')
1724
1725 def testNoBlankSecondLine(self):
1726 """Reject messages that have stuff on the second line."""
1727 self.assertMessageRejected('one sentence.\nbad fish!\n')
1728
1729 def testFirstLineMultipleSentences(self):
1730 """Reject messages that have more than one sentence in the summary."""
1731 self.assertMessageRejected('one sentence. two sentence!\n')
1732
1733 def testFirstLineTooLone(self):
1734 """Reject first lines that are too long."""
1735 self.assertMessageRejected('o' * 200)
1736
Mike Frysingerd2451822021-02-17 13:52:19 -05001737 def testGitKeywords(self):
1738 """Reject git special keywords."""
1739 BAD_MESSAGES = (
1740 'fixup!',
1741 'fixup! that old commit',
1742 'squash!',
1743 'squash! that old commit',
1744 )
1745 GOOD_MESSAGES = (
1746 'this change needs a fixup!',
1747 'you should eat your squash!',
1748 )
1749 for msg in BAD_MESSAGES:
1750 self.assertMessageRejected(msg)
1751 for msg in GOOD_MESSAGES:
1752 self.assertMessageAccepted(msg)
1753
Mike Frysinger36b2ebc2014-10-31 14:02:03 -04001754
Mike Frysinger292b45d2014-11-25 01:17:10 -05001755def DiffEntry(src_file=None, dst_file=None, src_mode=None, dst_mode='100644',
1756 status='M'):
1757 """Helper to create a stub RawDiffEntry object"""
1758 if src_mode is None:
1759 if status == 'A':
1760 src_mode = '000000'
1761 elif status == 'M':
1762 src_mode = dst_mode
1763 elif status == 'D':
1764 src_mode = dst_mode
1765 dst_mode = '000000'
1766
1767 src_sha = dst_sha = 'abc'
1768 if status == 'D':
1769 dst_sha = '000000'
1770 elif status == 'A':
1771 src_sha = '000000'
1772
1773 return git.RawDiffEntry(src_mode=src_mode, dst_mode=dst_mode, src_sha=src_sha,
1774 dst_sha=dst_sha, status=status, score=None,
1775 src_file=src_file, dst_file=dst_file)
1776
1777
Mike Frysingerb2496652019-09-12 23:35:46 -04001778class HelpersTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001779 """Various tests for utility functions."""
1780
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001781 def setUp(self):
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001782 os.chdir(self.tempdir)
1783
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001784 self.PatchObject(git, 'RawDiff', return_value=[
1785 # A modified normal file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001786 DiffEntry(src_file='buildbot/constants.py', status='M'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001787 # A new symlink file.
Bob Haarman0dc1f942020-10-03 00:06:59 +00001788 DiffEntry(dst_file='scripts/cros_env_allowlist', dst_mode='120000',
Mike Frysinger292b45d2014-11-25 01:17:10 -05001789 status='A'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001790 # A deleted file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001791 DiffEntry(src_file='scripts/sync_sonic.py', status='D'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001792 ])
1793
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001794 def _WritePresubmitIgnoreFile(self, subdir, data):
1795 """Writes to a .presubmitignore file in the passed-in subdirectory."""
1796 directory = os.path.join(self.tempdir, subdir)
1797 if not os.path.exists(directory):
1798 os.makedirs(directory)
1799 osutils.WriteFile(os.path.join(directory, pre_upload._IGNORE_FILE), data)
1800
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001801 def testGetAffectedFilesNoDeletesNoRelative(self):
1802 """Verify _get_affected_files() works w/no delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001803 path = os.getcwd()
1804 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1805 relative=False)
1806 exp_files = [os.path.join(path, 'buildbot/constants.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001807 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001808
1809 def testGetAffectedFilesDeletesNoRelative(self):
1810 """Verify _get_affected_files() works w/delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001811 path = os.getcwd()
1812 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1813 relative=False)
1814 exp_files = [os.path.join(path, 'buildbot/constants.py'),
1815 os.path.join(path, 'scripts/sync_sonic.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001816 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001817
1818 def testGetAffectedFilesNoDeletesRelative(self):
1819 """Verify _get_affected_files() works w/no delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001820 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1821 relative=True)
1822 exp_files = ['buildbot/constants.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001823 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001824
1825 def testGetAffectedFilesDeletesRelative(self):
1826 """Verify _get_affected_files() works w/delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001827 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1828 relative=True)
1829 exp_files = ['buildbot/constants.py', 'scripts/sync_sonic.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001830 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001831
Mike Frysinger292b45d2014-11-25 01:17:10 -05001832 def testGetAffectedFilesDetails(self):
1833 """Verify _get_affected_files() works w/full_details."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001834 files = pre_upload._get_affected_files('HEAD', full_details=True,
1835 relative=True)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001836 self.assertEqual(files[0].src_file, 'buildbot/constants.py')
Mike Frysinger292b45d2014-11-25 01:17:10 -05001837
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001838 def testGetAffectedFilesPresubmitIgnoreDirectory(self):
1839 """Verify .presubmitignore can be used to exclude a directory."""
1840 self._WritePresubmitIgnoreFile('.', 'buildbot/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001841 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001842
1843 def testGetAffectedFilesPresubmitIgnoreDirectoryWildcard(self):
1844 """Verify .presubmitignore can be used with a directory wildcard."""
1845 self._WritePresubmitIgnoreFile('.', '*/constants.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001846 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001847
1848 def testGetAffectedFilesPresubmitIgnoreWithinDirectory(self):
1849 """Verify .presubmitignore can be placed in a subdirectory."""
1850 self._WritePresubmitIgnoreFile('buildbot', '*.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001851 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001852
1853 def testGetAffectedFilesPresubmitIgnoreDoesntMatch(self):
1854 """Verify .presubmitignore has no effect when it doesn't match a file."""
1855 self._WritePresubmitIgnoreFile('buildbot', '*.txt')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001856 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True),
1857 ['buildbot/constants.py'])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001858
1859 def testGetAffectedFilesPresubmitIgnoreAddedFile(self):
1860 """Verify .presubmitignore matches added files."""
1861 self._WritePresubmitIgnoreFile('.', 'buildbot/\nscripts/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001862 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True,
1863 include_symlinks=True),
1864 [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001865
1866 def testGetAffectedFilesPresubmitIgnoreSkipIgnoreFile(self):
1867 """Verify .presubmitignore files are automatically skipped."""
1868 self.PatchObject(git, 'RawDiff', return_value=[
1869 DiffEntry(src_file='.presubmitignore', status='M')
1870 ])
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001871 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Mike Frysinger292b45d2014-11-25 01:17:10 -05001872
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001873
Mike Frysingerb2496652019-09-12 23:35:46 -04001874class ExecFilesTest(PreUploadTestCase):
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001875 """Tests for _check_exec_files."""
1876
1877 def setUp(self):
1878 self.diff_mock = self.PatchObject(git, 'RawDiff')
1879
1880 def testNotExec(self):
1881 """Do not flag files that are not executable."""
1882 self.diff_mock.return_value = [
1883 DiffEntry(src_file='make.conf', dst_mode='100644', status='A'),
1884 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001885 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001886
1887 def testExec(self):
1888 """Flag files that are executable."""
1889 self.diff_mock.return_value = [
1890 DiffEntry(src_file='make.conf', dst_mode='100755', status='A'),
1891 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001892 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001893
1894 def testDeletedExec(self):
1895 """Ignore bad files that are being deleted."""
1896 self.diff_mock.return_value = [
1897 DiffEntry(src_file='make.conf', dst_mode='100755', status='D'),
1898 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001899 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001900
1901 def testModifiedExec(self):
1902 """Flag bad files that weren't exec, but now are."""
1903 self.diff_mock.return_value = [
1904 DiffEntry(src_file='make.conf', src_mode='100644', dst_mode='100755',
1905 status='M'),
1906 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001907 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001908
1909 def testNormalExec(self):
1910 """Don't flag normal files (e.g. scripts) that are executable."""
1911 self.diff_mock.return_value = [
1912 DiffEntry(src_file='foo.sh', dst_mode='100755', status='A'),
1913 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001914 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001915
1916
Mike Frysingerb2496652019-09-12 23:35:46 -04001917class CheckForUprev(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger292b45d2014-11-25 01:17:10 -05001918 """Tests for _check_for_uprev."""
1919
1920 def setUp(self):
1921 self.file_mock = self.PatchObject(git, 'RawDiff')
1922
1923 def _Files(self, files):
1924 """Create |files| in the tempdir and return full paths to them."""
1925 for obj in files:
1926 if obj.status == 'D':
1927 continue
1928 if obj.dst_file is None:
1929 f = obj.src_file
1930 else:
1931 f = obj.dst_file
1932 osutils.Touch(os.path.join(self.tempdir, f), makedirs=True)
1933 return files
1934
1935 def assertAccepted(self, files, project='project', commit='fake sha1'):
1936 """Assert _check_for_uprev accepts |files|."""
1937 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001938 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1939 project_top=self.tempdir)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001940 self.assertIsNone(ret)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001941
1942 def assertRejected(self, files, project='project', commit='fake sha1'):
1943 """Assert _check_for_uprev rejects |files|."""
1944 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001945 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1946 project_top=self.tempdir)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001947 self.assertTrue(isinstance(ret, errors.HookFailure))
1948
Bob Haarman0dc1f942020-10-03 00:06:59 +00001949 def testAllowlistOverlay(self):
1950 """Skip checks on allowed overlays."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001951 self.assertAccepted([DiffEntry(src_file='cat/pkg/pkg-0.ebuild')],
1952 project='chromiumos/overlays/portage-stable')
1953
Bob Haarman0dc1f942020-10-03 00:06:59 +00001954 def testAllowlistFiles(self):
1955 """Skip checks on allowed files."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001956 files = ['ChangeLog', 'Manifest', 'metadata.xml']
1957 self.assertAccepted([DiffEntry(src_file=os.path.join('c', 'p', x),
1958 status='M')
1959 for x in files])
1960
1961 def testRejectBasic(self):
1962 """Reject ebuilds missing uprevs."""
1963 self.assertRejected([DiffEntry(src_file='c/p/p-0.ebuild', status='M')])
1964
1965 def testNewPackage(self):
1966 """Accept new ebuilds w/out uprevs."""
1967 self.assertAccepted([DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1968 self.assertAccepted([DiffEntry(src_file='c/p/p-0-r12.ebuild', status='A')])
1969
1970 def testModifiedFilesOnly(self):
1971 """Reject ebuilds w/out uprevs and changes in files/."""
1972 osutils.Touch(os.path.join(self.tempdir, 'cat/pkg/pkg-0.ebuild'),
1973 makedirs=True)
1974 self.assertRejected([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1975 self.assertRejected([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1976
1977 def testFilesNoEbuilds(self):
1978 """Ignore changes to paths w/out ebuilds."""
1979 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1980 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1981
1982 def testModifiedFilesWithUprev(self):
1983 """Accept ebuilds w/uprevs and changes in files/."""
1984 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='A'),
1985 DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1986 self.assertAccepted([
1987 DiffEntry(src_file='c/p/files/f', status='M'),
1988 DiffEntry(src_file='c/p/p-0-r1.ebuild', src_mode='120000',
1989 dst_file='c/p/p-0-r2.ebuild', dst_mode='120000', status='R')])
1990
Gwendal Grignoua3086c32014-12-09 11:17:22 -08001991 def testModifiedFilesWith9999(self):
1992 """Accept 9999 ebuilds and changes in files/."""
1993 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M'),
1994 DiffEntry(src_file='c/p/p-9999.ebuild', status='M')])
1995
C Shapiroae157ae2017-09-18 16:24:03 -06001996 def testModifiedFilesIn9999SubDirWithout9999Change(self):
1997 """Accept changes in files/ with a parent 9999 ebuild"""
1998 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
1999 os.makedirs(os.path.dirname(ebuild_9999_file))
2000 osutils.WriteFile(ebuild_9999_file, 'fake')
2001 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M')])
2002
Stephen Boyd6bf5ea82020-10-15 00:02:07 -07002003 def testModifiedFilesAndProfilesWith9999(self):
2004 """Accept changes in files/ with a parent 9999 ebuild and profile change"""
2005 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
2006 os.makedirs(os.path.dirname(ebuild_9999_file))
2007 osutils.WriteFile(ebuild_9999_file, 'fake')
2008 self.assertAccepted([
2009 DiffEntry(src_file='c/p/files/f', status='M'),
2010 DiffEntry(src_file='c/profiles/base/make.defaults', status='M')])
2011
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05002012
Mike Frysingerb2496652019-09-12 23:35:46 -04002013class DirectMainTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger55f85b52014-12-18 14:45:21 -05002014 """Tests for direct_main()"""
2015
2016 def setUp(self):
2017 self.hooks_mock = self.PatchObject(pre_upload, '_run_project_hooks',
2018 return_value=None)
2019
2020 def testNoArgs(self):
2021 """If run w/no args, should check the current dir."""
2022 ret = pre_upload.direct_main([])
2023 self.assertEqual(ret, 0)
2024 self.hooks_mock.assert_called_once_with(
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07002025 mock.ANY, proj_dir=os.getcwd(), commit_list=[], presubmit=mock.ANY,
2026 config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05002027
2028 def testExplicitDir(self):
2029 """Verify we can run on a diff dir."""
2030 # Use the chromite dir since we know it exists.
2031 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR])
2032 self.assertEqual(ret, 0)
2033 self.hooks_mock.assert_called_once_with(
2034 mock.ANY, proj_dir=constants.CHROMITE_DIR, commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07002035 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05002036
2037 def testBogusProject(self):
2038 """A bogus project name should be fine (use default settings)."""
2039 # Use the chromite dir since we know it exists.
2040 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR,
2041 '--project', 'foooooooooo'])
2042 self.assertEqual(ret, 0)
2043 self.hooks_mock.assert_called_once_with(
2044 'foooooooooo', proj_dir=constants.CHROMITE_DIR, commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07002045 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05002046
2047 def testBogustProjectNoDir(self):
2048 """Make sure --dir is detected even with --project."""
2049 ret = pre_upload.direct_main(['--project', 'foooooooooo'])
2050 self.assertEqual(ret, 0)
2051 self.hooks_mock.assert_called_once_with(
Mike Frysinger9ba001a2020-11-20 01:02:11 -05002052 'foooooooooo', proj_dir=os.path.dirname(os.path.abspath(__file__)),
2053 commit_list=[], presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05002054
2055 def testNoGitDir(self):
2056 """We should die when run on a non-git dir."""
2057 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
2058 ['--dir', self.tempdir])
2059
2060 def testNoDir(self):
2061 """We should die when run on a missing dir."""
2062 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
2063 ['--dir', os.path.join(self.tempdir, 'foooooooo')])
2064
2065 def testCommitList(self):
2066 """Any args on the command line should be treated as commits."""
2067 commits = ['sha1', 'sha2', 'shaaaaaaaaaaaan']
2068 ret = pre_upload.direct_main(commits)
2069 self.assertEqual(ret, 0)
2070 self.hooks_mock.assert_called_once_with(
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07002071 mock.ANY, proj_dir=mock.ANY, commit_list=commits, presubmit=mock.ANY,
2072 config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05002073
2074
Mike Frysingerb2496652019-09-12 23:35:46 -04002075class CheckRustfmtTest(PreUploadTestCase):
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06002076 """Tests for _check_rustfmt."""
2077
2078 def setUp(self):
2079 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
2080
2081 def testBadRustFile(self):
2082 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
2083 # Bad because it's missing trailing newline.
Mike Frysingere85b0062019-08-20 15:10:33 -04002084 content = 'fn main() {}'
2085 self.content_mock.return_value = content
2086 self.PatchObject(pre_upload, '_run_command', return_value=content + '\n')
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06002087 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
2088 self.assertIsNotNone(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04002089 self.assertEqual('Files not formatted with rustfmt: '
2090 "(run 'cargo fmt' to fix)",
2091 failure.msg)
2092 self.assertEqual(['a.rs'], failure.items)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06002093
2094 def testGoodRustFile(self):
2095 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
Mike Frysingere85b0062019-08-20 15:10:33 -04002096 content = 'fn main() {}\n'
2097 self.content_mock.return_value = content
2098 self.PatchObject(pre_upload, '_run_command', return_value=content)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06002099 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
2100 self.assertIsNone(failure)
2101
2102 def testFilterNonRustFiles(self):
2103 self.PatchObject(pre_upload, '_get_affected_files',
2104 return_value=['a.cc', 'a.rsa', 'a.irs', 'rs.cc'])
2105 self.content_mock.return_value = 'fn main() {\n}'
2106 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
2107 self.assertIsNone(failure)
2108
2109
Keiichi Watanabe6ab73fd2020-07-14 23:42:02 +09002110class GetCargoClippyParserTest(cros_test_lib.TestCase):
2111 """Tests for _get_cargo_clippy_parser."""
2112
2113 def testSingleProject(self):
2114 parser = pre_upload._get_cargo_clippy_parser()
2115 args = parser.parse_args(['--project', 'foo'])
2116 self.assertEqual(args.project,
2117 [pre_upload.ClippyProject(root='foo', script=None)])
2118
2119 def testMultipleProjects(self):
2120 parser = pre_upload._get_cargo_clippy_parser()
2121 args = parser.parse_args(['--project', 'foo:bar',
2122 '--project', 'baz'])
2123 self.assertCountEqual(args.project,
2124 [pre_upload.ClippyProject(root='foo', script='bar'),
2125 pre_upload.ClippyProject(root='baz', script=None)])
2126
2127
2128class CheckCargoClippyTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
2129 """Tests for _check_cargo_clippy."""
2130
2131 def setUp(self):
2132 self.project = pre_upload.Project(name='PROJECT', dir=self.tempdir,
2133 remote=None)
2134
2135 def testClippy(self):
2136 """Verify clippy is called when a monitored file was changed."""
2137 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
2138
2139 self.PatchObject(pre_upload, '_get_affected_files',
2140 return_value=[f'{self.project.dir}/repo_a/a.rs'])
2141
2142 ret = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
2143 options=['--project=repo_a',
2144 '--project=repo_b:foo'])
2145 self.assertFalse(ret)
2146
2147 # Check if `cargo clippy` ran.
2148 called = False
2149 for args, _ in rc_mock.call_args_list:
2150 cmd = args[0]
2151 if len(cmd) > 1 and cmd[0] == 'cargo' and cmd[1] == 'clippy':
2152 called = True
2153 break
2154
2155 self.assertTrue(called)
2156
2157 def testDontRun(self):
2158 """Skip checks when no monitored files are modified."""
2159 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
2160
2161 # A file under `repo_a` was monitored.
2162 self.PatchObject(pre_upload, '_get_affected_files',
2163 return_value=[f'{self.project.dir}/repo_a/a.rs'])
2164 # But, we only care about files under `repo_b`.
2165 errs = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
2166 options=['--project=repo_b:foo'])
2167
2168 self.assertFalse(errs)
2169
2170 rc_mock.assert_not_called()
2171
2172 def testCustomScript(self):
2173 """Verify project-specific script is used."""
2174 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
2175
2176 self.PatchObject(pre_upload, '_get_affected_files',
2177 return_value=[f'{self.project.dir}/repo_b/b.rs'])
2178
2179 errs = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
2180 options=['--project=repo_a',
2181 '--project=repo_b:foo'])
2182 self.assertFalse(errs)
2183
2184 # Check if the script `foo` ran.
2185 called = False
2186 for args, _ in rc_mock.call_args_list:
2187 cmd = args[0]
2188 if len(cmd) > 0 and cmd[0] == os.path.join(self.project.dir, 'foo'):
2189 called = True
2190 break
2191
2192 self.assertTrue(called)
2193
2194
Mike Frysinger180ecd62020-08-19 00:41:51 -04002195class OverrideHooksProcessing(PreUploadTestCase):
2196 """Verify _get_override_hooks processing."""
2197
2198 @staticmethod
2199 def parse(data):
2200 """Helper to create a config & parse it."""
2201 cfg = configparser.ConfigParser()
2202 cfg.read_string(data)
2203 return pre_upload._get_override_hooks(cfg)
2204
2205 def testHooks(self):
2206 """Verify we reject unknown hook names (e.g. typos)."""
2207 with self.assertRaises(ValueError) as e:
2208 self.parse("""
2209[Hook Overrides]
2210foobar: true
2211""")
2212 self.assertIn('foobar', str(e.exception))
2213
2214 def testImplicitDisable(self):
2215 """Verify non-common hooks aren't enabled by default."""
2216 enabled, _ = self.parse('')
2217 self.assertNotIn(pre_upload._run_checkpatch, enabled)
2218
2219 def testExplicitDisable(self):
2220 """Verify hooks disabled are disabled."""
2221 _, disabled = self.parse("""
2222[Hook Overrides]
2223tab_check: false
2224""")
2225 self.assertIn(pre_upload._check_no_tabs, disabled)
2226
2227 def testExplicitEnable(self):
2228 """Verify hooks enabled are enabled."""
2229 enabled, _ = self.parse("""
2230[Hook Overrides]
2231tab_check: true
2232""")
2233 self.assertIn(pre_upload._check_no_tabs, enabled)
2234
2235 def testOptions(self):
2236 """Verify hook options are loaded."""
2237 enabled, _ = self.parse("""
2238[Hook Overrides Options]
2239keyword_check: --kw
2240""")
2241 for func in enabled:
2242 if func.__name__ == 'keyword_check':
2243 self.assertIn('options', func.keywords)
2244 self.assertEqual(func.keywords['options'], ['--kw'])
2245 break
2246 else:
2247 self.fail('could not find "keyword_check" enabled hook')
2248
2249 def testSignOffField(self):
2250 """Verify signoff field handling."""
2251 # Enforce no s-o-b by default.
2252 enabled, disabled = self.parse('')
2253 self.assertIn(pre_upload._check_change_has_no_signoff_field, enabled)
2254 self.assertNotIn(pre_upload._check_change_has_signoff_field, enabled)
2255 self.assertNotIn(pre_upload._check_change_has_signoff_field, disabled)
2256
2257 # If disabled, don't enforce either policy.
2258 enabled, disabled = self.parse("""
2259[Hook Overrides]
2260signoff_check: false
2261""")
2262 self.assertNotIn(pre_upload._check_change_has_no_signoff_field, enabled)
2263 self.assertNotIn(pre_upload._check_change_has_signoff_field, enabled)
2264 self.assertIn(pre_upload._check_change_has_signoff_field, disabled)
2265
2266 # If enabled, require s-o-b.
2267 enabled, disabled = self.parse("""
2268[Hook Overrides]
2269signoff_check: true
2270""")
2271 self.assertNotIn(pre_upload._check_change_has_no_signoff_field, enabled)
2272 self.assertIn(pre_upload._check_change_has_signoff_field, enabled)
2273 self.assertNotIn(pre_upload._check_change_has_signoff_field, disabled)
2274
2275 def testBranchField(self):
2276 """Verify branch field enabling."""
2277 # Should be disabled by default.
2278 enabled, disabled = self.parse('')
2279 self.assertIn(pre_upload._check_change_has_no_branch_field, enabled)
2280 self.assertNotIn(pre_upload._check_change_has_branch_field, enabled)
2281 self.assertNotIn(pre_upload._check_change_has_branch_field, disabled)
2282
2283 # Should be disabled if requested.
2284 enabled, disabled = self.parse("""
2285[Hook Overrides]
2286branch_check: false
2287""")
2288 self.assertIn(pre_upload._check_change_has_no_branch_field, enabled)
2289 self.assertNotIn(pre_upload._check_change_has_branch_field, enabled)
2290 self.assertIn(pre_upload._check_change_has_branch_field, disabled)
2291
2292 # Should be enabled if requested.
2293 enabled, disabled = self.parse("""
2294[Hook Overrides]
2295branch_check: true
2296""")
2297 self.assertNotIn(pre_upload._check_change_has_no_branch_field, enabled)
2298 self.assertIn(pre_upload._check_change_has_branch_field, enabled)
2299 self.assertNotIn(pre_upload._check_change_has_branch_field, disabled)
2300
2301
Tom Hughes1ed799d2020-09-25 14:37:28 -07002302class ProjectHooksProcessing(PreUploadTestCase, cros_test_lib.TempDirTestCase):
2303 """Verify _get_project_hooks processing."""
2304
2305 def parse(self, data):
2306 """Helper to write config and parse it."""
2307 filename = os.path.join(self.tempdir, 'config')
2308 osutils.WriteFile(filename, data)
Mike Frysingerff916c62020-12-18 01:58:08 -05002309 return pre_upload._get_project_hooks(presubmit=True, config_file=filename)
Tom Hughes1ed799d2020-09-25 14:37:28 -07002310
2311 def testClangFormatCheckDefault(self):
2312 """Verify clang-format check disabled by default."""
2313 hooks = self.parse('')
2314 for func in hooks:
2315 self.assertNotEqual(func.__name__, '_check_clang_format')
2316 self.assertNotEqual(func.__name__, 'clang_format_check')
2317
2318 def testClangFormatCheckDisabled(self):
2319 """Verify clang-format check disabled when requested."""
2320 hooks = self.parse("""
2321[Hook Overrides]
2322clang_format_check: false
2323""")
2324 for func in hooks:
2325 self.assertNotEqual(func.__name__, '_check_clang_format')
2326 self.assertNotEqual(func.__name__, 'clang_format_check')
2327
2328 def testClangFormatCheckEnabled(self):
2329 """Verify clang-format check enabled when requested."""
2330 hooks = self.parse("""
2331[Hook Overrides]
2332clang_format_check: true
2333""")
2334 for func in hooks:
2335 if func.__name__ == '_check_clang_format':
2336 self.assertFalse(hasattr(func, 'keywords'))
2337 break
2338 else:
2339 self.fail('could not find "_check_clang_format" enabled hook')
2340
2341 def testClangFormatCheckEnabledWithOptions(self):
2342 """Verify clang-format check has options when provided."""
2343 hooks = self.parse("""
2344[Hook Overrides]
2345clang_format_check: true
2346
2347[Hook Overrides Options]
2348clang_format_check:
2349 some_dir/
2350""")
2351 for func in hooks:
2352 if func.__name__ == 'clang_format_check':
2353 self.assertIn('options', func.keywords)
2354 self.assertEqual(func.keywords['options'], ['some_dir/'])
2355 break
2356 else:
2357 self.fail('could not find "clang_format_check" enabled hook')
2358
2359
Jon Salz98255932012-08-18 14:48:02 +08002360if __name__ == '__main__':
Mike Frysinger884a8dd2015-05-17 03:43:43 -04002361 cros_test_lib.main(module=__name__)