blob: 4a98523ac34f1322ec3acb1a1ac22d16a6ce38db [file] [log] [blame]
Mike Frysinger4f994402019-09-13 17:40:45 -04001#!/usr/bin/env python3
Jon Salz98255932012-08-18 14:48:02 +08002# -*- coding: utf-8 -*-
3# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
Mike Frysingerae409522014-02-01 03:16:11 -05007"""Unittests for pre-upload.py."""
8
Mike Frysinger09d6a3d2013-10-08 22:21:03 -04009from __future__ import print_function
10
Mike Frysinger180ecd62020-08-19 00:41:51 -040011import configparser
Keigo Oka7e880ac2019-07-03 15:03:43 +090012import datetime
David Jamesc3b68b32013-04-03 09:17:03 -070013import os
14import sys
Mike Frysingerf6a29772020-08-22 03:57:08 -040015from unittest import mock
Mike Frysingerfd481ce2019-09-13 18:14:48 -040016
Mike Frysingerbf8b91c2014-02-01 02:50:27 -050017import errors
18
Jon Salz98255932012-08-18 14:48:02 +080019# pylint: disable=W0212
Mike Frysinger65d93c12014-02-01 02:59:46 -050020# We access private members of the pre_upload module all over the place.
21
Mike Frysinger55f85b52014-12-18 14:45:21 -050022# Make sure we can find the chromite paths.
23sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)),
24 '..', '..'))
Jon Salz98255932012-08-18 14:48:02 +080025
Mike Frysingerfd481ce2019-09-13 18:14:48 -040026# The sys.path monkey patching confuses the linter.
27# pylint: disable=wrong-import-position
Mike Frysinger71e643e2019-09-13 17:26:39 -040028from chromite.lib import constants
29from chromite.lib import cros_build_lib
Mike Frysinger65d93c12014-02-01 02:59:46 -050030from chromite.lib import cros_test_lib
Mike Frysingerd3bd32c2014-11-24 23:34:29 -050031from chromite.lib import git
Daniel Erata350fd32014-09-29 14:02:34 -070032from chromite.lib import osutils
Mike Frysinger65d93c12014-02-01 02:59:46 -050033
34
Mike Frysingerff4768e2020-02-27 18:48:13 -050035assert sys.version_info >= (3, 6), 'This module requires Python 3.6+'
36
37
Jon Salz98255932012-08-18 14:48:02 +080038pre_upload = __import__('pre-upload')
39
40
Alex Deymo643ac4c2015-09-03 10:40:50 -070041def ProjectNamed(project_name):
42 """Wrapper to create a Project with just the name"""
43 return pre_upload.Project(project_name, None, None)
44
45
Mike Frysingerb2496652019-09-12 23:35:46 -040046class PreUploadTestCase(cros_test_lib.MockTestCase):
47 """Common test case base."""
48
49 def setUp(self):
50 pre_upload.CACHE.clear()
51
52
53class TryUTF8DecodeTest(PreUploadTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -050054 """Verify we sanely handle unicode content."""
55
Mike Frysinger71e643e2019-09-13 17:26:39 -040056 def setUp(self):
Mike Frysinger7bb709f2019-09-29 23:20:12 -040057 self.rc_mock = self.PatchObject(cros_build_lib, 'run')
Mike Frysinger71e643e2019-09-13 17:26:39 -040058
59 def _run(self, content):
60 """Helper for round tripping through _run_command."""
61 self.rc_mock.return_value = cros_build_lib.CommandResult(
62 output=content, returncode=0)
63 return pre_upload._run_command([])
64
65 def testEmpty(self):
66 """Check empty output."""
67 ret = self._run(b'')
68 self.assertEqual('', ret)
69
70 if sys.version_info.major < 3:
71 ret = self._run('')
72 self.assertEqual(u'', ret)
73
74 def testAscii(self):
75 """Check ascii output."""
76 ret = self._run(b'abc')
77 self.assertEqual('abc', ret)
78
79 if sys.version_info.major < 3:
80 ret = self._run('abc')
81 self.assertEqual(u'abc', ret)
82
83 def testUtf8(self):
84 """Check valid UTF-8 output."""
85 text = u'你好布萊恩'
86 ret = self._run(text.encode('utf-8'))
87 self.assertEqual(text, ret)
88
89 def testBinary(self):
90 """Check binary (invalid UTF-8) output."""
91 ret = self._run(b'hi \x80 there')
Mike Frysinger8a4e8942019-09-16 23:43:49 -040092 self.assertEqual(u'hi \ufffd there', ret)
Jon Salz98255932012-08-18 14:48:02 +080093
94
Daisuke Nojiri2089e012020-08-20 15:12:36 -070095class CheckKeywordsTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Bernie Thompson8e26f742020-07-23 14:32:31 -070096 """Tests for _check_keywords."""
97
98 def setUp(self):
99 self.PatchObject(pre_upload, '_get_affected_files',
100 return_value=['x.ebuild'])
101 self.PatchObject(pre_upload, '_filter_files', return_value=['x.ebuild'])
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700102 # First call for blocked_terms.txt and second call for unblocked_terms.txt.
103 self.rf_mock = self.PatchObject(
104 osutils, 'ReadFile',
105 side_effect=['scruffy\nmangy\ndog.?pile\ncat.?circle', 'fox'])
Bernie Thompson8e26f742020-07-23 14:32:31 -0700106 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
107 self.desc_mock = self.PatchObject(pre_upload, '_get_commit_desc')
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700108 self.project = pre_upload.Project(name='PROJECT', dir=self.tempdir,
109 remote=None)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700110
111 def test_good_cases(self):
112 self.desc_mock.return_value = 'Commit Message.\nLine 2'
113 self.diff_mock.return_value = [
114 (1, 'Some text without keywords.'),
115 (2, 'The dog is black has a partial keyword that does not count.'),
116 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700117 failures = pre_upload._check_keywords(self.project, 'COMMIT')
Bernie Thompson8e26f742020-07-23 14:32:31 -0700118 self.assertEqual(failures, [])
119
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700120 self.rf_mock.assert_has_calls([
121 mock.call(os.path.join(pre_upload._get_hooks_dir(),
122 pre_upload.BLOCKED_TERMS_FILE)),
123 mock.call(os.path.join(pre_upload._get_hooks_dir(),
124 pre_upload.UNBLOCKED_TERMS_FILE)),
125 ])
126
Bernie Thompson8e26f742020-07-23 14:32:31 -0700127 def test_bad_cases(self):
128 self.desc_mock.return_value = 'Commit Message.\nLine 2\nLine 3 scruffy'
129 self.diff_mock.return_value = [
130 (1, 'Scruffy plain catch'),
131 (2, 'dog-pile hyphenated catch'),
132 (3, 'cat_circle underscored catch'),
133 (4, 'dog pile space catch'),
134 (5, 'dogpiled substring catch'),
135 (6, 'scruffy mangy dog, multiple in a line catch'),
136 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700137 failures = pre_upload._check_keywords(self.project, 'COMMIT')
Bernie Thompson8e26f742020-07-23 14:32:31 -0700138 self.assertNotEqual(failures, [])
139 self.assertEqual('Found a blocked keyword in:', failures[0].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700140 self.assertEqual(
141 ['x.ebuild, line 1: Matched "Scruffy" with regex of "scruffy"',
142 'x.ebuild, line 2: Matched "dog-pile" with regex of "dog.?pile"',
143 'x.ebuild, line 3: Matched "cat_circle" with regex of "cat.?circle"',
144 'x.ebuild, line 4: Matched "dog pile" with regex of "dog.?pile"',
145 'x.ebuild, line 5: Matched "dogpile" with regex of "dog.?pile"',
146 'x.ebuild, line 6: Matched "mangy" with regex of "mangy"'],
147 failures[0].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700148 self.assertEqual('Found a blocked keyword in:', failures[1].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700149 self.assertEqual(
150 ['Commit message, line 3: Matched "scruffy" with regex of "scruffy"'],
151 failures[1].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700152
153 def test_block_option_cases(self):
154 self.desc_mock.return_value = 'Commit Message.\nLine 2 voldemort'
155 self.diff_mock.return_value = [
156 (1, 'Line with a new term voldemort.'),
157 (2, 'Line with only they who shall not be named.'),
158 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700159 failures = pre_upload._check_keywords(self.project,
Bernie Thompson8e26f742020-07-23 14:32:31 -0700160 'COMMIT', ['--block', 'voldemort'])
161 self.assertNotEqual(failures, [])
162 self.assertEqual('Found a blocked keyword in:', failures[0].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700163 self.assertEqual(
164 ['x.ebuild, line 1: Matched "voldemort" with regex of "voldemort"'],
165 failures[0].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700166 self.assertEqual('Found a blocked keyword in:', failures[1].msg)
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700167 self.assertEqual(
168 ['Commit message, line 2: '
169 'Matched "voldemort" with regex of "voldemort"'], failures[1].items)
Bernie Thompson8e26f742020-07-23 14:32:31 -0700170
171 def test_unblock_option_cases(self):
172 self.desc_mock.return_value = 'Commit message with scruffy'
173 self.diff_mock.return_value = [
Bernie Thompson4e362922020-09-02 16:17:50 -0700174 (1, 'Line with two unblocked terms scruffy big dog-pile'),
175 (2, 'Line with without any blocked terms'),
176 ]
Daisuke Nojiri2089e012020-08-20 15:12:36 -0700177 # scruffy matches regex of 'scruffy' in block list but excluded by
178 # different regex of 'scru.?fy' in unblock list.
179 failures = pre_upload._check_keywords(self.project,
Bernie Thompson4e362922020-09-02 16:17:50 -0700180 'COMMIT', ['--unblock', 'dog.?pile',
181 '--unblock', 'scru.?fy'])
182 self.assertEqual(failures, [])
Bernie Thompson8e26f742020-07-23 14:32:31 -0700183
Laurent Chavey434af9a2020-09-28 22:25:16 +0900184 def test_unblock_and_block_option_cases(self):
185 self.desc_mock.return_value = 'Commit message with scruffy'
186 self.diff_mock.return_value = [
187 (1, 'Two unblocked terms scruffy and dog-pile'),
188 (2, 'Without any blocked terms'),
189 (3, 'Blocked dogpile'),
190 (4, 'Unblocked m.dogpile'),
191 (5, 'Blocked dogpile and unblocked m.dogpile'),
192 (6, 'Unlocked m.dogpile and blocked dogpile'),
193 (7, 'Unlocked m.dogpile and unblocked dog-pile'),
194 ]
195 # scruffy matches regex of 'scruffy' in block list but excluded by
196 # a different regex of 'scru.?fy' in unblock list.
197 # dogpile, dog.pile matches regex of 'dog.?pile' in block list.
198 # m.dogpile and dog-pile matches regex of 'dog.?pile' in block list but
199 # excluded by different regex '\.dog.?pile' and 'dog-pile' in unblock list.
200 failures = pre_upload._check_keywords(self.project,
201 'COMMIT',
202 ['--unblock', r'dog-pile',
203 '--unblock', r'scru.?fy',
204 '--unblock', r'\.dog.?pile'])
205 self.assertNotEqual(failures, [])
206 self.assertEqual('Found a blocked keyword in:', failures[0].msg)
207 self.assertEqual(
208 [r'x.ebuild, line 3: Matched "dogpile" with regex of "dog.?pile"',
209 r'x.ebuild, line 5: Matched "dogpile" with regex of "dog.?pile"',
210 r'x.ebuild, line 6: Matched "dogpile" with regex of "dog.?pile"'],
211 failures[0].items)
212
Bernie Thompson8e26f742020-07-23 14:32:31 -0700213
Mike Frysingerb2496652019-09-12 23:35:46 -0400214class CheckNoLongLinesTest(PreUploadTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -0500215 """Tests for _check_no_long_lines."""
216
Jon Salz98255932012-08-18 14:48:02 +0800217 def setUp(self):
Mike Frysinger1459d362014-12-06 13:53:23 -0500218 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
Jon Salz98255932012-08-18 14:48:02 +0800219
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900220 def testCheck(self):
Mike Frysingerf8961942020-05-15 00:36:31 -0400221 path = 'x.cc'
222 self.PatchObject(pre_upload, '_get_affected_files', return_value=[path])
Mike Frysinger1459d362014-12-06 13:53:23 -0500223 self.diff_mock.return_value = [
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400224 (1, u'x' * 80), # OK
225 (2, '\x80' * 80), # OK
226 (3, u'x' * 81), # Too long
227 (4, '\x80' * 81), # Too long
228 (5, u'See http://' + (u'x' * 80)), # OK (URL)
229 (6, u'See https://' + (u'x' * 80)), # OK (URL)
230 (7, u'# define ' + (u'x' * 80)), # OK (compiler directive)
231 (8, u'#define' + (u'x' * 74)), # Too long
Mike Frysinger1459d362014-12-06 13:53:23 -0500232 ]
Alex Deymo643ac4c2015-09-03 10:40:50 -0700233 failure = pre_upload._check_no_long_lines(ProjectNamed('PROJECT'), 'COMMIT')
Jon Salz98255932012-08-18 14:48:02 +0800234 self.assertTrue(failure)
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900235 self.assertEqual('Found lines longer than the limit (first 5 shown):',
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400236 failure.msg)
Mike Frysingerf8961942020-05-15 00:36:31 -0400237 self.assertEqual([path + ', line %d, 81 chars, over 80 chars' %
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900238 x for x in [3, 4, 8]],
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400239 failure.items)
Jon Salz98255932012-08-18 14:48:02 +0800240
George Burgess IVf9f79eb2019-07-09 20:12:55 -0700241 def testCheckTreatsOwnersFilesSpecially(self):
242 affected_files = self.PatchObject(pre_upload, '_get_affected_files')
243
244 mock_files = (
245 ('foo-OWNERS', False),
246 ('OWNERS', True),
247 ('/path/to/OWNERS', True),
248 ('/path/to/OWNERS.foo', True),
249 )
250
251 mock_lines = (
252 (u'x' * 81, False),
253 (u'foo file:' + u'x' * 80, True),
254 (u'include ' + u'x' * 80, True),
255 )
256 assert all(len(line) > 80 for line, _ in mock_lines)
257
258 for file_name, is_owners in mock_files:
259 affected_files.return_value = [file_name]
260 for line, is_ok in mock_lines:
261 self.diff_mock.return_value = [(1, line)]
262 failure = pre_upload._check_no_long_lines(ProjectNamed('PROJECT'),
263 'COMMIT')
264
265 assert_msg = 'file: %r; line: %r' % (file_name, line)
266 if is_owners and is_ok:
267 self.assertFalse(failure, assert_msg)
268 else:
269 self.assertTrue(failure, assert_msg)
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900270 self.assertIn('Found lines longer than the limit', failure.msg,
George Burgess IVf9f79eb2019-07-09 20:12:55 -0700271 assert_msg)
272
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900273 def testIncludeOptions(self):
274 self.PatchObject(pre_upload,
275 '_get_affected_files',
276 return_value=['foo.txt'])
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400277 self.diff_mock.return_value = [(1, u'x' * 81)]
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900278 self.assertFalse(pre_upload._check_no_long_lines(
279 ProjectNamed('PROJECT'), 'COMMIT'))
280 self.assertTrue(pre_upload._check_no_long_lines(
281 ProjectNamed('PROJECT'), 'COMMIT', options=['--include_regex=foo']))
282
283 def testExcludeOptions(self):
284 self.PatchObject(pre_upload,
285 '_get_affected_files',
Mike Frysingerf8961942020-05-15 00:36:31 -0400286 return_value=['foo.cc'])
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400287 self.diff_mock.return_value = [(1, u'x' * 81)]
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900288 self.assertTrue(pre_upload._check_no_long_lines(
289 ProjectNamed('PROJECT'), 'COMMIT'))
290 self.assertFalse(pre_upload._check_no_long_lines(
291 ProjectNamed('PROJECT'), 'COMMIT', options=['--exclude_regex=foo']))
292
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900293 def testSpecialLineLength(self):
294 mock_lines = (
295 (u'x' * 101, True),
296 (u'x' * 100, False),
297 (u'x' * 81, False),
298 (u'x' * 80, False),
299 )
300 self.PatchObject(pre_upload,
301 '_get_affected_files',
302 return_value=['foo.java'])
303 for line, is_ok in mock_lines:
304 self.diff_mock.return_value = [(1, line)]
305 if is_ok:
306 self.assertTrue(pre_upload._check_no_long_lines(
307 ProjectNamed('PROJECT'), 'COMMIT'))
308 else:
309 self.assertFalse(pre_upload._check_no_long_lines(
310 ProjectNamed('PROJECT'), 'COMMIT'))
311
Mike Frysingerae409522014-02-01 03:16:11 -0500312
Mike Frysingerb2496652019-09-12 23:35:46 -0400313class CheckTabbedIndentsTest(PreUploadTestCase):
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800314 """Tests for _check_tabbed_indents."""
Mike Frysingerb2496652019-09-12 23:35:46 -0400315
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800316 def setUp(self):
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900317 self.PatchObject(pre_upload,
318 '_get_affected_files',
319 return_value=['x.ebuild'])
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800320 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
321
322 def test_good_cases(self):
323 self.diff_mock.return_value = [
324 (1, u'no_tabs_anywhere'),
325 (2, u' leading_tab_only'),
326 (3, u' leading_tab another_tab'),
327 (4, u' leading_tab trailing_too '),
328 (5, u' leading_tab then_spaces_trailing '),
329 ]
330 failure = pre_upload._check_tabbed_indents(ProjectNamed('PROJECT'),
331 'COMMIT')
332 self.assertIsNone(failure)
333
334 def test_bad_cases(self):
335 self.diff_mock.return_value = [
336 (1, u' leading_space'),
337 (2, u' tab_followed_by_space'),
338 (3, u' space_followed_by_tab'),
339 (4, u' mix_em_up'),
340 (5, u' mix_on_both_sides '),
341 ]
342 failure = pre_upload._check_tabbed_indents(ProjectNamed('PROJECT'),
343 'COMMIT')
344 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400345 self.assertEqual('Found a space in indentation (must be all tabs):',
346 failure.msg)
347 self.assertEqual(['x.ebuild, line %d' % x for x in range(1, 6)],
348 failure.items)
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800349
350
Mike Frysingerb2496652019-09-12 23:35:46 -0400351class CheckProjectPrefix(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Daniel Erata350fd32014-09-29 14:02:34 -0700352 """Tests for _check_project_prefix."""
353
354 def setUp(self):
355 self.orig_cwd = os.getcwd()
356 os.chdir(self.tempdir)
357 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
358 self.desc_mock = self.PatchObject(pre_upload, '_get_commit_desc')
359
360 def tearDown(self):
361 os.chdir(self.orig_cwd)
362
363 def _WriteAliasFile(self, filename, project):
364 """Writes a project name to a file, creating directories if needed."""
365 os.makedirs(os.path.dirname(filename))
366 osutils.WriteFile(filename, project)
367
368 def testInvalidPrefix(self):
369 """Report an error when the prefix doesn't match the base directory."""
370 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
371 self.desc_mock.return_value = 'bar: Some commit'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700372 failure = pre_upload._check_project_prefix(ProjectNamed('PROJECT'),
373 'COMMIT')
Daniel Erata350fd32014-09-29 14:02:34 -0700374 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400375 self.assertEqual('The commit title for changes affecting only foo should '
376 'start with "foo: "', failure.msg)
Daniel Erata350fd32014-09-29 14:02:34 -0700377
378 def testValidPrefix(self):
379 """Use a prefix that matches the base directory."""
380 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
381 self.desc_mock.return_value = 'foo: Change some files.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700382 self.assertFalse(
383 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700384
385 def testAliasFile(self):
386 """Use .project_alias to override the project name."""
387 self._WriteAliasFile('foo/.project_alias', 'project')
388 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/bar.cc']
389 self.desc_mock.return_value = 'project: Use an alias.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700390 self.assertFalse(
391 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700392
393 def testAliasFileWithSubdirs(self):
394 """Check that .project_alias is used when only modifying subdirectories."""
395 self._WriteAliasFile('foo/.project_alias', 'project')
396 self.file_mock.return_value = [
397 'foo/subdir/foo.cc',
398 'foo/subdir/bar.cc'
399 'foo/subdir/blah/baz.cc'
400 ]
401 self.desc_mock.return_value = 'project: Alias with subdirs.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700402 self.assertFalse(
403 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700404
405
Mike Frysingerb2496652019-09-12 23:35:46 -0400406class CheckFilePathCharTypeTest(PreUploadTestCase):
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900407 """Tests for _check_filepath_chartype."""
408
409 def setUp(self):
410 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
411
412 def testCheck(self):
413 self.PatchObject(pre_upload, '_get_affected_files', return_value=['x.cc'])
414 self.diff_mock.return_value = [
415 (1, 'base::FilePath'), # OK
416 (2, 'base::FilePath::CharType'), # NG
417 (3, 'base::FilePath::StringType'), # NG
418 (4, 'base::FilePath::StringPieceType'), # NG
Satoru Takabayashi4ca37922018-08-08 10:16:38 +0900419 (5, 'base::FilePath::FromUTF8Unsafe'), # NG
420 (6, 'FilePath::CharType'), # NG
421 (7, 'FilePath::StringType'), # NG
422 (8, 'FilePath::StringPieceType'), # NG
423 (9, 'FilePath::FromUTF8Unsafe'), # NG
424 (10, 'AsUTF8Unsafe'), # NG
425 (11, 'FILE_PATH_LITERAL'), # NG
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900426 ]
427 failure = pre_upload._check_filepath_chartype(ProjectNamed('PROJECT'),
428 'COMMIT')
429 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400430 self.assertEqual(
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900431 'Please assume FilePath::CharType is char (crbug.com/870621):',
432 failure.msg)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400433 self.assertEqual(['x.cc, line 2 has base::FilePath::CharType',
434 'x.cc, line 3 has base::FilePath::StringType',
435 'x.cc, line 4 has base::FilePath::StringPieceType',
436 'x.cc, line 5 has base::FilePath::FromUTF8Unsafe',
437 'x.cc, line 6 has FilePath::CharType',
438 'x.cc, line 7 has FilePath::StringType',
439 'x.cc, line 8 has FilePath::StringPieceType',
440 'x.cc, line 9 has FilePath::FromUTF8Unsafe',
441 'x.cc, line 10 has AsUTF8Unsafe',
442 'x.cc, line 11 has FILE_PATH_LITERAL'],
443 failure.items)
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900444
445
Mike Frysingerb2496652019-09-12 23:35:46 -0400446class CheckKernelConfig(PreUploadTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -0500447 """Tests for _kernel_configcheck."""
448
Mike Frysinger1459d362014-12-06 13:53:23 -0500449 def setUp(self):
450 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
451
452 def testMixedChanges(self):
453 """Mixing of changes should fail."""
454 self.file_mock.return_value = [
455 '/kernel/files/chromeos/config/base.config',
456 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
457 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700458 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
459 self.assertTrue(failure)
460
Mike Frysinger1459d362014-12-06 13:53:23 -0500461 def testCodeOnly(self):
462 """Code-only changes should pass."""
463 self.file_mock.return_value = [
464 '/kernel/files/Makefile',
465 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
466 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700467 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
468 self.assertFalse(failure)
469
Mike Frysinger1459d362014-12-06 13:53:23 -0500470 def testConfigOnlyChanges(self):
471 """Config-only changes should pass."""
472 self.file_mock.return_value = [
473 '/kernel/files/chromeos/config/base.config',
474 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700475 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
476 self.assertFalse(failure)
477
Jon Salz98255932012-08-18 14:48:02 +0800478
Mike Frysingerb2496652019-09-12 23:35:46 -0400479class CheckJson(PreUploadTestCase):
Mike Frysinger908be682018-01-04 02:21:50 -0500480 """Tests for _run_json_check."""
481
482 def setUp(self):
483 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
484 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
485
486 def testNoJson(self):
487 """Nothing should be checked w/no JSON files."""
488 self.file_mock.return_value = [
489 '/foo/bar.txt',
490 '/readme.md',
491 ]
492 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
493 self.assertIsNone(ret)
494
495 def testValidJson(self):
496 """We should accept valid json files."""
497 self.file_mock.return_value = [
498 '/foo/bar.txt',
499 '/data.json',
500 ]
501 self.content_mock.return_value = '{}'
502 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
503 self.assertIsNone(ret)
504 self.content_mock.assert_called_once_with('/data.json', 'COMMIT')
505
506 def testInvalidJson(self):
507 """We should reject invalid json files."""
508 self.file_mock.return_value = [
509 '/foo/bar.txt',
510 '/data.json',
511 ]
512 self.content_mock.return_value = '{'
513 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
514 self.assertIsNotNone(ret)
515 self.content_mock.assert_called_once_with('/data.json', 'COMMIT')
516
517
Mike Frysingerb2496652019-09-12 23:35:46 -0400518class CheckManifests(PreUploadTestCase):
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500519 """Tests _check_manifests."""
520
521 def setUp(self):
522 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
523 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
524
525 def testNoManifests(self):
526 """Nothing should be checked w/no Manifest files."""
527 self.file_mock.return_value = [
528 '/foo/bar.txt',
529 '/readme.md',
530 '/manifest',
531 '/Manifest.txt',
532 ]
533 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
534 self.assertIsNone(ret)
535
536 def testValidManifest(self):
537 """Accept valid Manifest files."""
538 self.file_mock.return_value = [
539 '/foo/bar.txt',
540 '/cat/pkg/Manifest',
541 ]
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400542 self.content_mock.return_value = """# Comment and blank lines.
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500543
544DIST lines
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400545"""
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500546 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
547 self.assertIsNone(ret)
548 self.content_mock.assert_called_once_with('/cat/pkg/Manifest', 'COMMIT')
549
550 def _testReject(self, content):
551 """Make sure |content| is rejected."""
552 self.file_mock.return_value = ('/Manifest',)
553 self.content_mock.reset_mock()
554 self.content_mock.return_value = content
555 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
556 self.assertIsNotNone(ret)
557 self.content_mock.assert_called_once_with('/Manifest', 'COMMIT')
558
559 def testRejectBlank(self):
560 """Reject empty manifests."""
561 self._testReject('')
562
563 def testNoTrailingNewLine(self):
564 """Reject manifests w/out trailing newline."""
565 self._testReject('DIST foo')
566
567 def testNoLeadingBlankLines(self):
568 """Reject manifests w/leading blank lines."""
569 self._testReject('\nDIST foo\n')
570
571 def testNoTrailingBlankLines(self):
572 """Reject manifests w/trailing blank lines."""
573 self._testReject('DIST foo\n\n')
574
575 def testNoLeadingWhitespace(self):
576 """Reject manifests w/lines w/leading spaces."""
577 self._testReject(' DIST foo\n')
578 self._testReject(' # Comment\n')
579
580 def testNoTrailingWhitespace(self):
581 """Reject manifests w/lines w/trailing spaces."""
582 self._testReject('DIST foo \n')
583 self._testReject('# Comment \n')
584 self._testReject(' \n')
585
586 def testOnlyDistLines(self):
587 """Only allow DIST lines in here."""
588 self._testReject('EBUILD foo\n')
589
590
Mike Frysingerb2496652019-09-12 23:35:46 -0400591class CheckPortageMakeUseVar(PreUploadTestCase):
Daniel Erat9d203ff2015-02-17 10:12:21 -0700592 """Tests for _check_portage_make_use_var."""
593
594 def setUp(self):
595 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
596 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
597
598 def testMakeConfOmitsOriginalUseValue(self):
599 """Fail for make.conf that discards the previous value of $USE."""
600 self.file_mock.return_value = ['make.conf']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400601 self.content_mock.return_value = u'USE="foo"\nUSE="${USE} bar"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700602 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
603 self.assertTrue(failure, failure)
604
605 def testMakeConfCorrectUsage(self):
606 """Succeed for make.conf that preserves the previous value of $USE."""
607 self.file_mock.return_value = ['make.conf']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400608 self.content_mock.return_value = u'USE="${USE} foo"\nUSE="${USE}" bar'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700609 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
610 self.assertFalse(failure, failure)
611
612 def testMakeDefaultsReferencesOriginalUseValue(self):
613 """Fail for make.defaults that refers to a not-yet-set $USE value."""
614 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400615 self.content_mock.return_value = u'USE="${USE} foo"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700616 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
617 self.assertTrue(failure, failure)
618
619 # Also check for "$USE" without curly brackets.
Mike Frysinger71e643e2019-09-13 17:26:39 -0400620 self.content_mock.return_value = u'USE="$USE foo"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700621 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
622 self.assertTrue(failure, failure)
623
624 def testMakeDefaultsOverwritesUseValue(self):
625 """Fail for make.defaults that discards its own $USE value."""
626 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400627 self.content_mock.return_value = u'USE="foo"\nUSE="bar"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700628 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
629 self.assertTrue(failure, failure)
630
631 def testMakeDefaultsCorrectUsage(self):
632 """Succeed for make.defaults that sets and preserves $USE."""
633 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400634 self.content_mock.return_value = u'USE="foo"\nUSE="${USE}" bar'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700635 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
636 self.assertFalse(failure, failure)
637
638
Mike Frysingerb2496652019-09-12 23:35:46 -0400639class CheckEbuildEapi(PreUploadTestCase):
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500640 """Tests for _check_ebuild_eapi."""
641
Alex Deymo643ac4c2015-09-03 10:40:50 -0700642 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500643
644 def setUp(self):
645 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
646 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
647 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff',
648 side_effect=Exception())
649
650 def testSkipUpstreamOverlays(self):
651 """Skip ebuilds found in upstream overlays."""
652 self.file_mock.side_effect = Exception()
653 ret = pre_upload._check_ebuild_eapi(self.PORTAGE_STABLE, 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400654 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500655
656 # Make sure our condition above triggers.
657 self.assertRaises(Exception, pre_upload._check_ebuild_eapi, 'o', 'HEAD')
658
659 def testSkipNonEbuilds(self):
660 """Skip non-ebuild files."""
661 self.content_mock.side_effect = Exception()
662
663 self.file_mock.return_value = ['some-file', 'ebuild/dir', 'an.ebuild~']
Alex Deymo643ac4c2015-09-03 10:40:50 -0700664 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400665 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500666
667 # Make sure our condition above triggers.
668 self.file_mock.return_value.append('a/real.ebuild')
Alex Deymo643ac4c2015-09-03 10:40:50 -0700669 self.assertRaises(Exception, pre_upload._check_ebuild_eapi,
670 ProjectNamed('o'), 'HEAD')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500671
672 def testSkipSymlink(self):
673 """Skip files that are just symlinks."""
674 self.file_mock.return_value = ['a-r1.ebuild']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400675 self.content_mock.return_value = u'a.ebuild'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700676 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400677 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500678
679 def testRejectEapiImplicit0Content(self):
680 """Reject ebuilds that do not declare EAPI (so it's 0)."""
681 self.file_mock.return_value = ['a.ebuild']
682
Mike Frysinger71e643e2019-09-13 17:26:39 -0400683 self.content_mock.return_value = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500684IUSE="foo"
685src_compile() { }
686"""
Alex Deymo643ac4c2015-09-03 10:40:50 -0700687 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500688 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500689
690 def testRejectExplicitEapi1Content(self):
691 """Reject ebuilds that do declare old EAPI explicitly."""
692 self.file_mock.return_value = ['a.ebuild']
693
Mike Frysinger71e643e2019-09-13 17:26:39 -0400694 template = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500695EAPI=%s
696IUSE="foo"
697src_compile() { }
698"""
699 # Make sure we only check the first EAPI= setting.
Mike Frysinger948284a2018-02-01 15:22:56 -0500700 self.content_mock.return_value = template % '1\nEAPI=60'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700701 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500702 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500703
704 # Verify we handle double quotes too.
705 self.content_mock.return_value = template % '"1"'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700706 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500707 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500708
709 # Verify we handle single quotes too.
710 self.content_mock.return_value = template % "'1'"
Alex Deymo643ac4c2015-09-03 10:40:50 -0700711 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500712 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500713
Mike Frysinger948284a2018-02-01 15:22:56 -0500714 def testAcceptExplicitNewEapiContent(self):
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500715 """Accept ebuilds that do declare new EAPI explicitly."""
716 self.file_mock.return_value = ['a.ebuild']
717
Mike Frysinger71e643e2019-09-13 17:26:39 -0400718 template = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500719EAPI=%s
720IUSE="foo"
721src_compile() { }
722"""
723 # Make sure we only check the first EAPI= setting.
Mike Frysinger948284a2018-02-01 15:22:56 -0500724 self.content_mock.return_value = template % '6\nEAPI=1'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700725 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400726 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500727
728 # Verify we handle double quotes too.
729 self.content_mock.return_value = template % '"5"'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700730 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400731 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500732
733 # Verify we handle single quotes too.
734 self.content_mock.return_value = template % "'5-hdepend'"
Alex Deymo643ac4c2015-09-03 10:40:50 -0700735 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400736 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500737
738
Mike Frysingerb2496652019-09-12 23:35:46 -0400739class CheckEbuildKeywords(PreUploadTestCase):
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400740 """Tests for _check_ebuild_keywords."""
741
742 def setUp(self):
743 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
744 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
745
746 def testNoEbuilds(self):
747 """If no ebuilds are found, do not scan."""
748 self.file_mock.return_value = ['a.file', 'ebuild-is-not.foo']
749
Alex Deymo643ac4c2015-09-03 10:40:50 -0700750 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400751 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400752
753 self.assertEqual(self.content_mock.call_count, 0)
754
755 def testSomeEbuilds(self):
756 """If ebuilds are found, only scan them."""
757 self.file_mock.return_value = ['a.file', 'blah', 'foo.ebuild', 'cow']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400758 self.content_mock.return_value = u''
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400759
Alex Deymo643ac4c2015-09-03 10:40:50 -0700760 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400761 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400762
763 self.assertEqual(self.content_mock.call_count, 1)
764
765 def _CheckContent(self, content, fails):
766 """Test helper for inputs/outputs.
767
768 Args:
769 content: The ebuild content to test.
770 fails: Whether |content| should trigger a hook failure.
771 """
772 self.file_mock.return_value = ['a.ebuild']
773 self.content_mock.return_value = content
774
Alex Deymo643ac4c2015-09-03 10:40:50 -0700775 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400776 if fails:
Mike Frysingerb81102f2014-11-21 00:33:35 -0500777 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400778 else:
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400779 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400780
781 self.assertEqual(self.content_mock.call_count, 1)
782
783 def testEmpty(self):
784 """Check KEYWORDS= is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400785 self._CheckContent(u'# HEADER\nKEYWORDS=\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400786
787 def testEmptyQuotes(self):
788 """Check KEYWORDS="" is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400789 self._CheckContent(u'# HEADER\nKEYWORDS=" "\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400790
791 def testStableGlob(self):
792 """Check KEYWORDS=* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400793 self._CheckContent(u'# HEADER\nKEYWORDS="\t*\t"\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400794
795 def testUnstableGlob(self):
796 """Check KEYWORDS=~* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400797 self._CheckContent(u'# HEADER\nKEYWORDS="~* "\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400798
799 def testRestrictedGlob(self):
800 """Check KEYWORDS=-* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400801 self._CheckContent(u'# HEADER\nKEYWORDS="\t-* arm"\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400802
803 def testMissingGlobs(self):
804 """Reject KEYWORDS missing any globs."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400805 self._CheckContent(u'# HEADER\nKEYWORDS="~arm x86"\nblah\n', True)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400806
807
Mike Frysingerb2496652019-09-12 23:35:46 -0400808class CheckEbuildVirtualPv(PreUploadTestCase):
Mike Frysingercd363c82014-02-01 05:20:18 -0500809 """Tests for _check_ebuild_virtual_pv."""
810
Alex Deymo643ac4c2015-09-03 10:40:50 -0700811 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
812 CHROMIUMOS_OVERLAY = ProjectNamed('chromiumos/overlays/chromiumos')
813 BOARD_OVERLAY = ProjectNamed('chromiumos/overlays/board-overlays')
814 PRIVATE_OVERLAY = ProjectNamed('chromeos/overlays/overlay-link-private')
815 PRIVATE_VARIANT_OVERLAY = ProjectNamed('chromeos/overlays/'
816 'overlay-variant-daisy-spring-private')
Mike Frysingercd363c82014-02-01 05:20:18 -0500817
818 def setUp(self):
819 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
820
821 def testNoVirtuals(self):
822 """Skip non virtual packages."""
823 self.file_mock.return_value = ['some/package/package-3.ebuild']
Alex Deymo643ac4c2015-09-03 10:40:50 -0700824 ret = pre_upload._check_ebuild_virtual_pv(ProjectNamed('overlay'), 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400825 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500826
827 def testCommonVirtuals(self):
828 """Non-board overlays should use PV=1."""
829 template = 'virtual/foo/foo-%s.ebuild'
830 self.file_mock.return_value = [template % '1']
831 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400832 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500833
834 self.file_mock.return_value = [template % '2']
835 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500836 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500837
838 def testPublicBoardVirtuals(self):
839 """Public board overlays should use PV=2."""
840 template = 'overlay-lumpy/virtual/foo/foo-%s.ebuild'
841 self.file_mock.return_value = [template % '2']
842 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400843 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500844
845 self.file_mock.return_value = [template % '2.5']
846 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500847 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500848
849 def testPublicBoardVariantVirtuals(self):
850 """Public board variant overlays should use PV=2.5."""
851 template = 'overlay-variant-lumpy-foo/virtual/foo/foo-%s.ebuild'
852 self.file_mock.return_value = [template % '2.5']
853 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400854 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500855
856 self.file_mock.return_value = [template % '3']
857 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500858 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500859
860 def testPrivateBoardVirtuals(self):
861 """Private board overlays should use PV=3."""
862 template = 'virtual/foo/foo-%s.ebuild'
863 self.file_mock.return_value = [template % '3']
864 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400865 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500866
867 self.file_mock.return_value = [template % '3.5']
868 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500869 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500870
871 def testPrivateBoardVariantVirtuals(self):
872 """Private board variant overlays should use PV=3.5."""
873 template = 'virtual/foo/foo-%s.ebuild'
874 self.file_mock.return_value = [template % '3.5']
875 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400876 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500877
Bernie Thompsone5ee1822016-01-12 14:22:23 -0800878 def testSpecialVirtuals(self):
879 """Some cases require deeper versioning and can be >= 4."""
880 template = 'virtual/foo/foo-%s.ebuild'
Mike Frysingercd363c82014-02-01 05:20:18 -0500881 self.file_mock.return_value = [template % '4']
882 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400883 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500884
Bernie Thompsone5ee1822016-01-12 14:22:23 -0800885 self.file_mock.return_value = [template % '4.5']
886 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400887 self.assertIsNone(ret)
Mike Frysinger98638102014-08-28 00:15:08 -0400888
Mike Frysingerb2496652019-09-12 23:35:46 -0400889class CheckCrosLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -0700890 """Tests for _check_cros_license."""
Mike Frysinger98638102014-08-28 00:15:08 -0400891
892 def setUp(self):
893 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
894 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
895
896 def testOldHeaders(self):
897 """Accept old header styles."""
898 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400899 (u'#!/bin/sh\n'
900 u'# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.\n'
901 u'# Use of this source code is governed by a BSD-style license that'
902 u' can be\n'
903 u'# found in the LICENSE file.\n'),
904 (u'// Copyright 2010-2013 The Chromium OS Authors. All rights reserved.'
905 u'\n// Use of this source code is governed by a BSD-style license that'
906 u' can be\n'
907 u'// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -0400908 )
909 self.file_mock.return_value = ['file']
910 for header in HEADERS:
911 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +0900912 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
913
914 def testNewFileYear(self):
915 """Added files should have the current year in license header."""
916 year = datetime.datetime.now().year
917 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400918 (u'// Copyright 2016 The Chromium OS Authors. All rights reserved.\n'
919 u'// Use of this source code is governed by a BSD-style license that'
920 u' can be\n'
921 u'// found in the LICENSE file.\n'),
922 (u'// Copyright %d The Chromium OS Authors. All rights reserved.\n'
923 u'// Use of this source code is governed by a BSD-style license that'
924 u' can be\n'
925 u'// found in the LICENSE file.\n') % year,
Keigo Oka7e880ac2019-07-03 15:03:43 +0900926 )
927 want_error = (True, False)
928 def fake_get_affected_files(_, relative, include_adds=True):
929 _ = relative
930 if include_adds:
931 return ['file']
932 else:
933 return []
934
935 self.file_mock.side_effect = fake_get_affected_files
936 for i, header in enumerate(HEADERS):
937 self.content_mock.return_value = header
938 if want_error[i]:
939 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
940 else:
941 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -0400942
943 def testRejectC(self):
944 """Reject the (c) in newer headers."""
945 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400946 (u'// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.'
947 u'\n'
948 u'// Use of this source code is governed by a BSD-style license that'
949 u' can be\n'
950 u'// found in the LICENSE file.\n'),
951 (u'// Copyright (c) 2020 The Chromium OS Authors. All rights reserved.'
952 u'\n'
953 u'// Use of this source code is governed by a BSD-style license that'
954 u' can be\n'
955 u'// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -0400956 )
957 self.file_mock.return_value = ['file']
958 for header in HEADERS:
959 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +0900960 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -0700961
Brian Norris68838dd2018-09-26 18:30:24 -0700962 def testNoLeadingSpace(self):
963 """Allow headers without leading space (e.g., not a source comment)"""
964 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400965 (u'Copyright 2018 The Chromium OS Authors. All rights reserved.\n'
966 u'Use of this source code is governed by a BSD-style license that '
967 u'can be\n'
968 u'found in the LICENSE file.\n'),
Brian Norris68838dd2018-09-26 18:30:24 -0700969 )
970 self.file_mock.return_value = ['file']
971 for header in HEADERS:
972 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +0900973 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Brian Norris68838dd2018-09-26 18:30:24 -0700974
Keigo Oka9732e382019-06-28 17:44:59 +0900975 def testNoExcludedGolang(self):
976 """Don't exclude .go files for license checks."""
977 self.file_mock.return_value = ['foo/main.go']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400978 self.content_mock.return_value = u'package main\nfunc main() {}'
Keigo Oka7e880ac2019-07-03 15:03:43 +0900979 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Keigo Oka9732e382019-06-28 17:44:59 +0900980
Ken Turnerd07564b2018-02-08 17:57:59 +1100981 def testIgnoreExcludedPaths(self):
982 """Ignores excluded paths for license checks."""
983 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400984 self.content_mock.return_value = u'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +0900985 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +1100986
Chris McDonald7b63c8e2019-04-25 10:27:27 -0600987 def testIgnoreTopLevelExcludedPaths(self):
988 """Ignores excluded paths for license checks."""
989 self.file_mock.return_value = ['OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400990 self.content_mock.return_value = u'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +0900991 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -0700992
Mike Frysingerb2496652019-09-12 23:35:46 -0400993class CheckAOSPLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -0700994 """Tests for _check_aosp_license."""
995
996 def setUp(self):
997 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
998 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
999
1000 def testHeaders(self):
1001 """Accept old header styles."""
1002 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001003 u"""//
Alex Deymof5792ce2015-08-24 22:50:08 -07001004// Copyright (C) 2011 The Android Open Source Project
1005//
1006// Licensed under the Apache License, Version 2.0 (the "License");
1007// you may not use this file except in compliance with the License.
1008// You may obtain a copy of the License at
1009//
1010// http://www.apache.org/licenses/LICENSE-2.0
1011//
1012// Unless required by applicable law or agreed to in writing, software
1013// distributed under the License is distributed on an "AS IS" BASIS,
1014// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1015// See the License for the specific language governing permissions and
1016// limitations under the License.
1017//
1018""",
Mike Frysinger71e643e2019-09-13 17:26:39 -04001019 u"""#
Alex Deymof5792ce2015-08-24 22:50:08 -07001020# Copyright (c) 2015 The Android Open Source Project
1021#
1022# Licensed under the Apache License, Version 2.0 (the "License");
1023# you may not use this file except in compliance with the License.
1024# You may obtain a copy of the License at
1025#
1026# http://www.apache.org/licenses/LICENSE-2.0
1027#
1028# Unless required by applicable law or agreed to in writing, software
1029# distributed under the License is distributed on an "AS IS" BASIS,
1030# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1031# See the License for the specific language governing permissions and
1032# limitations under the License.
1033#
1034""",
1035 )
1036 self.file_mock.return_value = ['file']
1037 for header in HEADERS:
1038 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001039 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -07001040
1041 def testRejectNoLinesAround(self):
1042 """Reject headers missing the empty lines before/after the license."""
1043 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -04001044 u"""# Copyright (c) 2015 The Android Open Source Project
Alex Deymof5792ce2015-08-24 22:50:08 -07001045#
1046# Licensed under the Apache License, Version 2.0 (the "License");
1047# you may not use this file except in compliance with the License.
1048# You may obtain a copy of the License at
1049#
1050# http://www.apache.org/licenses/LICENSE-2.0
1051#
1052# Unless required by applicable law or agreed to in writing, software
1053# distributed under the License is distributed on an "AS IS" BASIS,
1054# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1055# See the License for the specific language governing permissions and
1056# limitations under the License.
1057""",
1058 )
1059 self.file_mock.return_value = ['file']
1060 for header in HEADERS:
1061 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001062 self.assertIsNotNone(pre_upload._check_aosp_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -04001063
Ken Turnerd07564b2018-02-08 17:57:59 +11001064 def testIgnoreExcludedPaths(self):
1065 """Ignores excluded paths for license checks."""
1066 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001067 self.content_mock.return_value = u'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001068 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +11001069
Chris McDonald7b63c8e2019-04-25 10:27:27 -06001070 def testIgnoreTopLevelExcludedPaths(self):
1071 """Ignores excluded paths for license checks."""
1072 self.file_mock.return_value = ['OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -04001073 self.content_mock.return_value = u'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001074 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
1075
Mike Frysinger98638102014-08-28 00:15:08 -04001076
Mike Frysingerb2496652019-09-12 23:35:46 -04001077class CheckLayoutConfTestCase(PreUploadTestCase):
Mike Frysingerd7734522015-02-26 16:12:43 -05001078 """Tests for _check_layout_conf."""
1079
1080 def setUp(self):
1081 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
1082 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1083
1084 def assertAccepted(self, files, project='project', commit='fake sha1'):
1085 """Assert _check_layout_conf accepts |files|."""
1086 self.file_mock.return_value = files
1087 ret = pre_upload._check_layout_conf(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001088 self.assertIsNone(ret, msg='rejected with:\n%s' % ret)
Mike Frysingerd7734522015-02-26 16:12:43 -05001089
1090 def assertRejected(self, files, project='project', commit='fake sha1'):
1091 """Assert _check_layout_conf rejects |files|."""
1092 self.file_mock.return_value = files
1093 ret = pre_upload._check_layout_conf(project, commit)
1094 self.assertTrue(isinstance(ret, errors.HookFailure))
1095
1096 def GetLayoutConf(self, filters=()):
1097 """Return a valid layout.conf with |filters| lines removed."""
1098 all_lines = [
Mike Frysinger71e643e2019-09-13 17:26:39 -04001099 u'masters = portage-stable chromiumos',
1100 u'profile-formats = portage-2 profile-default-eapi',
1101 u'profile_eapi_when_unspecified = 5-progress',
1102 u'repo-name = link',
1103 u'thin-manifests = true',
1104 u'use-manifests = strict',
Mike Frysingerd7734522015-02-26 16:12:43 -05001105 ]
1106
1107 lines = []
1108 for line in all_lines:
1109 for filt in filters:
1110 if line.startswith(filt):
1111 break
1112 else:
1113 lines.append(line)
1114
Mike Frysinger71e643e2019-09-13 17:26:39 -04001115 return u'\n'.join(lines)
Mike Frysingerd7734522015-02-26 16:12:43 -05001116
1117 def testNoFilesToCheck(self):
1118 """Don't blow up when there are no layout.conf files."""
1119 self.assertAccepted([])
1120
1121 def testRejectRepoNameFile(self):
1122 """If profiles/repo_name is set, kick it out."""
1123 self.assertRejected(['profiles/repo_name'])
1124
1125 def testAcceptValidLayoutConf(self):
1126 """Accept a fully valid layout.conf."""
1127 self.content_mock.return_value = self.GetLayoutConf()
1128 self.assertAccepted(['metadata/layout.conf'])
1129
1130 def testAcceptUnknownKeys(self):
1131 """Accept keys we don't explicitly know about."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001132 self.content_mock.return_value = self.GetLayoutConf() + u'\nzzz-top = ok'
Mike Frysingerd7734522015-02-26 16:12:43 -05001133 self.assertAccepted(['metadata/layout.conf'])
1134
1135 def testRejectUnsorted(self):
1136 """Reject an unsorted layout.conf."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001137 self.content_mock.return_value = u'zzz-top = bad\n' + self.GetLayoutConf()
Mike Frysingerd7734522015-02-26 16:12:43 -05001138 self.assertRejected(['metadata/layout.conf'])
1139
1140 def testRejectMissingThinManifests(self):
1141 """Reject a layout.conf missing thin-manifests."""
1142 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001143 filters=[u'thin-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001144 self.assertRejected(['metadata/layout.conf'])
1145
1146 def testRejectMissingUseManifests(self):
1147 """Reject a layout.conf missing use-manifests."""
1148 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001149 filters=[u'use-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001150 self.assertRejected(['metadata/layout.conf'])
1151
1152 def testRejectMissingEapiFallback(self):
1153 """Reject a layout.conf missing profile_eapi_when_unspecified."""
1154 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001155 filters=[u'profile_eapi_when_unspecified'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001156 self.assertRejected(['metadata/layout.conf'])
1157
1158 def testRejectMissingRepoName(self):
1159 """Reject a layout.conf missing repo-name."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001160 self.content_mock.return_value = self.GetLayoutConf(filters=[u'repo-name'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001161 self.assertRejected(['metadata/layout.conf'])
1162
1163
Mike Frysingerb2496652019-09-12 23:35:46 -04001164class CommitMessageTestCase(PreUploadTestCase):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001165 """Test case for funcs that check commit messages."""
1166
1167 def setUp(self):
1168 self.msg_mock = self.PatchObject(pre_upload, '_get_commit_desc')
1169
1170 @staticmethod
1171 def CheckMessage(_project, _commit):
1172 raise AssertionError('Test class must declare CheckMessage')
1173 # This dummy return is to silence pylint warning W1111 so we don't have to
1174 # enable it for all the call sites below.
1175 return 1 # pylint: disable=W0101
1176
Alex Deymo643ac4c2015-09-03 10:40:50 -07001177 def assertMessageAccepted(self, msg, project=ProjectNamed('project'),
1178 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001179 """Assert _check_change_has_bug_field accepts |msg|."""
1180 self.msg_mock.return_value = msg
1181 ret = self.CheckMessage(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001182 self.assertIsNone(ret)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001183
Alex Deymo643ac4c2015-09-03 10:40:50 -07001184 def assertMessageRejected(self, msg, project=ProjectNamed('project'),
1185 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001186 """Assert _check_change_has_bug_field rejects |msg|."""
1187 self.msg_mock.return_value = msg
1188 ret = self.CheckMessage(project, commit)
1189 self.assertTrue(isinstance(ret, errors.HookFailure))
1190
1191
1192class CheckCommitMessageBug(CommitMessageTestCase):
1193 """Tests for _check_change_has_bug_field."""
1194
Alex Deymo643ac4c2015-09-03 10:40:50 -07001195 AOSP_PROJECT = pre_upload.Project(name='overlay', dir='', remote='aosp')
1196 CROS_PROJECT = pre_upload.Project(name='overlay', dir='', remote='cros')
1197
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001198 @staticmethod
1199 def CheckMessage(project, commit):
1200 return pre_upload._check_change_has_bug_field(project, commit)
1201
1202 def testNormal(self):
1203 """Accept a commit message w/a valid BUG."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001204 self.assertMessageAccepted('\nBUG=chromium:1234\n', self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001205 self.assertMessageAccepted('\nBUG=b:1234\n', self.CROS_PROJECT)
1206
1207 self.assertMessageAccepted('\nBug: 1234\n', self.AOSP_PROJECT)
1208 self.assertMessageAccepted('\nBug:1234\n', self.AOSP_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001209
1210 def testNone(self):
1211 """Accept BUG=None."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001212 self.assertMessageAccepted('\nBUG=None\n', self.CROS_PROJECT)
1213 self.assertMessageAccepted('\nBUG=none\n', self.CROS_PROJECT)
1214 self.assertMessageAccepted('\nBug: None\n', self.AOSP_PROJECT)
1215 self.assertMessageAccepted('\nBug:none\n', self.AOSP_PROJECT)
1216
1217 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1218 self.assertMessageRejected('\nBUG=NONE\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001219
1220 def testBlank(self):
1221 """Reject blank values."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001222 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1223 self.assertMessageRejected('\nBUG=\n', project)
1224 self.assertMessageRejected('\nBUG= \n', project)
1225 self.assertMessageRejected('\nBug:\n', project)
1226 self.assertMessageRejected('\nBug: \n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001227
1228 def testNotFirstLine(self):
1229 """Reject the first line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001230 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1231 self.assertMessageRejected('BUG=None\n\n\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001232
1233 def testNotInline(self):
1234 """Reject not at the start of line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001235 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1236 self.assertMessageRejected('\n BUG=None\n', project)
1237 self.assertMessageRejected('\n\tBUG=None\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001238
1239 def testOldTrackers(self):
1240 """Reject commit messages using old trackers."""
Mike Frysingera2f28252017-10-27 22:26:14 -04001241 self.assertMessageRejected('\nBUG=chrome-os-partner:1234\n',
1242 self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001243 self.assertMessageRejected('\nBUG=chromium-os:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001244
1245 def testNoTrackers(self):
1246 """Reject commit messages w/invalid trackers."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001247 self.assertMessageRejected('\nBUG=booga:1234\n', self.CROS_PROJECT)
1248 self.assertMessageRejected('\nBUG=br:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001249
1250 def testMissing(self):
1251 """Reject commit messages w/no BUG line."""
1252 self.assertMessageRejected('foo\n')
1253
1254 def testCase(self):
1255 """Reject bug lines that are not BUG."""
1256 self.assertMessageRejected('\nbug=none\n')
1257
Cheng Yuehb707c522020-01-02 14:06:59 +08001258 def testNotAfterTest(self):
1259 """Reject any TEST line before any BUG line."""
1260 test_field = 'TEST=i did not do it\n'
1261 middle_field = 'A random between line\n'
1262 for project, bug_field in ((self.AOSP_PROJECT, 'Bug:none\n'),
1263 (self.CROS_PROJECT, 'BUG=None\n')):
1264 self.assertMessageRejected(
1265 '\n' + test_field + middle_field + bug_field, project)
1266 self.assertMessageRejected(
1267 '\n' + test_field + bug_field, project)
1268
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001269
1270class CheckCommitMessageCqDepend(CommitMessageTestCase):
1271 """Tests for _check_change_has_valid_cq_depend."""
1272
1273 @staticmethod
1274 def CheckMessage(project, commit):
1275 return pre_upload._check_change_has_valid_cq_depend(project, commit)
1276
1277 def testNormal(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001278 """Accept valid Cq-Depends line."""
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001279 self.assertMessageAccepted('\nCq-Depend: chromium:1234\nChange-Id: I123')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001280
1281 def testInvalid(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001282 """Reject invalid Cq-Depends line."""
1283 self.assertMessageRejected('\nCq-Depend=chromium=1234\n')
1284 self.assertMessageRejected('\nCq-Depend: None\n')
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001285 self.assertMessageRejected('\nCq-Depend: chromium:1234\n\nChange-Id: I123')
Mike Frysingere39d0cd2019-11-25 13:30:06 -05001286 self.assertMessageRejected('\nCQ-DEPEND=1234\n')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001287
1288
Bernie Thompsonf8fea992016-01-14 10:27:18 -08001289class CheckCommitMessageContribution(CommitMessageTestCase):
1290 """Tests for _check_change_is_contribution."""
1291
1292 @staticmethod
1293 def CheckMessage(project, commit):
1294 return pre_upload._check_change_is_contribution(project, commit)
1295
1296 def testNormal(self):
1297 """Accept a commit message which is a contribution."""
1298 self.assertMessageAccepted('\nThis is cool code I am contributing.\n')
1299
1300 def testFailureLowerCase(self):
1301 """Deny a commit message which is not a contribution."""
1302 self.assertMessageRejected('\nThis is not a contribution.\n')
1303
1304 def testFailureUpperCase(self):
1305 """Deny a commit message which is not a contribution."""
1306 self.assertMessageRejected('\nNOT A CONTRIBUTION\n')
1307
1308
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001309class CheckCommitMessageTest(CommitMessageTestCase):
1310 """Tests for _check_change_has_test_field."""
1311
1312 @staticmethod
1313 def CheckMessage(project, commit):
1314 return pre_upload._check_change_has_test_field(project, commit)
1315
1316 def testNormal(self):
1317 """Accept a commit message w/a valid TEST."""
1318 self.assertMessageAccepted('\nTEST=i did it\n')
1319
1320 def testNone(self):
1321 """Accept TEST=None."""
1322 self.assertMessageAccepted('\nTEST=None\n')
1323 self.assertMessageAccepted('\nTEST=none\n')
1324
1325 def testBlank(self):
1326 """Reject blank values."""
1327 self.assertMessageRejected('\nTEST=\n')
1328 self.assertMessageRejected('\nTEST= \n')
1329
1330 def testNotFirstLine(self):
1331 """Reject the first line."""
1332 self.assertMessageRejected('TEST=None\n\n\n')
1333
1334 def testNotInline(self):
1335 """Reject not at the start of line."""
1336 self.assertMessageRejected('\n TEST=None\n')
1337 self.assertMessageRejected('\n\tTEST=None\n')
1338
1339 def testMissing(self):
1340 """Reject commit messages w/no TEST line."""
1341 self.assertMessageRejected('foo\n')
1342
1343 def testCase(self):
1344 """Reject bug lines that are not TEST."""
1345 self.assertMessageRejected('\ntest=none\n')
1346
1347
1348class CheckCommitMessageChangeId(CommitMessageTestCase):
1349 """Tests for _check_change_has_proper_changeid."""
1350
1351 @staticmethod
1352 def CheckMessage(project, commit):
1353 return pre_upload._check_change_has_proper_changeid(project, commit)
1354
1355 def testNormal(self):
1356 """Accept a commit message w/a valid Change-Id."""
1357 self.assertMessageAccepted('foo\n\nChange-Id: I1234\n')
1358
1359 def testBlank(self):
1360 """Reject blank values."""
1361 self.assertMessageRejected('\nChange-Id:\n')
1362 self.assertMessageRejected('\nChange-Id: \n')
1363
1364 def testNotFirstLine(self):
1365 """Reject the first line."""
1366 self.assertMessageRejected('TEST=None\n\n\n')
1367
1368 def testNotInline(self):
1369 """Reject not at the start of line."""
1370 self.assertMessageRejected('\n Change-Id: I1234\n')
1371 self.assertMessageRejected('\n\tChange-Id: I1234\n')
1372
1373 def testMissing(self):
1374 """Reject commit messages missing the line."""
1375 self.assertMessageRejected('foo\n')
1376
1377 def testCase(self):
1378 """Reject bug lines that are not Change-Id."""
1379 self.assertMessageRejected('\nchange-id: I1234\n')
1380 self.assertMessageRejected('\nChange-id: I1234\n')
1381 self.assertMessageRejected('\nChange-ID: I1234\n')
1382
1383 def testEnd(self):
1384 """Reject Change-Id's that are not last."""
1385 self.assertMessageRejected('\nChange-Id: I1234\nbar\n')
1386
Mike Frysinger02b88bd2014-11-21 00:29:38 -05001387 def testSobTag(self):
1388 """Permit s-o-b tags to follow the Change-Id."""
1389 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nSigned-off-by: Hi\n')
1390
LaMont Jones237f3ef2020-01-22 10:40:52 -07001391 def testCqClTag(self):
1392 """Permit Cq-Cl-Tag tags to follow the Change-Id."""
1393 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nCq-Cl-Tag: Hi\n')
1394
LaMont Jonesfb5e8bf2020-03-03 12:50:06 -07001395 def testCqIncludeTrybotsTag(self):
1396 """Permit Cq-Include-Trybots tags to follow the Change-Id."""
1397 self.assertMessageAccepted(
1398 'foo\n\nChange-Id: I1234\nCq-Include-Trybots: chromeos/cq:foo\n')
1399
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001400
Jack Neus8edbf642019-07-10 16:08:31 -06001401class CheckCommitMessageNoOEM(CommitMessageTestCase):
1402 """Tests for _check_change_no_include_oem."""
1403
1404 @staticmethod
1405 def CheckMessage(project, commit):
1406 return pre_upload._check_change_no_include_oem(project, commit)
1407
1408 def testNormal(self):
1409 """Accept a commit message w/o reference to an OEM/ODM."""
1410 self.assertMessageAccepted('foo\n')
1411
1412 def testHasOEM(self):
1413 """Catch commit messages referencing OEMs."""
1414 self.assertMessageRejected('HP Project\n\n')
1415 self.assertMessageRejected('hewlett-packard\n')
1416 self.assertMessageRejected('Hewlett\nPackard\n')
1417 self.assertMessageRejected('Dell Chromebook\n\n\n')
1418 self.assertMessageRejected('product@acer.com\n')
1419 self.assertMessageRejected('This is related to Asus\n')
1420 self.assertMessageRejected('lenovo machine\n')
1421
1422 def testHasODM(self):
1423 """Catch commit messages referencing ODMs."""
1424 self.assertMessageRejected('new samsung laptop\n\n')
1425 self.assertMessageRejected('pegatron(ems) project\n')
1426 self.assertMessageRejected('new Wistron device\n')
1427
1428 def testContainsOEM(self):
1429 """Check that the check handles word boundaries properly."""
1430 self.assertMessageAccepted('oheahpohea')
1431 self.assertMessageAccepted('Play an Asus7 guitar chord.\n\n')
1432
1433 def testTag(self):
1434 """Check that the check ignores tags."""
1435 self.assertMessageAccepted(
1436 'Harmless project\n'
1437 'Reviewed-by: partner@asus.corp-partner.google.com\n'
1438 'Tested-by: partner@hp.corp-partner.google.com\n'
1439 'Signed-off-by: partner@dell.corp-partner.google.com\n'
1440 'Commit-Queue: partner@lenovo.corp-partner.google.com\n'
Jack Neus8edbf642019-07-10 16:08:31 -06001441 'CC: partner@acer.corp-partner.google.com\n'
1442 'Acked-by: partner@hewlett-packard.corp-partner.google.com\n')
1443 self.assertMessageRejected(
1444 'Asus project\n'
1445 'Reviewed-by: partner@asus.corp-partner.google.com')
Mike Frysingerbb34a222019-07-31 14:40:46 -04001446 self.assertMessageRejected(
1447 'my project\n'
1448 'Bad-tag: partner@asus.corp-partner.google.com')
Jack Neus8edbf642019-07-10 16:08:31 -06001449
1450
Mike Frysinger36b2ebc2014-10-31 14:02:03 -04001451class CheckCommitMessageStyle(CommitMessageTestCase):
1452 """Tests for _check_commit_message_style."""
1453
1454 @staticmethod
1455 def CheckMessage(project, commit):
1456 return pre_upload._check_commit_message_style(project, commit)
1457
1458 def testNormal(self):
1459 """Accept valid commit messages."""
1460 self.assertMessageAccepted('one sentence.\n')
1461 self.assertMessageAccepted('some.module: do it!\n')
1462 self.assertMessageAccepted('one line\n\nmore stuff here.')
1463
1464 def testNoBlankSecondLine(self):
1465 """Reject messages that have stuff on the second line."""
1466 self.assertMessageRejected('one sentence.\nbad fish!\n')
1467
1468 def testFirstLineMultipleSentences(self):
1469 """Reject messages that have more than one sentence in the summary."""
1470 self.assertMessageRejected('one sentence. two sentence!\n')
1471
1472 def testFirstLineTooLone(self):
1473 """Reject first lines that are too long."""
1474 self.assertMessageRejected('o' * 200)
1475
1476
Mike Frysinger292b45d2014-11-25 01:17:10 -05001477def DiffEntry(src_file=None, dst_file=None, src_mode=None, dst_mode='100644',
1478 status='M'):
1479 """Helper to create a stub RawDiffEntry object"""
1480 if src_mode is None:
1481 if status == 'A':
1482 src_mode = '000000'
1483 elif status == 'M':
1484 src_mode = dst_mode
1485 elif status == 'D':
1486 src_mode = dst_mode
1487 dst_mode = '000000'
1488
1489 src_sha = dst_sha = 'abc'
1490 if status == 'D':
1491 dst_sha = '000000'
1492 elif status == 'A':
1493 src_sha = '000000'
1494
1495 return git.RawDiffEntry(src_mode=src_mode, dst_mode=dst_mode, src_sha=src_sha,
1496 dst_sha=dst_sha, status=status, score=None,
1497 src_file=src_file, dst_file=dst_file)
1498
1499
Mike Frysingerb2496652019-09-12 23:35:46 -04001500class HelpersTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001501 """Various tests for utility functions."""
1502
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001503 def setUp(self):
1504 self.orig_cwd = os.getcwd()
1505 os.chdir(self.tempdir)
1506
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001507 self.PatchObject(git, 'RawDiff', return_value=[
1508 # A modified normal file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001509 DiffEntry(src_file='buildbot/constants.py', status='M'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001510 # A new symlink file.
Bob Haarman0dc1f942020-10-03 00:06:59 +00001511 DiffEntry(dst_file='scripts/cros_env_allowlist', dst_mode='120000',
Mike Frysinger292b45d2014-11-25 01:17:10 -05001512 status='A'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001513 # A deleted file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001514 DiffEntry(src_file='scripts/sync_sonic.py', status='D'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001515 ])
1516
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001517 def tearDown(self):
1518 os.chdir(self.orig_cwd)
1519
1520 def _WritePresubmitIgnoreFile(self, subdir, data):
1521 """Writes to a .presubmitignore file in the passed-in subdirectory."""
1522 directory = os.path.join(self.tempdir, subdir)
1523 if not os.path.exists(directory):
1524 os.makedirs(directory)
1525 osutils.WriteFile(os.path.join(directory, pre_upload._IGNORE_FILE), data)
1526
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001527 def testGetAffectedFilesNoDeletesNoRelative(self):
1528 """Verify _get_affected_files() works w/no delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001529 path = os.getcwd()
1530 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1531 relative=False)
1532 exp_files = [os.path.join(path, 'buildbot/constants.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001533 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001534
1535 def testGetAffectedFilesDeletesNoRelative(self):
1536 """Verify _get_affected_files() works w/delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001537 path = os.getcwd()
1538 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1539 relative=False)
1540 exp_files = [os.path.join(path, 'buildbot/constants.py'),
1541 os.path.join(path, 'scripts/sync_sonic.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001542 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001543
1544 def testGetAffectedFilesNoDeletesRelative(self):
1545 """Verify _get_affected_files() works w/no delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001546 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1547 relative=True)
1548 exp_files = ['buildbot/constants.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001549 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001550
1551 def testGetAffectedFilesDeletesRelative(self):
1552 """Verify _get_affected_files() works w/delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001553 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1554 relative=True)
1555 exp_files = ['buildbot/constants.py', 'scripts/sync_sonic.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001556 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001557
Mike Frysinger292b45d2014-11-25 01:17:10 -05001558 def testGetAffectedFilesDetails(self):
1559 """Verify _get_affected_files() works w/full_details."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001560 files = pre_upload._get_affected_files('HEAD', full_details=True,
1561 relative=True)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001562 self.assertEqual(files[0].src_file, 'buildbot/constants.py')
Mike Frysinger292b45d2014-11-25 01:17:10 -05001563
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001564 def testGetAffectedFilesPresubmitIgnoreDirectory(self):
1565 """Verify .presubmitignore can be used to exclude a directory."""
1566 self._WritePresubmitIgnoreFile('.', 'buildbot/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001567 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001568
1569 def testGetAffectedFilesPresubmitIgnoreDirectoryWildcard(self):
1570 """Verify .presubmitignore can be used with a directory wildcard."""
1571 self._WritePresubmitIgnoreFile('.', '*/constants.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001572 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001573
1574 def testGetAffectedFilesPresubmitIgnoreWithinDirectory(self):
1575 """Verify .presubmitignore can be placed in a subdirectory."""
1576 self._WritePresubmitIgnoreFile('buildbot', '*.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001577 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001578
1579 def testGetAffectedFilesPresubmitIgnoreDoesntMatch(self):
1580 """Verify .presubmitignore has no effect when it doesn't match a file."""
1581 self._WritePresubmitIgnoreFile('buildbot', '*.txt')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001582 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True),
1583 ['buildbot/constants.py'])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001584
1585 def testGetAffectedFilesPresubmitIgnoreAddedFile(self):
1586 """Verify .presubmitignore matches added files."""
1587 self._WritePresubmitIgnoreFile('.', 'buildbot/\nscripts/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001588 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True,
1589 include_symlinks=True),
1590 [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001591
1592 def testGetAffectedFilesPresubmitIgnoreSkipIgnoreFile(self):
1593 """Verify .presubmitignore files are automatically skipped."""
1594 self.PatchObject(git, 'RawDiff', return_value=[
1595 DiffEntry(src_file='.presubmitignore', status='M')
1596 ])
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001597 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Mike Frysinger292b45d2014-11-25 01:17:10 -05001598
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001599
Mike Frysingerb2496652019-09-12 23:35:46 -04001600class ExecFilesTest(PreUploadTestCase):
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001601 """Tests for _check_exec_files."""
1602
1603 def setUp(self):
1604 self.diff_mock = self.PatchObject(git, 'RawDiff')
1605
1606 def testNotExec(self):
1607 """Do not flag files that are not executable."""
1608 self.diff_mock.return_value = [
1609 DiffEntry(src_file='make.conf', dst_mode='100644', status='A'),
1610 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001611 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001612
1613 def testExec(self):
1614 """Flag files that are executable."""
1615 self.diff_mock.return_value = [
1616 DiffEntry(src_file='make.conf', dst_mode='100755', status='A'),
1617 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001618 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001619
1620 def testDeletedExec(self):
1621 """Ignore bad files that are being deleted."""
1622 self.diff_mock.return_value = [
1623 DiffEntry(src_file='make.conf', dst_mode='100755', status='D'),
1624 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001625 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001626
1627 def testModifiedExec(self):
1628 """Flag bad files that weren't exec, but now are."""
1629 self.diff_mock.return_value = [
1630 DiffEntry(src_file='make.conf', src_mode='100644', dst_mode='100755',
1631 status='M'),
1632 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001633 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001634
1635 def testNormalExec(self):
1636 """Don't flag normal files (e.g. scripts) that are executable."""
1637 self.diff_mock.return_value = [
1638 DiffEntry(src_file='foo.sh', dst_mode='100755', status='A'),
1639 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001640 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001641
1642
Mike Frysingerb2496652019-09-12 23:35:46 -04001643class CheckForUprev(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger292b45d2014-11-25 01:17:10 -05001644 """Tests for _check_for_uprev."""
1645
1646 def setUp(self):
1647 self.file_mock = self.PatchObject(git, 'RawDiff')
1648
1649 def _Files(self, files):
1650 """Create |files| in the tempdir and return full paths to them."""
1651 for obj in files:
1652 if obj.status == 'D':
1653 continue
1654 if obj.dst_file is None:
1655 f = obj.src_file
1656 else:
1657 f = obj.dst_file
1658 osutils.Touch(os.path.join(self.tempdir, f), makedirs=True)
1659 return files
1660
1661 def assertAccepted(self, files, project='project', commit='fake sha1'):
1662 """Assert _check_for_uprev accepts |files|."""
1663 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001664 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1665 project_top=self.tempdir)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001666 self.assertIsNone(ret)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001667
1668 def assertRejected(self, files, project='project', commit='fake sha1'):
1669 """Assert _check_for_uprev rejects |files|."""
1670 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001671 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1672 project_top=self.tempdir)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001673 self.assertTrue(isinstance(ret, errors.HookFailure))
1674
Bob Haarman0dc1f942020-10-03 00:06:59 +00001675 def testAllowlistOverlay(self):
1676 """Skip checks on allowed overlays."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001677 self.assertAccepted([DiffEntry(src_file='cat/pkg/pkg-0.ebuild')],
1678 project='chromiumos/overlays/portage-stable')
1679
Bob Haarman0dc1f942020-10-03 00:06:59 +00001680 def testAllowlistFiles(self):
1681 """Skip checks on allowed files."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001682 files = ['ChangeLog', 'Manifest', 'metadata.xml']
1683 self.assertAccepted([DiffEntry(src_file=os.path.join('c', 'p', x),
1684 status='M')
1685 for x in files])
1686
1687 def testRejectBasic(self):
1688 """Reject ebuilds missing uprevs."""
1689 self.assertRejected([DiffEntry(src_file='c/p/p-0.ebuild', status='M')])
1690
1691 def testNewPackage(self):
1692 """Accept new ebuilds w/out uprevs."""
1693 self.assertAccepted([DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1694 self.assertAccepted([DiffEntry(src_file='c/p/p-0-r12.ebuild', status='A')])
1695
1696 def testModifiedFilesOnly(self):
1697 """Reject ebuilds w/out uprevs and changes in files/."""
1698 osutils.Touch(os.path.join(self.tempdir, 'cat/pkg/pkg-0.ebuild'),
1699 makedirs=True)
1700 self.assertRejected([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1701 self.assertRejected([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1702
1703 def testFilesNoEbuilds(self):
1704 """Ignore changes to paths w/out ebuilds."""
1705 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1706 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1707
1708 def testModifiedFilesWithUprev(self):
1709 """Accept ebuilds w/uprevs and changes in files/."""
1710 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='A'),
1711 DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1712 self.assertAccepted([
1713 DiffEntry(src_file='c/p/files/f', status='M'),
1714 DiffEntry(src_file='c/p/p-0-r1.ebuild', src_mode='120000',
1715 dst_file='c/p/p-0-r2.ebuild', dst_mode='120000', status='R')])
1716
Gwendal Grignoua3086c32014-12-09 11:17:22 -08001717 def testModifiedFilesWith9999(self):
1718 """Accept 9999 ebuilds and changes in files/."""
1719 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M'),
1720 DiffEntry(src_file='c/p/p-9999.ebuild', status='M')])
1721
C Shapiroae157ae2017-09-18 16:24:03 -06001722 def testModifiedFilesIn9999SubDirWithout9999Change(self):
1723 """Accept changes in files/ with a parent 9999 ebuild"""
1724 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
1725 os.makedirs(os.path.dirname(ebuild_9999_file))
1726 osutils.WriteFile(ebuild_9999_file, 'fake')
1727 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M')])
1728
Stephen Boyd6bf5ea82020-10-15 00:02:07 -07001729 def testModifiedFilesAndProfilesWith9999(self):
1730 """Accept changes in files/ with a parent 9999 ebuild and profile change"""
1731 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
1732 os.makedirs(os.path.dirname(ebuild_9999_file))
1733 osutils.WriteFile(ebuild_9999_file, 'fake')
1734 self.assertAccepted([
1735 DiffEntry(src_file='c/p/files/f', status='M'),
1736 DiffEntry(src_file='c/profiles/base/make.defaults', status='M')])
1737
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001738
Mike Frysingerb2496652019-09-12 23:35:46 -04001739class DirectMainTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger55f85b52014-12-18 14:45:21 -05001740 """Tests for direct_main()"""
1741
1742 def setUp(self):
1743 self.hooks_mock = self.PatchObject(pre_upload, '_run_project_hooks',
1744 return_value=None)
1745
1746 def testNoArgs(self):
1747 """If run w/no args, should check the current dir."""
1748 ret = pre_upload.direct_main([])
1749 self.assertEqual(ret, 0)
1750 self.hooks_mock.assert_called_once_with(
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001751 mock.ANY, proj_dir=os.getcwd(), commit_list=[], presubmit=mock.ANY,
1752 config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001753
1754 def testExplicitDir(self):
1755 """Verify we can run on a diff dir."""
1756 # Use the chromite dir since we know it exists.
1757 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR])
1758 self.assertEqual(ret, 0)
1759 self.hooks_mock.assert_called_once_with(
1760 mock.ANY, proj_dir=constants.CHROMITE_DIR, commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001761 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001762
1763 def testBogusProject(self):
1764 """A bogus project name should be fine (use default settings)."""
1765 # Use the chromite dir since we know it exists.
1766 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR,
1767 '--project', 'foooooooooo'])
1768 self.assertEqual(ret, 0)
1769 self.hooks_mock.assert_called_once_with(
1770 'foooooooooo', proj_dir=constants.CHROMITE_DIR, commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001771 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001772
1773 def testBogustProjectNoDir(self):
1774 """Make sure --dir is detected even with --project."""
1775 ret = pre_upload.direct_main(['--project', 'foooooooooo'])
1776 self.assertEqual(ret, 0)
1777 self.hooks_mock.assert_called_once_with(
1778 'foooooooooo', proj_dir=os.getcwd(), commit_list=[],
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001779 presubmit=mock.ANY, config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001780
1781 def testNoGitDir(self):
1782 """We should die when run on a non-git dir."""
1783 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
1784 ['--dir', self.tempdir])
1785
1786 def testNoDir(self):
1787 """We should die when run on a missing dir."""
1788 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
1789 ['--dir', os.path.join(self.tempdir, 'foooooooo')])
1790
1791 def testCommitList(self):
1792 """Any args on the command line should be treated as commits."""
1793 commits = ['sha1', 'sha2', 'shaaaaaaaaaaaan']
1794 ret = pre_upload.direct_main(commits)
1795 self.assertEqual(ret, 0)
1796 self.hooks_mock.assert_called_once_with(
Sonny Sasaka5a905ea2020-07-24 15:30:12 -07001797 mock.ANY, proj_dir=mock.ANY, commit_list=commits, presubmit=mock.ANY,
1798 config_file=mock.ANY)
Mike Frysinger55f85b52014-12-18 14:45:21 -05001799
1800
Mike Frysingerb2496652019-09-12 23:35:46 -04001801class CheckRustfmtTest(PreUploadTestCase):
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001802 """Tests for _check_rustfmt."""
1803
1804 def setUp(self):
1805 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1806
1807 def testBadRustFile(self):
1808 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
1809 # Bad because it's missing trailing newline.
Mike Frysingere85b0062019-08-20 15:10:33 -04001810 content = 'fn main() {}'
1811 self.content_mock.return_value = content
1812 self.PatchObject(pre_upload, '_run_command', return_value=content + '\n')
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001813 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1814 self.assertIsNotNone(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001815 self.assertEqual('Files not formatted with rustfmt: '
1816 "(run 'cargo fmt' to fix)",
1817 failure.msg)
1818 self.assertEqual(['a.rs'], failure.items)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001819
1820 def testGoodRustFile(self):
1821 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
Mike Frysingere85b0062019-08-20 15:10:33 -04001822 content = 'fn main() {}\n'
1823 self.content_mock.return_value = content
1824 self.PatchObject(pre_upload, '_run_command', return_value=content)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001825 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1826 self.assertIsNone(failure)
1827
1828 def testFilterNonRustFiles(self):
1829 self.PatchObject(pre_upload, '_get_affected_files',
1830 return_value=['a.cc', 'a.rsa', 'a.irs', 'rs.cc'])
1831 self.content_mock.return_value = 'fn main() {\n}'
1832 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1833 self.assertIsNone(failure)
1834
1835
Keiichi Watanabe6ab73fd2020-07-14 23:42:02 +09001836class GetCargoClippyParserTest(cros_test_lib.TestCase):
1837 """Tests for _get_cargo_clippy_parser."""
1838
1839 def testSingleProject(self):
1840 parser = pre_upload._get_cargo_clippy_parser()
1841 args = parser.parse_args(['--project', 'foo'])
1842 self.assertEqual(args.project,
1843 [pre_upload.ClippyProject(root='foo', script=None)])
1844
1845 def testMultipleProjects(self):
1846 parser = pre_upload._get_cargo_clippy_parser()
1847 args = parser.parse_args(['--project', 'foo:bar',
1848 '--project', 'baz'])
1849 self.assertCountEqual(args.project,
1850 [pre_upload.ClippyProject(root='foo', script='bar'),
1851 pre_upload.ClippyProject(root='baz', script=None)])
1852
1853
1854class CheckCargoClippyTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
1855 """Tests for _check_cargo_clippy."""
1856
1857 def setUp(self):
1858 self.project = pre_upload.Project(name='PROJECT', dir=self.tempdir,
1859 remote=None)
1860
1861 def testClippy(self):
1862 """Verify clippy is called when a monitored file was changed."""
1863 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
1864
1865 self.PatchObject(pre_upload, '_get_affected_files',
1866 return_value=[f'{self.project.dir}/repo_a/a.rs'])
1867
1868 ret = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
1869 options=['--project=repo_a',
1870 '--project=repo_b:foo'])
1871 self.assertFalse(ret)
1872
1873 # Check if `cargo clippy` ran.
1874 called = False
1875 for args, _ in rc_mock.call_args_list:
1876 cmd = args[0]
1877 if len(cmd) > 1 and cmd[0] == 'cargo' and cmd[1] == 'clippy':
1878 called = True
1879 break
1880
1881 self.assertTrue(called)
1882
1883 def testDontRun(self):
1884 """Skip checks when no monitored files are modified."""
1885 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
1886
1887 # A file under `repo_a` was monitored.
1888 self.PatchObject(pre_upload, '_get_affected_files',
1889 return_value=[f'{self.project.dir}/repo_a/a.rs'])
1890 # But, we only care about files under `repo_b`.
1891 errs = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
1892 options=['--project=repo_b:foo'])
1893
1894 self.assertFalse(errs)
1895
1896 rc_mock.assert_not_called()
1897
1898 def testCustomScript(self):
1899 """Verify project-specific script is used."""
1900 rc_mock = self.PatchObject(pre_upload, '_run_command', return_value='')
1901
1902 self.PatchObject(pre_upload, '_get_affected_files',
1903 return_value=[f'{self.project.dir}/repo_b/b.rs'])
1904
1905 errs = pre_upload._check_cargo_clippy(self.project, 'COMMIT',
1906 options=['--project=repo_a',
1907 '--project=repo_b:foo'])
1908 self.assertFalse(errs)
1909
1910 # Check if the script `foo` ran.
1911 called = False
1912 for args, _ in rc_mock.call_args_list:
1913 cmd = args[0]
1914 if len(cmd) > 0 and cmd[0] == os.path.join(self.project.dir, 'foo'):
1915 called = True
1916 break
1917
1918 self.assertTrue(called)
1919
1920
Mike Frysinger180ecd62020-08-19 00:41:51 -04001921class OverrideHooksProcessing(PreUploadTestCase):
1922 """Verify _get_override_hooks processing."""
1923
1924 @staticmethod
1925 def parse(data):
1926 """Helper to create a config & parse it."""
1927 cfg = configparser.ConfigParser()
1928 cfg.read_string(data)
1929 return pre_upload._get_override_hooks(cfg)
1930
1931 def testHooks(self):
1932 """Verify we reject unknown hook names (e.g. typos)."""
1933 with self.assertRaises(ValueError) as e:
1934 self.parse("""
1935[Hook Overrides]
1936foobar: true
1937""")
1938 self.assertIn('foobar', str(e.exception))
1939
1940 def testImplicitDisable(self):
1941 """Verify non-common hooks aren't enabled by default."""
1942 enabled, _ = self.parse('')
1943 self.assertNotIn(pre_upload._run_checkpatch, enabled)
1944
1945 def testExplicitDisable(self):
1946 """Verify hooks disabled are disabled."""
1947 _, disabled = self.parse("""
1948[Hook Overrides]
1949tab_check: false
1950""")
1951 self.assertIn(pre_upload._check_no_tabs, disabled)
1952
1953 def testExplicitEnable(self):
1954 """Verify hooks enabled are enabled."""
1955 enabled, _ = self.parse("""
1956[Hook Overrides]
1957tab_check: true
1958""")
1959 self.assertIn(pre_upload._check_no_tabs, enabled)
1960
1961 def testOptions(self):
1962 """Verify hook options are loaded."""
1963 enabled, _ = self.parse("""
1964[Hook Overrides Options]
1965keyword_check: --kw
1966""")
1967 for func in enabled:
1968 if func.__name__ == 'keyword_check':
1969 self.assertIn('options', func.keywords)
1970 self.assertEqual(func.keywords['options'], ['--kw'])
1971 break
1972 else:
1973 self.fail('could not find "keyword_check" enabled hook')
1974
1975 def testSignOffField(self):
1976 """Verify signoff field handling."""
1977 # Enforce no s-o-b by default.
1978 enabled, disabled = self.parse('')
1979 self.assertIn(pre_upload._check_change_has_no_signoff_field, enabled)
1980 self.assertNotIn(pre_upload._check_change_has_signoff_field, enabled)
1981 self.assertNotIn(pre_upload._check_change_has_signoff_field, disabled)
1982
1983 # If disabled, don't enforce either policy.
1984 enabled, disabled = self.parse("""
1985[Hook Overrides]
1986signoff_check: false
1987""")
1988 self.assertNotIn(pre_upload._check_change_has_no_signoff_field, enabled)
1989 self.assertNotIn(pre_upload._check_change_has_signoff_field, enabled)
1990 self.assertIn(pre_upload._check_change_has_signoff_field, disabled)
1991
1992 # If enabled, require s-o-b.
1993 enabled, disabled = self.parse("""
1994[Hook Overrides]
1995signoff_check: true
1996""")
1997 self.assertNotIn(pre_upload._check_change_has_no_signoff_field, enabled)
1998 self.assertIn(pre_upload._check_change_has_signoff_field, enabled)
1999 self.assertNotIn(pre_upload._check_change_has_signoff_field, disabled)
2000
2001 def testBranchField(self):
2002 """Verify branch field enabling."""
2003 # Should be disabled by default.
2004 enabled, disabled = self.parse('')
2005 self.assertIn(pre_upload._check_change_has_no_branch_field, enabled)
2006 self.assertNotIn(pre_upload._check_change_has_branch_field, enabled)
2007 self.assertNotIn(pre_upload._check_change_has_branch_field, disabled)
2008
2009 # Should be disabled if requested.
2010 enabled, disabled = self.parse("""
2011[Hook Overrides]
2012branch_check: false
2013""")
2014 self.assertIn(pre_upload._check_change_has_no_branch_field, enabled)
2015 self.assertNotIn(pre_upload._check_change_has_branch_field, enabled)
2016 self.assertIn(pre_upload._check_change_has_branch_field, disabled)
2017
2018 # Should be enabled if requested.
2019 enabled, disabled = self.parse("""
2020[Hook Overrides]
2021branch_check: true
2022""")
2023 self.assertNotIn(pre_upload._check_change_has_no_branch_field, enabled)
2024 self.assertIn(pre_upload._check_change_has_branch_field, enabled)
2025 self.assertNotIn(pre_upload._check_change_has_branch_field, disabled)
2026
2027
Tom Hughes1ed799d2020-09-25 14:37:28 -07002028class ProjectHooksProcessing(PreUploadTestCase, cros_test_lib.TempDirTestCase):
2029 """Verify _get_project_hooks processing."""
2030
2031 def parse(self, data):
2032 """Helper to write config and parse it."""
2033 filename = os.path.join(self.tempdir, 'config')
2034 osutils.WriteFile(filename, data)
2035 return pre_upload._get_project_hooks(project='test', presubmit=True,
2036 config_file=filename)
2037
2038 def testClangFormatCheckDefault(self):
2039 """Verify clang-format check disabled by default."""
2040 hooks = self.parse('')
2041 for func in hooks:
2042 self.assertNotEqual(func.__name__, '_check_clang_format')
2043 self.assertNotEqual(func.__name__, 'clang_format_check')
2044
2045 def testClangFormatCheckDisabled(self):
2046 """Verify clang-format check disabled when requested."""
2047 hooks = self.parse("""
2048[Hook Overrides]
2049clang_format_check: false
2050""")
2051 for func in hooks:
2052 self.assertNotEqual(func.__name__, '_check_clang_format')
2053 self.assertNotEqual(func.__name__, 'clang_format_check')
2054
2055 def testClangFormatCheckEnabled(self):
2056 """Verify clang-format check enabled when requested."""
2057 hooks = self.parse("""
2058[Hook Overrides]
2059clang_format_check: true
2060""")
2061 for func in hooks:
2062 if func.__name__ == '_check_clang_format':
2063 self.assertFalse(hasattr(func, 'keywords'))
2064 break
2065 else:
2066 self.fail('could not find "_check_clang_format" enabled hook')
2067
2068 def testClangFormatCheckEnabledWithOptions(self):
2069 """Verify clang-format check has options when provided."""
2070 hooks = self.parse("""
2071[Hook Overrides]
2072clang_format_check: true
2073
2074[Hook Overrides Options]
2075clang_format_check:
2076 some_dir/
2077""")
2078 for func in hooks:
2079 if func.__name__ == 'clang_format_check':
2080 self.assertIn('options', func.keywords)
2081 self.assertEqual(func.keywords['options'], ['some_dir/'])
2082 break
2083 else:
2084 self.fail('could not find "clang_format_check" enabled hook')
2085
2086
Jon Salz98255932012-08-18 14:48:02 +08002087if __name__ == '__main__':
Mike Frysinger884a8dd2015-05-17 03:43:43 -04002088 cros_test_lib.main(module=__name__)