blob: 2e1529525ebc47d2ee1a822af76929190f7e435c [file] [log] [blame]
Simon Glass6e87ae12017-05-29 15:31:31 -06001# -*- coding: utf-8 -*-
Tom Rini83d290c2018-05-06 17:58:06 -04002# SPDX-License-Identifier: GPL-2.0+
Simon Glass6e87ae12017-05-29 15:31:31 -06003#
4# Copyright 2017 Google, Inc
5#
Simon Glass6e87ae12017-05-29 15:31:31 -06006
Simon Glassfca99112020-10-29 21:46:15 -06007"""Functional tests for checking that patman behaves correctly"""
8
Simon Glass6e87ae12017-05-29 15:31:31 -06009import os
10import re
11import shutil
12import sys
13import tempfile
14import unittest
15
Simon Glassdc6df972020-10-29 21:46:35 -060016
17from patman.commit import Commit
Simon Glassfd709862020-07-05 21:41:50 -060018from patman import control
Simon Glassbf776672020-04-17 18:09:04 -060019from patman import gitutil
20from patman import patchstream
Simon Glass47f62952020-10-29 21:46:26 -060021from patman.patchstream import PatchStream
Simon Glassdc6df972020-10-29 21:46:35 -060022from patman.series import Series
Simon Glassbf776672020-04-17 18:09:04 -060023from patman import settings
Simon Glassfd709862020-07-05 21:41:50 -060024from patman import terminal
Simon Glassbf776672020-04-17 18:09:04 -060025from patman import tools
Simon Glassfd709862020-07-05 21:41:50 -060026from patman.test_util import capture_sys_output
27
28try:
29 import pygit2
Simon Glass427b0282020-10-29 21:46:13 -060030 HAVE_PYGIT2 = True
Simon Glassdc6df972020-10-29 21:46:35 -060031 from patman import status
Simon Glassfd709862020-07-05 21:41:50 -060032except ModuleNotFoundError:
33 HAVE_PYGIT2 = False
Simon Glass6e87ae12017-05-29 15:31:31 -060034
35
Simon Glass6e87ae12017-05-29 15:31:31 -060036class TestFunctional(unittest.TestCase):
Simon Glassfca99112020-10-29 21:46:15 -060037 """Functional tests for checking that patman behaves correctly"""
Simon Glass74570512020-10-29 21:46:27 -060038 leb = (b'Lord Edmund Blackadd\xc3\xabr <weasel@blackadder.org>'.
39 decode('utf-8'))
Simon Glass4af99872020-10-29 21:46:28 -060040 fred = 'Fred Bloggs <f.bloggs@napier.net>'
41 joe = 'Joe Bloggs <joe@napierwallies.co.nz>'
42 mary = 'Mary Bloggs <mary@napierwallies.co.nz>'
Simon Glassdc6df972020-10-29 21:46:35 -060043 commits = None
44 patches = None
Simon Glass74570512020-10-29 21:46:27 -060045
Simon Glass6e87ae12017-05-29 15:31:31 -060046 def setUp(self):
47 self.tmpdir = tempfile.mkdtemp(prefix='patman.')
Simon Glassfd709862020-07-05 21:41:50 -060048 self.gitdir = os.path.join(self.tmpdir, 'git')
49 self.repo = None
Simon Glass6e87ae12017-05-29 15:31:31 -060050
51 def tearDown(self):
52 shutil.rmtree(self.tmpdir)
Simon Glassdc6df972020-10-29 21:46:35 -060053 terminal.SetPrintTestMode(False)
Simon Glass6e87ae12017-05-29 15:31:31 -060054
55 @staticmethod
Simon Glassfca99112020-10-29 21:46:15 -060056 def _get_path(fname):
57 """Get the path to a test file
58
59 Args:
60 fname (str): Filename to obtain
61
62 Returns:
63 str: Full path to file in the test directory
64 """
Simon Glass6e87ae12017-05-29 15:31:31 -060065 return os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
66 'test', fname)
67
68 @classmethod
Simon Glassfca99112020-10-29 21:46:15 -060069 def _get_text(cls, fname):
70 """Read a file as text
71
72 Args:
73 fname (str): Filename to read
74
75 Returns:
76 str: Contents of file
77 """
78 return open(cls._get_path(fname), encoding='utf-8').read()
Simon Glass6e87ae12017-05-29 15:31:31 -060079
80 @classmethod
Simon Glassfca99112020-10-29 21:46:15 -060081 def _get_patch_name(cls, subject):
82 """Get the filename of a patch given its subject
83
84 Args:
85 subject (str): Patch subject
86
87 Returns:
88 str: Filename for that patch
89 """
Simon Glass6e87ae12017-05-29 15:31:31 -060090 fname = re.sub('[ :]', '-', subject)
91 return fname.replace('--', '-')
92
Simon Glassfca99112020-10-29 21:46:15 -060093 def _create_patches_for_test(self, series):
94 """Create patch files for use by tests
95
96 This copies patch files from the test directory as needed by the series
97
98 Args:
99 series (Series): Series containing commits to convert
100
101 Returns:
102 tuple:
103 str: Cover-letter filename, or None if none
104 fname_list: list of str, each a patch filename
105 """
Simon Glass6e87ae12017-05-29 15:31:31 -0600106 cover_fname = None
107 fname_list = []
108 for i, commit in enumerate(series.commits):
Simon Glassfca99112020-10-29 21:46:15 -0600109 clean_subject = self._get_patch_name(commit.subject)
Simon Glass6e87ae12017-05-29 15:31:31 -0600110 src_fname = '%04d-%s.patch' % (i + 1, clean_subject[:52])
111 fname = os.path.join(self.tmpdir, src_fname)
Simon Glassfca99112020-10-29 21:46:15 -0600112 shutil.copy(self._get_path(src_fname), fname)
Simon Glass6e87ae12017-05-29 15:31:31 -0600113 fname_list.append(fname)
114 if series.get('cover'):
115 src_fname = '0000-cover-letter.patch'
116 cover_fname = os.path.join(self.tmpdir, src_fname)
117 fname = os.path.join(self.tmpdir, src_fname)
Simon Glassfca99112020-10-29 21:46:15 -0600118 shutil.copy(self._get_path(src_fname), fname)
Simon Glass6e87ae12017-05-29 15:31:31 -0600119
120 return cover_fname, fname_list
121
122 def testBasic(self):
123 """Tests the basic flow of patman
124
125 This creates a series from some hard-coded patches build from a simple
126 tree with the following metadata in the top commit:
127
128 Series-to: u-boot
129 Series-prefix: RFC
130 Series-cc: Stefan Brüns <stefan.bruens@rwth-aachen.de>
131 Cover-letter-cc: Lord Mëlchett <clergy@palace.gov>
Sean Andersondc03ba42020-05-04 16:28:36 -0400132 Series-version: 3
133 Patch-cc: fred
134 Series-process-log: sort, uniq
Simon Glass6e87ae12017-05-29 15:31:31 -0600135 Series-changes: 4
136 - Some changes
Sean Andersondc03ba42020-05-04 16:28:36 -0400137 - Multi
138 line
139 change
140
141 Commit-changes: 2
142 - Changes only for this commit
143
144 Cover-changes: 4
145 - Some notes for the cover letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600146
147 Cover-letter:
148 test: A test patch series
149 This is a test of how the cover
Sean Andersondc03ba42020-05-04 16:28:36 -0400150 letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600151 works
152 END
153
154 and this in the first commit:
155
Sean Andersondc03ba42020-05-04 16:28:36 -0400156 Commit-changes: 2
157 - second revision change
158
Simon Glass6e87ae12017-05-29 15:31:31 -0600159 Series-notes:
160 some notes
161 about some things
162 from the first commit
163 END
164
165 Commit-notes:
166 Some notes about
167 the first commit
168 END
169
170 with the following commands:
171
172 git log -n2 --reverse >/path/to/tools/patman/test/test01.txt
173 git format-patch --subject-prefix RFC --cover-letter HEAD~2
174 mv 00* /path/to/tools/patman/test
175
176 It checks these aspects:
177 - git log can be processed by patchstream
178 - emailing patches uses the correct command
179 - CC file has information on each commit
180 - cover letter has the expected text and subject
181 - each patch has the correct subject
182 - dry-run information prints out correctly
183 - unicode is handled correctly
184 - Series-to, Series-cc, Series-prefix, Cover-letter
185 - Cover-letter-cc, Series-version, Series-changes, Series-notes
186 - Commit-notes
187 """
188 process_tags = True
189 ignore_bad_tags = True
Simon Glasse6dca5e2019-05-14 15:53:53 -0600190 stefan = b'Stefan Br\xc3\xbcns <stefan.bruens@rwth-aachen.de>'.decode('utf-8')
Simon Glass6e87ae12017-05-29 15:31:31 -0600191 rick = 'Richard III <richard@palace.gov>'
Simon Glasse6dca5e2019-05-14 15:53:53 -0600192 mel = b'Lord M\xc3\xablchett <clergy@palace.gov>'.decode('utf-8')
Simon Glass6e87ae12017-05-29 15:31:31 -0600193 add_maintainers = [stefan, rick]
194 dry_run = True
195 in_reply_to = mel
196 count = 2
197 settings.alias = {
Simon Glass427b0282020-10-29 21:46:13 -0600198 'fdt': ['simon'],
199 'u-boot': ['u-boot@lists.denx.de'],
Simon Glass74570512020-10-29 21:46:27 -0600200 'simon': [self.leb],
Simon Glass4af99872020-10-29 21:46:28 -0600201 'fred': [self.fred],
Simon Glass6e87ae12017-05-29 15:31:31 -0600202 }
203
Simon Glassfca99112020-10-29 21:46:15 -0600204 text = self._get_text('test01.txt')
Simon Glassd93720e2020-10-29 21:46:19 -0600205 series = patchstream.get_metadata_for_test(text)
Simon Glassfca99112020-10-29 21:46:15 -0600206 cover_fname, args = self._create_patches_for_test(series)
Simon Glass366954f2020-10-29 21:46:14 -0600207 with capture_sys_output() as out:
Simon Glassd93720e2020-10-29 21:46:19 -0600208 patchstream.fix_patches(series, args)
Simon Glass6e87ae12017-05-29 15:31:31 -0600209 if cover_fname and series.get('cover'):
Simon Glassd93720e2020-10-29 21:46:19 -0600210 patchstream.insert_cover_letter(cover_fname, series, count)
Simon Glass6e87ae12017-05-29 15:31:31 -0600211 series.DoChecks()
212 cc_file = series.MakeCcFile(process_tags, cover_fname,
Chris Packham4fb35022018-06-07 20:45:06 +1200213 not ignore_bad_tags, add_maintainers,
214 None)
Simon Glass427b0282020-10-29 21:46:13 -0600215 cmd = gitutil.EmailPatches(
216 series, cover_fname, args, dry_run, not ignore_bad_tags,
217 cc_file, in_reply_to=in_reply_to, thread=None)
Simon Glass6e87ae12017-05-29 15:31:31 -0600218 series.ShowActions(args, cmd, process_tags)
Simon Glass272cd852019-10-31 07:42:51 -0600219 cc_lines = open(cc_file, encoding='utf-8').read().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600220 os.remove(cc_file)
221
Simon Glass8c17f8c2020-10-29 21:46:29 -0600222 lines = iter(out[0].getvalue().splitlines())
223 self.assertEqual('Cleaned %s patches' % len(series.commits),
224 next(lines))
225 self.assertEqual('Change log missing for v2', next(lines))
226 self.assertEqual('Change log missing for v3', next(lines))
227 self.assertEqual('Change log for unknown version v4', next(lines))
228 self.assertEqual("Alias 'pci' not found", next(lines))
229 self.assertIn('Dry run', next(lines))
230 self.assertEqual('', next(lines))
231 self.assertIn('Send a total of %d patches' % count, next(lines))
232 prev = next(lines)
233 for i, commit in enumerate(series.commits):
234 self.assertEqual(' %s' % args[i], prev)
235 while True:
236 prev = next(lines)
237 if 'Cc:' not in prev:
238 break
239 self.assertEqual('To: u-boot@lists.denx.de', prev)
240 self.assertEqual('Cc: %s' % tools.FromUnicode(stefan), next(lines))
241 self.assertEqual('Version: 3', next(lines))
242 self.assertEqual('Prefix:\t RFC', next(lines))
243 self.assertEqual('Cover: 4 lines', next(lines))
244 self.assertEqual(' Cc: %s' % self.fred, next(lines))
Simon Glass74570512020-10-29 21:46:27 -0600245 self.assertEqual(' Cc: %s' % tools.FromUnicode(self.leb),
Simon Glass8c17f8c2020-10-29 21:46:29 -0600246 next(lines))
247 self.assertEqual(' Cc: %s' % tools.FromUnicode(mel), next(lines))
248 self.assertEqual(' Cc: %s' % rick, next(lines))
Simon Glass6e87ae12017-05-29 15:31:31 -0600249 expected = ('Git command: git send-email --annotate '
250 '--in-reply-to="%s" --to "u-boot@lists.denx.de" '
251 '--cc "%s" --cc-cmd "%s --cc-cmd %s" %s %s'
252 % (in_reply_to, stefan, sys.argv[0], cc_file, cover_fname,
Simon Glasse6dca5e2019-05-14 15:53:53 -0600253 ' '.join(args)))
Simon Glass8c17f8c2020-10-29 21:46:29 -0600254 self.assertEqual(expected, tools.ToUnicode(next(lines)))
Simon Glass6e87ae12017-05-29 15:31:31 -0600255
Dmitry Torokhov8ab452d2019-10-21 20:09:56 -0700256 self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)),
Simon Glasse6dca5e2019-05-14 15:53:53 -0600257 tools.ToUnicode(cc_lines[0]))
Simon Glass427b0282020-10-29 21:46:13 -0600258 self.assertEqual(
Simon Glass4af99872020-10-29 21:46:28 -0600259 '%s %s\0%s\0%s\0%s' % (args[1], self.fred, self.leb, rick, stefan),
Simon Glass427b0282020-10-29 21:46:13 -0600260 tools.ToUnicode(cc_lines[1]))
Simon Glass6e87ae12017-05-29 15:31:31 -0600261
262 expected = '''
263This is a test of how the cover
Sean Andersondc03ba42020-05-04 16:28:36 -0400264letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600265works
266
267some notes
268about some things
269from the first commit
270
271Changes in v4:
Sean Andersondc03ba42020-05-04 16:28:36 -0400272- Multi
273 line
274 change
Simon Glass6e87ae12017-05-29 15:31:31 -0600275- Some changes
Sean Andersondc03ba42020-05-04 16:28:36 -0400276- Some notes for the cover letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600277
278Simon Glass (2):
279 pci: Correct cast for sandbox
Siva Durga Prasad Paladugu12308b12018-07-16 15:56:11 +0530280 fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
Simon Glass6e87ae12017-05-29 15:31:31 -0600281
282 cmd/pci.c | 3 ++-
283 fs/fat/fat.c | 1 +
284 lib/efi_loader/efi_memory.c | 1 +
285 lib/fdtdec.c | 3 ++-
286 4 files changed, 6 insertions(+), 2 deletions(-)
287
288--\x20
2892.7.4
290
291'''
Simon Glass272cd852019-10-31 07:42:51 -0600292 lines = open(cover_fname, encoding='utf-8').read().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600293 self.assertEqual(
Simon Glass427b0282020-10-29 21:46:13 -0600294 'Subject: [RFC PATCH v3 0/2] test: A test patch series',
295 lines[3])
Simon Glass6e87ae12017-05-29 15:31:31 -0600296 self.assertEqual(expected.splitlines(), lines[7:])
297
298 for i, fname in enumerate(args):
Simon Glass272cd852019-10-31 07:42:51 -0600299 lines = open(fname, encoding='utf-8').read().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600300 subject = [line for line in lines if line.startswith('Subject')]
301 self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count),
302 subject[0][:18])
Sean Andersondc03ba42020-05-04 16:28:36 -0400303
304 # Check that we got our commit notes
305 start = 0
306 expected = ''
307
Simon Glass6e87ae12017-05-29 15:31:31 -0600308 if i == 0:
Sean Andersondc03ba42020-05-04 16:28:36 -0400309 start = 17
310 expected = '''---
311Some notes about
312the first commit
313
314(no changes since v2)
315
316Changes in v2:
317- second revision change'''
318 elif i == 1:
319 start = 17
320 expected = '''---
321
322Changes in v4:
323- Multi
324 line
325 change
326- Some changes
327
328Changes in v2:
329- Changes only for this commit'''
330
331 if expected:
332 expected = expected.splitlines()
333 self.assertEqual(expected, lines[start:(start+len(expected))])
Simon Glassfd709862020-07-05 21:41:50 -0600334
335 def make_commit_with_file(self, subject, body, fname, text):
336 """Create a file and add it to the git repo with a new commit
337
338 Args:
339 subject (str): Subject for the commit
340 body (str): Body text of the commit
341 fname (str): Filename of file to create
342 text (str): Text to put into the file
343 """
344 path = os.path.join(self.gitdir, fname)
345 tools.WriteFile(path, text, binary=False)
346 index = self.repo.index
347 index.add(fname)
Simon Glass427b0282020-10-29 21:46:13 -0600348 author = pygit2.Signature('Test user', 'test@email.com')
Simon Glassfd709862020-07-05 21:41:50 -0600349 committer = author
350 tree = index.write_tree()
351 message = subject + '\n' + body
352 self.repo.create_commit('HEAD', author, committer, message, tree,
353 [self.repo.head.target])
354
355 def make_git_tree(self):
356 """Make a simple git tree suitable for testing
357
358 It has three branches:
359 'base' has two commits: PCI, main
360 'first' has base as upstream and two more commits: I2C, SPI
361 'second' has base as upstream and three more: video, serial, bootm
362
363 Returns:
Simon Glassfca99112020-10-29 21:46:15 -0600364 pygit2.Repository: repository
Simon Glassfd709862020-07-05 21:41:50 -0600365 """
366 repo = pygit2.init_repository(self.gitdir)
367 self.repo = repo
368 new_tree = repo.TreeBuilder().write()
369
370 author = pygit2.Signature('Test user', 'test@email.com')
371 committer = author
Simon Glassfca99112020-10-29 21:46:15 -0600372 _ = repo.create_commit('HEAD', author, committer, 'Created master',
373 new_tree, [])
Simon Glassfd709862020-07-05 21:41:50 -0600374
375 self.make_commit_with_file('Initial commit', '''
376Add a README
377
378''', 'README', '''This is the README file
379describing this project
380in very little detail''')
381
382 self.make_commit_with_file('pci: PCI implementation', '''
383Here is a basic PCI implementation
384
385''', 'pci.c', '''This is a file
386it has some contents
387and some more things''')
388 self.make_commit_with_file('main: Main program', '''
389Hello here is the second commit.
390''', 'main.c', '''This is the main file
391there is very little here
392but we can always add more later
393if we want to
394
395Series-to: u-boot
396Series-cc: Barry Crump <bcrump@whataroa.nz>
397''')
398 base_target = repo.revparse_single('HEAD')
399 self.make_commit_with_file('i2c: I2C things', '''
400This has some stuff to do with I2C
401''', 'i2c.c', '''And this is the file contents
402with some I2C-related things in it''')
403 self.make_commit_with_file('spi: SPI fixes', '''
404SPI needs some fixes
405and here they are
Simon Glass8f9ba3a2020-10-29 21:46:36 -0600406
407Signed-off-by: %s
408
409Series-to: u-boot
410Commit-notes:
411title of the series
412This is the cover letter for the series
413with various details
414END
415''' % self.leb, 'spi.c', '''Some fixes for SPI in this
Simon Glassfd709862020-07-05 21:41:50 -0600416file to make SPI work
417better than before''')
418 first_target = repo.revparse_single('HEAD')
419
420 target = repo.revparse_single('HEAD~2')
421 repo.reset(target.oid, pygit2.GIT_CHECKOUT_FORCE)
422 self.make_commit_with_file('video: Some video improvements', '''
423Fix up the video so that
424it looks more purple. Purple is
425a very nice colour.
426''', 'video.c', '''More purple here
427Purple and purple
428Even more purple
429Could not be any more purple''')
430 self.make_commit_with_file('serial: Add a serial driver', '''
431Here is the serial driver
432for my chip.
433
434Cover-letter:
435Series for my board
436This series implements support
437for my glorious board.
438END
Simon Glassf9e42842020-10-29 21:46:16 -0600439Series-links: 183237
Simon Glassfd709862020-07-05 21:41:50 -0600440''', 'serial.c', '''The code for the
441serial driver is here''')
442 self.make_commit_with_file('bootm: Make it boot', '''
443This makes my board boot
444with a fix to the bootm
445command
446''', 'bootm.c', '''Fix up the bootm
447command to make the code as
448complicated as possible''')
449 second_target = repo.revparse_single('HEAD')
450
451 repo.branches.local.create('first', first_target)
452 repo.config.set_multivar('branch.first.remote', '', '.')
453 repo.config.set_multivar('branch.first.merge', '', 'refs/heads/base')
454
455 repo.branches.local.create('second', second_target)
456 repo.config.set_multivar('branch.second.remote', '', '.')
457 repo.config.set_multivar('branch.second.merge', '', 'refs/heads/base')
458
459 repo.branches.local.create('base', base_target)
460 return repo
461
462 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
463 def testBranch(self):
464 """Test creating patches from a branch"""
465 repo = self.make_git_tree()
466 target = repo.lookup_reference('refs/heads/first')
467 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
468 control.setup()
469 try:
470 orig_dir = os.getcwd()
471 os.chdir(self.gitdir)
472
473 # Check that it can detect the current branch
Simon Glass262130f2020-07-05 21:41:51 -0600474 self.assertEqual(2, gitutil.CountCommitsToBranch(None))
Simon Glassfd709862020-07-05 21:41:50 -0600475 col = terminal.Color()
476 with capture_sys_output() as _:
477 _, cover_fname, patch_files = control.prepare_patches(
Simon Glass137947e2020-07-05 21:41:52 -0600478 col, branch=None, count=-1, start=0, end=0,
479 ignore_binary=False)
Simon Glassfd709862020-07-05 21:41:50 -0600480 self.assertIsNone(cover_fname)
481 self.assertEqual(2, len(patch_files))
Simon Glass262130f2020-07-05 21:41:51 -0600482
483 # Check that it can detect a different branch
484 self.assertEqual(3, gitutil.CountCommitsToBranch('second'))
485 with capture_sys_output() as _:
486 _, cover_fname, patch_files = control.prepare_patches(
Simon Glass137947e2020-07-05 21:41:52 -0600487 col, branch='second', count=-1, start=0, end=0,
Simon Glass262130f2020-07-05 21:41:51 -0600488 ignore_binary=False)
489 self.assertIsNotNone(cover_fname)
490 self.assertEqual(3, len(patch_files))
Simon Glass137947e2020-07-05 21:41:52 -0600491
492 # Check that it can skip patches at the end
493 with capture_sys_output() as _:
494 _, cover_fname, patch_files = control.prepare_patches(
495 col, branch='second', count=-1, start=0, end=1,
496 ignore_binary=False)
497 self.assertIsNotNone(cover_fname)
498 self.assertEqual(2, len(patch_files))
Simon Glassfd709862020-07-05 21:41:50 -0600499 finally:
500 os.chdir(orig_dir)
Simon Glass74570512020-10-29 21:46:27 -0600501
502 def testTags(self):
503 """Test collection of tags in a patchstream"""
504 text = '''This is a patch
505
506Signed-off-by: Terminator
Simon Glass4af99872020-10-29 21:46:28 -0600507Reviewed-by: %s
508Reviewed-by: %s
Simon Glass74570512020-10-29 21:46:27 -0600509Tested-by: %s
Simon Glass4af99872020-10-29 21:46:28 -0600510''' % (self.joe, self.mary, self.leb)
Simon Glass74570512020-10-29 21:46:27 -0600511 pstrm = PatchStream.process_text(text)
512 self.assertEqual(pstrm.commit.rtags, {
Simon Glass4af99872020-10-29 21:46:28 -0600513 'Reviewed-by': {self.joe, self.mary},
Simon Glass74570512020-10-29 21:46:27 -0600514 'Tested-by': {self.leb}})
Simon Glass4af99872020-10-29 21:46:28 -0600515
516 def testMissingEnd(self):
517 """Test a missing END tag"""
518 text = '''This is a patch
519
520Cover-letter:
521This is the title
522missing END after this line
523Signed-off-by: Fred
524'''
525 pstrm = PatchStream.process_text(text)
526 self.assertEqual(["Missing 'END' in section 'cover'"],
527 pstrm.commit.warn)
528
529 def testMissingBlankLine(self):
530 """Test a missing blank line after a tag"""
531 text = '''This is a patch
532
533Series-changes: 2
534- First line of changes
535- Missing blank line after this line
536Signed-off-by: Fred
537'''
538 pstrm = PatchStream.process_text(text)
539 self.assertEqual(["Missing 'blank line' in section 'Series-changes'"],
540 pstrm.commit.warn)
541
542 def testInvalidCommitTag(self):
543 """Test an invalid Commit-xxx tag"""
544 text = '''This is a patch
545
546Commit-fred: testing
547'''
548 pstrm = PatchStream.process_text(text)
549 self.assertEqual(["Line 3: Ignoring Commit-fred"], pstrm.commit.warn)
550
551 def testSelfTest(self):
552 """Test a tested by tag by this user"""
553 test_line = 'Tested-by: %s@napier.com' % os.getenv('USER')
554 text = '''This is a patch
555
556%s
557''' % test_line
558 pstrm = PatchStream.process_text(text)
559 self.assertEqual(["Ignoring '%s'" % test_line], pstrm.commit.warn)
560
561 def testSpaceBeforeTab(self):
562 """Test a space before a tab"""
563 text = '''This is a patch
564
565+ \tSomething
566'''
567 pstrm = PatchStream.process_text(text)
568 self.assertEqual(["Line 3/0 has space before tab"], pstrm.commit.warn)
569
570 def testLinesAfterTest(self):
571 """Test detecting lines after TEST= line"""
572 text = '''This is a patch
573
574TEST=sometest
575more lines
576here
577'''
578 pstrm = PatchStream.process_text(text)
579 self.assertEqual(["Found 2 lines after TEST="], pstrm.commit.warn)
580
581 def testBlankLineAtEnd(self):
582 """Test detecting a blank line at the end of a file"""
583 text = '''This is a patch
584
585diff --git a/lib/fdtdec.c b/lib/fdtdec.c
586index c072e54..942244f 100644
587--- a/lib/fdtdec.c
588+++ b/lib/fdtdec.c
589@@ -1200,7 +1200,8 @@ int fdtdec_setup_mem_size_base(void)
590 }
591
592 gd->ram_size = (phys_size_t)(res.end - res.start + 1);
593- debug("%s: Initial DRAM size %llx\n", __func__, (u64)gd->ram_size);
594+ debug("%s: Initial DRAM size %llx\n", __func__,
595+ (unsigned long long)gd->ram_size);
596+
597diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c
598
599--
6002.7.4
601
602 '''
603 pstrm = PatchStream.process_text(text)
604 self.assertEqual(
605 ["Found possible blank line(s) at end of file 'lib/fdtdec.c'"],
606 pstrm.commit.warn)
Simon Glassbe051c02020-10-29 21:46:34 -0600607
608 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
609 def testNoUpstream(self):
610 """Test CountCommitsToBranch when there is no upstream"""
611 repo = self.make_git_tree()
612 target = repo.lookup_reference('refs/heads/base')
613 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
614
615 # Check that it can detect the current branch
616 try:
617 orig_dir = os.getcwd()
618 os.chdir(self.gitdir)
619 with self.assertRaises(ValueError) as exc:
620 gitutil.CountCommitsToBranch(None)
621 self.assertIn(
622 "Failed to determine upstream: fatal: no upstream configured for branch 'base'",
623 str(exc.exception))
624 finally:
625 os.chdir(orig_dir)
Simon Glassdc6df972020-10-29 21:46:35 -0600626
627 @staticmethod
628 def _fake_patchwork(subpath):
629 """Fake Patchwork server for the function below
630
631 This handles accessing a series, providing a list consisting of a
632 single patch
633 """
634 re_series = re.match(r'series/(\d*)/$', subpath)
635 if re_series:
636 series_num = re_series.group(1)
637 if series_num == '1234':
638 return {'patches': [
639 {'id': '1', 'name': 'Some patch'}]}
640 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
641
642 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
643 def testStatusMismatch(self):
644 """Test Patchwork patches not matching the series"""
645 series = Series()
646
647 with capture_sys_output() as (_, err):
648 status.collect_patches(series, 1234, self._fake_patchwork)
649 self.assertIn('Warning: Patchwork reports 1 patches, series has 0',
650 err.getvalue())
651
652 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
653 def testStatusReadPatch(self):
654 """Test handling a single patch in Patchwork"""
655 series = Series()
656 series.commits = [Commit('abcd')]
657
658 patches = status.collect_patches(series, 1234, self._fake_patchwork)
659 self.assertEqual(1, len(patches))
660 patch = patches[0]
661 self.assertEqual('1', patch.id)
662 self.assertEqual('Some patch', patch.raw_subject)
663
664 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
665 def testParseSubject(self):
666 """Test parsing of the patch subject"""
667 patch = status.Patch('1')
668
669 # Simple patch not in a series
670 patch.parse_subject('Testing')
671 self.assertEqual('Testing', patch.raw_subject)
672 self.assertEqual('Testing', patch.subject)
673 self.assertEqual(1, patch.seq)
674 self.assertEqual(1, patch.count)
675 self.assertEqual(None, patch.prefix)
676 self.assertEqual(None, patch.version)
677
678 # First patch in a series
679 patch.parse_subject('[1/2] Testing')
680 self.assertEqual('[1/2] Testing', patch.raw_subject)
681 self.assertEqual('Testing', patch.subject)
682 self.assertEqual(1, patch.seq)
683 self.assertEqual(2, patch.count)
684 self.assertEqual(None, patch.prefix)
685 self.assertEqual(None, patch.version)
686
687 # Second patch in a series
688 patch.parse_subject('[2/2] Testing')
689 self.assertEqual('Testing', patch.subject)
690 self.assertEqual(2, patch.seq)
691 self.assertEqual(2, patch.count)
692 self.assertEqual(None, patch.prefix)
693 self.assertEqual(None, patch.version)
694
695 # RFC patch
696 patch.parse_subject('[RFC,3/7] Testing')
697 self.assertEqual('Testing', patch.subject)
698 self.assertEqual(3, patch.seq)
699 self.assertEqual(7, patch.count)
700 self.assertEqual('RFC', patch.prefix)
701 self.assertEqual(None, patch.version)
702
703 # Version patch
704 patch.parse_subject('[v2,3/7] Testing')
705 self.assertEqual('Testing', patch.subject)
706 self.assertEqual(3, patch.seq)
707 self.assertEqual(7, patch.count)
708 self.assertEqual(None, patch.prefix)
709 self.assertEqual('v2', patch.version)
710
711 # All fields
712 patch.parse_subject('[RESEND,v2,3/7] Testing')
713 self.assertEqual('Testing', patch.subject)
714 self.assertEqual(3, patch.seq)
715 self.assertEqual(7, patch.count)
716 self.assertEqual('RESEND', patch.prefix)
717 self.assertEqual('v2', patch.version)
718
719 # RFC only
720 patch.parse_subject('[RESEND] Testing')
721 self.assertEqual('Testing', patch.subject)
722 self.assertEqual(1, patch.seq)
723 self.assertEqual(1, patch.count)
724 self.assertEqual('RESEND', patch.prefix)
725 self.assertEqual(None, patch.version)
726
727 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
728 def testCompareSeries(self):
729 """Test operation of compare_with_series()"""
730 commit1 = Commit('abcd')
731 commit1.subject = 'Subject 1'
732 commit2 = Commit('ef12')
733 commit2.subject = 'Subject 2'
734 commit3 = Commit('3456')
735 commit3.subject = 'Subject 2'
736
737 patch1 = status.Patch('1')
738 patch1.subject = 'Subject 1'
739 patch2 = status.Patch('2')
740 patch2.subject = 'Subject 2'
741 patch3 = status.Patch('3')
742 patch3.subject = 'Subject 2'
743
744 series = Series()
745 series.commits = [commit1]
746 patches = [patch1]
747 patch_for_commit, commit_for_patch, warnings = (
748 status.compare_with_series(series, patches))
749 self.assertEqual(1, len(patch_for_commit))
750 self.assertEqual(patch1, patch_for_commit[0])
751 self.assertEqual(1, len(commit_for_patch))
752 self.assertEqual(commit1, commit_for_patch[0])
753
754 series.commits = [commit1]
755 patches = [patch1, patch2]
756 patch_for_commit, commit_for_patch, warnings = (
757 status.compare_with_series(series, patches))
758 self.assertEqual(1, len(patch_for_commit))
759 self.assertEqual(patch1, patch_for_commit[0])
760 self.assertEqual(1, len(commit_for_patch))
761 self.assertEqual(commit1, commit_for_patch[0])
762 self.assertEqual(["Cannot find commit for patch 2 ('Subject 2')"],
763 warnings)
764
765 series.commits = [commit1, commit2]
766 patches = [patch1]
767 patch_for_commit, commit_for_patch, warnings = (
768 status.compare_with_series(series, patches))
769 self.assertEqual(1, len(patch_for_commit))
770 self.assertEqual(patch1, patch_for_commit[0])
771 self.assertEqual(1, len(commit_for_patch))
772 self.assertEqual(commit1, commit_for_patch[0])
773 self.assertEqual(["Cannot find patch for commit 2 ('Subject 2')"],
774 warnings)
775
776 series.commits = [commit1, commit2, commit3]
777 patches = [patch1, patch2]
778 patch_for_commit, commit_for_patch, warnings = (
779 status.compare_with_series(series, patches))
780 self.assertEqual(2, len(patch_for_commit))
781 self.assertEqual(patch1, patch_for_commit[0])
782 self.assertEqual(patch2, patch_for_commit[1])
783 self.assertEqual(1, len(commit_for_patch))
784 self.assertEqual(commit1, commit_for_patch[0])
785 self.assertEqual(["Cannot find patch for commit 3 ('Subject 2')",
786 "Multiple commits match patch 2 ('Subject 2'):\n"
787 ' Subject 2\n Subject 2'],
788 warnings)
789
790 series.commits = [commit1, commit2]
791 patches = [patch1, patch2, patch3]
792 patch_for_commit, commit_for_patch, warnings = (
793 status.compare_with_series(series, patches))
794 self.assertEqual(1, len(patch_for_commit))
795 self.assertEqual(patch1, patch_for_commit[0])
796 self.assertEqual(2, len(commit_for_patch))
797 self.assertEqual(commit1, commit_for_patch[0])
798 self.assertEqual(["Multiple patches match commit 2 ('Subject 2'):\n"
799 ' Subject 2\n Subject 2',
800 "Cannot find commit for patch 3 ('Subject 2')"],
801 warnings)
802
803 def _fake_patchwork2(self, subpath):
804 """Fake Patchwork server for the function below
805
806 This handles accessing series, patches and comments, providing the data
807 in self.patches to the caller
808 """
809 re_series = re.match(r'series/(\d*)/$', subpath)
810 re_patch = re.match(r'patches/(\d*)/$', subpath)
811 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
812 if re_series:
813 series_num = re_series.group(1)
814 if series_num == '1234':
815 return {'patches': self.patches}
816 elif re_patch:
817 patch_num = int(re_patch.group(1))
818 patch = self.patches[patch_num - 1]
819 return patch
820 elif re_comments:
821 patch_num = int(re_comments.group(1))
822 patch = self.patches[patch_num - 1]
823 return patch.comments
824 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
825
826 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
827 def testFindNewResponses(self):
828 """Test operation of find_new_responses()"""
829 commit1 = Commit('abcd')
830 commit1.subject = 'Subject 1'
831 commit2 = Commit('ef12')
832 commit2.subject = 'Subject 2'
833
834 patch1 = status.Patch('1')
835 patch1.parse_subject('[1/2] Subject 1')
836 patch1.name = patch1.raw_subject
837 patch1.content = 'This is my patch content'
838 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
839
840 patch1.comments = [comment1a]
841
842 patch2 = status.Patch('2')
843 patch2.parse_subject('[2/2] Subject 2')
844 patch2.name = patch2.raw_subject
845 patch2.content = 'Some other patch content'
846 comment2a = {
847 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
848 (self.mary, self.leb)}
849 comment2b = {'content': 'Reviewed-by: %s' % self.fred}
850 patch2.comments = [comment2a, comment2b]
851
852 # This test works by setting up commits and patch for use by the fake
853 # Rest API function _fake_patchwork2(). It calls various functions in
854 # the status module after setting up tags in the commits, checking that
855 # things behaves as expected
856 self.commits = [commit1, commit2]
857 self.patches = [patch1, patch2]
858 count = 2
859 new_rtag_list = [None] * count
860
861 # Check that the tags are picked up on the first patch
862 status.find_new_responses(new_rtag_list, 0, commit1, patch1,
863 self._fake_patchwork2)
864 self.assertEqual(new_rtag_list[0], {'Reviewed-by': {self.joe}})
865
866 # Now the second patch
867 status.find_new_responses(new_rtag_list, 1, commit2, patch2,
868 self._fake_patchwork2)
869 self.assertEqual(new_rtag_list[1], {
870 'Reviewed-by': {self.mary, self.fred},
871 'Tested-by': {self.leb}})
872
873 # Now add some tags to the commit, which means they should not appear as
874 # 'new' tags when scanning comments
875 new_rtag_list = [None] * count
876 commit1.rtags = {'Reviewed-by': {self.joe}}
877 status.find_new_responses(new_rtag_list, 0, commit1, patch1,
878 self._fake_patchwork2)
879 self.assertEqual(new_rtag_list[0], {})
880
881 # For the second commit, add Ed and Fred, so only Mary should be left
882 commit2.rtags = {
883 'Tested-by': {self.leb},
884 'Reviewed-by': {self.fred}}
885 status.find_new_responses(new_rtag_list, 1, commit2, patch2,
886 self._fake_patchwork2)
887 self.assertEqual(new_rtag_list[1], {'Reviewed-by': {self.mary}})
888
889 # Check that the output patches expectations:
890 # 1 Subject 1
891 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
892 # 2 Subject 2
893 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
894 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
895 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
896 # 1 new response available in patchwork
897
898 series = Series()
899 series.commits = [commit1, commit2]
900 terminal.SetPrintTestMode()
Simon Glass8f9ba3a2020-10-29 21:46:36 -0600901 status.check_patchwork_status(series, '1234', None, None, False,
902 self._fake_patchwork2)
Simon Glassdc6df972020-10-29 21:46:35 -0600903 lines = iter(terminal.GetPrintTestLines())
904 col = terminal.Color()
905 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
906 next(lines))
907 self.assertEqual(
908 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
909 bright=False),
910 next(lines))
911 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE, bright=False),
912 next(lines))
913
914 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
915 next(lines))
916 self.assertEqual(
917 terminal.PrintLine(' Tested-by: ', col.GREEN, newline=False,
918 bright=False),
919 next(lines))
920 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE, bright=False),
921 next(lines))
922 self.assertEqual(
923 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
924 bright=False),
925 next(lines))
926 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE, bright=False),
927 next(lines))
928 self.assertEqual(
929 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
930 next(lines))
931 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
932 next(lines))
933 self.assertEqual(terminal.PrintLine(
Simon Glass8f9ba3a2020-10-29 21:46:36 -0600934 '1 new response available in patchwork (use -d to write them to a new branch)',
935 None), next(lines))
936
937 def _fake_patchwork3(self, subpath):
938 """Fake Patchwork server for the function below
939
940 This handles accessing series, patches and comments, providing the data
941 in self.patches to the caller
942 """
943 re_series = re.match(r'series/(\d*)/$', subpath)
944 re_patch = re.match(r'patches/(\d*)/$', subpath)
945 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
946 if re_series:
947 series_num = re_series.group(1)
948 if series_num == '1234':
949 return {'patches': self.patches}
950 elif re_patch:
951 patch_num = int(re_patch.group(1))
952 patch = self.patches[patch_num - 1]
953 return patch
954 elif re_comments:
955 patch_num = int(re_comments.group(1))
956 patch = self.patches[patch_num - 1]
957 return patch.comments
958 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
959
960 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
961 def testCreateBranch(self):
962 """Test operation of create_branch()"""
963 repo = self.make_git_tree()
964 branch = 'first'
965 dest_branch = 'first2'
966 count = 2
967 gitdir = os.path.join(self.gitdir, '.git')
968
969 # Set up the test git tree. We use branch 'first' which has two commits
970 # in it
971 series = patchstream.get_metadata_for_list(branch, gitdir, count)
972 self.assertEqual(2, len(series.commits))
973
974 patch1 = status.Patch('1')
975 patch1.parse_subject('[1/2] %s' % series.commits[0].subject)
976 patch1.name = patch1.raw_subject
977 patch1.content = 'This is my patch content'
978 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
979
980 patch1.comments = [comment1a]
981
982 patch2 = status.Patch('2')
983 patch2.parse_subject('[2/2] %s' % series.commits[1].subject)
984 patch2.name = patch2.raw_subject
985 patch2.content = 'Some other patch content'
986 comment2a = {
987 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
988 (self.mary, self.leb)}
989 comment2b = {
990 'content': 'Reviewed-by: %s' % self.fred}
991 patch2.comments = [comment2a, comment2b]
992
993 # This test works by setting up patches for use by the fake Rest API
994 # function _fake_patchwork3(). The fake patch comments above should
995 # result in new review tags that are collected and added to the commits
996 # created in the destination branch.
997 self.patches = [patch1, patch2]
998 count = 2
999
1000 # Expected output:
1001 # 1 i2c: I2C things
1002 # + Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1003 # 2 spi: SPI fixes
1004 # + Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1005 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1006 # + Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1007 # 4 new responses available in patchwork
1008 # 4 responses added from patchwork into new branch 'first2'
1009 # <unittest.result.TestResult run=8 errors=0 failures=0>
1010
1011 terminal.SetPrintTestMode()
1012 status.check_patchwork_status(series, '1234', branch, dest_branch,
1013 False, self._fake_patchwork3, repo)
1014 lines = terminal.GetPrintTestLines()
1015 self.assertEqual(12, len(lines))
1016 self.assertEqual(
1017 "4 responses added from patchwork into new branch 'first2'",
1018 lines[11].text)
1019
1020 # Check that the destination branch has the new tags
1021 new_series = patchstream.get_metadata_for_list(dest_branch, gitdir,
1022 count)
1023 self.assertEqual(
1024 {'Reviewed-by': {self.joe}},
1025 new_series.commits[0].rtags)
1026 self.assertEqual(
1027 {'Tested-by': {self.leb},
1028 'Reviewed-by': {self.fred, self.mary}},
1029 new_series.commits[1].rtags)
1030
1031 # Now check the actual test of the first commit message. We expect to
1032 # see the new tags immediately below the old ones.
1033 stdout = patchstream.get_list(dest_branch, count=count, git_dir=gitdir)
1034 lines = iter([line.strip() for line in stdout.splitlines()
1035 if '-by:' in line])
1036
1037 # First patch should have the review tag
1038 self.assertEqual('Reviewed-by: %s' % self.joe, next(lines))
1039
1040 # Second patch should have the sign-off then the tested-by and two
1041 # reviewed-by tags
1042 self.assertEqual('Signed-off-by: %s' % self.leb, next(lines))
1043 self.assertEqual('Reviewed-by: %s' % self.fred, next(lines))
1044 self.assertEqual('Reviewed-by: %s' % self.mary, next(lines))
1045 self.assertEqual('Tested-by: %s' % self.leb, next(lines))