blob: b256895db197210793314f292fb924b387d24e24 [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
Mike Frysingerd3bd32c2014-11-24 23:34:29 -050027from chromite.lib import git
Daniel Erata350fd32014-09-29 14:02:34 -070028from chromite.lib import osutils
Mike Frysinger65d93c12014-02-01 02:59:46 -050029
30
Jon Salz98255932012-08-18 14:48:02 +080031pre_upload = __import__('pre-upload')
32
33
Mike Frysinger65d93c12014-02-01 02:59:46 -050034class TryUTF8DecodeTest(cros_test_lib.TestCase):
Mike Frysingerae409522014-02-01 03:16:11 -050035 """Verify we sanely handle unicode content."""
36
Jon Salz98255932012-08-18 14:48:02 +080037 def runTest(self):
38 self.assertEquals(u'', pre_upload._try_utf8_decode(''))
39 self.assertEquals(u'abc', pre_upload._try_utf8_decode('abc'))
40 self.assertEquals(u'你好布萊恩', pre_upload._try_utf8_decode('你好布萊恩'))
41 # Invalid UTF-8
42 self.assertEquals('\x80', pre_upload._try_utf8_decode('\x80'))
43
44
Mike Frysinger65d93c12014-02-01 02:59:46 -050045class CheckNoLongLinesTest(cros_test_lib.MoxTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -050046 """Tests for _check_no_long_lines."""
47
Jon Salz98255932012-08-18 14:48:02 +080048 def setUp(self):
Mike Frysinger65d93c12014-02-01 02:59:46 -050049 self.mox.StubOutWithMock(pre_upload, '_filter_files')
50 self.mox.StubOutWithMock(pre_upload, '_get_affected_files')
51 self.mox.StubOutWithMock(pre_upload, '_get_file_diff')
Jon Salz98255932012-08-18 14:48:02 +080052 pre_upload._get_affected_files(mox.IgnoreArg()).AndReturn(['x.py'])
53 pre_upload._filter_files(
54 ['x.py'], mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(['x.py'])
55
Jon Salz98255932012-08-18 14:48:02 +080056 def runTest(self):
57 pre_upload._get_file_diff(mox.IgnoreArg(), mox.IgnoreArg()).AndReturn(
58 [(1, u"x" * 80), # OK
59 (2, "\x80" * 80), # OK
60 (3, u"x" * 81), # Too long
61 (4, "\x80" * 81), # Too long
62 (5, u"See http://" + (u"x" * 80)), # OK (URL)
63 (6, u"See https://" + (u"x" * 80)), # OK (URL)
64 (7, u"# define " + (u"x" * 80)), # OK (compiler directive)
65 (8, u"#define" + (u"x" * 74)), # Too long
Mike Frysingerb81102f2014-11-21 00:33:35 -050066 ])
Mike Frysinger65d93c12014-02-01 02:59:46 -050067 self.mox.ReplayAll()
Jon Salz98255932012-08-18 14:48:02 +080068 failure = pre_upload._check_no_long_lines('PROJECT', 'COMMIT')
69 self.assertTrue(failure)
70 self.assertEquals('Found lines longer than 80 characters (first 5 shown):',
71 failure.msg)
72 self.assertEquals(['x.py, line %d, 81 chars' % line
73 for line in [3, 4, 8]],
74 failure.items)
75
Mike Frysingerae409522014-02-01 03:16:11 -050076
Daniel Erata350fd32014-09-29 14:02:34 -070077class CheckProjectPrefix(cros_test_lib.MockTempDirTestCase):
78 """Tests for _check_project_prefix."""
79
80 def setUp(self):
81 self.orig_cwd = os.getcwd()
82 os.chdir(self.tempdir)
83 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
84 self.desc_mock = self.PatchObject(pre_upload, '_get_commit_desc')
85
86 def tearDown(self):
87 os.chdir(self.orig_cwd)
88
89 def _WriteAliasFile(self, filename, project):
90 """Writes a project name to a file, creating directories if needed."""
91 os.makedirs(os.path.dirname(filename))
92 osutils.WriteFile(filename, project)
93
94 def testInvalidPrefix(self):
95 """Report an error when the prefix doesn't match the base directory."""
96 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
97 self.desc_mock.return_value = 'bar: Some commit'
98 failure = pre_upload._check_project_prefix('PROJECT', 'COMMIT')
99 self.assertTrue(failure)
100 self.assertEquals(('The commit title for changes affecting only foo' +
101 ' should start with "foo: "'), failure.msg)
102
103 def testValidPrefix(self):
104 """Use a prefix that matches the base directory."""
105 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
106 self.desc_mock.return_value = 'foo: Change some files.'
107 self.assertFalse(pre_upload._check_project_prefix('PROJECT', 'COMMIT'))
108
109 def testAliasFile(self):
110 """Use .project_alias to override the project name."""
111 self._WriteAliasFile('foo/.project_alias', 'project')
112 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/bar.cc']
113 self.desc_mock.return_value = 'project: Use an alias.'
114 self.assertFalse(pre_upload._check_project_prefix('PROJECT', 'COMMIT'))
115
116 def testAliasFileWithSubdirs(self):
117 """Check that .project_alias is used when only modifying subdirectories."""
118 self._WriteAliasFile('foo/.project_alias', 'project')
119 self.file_mock.return_value = [
120 'foo/subdir/foo.cc',
121 'foo/subdir/bar.cc'
122 'foo/subdir/blah/baz.cc'
123 ]
124 self.desc_mock.return_value = 'project: Alias with subdirs.'
125 self.assertFalse(pre_upload._check_project_prefix('PROJECT', 'COMMIT'))
126
127
Mike Frysinger65d93c12014-02-01 02:59:46 -0500128class CheckKernelConfig(cros_test_lib.MoxTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -0500129 """Tests for _kernel_configcheck."""
130
Olof Johanssona96810f2012-09-04 16:20:03 -0700131 def runTest(self):
Olof Johanssona96810f2012-09-04 16:20:03 -0700132 # Mixed changes, should fail
Mike Frysinger65d93c12014-02-01 02:59:46 -0500133 self.mox.StubOutWithMock(pre_upload, '_get_affected_files')
Olof Johanssona96810f2012-09-04 16:20:03 -0700134 pre_upload._get_affected_files(mox.IgnoreArg()).AndReturn(
135 ['/kernel/files/chromeos/config/base.config',
136 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
137 ])
Mike Frysinger65d93c12014-02-01 02:59:46 -0500138 self.mox.ReplayAll()
Olof Johanssona96810f2012-09-04 16:20:03 -0700139 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
140 self.assertTrue(failure)
141
142 # Code-only changes, should pass
Mike Frysinger65d93c12014-02-01 02:59:46 -0500143 self.mox.UnsetStubs()
144 self.mox.StubOutWithMock(pre_upload, '_get_affected_files')
Olof Johanssona96810f2012-09-04 16:20:03 -0700145 pre_upload._get_affected_files(mox.IgnoreArg()).AndReturn(
146 ['/kernel/files/Makefile',
147 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
148 ])
Mike Frysinger65d93c12014-02-01 02:59:46 -0500149 self.mox.ReplayAll()
Olof Johanssona96810f2012-09-04 16:20:03 -0700150 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
151 self.assertFalse(failure)
152
153 # Config-only changes, should pass
Mike Frysinger65d93c12014-02-01 02:59:46 -0500154 self.mox.UnsetStubs()
155 self.mox.StubOutWithMock(pre_upload, '_get_affected_files')
Olof Johanssona96810f2012-09-04 16:20:03 -0700156 pre_upload._get_affected_files(mox.IgnoreArg()).AndReturn(
157 ['/kernel/files/chromeos/config/base.config',
158 ])
Mike Frysinger65d93c12014-02-01 02:59:46 -0500159 self.mox.ReplayAll()
Olof Johanssona96810f2012-09-04 16:20:03 -0700160 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
161 self.assertFalse(failure)
162
Jon Salz98255932012-08-18 14:48:02 +0800163
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500164class CheckEbuildEapi(cros_test_lib.MockTestCase):
165 """Tests for _check_ebuild_eapi."""
166
167 PORTAGE_STABLE = 'chromiumos/overlays/portage-stable'
168
169 def setUp(self):
170 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
171 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
172 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff',
173 side_effect=Exception())
174
175 def testSkipUpstreamOverlays(self):
176 """Skip ebuilds found in upstream overlays."""
177 self.file_mock.side_effect = Exception()
178 ret = pre_upload._check_ebuild_eapi(self.PORTAGE_STABLE, 'HEAD')
179 self.assertEqual(ret, None)
180
181 # Make sure our condition above triggers.
182 self.assertRaises(Exception, pre_upload._check_ebuild_eapi, 'o', 'HEAD')
183
184 def testSkipNonEbuilds(self):
185 """Skip non-ebuild files."""
186 self.content_mock.side_effect = Exception()
187
188 self.file_mock.return_value = ['some-file', 'ebuild/dir', 'an.ebuild~']
189 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
190 self.assertEqual(ret, None)
191
192 # Make sure our condition above triggers.
193 self.file_mock.return_value.append('a/real.ebuild')
194 self.assertRaises(Exception, pre_upload._check_ebuild_eapi, 'o', 'HEAD')
195
196 def testSkipSymlink(self):
197 """Skip files that are just symlinks."""
198 self.file_mock.return_value = ['a-r1.ebuild']
199 self.content_mock.return_value = 'a.ebuild'
200 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
201 self.assertEqual(ret, None)
202
203 def testRejectEapiImplicit0Content(self):
204 """Reject ebuilds that do not declare EAPI (so it's 0)."""
205 self.file_mock.return_value = ['a.ebuild']
206
207 self.content_mock.return_value = """# Header
208IUSE="foo"
209src_compile() { }
210"""
211 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500212 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500213
214 def testRejectExplicitEapi1Content(self):
215 """Reject ebuilds that do declare old EAPI explicitly."""
216 self.file_mock.return_value = ['a.ebuild']
217
218 template = """# Header
219EAPI=%s
220IUSE="foo"
221src_compile() { }
222"""
223 # Make sure we only check the first EAPI= setting.
224 self.content_mock.return_value = template % '1\nEAPI=4'
225 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500226 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500227
228 # Verify we handle double quotes too.
229 self.content_mock.return_value = template % '"1"'
230 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500231 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500232
233 # Verify we handle single quotes too.
234 self.content_mock.return_value = template % "'1'"
235 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500236 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500237
238 def testAcceptExplicitEapi4Content(self):
239 """Accept ebuilds that do declare new EAPI explicitly."""
240 self.file_mock.return_value = ['a.ebuild']
241
242 template = """# Header
243EAPI=%s
244IUSE="foo"
245src_compile() { }
246"""
247 # Make sure we only check the first EAPI= setting.
248 self.content_mock.return_value = template % '4\nEAPI=1'
249 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
250 self.assertEqual(ret, None)
251
252 # Verify we handle double quotes too.
253 self.content_mock.return_value = template % '"5"'
254 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
255 self.assertEqual(ret, None)
256
257 # Verify we handle single quotes too.
258 self.content_mock.return_value = template % "'5-hdepend'"
259 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
260 self.assertEqual(ret, None)
261
262
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400263class CheckEbuildKeywords(cros_test_lib.MockTestCase):
264 """Tests for _check_ebuild_keywords."""
265
266 def setUp(self):
267 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
268 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
269
270 def testNoEbuilds(self):
271 """If no ebuilds are found, do not scan."""
272 self.file_mock.return_value = ['a.file', 'ebuild-is-not.foo']
273
274 ret = pre_upload._check_ebuild_keywords('overlay', 'HEAD')
275 self.assertEqual(ret, None)
276
277 self.assertEqual(self.content_mock.call_count, 0)
278
279 def testSomeEbuilds(self):
280 """If ebuilds are found, only scan them."""
281 self.file_mock.return_value = ['a.file', 'blah', 'foo.ebuild', 'cow']
282 self.content_mock.return_value = ''
283
284 ret = pre_upload._check_ebuild_keywords('overlay', 'HEAD')
285 self.assertEqual(ret, None)
286
287 self.assertEqual(self.content_mock.call_count, 1)
288
289 def _CheckContent(self, content, fails):
290 """Test helper for inputs/outputs.
291
292 Args:
293 content: The ebuild content to test.
294 fails: Whether |content| should trigger a hook failure.
295 """
296 self.file_mock.return_value = ['a.ebuild']
297 self.content_mock.return_value = content
298
299 ret = pre_upload._check_ebuild_keywords('overlay', 'HEAD')
300 if fails:
Mike Frysingerb81102f2014-11-21 00:33:35 -0500301 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400302 else:
303 self.assertEqual(ret, None)
304
305 self.assertEqual(self.content_mock.call_count, 1)
306
307 def testEmpty(self):
308 """Check KEYWORDS= is accepted."""
309 self._CheckContent('# HEADER\nKEYWORDS=\nblah\n', False)
310
311 def testEmptyQuotes(self):
312 """Check KEYWORDS="" is accepted."""
313 self._CheckContent('# HEADER\nKEYWORDS=" "\nblah\n', False)
314
315 def testStableGlob(self):
316 """Check KEYWORDS=* is accepted."""
317 self._CheckContent('# HEADER\nKEYWORDS="\t*\t"\nblah\n', False)
318
319 def testUnstableGlob(self):
320 """Check KEYWORDS=~* is accepted."""
321 self._CheckContent('# HEADER\nKEYWORDS="~* "\nblah\n', False)
322
323 def testRestrictedGlob(self):
324 """Check KEYWORDS=-* is accepted."""
325 self._CheckContent('# HEADER\nKEYWORDS="\t-* arm"\nblah\n', False)
326
327 def testMissingGlobs(self):
328 """Reject KEYWORDS missing any globs."""
329 self._CheckContent('# HEADER\nKEYWORDS="~arm x86"\nblah\n', True)
330
331
Mike Frysingercd363c82014-02-01 05:20:18 -0500332class CheckEbuildVirtualPv(cros_test_lib.MockTestCase):
333 """Tests for _check_ebuild_virtual_pv."""
334
335 PORTAGE_STABLE = 'chromiumos/overlays/portage-stable'
336 CHROMIUMOS_OVERLAY = 'chromiumos/overlays/chromiumos'
337 BOARD_OVERLAY = 'chromiumos/overlays/board-overlays'
338 PRIVATE_OVERLAY = 'chromeos/overlays/overlay-link-private'
339 PRIVATE_VARIANT_OVERLAY = ('chromeos/overlays/'
340 'overlay-variant-daisy-spring-private')
341
342 def setUp(self):
343 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
344
345 def testNoVirtuals(self):
346 """Skip non virtual packages."""
347 self.file_mock.return_value = ['some/package/package-3.ebuild']
348 ret = pre_upload._check_ebuild_virtual_pv('overlay', 'H')
349 self.assertEqual(ret, None)
350
351 def testCommonVirtuals(self):
352 """Non-board overlays should use PV=1."""
353 template = 'virtual/foo/foo-%s.ebuild'
354 self.file_mock.return_value = [template % '1']
355 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
356 self.assertEqual(ret, None)
357
358 self.file_mock.return_value = [template % '2']
359 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500360 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500361
362 def testPublicBoardVirtuals(self):
363 """Public board overlays should use PV=2."""
364 template = 'overlay-lumpy/virtual/foo/foo-%s.ebuild'
365 self.file_mock.return_value = [template % '2']
366 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
367 self.assertEqual(ret, None)
368
369 self.file_mock.return_value = [template % '2.5']
370 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500371 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500372
373 def testPublicBoardVariantVirtuals(self):
374 """Public board variant overlays should use PV=2.5."""
375 template = 'overlay-variant-lumpy-foo/virtual/foo/foo-%s.ebuild'
376 self.file_mock.return_value = [template % '2.5']
377 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
378 self.assertEqual(ret, None)
379
380 self.file_mock.return_value = [template % '3']
381 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500382 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500383
384 def testPrivateBoardVirtuals(self):
385 """Private board overlays should use PV=3."""
386 template = 'virtual/foo/foo-%s.ebuild'
387 self.file_mock.return_value = [template % '3']
388 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
389 self.assertEqual(ret, None)
390
391 self.file_mock.return_value = [template % '3.5']
392 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500393 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500394
395 def testPrivateBoardVariantVirtuals(self):
396 """Private board variant overlays should use PV=3.5."""
397 template = 'virtual/foo/foo-%s.ebuild'
398 self.file_mock.return_value = [template % '3.5']
399 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
400 self.assertEqual(ret, None)
401
402 self.file_mock.return_value = [template % '4']
403 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500404 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500405
Mike Frysinger98638102014-08-28 00:15:08 -0400406
Mike Frysinger98638102014-08-28 00:15:08 -0400407class CheckLicenseCopyrightHeader(cros_test_lib.MockTestCase):
408 """Tests for _check_license."""
409
410 def setUp(self):
411 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
412 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
413
414 def testOldHeaders(self):
415 """Accept old header styles."""
416 HEADERS = (
417 ('#!/bin/sh\n'
418 '# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.\n'
419 '# Use of this source code is governed by a BSD-style license that'
420 ' can be\n'
421 '# found in the LICENSE file.\n'),
422 ('// Copyright 2010-13 The Chromium OS Authors. All rights reserved.\n'
423 '// Use of this source code is governed by a BSD-style license that'
424 ' can be\n'
425 '// found in the LICENSE file.\n'),
426 )
427 self.file_mock.return_value = ['file']
428 for header in HEADERS:
429 self.content_mock.return_value = header
430 self.assertEqual(None, pre_upload._check_license('proj', 'sha1'))
431
432 def testRejectC(self):
433 """Reject the (c) in newer headers."""
434 HEADERS = (
435 ('// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.\n'
436 '// Use of this source code is governed by a BSD-style license that'
437 ' can be\n'
438 '// found in the LICENSE file.\n'),
439 ('// Copyright (c) 2020 The Chromium OS Authors. All rights reserved.\n'
440 '// Use of this source code is governed by a BSD-style license that'
441 ' can be\n'
442 '// found in the LICENSE file.\n'),
443 )
444 self.file_mock.return_value = ['file']
445 for header in HEADERS:
446 self.content_mock.return_value = header
447 self.assertNotEqual(None, pre_upload._check_license('proj', 'sha1'))
448
449
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400450class CommitMessageTestCase(cros_test_lib.MockTestCase):
451 """Test case for funcs that check commit messages."""
452
453 def setUp(self):
454 self.msg_mock = self.PatchObject(pre_upload, '_get_commit_desc')
455
456 @staticmethod
457 def CheckMessage(_project, _commit):
458 raise AssertionError('Test class must declare CheckMessage')
459 # This dummy return is to silence pylint warning W1111 so we don't have to
460 # enable it for all the call sites below.
461 return 1 # pylint: disable=W0101
462
463 def assertMessageAccepted(self, msg, project='project', commit='1234'):
464 """Assert _check_change_has_bug_field accepts |msg|."""
465 self.msg_mock.return_value = msg
466 ret = self.CheckMessage(project, commit)
467 self.assertEqual(ret, None)
468
469 def assertMessageRejected(self, msg, project='project', commit='1234'):
470 """Assert _check_change_has_bug_field rejects |msg|."""
471 self.msg_mock.return_value = msg
472 ret = self.CheckMessage(project, commit)
473 self.assertTrue(isinstance(ret, errors.HookFailure))
474
475
476class CheckCommitMessageBug(CommitMessageTestCase):
477 """Tests for _check_change_has_bug_field."""
478
479 @staticmethod
480 def CheckMessage(project, commit):
481 return pre_upload._check_change_has_bug_field(project, commit)
482
483 def testNormal(self):
484 """Accept a commit message w/a valid BUG."""
485 self.assertMessageAccepted('\nBUG=chromium:1234\n')
486 self.assertMessageAccepted('\nBUG=chrome-os-partner:1234\n')
487
488 def testNone(self):
489 """Accept BUG=None."""
490 self.assertMessageAccepted('\nBUG=None\n')
491 self.assertMessageAccepted('\nBUG=none\n')
492 self.assertMessageRejected('\nBUG=NONE\n')
493
494 def testBlank(self):
495 """Reject blank values."""
496 self.assertMessageRejected('\nBUG=\n')
497 self.assertMessageRejected('\nBUG= \n')
498
499 def testNotFirstLine(self):
500 """Reject the first line."""
501 self.assertMessageRejected('BUG=None\n\n\n')
502
503 def testNotInline(self):
504 """Reject not at the start of line."""
505 self.assertMessageRejected('\n BUG=None\n')
506 self.assertMessageRejected('\n\tBUG=None\n')
507
508 def testOldTrackers(self):
509 """Reject commit messages using old trackers."""
510 self.assertMessageRejected('\nBUG=chromium-os:1234\n')
511
512 def testNoTrackers(self):
513 """Reject commit messages w/invalid trackers."""
514 self.assertMessageRejected('\nBUG=booga:1234\n')
515
516 def testMissing(self):
517 """Reject commit messages w/no BUG line."""
518 self.assertMessageRejected('foo\n')
519
520 def testCase(self):
521 """Reject bug lines that are not BUG."""
522 self.assertMessageRejected('\nbug=none\n')
523
524
525class CheckCommitMessageCqDepend(CommitMessageTestCase):
526 """Tests for _check_change_has_valid_cq_depend."""
527
528 @staticmethod
529 def CheckMessage(project, commit):
530 return pre_upload._check_change_has_valid_cq_depend(project, commit)
531
532 def testNormal(self):
533 """Accept valid CQ-DEPENDs line."""
534 self.assertMessageAccepted('\nCQ-DEPEND=CL:1234\n')
535
536 def testInvalid(self):
537 """Reject invalid CQ-DEPENDs line."""
538 self.assertMessageRejected('\nCQ-DEPEND=CL=1234\n')
539 self.assertMessageRejected('\nCQ-DEPEND=None\n')
540
541
542class CheckCommitMessageTest(CommitMessageTestCase):
543 """Tests for _check_change_has_test_field."""
544
545 @staticmethod
546 def CheckMessage(project, commit):
547 return pre_upload._check_change_has_test_field(project, commit)
548
549 def testNormal(self):
550 """Accept a commit message w/a valid TEST."""
551 self.assertMessageAccepted('\nTEST=i did it\n')
552
553 def testNone(self):
554 """Accept TEST=None."""
555 self.assertMessageAccepted('\nTEST=None\n')
556 self.assertMessageAccepted('\nTEST=none\n')
557
558 def testBlank(self):
559 """Reject blank values."""
560 self.assertMessageRejected('\nTEST=\n')
561 self.assertMessageRejected('\nTEST= \n')
562
563 def testNotFirstLine(self):
564 """Reject the first line."""
565 self.assertMessageRejected('TEST=None\n\n\n')
566
567 def testNotInline(self):
568 """Reject not at the start of line."""
569 self.assertMessageRejected('\n TEST=None\n')
570 self.assertMessageRejected('\n\tTEST=None\n')
571
572 def testMissing(self):
573 """Reject commit messages w/no TEST line."""
574 self.assertMessageRejected('foo\n')
575
576 def testCase(self):
577 """Reject bug lines that are not TEST."""
578 self.assertMessageRejected('\ntest=none\n')
579
580
581class CheckCommitMessageChangeId(CommitMessageTestCase):
582 """Tests for _check_change_has_proper_changeid."""
583
584 @staticmethod
585 def CheckMessage(project, commit):
586 return pre_upload._check_change_has_proper_changeid(project, commit)
587
588 def testNormal(self):
589 """Accept a commit message w/a valid Change-Id."""
590 self.assertMessageAccepted('foo\n\nChange-Id: I1234\n')
591
592 def testBlank(self):
593 """Reject blank values."""
594 self.assertMessageRejected('\nChange-Id:\n')
595 self.assertMessageRejected('\nChange-Id: \n')
596
597 def testNotFirstLine(self):
598 """Reject the first line."""
599 self.assertMessageRejected('TEST=None\n\n\n')
600
601 def testNotInline(self):
602 """Reject not at the start of line."""
603 self.assertMessageRejected('\n Change-Id: I1234\n')
604 self.assertMessageRejected('\n\tChange-Id: I1234\n')
605
606 def testMissing(self):
607 """Reject commit messages missing the line."""
608 self.assertMessageRejected('foo\n')
609
610 def testCase(self):
611 """Reject bug lines that are not Change-Id."""
612 self.assertMessageRejected('\nchange-id: I1234\n')
613 self.assertMessageRejected('\nChange-id: I1234\n')
614 self.assertMessageRejected('\nChange-ID: I1234\n')
615
616 def testEnd(self):
617 """Reject Change-Id's that are not last."""
618 self.assertMessageRejected('\nChange-Id: I1234\nbar\n')
619
Mike Frysinger02b88bd2014-11-21 00:29:38 -0500620 def testSobTag(self):
621 """Permit s-o-b tags to follow the Change-Id."""
622 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nSigned-off-by: Hi\n')
623
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400624
Mike Frysinger36b2ebc2014-10-31 14:02:03 -0400625class CheckCommitMessageStyle(CommitMessageTestCase):
626 """Tests for _check_commit_message_style."""
627
628 @staticmethod
629 def CheckMessage(project, commit):
630 return pre_upload._check_commit_message_style(project, commit)
631
632 def testNormal(self):
633 """Accept valid commit messages."""
634 self.assertMessageAccepted('one sentence.\n')
635 self.assertMessageAccepted('some.module: do it!\n')
636 self.assertMessageAccepted('one line\n\nmore stuff here.')
637
638 def testNoBlankSecondLine(self):
639 """Reject messages that have stuff on the second line."""
640 self.assertMessageRejected('one sentence.\nbad fish!\n')
641
642 def testFirstLineMultipleSentences(self):
643 """Reject messages that have more than one sentence in the summary."""
644 self.assertMessageRejected('one sentence. two sentence!\n')
645
646 def testFirstLineTooLone(self):
647 """Reject first lines that are too long."""
648 self.assertMessageRejected('o' * 200)
649
650
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500651class HelpersTest(cros_test_lib.MockTestCase):
652 """Various tests for utility functions."""
653
654 def _SetupGetAffectedFiles(self):
655 self.PatchObject(git, 'RawDiff', return_value=[
656 # A modified normal file.
657 git.RawDiffEntry(src_mode='100644', dst_mode='100644', src_sha='abc',
658 dst_sha='abc', status='M', score=None,
659 src_file='buildbot/constants.py', dst_file=None),
660 # A new symlink file.
661 git.RawDiffEntry(src_mode='000000', dst_mode='120000', src_sha='abc',
662 dst_sha='abc', status='A', score=None,
663 src_file='scripts/cros_env_whitelist', dst_file=None),
664 # A deleted file.
665 git.RawDiffEntry(src_mode='100644', dst_mode='000000', src_sha='abc',
666 dst_sha='000000', status='D', score=None,
667 src_file='scripts/sync_sonic.py', dst_file=None),
668 ])
669
670 def testGetAffectedFilesNoDeletesNoRelative(self):
671 """Verify _get_affected_files() works w/no delete & not relative."""
672 self._SetupGetAffectedFiles()
673 path = os.getcwd()
674 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
675 relative=False)
676 exp_files = [os.path.join(path, 'buildbot/constants.py')]
677 self.assertEquals(files, exp_files)
678
679 def testGetAffectedFilesDeletesNoRelative(self):
680 """Verify _get_affected_files() works w/delete & not relative."""
681 self._SetupGetAffectedFiles()
682 path = os.getcwd()
683 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
684 relative=False)
685 exp_files = [os.path.join(path, 'buildbot/constants.py'),
686 os.path.join(path, 'scripts/sync_sonic.py')]
687 self.assertEquals(files, exp_files)
688
689 def testGetAffectedFilesNoDeletesRelative(self):
690 """Verify _get_affected_files() works w/no delete & relative."""
691 self._SetupGetAffectedFiles()
692 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
693 relative=True)
694 exp_files = ['buildbot/constants.py']
695 self.assertEquals(files, exp_files)
696
697 def testGetAffectedFilesDeletesRelative(self):
698 """Verify _get_affected_files() works w/delete & relative."""
699 self._SetupGetAffectedFiles()
700 path = os.getcwd()
701 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
702 relative=True)
703 exp_files = ['buildbot/constants.py', 'scripts/sync_sonic.py']
704 self.assertEquals(files, exp_files)
705
706
Jon Salz98255932012-08-18 14:48:02 +0800707if __name__ == '__main__':
Mike Frysinger65d93c12014-02-01 02:59:46 -0500708 cros_test_lib.main()