blob: c0b4ea4afb1a45ef6715714121f6a97f0379d772 [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')
531
532 def testNone(self):
533 """Accept BUG=None."""
534 self.assertMessageAccepted('\nBUG=None\n')
535 self.assertMessageAccepted('\nBUG=none\n')
536 self.assertMessageRejected('\nBUG=NONE\n')
537
538 def testBlank(self):
539 """Reject blank values."""
540 self.assertMessageRejected('\nBUG=\n')
541 self.assertMessageRejected('\nBUG= \n')
542
543 def testNotFirstLine(self):
544 """Reject the first line."""
545 self.assertMessageRejected('BUG=None\n\n\n')
546
547 def testNotInline(self):
548 """Reject not at the start of line."""
549 self.assertMessageRejected('\n BUG=None\n')
550 self.assertMessageRejected('\n\tBUG=None\n')
551
552 def testOldTrackers(self):
553 """Reject commit messages using old trackers."""
554 self.assertMessageRejected('\nBUG=chromium-os:1234\n')
555
556 def testNoTrackers(self):
557 """Reject commit messages w/invalid trackers."""
558 self.assertMessageRejected('\nBUG=booga:1234\n')
559
560 def testMissing(self):
561 """Reject commit messages w/no BUG line."""
562 self.assertMessageRejected('foo\n')
563
564 def testCase(self):
565 """Reject bug lines that are not BUG."""
566 self.assertMessageRejected('\nbug=none\n')
567
568
569class CheckCommitMessageCqDepend(CommitMessageTestCase):
570 """Tests for _check_change_has_valid_cq_depend."""
571
572 @staticmethod
573 def CheckMessage(project, commit):
574 return pre_upload._check_change_has_valid_cq_depend(project, commit)
575
576 def testNormal(self):
577 """Accept valid CQ-DEPENDs line."""
578 self.assertMessageAccepted('\nCQ-DEPEND=CL:1234\n')
579
580 def testInvalid(self):
581 """Reject invalid CQ-DEPENDs line."""
582 self.assertMessageRejected('\nCQ-DEPEND=CL=1234\n')
583 self.assertMessageRejected('\nCQ-DEPEND=None\n')
584
585
586class CheckCommitMessageTest(CommitMessageTestCase):
587 """Tests for _check_change_has_test_field."""
588
589 @staticmethod
590 def CheckMessage(project, commit):
591 return pre_upload._check_change_has_test_field(project, commit)
592
593 def testNormal(self):
594 """Accept a commit message w/a valid TEST."""
595 self.assertMessageAccepted('\nTEST=i did it\n')
596
597 def testNone(self):
598 """Accept TEST=None."""
599 self.assertMessageAccepted('\nTEST=None\n')
600 self.assertMessageAccepted('\nTEST=none\n')
601
602 def testBlank(self):
603 """Reject blank values."""
604 self.assertMessageRejected('\nTEST=\n')
605 self.assertMessageRejected('\nTEST= \n')
606
607 def testNotFirstLine(self):
608 """Reject the first line."""
609 self.assertMessageRejected('TEST=None\n\n\n')
610
611 def testNotInline(self):
612 """Reject not at the start of line."""
613 self.assertMessageRejected('\n TEST=None\n')
614 self.assertMessageRejected('\n\tTEST=None\n')
615
616 def testMissing(self):
617 """Reject commit messages w/no TEST line."""
618 self.assertMessageRejected('foo\n')
619
620 def testCase(self):
621 """Reject bug lines that are not TEST."""
622 self.assertMessageRejected('\ntest=none\n')
623
624
625class CheckCommitMessageChangeId(CommitMessageTestCase):
626 """Tests for _check_change_has_proper_changeid."""
627
628 @staticmethod
629 def CheckMessage(project, commit):
630 return pre_upload._check_change_has_proper_changeid(project, commit)
631
632 def testNormal(self):
633 """Accept a commit message w/a valid Change-Id."""
634 self.assertMessageAccepted('foo\n\nChange-Id: I1234\n')
635
636 def testBlank(self):
637 """Reject blank values."""
638 self.assertMessageRejected('\nChange-Id:\n')
639 self.assertMessageRejected('\nChange-Id: \n')
640
641 def testNotFirstLine(self):
642 """Reject the first line."""
643 self.assertMessageRejected('TEST=None\n\n\n')
644
645 def testNotInline(self):
646 """Reject not at the start of line."""
647 self.assertMessageRejected('\n Change-Id: I1234\n')
648 self.assertMessageRejected('\n\tChange-Id: I1234\n')
649
650 def testMissing(self):
651 """Reject commit messages missing the line."""
652 self.assertMessageRejected('foo\n')
653
654 def testCase(self):
655 """Reject bug lines that are not Change-Id."""
656 self.assertMessageRejected('\nchange-id: I1234\n')
657 self.assertMessageRejected('\nChange-id: I1234\n')
658 self.assertMessageRejected('\nChange-ID: I1234\n')
659
660 def testEnd(self):
661 """Reject Change-Id's that are not last."""
662 self.assertMessageRejected('\nChange-Id: I1234\nbar\n')
663
Mike Frysinger02b88bd2014-11-21 00:29:38 -0500664 def testSobTag(self):
665 """Permit s-o-b tags to follow the Change-Id."""
666 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nSigned-off-by: Hi\n')
667
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400668
Mike Frysinger36b2ebc2014-10-31 14:02:03 -0400669class CheckCommitMessageStyle(CommitMessageTestCase):
670 """Tests for _check_commit_message_style."""
671
672 @staticmethod
673 def CheckMessage(project, commit):
674 return pre_upload._check_commit_message_style(project, commit)
675
676 def testNormal(self):
677 """Accept valid commit messages."""
678 self.assertMessageAccepted('one sentence.\n')
679 self.assertMessageAccepted('some.module: do it!\n')
680 self.assertMessageAccepted('one line\n\nmore stuff here.')
681
682 def testNoBlankSecondLine(self):
683 """Reject messages that have stuff on the second line."""
684 self.assertMessageRejected('one sentence.\nbad fish!\n')
685
686 def testFirstLineMultipleSentences(self):
687 """Reject messages that have more than one sentence in the summary."""
688 self.assertMessageRejected('one sentence. two sentence!\n')
689
690 def testFirstLineTooLone(self):
691 """Reject first lines that are too long."""
692 self.assertMessageRejected('o' * 200)
693
694
Mike Frysinger292b45d2014-11-25 01:17:10 -0500695def DiffEntry(src_file=None, dst_file=None, src_mode=None, dst_mode='100644',
696 status='M'):
697 """Helper to create a stub RawDiffEntry object"""
698 if src_mode is None:
699 if status == 'A':
700 src_mode = '000000'
701 elif status == 'M':
702 src_mode = dst_mode
703 elif status == 'D':
704 src_mode = dst_mode
705 dst_mode = '000000'
706
707 src_sha = dst_sha = 'abc'
708 if status == 'D':
709 dst_sha = '000000'
710 elif status == 'A':
711 src_sha = '000000'
712
713 return git.RawDiffEntry(src_mode=src_mode, dst_mode=dst_mode, src_sha=src_sha,
714 dst_sha=dst_sha, status=status, score=None,
715 src_file=src_file, dst_file=dst_file)
716
717
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500718class HelpersTest(cros_test_lib.MockTestCase):
719 """Various tests for utility functions."""
720
721 def _SetupGetAffectedFiles(self):
722 self.PatchObject(git, 'RawDiff', return_value=[
723 # A modified normal file.
Mike Frysinger292b45d2014-11-25 01:17:10 -0500724 DiffEntry(src_file='buildbot/constants.py', status='M'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500725 # A new symlink file.
Mike Frysinger292b45d2014-11-25 01:17:10 -0500726 DiffEntry(dst_file='scripts/cros_env_whitelist', dst_mode='120000',
727 status='A'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500728 # A deleted file.
Mike Frysinger292b45d2014-11-25 01:17:10 -0500729 DiffEntry(src_file='scripts/sync_sonic.py', status='D'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500730 ])
731
732 def testGetAffectedFilesNoDeletesNoRelative(self):
733 """Verify _get_affected_files() works w/no delete & not relative."""
734 self._SetupGetAffectedFiles()
735 path = os.getcwd()
736 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
737 relative=False)
738 exp_files = [os.path.join(path, 'buildbot/constants.py')]
739 self.assertEquals(files, exp_files)
740
741 def testGetAffectedFilesDeletesNoRelative(self):
742 """Verify _get_affected_files() works w/delete & not relative."""
743 self._SetupGetAffectedFiles()
744 path = os.getcwd()
745 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
746 relative=False)
747 exp_files = [os.path.join(path, 'buildbot/constants.py'),
748 os.path.join(path, 'scripts/sync_sonic.py')]
749 self.assertEquals(files, exp_files)
750
751 def testGetAffectedFilesNoDeletesRelative(self):
752 """Verify _get_affected_files() works w/no delete & relative."""
753 self._SetupGetAffectedFiles()
754 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
755 relative=True)
756 exp_files = ['buildbot/constants.py']
757 self.assertEquals(files, exp_files)
758
759 def testGetAffectedFilesDeletesRelative(self):
760 """Verify _get_affected_files() works w/delete & relative."""
761 self._SetupGetAffectedFiles()
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500762 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
763 relative=True)
764 exp_files = ['buildbot/constants.py', 'scripts/sync_sonic.py']
765 self.assertEquals(files, exp_files)
766
Mike Frysinger292b45d2014-11-25 01:17:10 -0500767 def testGetAffectedFilesDetails(self):
768 """Verify _get_affected_files() works w/full_details."""
769 self._SetupGetAffectedFiles()
770 files = pre_upload._get_affected_files('HEAD', full_details=True,
771 relative=True)
772 self.assertEquals(files[0].src_file, 'buildbot/constants.py')
773
774
775class CheckForUprev(cros_test_lib.MockTempDirTestCase):
776 """Tests for _check_for_uprev."""
777
778 def setUp(self):
779 self.file_mock = self.PatchObject(git, 'RawDiff')
780
781 def _Files(self, files):
782 """Create |files| in the tempdir and return full paths to them."""
783 for obj in files:
784 if obj.status == 'D':
785 continue
786 if obj.dst_file is None:
787 f = obj.src_file
788 else:
789 f = obj.dst_file
790 osutils.Touch(os.path.join(self.tempdir, f), makedirs=True)
791 return files
792
793 def assertAccepted(self, files, project='project', commit='fake sha1'):
794 """Assert _check_for_uprev accepts |files|."""
795 self.file_mock.return_value = self._Files(files)
796 ret = pre_upload._check_for_uprev(project, commit, project_top=self.tempdir)
797 self.assertEqual(ret, None)
798
799 def assertRejected(self, files, project='project', commit='fake sha1'):
800 """Assert _check_for_uprev rejects |files|."""
801 self.file_mock.return_value = self._Files(files)
802 ret = pre_upload._check_for_uprev(project, commit, project_top=self.tempdir)
803 self.assertTrue(isinstance(ret, errors.HookFailure))
804
805 def testWhitelistOverlay(self):
806 """Skip checks on whitelisted overlays."""
807 self.assertAccepted([DiffEntry(src_file='cat/pkg/pkg-0.ebuild')],
808 project='chromiumos/overlays/portage-stable')
809
810 def testWhitelistFiles(self):
811 """Skip checks on whitelisted files."""
812 files = ['ChangeLog', 'Manifest', 'metadata.xml']
813 self.assertAccepted([DiffEntry(src_file=os.path.join('c', 'p', x),
814 status='M')
815 for x in files])
816
817 def testRejectBasic(self):
818 """Reject ebuilds missing uprevs."""
819 self.assertRejected([DiffEntry(src_file='c/p/p-0.ebuild', status='M')])
820
821 def testNewPackage(self):
822 """Accept new ebuilds w/out uprevs."""
823 self.assertAccepted([DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
824 self.assertAccepted([DiffEntry(src_file='c/p/p-0-r12.ebuild', status='A')])
825
826 def testModifiedFilesOnly(self):
827 """Reject ebuilds w/out uprevs and changes in files/."""
828 osutils.Touch(os.path.join(self.tempdir, 'cat/pkg/pkg-0.ebuild'),
829 makedirs=True)
830 self.assertRejected([DiffEntry(src_file='cat/pkg/files/f', status='A')])
831 self.assertRejected([DiffEntry(src_file='cat/pkg/files/g', status='M')])
832
833 def testFilesNoEbuilds(self):
834 """Ignore changes to paths w/out ebuilds."""
835 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/f', status='A')])
836 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/g', status='M')])
837
838 def testModifiedFilesWithUprev(self):
839 """Accept ebuilds w/uprevs and changes in files/."""
840 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='A'),
841 DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
842 self.assertAccepted([
843 DiffEntry(src_file='c/p/files/f', status='M'),
844 DiffEntry(src_file='c/p/p-0-r1.ebuild', src_mode='120000',
845 dst_file='c/p/p-0-r2.ebuild', dst_mode='120000', status='R')])
846
Gwendal Grignoua3086c32014-12-09 11:17:22 -0800847 def testModifiedFilesWith9999(self):
848 """Accept 9999 ebuilds and changes in files/."""
849 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M'),
850 DiffEntry(src_file='c/p/p-9999.ebuild', status='M')])
851
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500852
Mike Frysinger55f85b52014-12-18 14:45:21 -0500853class DirectMainTest(cros_test_lib.MockTempDirTestCase):
854 """Tests for direct_main()"""
855
856 def setUp(self):
857 self.hooks_mock = self.PatchObject(pre_upload, '_run_project_hooks',
858 return_value=None)
859
860 def testNoArgs(self):
861 """If run w/no args, should check the current dir."""
862 ret = pre_upload.direct_main([])
863 self.assertEqual(ret, 0)
864 self.hooks_mock.assert_called_once_with(
865 mock.ANY, proj_dir=os.getcwd(), commit_list=[], presubmit=mock.ANY)
866
867 def testExplicitDir(self):
868 """Verify we can run on a diff dir."""
869 # Use the chromite dir since we know it exists.
870 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR])
871 self.assertEqual(ret, 0)
872 self.hooks_mock.assert_called_once_with(
873 mock.ANY, proj_dir=constants.CHROMITE_DIR, commit_list=[],
874 presubmit=mock.ANY)
875
876 def testBogusProject(self):
877 """A bogus project name should be fine (use default settings)."""
878 # Use the chromite dir since we know it exists.
879 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR,
880 '--project', 'foooooooooo'])
881 self.assertEqual(ret, 0)
882 self.hooks_mock.assert_called_once_with(
883 'foooooooooo', proj_dir=constants.CHROMITE_DIR, commit_list=[],
884 presubmit=mock.ANY)
885
886 def testBogustProjectNoDir(self):
887 """Make sure --dir is detected even with --project."""
888 ret = pre_upload.direct_main(['--project', 'foooooooooo'])
889 self.assertEqual(ret, 0)
890 self.hooks_mock.assert_called_once_with(
891 'foooooooooo', proj_dir=os.getcwd(), commit_list=[],
892 presubmit=mock.ANY)
893
894 def testNoGitDir(self):
895 """We should die when run on a non-git dir."""
896 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
897 ['--dir', self.tempdir])
898
899 def testNoDir(self):
900 """We should die when run on a missing dir."""
901 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
902 ['--dir', os.path.join(self.tempdir, 'foooooooo')])
903
904 def testCommitList(self):
905 """Any args on the command line should be treated as commits."""
906 commits = ['sha1', 'sha2', 'shaaaaaaaaaaaan']
907 ret = pre_upload.direct_main(commits)
908 self.assertEqual(ret, 0)
909 self.hooks_mock.assert_called_once_with(
910 mock.ANY, proj_dir=mock.ANY, commit_list=commits, presubmit=mock.ANY)
911
912
Jon Salz98255932012-08-18 14:48:02 +0800913if __name__ == '__main__':
Mike Frysinger65d93c12014-02-01 02:59:46 -0500914 cros_test_lib.main()