blob: c8efbc18a441f26ce6d7c9dfee8c376889969d84 [file] [log] [blame]
Prathmesh Prabhuc5254652016-12-22 12:58:05 -08001#!/usr/bin/env 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
Alex Deymo643ac4c2015-09-03 10:40:50 -070035def ProjectNamed(project_name):
36 """Wrapper to create a Project with just the name"""
37 return pre_upload.Project(project_name, None, None)
38
39
Mike Frysinger65d93c12014-02-01 02:59:46 -050040class TryUTF8DecodeTest(cros_test_lib.TestCase):
Mike Frysingerae409522014-02-01 03:16:11 -050041 """Verify we sanely handle unicode content."""
42
Jon Salz98255932012-08-18 14:48:02 +080043 def runTest(self):
44 self.assertEquals(u'', pre_upload._try_utf8_decode(''))
45 self.assertEquals(u'abc', pre_upload._try_utf8_decode('abc'))
Daniel Erat9d203ff2015-02-17 10:12:21 -070046 self.assertEquals(
47 u'你好布萊恩',
48 pre_upload._try_utf8_decode('你好布萊恩'))
Jon Salz98255932012-08-18 14:48:02 +080049 # Invalid UTF-8
50 self.assertEquals('\x80', pre_upload._try_utf8_decode('\x80'))
51
52
Mike Frysinger1459d362014-12-06 13:53:23 -050053class CheckNoLongLinesTest(cros_test_lib.MockTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -050054 """Tests for _check_no_long_lines."""
55
Jon Salz98255932012-08-18 14:48:02 +080056 def setUp(self):
Mike Frysinger1459d362014-12-06 13:53:23 -050057 self.PatchObject(pre_upload, '_get_affected_files', return_value=['x.py'])
58 self.PatchObject(pre_upload, '_filter_files', return_value=['x.py'])
59 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
Jon Salz98255932012-08-18 14:48:02 +080060
Jon Salz98255932012-08-18 14:48:02 +080061 def runTest(self):
Mike Frysinger1459d362014-12-06 13:53:23 -050062 self.diff_mock.return_value = [
63 (1, u"x" * 80), # OK
64 (2, "\x80" * 80), # OK
65 (3, u"x" * 81), # Too long
66 (4, "\x80" * 81), # Too long
67 (5, u"See http://" + (u"x" * 80)), # OK (URL)
68 (6, u"See https://" + (u"x" * 80)), # OK (URL)
69 (7, u"# define " + (u"x" * 80)), # OK (compiler directive)
70 (8, u"#define" + (u"x" * 74)), # Too long
71 ]
Alex Deymo643ac4c2015-09-03 10:40:50 -070072 failure = pre_upload._check_no_long_lines(ProjectNamed('PROJECT'), 'COMMIT')
Jon Salz98255932012-08-18 14:48:02 +080073 self.assertTrue(failure)
74 self.assertEquals('Found lines longer than 80 characters (first 5 shown):',
75 failure.msg)
76 self.assertEquals(['x.py, line %d, 81 chars' % line
77 for line in [3, 4, 8]],
78 failure.items)
79
Mike Frysingerae409522014-02-01 03:16:11 -050080
Prathmesh Prabhuc5254652016-12-22 12:58:05 -080081class CheckTabbedIndentsTest(cros_test_lib.MockTestCase):
82 """Tests for _check_tabbed_indents."""
83 def setUp(self):
84 self.PatchObject(pre_upload, '_get_affected_files', return_value=['x.py'])
85 self.PatchObject(pre_upload, '_filter_files', return_value=['x.py'])
86 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff')
87
88 def test_good_cases(self):
89 self.diff_mock.return_value = [
90 (1, u'no_tabs_anywhere'),
91 (2, u' leading_tab_only'),
92 (3, u' leading_tab another_tab'),
93 (4, u' leading_tab trailing_too '),
94 (5, u' leading_tab then_spaces_trailing '),
95 ]
96 failure = pre_upload._check_tabbed_indents(ProjectNamed('PROJECT'),
97 'COMMIT')
98 self.assertIsNone(failure)
99
100 def test_bad_cases(self):
101 self.diff_mock.return_value = [
102 (1, u' leading_space'),
103 (2, u' tab_followed_by_space'),
104 (3, u' space_followed_by_tab'),
105 (4, u' mix_em_up'),
106 (5, u' mix_on_both_sides '),
107 ]
108 failure = pre_upload._check_tabbed_indents(ProjectNamed('PROJECT'),
109 'COMMIT')
110 self.assertTrue(failure)
111 self.assertEquals('Found a space in indentation (must be all tabs):',
112 failure.msg)
113 self.assertEquals(['x.py, line %d' % line for line in xrange(1, 6)],
114 failure.items)
115
116
Daniel Erata350fd32014-09-29 14:02:34 -0700117class CheckProjectPrefix(cros_test_lib.MockTempDirTestCase):
118 """Tests for _check_project_prefix."""
119
120 def setUp(self):
121 self.orig_cwd = os.getcwd()
122 os.chdir(self.tempdir)
123 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
124 self.desc_mock = self.PatchObject(pre_upload, '_get_commit_desc')
125
126 def tearDown(self):
127 os.chdir(self.orig_cwd)
128
129 def _WriteAliasFile(self, filename, project):
130 """Writes a project name to a file, creating directories if needed."""
131 os.makedirs(os.path.dirname(filename))
132 osutils.WriteFile(filename, project)
133
134 def testInvalidPrefix(self):
135 """Report an error when the prefix doesn't match the base directory."""
136 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
137 self.desc_mock.return_value = 'bar: Some commit'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700138 failure = pre_upload._check_project_prefix(ProjectNamed('PROJECT'),
139 'COMMIT')
Daniel Erata350fd32014-09-29 14:02:34 -0700140 self.assertTrue(failure)
141 self.assertEquals(('The commit title for changes affecting only foo' +
142 ' should start with "foo: "'), failure.msg)
143
144 def testValidPrefix(self):
145 """Use a prefix that matches the base directory."""
146 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/baz.cc']
147 self.desc_mock.return_value = 'foo: Change some files.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700148 self.assertFalse(
149 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700150
151 def testAliasFile(self):
152 """Use .project_alias to override the project name."""
153 self._WriteAliasFile('foo/.project_alias', 'project')
154 self.file_mock.return_value = ['foo/foo.cc', 'foo/subdir/bar.cc']
155 self.desc_mock.return_value = 'project: Use an alias.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700156 self.assertFalse(
157 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700158
159 def testAliasFileWithSubdirs(self):
160 """Check that .project_alias is used when only modifying subdirectories."""
161 self._WriteAliasFile('foo/.project_alias', 'project')
162 self.file_mock.return_value = [
163 'foo/subdir/foo.cc',
164 'foo/subdir/bar.cc'
165 'foo/subdir/blah/baz.cc'
166 ]
167 self.desc_mock.return_value = 'project: Alias with subdirs.'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700168 self.assertFalse(
169 pre_upload._check_project_prefix(ProjectNamed('PROJECT'), 'COMMIT'))
Daniel Erata350fd32014-09-29 14:02:34 -0700170
171
Mike Frysinger1459d362014-12-06 13:53:23 -0500172class CheckKernelConfig(cros_test_lib.MockTestCase):
Mike Frysingerae409522014-02-01 03:16:11 -0500173 """Tests for _kernel_configcheck."""
174
Mike Frysinger1459d362014-12-06 13:53:23 -0500175 def setUp(self):
176 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
177
178 def testMixedChanges(self):
179 """Mixing of changes should fail."""
180 self.file_mock.return_value = [
181 '/kernel/files/chromeos/config/base.config',
182 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
183 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700184 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
185 self.assertTrue(failure)
186
Mike Frysinger1459d362014-12-06 13:53:23 -0500187 def testCodeOnly(self):
188 """Code-only changes should pass."""
189 self.file_mock.return_value = [
190 '/kernel/files/Makefile',
191 '/kernel/files/arch/arm/mach-exynos/mach-exynos5-dt.c'
192 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700193 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
194 self.assertFalse(failure)
195
Mike Frysinger1459d362014-12-06 13:53:23 -0500196 def testConfigOnlyChanges(self):
197 """Config-only changes should pass."""
198 self.file_mock.return_value = [
199 '/kernel/files/chromeos/config/base.config',
200 ]
Olof Johanssona96810f2012-09-04 16:20:03 -0700201 failure = pre_upload._kernel_configcheck('PROJECT', 'COMMIT')
202 self.assertFalse(failure)
203
Jon Salz98255932012-08-18 14:48:02 +0800204
Daniel Erat9d203ff2015-02-17 10:12:21 -0700205class CheckPortageMakeUseVar(cros_test_lib.MockTestCase):
206 """Tests for _check_portage_make_use_var."""
207
208 def setUp(self):
209 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
210 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
211
212 def testMakeConfOmitsOriginalUseValue(self):
213 """Fail for make.conf that discards the previous value of $USE."""
214 self.file_mock.return_value = ['make.conf']
215 self.content_mock.return_value = 'USE="foo"\nUSE="${USE} bar"'
216 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
217 self.assertTrue(failure, failure)
218
219 def testMakeConfCorrectUsage(self):
220 """Succeed for make.conf that preserves the previous value of $USE."""
221 self.file_mock.return_value = ['make.conf']
222 self.content_mock.return_value = 'USE="${USE} foo"\nUSE="${USE}" bar'
223 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
224 self.assertFalse(failure, failure)
225
226 def testMakeDefaultsReferencesOriginalUseValue(self):
227 """Fail for make.defaults that refers to a not-yet-set $USE value."""
228 self.file_mock.return_value = ['make.defaults']
229 self.content_mock.return_value = 'USE="${USE} foo"'
230 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
231 self.assertTrue(failure, failure)
232
233 # Also check for "$USE" without curly brackets.
234 self.content_mock.return_value = 'USE="$USE foo"'
235 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
236 self.assertTrue(failure, failure)
237
238 def testMakeDefaultsOverwritesUseValue(self):
239 """Fail for make.defaults that discards its own $USE value."""
240 self.file_mock.return_value = ['make.defaults']
241 self.content_mock.return_value = 'USE="foo"\nUSE="bar"'
242 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
243 self.assertTrue(failure, failure)
244
245 def testMakeDefaultsCorrectUsage(self):
246 """Succeed for make.defaults that sets and preserves $USE."""
247 self.file_mock.return_value = ['make.defaults']
248 self.content_mock.return_value = 'USE="foo"\nUSE="${USE}" bar'
249 failure = pre_upload._check_portage_make_use_var('PROJECT', 'COMMIT')
250 self.assertFalse(failure, failure)
251
252
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500253class CheckEbuildEapi(cros_test_lib.MockTestCase):
254 """Tests for _check_ebuild_eapi."""
255
Alex Deymo643ac4c2015-09-03 10:40:50 -0700256 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500257
258 def setUp(self):
259 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
260 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
261 self.diff_mock = self.PatchObject(pre_upload, '_get_file_diff',
262 side_effect=Exception())
263
264 def testSkipUpstreamOverlays(self):
265 """Skip ebuilds found in upstream overlays."""
266 self.file_mock.side_effect = Exception()
267 ret = pre_upload._check_ebuild_eapi(self.PORTAGE_STABLE, 'HEAD')
268 self.assertEqual(ret, None)
269
270 # Make sure our condition above triggers.
271 self.assertRaises(Exception, pre_upload._check_ebuild_eapi, 'o', 'HEAD')
272
273 def testSkipNonEbuilds(self):
274 """Skip non-ebuild files."""
275 self.content_mock.side_effect = Exception()
276
277 self.file_mock.return_value = ['some-file', 'ebuild/dir', 'an.ebuild~']
Alex Deymo643ac4c2015-09-03 10:40:50 -0700278 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500279 self.assertEqual(ret, None)
280
281 # Make sure our condition above triggers.
282 self.file_mock.return_value.append('a/real.ebuild')
Alex Deymo643ac4c2015-09-03 10:40:50 -0700283 self.assertRaises(Exception, pre_upload._check_ebuild_eapi,
284 ProjectNamed('o'), 'HEAD')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500285
286 def testSkipSymlink(self):
287 """Skip files that are just symlinks."""
288 self.file_mock.return_value = ['a-r1.ebuild']
289 self.content_mock.return_value = 'a.ebuild'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700290 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500291 self.assertEqual(ret, None)
292
293 def testRejectEapiImplicit0Content(self):
294 """Reject ebuilds that do not declare EAPI (so it's 0)."""
295 self.file_mock.return_value = ['a.ebuild']
296
297 self.content_mock.return_value = """# Header
298IUSE="foo"
299src_compile() { }
300"""
Alex Deymo643ac4c2015-09-03 10:40:50 -0700301 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500302 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500303
304 def testRejectExplicitEapi1Content(self):
305 """Reject ebuilds that do declare old EAPI explicitly."""
306 self.file_mock.return_value = ['a.ebuild']
307
308 template = """# Header
309EAPI=%s
310IUSE="foo"
311src_compile() { }
312"""
313 # Make sure we only check the first EAPI= setting.
314 self.content_mock.return_value = template % '1\nEAPI=4'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700315 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500316 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500317
318 # Verify we handle double quotes too.
319 self.content_mock.return_value = template % '"1"'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700320 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500321 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500322
323 # Verify we handle single quotes too.
324 self.content_mock.return_value = template % "'1'"
Alex Deymo643ac4c2015-09-03 10:40:50 -0700325 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500326 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500327
328 def testAcceptExplicitEapi4Content(self):
329 """Accept ebuilds that do declare new EAPI explicitly."""
330 self.file_mock.return_value = ['a.ebuild']
331
332 template = """# Header
333EAPI=%s
334IUSE="foo"
335src_compile() { }
336"""
337 # Make sure we only check the first EAPI= setting.
338 self.content_mock.return_value = template % '4\nEAPI=1'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700339 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500340 self.assertEqual(ret, None)
341
342 # Verify we handle double quotes too.
343 self.content_mock.return_value = template % '"5"'
Alex Deymo643ac4c2015-09-03 10:40:50 -0700344 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500345 self.assertEqual(ret, None)
346
347 # Verify we handle single quotes too.
348 self.content_mock.return_value = template % "'5-hdepend'"
Alex Deymo643ac4c2015-09-03 10:40:50 -0700349 ret = pre_upload._check_ebuild_eapi(ProjectNamed('overlay'), 'HEAD')
Mike Frysingerbf8b91c2014-02-01 02:50:27 -0500350 self.assertEqual(ret, None)
351
352
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400353class CheckEbuildKeywords(cros_test_lib.MockTestCase):
354 """Tests for _check_ebuild_keywords."""
355
356 def setUp(self):
357 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
358 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
359
360 def testNoEbuilds(self):
361 """If no ebuilds are found, do not scan."""
362 self.file_mock.return_value = ['a.file', 'ebuild-is-not.foo']
363
Alex Deymo643ac4c2015-09-03 10:40:50 -0700364 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400365 self.assertEqual(ret, None)
366
367 self.assertEqual(self.content_mock.call_count, 0)
368
369 def testSomeEbuilds(self):
370 """If ebuilds are found, only scan them."""
371 self.file_mock.return_value = ['a.file', 'blah', 'foo.ebuild', 'cow']
372 self.content_mock.return_value = ''
373
Alex Deymo643ac4c2015-09-03 10:40:50 -0700374 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400375 self.assertEqual(ret, None)
376
377 self.assertEqual(self.content_mock.call_count, 1)
378
379 def _CheckContent(self, content, fails):
380 """Test helper for inputs/outputs.
381
382 Args:
383 content: The ebuild content to test.
384 fails: Whether |content| should trigger a hook failure.
385 """
386 self.file_mock.return_value = ['a.ebuild']
387 self.content_mock.return_value = content
388
Alex Deymo643ac4c2015-09-03 10:40:50 -0700389 ret = pre_upload._check_ebuild_keywords(ProjectNamed('overlay'), 'HEAD')
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400390 if fails:
Mike Frysingerb81102f2014-11-21 00:33:35 -0500391 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysinger5c9e58d2014-09-09 03:32:50 -0400392 else:
393 self.assertEqual(ret, None)
394
395 self.assertEqual(self.content_mock.call_count, 1)
396
397 def testEmpty(self):
398 """Check KEYWORDS= is accepted."""
399 self._CheckContent('# HEADER\nKEYWORDS=\nblah\n', False)
400
401 def testEmptyQuotes(self):
402 """Check KEYWORDS="" is accepted."""
403 self._CheckContent('# HEADER\nKEYWORDS=" "\nblah\n', False)
404
405 def testStableGlob(self):
406 """Check KEYWORDS=* is accepted."""
407 self._CheckContent('# HEADER\nKEYWORDS="\t*\t"\nblah\n', False)
408
409 def testUnstableGlob(self):
410 """Check KEYWORDS=~* is accepted."""
411 self._CheckContent('# HEADER\nKEYWORDS="~* "\nblah\n', False)
412
413 def testRestrictedGlob(self):
414 """Check KEYWORDS=-* is accepted."""
415 self._CheckContent('# HEADER\nKEYWORDS="\t-* arm"\nblah\n', False)
416
417 def testMissingGlobs(self):
418 """Reject KEYWORDS missing any globs."""
419 self._CheckContent('# HEADER\nKEYWORDS="~arm x86"\nblah\n', True)
420
421
Mike Frysingercd363c82014-02-01 05:20:18 -0500422class CheckEbuildVirtualPv(cros_test_lib.MockTestCase):
423 """Tests for _check_ebuild_virtual_pv."""
424
Alex Deymo643ac4c2015-09-03 10:40:50 -0700425 PORTAGE_STABLE = ProjectNamed('chromiumos/overlays/portage-stable')
426 CHROMIUMOS_OVERLAY = ProjectNamed('chromiumos/overlays/chromiumos')
427 BOARD_OVERLAY = ProjectNamed('chromiumos/overlays/board-overlays')
428 PRIVATE_OVERLAY = ProjectNamed('chromeos/overlays/overlay-link-private')
429 PRIVATE_VARIANT_OVERLAY = ProjectNamed('chromeos/overlays/'
430 'overlay-variant-daisy-spring-private')
Mike Frysingercd363c82014-02-01 05:20:18 -0500431
432 def setUp(self):
433 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
434
435 def testNoVirtuals(self):
436 """Skip non virtual packages."""
437 self.file_mock.return_value = ['some/package/package-3.ebuild']
Alex Deymo643ac4c2015-09-03 10:40:50 -0700438 ret = pre_upload._check_ebuild_virtual_pv(ProjectNamed('overlay'), 'H')
Mike Frysingercd363c82014-02-01 05:20:18 -0500439 self.assertEqual(ret, None)
440
441 def testCommonVirtuals(self):
442 """Non-board overlays should use PV=1."""
443 template = 'virtual/foo/foo-%s.ebuild'
444 self.file_mock.return_value = [template % '1']
445 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
446 self.assertEqual(ret, None)
447
448 self.file_mock.return_value = [template % '2']
449 ret = pre_upload._check_ebuild_virtual_pv(self.CHROMIUMOS_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500450 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500451
452 def testPublicBoardVirtuals(self):
453 """Public board overlays should use PV=2."""
454 template = 'overlay-lumpy/virtual/foo/foo-%s.ebuild'
455 self.file_mock.return_value = [template % '2']
456 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
457 self.assertEqual(ret, None)
458
459 self.file_mock.return_value = [template % '2.5']
460 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500461 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500462
463 def testPublicBoardVariantVirtuals(self):
464 """Public board variant overlays should use PV=2.5."""
465 template = 'overlay-variant-lumpy-foo/virtual/foo/foo-%s.ebuild'
466 self.file_mock.return_value = [template % '2.5']
467 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
468 self.assertEqual(ret, None)
469
470 self.file_mock.return_value = [template % '3']
471 ret = pre_upload._check_ebuild_virtual_pv(self.BOARD_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500472 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500473
474 def testPrivateBoardVirtuals(self):
475 """Private board overlays should use PV=3."""
476 template = 'virtual/foo/foo-%s.ebuild'
477 self.file_mock.return_value = [template % '3']
478 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
479 self.assertEqual(ret, None)
480
481 self.file_mock.return_value = [template % '3.5']
482 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_OVERLAY, 'H')
Mike Frysingerb81102f2014-11-21 00:33:35 -0500483 self.assertTrue(isinstance(ret, errors.HookFailure))
Mike Frysingercd363c82014-02-01 05:20:18 -0500484
485 def testPrivateBoardVariantVirtuals(self):
486 """Private board variant overlays should use PV=3.5."""
487 template = 'virtual/foo/foo-%s.ebuild'
488 self.file_mock.return_value = [template % '3.5']
489 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
490 self.assertEqual(ret, None)
491
Bernie Thompsone5ee1822016-01-12 14:22:23 -0800492 def testSpecialVirtuals(self):
493 """Some cases require deeper versioning and can be >= 4."""
494 template = 'virtual/foo/foo-%s.ebuild'
Mike Frysingercd363c82014-02-01 05:20:18 -0500495 self.file_mock.return_value = [template % '4']
496 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
Bernie Thompsone5ee1822016-01-12 14:22:23 -0800497 self.assertEqual(ret, None)
Mike Frysingercd363c82014-02-01 05:20:18 -0500498
Bernie Thompsone5ee1822016-01-12 14:22:23 -0800499 self.file_mock.return_value = [template % '4.5']
500 ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
501 self.assertEqual(ret, None)
Mike Frysinger98638102014-08-28 00:15:08 -0400502
Alex Deymof5792ce2015-08-24 22:50:08 -0700503class CheckCrosLicenseCopyrightHeader(cros_test_lib.MockTestCase):
504 """Tests for _check_cros_license."""
Mike Frysinger98638102014-08-28 00:15:08 -0400505
506 def setUp(self):
507 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
508 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
509
510 def testOldHeaders(self):
511 """Accept old header styles."""
512 HEADERS = (
513 ('#!/bin/sh\n'
514 '# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.\n'
515 '# Use of this source code is governed by a BSD-style license that'
516 ' can be\n'
517 '# found in the LICENSE file.\n'),
518 ('// Copyright 2010-13 The Chromium OS Authors. All rights reserved.\n'
519 '// Use of this source code is governed by a BSD-style license that'
520 ' can be\n'
521 '// found in the LICENSE file.\n'),
522 )
523 self.file_mock.return_value = ['file']
524 for header in HEADERS:
525 self.content_mock.return_value = header
Alex Deymof5792ce2015-08-24 22:50:08 -0700526 self.assertEqual(None, pre_upload._check_cros_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -0400527
528 def testRejectC(self):
529 """Reject the (c) in newer headers."""
530 HEADERS = (
531 ('// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.\n'
532 '// Use of this source code is governed by a BSD-style license that'
533 ' can be\n'
534 '// found in the LICENSE file.\n'),
535 ('// Copyright (c) 2020 The Chromium OS Authors. All rights reserved.\n'
536 '// Use of this source code is governed by a BSD-style license that'
537 ' can be\n'
538 '// found in the LICENSE file.\n'),
539 )
540 self.file_mock.return_value = ['file']
541 for header in HEADERS:
542 self.content_mock.return_value = header
Alex Deymof5792ce2015-08-24 22:50:08 -0700543 self.assertNotEqual(None, pre_upload._check_cros_license('proj', 'sha1'))
544
545
546class CheckAOSPLicenseCopyrightHeader(cros_test_lib.MockTestCase):
547 """Tests for _check_aosp_license."""
548
549 def setUp(self):
550 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
551 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
552
553 def testHeaders(self):
554 """Accept old header styles."""
555 HEADERS = (
556 """//
557// Copyright (C) 2011 The Android Open Source Project
558//
559// Licensed under the Apache License, Version 2.0 (the "License");
560// you may not use this file except in compliance with the License.
561// You may obtain a copy of the License at
562//
563// http://www.apache.org/licenses/LICENSE-2.0
564//
565// Unless required by applicable law or agreed to in writing, software
566// distributed under the License is distributed on an "AS IS" BASIS,
567// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
568// See the License for the specific language governing permissions and
569// limitations under the License.
570//
571""",
572 """#
573# Copyright (c) 2015 The Android Open Source Project
574#
575# Licensed under the Apache License, Version 2.0 (the "License");
576# you may not use this file except in compliance with the License.
577# You may obtain a copy of the License at
578#
579# http://www.apache.org/licenses/LICENSE-2.0
580#
581# Unless required by applicable law or agreed to in writing, software
582# distributed under the License is distributed on an "AS IS" BASIS,
583# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
584# See the License for the specific language governing permissions and
585# limitations under the License.
586#
587""",
588 )
589 self.file_mock.return_value = ['file']
590 for header in HEADERS:
591 self.content_mock.return_value = header
592 self.assertEqual(None, pre_upload._check_aosp_license('proj', 'sha1'))
593
594 def testRejectNoLinesAround(self):
595 """Reject headers missing the empty lines before/after the license."""
596 HEADERS = (
597 """# Copyright (c) 2015 The Android Open Source Project
598#
599# Licensed under the Apache License, Version 2.0 (the "License");
600# you may not use this file except in compliance with the License.
601# You may obtain a copy of the License at
602#
603# http://www.apache.org/licenses/LICENSE-2.0
604#
605# Unless required by applicable law or agreed to in writing, software
606# distributed under the License is distributed on an "AS IS" BASIS,
607# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
608# See the License for the specific language governing permissions and
609# limitations under the License.
610""",
611 )
612 self.file_mock.return_value = ['file']
613 for header in HEADERS:
614 self.content_mock.return_value = header
615 self.assertNotEqual(None, pre_upload._check_aosp_license('proj', 'sha1'))
Mike Frysinger98638102014-08-28 00:15:08 -0400616
617
Mike Frysingerd7734522015-02-26 16:12:43 -0500618class CheckLayoutConfTestCase(cros_test_lib.MockTestCase):
619 """Tests for _check_layout_conf."""
620
621 def setUp(self):
622 self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
623 self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
624
625 def assertAccepted(self, files, project='project', commit='fake sha1'):
626 """Assert _check_layout_conf accepts |files|."""
627 self.file_mock.return_value = files
628 ret = pre_upload._check_layout_conf(project, commit)
629 self.assertEqual(ret, None, msg='rejected with:\n%s' % ret)
630
631 def assertRejected(self, files, project='project', commit='fake sha1'):
632 """Assert _check_layout_conf rejects |files|."""
633 self.file_mock.return_value = files
634 ret = pre_upload._check_layout_conf(project, commit)
635 self.assertTrue(isinstance(ret, errors.HookFailure))
636
637 def GetLayoutConf(self, filters=()):
638 """Return a valid layout.conf with |filters| lines removed."""
639 all_lines = [
640 'masters = portage-stable chromiumos',
641 'profile-formats = portage-2 profile-default-eapi',
642 'profile_eapi_when_unspecified = 5-progress',
643 'repo-name = link',
644 'thin-manifests = true',
Mike Frysinger5fae62d2015-11-11 20:12:15 -0500645 'use-manifests = strict',
Mike Frysingerd7734522015-02-26 16:12:43 -0500646 ]
647
648 lines = []
649 for line in all_lines:
650 for filt in filters:
651 if line.startswith(filt):
652 break
653 else:
654 lines.append(line)
655
656 return '\n'.join(lines)
657
658 def testNoFilesToCheck(self):
659 """Don't blow up when there are no layout.conf files."""
660 self.assertAccepted([])
661
662 def testRejectRepoNameFile(self):
663 """If profiles/repo_name is set, kick it out."""
664 self.assertRejected(['profiles/repo_name'])
665
666 def testAcceptValidLayoutConf(self):
667 """Accept a fully valid layout.conf."""
668 self.content_mock.return_value = self.GetLayoutConf()
669 self.assertAccepted(['metadata/layout.conf'])
670
671 def testAcceptUnknownKeys(self):
672 """Accept keys we don't explicitly know about."""
673 self.content_mock.return_value = self.GetLayoutConf() + '\nzzz-top = ok'
674 self.assertAccepted(['metadata/layout.conf'])
675
676 def testRejectUnsorted(self):
677 """Reject an unsorted layout.conf."""
678 self.content_mock.return_value = 'zzz-top = bad\n' + self.GetLayoutConf()
679 self.assertRejected(['metadata/layout.conf'])
680
681 def testRejectMissingThinManifests(self):
682 """Reject a layout.conf missing thin-manifests."""
683 self.content_mock.return_value = self.GetLayoutConf(
684 filters=['thin-manifests'])
685 self.assertRejected(['metadata/layout.conf'])
686
687 def testRejectMissingUseManifests(self):
688 """Reject a layout.conf missing use-manifests."""
689 self.content_mock.return_value = self.GetLayoutConf(
690 filters=['use-manifests'])
691 self.assertRejected(['metadata/layout.conf'])
692
693 def testRejectMissingEapiFallback(self):
694 """Reject a layout.conf missing profile_eapi_when_unspecified."""
695 self.content_mock.return_value = self.GetLayoutConf(
696 filters=['profile_eapi_when_unspecified'])
697 self.assertRejected(['metadata/layout.conf'])
698
699 def testRejectMissingRepoName(self):
700 """Reject a layout.conf missing repo-name."""
701 self.content_mock.return_value = self.GetLayoutConf(filters=['repo-name'])
702 self.assertRejected(['metadata/layout.conf'])
703
704
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400705class CommitMessageTestCase(cros_test_lib.MockTestCase):
706 """Test case for funcs that check commit messages."""
707
708 def setUp(self):
709 self.msg_mock = self.PatchObject(pre_upload, '_get_commit_desc')
710
711 @staticmethod
712 def CheckMessage(_project, _commit):
713 raise AssertionError('Test class must declare CheckMessage')
714 # This dummy return is to silence pylint warning W1111 so we don't have to
715 # enable it for all the call sites below.
716 return 1 # pylint: disable=W0101
717
Alex Deymo643ac4c2015-09-03 10:40:50 -0700718 def assertMessageAccepted(self, msg, project=ProjectNamed('project'),
719 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400720 """Assert _check_change_has_bug_field accepts |msg|."""
721 self.msg_mock.return_value = msg
722 ret = self.CheckMessage(project, commit)
723 self.assertEqual(ret, None)
724
Alex Deymo643ac4c2015-09-03 10:40:50 -0700725 def assertMessageRejected(self, msg, project=ProjectNamed('project'),
726 commit='1234'):
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400727 """Assert _check_change_has_bug_field rejects |msg|."""
728 self.msg_mock.return_value = msg
729 ret = self.CheckMessage(project, commit)
730 self.assertTrue(isinstance(ret, errors.HookFailure))
731
732
733class CheckCommitMessageBug(CommitMessageTestCase):
734 """Tests for _check_change_has_bug_field."""
735
Alex Deymo643ac4c2015-09-03 10:40:50 -0700736 AOSP_PROJECT = pre_upload.Project(name='overlay', dir='', remote='aosp')
737 CROS_PROJECT = pre_upload.Project(name='overlay', dir='', remote='cros')
738
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400739 @staticmethod
740 def CheckMessage(project, commit):
741 return pre_upload._check_change_has_bug_field(project, commit)
742
743 def testNormal(self):
744 """Accept a commit message w/a valid BUG."""
Alex Deymo643ac4c2015-09-03 10:40:50 -0700745 self.assertMessageAccepted('\nBUG=chromium:1234\n', self.CROS_PROJECT)
746 self.assertMessageAccepted('\nBUG=chrome-os-partner:1234\n',
747 self.CROS_PROJECT)
748 self.assertMessageAccepted('\nBUG=b:1234\n', self.CROS_PROJECT)
749
750 self.assertMessageAccepted('\nBug: 1234\n', self.AOSP_PROJECT)
751 self.assertMessageAccepted('\nBug:1234\n', self.AOSP_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400752
753 def testNone(self):
754 """Accept BUG=None."""
Alex Deymo643ac4c2015-09-03 10:40:50 -0700755 self.assertMessageAccepted('\nBUG=None\n', self.CROS_PROJECT)
756 self.assertMessageAccepted('\nBUG=none\n', self.CROS_PROJECT)
757 self.assertMessageAccepted('\nBug: None\n', self.AOSP_PROJECT)
758 self.assertMessageAccepted('\nBug:none\n', self.AOSP_PROJECT)
759
760 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
761 self.assertMessageRejected('\nBUG=NONE\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400762
763 def testBlank(self):
764 """Reject blank values."""
Alex Deymo643ac4c2015-09-03 10:40:50 -0700765 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
766 self.assertMessageRejected('\nBUG=\n', project)
767 self.assertMessageRejected('\nBUG= \n', project)
768 self.assertMessageRejected('\nBug:\n', project)
769 self.assertMessageRejected('\nBug: \n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400770
771 def testNotFirstLine(self):
772 """Reject the first line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -0700773 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
774 self.assertMessageRejected('BUG=None\n\n\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400775
776 def testNotInline(self):
777 """Reject not at the start of line."""
Alex Deymo643ac4c2015-09-03 10:40:50 -0700778 for project in (self.AOSP_PROJECT, self.CROS_PROJECT):
779 self.assertMessageRejected('\n BUG=None\n', project)
780 self.assertMessageRejected('\n\tBUG=None\n', project)
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400781
782 def testOldTrackers(self):
783 """Reject commit messages using old trackers."""
Alex Deymo643ac4c2015-09-03 10:40:50 -0700784 self.assertMessageRejected('\nBUG=chromium-os:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400785
786 def testNoTrackers(self):
787 """Reject commit messages w/invalid trackers."""
Alex Deymo643ac4c2015-09-03 10:40:50 -0700788 self.assertMessageRejected('\nBUG=booga:1234\n', self.CROS_PROJECT)
789 self.assertMessageRejected('\nBUG=br:1234\n', self.CROS_PROJECT)
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400790
791 def testMissing(self):
792 """Reject commit messages w/no BUG line."""
793 self.assertMessageRejected('foo\n')
794
795 def testCase(self):
796 """Reject bug lines that are not BUG."""
797 self.assertMessageRejected('\nbug=none\n')
798
799
800class CheckCommitMessageCqDepend(CommitMessageTestCase):
801 """Tests for _check_change_has_valid_cq_depend."""
802
803 @staticmethod
804 def CheckMessage(project, commit):
805 return pre_upload._check_change_has_valid_cq_depend(project, commit)
806
807 def testNormal(self):
808 """Accept valid CQ-DEPENDs line."""
809 self.assertMessageAccepted('\nCQ-DEPEND=CL:1234\n')
810
811 def testInvalid(self):
812 """Reject invalid CQ-DEPENDs line."""
813 self.assertMessageRejected('\nCQ-DEPEND=CL=1234\n')
814 self.assertMessageRejected('\nCQ-DEPEND=None\n')
815
816
Bernie Thompsonf8fea992016-01-14 10:27:18 -0800817class CheckCommitMessageContribution(CommitMessageTestCase):
818 """Tests for _check_change_is_contribution."""
819
820 @staticmethod
821 def CheckMessage(project, commit):
822 return pre_upload._check_change_is_contribution(project, commit)
823
824 def testNormal(self):
825 """Accept a commit message which is a contribution."""
826 self.assertMessageAccepted('\nThis is cool code I am contributing.\n')
827
828 def testFailureLowerCase(self):
829 """Deny a commit message which is not a contribution."""
830 self.assertMessageRejected('\nThis is not a contribution.\n')
831
832 def testFailureUpperCase(self):
833 """Deny a commit message which is not a contribution."""
834 self.assertMessageRejected('\nNOT A CONTRIBUTION\n')
835
836
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400837class CheckCommitMessageTest(CommitMessageTestCase):
838 """Tests for _check_change_has_test_field."""
839
840 @staticmethod
841 def CheckMessage(project, commit):
842 return pre_upload._check_change_has_test_field(project, commit)
843
844 def testNormal(self):
845 """Accept a commit message w/a valid TEST."""
846 self.assertMessageAccepted('\nTEST=i did it\n')
847
848 def testNone(self):
849 """Accept TEST=None."""
850 self.assertMessageAccepted('\nTEST=None\n')
851 self.assertMessageAccepted('\nTEST=none\n')
852
853 def testBlank(self):
854 """Reject blank values."""
855 self.assertMessageRejected('\nTEST=\n')
856 self.assertMessageRejected('\nTEST= \n')
857
858 def testNotFirstLine(self):
859 """Reject the first line."""
860 self.assertMessageRejected('TEST=None\n\n\n')
861
862 def testNotInline(self):
863 """Reject not at the start of line."""
864 self.assertMessageRejected('\n TEST=None\n')
865 self.assertMessageRejected('\n\tTEST=None\n')
866
867 def testMissing(self):
868 """Reject commit messages w/no TEST line."""
869 self.assertMessageRejected('foo\n')
870
871 def testCase(self):
872 """Reject bug lines that are not TEST."""
873 self.assertMessageRejected('\ntest=none\n')
874
875
876class CheckCommitMessageChangeId(CommitMessageTestCase):
877 """Tests for _check_change_has_proper_changeid."""
878
879 @staticmethod
880 def CheckMessage(project, commit):
881 return pre_upload._check_change_has_proper_changeid(project, commit)
882
883 def testNormal(self):
884 """Accept a commit message w/a valid Change-Id."""
885 self.assertMessageAccepted('foo\n\nChange-Id: I1234\n')
886
887 def testBlank(self):
888 """Reject blank values."""
889 self.assertMessageRejected('\nChange-Id:\n')
890 self.assertMessageRejected('\nChange-Id: \n')
891
892 def testNotFirstLine(self):
893 """Reject the first line."""
894 self.assertMessageRejected('TEST=None\n\n\n')
895
896 def testNotInline(self):
897 """Reject not at the start of line."""
898 self.assertMessageRejected('\n Change-Id: I1234\n')
899 self.assertMessageRejected('\n\tChange-Id: I1234\n')
900
901 def testMissing(self):
902 """Reject commit messages missing the line."""
903 self.assertMessageRejected('foo\n')
904
905 def testCase(self):
906 """Reject bug lines that are not Change-Id."""
907 self.assertMessageRejected('\nchange-id: I1234\n')
908 self.assertMessageRejected('\nChange-id: I1234\n')
909 self.assertMessageRejected('\nChange-ID: I1234\n')
910
911 def testEnd(self):
912 """Reject Change-Id's that are not last."""
913 self.assertMessageRejected('\nChange-Id: I1234\nbar\n')
914
Mike Frysinger02b88bd2014-11-21 00:29:38 -0500915 def testSobTag(self):
916 """Permit s-o-b tags to follow the Change-Id."""
917 self.assertMessageAccepted('foo\n\nChange-Id: I1234\nSigned-off-by: Hi\n')
918
Mike Frysinger4a22bf02014-10-31 13:53:35 -0400919
Mike Frysinger36b2ebc2014-10-31 14:02:03 -0400920class CheckCommitMessageStyle(CommitMessageTestCase):
921 """Tests for _check_commit_message_style."""
922
923 @staticmethod
924 def CheckMessage(project, commit):
925 return pre_upload._check_commit_message_style(project, commit)
926
927 def testNormal(self):
928 """Accept valid commit messages."""
929 self.assertMessageAccepted('one sentence.\n')
930 self.assertMessageAccepted('some.module: do it!\n')
931 self.assertMessageAccepted('one line\n\nmore stuff here.')
932
933 def testNoBlankSecondLine(self):
934 """Reject messages that have stuff on the second line."""
935 self.assertMessageRejected('one sentence.\nbad fish!\n')
936
937 def testFirstLineMultipleSentences(self):
938 """Reject messages that have more than one sentence in the summary."""
939 self.assertMessageRejected('one sentence. two sentence!\n')
940
941 def testFirstLineTooLone(self):
942 """Reject first lines that are too long."""
943 self.assertMessageRejected('o' * 200)
944
945
Mike Frysinger292b45d2014-11-25 01:17:10 -0500946def DiffEntry(src_file=None, dst_file=None, src_mode=None, dst_mode='100644',
947 status='M'):
948 """Helper to create a stub RawDiffEntry object"""
949 if src_mode is None:
950 if status == 'A':
951 src_mode = '000000'
952 elif status == 'M':
953 src_mode = dst_mode
954 elif status == 'D':
955 src_mode = dst_mode
956 dst_mode = '000000'
957
958 src_sha = dst_sha = 'abc'
959 if status == 'D':
960 dst_sha = '000000'
961 elif status == 'A':
962 src_sha = '000000'
963
964 return git.RawDiffEntry(src_mode=src_mode, dst_mode=dst_mode, src_sha=src_sha,
965 dst_sha=dst_sha, status=status, score=None,
966 src_file=src_file, dst_file=dst_file)
967
968
Daniel Erate3ea3fc2015-02-13 15:27:52 -0700969class HelpersTest(cros_test_lib.MockTempDirTestCase):
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500970 """Various tests for utility functions."""
971
Daniel Erate3ea3fc2015-02-13 15:27:52 -0700972 def setUp(self):
973 self.orig_cwd = os.getcwd()
974 os.chdir(self.tempdir)
975
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500976 self.PatchObject(git, 'RawDiff', return_value=[
977 # A modified normal file.
Mike Frysinger292b45d2014-11-25 01:17:10 -0500978 DiffEntry(src_file='buildbot/constants.py', status='M'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500979 # A new symlink file.
Mike Frysinger292b45d2014-11-25 01:17:10 -0500980 DiffEntry(dst_file='scripts/cros_env_whitelist', dst_mode='120000',
981 status='A'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500982 # A deleted file.
Mike Frysinger292b45d2014-11-25 01:17:10 -0500983 DiffEntry(src_file='scripts/sync_sonic.py', status='D'),
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500984 ])
985
Daniel Erate3ea3fc2015-02-13 15:27:52 -0700986 def tearDown(self):
987 os.chdir(self.orig_cwd)
988
989 def _WritePresubmitIgnoreFile(self, subdir, data):
990 """Writes to a .presubmitignore file in the passed-in subdirectory."""
991 directory = os.path.join(self.tempdir, subdir)
992 if not os.path.exists(directory):
993 os.makedirs(directory)
994 osutils.WriteFile(os.path.join(directory, pre_upload._IGNORE_FILE), data)
995
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500996 def testGetAffectedFilesNoDeletesNoRelative(self):
997 """Verify _get_affected_files() works w/no delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -0500998 path = os.getcwd()
999 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1000 relative=False)
1001 exp_files = [os.path.join(path, 'buildbot/constants.py')]
1002 self.assertEquals(files, exp_files)
1003
1004 def testGetAffectedFilesDeletesNoRelative(self):
1005 """Verify _get_affected_files() works w/delete & not relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001006 path = os.getcwd()
1007 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1008 relative=False)
1009 exp_files = [os.path.join(path, 'buildbot/constants.py'),
1010 os.path.join(path, 'scripts/sync_sonic.py')]
1011 self.assertEquals(files, exp_files)
1012
1013 def testGetAffectedFilesNoDeletesRelative(self):
1014 """Verify _get_affected_files() works w/no delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001015 files = pre_upload._get_affected_files('HEAD', include_deletes=False,
1016 relative=True)
1017 exp_files = ['buildbot/constants.py']
1018 self.assertEquals(files, exp_files)
1019
1020 def testGetAffectedFilesDeletesRelative(self):
1021 """Verify _get_affected_files() works w/delete & relative."""
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001022 files = pre_upload._get_affected_files('HEAD', include_deletes=True,
1023 relative=True)
1024 exp_files = ['buildbot/constants.py', 'scripts/sync_sonic.py']
1025 self.assertEquals(files, exp_files)
1026
Mike Frysinger292b45d2014-11-25 01:17:10 -05001027 def testGetAffectedFilesDetails(self):
1028 """Verify _get_affected_files() works w/full_details."""
Mike Frysinger292b45d2014-11-25 01:17:10 -05001029 files = pre_upload._get_affected_files('HEAD', full_details=True,
1030 relative=True)
1031 self.assertEquals(files[0].src_file, 'buildbot/constants.py')
1032
Daniel Erate3ea3fc2015-02-13 15:27:52 -07001033 def testGetAffectedFilesPresubmitIgnoreDirectory(self):
1034 """Verify .presubmitignore can be used to exclude a directory."""
1035 self._WritePresubmitIgnoreFile('.', 'buildbot/')
1036 self.assertEquals(pre_upload._get_affected_files('HEAD', relative=True), [])
1037
1038 def testGetAffectedFilesPresubmitIgnoreDirectoryWildcard(self):
1039 """Verify .presubmitignore can be used with a directory wildcard."""
1040 self._WritePresubmitIgnoreFile('.', '*/constants.py')
1041 self.assertEquals(pre_upload._get_affected_files('HEAD', relative=True), [])
1042
1043 def testGetAffectedFilesPresubmitIgnoreWithinDirectory(self):
1044 """Verify .presubmitignore can be placed in a subdirectory."""
1045 self._WritePresubmitIgnoreFile('buildbot', '*.py')
1046 self.assertEquals(pre_upload._get_affected_files('HEAD', relative=True), [])
1047
1048 def testGetAffectedFilesPresubmitIgnoreDoesntMatch(self):
1049 """Verify .presubmitignore has no effect when it doesn't match a file."""
1050 self._WritePresubmitIgnoreFile('buildbot', '*.txt')
1051 self.assertEquals(pre_upload._get_affected_files('HEAD', relative=True),
1052 ['buildbot/constants.py'])
1053
1054 def testGetAffectedFilesPresubmitIgnoreAddedFile(self):
1055 """Verify .presubmitignore matches added files."""
1056 self._WritePresubmitIgnoreFile('.', 'buildbot/\nscripts/')
1057 self.assertEquals(pre_upload._get_affected_files('HEAD', relative=True,
1058 include_symlinks=True),
1059 [])
1060
1061 def testGetAffectedFilesPresubmitIgnoreSkipIgnoreFile(self):
1062 """Verify .presubmitignore files are automatically skipped."""
1063 self.PatchObject(git, 'RawDiff', return_value=[
1064 DiffEntry(src_file='.presubmitignore', status='M')
1065 ])
1066 self.assertEquals(pre_upload._get_affected_files('HEAD', relative=True), [])
Mike Frysinger292b45d2014-11-25 01:17:10 -05001067
1068class CheckForUprev(cros_test_lib.MockTempDirTestCase):
1069 """Tests for _check_for_uprev."""
1070
1071 def setUp(self):
1072 self.file_mock = self.PatchObject(git, 'RawDiff')
1073
1074 def _Files(self, files):
1075 """Create |files| in the tempdir and return full paths to them."""
1076 for obj in files:
1077 if obj.status == 'D':
1078 continue
1079 if obj.dst_file is None:
1080 f = obj.src_file
1081 else:
1082 f = obj.dst_file
1083 osutils.Touch(os.path.join(self.tempdir, f), makedirs=True)
1084 return files
1085
1086 def assertAccepted(self, files, project='project', commit='fake sha1'):
1087 """Assert _check_for_uprev accepts |files|."""
1088 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001089 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1090 project_top=self.tempdir)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001091 self.assertEqual(ret, None)
1092
1093 def assertRejected(self, files, project='project', commit='fake sha1'):
1094 """Assert _check_for_uprev rejects |files|."""
1095 self.file_mock.return_value = self._Files(files)
Alex Deymo643ac4c2015-09-03 10:40:50 -07001096 ret = pre_upload._check_for_uprev(ProjectNamed(project), commit,
1097 project_top=self.tempdir)
Mike Frysinger292b45d2014-11-25 01:17:10 -05001098 self.assertTrue(isinstance(ret, errors.HookFailure))
1099
1100 def testWhitelistOverlay(self):
1101 """Skip checks on whitelisted overlays."""
1102 self.assertAccepted([DiffEntry(src_file='cat/pkg/pkg-0.ebuild')],
1103 project='chromiumos/overlays/portage-stable')
1104
1105 def testWhitelistFiles(self):
1106 """Skip checks on whitelisted files."""
1107 files = ['ChangeLog', 'Manifest', 'metadata.xml']
1108 self.assertAccepted([DiffEntry(src_file=os.path.join('c', 'p', x),
1109 status='M')
1110 for x in files])
1111
1112 def testRejectBasic(self):
1113 """Reject ebuilds missing uprevs."""
1114 self.assertRejected([DiffEntry(src_file='c/p/p-0.ebuild', status='M')])
1115
1116 def testNewPackage(self):
1117 """Accept new ebuilds w/out uprevs."""
1118 self.assertAccepted([DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1119 self.assertAccepted([DiffEntry(src_file='c/p/p-0-r12.ebuild', status='A')])
1120
1121 def testModifiedFilesOnly(self):
1122 """Reject ebuilds w/out uprevs and changes in files/."""
1123 osutils.Touch(os.path.join(self.tempdir, 'cat/pkg/pkg-0.ebuild'),
1124 makedirs=True)
1125 self.assertRejected([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1126 self.assertRejected([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1127
1128 def testFilesNoEbuilds(self):
1129 """Ignore changes to paths w/out ebuilds."""
1130 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/f', status='A')])
1131 self.assertAccepted([DiffEntry(src_file='cat/pkg/files/g', status='M')])
1132
1133 def testModifiedFilesWithUprev(self):
1134 """Accept ebuilds w/uprevs and changes in files/."""
1135 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='A'),
1136 DiffEntry(src_file='c/p/p-0.ebuild', status='A')])
1137 self.assertAccepted([
1138 DiffEntry(src_file='c/p/files/f', status='M'),
1139 DiffEntry(src_file='c/p/p-0-r1.ebuild', src_mode='120000',
1140 dst_file='c/p/p-0-r2.ebuild', dst_mode='120000', status='R')])
1141
Gwendal Grignoua3086c32014-12-09 11:17:22 -08001142 def testModifiedFilesWith9999(self):
1143 """Accept 9999 ebuilds and changes in files/."""
1144 self.assertAccepted([DiffEntry(src_file='c/p/files/f', status='M'),
1145 DiffEntry(src_file='c/p/p-9999.ebuild', status='M')])
1146
Mike Frysingerd3bd32c2014-11-24 23:34:29 -05001147
Mike Frysinger55f85b52014-12-18 14:45:21 -05001148class DirectMainTest(cros_test_lib.MockTempDirTestCase):
1149 """Tests for direct_main()"""
1150
1151 def setUp(self):
1152 self.hooks_mock = self.PatchObject(pre_upload, '_run_project_hooks',
1153 return_value=None)
1154
1155 def testNoArgs(self):
1156 """If run w/no args, should check the current dir."""
1157 ret = pre_upload.direct_main([])
1158 self.assertEqual(ret, 0)
1159 self.hooks_mock.assert_called_once_with(
1160 mock.ANY, proj_dir=os.getcwd(), commit_list=[], presubmit=mock.ANY)
1161
1162 def testExplicitDir(self):
1163 """Verify we can run on a diff dir."""
1164 # Use the chromite dir since we know it exists.
1165 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR])
1166 self.assertEqual(ret, 0)
1167 self.hooks_mock.assert_called_once_with(
1168 mock.ANY, proj_dir=constants.CHROMITE_DIR, commit_list=[],
1169 presubmit=mock.ANY)
1170
1171 def testBogusProject(self):
1172 """A bogus project name should be fine (use default settings)."""
1173 # Use the chromite dir since we know it exists.
1174 ret = pre_upload.direct_main(['--dir', constants.CHROMITE_DIR,
1175 '--project', 'foooooooooo'])
1176 self.assertEqual(ret, 0)
1177 self.hooks_mock.assert_called_once_with(
1178 'foooooooooo', proj_dir=constants.CHROMITE_DIR, commit_list=[],
1179 presubmit=mock.ANY)
1180
1181 def testBogustProjectNoDir(self):
1182 """Make sure --dir is detected even with --project."""
1183 ret = pre_upload.direct_main(['--project', 'foooooooooo'])
1184 self.assertEqual(ret, 0)
1185 self.hooks_mock.assert_called_once_with(
1186 'foooooooooo', proj_dir=os.getcwd(), commit_list=[],
1187 presubmit=mock.ANY)
1188
1189 def testNoGitDir(self):
1190 """We should die when run on a non-git dir."""
1191 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
1192 ['--dir', self.tempdir])
1193
1194 def testNoDir(self):
1195 """We should die when run on a missing dir."""
1196 self.assertRaises(pre_upload.BadInvocation, pre_upload.direct_main,
1197 ['--dir', os.path.join(self.tempdir, 'foooooooo')])
1198
1199 def testCommitList(self):
1200 """Any args on the command line should be treated as commits."""
1201 commits = ['sha1', 'sha2', 'shaaaaaaaaaaaan']
1202 ret = pre_upload.direct_main(commits)
1203 self.assertEqual(ret, 0)
1204 self.hooks_mock.assert_called_once_with(
1205 mock.ANY, proj_dir=mock.ANY, commit_list=commits, presubmit=mock.ANY)
1206
1207
Jon Salz98255932012-08-18 14:48:02 +08001208if __name__ == '__main__':
Mike Frysinger884a8dd2015-05-17 03:43:43 -04001209 cros_test_lib.main(module=__name__)