blob: 1e3d4cb9c682ae75647dae693c4b87c3f0ed067e [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
Keigo Oka7e880ac2019-07-03 15:03:43 +090011import datetime
David Jamesc3b68b32013-04-03 09:17:03 -070012import os
13import sys
Jon Salz98255932012-08-18 14:48:02 +080014
Mike Frysingerfd481ce2019-09-13 18:14:48 -040015import mock
16
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
Mike Frysingerb2496652019-09-12 23:35:46 -040095class CheckNoLongLinesTest(PreUploadTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -050096 """Tests for _check_no_long_lines."""
97
Jon Salz98255932012-08-18 14:48:02 +080098 def setUp(self):
Mike Frysinger1459d362014-12-06 13:53:23 -050099 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
Jon Salz98255932012-08-18 14:48:02 +0800100
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900101 def testCheck(self):
102 self.PatchObject(pre_upload, '_get_affected_files', return_value=['x.py'])
Mike Frysinger1459d362014-12-06 13:53:23 -0500103 self.diff_mock.return_value = [
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400104 (1, u'x' * 80), # OK
105 (2, '\x80' * 80), # OK
106 (3, u'x' * 81), # Too long
107 (4, '\x80' * 81), # Too long
108 (5, u'See http://' + (u'x' * 80)), # OK (URL)
109 (6, u'See https://' + (u'x' * 80)), # OK (URL)
110 (7, u'# define ' + (u'x' * 80)), # OK (compiler directive)
111 (8, u'#define' + (u'x' * 74)), # Too long
Mike Frysinger1459d362014-12-06 13:53:23 -0500112 ]
Alex Deymo643ac4c2015-09-03 10:40:50 -0700113 failure = pre_upload._check_no_long_lines(ProjectNamed('PROJECT'), 'COMMIT')
Jon Salz98255932012-08-18 14:48:02 +0800114 self.assertTrue(failure)
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900115 self.assertEqual('Found lines longer than the limit (first 5 shown):',
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400116 failure.msg)
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900117 self.assertEqual(['x.py, line %d, 81 chars, over 80 chars' %
118 x for x in [3, 4, 8]],
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400119 failure.items)
Jon Salz98255932012-08-18 14:48:02 +0800120
George Burgess IVf9f79eb2019-07-09 20:12:55 -0700121 def testCheckTreatsOwnersFilesSpecially(self):
122 affected_files = self.PatchObject(pre_upload, '_get_affected_files')
123
124 mock_files = (
125 ('foo-OWNERS', False),
126 ('OWNERS', True),
127 ('/path/to/OWNERS', True),
128 ('/path/to/OWNERS.foo', True),
129 )
130
131 mock_lines = (
132 (u'x' * 81, False),
133 (u'foo file:' + u'x' * 80, True),
134 (u'include ' + u'x' * 80, True),
135 )
136 assert all(len(line) > 80 for line, _ in mock_lines)
137
138 for file_name, is_owners in mock_files:
139 affected_files.return_value = [file_name]
140 for line, is_ok in mock_lines:
141 self.diff_mock.return_value = [(1, line)]
142 failure = pre_upload._check_no_long_lines(ProjectNamed('PROJECT'),
143 'COMMIT')
144
145 assert_msg = 'file: %r; line: %r' % (file_name, line)
146 if is_owners and is_ok:
147 self.assertFalse(failure, assert_msg)
148 else:
149 self.assertTrue(failure, assert_msg)
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900150 self.assertIn('Found lines longer than the limit', failure.msg,
George Burgess IVf9f79eb2019-07-09 20:12:55 -0700151 assert_msg)
152
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900153 def testIncludeOptions(self):
154 self.PatchObject(pre_upload,
155 '_get_affected_files',
156 return_value=['foo.txt'])
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400157 self.diff_mock.return_value = [(1, u'x' * 81)]
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900158 self.assertFalse(pre_upload._check_no_long_lines(
159 ProjectNamed('PROJECT'), 'COMMIT'))
160 self.assertTrue(pre_upload._check_no_long_lines(
161 ProjectNamed('PROJECT'), 'COMMIT', options=['--include_regex=foo']))
162
163 def testExcludeOptions(self):
164 self.PatchObject(pre_upload,
165 '_get_affected_files',
166 return_value=['foo.py'])
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400167 self.diff_mock.return_value = [(1, u'x' * 81)]
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900168 self.assertTrue(pre_upload._check_no_long_lines(
169 ProjectNamed('PROJECT'), 'COMMIT'))
170 self.assertFalse(pre_upload._check_no_long_lines(
171 ProjectNamed('PROJECT'), 'COMMIT', options=['--exclude_regex=foo']))
172
Ayato Tokubi5aae3f72020-01-16 17:43:47 +0900173 def testSpecialLineLength(self):
174 mock_lines = (
175 (u'x' * 101, True),
176 (u'x' * 100, False),
177 (u'x' * 81, False),
178 (u'x' * 80, False),
179 )
180 self.PatchObject(pre_upload,
181 '_get_affected_files',
182 return_value=['foo.java'])
183 for line, is_ok in mock_lines:
184 self.diff_mock.return_value = [(1, line)]
185 if is_ok:
186 self.assertTrue(pre_upload._check_no_long_lines(
187 ProjectNamed('PROJECT'), 'COMMIT'))
188 else:
189 self.assertFalse(pre_upload._check_no_long_lines(
190 ProjectNamed('PROJECT'), 'COMMIT'))
191
Mike Frysingerae409522014-02-01 03:16:11 -0500192
Mike Frysingerb2496652019-09-12 23:35:46 -0400193class CheckTabbedIndentsTest(PreUploadTestCase):
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800194 """Tests for _check_tabbed_indents."""
Mike Frysingerb2496652019-09-12 23:35:46 -0400195
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800196 def setUp(self):
Shuhei Takahashiabc20f32017-07-10 19:35:45 +0900197 self.PatchObject(pre_upload,
198 '_get_affected_files',
199 return_value=['x.ebuild'])
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800200 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
201
202 def test_good_cases(self):
203 self.diff_mock.return_value = [
204 (1, u'no_tabs_anywhere'),
205 (2, u' leading_tab_only'),
206 (3, u' leading_tab another_tab'),
207 (4, u' leading_tab trailing_too '),
208 (5, u' leading_tab then_spaces_trailing '),
209 ]
210 failure = pre_upload._check_tabbed_indents(ProjectNamed('PROJECT'),
211 'COMMIT')
212 self.assertIsNone(failure)
213
214 def test_bad_cases(self):
215 self.diff_mock.return_value = [
216 (1, u' leading_space'),
217 (2, u' tab_followed_by_space'),
218 (3, u' space_followed_by_tab'),
219 (4, u' mix_em_up'),
220 (5, u' mix_on_both_sides '),
221 ]
222 failure = pre_upload._check_tabbed_indents(ProjectNamed('PROJECT'),
223 'COMMIT')
224 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400225 self.assertEqual('Found a space in indentation (must be all tabs):',
226 failure.msg)
227 self.assertEqual(['x.ebuild, line %d' % x for x in range(1, 6)],
228 failure.items)
Prathmesh Prabhuc5254652016-12-22 12:58:05 -0800229
230
Mike Frysingerb2496652019-09-12 23:35:46 -0400231class CheckProjectPrefix(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Daniel Erata350fd32014-09-29 14:02:34 -0700232 """Tests for _check_project_prefix."""
233
234 def setUp(self):
235 self.orig_cwd = os.getcwd()
236 os.chdir(self.tempdir)
237 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
238 self.desc_mock = self.PatchObject(pre_upload, '_get_commit_desc')
239
240 def tearDown(self):
241 os.chdir(self.orig_cwd)
242
243 def _WriteAliasFile(self, filename, project):
244 """Writes a project name to a file, creating directories if needed."""
245 os.makedirs(os.path.dirname(filename))
246 osutils.WriteFile(filename, project)
247
248 def testInvalidPrefix(self):
249 """Report an error when the prefix doesn't match the base directory."""
250 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
251 self.desc_mock.return_value = 'bar: Some commit'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700252 failure = pre_upload._check_project_prefix(ProjectNamed('PROJECT'),
253 'COMMIT')
Daniel Erata350fd32014-09-29 14:02:34 -0700254 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400255 self.assertEqual('The commit title for changes affecting only foo should '
256 'start with "foo: "', failure.msg)
Daniel Erata350fd32014-09-29 14:02:34 -0700257
258 def testValidPrefix(self):
259 """Use a prefix that matches the base directory."""
260 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
261 self.desc_mock.return_value = 'foo: Change some files.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700262 self.assertFalse(
263 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700264
265 def testAliasFile(self):
266 """Use .project_alias to override the project name."""
267 self._WriteAliasFile('foo/.project_alias', 'project')
268 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/bar.cc']
269 self.desc_mock.return_value = 'project: Use an alias.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700270 self.assertFalse(
271 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700272
273 def testAliasFileWithSubdirs(self):
274 """Check that .project_alias is used when only modifying subdirectories."""
275 self._WriteAliasFile('foo/.project_alias', 'project')
276 self.file_mock.return_value = [
277 'foo/subdir/foo.cc',
278 'foo/subdir/bar.cc'
279 'foo/subdir/blah/baz.cc'
280 ]
281 self.desc_mock.return_value = 'project: Alias with subdirs.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700282 self.assertFalse(
283 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700284
285
Mike Frysingerb2496652019-09-12 23:35:46 -0400286class CheckFilePathCharTypeTest(PreUploadTestCase):
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900287 """Tests for _check_filepath_chartype."""
288
289 def setUp(self):
290 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
291
292 def testCheck(self):
293 self.PatchObject(pre_upload, '_get_affected_files', return_value=['x.cc'])
294 self.diff_mock.return_value = [
295 (1, 'base::FilePath'), # OK
296 (2, 'base::FilePath::CharType'), # NG
297 (3, 'base::FilePath::StringType'), # NG
298 (4, 'base::FilePath::StringPieceType'), # NG
Satoru Takabayashi4ca37922018-08-08 10:16:38 +0900299 (5, 'base::FilePath::FromUTF8Unsafe'), # NG
300 (6, 'FilePath::CharType'), # NG
301 (7, 'FilePath::StringType'), # NG
302 (8, 'FilePath::StringPieceType'), # NG
303 (9, 'FilePath::FromUTF8Unsafe'), # NG
304 (10, 'AsUTF8Unsafe'), # NG
305 (11, 'FILE_PATH_LITERAL'), # NG
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900306 ]
307 failure = pre_upload._check_filepath_chartype(ProjectNamed('PROJECT'),
308 'COMMIT')
309 self.assertTrue(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400310 self.assertEqual(
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900311 'Please assume FilePath::CharType is char (crbug.com/870621):',
312 failure.msg)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400313 self.assertEqual(['x.cc, line 2 has base::FilePath::CharType',
314 'x.cc, line 3 has base::FilePath::StringType',
315 'x.cc, line 4 has base::FilePath::StringPieceType',
316 'x.cc, line 5 has base::FilePath::FromUTF8Unsafe',
317 'x.cc, line 6 has FilePath::CharType',
318 'x.cc, line 7 has FilePath::StringType',
319 'x.cc, line 8 has FilePath::StringPieceType',
320 'x.cc, line 9 has FilePath::FromUTF8Unsafe',
321 'x.cc, line 10 has AsUTF8Unsafe',
322 'x.cc, line 11 has FILE_PATH_LITERAL'],
323 failure.items)
Satoru Takabayashi15d17a52018-08-06 11:12:15 +0900324
325
Mike Frysingerb2496652019-09-12 23:35:46 -0400326class CheckKernelConfig(PreUploadTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -0500327 """Tests for _kernel_configcheck."""
328
Mike Frysinger1459d362014-12-06 13:53:23 -0500329 def setUp(self):
330 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
331
332 def testMixedChanges(self):
333 """Mixing of changes should fail."""
334 self.file_mock.return_value = [
335 '/kernel/files/chromeos/config/base.config',
336 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
337 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700338 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
339 self.assertTrue(failure)
340
Mike Frysinger1459d362014-12-06 13:53:23 -0500341 def testCodeOnly(self):
342 """Code-only changes should pass."""
343 self.file_mock.return_value = [
344 '/kernel/files/Makefile',
345 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
346 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700347 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
348 self.assertFalse(failure)
349
Mike Frysinger1459d362014-12-06 13:53:23 -0500350 def testConfigOnlyChanges(self):
351 """Config-only changes should pass."""
352 self.file_mock.return_value = [
353 '/kernel/files/chromeos/config/base.config',
354 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700355 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
356 self.assertFalse(failure)
357
Jon Salz98255932012-08-18 14:48:02 +0800358
Mike Frysingerb2496652019-09-12 23:35:46 -0400359class CheckJson(PreUploadTestCase):
Mike Frysinger908be682018-01-04 02:21:50 -0500360 """Tests for _run_json_check."""
361
362 def setUp(self):
363 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
364 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
365
366 def testNoJson(self):
367 """Nothing should be checked w/no JSON files."""
368 self.file_mock.return_value = [
369 '/foo/bar.txt',
370 '/readme.md',
371 ]
372 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
373 self.assertIsNone(ret)
374
375 def testValidJson(self):
376 """We should accept valid json files."""
377 self.file_mock.return_value = [
378 '/foo/bar.txt',
379 '/data.json',
380 ]
381 self.content_mock.return_value = '{}'
382 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
383 self.assertIsNone(ret)
384 self.content_mock.assert_called_once_with('/data.json', 'COMMIT')
385
386 def testInvalidJson(self):
387 """We should reject invalid json files."""
388 self.file_mock.return_value = [
389 '/foo/bar.txt',
390 '/data.json',
391 ]
392 self.content_mock.return_value = '{'
393 ret = pre_upload._run_json_check('PROJECT', 'COMMIT')
394 self.assertIsNotNone(ret)
395 self.content_mock.assert_called_once_with('/data.json', 'COMMIT')
396
397
Mike Frysingerb2496652019-09-12 23:35:46 -0400398class CheckManifests(PreUploadTestCase):
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500399 """Tests _check_manifests."""
400
401 def setUp(self):
402 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
403 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
404
405 def testNoManifests(self):
406 """Nothing should be checked w/no Manifest files."""
407 self.file_mock.return_value = [
408 '/foo/bar.txt',
409 '/readme.md',
410 '/manifest',
411 '/Manifest.txt',
412 ]
413 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
414 self.assertIsNone(ret)
415
416 def testValidManifest(self):
417 """Accept valid Manifest files."""
418 self.file_mock.return_value = [
419 '/foo/bar.txt',
420 '/cat/pkg/Manifest',
421 ]
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400422 self.content_mock.return_value = """# Comment and blank lines.
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500423
424DIST lines
Mike Frysinger24dd3c52019-08-17 14:22:48 -0400425"""
Mike Frysingeraae3cb52018-01-03 16:49:33 -0500426 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
427 self.assertIsNone(ret)
428 self.content_mock.assert_called_once_with('/cat/pkg/Manifest', 'COMMIT')
429
430 def _testReject(self, content):
431 """Make sure |content| is rejected."""
432 self.file_mock.return_value = ('/Manifest',)
433 self.content_mock.reset_mock()
434 self.content_mock.return_value = content
435 ret = pre_upload._check_manifests('PROJECT', 'COMMIT')
436 self.assertIsNotNone(ret)
437 self.content_mock.assert_called_once_with('/Manifest', 'COMMIT')
438
439 def testRejectBlank(self):
440 """Reject empty manifests."""
441 self._testReject('')
442
443 def testNoTrailingNewLine(self):
444 """Reject manifests w/out trailing newline."""
445 self._testReject('DIST foo')
446
447 def testNoLeadingBlankLines(self):
448 """Reject manifests w/leading blank lines."""
449 self._testReject('\nDIST foo\n')
450
451 def testNoTrailingBlankLines(self):
452 """Reject manifests w/trailing blank lines."""
453 self._testReject('DIST foo\n\n')
454
455 def testNoLeadingWhitespace(self):
456 """Reject manifests w/lines w/leading spaces."""
457 self._testReject(' DIST foo\n')
458 self._testReject(' # Comment\n')
459
460 def testNoTrailingWhitespace(self):
461 """Reject manifests w/lines w/trailing spaces."""
462 self._testReject('DIST foo \n')
463 self._testReject('# Comment \n')
464 self._testReject(' \n')
465
466 def testOnlyDistLines(self):
467 """Only allow DIST lines in here."""
468 self._testReject('EBUILD foo\n')
469
470
Mike Frysingerb2496652019-09-12 23:35:46 -0400471class CheckPortageMakeUseVar(PreUploadTestCase):
Daniel Erat9d203ff2015-02-17 10:12:21 -0700472 """Tests for _check_portage_make_use_var."""
473
474 def setUp(self):
475 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
476 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
477
478 def testMakeConfOmitsOriginalUseValue(self):
479 """Fail for make.conf that discards the previous value of $USE."""
480 self.file_mock.return_value = ['make.conf']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400481 self.content_mock.return_value = u'USE="foo"\nUSE="${USE} bar"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700482 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
483 self.assertTrue(failure, failure)
484
485 def testMakeConfCorrectUsage(self):
486 """Succeed for make.conf that preserves the previous value of $USE."""
487 self.file_mock.return_value = ['make.conf']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400488 self.content_mock.return_value = u'USE="${USE} foo"\nUSE="${USE}" bar'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700489 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
490 self.assertFalse(failure, failure)
491
492 def testMakeDefaultsReferencesOriginalUseValue(self):
493 """Fail for make.defaults that refers to a not-yet-set $USE value."""
494 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400495 self.content_mock.return_value = u'USE="${USE} foo"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700496 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
497 self.assertTrue(failure, failure)
498
499 # Also check for "$USE" without curly brackets.
Mike Frysinger71e643e2019-09-13 17:26:39 -0400500 self.content_mock.return_value = u'USE="$USE foo"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700501 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
502 self.assertTrue(failure, failure)
503
504 def testMakeDefaultsOverwritesUseValue(self):
505 """Fail for make.defaults that discards its own $USE value."""
506 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400507 self.content_mock.return_value = u'USE="foo"\nUSE="bar"'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700508 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
509 self.assertTrue(failure, failure)
510
511 def testMakeDefaultsCorrectUsage(self):
512 """Succeed for make.defaults that sets and preserves $USE."""
513 self.file_mock.return_value = ['make.defaults']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400514 self.content_mock.return_value = u'USE="foo"\nUSE="${USE}" bar'
Daniel Erat9d203ff2015-02-17 10:12:21 -0700515 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
516 self.assertFalse(failure, failure)
517
518
Mike Frysingerb2496652019-09-12 23:35:46 -0400519class CheckEbuildEapi(PreUploadTestCase):
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500520 """Tests for _check_ebuild_eapi."""
521
Alex Deymo643ac4c2015-09-03 10:40:50 -0700522 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500523
524 def setUp(self):
525 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
526 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
527 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff',
528 side_effect=Exception())
529
530 def testSkipUpstreamOverlays(self):
531 """Skip ebuilds found in upstream overlays."""
532 self.file_mock.side_effect = Exception()
533 ret = pre_upload._check_ebuild_eapi(self.PORTAGE_STABLE, 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400534 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500535
536 # Make sure our condition above triggers.
537 self.assertRaises(Exception, pre_upload._check_ebuild_eapi, 'o', 'HEAD')
538
539 def testSkipNonEbuilds(self):
540 """Skip non-ebuild files."""
541 self.content_mock.side_effect = Exception()
542
543 self.file_mock.return_value = ['some-file', 'ebuild/dir', 'an.ebuild~']
Alex Deymo643ac4c2015-09-03 10:40:50 -0700544 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400545 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500546
547 # Make sure our condition above triggers.
548 self.file_mock.return_value.append('a/real.ebuild')
Alex Deymo643ac4c2015-09-03 10:40:50 -0700549 self.assertRaises(Exception, pre_upload._check_ebuild_eapi,
550 ProjectNamed('o'), 'HEAD')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500551
552 def testSkipSymlink(self):
553 """Skip files that are just symlinks."""
554 self.file_mock.return_value = ['a-r1.ebuild']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400555 self.content_mock.return_value = u'a.ebuild'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700556 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400557 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500558
559 def testRejectEapiImplicit0Content(self):
560 """Reject ebuilds that do not declare EAPI (so it's 0)."""
561 self.file_mock.return_value = ['a.ebuild']
562
Mike Frysinger71e643e2019-09-13 17:26:39 -0400563 self.content_mock.return_value = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500564IUSE="foo"
565src_compile() { }
566"""
Alex Deymo643ac4c2015-09-03 10:40:50 -0700567 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500568 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500569
570 def testRejectExplicitEapi1Content(self):
571 """Reject ebuilds that do declare old EAPI explicitly."""
572 self.file_mock.return_value = ['a.ebuild']
573
Mike Frysinger71e643e2019-09-13 17:26:39 -0400574 template = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500575EAPI=%s
576IUSE="foo"
577src_compile() { }
578"""
579 # Make sure we only check the first EAPI= setting.
Mike Frysinger948284a2018-02-01 15:22:56 -0500580 self.content_mock.return_value = template % '1\nEAPI=60'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700581 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500582 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500583
584 # Verify we handle double quotes too.
585 self.content_mock.return_value = template % '"1"'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700586 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500587 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500588
589 # Verify we handle single quotes too.
590 self.content_mock.return_value = template % "'1'"
Alex Deymo643ac4c2015-09-03 10:40:50 -0700591 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500592 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500593
Mike Frysinger948284a2018-02-01 15:22:56 -0500594 def testAcceptExplicitNewEapiContent(self):
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500595 """Accept ebuilds that do declare new EAPI explicitly."""
596 self.file_mock.return_value = ['a.ebuild']
597
Mike Frysinger71e643e2019-09-13 17:26:39 -0400598 template = u"""# Header
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500599EAPI=%s
600IUSE="foo"
601src_compile() { }
602"""
603 # Make sure we only check the first EAPI= setting.
Mike Frysinger948284a2018-02-01 15:22:56 -0500604 self.content_mock.return_value = template % '6\nEAPI=1'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700605 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400606 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500607
608 # Verify we handle double quotes too.
609 self.content_mock.return_value = template % '"5"'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700610 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400611 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500612
613 # Verify we handle single quotes too.
614 self.content_mock.return_value = template % "'5-hdepend'"
Alex Deymo643ac4c2015-09-03 10:40:50 -0700615 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400616 self.assertIsNone(ret)
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500617
618
Mike Frysingerb2496652019-09-12 23:35:46 -0400619class CheckEbuildKeywords(PreUploadTestCase):
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400620 """Tests for _check_ebuild_keywords."""
621
622 def setUp(self):
623 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
624 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
625
626 def testNoEbuilds(self):
627 """If no ebuilds are found, do not scan."""
628 self.file_mock.return_value = ['a.file', 'ebuild-is-not.foo']
629
Alex Deymo643ac4c2015-09-03 10:40:50 -0700630 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400631 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400632
633 self.assertEqual(self.content_mock.call_count, 0)
634
635 def testSomeEbuilds(self):
636 """If ebuilds are found, only scan them."""
637 self.file_mock.return_value = ['a.file', 'blah', 'foo.ebuild', 'cow']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400638 self.content_mock.return_value = u''
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400639
Alex Deymo643ac4c2015-09-03 10:40:50 -0700640 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400641 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400642
643 self.assertEqual(self.content_mock.call_count, 1)
644
645 def _CheckContent(self, content, fails):
646 """Test helper for inputs/outputs.
647
648 Args:
649 content: The ebuild content to test.
650 fails: Whether |content| should trigger a hook failure.
651 """
652 self.file_mock.return_value = ['a.ebuild']
653 self.content_mock.return_value = content
654
Alex Deymo643ac4c2015-09-03 10:40:50 -0700655 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400656 if fails:
Mike Frysingerb81102f2014-11-21 00:33:35 -0500657 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400658 else:
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400659 self.assertIsNone(ret)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400660
661 self.assertEqual(self.content_mock.call_count, 1)
662
663 def testEmpty(self):
664 """Check KEYWORDS= is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400665 self._CheckContent(u'# HEADER\nKEYWORDS=\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400666
667 def testEmptyQuotes(self):
668 """Check KEYWORDS="" is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400669 self._CheckContent(u'# HEADER\nKEYWORDS=" "\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400670
671 def testStableGlob(self):
672 """Check KEYWORDS=* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400673 self._CheckContent(u'# HEADER\nKEYWORDS="\t*\t"\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400674
675 def testUnstableGlob(self):
676 """Check KEYWORDS=~* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400677 self._CheckContent(u'# HEADER\nKEYWORDS="~* "\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400678
679 def testRestrictedGlob(self):
680 """Check KEYWORDS=-* is accepted."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400681 self._CheckContent(u'# HEADER\nKEYWORDS="\t-* arm"\nblah\n', False)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400682
683 def testMissingGlobs(self):
684 """Reject KEYWORDS missing any globs."""
Mike Frysinger71e643e2019-09-13 17:26:39 -0400685 self._CheckContent(u'# HEADER\nKEYWORDS="~arm x86"\nblah\n', True)
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400686
687
Mike Frysingerb2496652019-09-12 23:35:46 -0400688class CheckEbuildVirtualPv(PreUploadTestCase):
Mike Frysingercd363c82014-02-01 05:20:18 -0500689 """Tests for _check_ebuild_virtual_pv."""
690
Alex Deymo643ac4c2015-09-03 10:40:50 -0700691 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
692 CHROMIUMOS_OVERLAY = ProjectNamed('chromiumos/overlays/chromiumos')
693 BOARD_OVERLAY = ProjectNamed('chromiumos/overlays/board-overlays')
694 PRIVATE_OVERLAY = ProjectNamed('chromeos/overlays/overlay-link-private')
695 PRIVATE_VARIANT_OVERLAY = ProjectNamed('chromeos/overlays/'
696 'overlay-variant-daisy-spring-private')
Mike Frysingercd363c82014-02-01 05:20:18 -0500697
698 def setUp(self):
699 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
700
701 def testNoVirtuals(self):
702 """Skip non virtual packages."""
703 self.file_mock.return_value = ['some/package/package-3.ebuild']
Alex Deymo643ac4c2015-09-03 10:40:50 -0700704 ret = pre_upload._check_ebuild_virtual_pv(ProjectNamed('overlay'), 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400705 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500706
707 def testCommonVirtuals(self):
708 """Non-board overlays should use PV=1."""
709 template = 'virtual/foo/foo-%s.ebuild'
710 self.file_mock.return_value = [template % '1']
711 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400712 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500713
714 self.file_mock.return_value = [template % '2']
715 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500716 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500717
718 def testPublicBoardVirtuals(self):
719 """Public board overlays should use PV=2."""
720 template = 'overlay-lumpy/virtual/foo/foo-%s.ebuild'
721 self.file_mock.return_value = [template % '2']
722 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400723 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500724
725 self.file_mock.return_value = [template % '2.5']
726 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500727 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500728
729 def testPublicBoardVariantVirtuals(self):
730 """Public board variant overlays should use PV=2.5."""
731 template = 'overlay-variant-lumpy-foo/virtual/foo/foo-%s.ebuild'
732 self.file_mock.return_value = [template % '2.5']
733 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400734 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500735
736 self.file_mock.return_value = [template % '3']
737 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500738 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500739
740 def testPrivateBoardVirtuals(self):
741 """Private board overlays should use PV=3."""
742 template = 'virtual/foo/foo-%s.ebuild'
743 self.file_mock.return_value = [template % '3']
744 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400745 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500746
747 self.file_mock.return_value = [template % '3.5']
748 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500749 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500750
751 def testPrivateBoardVariantVirtuals(self):
752 """Private board variant overlays should use PV=3.5."""
753 template = 'virtual/foo/foo-%s.ebuild'
754 self.file_mock.return_value = [template % '3.5']
755 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400756 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500757
Bernie Thompsone5ee1822016-01-12 14:22:23 -0800758 def testSpecialVirtuals(self):
759 """Some cases require deeper versioning and can be >= 4."""
760 template = 'virtual/foo/foo-%s.ebuild'
Mike Frysingercd363c82014-02-01 05:20:18 -0500761 self.file_mock.return_value = [template % '4']
762 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400763 self.assertIsNone(ret)
Mike Frysingercd363c82014-02-01 05:20:18 -0500764
Bernie Thompsone5ee1822016-01-12 14:22:23 -0800765 self.file_mock.return_value = [template % '4.5']
766 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400767 self.assertIsNone(ret)
Mike Frysinger98638102014-08-28 00:15:08 -0400768
Mike Frysingerb2496652019-09-12 23:35:46 -0400769class CheckCrosLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -0700770 """Tests for _check_cros_license."""
Mike Frysinger98638102014-08-28 00:15:08 -0400771
772 def setUp(self):
773 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
774 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
775
776 def testOldHeaders(self):
777 """Accept old header styles."""
778 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400779 (u'#!/bin/sh\n'
780 u'# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.\n'
781 u'# Use of this source code is governed by a BSD-style license that'
782 u' can be\n'
783 u'# found in the LICENSE file.\n'),
784 (u'// Copyright 2010-2013 The Chromium OS Authors. All rights reserved.'
785 u'\n// Use of this source code is governed by a BSD-style license that'
786 u' can be\n'
787 u'// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -0400788 )
789 self.file_mock.return_value = ['file']
790 for header in HEADERS:
791 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +0900792 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
793
794 def testNewFileYear(self):
795 """Added files should have the current year in license header."""
796 year = datetime.datetime.now().year
797 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400798 (u'// Copyright 2016 The Chromium OS Authors. All rights reserved.\n'
799 u'// Use of this source code is governed by a BSD-style license that'
800 u' can be\n'
801 u'// found in the LICENSE file.\n'),
802 (u'// Copyright %d The Chromium OS Authors. All rights reserved.\n'
803 u'// Use of this source code is governed by a BSD-style license that'
804 u' can be\n'
805 u'// found in the LICENSE file.\n') % year,
Keigo Oka7e880ac2019-07-03 15:03:43 +0900806 )
807 want_error = (True, False)
808 def fake_get_affected_files(_, relative, include_adds=True):
809 _ = relative
810 if include_adds:
811 return ['file']
812 else:
813 return []
814
815 self.file_mock.side_effect = fake_get_affected_files
816 for i, header in enumerate(HEADERS):
817 self.content_mock.return_value = header
818 if want_error[i]:
819 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
820 else:
821 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -0400822
823 def testRejectC(self):
824 """Reject the (c) in newer headers."""
825 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400826 (u'// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.'
827 u'\n'
828 u'// Use of this source code is governed by a BSD-style license that'
829 u' can be\n'
830 u'// found in the LICENSE file.\n'),
831 (u'// Copyright (c) 2020 The Chromium OS Authors. All rights reserved.'
832 u'\n'
833 u'// Use of this source code is governed by a BSD-style license that'
834 u' can be\n'
835 u'// found in the LICENSE file.\n'),
Mike Frysinger98638102014-08-28 00:15:08 -0400836 )
837 self.file_mock.return_value = ['file']
838 for header in HEADERS:
839 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +0900840 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -0700841
Brian Norris68838dd2018-09-26 18:30:24 -0700842 def testNoLeadingSpace(self):
843 """Allow headers without leading space (e.g., not a source comment)"""
844 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400845 (u'Copyright 2018 The Chromium OS Authors. All rights reserved.\n'
846 u'Use of this source code is governed by a BSD-style license that '
847 u'can be\n'
848 u'found in the LICENSE file.\n'),
Brian Norris68838dd2018-09-26 18:30:24 -0700849 )
850 self.file_mock.return_value = ['file']
851 for header in HEADERS:
852 self.content_mock.return_value = header
Keigo Oka7e880ac2019-07-03 15:03:43 +0900853 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Brian Norris68838dd2018-09-26 18:30:24 -0700854
Keigo Oka9732e382019-06-28 17:44:59 +0900855 def testNoExcludedGolang(self):
856 """Don't exclude .go files for license checks."""
857 self.file_mock.return_value = ['foo/main.go']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400858 self.content_mock.return_value = u'package main\nfunc main() {}'
Keigo Oka7e880ac2019-07-03 15:03:43 +0900859 self.assertTrue(pre_upload._check_cros_license('proj', 'sha1'))
Keigo Oka9732e382019-06-28 17:44:59 +0900860
Ken Turnerd07564b2018-02-08 17:57:59 +1100861 def testIgnoreExcludedPaths(self):
862 """Ignores excluded paths for license checks."""
863 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400864 self.content_mock.return_value = u'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +0900865 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +1100866
Chris McDonald7b63c8e2019-04-25 10:27:27 -0600867 def testIgnoreTopLevelExcludedPaths(self):
868 """Ignores excluded paths for license checks."""
869 self.file_mock.return_value = ['OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400870 self.content_mock.return_value = u'owner@chromium.org'
Keigo Oka7e880ac2019-07-03 15:03:43 +0900871 self.assertFalse(pre_upload._check_cros_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -0700872
Mike Frysingerb2496652019-09-12 23:35:46 -0400873class CheckAOSPLicenseCopyrightHeader(PreUploadTestCase):
Alex Deymof5792ce2015-08-24 22:50:08 -0700874 """Tests for _check_aosp_license."""
875
876 def setUp(self):
877 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
878 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
879
880 def testHeaders(self):
881 """Accept old header styles."""
882 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400883 u"""//
Alex Deymof5792ce2015-08-24 22:50:08 -0700884// Copyright (C) 2011 The Android Open Source Project
885//
886// Licensed under the Apache License, Version 2.0 (the "License");
887// you may not use this file except in compliance with the License.
888// You may obtain a copy of the License at
889//
890// http://www.apache.org/licenses/LICENSE-2.0
891//
892// Unless required by applicable law or agreed to in writing, software
893// distributed under the License is distributed on an "AS IS" BASIS,
894// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
895// See the License for the specific language governing permissions and
896// limitations under the License.
897//
898""",
Mike Frysinger71e643e2019-09-13 17:26:39 -0400899 u"""#
Alex Deymof5792ce2015-08-24 22:50:08 -0700900# Copyright (c) 2015 The Android Open Source Project
901#
902# Licensed under the Apache License, Version 2.0 (the "License");
903# you may not use this file except in compliance with the License.
904# You may obtain a copy of the License at
905#
906# http://www.apache.org/licenses/LICENSE-2.0
907#
908# Unless required by applicable law or agreed to in writing, software
909# distributed under the License is distributed on an "AS IS" BASIS,
910# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
911# See the License for the specific language governing permissions and
912# limitations under the License.
913#
914""",
915 )
916 self.file_mock.return_value = ['file']
917 for header in HEADERS:
918 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400919 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Alex Deymof5792ce2015-08-24 22:50:08 -0700920
921 def testRejectNoLinesAround(self):
922 """Reject headers missing the empty lines before/after the license."""
923 HEADERS = (
Mike Frysinger71e643e2019-09-13 17:26:39 -0400924 u"""# Copyright (c) 2015 The Android Open Source Project
Alex Deymof5792ce2015-08-24 22:50:08 -0700925#
926# Licensed under the Apache License, Version 2.0 (the "License");
927# you may not use this file except in compliance with the License.
928# You may obtain a copy of the License at
929#
930# http://www.apache.org/licenses/LICENSE-2.0
931#
932# Unless required by applicable law or agreed to in writing, software
933# distributed under the License is distributed on an "AS IS" BASIS,
934# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
935# See the License for the specific language governing permissions and
936# limitations under the License.
937""",
938 )
939 self.file_mock.return_value = ['file']
940 for header in HEADERS:
941 self.content_mock.return_value = header
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400942 self.assertIsNotNone(pre_upload._check_aosp_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -0400943
Ken Turnerd07564b2018-02-08 17:57:59 +1100944 def testIgnoreExcludedPaths(self):
945 """Ignores excluded paths for license checks."""
946 self.file_mock.return_value = ['foo/OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400947 self.content_mock.return_value = u'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400948 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
Ken Turnerd07564b2018-02-08 17:57:59 +1100949
Chris McDonald7b63c8e2019-04-25 10:27:27 -0600950 def testIgnoreTopLevelExcludedPaths(self):
951 """Ignores excluded paths for license checks."""
952 self.file_mock.return_value = ['OWNERS']
Mike Frysinger71e643e2019-09-13 17:26:39 -0400953 self.content_mock.return_value = u'owner@chromium.org'
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400954 self.assertIsNone(pre_upload._check_aosp_license('proj', 'sha1'))
955
Mike Frysinger98638102014-08-28 00:15:08 -0400956
Mike Frysingerb2496652019-09-12 23:35:46 -0400957class CheckLayoutConfTestCase(PreUploadTestCase):
Mike Frysingerd7734522015-02-26 16:12:43 -0500958 """Tests for _check_layout_conf."""
959
960 def setUp(self):
961 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
962 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
963
964 def assertAccepted(self, files, project='project', commit='fake sha1'):
965 """Assert _check_layout_conf accepts |files|."""
966 self.file_mock.return_value = files
967 ret = pre_upload._check_layout_conf(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -0400968 self.assertIsNone(ret, msg='rejected with:\n%s' % ret)
Mike Frysingerd7734522015-02-26 16:12:43 -0500969
970 def assertRejected(self, files, project='project', commit='fake sha1'):
971 """Assert _check_layout_conf rejects |files|."""
972 self.file_mock.return_value = files
973 ret = pre_upload._check_layout_conf(project, commit)
974 self.assertTrue(isinstance(ret, errors.HookFailure))
975
976 def GetLayoutConf(self, filters=()):
977 """Return a valid layout.conf with |filters| lines removed."""
978 all_lines = [
Mike Frysinger71e643e2019-09-13 17:26:39 -0400979 u'masters = portage-stable chromiumos',
980 u'profile-formats = portage-2 profile-default-eapi',
981 u'profile_eapi_when_unspecified = 5-progress',
982 u'repo-name = link',
983 u'thin-manifests = true',
984 u'use-manifests = strict',
Mike Frysingerd7734522015-02-26 16:12:43 -0500985 ]
986
987 lines = []
988 for line in all_lines:
989 for filt in filters:
990 if line.startswith(filt):
991 break
992 else:
993 lines.append(line)
994
Mike Frysinger71e643e2019-09-13 17:26:39 -0400995 return u'\n'.join(lines)
Mike Frysingerd7734522015-02-26 16:12:43 -0500996
997 def testNoFilesToCheck(self):
998 """Don't blow up when there are no layout.conf files."""
999 self.assertAccepted([])
1000
1001 def testRejectRepoNameFile(self):
1002 """If profiles/repo_name is set, kick it out."""
1003 self.assertRejected(['profiles/repo_name'])
1004
1005 def testAcceptValidLayoutConf(self):
1006 """Accept a fully valid layout.conf."""
1007 self.content_mock.return_value = self.GetLayoutConf()
1008 self.assertAccepted(['metadata/layout.conf'])
1009
1010 def testAcceptUnknownKeys(self):
1011 """Accept keys we don't explicitly know about."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001012 self.content_mock.return_value = self.GetLayoutConf() + u'\nzzz-top = ok'
Mike Frysingerd7734522015-02-26 16:12:43 -05001013 self.assertAccepted(['metadata/layout.conf'])
1014
1015 def testRejectUnsorted(self):
1016 """Reject an unsorted layout.conf."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001017 self.content_mock.return_value = u'zzz-top = bad\n' + self.GetLayoutConf()
Mike Frysingerd7734522015-02-26 16:12:43 -05001018 self.assertRejected(['metadata/layout.conf'])
1019
1020 def testRejectMissingThinManifests(self):
1021 """Reject a layout.conf missing thin-manifests."""
1022 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001023 filters=[u'thin-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001024 self.assertRejected(['metadata/layout.conf'])
1025
1026 def testRejectMissingUseManifests(self):
1027 """Reject a layout.conf missing use-manifests."""
1028 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001029 filters=[u'use-manifests'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001030 self.assertRejected(['metadata/layout.conf'])
1031
1032 def testRejectMissingEapiFallback(self):
1033 """Reject a layout.conf missing profile_eapi_when_unspecified."""
1034 self.content_mock.return_value = self.GetLayoutConf(
Mike Frysinger71e643e2019-09-13 17:26:39 -04001035 filters=[u'profile_eapi_when_unspecified'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001036 self.assertRejected(['metadata/layout.conf'])
1037
1038 def testRejectMissingRepoName(self):
1039 """Reject a layout.conf missing repo-name."""
Mike Frysinger71e643e2019-09-13 17:26:39 -04001040 self.content_mock.return_value = self.GetLayoutConf(filters=[u'repo-name'])
Mike Frysingerd7734522015-02-26 16:12:43 -05001041 self.assertRejected(['metadata/layout.conf'])
1042
1043
Mike Frysingerb2496652019-09-12 23:35:46 -04001044class CommitMessageTestCase(PreUploadTestCase):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001045 """Test case for funcs that check commit messages."""
1046
1047 def setUp(self):
1048 self.msg_mock = self.PatchObject(pre_upload, '_get_commit_desc')
1049
1050 @staticmethod
1051 def CheckMessage(_project, _commit):
1052 raise AssertionError('Test class must declare CheckMessage')
1053 # This dummy return is to silence pylint warning W1111 so we don't have to
1054 # enable it for all the call sites below.
1055 return 1 # pylint: disable=W0101
1056
Alex Deymo643ac4c2015-09-03 10:40:50 -07001057 def assertMessageAccepted(self, msg, project=ProjectNamed('project'),
1058 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001059 """Assert _check_change_has_bug_field accepts |msg|."""
1060 self.msg_mock.return_value = msg
1061 ret = self.CheckMessage(project, commit)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001062 self.assertIsNone(ret)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001063
Alex Deymo643ac4c2015-09-03 10:40:50 -07001064 def assertMessageRejected(self, msg, project=ProjectNamed('project'),
1065 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001066 """Assert _check_change_has_bug_field rejects |msg|."""
1067 self.msg_mock.return_value = msg
1068 ret = self.CheckMessage(project, commit)
1069 self.assertTrue(isinstance(ret, errors.HookFailure))
1070
1071
1072class CheckCommitMessageBug(CommitMessageTestCase):
1073 """Tests for _check_change_has_bug_field."""
1074
Alex Deymo643ac4c2015-09-03 10:40:50 -07001075 AOSP_PROJECT = pre_upload.Project(name='overlay', dir='', remote='aosp')
1076 CROS_PROJECT = pre_upload.Project(name='overlay', dir='', remote='cros')
1077
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001078 @staticmethod
1079 def CheckMessage(project, commit):
1080 return pre_upload._check_change_has_bug_field(project, commit)
1081
1082 def testNormal(self):
1083 """Accept a commit message w/a valid BUG."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001084 self.assertMessageAccepted('\nBUG=chromium:1234\n', self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001085 self.assertMessageAccepted('\nBUG=b:1234\n', self.CROS_PROJECT)
1086
1087 self.assertMessageAccepted('\nBug: 1234\n', self.AOSP_PROJECT)
1088 self.assertMessageAccepted('\nBug:1234\n', self.AOSP_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001089
1090 def testNone(self):
1091 """Accept BUG=None."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001092 self.assertMessageAccepted('\nBUG=None\n', self.CROS_PROJECT)
1093 self.assertMessageAccepted('\nBUG=none\n', self.CROS_PROJECT)
1094 self.assertMessageAccepted('\nBug: None\n', self.AOSP_PROJECT)
1095 self.assertMessageAccepted('\nBug:none\n', self.AOSP_PROJECT)
1096
1097 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1098 self.assertMessageRejected('\nBUG=NONE\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001099
1100 def testBlank(self):
1101 """Reject blank values."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001102 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1103 self.assertMessageRejected('\nBUG=\n', project)
1104 self.assertMessageRejected('\nBUG= \n', project)
1105 self.assertMessageRejected('\nBug:\n', project)
1106 self.assertMessageRejected('\nBug: \n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001107
1108 def testNotFirstLine(self):
1109 """Reject the first line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001110 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1111 self.assertMessageRejected('BUG=None\n\n\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001112
1113 def testNotInline(self):
1114 """Reject not at the start of line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001115 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
1116 self.assertMessageRejected('\n BUG=None\n', project)
1117 self.assertMessageRejected('\n\tBUG=None\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001118
1119 def testOldTrackers(self):
1120 """Reject commit messages using old trackers."""
Mike Frysingera2f28252017-10-27 22:26:14 -04001121 self.assertMessageRejected('\nBUG=chrome-os-partner:1234\n',
1122 self.CROS_PROJECT)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001123 self.assertMessageRejected('\nBUG=chromium-os:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001124
1125 def testNoTrackers(self):
1126 """Reject commit messages w/invalid trackers."""
Alex Deymo643ac4c2015-09-03 10:40:50 -07001127 self.assertMessageRejected('\nBUG=booga:1234\n', self.CROS_PROJECT)
1128 self.assertMessageRejected('\nBUG=br:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001129
1130 def testMissing(self):
1131 """Reject commit messages w/no BUG line."""
1132 self.assertMessageRejected('foo\n')
1133
1134 def testCase(self):
1135 """Reject bug lines that are not BUG."""
1136 self.assertMessageRejected('\nbug=none\n')
1137
Cheng Yuehb707c522020-01-02 14:06:59 +08001138 def testNotAfterTest(self):
1139 """Reject any TEST line before any BUG line."""
1140 test_field = 'TEST=i did not do it\n'
1141 middle_field = 'A random between line\n'
1142 for project, bug_field in ((self.AOSP_PROJECT, 'Bug:none\n'),
1143 (self.CROS_PROJECT, 'BUG=None\n')):
1144 self.assertMessageRejected(
1145 '\n' + test_field + middle_field + bug_field, project)
1146 self.assertMessageRejected(
1147 '\n' + test_field + bug_field, project)
1148
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001149
1150class CheckCommitMessageCqDepend(CommitMessageTestCase):
1151 """Tests for _check_change_has_valid_cq_depend."""
1152
1153 @staticmethod
1154 def CheckMessage(project, commit):
1155 return pre_upload._check_change_has_valid_cq_depend(project, commit)
1156
1157 def testNormal(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001158 """Accept valid Cq-Depends line."""
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001159 self.assertMessageAccepted('\nCq-Depend: chromium:1234\nChange-Id: I123')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001160
1161 def testInvalid(self):
Jason D. Clinton299e3222019-05-23 09:42:03 -06001162 """Reject invalid Cq-Depends line."""
1163 self.assertMessageRejected('\nCq-Depend=chromium=1234\n')
1164 self.assertMessageRejected('\nCq-Depend: None\n')
Luigi Semenzatob8c7d7d2019-06-03 09:43:21 -07001165 self.assertMessageRejected('\nCq-Depend: chromium:1234\n\nChange-Id: I123')
Mike Frysingere39d0cd2019-11-25 13:30:06 -05001166 self.assertMessageRejected('\nCQ-DEPEND=1234\n')
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001167
1168
Bernie Thompsonf8fea992016-01-14 10:27:18 -08001169class CheckCommitMessageContribution(CommitMessageTestCase):
1170 """Tests for _check_change_is_contribution."""
1171
1172 @staticmethod
1173 def CheckMessage(project, commit):
1174 return pre_upload._check_change_is_contribution(project, commit)
1175
1176 def testNormal(self):
1177 """Accept a commit message which is a contribution."""
1178 self.assertMessageAccepted('\nThis is cool code I am contributing.\n')
1179
1180 def testFailureLowerCase(self):
1181 """Deny a commit message which is not a contribution."""
1182 self.assertMessageRejected('\nThis is not a contribution.\n')
1183
1184 def testFailureUpperCase(self):
1185 """Deny a commit message which is not a contribution."""
1186 self.assertMessageRejected('\nNOT A CONTRIBUTION\n')
1187
1188
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001189class CheckCommitMessageTest(CommitMessageTestCase):
1190 """Tests for _check_change_has_test_field."""
1191
1192 @staticmethod
1193 def CheckMessage(project, commit):
1194 return pre_upload._check_change_has_test_field(project, commit)
1195
1196 def testNormal(self):
1197 """Accept a commit message w/a valid TEST."""
1198 self.assertMessageAccepted('\nTEST=i did it\n')
1199
1200 def testNone(self):
1201 """Accept TEST=None."""
1202 self.assertMessageAccepted('\nTEST=None\n')
1203 self.assertMessageAccepted('\nTEST=none\n')
1204
1205 def testBlank(self):
1206 """Reject blank values."""
1207 self.assertMessageRejected('\nTEST=\n')
1208 self.assertMessageRejected('\nTEST= \n')
1209
1210 def testNotFirstLine(self):
1211 """Reject the first line."""
1212 self.assertMessageRejected('TEST=None\n\n\n')
1213
1214 def testNotInline(self):
1215 """Reject not at the start of line."""
1216 self.assertMessageRejected('\n TEST=None\n')
1217 self.assertMessageRejected('\n\tTEST=None\n')
1218
1219 def testMissing(self):
1220 """Reject commit messages w/no TEST line."""
1221 self.assertMessageRejected('foo\n')
1222
1223 def testCase(self):
1224 """Reject bug lines that are not TEST."""
1225 self.assertMessageRejected('\ntest=none\n')
1226
1227
1228class CheckCommitMessageChangeId(CommitMessageTestCase):
1229 """Tests for _check_change_has_proper_changeid."""
1230
1231 @staticmethod
1232 def CheckMessage(project, commit):
1233 return pre_upload._check_change_has_proper_changeid(project, commit)
1234
1235 def testNormal(self):
1236 """Accept a commit message w/a valid Change-Id."""
1237 self.assertMessageAccepted('foo\n\nChange-Id: I1234\n')
1238
1239 def testBlank(self):
1240 """Reject blank values."""
1241 self.assertMessageRejected('\nChange-Id:\n')
1242 self.assertMessageRejected('\nChange-Id: \n')
1243
1244 def testNotFirstLine(self):
1245 """Reject the first line."""
1246 self.assertMessageRejected('TEST=None\n\n\n')
1247
1248 def testNotInline(self):
1249 """Reject not at the start of line."""
1250 self.assertMessageRejected('\n Change-Id: I1234\n')
1251 self.assertMessageRejected('\n\tChange-Id: I1234\n')
1252
1253 def testMissing(self):
1254 """Reject commit messages missing the line."""
1255 self.assertMessageRejected('foo\n')
1256
1257 def testCase(self):
1258 """Reject bug lines that are not Change-Id."""
1259 self.assertMessageRejected('\nchange-id: I1234\n')
1260 self.assertMessageRejected('\nChange-id: I1234\n')
1261 self.assertMessageRejected('\nChange-ID: I1234\n')
1262
1263 def testEnd(self):
1264 """Reject Change-Id's that are not last."""
1265 self.assertMessageRejected('\nChange-Id: I1234\nbar\n')
1266
Mike Frysinger02b88bd2014-11-21 00:29:38 -05001267 def testSobTag(self):
1268 """Permit s-o-b tags to follow the Change-Id."""
1269 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nSigned-off-by: Hi\n')
1270
LaMont Jones237f3ef2020-01-22 10:40:52 -07001271 def testCqClTag(self):
1272 """Permit Cq-Cl-Tag tags to follow the Change-Id."""
1273 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nCq-Cl-Tag: Hi\n')
1274
Mike Frysinger4a22bf02014-10-31 13:53:35 -04001275
Jack Neus8edbf642019-07-10 16:08:31 -06001276class CheckCommitMessageNoOEM(CommitMessageTestCase):
1277 """Tests for _check_change_no_include_oem."""
1278
1279 @staticmethod
1280 def CheckMessage(project, commit):
1281 return pre_upload._check_change_no_include_oem(project, commit)
1282
1283 def testNormal(self):
1284 """Accept a commit message w/o reference to an OEM/ODM."""
1285 self.assertMessageAccepted('foo\n')
1286
1287 def testHasOEM(self):
1288 """Catch commit messages referencing OEMs."""
1289 self.assertMessageRejected('HP Project\n\n')
1290 self.assertMessageRejected('hewlett-packard\n')
1291 self.assertMessageRejected('Hewlett\nPackard\n')
1292 self.assertMessageRejected('Dell Chromebook\n\n\n')
1293 self.assertMessageRejected('product@acer.com\n')
1294 self.assertMessageRejected('This is related to Asus\n')
1295 self.assertMessageRejected('lenovo machine\n')
1296
1297 def testHasODM(self):
1298 """Catch commit messages referencing ODMs."""
1299 self.assertMessageRejected('new samsung laptop\n\n')
1300 self.assertMessageRejected('pegatron(ems) project\n')
1301 self.assertMessageRejected('new Wistron device\n')
1302
1303 def testContainsOEM(self):
1304 """Check that the check handles word boundaries properly."""
1305 self.assertMessageAccepted('oheahpohea')
1306 self.assertMessageAccepted('Play an Asus7 guitar chord.\n\n')
1307
1308 def testTag(self):
1309 """Check that the check ignores tags."""
1310 self.assertMessageAccepted(
1311 'Harmless project\n'
1312 'Reviewed-by: partner@asus.corp-partner.google.com\n'
1313 'Tested-by: partner@hp.corp-partner.google.com\n'
1314 'Signed-off-by: partner@dell.corp-partner.google.com\n'
1315 'Commit-Queue: partner@lenovo.corp-partner.google.com\n'
Jack Neus8edbf642019-07-10 16:08:31 -06001316 'CC: partner@acer.corp-partner.google.com\n'
1317 'Acked-by: partner@hewlett-packard.corp-partner.google.com\n')
1318 self.assertMessageRejected(
1319 'Asus project\n'
1320 'Reviewed-by: partner@asus.corp-partner.google.com')
Mike Frysingerbb34a222019-07-31 14:40:46 -04001321 self.assertMessageRejected(
1322 'my project\n'
1323 'Bad-tag: partner@asus.corp-partner.google.com')
Jack Neus8edbf642019-07-10 16:08:31 -06001324
1325
Mike Frysinger36b2ebc2014-10-31 14:02:03 -04001326class CheckCommitMessageStyle(CommitMessageTestCase):
1327 """Tests for _check_commit_message_style."""
1328
1329 @staticmethod
1330 def CheckMessage(project, commit):
1331 return pre_upload._check_commit_message_style(project, commit)
1332
1333 def testNormal(self):
1334 """Accept valid commit messages."""
1335 self.assertMessageAccepted('one sentence.\n')
1336 self.assertMessageAccepted('some.module: do it!\n')
1337 self.assertMessageAccepted('one line\n\nmore stuff here.')
1338
1339 def testNoBlankSecondLine(self):
1340 """Reject messages that have stuff on the second line."""
1341 self.assertMessageRejected('one sentence.\nbad fish!\n')
1342
1343 def testFirstLineMultipleSentences(self):
1344 """Reject messages that have more than one sentence in the summary."""
1345 self.assertMessageRejected('one sentence. two sentence!\n')
1346
1347 def testFirstLineTooLone(self):
1348 """Reject first lines that are too long."""
1349 self.assertMessageRejected('o' * 200)
1350
1351
Mike Frysinger292b45d2014-11-25 01:17:10 -05001352def DiffEntry(src_file=None, dst_file=None, src_mode=None, dst_mode='100644',
1353 status='M'):
1354 """Helper to create a stub RawDiffEntry object"""
1355 if src_mode is None:
1356 if status == 'A':
1357 src_mode = '000000'
1358 elif status == 'M':
1359 src_mode = dst_mode
1360 elif status == 'D':
1361 src_mode = dst_mode
1362 dst_mode = '000000'
1363
1364 src_sha = dst_sha = 'abc'
1365 if status == 'D':
1366 dst_sha = '000000'
1367 elif status == 'A':
1368 src_sha = '000000'
1369
1370 return git.RawDiffEntry(src_mode=src_mode, dst_mode=dst_mode, src_sha=src_sha,
1371 dst_sha=dst_sha, status=status, score=None,
1372 src_file=src_file, dst_file=dst_file)
1373
1374
Mike Frysingerb2496652019-09-12 23:35:46 -04001375class HelpersTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001376 """Various tests for utility functions."""
1377
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001378 def setUp(self):
1379 self.orig_cwd = os.getcwd()
1380 os.chdir(self.tempdir)
1381
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001382 self.PatchObject(git, 'RawDiff', return_value=[
1383 # A modified normal file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001384 DiffEntry(src_file='buildbot/constants.py', status='M'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001385 # A new symlink file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001386 DiffEntry(dst_file='scripts/cros_env_whitelist', dst_mode='120000',
1387 status='A'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001388 # A deleted file.
Mike Frysinger292b45d2014-11-25 01:17:10 -05001389 DiffEntry(src_file='scripts/sync_sonic.py', status='D'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001390 ])
1391
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001392 def tearDown(self):
1393 os.chdir(self.orig_cwd)
1394
1395 def _WritePresubmitIgnoreFile(self, subdir, data):
1396 """Writes to a .presubmitignore file in the passed-in subdirectory."""
1397 directory = os.path.join(self.tempdir, subdir)
1398 if not os.path.exists(directory):
1399 os.makedirs(directory)
1400 osutils.WriteFile(os.path.join(directory, pre_upload._IGNORE_FILE), data)
1401
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001402 def testGetAffectedFilesNoDeletesNoRelative(self):
1403 """Verify _get_affected_files() works w/no delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001404 path = os.getcwd()
1405 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1406 relative=False)
1407 exp_files = [os.path.join(path, 'buildbot/constants.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001408 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001409
1410 def testGetAffectedFilesDeletesNoRelative(self):
1411 """Verify _get_affected_files() works w/delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001412 path = os.getcwd()
1413 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1414 relative=False)
1415 exp_files = [os.path.join(path, 'buildbot/constants.py'),
1416 os.path.join(path, 'scripts/sync_sonic.py')]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001417 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001418
1419 def testGetAffectedFilesNoDeletesRelative(self):
1420 """Verify _get_affected_files() works w/no delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001421 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1422 relative=True)
1423 exp_files = ['buildbot/constants.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001424 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001425
1426 def testGetAffectedFilesDeletesRelative(self):
1427 """Verify _get_affected_files() works w/delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001428 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1429 relative=True)
1430 exp_files = ['buildbot/constants.py', 'scripts/sync_sonic.py']
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001431 self.assertEqual(files, exp_files)
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001432
Mike Frysinger292b45d2014-11-25 01:17:10 -05001433 def testGetAffectedFilesDetails(self):
1434 """Verify _get_affected_files() works w/full_details."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001435 files = pre_upload._get_affected_files('HEAD', full_details=True,
1436 relative=True)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001437 self.assertEqual(files[0].src_file, 'buildbot/constants.py')
Mike Frysinger292b45d2014-11-25 01:17:10 -05001438
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001439 def testGetAffectedFilesPresubmitIgnoreDirectory(self):
1440 """Verify .presubmitignore can be used to exclude a directory."""
1441 self._WritePresubmitIgnoreFile('.', 'buildbot/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001442 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001443
1444 def testGetAffectedFilesPresubmitIgnoreDirectoryWildcard(self):
1445 """Verify .presubmitignore can be used with a directory wildcard."""
1446 self._WritePresubmitIgnoreFile('.', '*/constants.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001447 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001448
1449 def testGetAffectedFilesPresubmitIgnoreWithinDirectory(self):
1450 """Verify .presubmitignore can be placed in a subdirectory."""
1451 self._WritePresubmitIgnoreFile('buildbot', '*.py')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001452 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001453
1454 def testGetAffectedFilesPresubmitIgnoreDoesntMatch(self):
1455 """Verify .presubmitignore has no effect when it doesn't match a file."""
1456 self._WritePresubmitIgnoreFile('buildbot', '*.txt')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001457 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True),
1458 ['buildbot/constants.py'])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001459
1460 def testGetAffectedFilesPresubmitIgnoreAddedFile(self):
1461 """Verify .presubmitignore matches added files."""
1462 self._WritePresubmitIgnoreFile('.', 'buildbot/\nscripts/')
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001463 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True,
1464 include_symlinks=True),
1465 [])
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001466
1467 def testGetAffectedFilesPresubmitIgnoreSkipIgnoreFile(self):
1468 """Verify .presubmitignore files are automatically skipped."""
1469 self.PatchObject(git, 'RawDiff', return_value=[
1470 DiffEntry(src_file='.presubmitignore', status='M')
1471 ])
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001472 self.assertEqual(pre_upload._get_affected_files('HEAD', relative=True), [])
Mike Frysinger292b45d2014-11-25 01:17:10 -05001473
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001474
Mike Frysingerb2496652019-09-12 23:35:46 -04001475class ExecFilesTest(PreUploadTestCase):
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001476 """Tests for _check_exec_files."""
1477
1478 def setUp(self):
1479 self.diff_mock = self.PatchObject(git, 'RawDiff')
1480
1481 def testNotExec(self):
1482 """Do not flag files that are not executable."""
1483 self.diff_mock.return_value = [
1484 DiffEntry(src_file='make.conf', dst_mode='100644', status='A'),
1485 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001486 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001487
1488 def testExec(self):
1489 """Flag files that are executable."""
1490 self.diff_mock.return_value = [
1491 DiffEntry(src_file='make.conf', dst_mode='100755', status='A'),
1492 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001493 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001494
1495 def testDeletedExec(self):
1496 """Ignore bad files that are being deleted."""
1497 self.diff_mock.return_value = [
1498 DiffEntry(src_file='make.conf', dst_mode='100755', status='D'),
1499 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001500 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001501
1502 def testModifiedExec(self):
1503 """Flag bad files that weren't exec, but now are."""
1504 self.diff_mock.return_value = [
1505 DiffEntry(src_file='make.conf', src_mode='100644', dst_mode='100755',
1506 status='M'),
1507 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001508 self.assertIsNotNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001509
1510 def testNormalExec(self):
1511 """Don't flag normal files (e.g. scripts) that are executable."""
1512 self.diff_mock.return_value = [
1513 DiffEntry(src_file='foo.sh', dst_mode='100755', status='A'),
1514 ]
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001515 self.assertIsNone(pre_upload._check_exec_files('proj', 'commit'))
Mike Frysingerf9d41b32017-02-23 15:20:04 -05001516
1517
Mike Frysingerb2496652019-09-12 23:35:46 -04001518class CheckForUprev(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger292b45d2014-11-25 01:17:10 -05001519 """Tests for _check_for_uprev."""
1520
1521 def setUp(self):
1522 self.file_mock = self.PatchObject(git, 'RawDiff')
1523
1524 def _Files(self, files):
1525 """Create |files| in the tempdir and return full paths to them."""
1526 for obj in files:
1527 if obj.status == 'D':
1528 continue
1529 if obj.dst_file is None:
1530 f = obj.src_file
1531 else:
1532 f = obj.dst_file
1533 osutils.Touch(os.path.join(self.tempdir, f), makedirs=True)
1534 return files
1535
1536 def assertAccepted(self, files, project='project', commit='fake sha1'):
1537 """Assert _check_for_uprev accepts |files|."""
1538 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001539 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1540 project_top=self.tempdir)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001541 self.assertIsNone(ret)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001542
1543 def assertRejected(self, files, project='project', commit='fake sha1'):
1544 """Assert _check_for_uprev rejects |files|."""
1545 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001546 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1547 project_top=self.tempdir)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001548 self.assertTrue(isinstance(ret, errors.HookFailure))
1549
1550 def testWhitelistOverlay(self):
1551 """Skip checks on whitelisted overlays."""
1552 self.assertAccepted([DiffEntry(src_file='cat/pkg/pkg-0.ebuild')],
1553 project='chromiumos/overlays/portage-stable')
1554
1555 def testWhitelistFiles(self):
1556 """Skip checks on whitelisted files."""
1557 files = ['ChangeLog', 'Manifest', 'metadata.xml']
1558 self.assertAccepted([DiffEntry(src_file=os.path.join('c', 'p', x),
1559 status='M')
1560 for x in files])
1561
1562 def testRejectBasic(self):
1563 """Reject ebuilds missing uprevs."""
1564 self.assertRejected([DiffEntry(src_file='c/p/p-0.ebuild', status='M')])
1565
1566 def testNewPackage(self):
1567 """Accept new ebuilds w/out uprevs."""
1568 self.assertAccepted([DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1569 self.assertAccepted([DiffEntry(src_file='c/p/p-0-r12.ebuild', status='A')])
1570
1571 def testModifiedFilesOnly(self):
1572 """Reject ebuilds w/out uprevs and changes in files/."""
1573 osutils.Touch(os.path.join(self.tempdir, 'cat/pkg/pkg-0.ebuild'),
1574 makedirs=True)
1575 self.assertRejected([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1576 self.assertRejected([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1577
1578 def testFilesNoEbuilds(self):
1579 """Ignore changes to paths w/out ebuilds."""
1580 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1581 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1582
1583 def testModifiedFilesWithUprev(self):
1584 """Accept ebuilds w/uprevs and changes in files/."""
1585 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='A'),
1586 DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1587 self.assertAccepted([
1588 DiffEntry(src_file='c/p/files/f', status='M'),
1589 DiffEntry(src_file='c/p/p-0-r1.ebuild', src_mode='120000',
1590 dst_file='c/p/p-0-r2.ebuild', dst_mode='120000', status='R')])
1591
Gwendal Grignoua3086c32014-12-09 11:17:22 -08001592 def testModifiedFilesWith9999(self):
1593 """Accept 9999 ebuilds and changes in files/."""
1594 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M'),
1595 DiffEntry(src_file='c/p/p-9999.ebuild', status='M')])
1596
C Shapiroae157ae2017-09-18 16:24:03 -06001597 def testModifiedFilesIn9999SubDirWithout9999Change(self):
1598 """Accept changes in files/ with a parent 9999 ebuild"""
1599 ebuild_9999_file = os.path.join(self.tempdir, 'c/p/p-9999.ebuild')
1600 os.makedirs(os.path.dirname(ebuild_9999_file))
1601 osutils.WriteFile(ebuild_9999_file, 'fake')
1602 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M')])
1603
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001604
Mike Frysingerb2496652019-09-12 23:35:46 -04001605class DirectMainTest(PreUploadTestCase, cros_test_lib.TempDirTestCase):
Mike Frysinger55f85b52014-12-18 14:45:21 -05001606 """Tests for direct_main()"""
1607
1608 def setUp(self):
1609 self.hooks_mock = self.PatchObject(pre_upload, '_run_project_hooks',
1610 return_value=None)
1611
1612 def testNoArgs(self):
1613 """If run w/no args, should check the current dir."""
1614 ret = pre_upload.direct_main([])
1615 self.assertEqual(ret, 0)
1616 self.hooks_mock.assert_called_once_with(
1617 mock.ANY, proj_dir=os.getcwd(), commit_list=[], presubmit=mock.ANY)
1618
1619 def testExplicitDir(self):
1620 """Verify we can run on a diff dir."""
1621 # Use the chromite dir since we know it exists.
1622 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR])
1623 self.assertEqual(ret, 0)
1624 self.hooks_mock.assert_called_once_with(
1625 mock.ANY, proj_dir=constants.CHROMITE_DIR, commit_list=[],
1626 presubmit=mock.ANY)
1627
1628 def testBogusProject(self):
1629 """A bogus project name should be fine (use default settings)."""
1630 # Use the chromite dir since we know it exists.
1631 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR,
1632 '--project', 'foooooooooo'])
1633 self.assertEqual(ret, 0)
1634 self.hooks_mock.assert_called_once_with(
1635 'foooooooooo', proj_dir=constants.CHROMITE_DIR, commit_list=[],
1636 presubmit=mock.ANY)
1637
1638 def testBogustProjectNoDir(self):
1639 """Make sure --dir is detected even with --project."""
1640 ret = pre_upload.direct_main(['--project', 'foooooooooo'])
1641 self.assertEqual(ret, 0)
1642 self.hooks_mock.assert_called_once_with(
1643 'foooooooooo', proj_dir=os.getcwd(), commit_list=[],
1644 presubmit=mock.ANY)
1645
1646 def testNoGitDir(self):
1647 """We should die when run on a non-git dir."""
1648 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
1649 ['--dir', self.tempdir])
1650
1651 def testNoDir(self):
1652 """We should die when run on a missing dir."""
1653 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
1654 ['--dir', os.path.join(self.tempdir, 'foooooooo')])
1655
1656 def testCommitList(self):
1657 """Any args on the command line should be treated as commits."""
1658 commits = ['sha1', 'sha2', 'shaaaaaaaaaaaan']
1659 ret = pre_upload.direct_main(commits)
1660 self.assertEqual(ret, 0)
1661 self.hooks_mock.assert_called_once_with(
1662 mock.ANY, proj_dir=mock.ANY, commit_list=commits, presubmit=mock.ANY)
1663
1664
Mike Frysingerb2496652019-09-12 23:35:46 -04001665class CheckRustfmtTest(PreUploadTestCase):
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001666 """Tests for _check_rustfmt."""
1667
1668 def setUp(self):
1669 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
1670
1671 def testBadRustFile(self):
1672 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
1673 # Bad because it's missing trailing newline.
Mike Frysingere85b0062019-08-20 15:10:33 -04001674 content = 'fn main() {}'
1675 self.content_mock.return_value = content
1676 self.PatchObject(pre_upload, '_run_command', return_value=content + '\n')
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001677 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1678 self.assertIsNotNone(failure)
Mike Frysinger8a4e8942019-09-16 23:43:49 -04001679 self.assertEqual('Files not formatted with rustfmt: '
1680 "(run 'cargo fmt' to fix)",
1681 failure.msg)
1682 self.assertEqual(['a.rs'], failure.items)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001683
1684 def testGoodRustFile(self):
1685 self.PatchObject(pre_upload, '_get_affected_files', return_value=['a.rs'])
Mike Frysingere85b0062019-08-20 15:10:33 -04001686 content = 'fn main() {}\n'
1687 self.content_mock.return_value = content
1688 self.PatchObject(pre_upload, '_run_command', return_value=content)
Fletcher Woodruffce1cb1b2019-08-16 15:59:32 -06001689 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1690 self.assertIsNone(failure)
1691
1692 def testFilterNonRustFiles(self):
1693 self.PatchObject(pre_upload, '_get_affected_files',
1694 return_value=['a.cc', 'a.rsa', 'a.irs', 'rs.cc'])
1695 self.content_mock.return_value = 'fn main() {\n}'
1696 failure = pre_upload._check_rustfmt(ProjectNamed('PROJECT'), 'COMMIT')
1697 self.assertIsNone(failure)
1698
1699
Jon Salz98255932012-08-18 14:48:02 +08001700if __name__ == '__main__':
Mike Frysinger884a8dd2015-05-17 03:43:43 -04001701 cros_test_lib.main(module=__name__)