blob: 5b78f1b4f02707112ae3227aabe428171fe8e5c8 [file] [log] [blame]
Daniel Erat9d203ff2015-02-17 10:12:21 -07001#!/usr/bin/python2
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
David Jamesc3b68b32013-04-03 09:17:03 -070011import os
12import sys
Jon Salz98255932012-08-18 14:48:02 +080013
Mike Frysingerbf8b91c2014-02-01 02:50:27 -050014import errors
15
Jon Salz98255932012-08-18 14:48:02 +080016# pylint: disable=W0212
Mike Frysinger65d93c12014-02-01 02:59:46 -050017# We access private members of the pre_upload module all over the place.
18
Mike Frysinger55f85b52014-12-18 14:45:21 -050019# Make sure we can find the chromite paths.
20sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)),
21 '..', '..'))
Jon Salz98255932012-08-18 14:48:02 +080022
Mike Frysinger55f85b52014-12-18 14:45:21 -050023from chromite.cbuildbot import constants
Mike Frysinger65d93c12014-02-01 02:59:46 -050024from chromite.lib import cros_test_lib
Mike Frysingerd3bd32c2014-11-24 23:34:29 -050025from chromite.lib import git
Daniel Erata350fd32014-09-29 14:02:34 -070026from chromite.lib import osutils
Mike Frysinger65d93c12014-02-01 02:59:46 -050027
Mike Frysinger55f85b52014-12-18 14:45:21 -050028# Needs to be after chromite imports so we use the bundled copy.
29import mock
30
Mike Frysinger65d93c12014-02-01 02:59:46 -050031
Jon Salz98255932012-08-18 14:48:02 +080032pre_upload = __import__('pre-upload')
33
34
Mike Frysinger65d93c12014-02-01 02:59:46 -050035class TryUTF8DecodeTest(cros_test_lib.TestCase):
Mike Frysingerae409522014-02-01 03:16:11 -050036 """Verify we sanely handle unicode content."""
37
Jon Salz98255932012-08-18 14:48:02 +080038 def runTest(self):
39 self.assertEquals(u'', pre_upload._try_utf8_decode(''))
40 self.assertEquals(u'abc', pre_upload._try_utf8_decode('abc'))
Daniel Erat9d203ff2015-02-17 10:12:21 -070041 self.assertEquals(
42 u'你好布萊恩',
43 pre_upload._try_utf8_decode('你好布萊恩'))
Jon Salz98255932012-08-18 14:48:02 +080044 # Invalid UTF-8
45 self.assertEquals('\x80', pre_upload._try_utf8_decode('\x80'))
46
47
Mike Frysinger1459d362014-12-06 13:53:23 -050048class CheckNoLongLinesTest(cros_test_lib.MockTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -050049 """Tests for _check_no_long_lines."""
50
Jon Salz98255932012-08-18 14:48:02 +080051 def setUp(self):
Mike Frysinger1459d362014-12-06 13:53:23 -050052 self.PatchObject(pre_upload, '_get_affected_files', return_value=['x.py'])
53 self.PatchObject(pre_upload, '_filter_files', return_value=['x.py'])
54 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
Jon Salz98255932012-08-18 14:48:02 +080055
Jon Salz98255932012-08-18 14:48:02 +080056 def runTest(self):
Mike Frysinger1459d362014-12-06 13:53:23 -050057 self.diff_mock.return_value = [
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
66 ]
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 Frysinger1459d362014-12-06 13:53:23 -0500127class CheckKernelConfig(cros_test_lib.MockTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -0500128 """Tests for _kernel_configcheck."""
129
Mike Frysinger1459d362014-12-06 13:53:23 -0500130 def setUp(self):
131 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
132
133 def testMixedChanges(self):
134 """Mixing of changes should fail."""
135 self.file_mock.return_value = [
136 '/kernel/files/chromeos/config/base.config',
137 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
138 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700139 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
140 self.assertTrue(failure)
141
Mike Frysinger1459d362014-12-06 13:53:23 -0500142 def testCodeOnly(self):
143 """Code-only changes should pass."""
144 self.file_mock.return_value = [
145 '/kernel/files/Makefile',
146 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
147 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700148 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
149 self.assertFalse(failure)
150
Mike Frysinger1459d362014-12-06 13:53:23 -0500151 def testConfigOnlyChanges(self):
152 """Config-only changes should pass."""
153 self.file_mock.return_value = [
154 '/kernel/files/chromeos/config/base.config',
155 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700156 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
157 self.assertFalse(failure)
158
Jon Salz98255932012-08-18 14:48:02 +0800159
Daniel Erat9d203ff2015-02-17 10:12:21 -0700160class CheckPortageMakeUseVar(cros_test_lib.MockTestCase):
161 """Tests for _check_portage_make_use_var."""
162
163 def setUp(self):
164 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
165 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
166
167 def testMakeConfOmitsOriginalUseValue(self):
168 """Fail for make.conf that discards the previous value of $USE."""
169 self.file_mock.return_value = ['make.conf']
170 self.content_mock.return_value = 'USE="foo"\nUSE="${USE} bar"'
171 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
172 self.assertTrue(failure, failure)
173
174 def testMakeConfCorrectUsage(self):
175 """Succeed for make.conf that preserves the previous value of $USE."""
176 self.file_mock.return_value = ['make.conf']
177 self.content_mock.return_value = 'USE="${USE} foo"\nUSE="${USE}" bar'
178 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
179 self.assertFalse(failure, failure)
180
181 def testMakeDefaultsReferencesOriginalUseValue(self):
182 """Fail for make.defaults that refers to a not-yet-set $USE value."""
183 self.file_mock.return_value = ['make.defaults']
184 self.content_mock.return_value = 'USE="${USE} foo"'
185 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
186 self.assertTrue(failure, failure)
187
188 # Also check for "$USE" without curly brackets.
189 self.content_mock.return_value = 'USE="$USE foo"'
190 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
191 self.assertTrue(failure, failure)
192
193 def testMakeDefaultsOverwritesUseValue(self):
194 """Fail for make.defaults that discards its own $USE value."""
195 self.file_mock.return_value = ['make.defaults']
196 self.content_mock.return_value = 'USE="foo"\nUSE="bar"'
197 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
198 self.assertTrue(failure, failure)
199
200 def testMakeDefaultsCorrectUsage(self):
201 """Succeed for make.defaults that sets and preserves $USE."""
202 self.file_mock.return_value = ['make.defaults']
203 self.content_mock.return_value = 'USE="foo"\nUSE="${USE}" bar'
204 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
205 self.assertFalse(failure, failure)
206
207
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500208class CheckEbuildEapi(cros_test_lib.MockTestCase):
209 """Tests for _check_ebuild_eapi."""
210
211 PORTAGE_STABLE = 'chromiumos/overlays/portage-stable'
212
213 def setUp(self):
214 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
215 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
216 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff',
217 side_effect=Exception())
218
219 def testSkipUpstreamOverlays(self):
220 """Skip ebuilds found in upstream overlays."""
221 self.file_mock.side_effect = Exception()
222 ret = pre_upload._check_ebuild_eapi(self.PORTAGE_STABLE, 'HEAD')
223 self.assertEqual(ret, None)
224
225 # Make sure our condition above triggers.
226 self.assertRaises(Exception, pre_upload._check_ebuild_eapi, 'o', 'HEAD')
227
228 def testSkipNonEbuilds(self):
229 """Skip non-ebuild files."""
230 self.content_mock.side_effect = Exception()
231
232 self.file_mock.return_value = ['some-file', 'ebuild/dir', 'an.ebuild~']
233 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
234 self.assertEqual(ret, None)
235
236 # Make sure our condition above triggers.
237 self.file_mock.return_value.append('a/real.ebuild')
238 self.assertRaises(Exception, pre_upload._check_ebuild_eapi, 'o', 'HEAD')
239
240 def testSkipSymlink(self):
241 """Skip files that are just symlinks."""
242 self.file_mock.return_value = ['a-r1.ebuild']
243 self.content_mock.return_value = 'a.ebuild'
244 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
245 self.assertEqual(ret, None)
246
247 def testRejectEapiImplicit0Content(self):
248 """Reject ebuilds that do not declare EAPI (so it's 0)."""
249 self.file_mock.return_value = ['a.ebuild']
250
251 self.content_mock.return_value = """# Header
252IUSE="foo"
253src_compile() { }
254"""
255 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500256 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500257
258 def testRejectExplicitEapi1Content(self):
259 """Reject ebuilds that do declare old EAPI explicitly."""
260 self.file_mock.return_value = ['a.ebuild']
261
262 template = """# Header
263EAPI=%s
264IUSE="foo"
265src_compile() { }
266"""
267 # Make sure we only check the first EAPI= setting.
268 self.content_mock.return_value = template % '1\nEAPI=4'
269 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500270 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500271
272 # Verify we handle double quotes too.
273 self.content_mock.return_value = template % '"1"'
274 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500275 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500276
277 # Verify we handle single quotes too.
278 self.content_mock.return_value = template % "'1'"
279 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500280 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500281
282 def testAcceptExplicitEapi4Content(self):
283 """Accept ebuilds that do declare new EAPI explicitly."""
284 self.file_mock.return_value = ['a.ebuild']
285
286 template = """# Header
287EAPI=%s
288IUSE="foo"
289src_compile() { }
290"""
291 # Make sure we only check the first EAPI= setting.
292 self.content_mock.return_value = template % '4\nEAPI=1'
293 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
294 self.assertEqual(ret, None)
295
296 # Verify we handle double quotes too.
297 self.content_mock.return_value = template % '"5"'
298 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
299 self.assertEqual(ret, None)
300
301 # Verify we handle single quotes too.
302 self.content_mock.return_value = template % "'5-hdepend'"
303 ret = pre_upload._check_ebuild_eapi('overlay', 'HEAD')
304 self.assertEqual(ret, None)
305
306
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400307class CheckEbuildKeywords(cros_test_lib.MockTestCase):
308 """Tests for _check_ebuild_keywords."""
309
310 def setUp(self):
311 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
312 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
313
314 def testNoEbuilds(self):
315 """If no ebuilds are found, do not scan."""
316 self.file_mock.return_value = ['a.file', 'ebuild-is-not.foo']
317
318 ret = pre_upload._check_ebuild_keywords('overlay', 'HEAD')
319 self.assertEqual(ret, None)
320
321 self.assertEqual(self.content_mock.call_count, 0)
322
323 def testSomeEbuilds(self):
324 """If ebuilds are found, only scan them."""
325 self.file_mock.return_value = ['a.file', 'blah', 'foo.ebuild', 'cow']
326 self.content_mock.return_value = ''
327
328 ret = pre_upload._check_ebuild_keywords('overlay', 'HEAD')
329 self.assertEqual(ret, None)
330
331 self.assertEqual(self.content_mock.call_count, 1)
332
333 def _CheckContent(self, content, fails):
334 """Test helper for inputs/outputs.
335
336 Args:
337 content: The ebuild content to test.
338 fails: Whether |content| should trigger a hook failure.
339 """
340 self.file_mock.return_value = ['a.ebuild']
341 self.content_mock.return_value = content
342
343 ret = pre_upload._check_ebuild_keywords('overlay', 'HEAD')
344 if fails:
Mike Frysingerb81102f2014-11-21 00:33:35 -0500345 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400346 else:
347 self.assertEqual(ret, None)
348
349 self.assertEqual(self.content_mock.call_count, 1)
350
351 def testEmpty(self):
352 """Check KEYWORDS= is accepted."""
353 self._CheckContent('# HEADER\nKEYWORDS=\nblah\n', False)
354
355 def testEmptyQuotes(self):
356 """Check KEYWORDS="" is accepted."""
357 self._CheckContent('# HEADER\nKEYWORDS=" "\nblah\n', False)
358
359 def testStableGlob(self):
360 """Check KEYWORDS=* is accepted."""
361 self._CheckContent('# HEADER\nKEYWORDS="\t*\t"\nblah\n', False)
362
363 def testUnstableGlob(self):
364 """Check KEYWORDS=~* is accepted."""
365 self._CheckContent('# HEADER\nKEYWORDS="~* "\nblah\n', False)
366
367 def testRestrictedGlob(self):
368 """Check KEYWORDS=-* is accepted."""
369 self._CheckContent('# HEADER\nKEYWORDS="\t-* arm"\nblah\n', False)
370
371 def testMissingGlobs(self):
372 """Reject KEYWORDS missing any globs."""
373 self._CheckContent('# HEADER\nKEYWORDS="~arm x86"\nblah\n', True)
374
375
Mike Frysingercd363c82014-02-01 05:20:18 -0500376class CheckEbuildVirtualPv(cros_test_lib.MockTestCase):
377 """Tests for _check_ebuild_virtual_pv."""
378
379 PORTAGE_STABLE = 'chromiumos/overlays/portage-stable'
380 CHROMIUMOS_OVERLAY = 'chromiumos/overlays/chromiumos'
381 BOARD_OVERLAY = 'chromiumos/overlays/board-overlays'
382 PRIVATE_OVERLAY = 'chromeos/overlays/overlay-link-private'
383 PRIVATE_VARIANT_OVERLAY = ('chromeos/overlays/'
384 'overlay-variant-daisy-spring-private')
385
386 def setUp(self):
387 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
388
389 def testNoVirtuals(self):
390 """Skip non virtual packages."""
391 self.file_mock.return_value = ['some/package/package-3.ebuild']
392 ret = pre_upload._check_ebuild_virtual_pv('overlay', 'H')
393 self.assertEqual(ret, None)
394
395 def testCommonVirtuals(self):
396 """Non-board overlays should use PV=1."""
397 template = 'virtual/foo/foo-%s.ebuild'
398 self.file_mock.return_value = [template % '1']
399 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
400 self.assertEqual(ret, None)
401
402 self.file_mock.return_value = [template % '2']
403 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500404 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500405
406 def testPublicBoardVirtuals(self):
407 """Public board overlays should use PV=2."""
408 template = 'overlay-lumpy/virtual/foo/foo-%s.ebuild'
409 self.file_mock.return_value = [template % '2']
410 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
411 self.assertEqual(ret, None)
412
413 self.file_mock.return_value = [template % '2.5']
414 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500415 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500416
417 def testPublicBoardVariantVirtuals(self):
418 """Public board variant overlays should use PV=2.5."""
419 template = 'overlay-variant-lumpy-foo/virtual/foo/foo-%s.ebuild'
420 self.file_mock.return_value = [template % '2.5']
421 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
422 self.assertEqual(ret, None)
423
424 self.file_mock.return_value = [template % '3']
425 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500426 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500427
428 def testPrivateBoardVirtuals(self):
429 """Private board overlays should use PV=3."""
430 template = 'virtual/foo/foo-%s.ebuild'
431 self.file_mock.return_value = [template % '3']
432 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
433 self.assertEqual(ret, None)
434
435 self.file_mock.return_value = [template % '3.5']
436 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500437 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500438
439 def testPrivateBoardVariantVirtuals(self):
440 """Private board variant overlays should use PV=3.5."""
441 template = 'virtual/foo/foo-%s.ebuild'
442 self.file_mock.return_value = [template % '3.5']
443 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
444 self.assertEqual(ret, None)
445
446 self.file_mock.return_value = [template % '4']
447 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500448 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500449
Mike Frysinger98638102014-08-28 00:15:08 -0400450
Mike Frysinger98638102014-08-28 00:15:08 -0400451class CheckLicenseCopyrightHeader(cros_test_lib.MockTestCase):
452 """Tests for _check_license."""
453
454 def setUp(self):
455 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
456 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
457
458 def testOldHeaders(self):
459 """Accept old header styles."""
460 HEADERS = (
461 ('#!/bin/sh\n'
462 '# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.\n'
463 '# Use of this source code is governed by a BSD-style license that'
464 ' can be\n'
465 '# found in the LICENSE file.\n'),
466 ('// Copyright 2010-13 The Chromium OS Authors. All rights reserved.\n'
467 '// Use of this source code is governed by a BSD-style license that'
468 ' can be\n'
469 '// found in the LICENSE file.\n'),
470 )
471 self.file_mock.return_value = ['file']
472 for header in HEADERS:
473 self.content_mock.return_value = header
474 self.assertEqual(None, pre_upload._check_license('proj', 'sha1'))
475
476 def testRejectC(self):
477 """Reject the (c) in newer headers."""
478 HEADERS = (
479 ('// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.\n'
480 '// Use of this source code is governed by a BSD-style license that'
481 ' can be\n'
482 '// found in the LICENSE file.\n'),
483 ('// Copyright (c) 2020 The Chromium OS Authors. All rights reserved.\n'
484 '// Use of this source code is governed by a BSD-style license that'
485 ' can be\n'
486 '// found in the LICENSE file.\n'),
487 )
488 self.file_mock.return_value = ['file']
489 for header in HEADERS:
490 self.content_mock.return_value = header
491 self.assertNotEqual(None, pre_upload._check_license('proj', 'sha1'))
492
493
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400494class CommitMessageTestCase(cros_test_lib.MockTestCase):
495 """Test case for funcs that check commit messages."""
496
497 def setUp(self):
498 self.msg_mock = self.PatchObject(pre_upload, '_get_commit_desc')
499
500 @staticmethod
501 def CheckMessage(_project, _commit):
502 raise AssertionError('Test class must declare CheckMessage')
503 # This dummy return is to silence pylint warning W1111 so we don't have to
504 # enable it for all the call sites below.
505 return 1 # pylint: disable=W0101
506
507 def assertMessageAccepted(self, msg, project='project', commit='1234'):
508 """Assert _check_change_has_bug_field accepts |msg|."""
509 self.msg_mock.return_value = msg
510 ret = self.CheckMessage(project, commit)
511 self.assertEqual(ret, None)
512
513 def assertMessageRejected(self, msg, project='project', commit='1234'):
514 """Assert _check_change_has_bug_field rejects |msg|."""
515 self.msg_mock.return_value = msg
516 ret = self.CheckMessage(project, commit)
517 self.assertTrue(isinstance(ret, errors.HookFailure))
518
519
520class CheckCommitMessageBug(CommitMessageTestCase):
521 """Tests for _check_change_has_bug_field."""
522
523 @staticmethod
524 def CheckMessage(project, commit):
525 return pre_upload._check_change_has_bug_field(project, commit)
526
527 def testNormal(self):
528 """Accept a commit message w/a valid BUG."""
529 self.assertMessageAccepted('\nBUG=chromium:1234\n')
530 self.assertMessageAccepted('\nBUG=chrome-os-partner:1234\n')
Stefan Sauerd74ad562015-03-10 10:14:28 +0100531 self.assertMessageAccepted('\nBUG=b:1234\n')
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400532
533 def testNone(self):
534 """Accept BUG=None."""
535 self.assertMessageAccepted('\nBUG=None\n')
536 self.assertMessageAccepted('\nBUG=none\n')
537 self.assertMessageRejected('\nBUG=NONE\n')
538
539 def testBlank(self):
540 """Reject blank values."""
541 self.assertMessageRejected('\nBUG=\n')
542 self.assertMessageRejected('\nBUG= \n')
543
544 def testNotFirstLine(self):
545 """Reject the first line."""
546 self.assertMessageRejected('BUG=None\n\n\n')
547
548 def testNotInline(self):
549 """Reject not at the start of line."""
550 self.assertMessageRejected('\n BUG=None\n')
551 self.assertMessageRejected('\n\tBUG=None\n')
552
553 def testOldTrackers(self):
554 """Reject commit messages using old trackers."""
555 self.assertMessageRejected('\nBUG=chromium-os:1234\n')
556
557 def testNoTrackers(self):
558 """Reject commit messages w/invalid trackers."""
559 self.assertMessageRejected('\nBUG=booga:1234\n')
Stefan Sauerd74ad562015-03-10 10:14:28 +0100560 self.assertMessageRejected('\nBUG=br:1234\n')
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400561
562 def testMissing(self):
563 """Reject commit messages w/no BUG line."""
564 self.assertMessageRejected('foo\n')
565
566 def testCase(self):
567 """Reject bug lines that are not BUG."""
568 self.assertMessageRejected('\nbug=none\n')
569
570
571class CheckCommitMessageCqDepend(CommitMessageTestCase):
572 """Tests for _check_change_has_valid_cq_depend."""
573
574 @staticmethod
575 def CheckMessage(project, commit):
576 return pre_upload._check_change_has_valid_cq_depend(project, commit)
577
578 def testNormal(self):
579 """Accept valid CQ-DEPENDs line."""
580 self.assertMessageAccepted('\nCQ-DEPEND=CL:1234\n')
581
582 def testInvalid(self):
583 """Reject invalid CQ-DEPENDs line."""
584 self.assertMessageRejected('\nCQ-DEPEND=CL=1234\n')
585 self.assertMessageRejected('\nCQ-DEPEND=None\n')
586
587
588class CheckCommitMessageTest(CommitMessageTestCase):
589 """Tests for _check_change_has_test_field."""
590
591 @staticmethod
592 def CheckMessage(project, commit):
593 return pre_upload._check_change_has_test_field(project, commit)
594
595 def testNormal(self):
596 """Accept a commit message w/a valid TEST."""
597 self.assertMessageAccepted('\nTEST=i did it\n')
598
599 def testNone(self):
600 """Accept TEST=None."""
601 self.assertMessageAccepted('\nTEST=None\n')
602 self.assertMessageAccepted('\nTEST=none\n')
603
604 def testBlank(self):
605 """Reject blank values."""
606 self.assertMessageRejected('\nTEST=\n')
607 self.assertMessageRejected('\nTEST= \n')
608
609 def testNotFirstLine(self):
610 """Reject the first line."""
611 self.assertMessageRejected('TEST=None\n\n\n')
612
613 def testNotInline(self):
614 """Reject not at the start of line."""
615 self.assertMessageRejected('\n TEST=None\n')
616 self.assertMessageRejected('\n\tTEST=None\n')
617
618 def testMissing(self):
619 """Reject commit messages w/no TEST line."""
620 self.assertMessageRejected('foo\n')
621
622 def testCase(self):
623 """Reject bug lines that are not TEST."""
624 self.assertMessageRejected('\ntest=none\n')
625
626
627class CheckCommitMessageChangeId(CommitMessageTestCase):
628 """Tests for _check_change_has_proper_changeid."""
629
630 @staticmethod
631 def CheckMessage(project, commit):
632 return pre_upload._check_change_has_proper_changeid(project, commit)
633
634 def testNormal(self):
635 """Accept a commit message w/a valid Change-Id."""
636 self.assertMessageAccepted('foo\n\nChange-Id: I1234\n')
637
638 def testBlank(self):
639 """Reject blank values."""
640 self.assertMessageRejected('\nChange-Id:\n')
641 self.assertMessageRejected('\nChange-Id: \n')
642
643 def testNotFirstLine(self):
644 """Reject the first line."""
645 self.assertMessageRejected('TEST=None\n\n\n')
646
647 def testNotInline(self):
648 """Reject not at the start of line."""
649 self.assertMessageRejected('\n Change-Id: I1234\n')
650 self.assertMessageRejected('\n\tChange-Id: I1234\n')
651
652 def testMissing(self):
653 """Reject commit messages missing the line."""
654 self.assertMessageRejected('foo\n')
655
656 def testCase(self):
657 """Reject bug lines that are not Change-Id."""
658 self.assertMessageRejected('\nchange-id: I1234\n')
659 self.assertMessageRejected('\nChange-id: I1234\n')
660 self.assertMessageRejected('\nChange-ID: I1234\n')
661
662 def testEnd(self):
663 """Reject Change-Id's that are not last."""
664 self.assertMessageRejected('\nChange-Id: I1234\nbar\n')
665
Mike Frysinger02b88bd2014-11-21 00:29:38 -0500666 def testSobTag(self):
667 """Permit s-o-b tags to follow the Change-Id."""
668 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nSigned-off-by: Hi\n')
669
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400670
Mike Frysinger36b2ebc2014-10-31 14:02:03 -0400671class CheckCommitMessageStyle(CommitMessageTestCase):
672 """Tests for _check_commit_message_style."""
673
674 @staticmethod
675 def CheckMessage(project, commit):
676 return pre_upload._check_commit_message_style(project, commit)
677
678 def testNormal(self):
679 """Accept valid commit messages."""
680 self.assertMessageAccepted('one sentence.\n')
681 self.assertMessageAccepted('some.module: do it!\n')
682 self.assertMessageAccepted('one line\n\nmore stuff here.')
683
684 def testNoBlankSecondLine(self):
685 """Reject messages that have stuff on the second line."""
686 self.assertMessageRejected('one sentence.\nbad fish!\n')
687
688 def testFirstLineMultipleSentences(self):
689 """Reject messages that have more than one sentence in the summary."""
690 self.assertMessageRejected('one sentence. two sentence!\n')
691
692 def testFirstLineTooLone(self):
693 """Reject first lines that are too long."""
694 self.assertMessageRejected('o' * 200)
695
696
Mike Frysinger292b45d2014-11-25 01:17:10 -0500697def DiffEntry(src_file=None, dst_file=None, src_mode=None, dst_mode='100644',
698 status='M'):
699 """Helper to create a stub RawDiffEntry object"""
700 if src_mode is None:
701 if status == 'A':
702 src_mode = '000000'
703 elif status == 'M':
704 src_mode = dst_mode
705 elif status == 'D':
706 src_mode = dst_mode
707 dst_mode = '000000'
708
709 src_sha = dst_sha = 'abc'
710 if status == 'D':
711 dst_sha = '000000'
712 elif status == 'A':
713 src_sha = '000000'
714
715 return git.RawDiffEntry(src_mode=src_mode, dst_mode=dst_mode, src_sha=src_sha,
716 dst_sha=dst_sha, status=status, score=None,
717 src_file=src_file, dst_file=dst_file)
718
719
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500720class HelpersTest(cros_test_lib.MockTestCase):
721 """Various tests for utility functions."""
722
723 def _SetupGetAffectedFiles(self):
724 self.PatchObject(git, 'RawDiff', return_value=[
725 # A modified normal file.
Mike Frysinger292b45d2014-11-25 01:17:10 -0500726 DiffEntry(src_file='buildbot/constants.py', status='M'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500727 # A new symlink file.
Mike Frysinger292b45d2014-11-25 01:17:10 -0500728 DiffEntry(dst_file='scripts/cros_env_whitelist', dst_mode='120000',
729 status='A'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500730 # A deleted file.
Mike Frysinger292b45d2014-11-25 01:17:10 -0500731 DiffEntry(src_file='scripts/sync_sonic.py', status='D'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500732 ])
733
734 def testGetAffectedFilesNoDeletesNoRelative(self):
735 """Verify _get_affected_files() works w/no delete & not relative."""
736 self._SetupGetAffectedFiles()
737 path = os.getcwd()
738 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
739 relative=False)
740 exp_files = [os.path.join(path, 'buildbot/constants.py')]
741 self.assertEquals(files, exp_files)
742
743 def testGetAffectedFilesDeletesNoRelative(self):
744 """Verify _get_affected_files() works w/delete & not relative."""
745 self._SetupGetAffectedFiles()
746 path = os.getcwd()
747 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
748 relative=False)
749 exp_files = [os.path.join(path, 'buildbot/constants.py'),
750 os.path.join(path, 'scripts/sync_sonic.py')]
751 self.assertEquals(files, exp_files)
752
753 def testGetAffectedFilesNoDeletesRelative(self):
754 """Verify _get_affected_files() works w/no delete & relative."""
755 self._SetupGetAffectedFiles()
756 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
757 relative=True)
758 exp_files = ['buildbot/constants.py']
759 self.assertEquals(files, exp_files)
760
761 def testGetAffectedFilesDeletesRelative(self):
762 """Verify _get_affected_files() works w/delete & relative."""
763 self._SetupGetAffectedFiles()
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500764 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
765 relative=True)
766 exp_files = ['buildbot/constants.py', 'scripts/sync_sonic.py']
767 self.assertEquals(files, exp_files)
768
Mike Frysinger292b45d2014-11-25 01:17:10 -0500769 def testGetAffectedFilesDetails(self):
770 """Verify _get_affected_files() works w/full_details."""
771 self._SetupGetAffectedFiles()
772 files = pre_upload._get_affected_files('HEAD', full_details=True,
773 relative=True)
774 self.assertEquals(files[0].src_file, 'buildbot/constants.py')
775
776
777class CheckForUprev(cros_test_lib.MockTempDirTestCase):
778 """Tests for _check_for_uprev."""
779
780 def setUp(self):
781 self.file_mock = self.PatchObject(git, 'RawDiff')
782
783 def _Files(self, files):
784 """Create |files| in the tempdir and return full paths to them."""
785 for obj in files:
786 if obj.status == 'D':
787 continue
788 if obj.dst_file is None:
789 f = obj.src_file
790 else:
791 f = obj.dst_file
792 osutils.Touch(os.path.join(self.tempdir, f), makedirs=True)
793 return files
794
795 def assertAccepted(self, files, project='project', commit='fake sha1'):
796 """Assert _check_for_uprev accepts |files|."""
797 self.file_mock.return_value = self._Files(files)
798 ret = pre_upload._check_for_uprev(project, commit, project_top=self.tempdir)
799 self.assertEqual(ret, None)
800
801 def assertRejected(self, files, project='project', commit='fake sha1'):
802 """Assert _check_for_uprev rejects |files|."""
803 self.file_mock.return_value = self._Files(files)
804 ret = pre_upload._check_for_uprev(project, commit, project_top=self.tempdir)
805 self.assertTrue(isinstance(ret, errors.HookFailure))
806
807 def testWhitelistOverlay(self):
808 """Skip checks on whitelisted overlays."""
809 self.assertAccepted([DiffEntry(src_file='cat/pkg/pkg-0.ebuild')],
810 project='chromiumos/overlays/portage-stable')
811
812 def testWhitelistFiles(self):
813 """Skip checks on whitelisted files."""
814 files = ['ChangeLog', 'Manifest', 'metadata.xml']
815 self.assertAccepted([DiffEntry(src_file=os.path.join('c', 'p', x),
816 status='M')
817 for x in files])
818
819 def testRejectBasic(self):
820 """Reject ebuilds missing uprevs."""
821 self.assertRejected([DiffEntry(src_file='c/p/p-0.ebuild', status='M')])
822
823 def testNewPackage(self):
824 """Accept new ebuilds w/out uprevs."""
825 self.assertAccepted([DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
826 self.assertAccepted([DiffEntry(src_file='c/p/p-0-r12.ebuild', status='A')])
827
828 def testModifiedFilesOnly(self):
829 """Reject ebuilds w/out uprevs and changes in files/."""
830 osutils.Touch(os.path.join(self.tempdir, 'cat/pkg/pkg-0.ebuild'),
831 makedirs=True)
832 self.assertRejected([DiffEntry(src_file='cat/pkg/files/f', status='A')])
833 self.assertRejected([DiffEntry(src_file='cat/pkg/files/g', status='M')])
834
835 def testFilesNoEbuilds(self):
836 """Ignore changes to paths w/out ebuilds."""
837 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/f', status='A')])
838 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/g', status='M')])
839
840 def testModifiedFilesWithUprev(self):
841 """Accept ebuilds w/uprevs and changes in files/."""
842 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='A'),
843 DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
844 self.assertAccepted([
845 DiffEntry(src_file='c/p/files/f', status='M'),
846 DiffEntry(src_file='c/p/p-0-r1.ebuild', src_mode='120000',
847 dst_file='c/p/p-0-r2.ebuild', dst_mode='120000', status='R')])
848
Gwendal Grignoua3086c32014-12-09 11:17:22 -0800849 def testModifiedFilesWith9999(self):
850 """Accept 9999 ebuilds and changes in files/."""
851 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M'),
852 DiffEntry(src_file='c/p/p-9999.ebuild', status='M')])
853
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500854
Mike Frysinger55f85b52014-12-18 14:45:21 -0500855class DirectMainTest(cros_test_lib.MockTempDirTestCase):
856 """Tests for direct_main()"""
857
858 def setUp(self):
859 self.hooks_mock = self.PatchObject(pre_upload, '_run_project_hooks',
860 return_value=None)
861
862 def testNoArgs(self):
863 """If run w/no args, should check the current dir."""
864 ret = pre_upload.direct_main([])
865 self.assertEqual(ret, 0)
866 self.hooks_mock.assert_called_once_with(
867 mock.ANY, proj_dir=os.getcwd(), commit_list=[], presubmit=mock.ANY)
868
869 def testExplicitDir(self):
870 """Verify we can run on a diff dir."""
871 # Use the chromite dir since we know it exists.
872 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR])
873 self.assertEqual(ret, 0)
874 self.hooks_mock.assert_called_once_with(
875 mock.ANY, proj_dir=constants.CHROMITE_DIR, commit_list=[],
876 presubmit=mock.ANY)
877
878 def testBogusProject(self):
879 """A bogus project name should be fine (use default settings)."""
880 # Use the chromite dir since we know it exists.
881 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR,
882 '--project', 'foooooooooo'])
883 self.assertEqual(ret, 0)
884 self.hooks_mock.assert_called_once_with(
885 'foooooooooo', proj_dir=constants.CHROMITE_DIR, commit_list=[],
886 presubmit=mock.ANY)
887
888 def testBogustProjectNoDir(self):
889 """Make sure --dir is detected even with --project."""
890 ret = pre_upload.direct_main(['--project', 'foooooooooo'])
891 self.assertEqual(ret, 0)
892 self.hooks_mock.assert_called_once_with(
893 'foooooooooo', proj_dir=os.getcwd(), commit_list=[],
894 presubmit=mock.ANY)
895
896 def testNoGitDir(self):
897 """We should die when run on a non-git dir."""
898 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
899 ['--dir', self.tempdir])
900
901 def testNoDir(self):
902 """We should die when run on a missing dir."""
903 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
904 ['--dir', os.path.join(self.tempdir, 'foooooooo')])
905
906 def testCommitList(self):
907 """Any args on the command line should be treated as commits."""
908 commits = ['sha1', 'sha2', 'shaaaaaaaaaaaan']
909 ret = pre_upload.direct_main(commits)
910 self.assertEqual(ret, 0)
911 self.hooks_mock.assert_called_once_with(
912 mock.ANY, proj_dir=mock.ANY, commit_list=commits, presubmit=mock.ANY)
913
914
Jon Salz98255932012-08-18 14:48:02 +0800915if __name__ == '__main__':
Mike Frysinger65d93c12014-02-01 02:59:46 -0500916 cros_test_lib.main()