blob: eddbb7e69e11681d21922d65ffc75a04dbbc7698 [file] [log] [blame]
Jon Salz98255932012-08-18 14:48:02 +08001#!/usr/bin/env python
2# -*- 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
Jon Salz98255932012-08-18 14:48:02 +080011import mox
David Jamesc3b68b32013-04-03 09:17:03 -070012import os
13import sys
Jon Salz98255932012-08-18 14:48:02 +080014
Mike Frysingerbf8b91c2014-02-01 02:50:27 -050015import errors
16
Jon Salz98255932012-08-18 14:48:02 +080017# pylint: disable=W0212
Mike Frysinger65d93c12014-02-01 02:59:46 -050018# We access private members of the pre_upload module all over the place.
19
20# If repo imports us, the __name__ will be __builtin__, and the wrapper will
21# be in $CHROMEOS_CHECKOUT/.repo/repo/main.py, so we need to go two directories
22# up. The same logic also happens to work if we're executed directly.
23if __name__ in ('__builtin__', '__main__'):
David Jamesc3b68b32013-04-03 09:17:03 -070024 sys.path.insert(0, os.path.join(os.path.dirname(sys.argv[0]), '..', '..'))
Jon Salz98255932012-08-18 14:48:02 +080025
Mike Frysinger65d93c12014-02-01 02:59:46 -050026from chromite.lib import cros_test_lib
Daniel Erata350fd32014-09-29 14:02:34 -070027from chromite.lib import osutils
Mike Frysinger65d93c12014-02-01 02:59:46 -050028
29
Jon Salz98255932012-08-18 14:48:02 +080030pre_upload = __import__('pre-upload')
31
32
Mike Frysinger65d93c12014-02-01 02:59:46 -050033class TryUTF8DecodeTest(cros_test_lib.TestCase):
Mike Frysingerae409522014-02-01 03:16:11 -050034 """Verify we sanely handle unicode content."""
35
Jon Salz98255932012-08-18 14:48:02 +080036 def runTest(self):
37 self.assertEquals(u'', pre_upload._try_utf8_decode(''))
38 self.assertEquals(u'abc', pre_upload._try_utf8_decode('abc'))
39 self.assertEquals(u'你好布萊恩', pre_upload._try_utf8_decode('你好布萊恩'))
40 # Invalid UTF-8
41 self.assertEquals('\x80', pre_upload._try_utf8_decode('\x80'))
42
43
Mike Frysinger65d93c12014-02-01 02:59:46 -050044class CheckNoLongLinesTest(cros_test_lib.MoxTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -050045 """Tests for _check_no_long_lines."""
46
Jon Salz98255932012-08-18 14:48:02 +080047 def setUp(self):
Mike Frysinger65d93c12014-02-01 02:59:46 -050048 self.mox.StubOutWithMock(pre_upload, '_filter_files')
49 self.mox.StubOutWithMock(pre_upload, '_get_affected_files')
50 self.mox.StubOutWithMock(pre_upload, '_get_file_diff')
Jon Salz98255932012-08-18 14:48:02 +080051 pre_upload._get_affected_files(mox.IgnoreArg()).AndReturn(['x.py'])
52 pre_upload._filter_files(
53 ['x.py'], mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(['x.py'])
54
Jon Salz98255932012-08-18 14:48:02 +080055 def runTest(self):
56 pre_upload._get_file_diff(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(
57 [(1, u"x" * 80), # OK
58 (2, "\x80" * 80), # OK
59 (3, u"x" * 81), # Too long
60 (4, "\x80" * 81), # Too long
61 (5, u"See http://" + (u"x" * 80)), # OK (URL)
62 (6, u"See https://" + (u"x" * 80)), # OK (URL)
63 (7, u"# define " + (u"x" * 80)), # OK (compiler directive)
64 (8, u"#define" + (u"x" * 74)), # Too long
65 ])
Mike Frysinger65d93c12014-02-01 02:59:46 -050066 self.mox.ReplayAll()
Jon Salz98255932012-08-18 14:48:02 +080067 failure = pre_upload._check_no_long_lines('PROJECT', 'COMMIT')
68 self.assertTrue(failure)
69 self.assertEquals('Found lines longer than 80 characters (first 5 shown):',
70 failure.msg)
71 self.assertEquals(['x.py, line %d, 81 chars' % line
72 for line in [3, 4, 8]],
73 failure.items)
74
Mike Frysingerae409522014-02-01 03:16:11 -050075
Daniel Erata350fd32014-09-29 14:02:34 -070076class CheckProjectPrefix(cros_test_lib.MockTempDirTestCase):
77 """Tests for _check_project_prefix."""
78
79 def setUp(self):
80 self.orig_cwd = os.getcwd()
81 os.chdir(self.tempdir)
82 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
83 self.desc_mock = self.PatchObject(pre_upload, '_get_commit_desc')
84
85 def tearDown(self):
86 os.chdir(self.orig_cwd)
87
88 def _WriteAliasFile(self, filename, project):
89 """Writes a project name to a file, creating directories if needed."""
90 os.makedirs(os.path.dirname(filename))
91 osutils.WriteFile(filename, project)
92
93 def testInvalidPrefix(self):
94 """Report an error when the prefix doesn't match the base directory."""
95 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
96 self.desc_mock.return_value = 'bar: Some commit'
97 failure = pre_upload._check_project_prefix('PROJECT', 'COMMIT')
98 self.assertTrue(failure)
99 self.assertEquals(('The commit title for changes affecting only foo' +
100 ' should start with "foo: "'), failure.msg)
101
102 def testValidPrefix(self):
103 """Use a prefix that matches the base directory."""
104 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
105 self.desc_mock.return_value = 'foo: Change some files.'
106 self.assertFalse(pre_upload._check_project_prefix('PROJECT', 'COMMIT'))
107
108 def testAliasFile(self):
109 """Use .project_alias to override the project name."""
110 self._WriteAliasFile('foo/.project_alias', 'project')
111 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/bar.cc']
112 self.desc_mock.return_value = 'project: Use an alias.'
113 self.assertFalse(pre_upload._check_project_prefix('PROJECT', 'COMMIT'))
114
115 def testAliasFileWithSubdirs(self):
116 """Check that .project_alias is used when only modifying subdirectories."""
117 self._WriteAliasFile('foo/.project_alias', 'project')
118 self.file_mock.return_value = [
119 'foo/subdir/foo.cc',
120 'foo/subdir/bar.cc'
121 'foo/subdir/blah/baz.cc'
122 ]
123 self.desc_mock.return_value = 'project: Alias with subdirs.'
124 self.assertFalse(pre_upload._check_project_prefix('PROJECT', 'COMMIT'))
125
126
Mike Frysinger65d93c12014-02-01 02:59:46 -0500127class CheckKernelConfig(cros_test_lib.MoxTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -0500128 """Tests for _kernel_configcheck."""
129
Olof Johanssona96810f2012-09-04 16:20:03 -0700130 def runTest(self):
Olof Johanssona96810f2012-09-04 16:20:03 -0700131 # Mixed changes, should fail
Mike Frysinger65d93c12014-02-01 02:59:46 -0500132 self.mox.StubOutWithMock(pre_upload, '_get_affected_files')
Olof Johanssona96810f2012-09-04 16:20:03 -0700133 pre_upload._get_affected_files(mox.IgnoreArg()).AndReturn(
134 ['/kernel/files/chromeos/config/base.config',
135 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
136 ])
Mike Frysinger65d93c12014-02-01 02:59:46 -0500137 self.mox.ReplayAll()
Olof Johanssona96810f2012-09-04 16:20:03 -0700138 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
139 self.assertTrue(failure)
140
141 # Code-only changes, should pass
Mike Frysinger65d93c12014-02-01 02:59:46 -0500142 self.mox.UnsetStubs()
143 self.mox.StubOutWithMock(pre_upload, '_get_affected_files')
Olof Johanssona96810f2012-09-04 16:20:03 -0700144 pre_upload._get_affected_files(mox.IgnoreArg()).AndReturn(
145 ['/kernel/files/Makefile',
146 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
147 ])
Mike Frysinger65d93c12014-02-01 02:59:46 -0500148 self.mox.ReplayAll()
Olof Johanssona96810f2012-09-04 16:20:03 -0700149 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
150 self.assertFalse(failure)
151
152 # Config-only changes, should pass
Mike Frysinger65d93c12014-02-01 02:59:46 -0500153 self.mox.UnsetStubs()
154 self.mox.StubOutWithMock(pre_upload, '_get_affected_files')
Olof Johanssona96810f2012-09-04 16:20:03 -0700155 pre_upload._get_affected_files(mox.IgnoreArg()).AndReturn(
156 ['/kernel/files/chromeos/config/base.config',
157 ])
Mike Frysinger65d93c12014-02-01 02:59:46 -0500158 self.mox.ReplayAll()
Olof Johanssona96810f2012-09-04 16:20:03 -0700159 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
160 self.assertFalse(failure)
161
Jon Salz98255932012-08-18 14:48:02 +0800162
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500163class CheckEbuildEapi(cros_test_lib.MockTestCase):
164 """Tests for _check_ebuild_eapi."""
165
166 PORTAGE_STABLE = 'chromiumos/overlays/portage-stable'
167
168 def setUp(self):
169 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
170 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
171 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff',
172 side_effect=Exception())
173
174 def testSkipUpstreamOverlays(self):
175 """Skip ebuilds found in upstream overlays."""
176 self.file_mock.side_effect = Exception()
177 ret = pre_upload._check_ebuild_eapi(self.PORTAGE_STABLE, 'HEAD')
178 self.assertEqual(ret, None)
179
180 # Make sure our condition above triggers.
181 self.assertRaises(Exception, pre_upload._check_ebuild_eapi, 'o', 'HEAD')
182
183 def testSkipNonEbuilds(self):
184 """Skip non-ebuild files."""
185 self.content_mock.side_effect = Exception()
186
187 self.file_mock.return_value = ['some-file', 'ebuild/dir', 'an.ebuild~']
188 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
189 self.assertEqual(ret, None)
190
191 # Make sure our condition above triggers.
192 self.file_mock.return_value.append('a/real.ebuild')
193 self.assertRaises(Exception, pre_upload._check_ebuild_eapi, 'o', 'HEAD')
194
195 def testSkipSymlink(self):
196 """Skip files that are just symlinks."""
197 self.file_mock.return_value = ['a-r1.ebuild']
198 self.content_mock.return_value = 'a.ebuild'
199 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
200 self.assertEqual(ret, None)
201
202 def testRejectEapiImplicit0Content(self):
203 """Reject ebuilds that do not declare EAPI (so it's 0)."""
204 self.file_mock.return_value = ['a.ebuild']
205
206 self.content_mock.return_value = """# Header
207IUSE="foo"
208src_compile() { }
209"""
210 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
211 self.assertTrue(isinstance (ret, errors.HookFailure))
212
213 def testRejectExplicitEapi1Content(self):
214 """Reject ebuilds that do declare old EAPI explicitly."""
215 self.file_mock.return_value = ['a.ebuild']
216
217 template = """# Header
218EAPI=%s
219IUSE="foo"
220src_compile() { }
221"""
222 # Make sure we only check the first EAPI= setting.
223 self.content_mock.return_value = template % '1\nEAPI=4'
224 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
225 self.assertTrue(isinstance (ret, errors.HookFailure))
226
227 # Verify we handle double quotes too.
228 self.content_mock.return_value = template % '"1"'
229 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
230 self.assertTrue(isinstance (ret, errors.HookFailure))
231
232 # Verify we handle single quotes too.
233 self.content_mock.return_value = template % "'1'"
234 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
235 self.assertTrue(isinstance (ret, errors.HookFailure))
236
237 def testAcceptExplicitEapi4Content(self):
238 """Accept ebuilds that do declare new EAPI explicitly."""
239 self.file_mock.return_value = ['a.ebuild']
240
241 template = """# Header
242EAPI=%s
243IUSE="foo"
244src_compile() { }
245"""
246 # Make sure we only check the first EAPI= setting.
247 self.content_mock.return_value = template % '4\nEAPI=1'
248 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
249 self.assertEqual(ret, None)
250
251 # Verify we handle double quotes too.
252 self.content_mock.return_value = template % '"5"'
253 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
254 self.assertEqual(ret, None)
255
256 # Verify we handle single quotes too.
257 self.content_mock.return_value = template % "'5-hdepend'"
258 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
259 self.assertEqual(ret, None)
260
261
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400262class CheckEbuildKeywords(cros_test_lib.MockTestCase):
263 """Tests for _check_ebuild_keywords."""
264
265 def setUp(self):
266 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
267 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
268
269 def testNoEbuilds(self):
270 """If no ebuilds are found, do not scan."""
271 self.file_mock.return_value = ['a.file', 'ebuild-is-not.foo']
272
273 ret = pre_upload._check_ebuild_keywords('overlay', 'HEAD')
274 self.assertEqual(ret, None)
275
276 self.assertEqual(self.content_mock.call_count, 0)
277
278 def testSomeEbuilds(self):
279 """If ebuilds are found, only scan them."""
280 self.file_mock.return_value = ['a.file', 'blah', 'foo.ebuild', 'cow']
281 self.content_mock.return_value = ''
282
283 ret = pre_upload._check_ebuild_keywords('overlay', 'HEAD')
284 self.assertEqual(ret, None)
285
286 self.assertEqual(self.content_mock.call_count, 1)
287
288 def _CheckContent(self, content, fails):
289 """Test helper for inputs/outputs.
290
291 Args:
292 content: The ebuild content to test.
293 fails: Whether |content| should trigger a hook failure.
294 """
295 self.file_mock.return_value = ['a.ebuild']
296 self.content_mock.return_value = content
297
298 ret = pre_upload._check_ebuild_keywords('overlay', 'HEAD')
299 if fails:
300 self.assertTrue(isinstance (ret, errors.HookFailure))
301 else:
302 self.assertEqual(ret, None)
303
304 self.assertEqual(self.content_mock.call_count, 1)
305
306 def testEmpty(self):
307 """Check KEYWORDS= is accepted."""
308 self._CheckContent('# HEADER\nKEYWORDS=\nblah\n', False)
309
310 def testEmptyQuotes(self):
311 """Check KEYWORDS="" is accepted."""
312 self._CheckContent('# HEADER\nKEYWORDS=" "\nblah\n', False)
313
314 def testStableGlob(self):
315 """Check KEYWORDS=* is accepted."""
316 self._CheckContent('# HEADER\nKEYWORDS="\t*\t"\nblah\n', False)
317
318 def testUnstableGlob(self):
319 """Check KEYWORDS=~* is accepted."""
320 self._CheckContent('# HEADER\nKEYWORDS="~* "\nblah\n', False)
321
322 def testRestrictedGlob(self):
323 """Check KEYWORDS=-* is accepted."""
324 self._CheckContent('# HEADER\nKEYWORDS="\t-* arm"\nblah\n', False)
325
326 def testMissingGlobs(self):
327 """Reject KEYWORDS missing any globs."""
328 self._CheckContent('# HEADER\nKEYWORDS="~arm x86"\nblah\n', True)
329
330
Mike Frysingercd363c82014-02-01 05:20:18 -0500331class CheckEbuildVirtualPv(cros_test_lib.MockTestCase):
332 """Tests for _check_ebuild_virtual_pv."""
333
334 PORTAGE_STABLE = 'chromiumos/overlays/portage-stable'
335 CHROMIUMOS_OVERLAY = 'chromiumos/overlays/chromiumos'
336 BOARD_OVERLAY = 'chromiumos/overlays/board-overlays'
337 PRIVATE_OVERLAY = 'chromeos/overlays/overlay-link-private'
338 PRIVATE_VARIANT_OVERLAY = ('chromeos/overlays/'
339 'overlay-variant-daisy-spring-private')
340
341 def setUp(self):
342 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
343
344 def testNoVirtuals(self):
345 """Skip non virtual packages."""
346 self.file_mock.return_value = ['some/package/package-3.ebuild']
347 ret = pre_upload._check_ebuild_virtual_pv('overlay', 'H')
348 self.assertEqual(ret, None)
349
350 def testCommonVirtuals(self):
351 """Non-board overlays should use PV=1."""
352 template = 'virtual/foo/foo-%s.ebuild'
353 self.file_mock.return_value = [template % '1']
354 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
355 self.assertEqual(ret, None)
356
357 self.file_mock.return_value = [template % '2']
358 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
359 self.assertTrue(isinstance (ret, errors.HookFailure))
360
361 def testPublicBoardVirtuals(self):
362 """Public board overlays should use PV=2."""
363 template = 'overlay-lumpy/virtual/foo/foo-%s.ebuild'
364 self.file_mock.return_value = [template % '2']
365 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
366 self.assertEqual(ret, None)
367
368 self.file_mock.return_value = [template % '2.5']
369 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
370 self.assertTrue(isinstance (ret, errors.HookFailure))
371
372 def testPublicBoardVariantVirtuals(self):
373 """Public board variant overlays should use PV=2.5."""
374 template = 'overlay-variant-lumpy-foo/virtual/foo/foo-%s.ebuild'
375 self.file_mock.return_value = [template % '2.5']
376 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
377 self.assertEqual(ret, None)
378
379 self.file_mock.return_value = [template % '3']
380 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
381 self.assertTrue(isinstance (ret, errors.HookFailure))
382
383 def testPrivateBoardVirtuals(self):
384 """Private board overlays should use PV=3."""
385 template = 'virtual/foo/foo-%s.ebuild'
386 self.file_mock.return_value = [template % '3']
387 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
388 self.assertEqual(ret, None)
389
390 self.file_mock.return_value = [template % '3.5']
391 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
392 self.assertTrue(isinstance (ret, errors.HookFailure))
393
394 def testPrivateBoardVariantVirtuals(self):
395 """Private board variant overlays should use PV=3.5."""
396 template = 'virtual/foo/foo-%s.ebuild'
397 self.file_mock.return_value = [template % '3.5']
398 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
399 self.assertEqual(ret, None)
400
401 self.file_mock.return_value = [template % '4']
402 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
403 self.assertTrue(isinstance (ret, errors.HookFailure))
404
Mike Frysinger98638102014-08-28 00:15:08 -0400405
Peter Ammon811f6702014-06-12 15:45:38 -0700406class CheckGitOutputParsing(cros_test_lib.MockTestCase):
407 """Tests for git output parsing."""
Mike Frysinger98638102014-08-28 00:15:08 -0400408
Peter Ammon811f6702014-06-12 15:45:38 -0700409 def testParseAffectedFiles(self):
410 """Test parsing git diff --raw output."""
411 # Sample from git diff --raw.
412 sample_git_output = '\n'.join([
413 ":100644 100644 ff03961... a198e8b... M\tMakefile",
414 ":100644 000000 e69de29... 0000000... D\tP1/P2",
415 ":100755 100644 454d5ef... 0000000... C86\tP3\tP4",
416 ":100755 100644 454d5ef... 0000000... R86\tP5\tP6/P7",
417 ":100755 120644 454d5ef... 0000000... M\tIsASymlink",
418 ])
419 expected_modified_files_no_deletes = ['Makefile', 'P4', 'P6/P7']
420 expected_modified_files_with_deletes = ['Makefile', 'P1/P2', 'P4', 'P6/P7']
421 result = pre_upload._parse_affected_files(sample_git_output,
422 include_deletes=True,
423 relative=True)
424 self.assertEqual(result, expected_modified_files_with_deletes)
425 result = pre_upload._parse_affected_files(sample_git_output,
426 include_deletes=False,
427 relative=True)
428 self.assertEqual(result, expected_modified_files_no_deletes)
Mike Frysingercd363c82014-02-01 05:20:18 -0500429
Mike Frysinger98638102014-08-28 00:15:08 -0400430
431class CheckLicenseCopyrightHeader(cros_test_lib.MockTestCase):
432 """Tests for _check_license."""
433
434 def setUp(self):
435 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
436 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
437
438 def testOldHeaders(self):
439 """Accept old header styles."""
440 HEADERS = (
441 ('#!/bin/sh\n'
442 '# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.\n'
443 '# Use of this source code is governed by a BSD-style license that'
444 ' can be\n'
445 '# found in the LICENSE file.\n'),
446 ('// Copyright 2010-13 The Chromium OS Authors. All rights reserved.\n'
447 '// Use of this source code is governed by a BSD-style license that'
448 ' can be\n'
449 '// found in the LICENSE file.\n'),
450 )
451 self.file_mock.return_value = ['file']
452 for header in HEADERS:
453 self.content_mock.return_value = header
454 self.assertEqual(None, pre_upload._check_license('proj', 'sha1'))
455
456 def testRejectC(self):
457 """Reject the (c) in newer headers."""
458 HEADERS = (
459 ('// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.\n'
460 '// Use of this source code is governed by a BSD-style license that'
461 ' can be\n'
462 '// found in the LICENSE file.\n'),
463 ('// Copyright (c) 2020 The Chromium OS Authors. All rights reserved.\n'
464 '// Use of this source code is governed by a BSD-style license that'
465 ' can be\n'
466 '// found in the LICENSE file.\n'),
467 )
468 self.file_mock.return_value = ['file']
469 for header in HEADERS:
470 self.content_mock.return_value = header
471 self.assertNotEqual(None, pre_upload._check_license('proj', 'sha1'))
472
473
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400474class CommitMessageTestCase(cros_test_lib.MockTestCase):
475 """Test case for funcs that check commit messages."""
476
477 def setUp(self):
478 self.msg_mock = self.PatchObject(pre_upload, '_get_commit_desc')
479
480 @staticmethod
481 def CheckMessage(_project, _commit):
482 raise AssertionError('Test class must declare CheckMessage')
483 # This dummy return is to silence pylint warning W1111 so we don't have to
484 # enable it for all the call sites below.
485 return 1 # pylint: disable=W0101
486
487 def assertMessageAccepted(self, msg, project='project', commit='1234'):
488 """Assert _check_change_has_bug_field accepts |msg|."""
489 self.msg_mock.return_value = msg
490 ret = self.CheckMessage(project, commit)
491 self.assertEqual(ret, None)
492
493 def assertMessageRejected(self, msg, project='project', commit='1234'):
494 """Assert _check_change_has_bug_field rejects |msg|."""
495 self.msg_mock.return_value = msg
496 ret = self.CheckMessage(project, commit)
497 self.assertTrue(isinstance(ret, errors.HookFailure))
498
499
500class CheckCommitMessageBug(CommitMessageTestCase):
501 """Tests for _check_change_has_bug_field."""
502
503 @staticmethod
504 def CheckMessage(project, commit):
505 return pre_upload._check_change_has_bug_field(project, commit)
506
507 def testNormal(self):
508 """Accept a commit message w/a valid BUG."""
509 self.assertMessageAccepted('\nBUG=chromium:1234\n')
510 self.assertMessageAccepted('\nBUG=chrome-os-partner:1234\n')
511
512 def testNone(self):
513 """Accept BUG=None."""
514 self.assertMessageAccepted('\nBUG=None\n')
515 self.assertMessageAccepted('\nBUG=none\n')
516 self.assertMessageRejected('\nBUG=NONE\n')
517
518 def testBlank(self):
519 """Reject blank values."""
520 self.assertMessageRejected('\nBUG=\n')
521 self.assertMessageRejected('\nBUG= \n')
522
523 def testNotFirstLine(self):
524 """Reject the first line."""
525 self.assertMessageRejected('BUG=None\n\n\n')
526
527 def testNotInline(self):
528 """Reject not at the start of line."""
529 self.assertMessageRejected('\n BUG=None\n')
530 self.assertMessageRejected('\n\tBUG=None\n')
531
532 def testOldTrackers(self):
533 """Reject commit messages using old trackers."""
534 self.assertMessageRejected('\nBUG=chromium-os:1234\n')
535
536 def testNoTrackers(self):
537 """Reject commit messages w/invalid trackers."""
538 self.assertMessageRejected('\nBUG=booga:1234\n')
539
540 def testMissing(self):
541 """Reject commit messages w/no BUG line."""
542 self.assertMessageRejected('foo\n')
543
544 def testCase(self):
545 """Reject bug lines that are not BUG."""
546 self.assertMessageRejected('\nbug=none\n')
547
548
549class CheckCommitMessageCqDepend(CommitMessageTestCase):
550 """Tests for _check_change_has_valid_cq_depend."""
551
552 @staticmethod
553 def CheckMessage(project, commit):
554 return pre_upload._check_change_has_valid_cq_depend(project, commit)
555
556 def testNormal(self):
557 """Accept valid CQ-DEPENDs line."""
558 self.assertMessageAccepted('\nCQ-DEPEND=CL:1234\n')
559
560 def testInvalid(self):
561 """Reject invalid CQ-DEPENDs line."""
562 self.assertMessageRejected('\nCQ-DEPEND=CL=1234\n')
563 self.assertMessageRejected('\nCQ-DEPEND=None\n')
564
565
566class CheckCommitMessageTest(CommitMessageTestCase):
567 """Tests for _check_change_has_test_field."""
568
569 @staticmethod
570 def CheckMessage(project, commit):
571 return pre_upload._check_change_has_test_field(project, commit)
572
573 def testNormal(self):
574 """Accept a commit message w/a valid TEST."""
575 self.assertMessageAccepted('\nTEST=i did it\n')
576
577 def testNone(self):
578 """Accept TEST=None."""
579 self.assertMessageAccepted('\nTEST=None\n')
580 self.assertMessageAccepted('\nTEST=none\n')
581
582 def testBlank(self):
583 """Reject blank values."""
584 self.assertMessageRejected('\nTEST=\n')
585 self.assertMessageRejected('\nTEST= \n')
586
587 def testNotFirstLine(self):
588 """Reject the first line."""
589 self.assertMessageRejected('TEST=None\n\n\n')
590
591 def testNotInline(self):
592 """Reject not at the start of line."""
593 self.assertMessageRejected('\n TEST=None\n')
594 self.assertMessageRejected('\n\tTEST=None\n')
595
596 def testMissing(self):
597 """Reject commit messages w/no TEST line."""
598 self.assertMessageRejected('foo\n')
599
600 def testCase(self):
601 """Reject bug lines that are not TEST."""
602 self.assertMessageRejected('\ntest=none\n')
603
604
605class CheckCommitMessageChangeId(CommitMessageTestCase):
606 """Tests for _check_change_has_proper_changeid."""
607
608 @staticmethod
609 def CheckMessage(project, commit):
610 return pre_upload._check_change_has_proper_changeid(project, commit)
611
612 def testNormal(self):
613 """Accept a commit message w/a valid Change-Id."""
614 self.assertMessageAccepted('foo\n\nChange-Id: I1234\n')
615
616 def testBlank(self):
617 """Reject blank values."""
618 self.assertMessageRejected('\nChange-Id:\n')
619 self.assertMessageRejected('\nChange-Id: \n')
620
621 def testNotFirstLine(self):
622 """Reject the first line."""
623 self.assertMessageRejected('TEST=None\n\n\n')
624
625 def testNotInline(self):
626 """Reject not at the start of line."""
627 self.assertMessageRejected('\n Change-Id: I1234\n')
628 self.assertMessageRejected('\n\tChange-Id: I1234\n')
629
630 def testMissing(self):
631 """Reject commit messages missing the line."""
632 self.assertMessageRejected('foo\n')
633
634 def testCase(self):
635 """Reject bug lines that are not Change-Id."""
636 self.assertMessageRejected('\nchange-id: I1234\n')
637 self.assertMessageRejected('\nChange-id: I1234\n')
638 self.assertMessageRejected('\nChange-ID: I1234\n')
639
640 def testEnd(self):
641 """Reject Change-Id's that are not last."""
642 self.assertMessageRejected('\nChange-Id: I1234\nbar\n')
643
644
Jon Salz98255932012-08-18 14:48:02 +0800645if __name__ == '__main__':
Mike Frysinger65d93c12014-02-01 02:59:46 -0500646 cros_test_lib.main()