blob: 722844e15d30903d9b87453326fbaed5c960c848 [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
406''', 'spi.c', '''Some fixes for SPI in this
407file to make SPI work
408better than before''')
409 first_target = repo.revparse_single('HEAD')
410
411 target = repo.revparse_single('HEAD~2')
412 repo.reset(target.oid, pygit2.GIT_CHECKOUT_FORCE)
413 self.make_commit_with_file('video: Some video improvements', '''
414Fix up the video so that
415it looks more purple. Purple is
416a very nice colour.
417''', 'video.c', '''More purple here
418Purple and purple
419Even more purple
420Could not be any more purple''')
421 self.make_commit_with_file('serial: Add a serial driver', '''
422Here is the serial driver
423for my chip.
424
425Cover-letter:
426Series for my board
427This series implements support
428for my glorious board.
429END
Simon Glassf9e42842020-10-29 21:46:16 -0600430Series-links: 183237
Simon Glassfd709862020-07-05 21:41:50 -0600431''', 'serial.c', '''The code for the
432serial driver is here''')
433 self.make_commit_with_file('bootm: Make it boot', '''
434This makes my board boot
435with a fix to the bootm
436command
437''', 'bootm.c', '''Fix up the bootm
438command to make the code as
439complicated as possible''')
440 second_target = repo.revparse_single('HEAD')
441
442 repo.branches.local.create('first', first_target)
443 repo.config.set_multivar('branch.first.remote', '', '.')
444 repo.config.set_multivar('branch.first.merge', '', 'refs/heads/base')
445
446 repo.branches.local.create('second', second_target)
447 repo.config.set_multivar('branch.second.remote', '', '.')
448 repo.config.set_multivar('branch.second.merge', '', 'refs/heads/base')
449
450 repo.branches.local.create('base', base_target)
451 return repo
452
453 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
454 def testBranch(self):
455 """Test creating patches from a branch"""
456 repo = self.make_git_tree()
457 target = repo.lookup_reference('refs/heads/first')
458 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
459 control.setup()
460 try:
461 orig_dir = os.getcwd()
462 os.chdir(self.gitdir)
463
464 # Check that it can detect the current branch
Simon Glass262130f2020-07-05 21:41:51 -0600465 self.assertEqual(2, gitutil.CountCommitsToBranch(None))
Simon Glassfd709862020-07-05 21:41:50 -0600466 col = terminal.Color()
467 with capture_sys_output() as _:
468 _, cover_fname, patch_files = control.prepare_patches(
Simon Glass137947e2020-07-05 21:41:52 -0600469 col, branch=None, count=-1, start=0, end=0,
470 ignore_binary=False)
Simon Glassfd709862020-07-05 21:41:50 -0600471 self.assertIsNone(cover_fname)
472 self.assertEqual(2, len(patch_files))
Simon Glass262130f2020-07-05 21:41:51 -0600473
474 # Check that it can detect a different branch
475 self.assertEqual(3, gitutil.CountCommitsToBranch('second'))
476 with capture_sys_output() as _:
477 _, cover_fname, patch_files = control.prepare_patches(
Simon Glass137947e2020-07-05 21:41:52 -0600478 col, branch='second', count=-1, start=0, end=0,
Simon Glass262130f2020-07-05 21:41:51 -0600479 ignore_binary=False)
480 self.assertIsNotNone(cover_fname)
481 self.assertEqual(3, len(patch_files))
Simon Glass137947e2020-07-05 21:41:52 -0600482
483 # Check that it can skip patches at the end
484 with capture_sys_output() as _:
485 _, cover_fname, patch_files = control.prepare_patches(
486 col, branch='second', count=-1, start=0, end=1,
487 ignore_binary=False)
488 self.assertIsNotNone(cover_fname)
489 self.assertEqual(2, len(patch_files))
Simon Glassfd709862020-07-05 21:41:50 -0600490 finally:
491 os.chdir(orig_dir)
Simon Glass74570512020-10-29 21:46:27 -0600492
493 def testTags(self):
494 """Test collection of tags in a patchstream"""
495 text = '''This is a patch
496
497Signed-off-by: Terminator
Simon Glass4af99872020-10-29 21:46:28 -0600498Reviewed-by: %s
499Reviewed-by: %s
Simon Glass74570512020-10-29 21:46:27 -0600500Tested-by: %s
Simon Glass4af99872020-10-29 21:46:28 -0600501''' % (self.joe, self.mary, self.leb)
Simon Glass74570512020-10-29 21:46:27 -0600502 pstrm = PatchStream.process_text(text)
503 self.assertEqual(pstrm.commit.rtags, {
Simon Glass4af99872020-10-29 21:46:28 -0600504 'Reviewed-by': {self.joe, self.mary},
Simon Glass74570512020-10-29 21:46:27 -0600505 'Tested-by': {self.leb}})
Simon Glass4af99872020-10-29 21:46:28 -0600506
507 def testMissingEnd(self):
508 """Test a missing END tag"""
509 text = '''This is a patch
510
511Cover-letter:
512This is the title
513missing END after this line
514Signed-off-by: Fred
515'''
516 pstrm = PatchStream.process_text(text)
517 self.assertEqual(["Missing 'END' in section 'cover'"],
518 pstrm.commit.warn)
519
520 def testMissingBlankLine(self):
521 """Test a missing blank line after a tag"""
522 text = '''This is a patch
523
524Series-changes: 2
525- First line of changes
526- Missing blank line after this line
527Signed-off-by: Fred
528'''
529 pstrm = PatchStream.process_text(text)
530 self.assertEqual(["Missing 'blank line' in section 'Series-changes'"],
531 pstrm.commit.warn)
532
533 def testInvalidCommitTag(self):
534 """Test an invalid Commit-xxx tag"""
535 text = '''This is a patch
536
537Commit-fred: testing
538'''
539 pstrm = PatchStream.process_text(text)
540 self.assertEqual(["Line 3: Ignoring Commit-fred"], pstrm.commit.warn)
541
542 def testSelfTest(self):
543 """Test a tested by tag by this user"""
544 test_line = 'Tested-by: %s@napier.com' % os.getenv('USER')
545 text = '''This is a patch
546
547%s
548''' % test_line
549 pstrm = PatchStream.process_text(text)
550 self.assertEqual(["Ignoring '%s'" % test_line], pstrm.commit.warn)
551
552 def testSpaceBeforeTab(self):
553 """Test a space before a tab"""
554 text = '''This is a patch
555
556+ \tSomething
557'''
558 pstrm = PatchStream.process_text(text)
559 self.assertEqual(["Line 3/0 has space before tab"], pstrm.commit.warn)
560
561 def testLinesAfterTest(self):
562 """Test detecting lines after TEST= line"""
563 text = '''This is a patch
564
565TEST=sometest
566more lines
567here
568'''
569 pstrm = PatchStream.process_text(text)
570 self.assertEqual(["Found 2 lines after TEST="], pstrm.commit.warn)
571
572 def testBlankLineAtEnd(self):
573 """Test detecting a blank line at the end of a file"""
574 text = '''This is a patch
575
576diff --git a/lib/fdtdec.c b/lib/fdtdec.c
577index c072e54..942244f 100644
578--- a/lib/fdtdec.c
579+++ b/lib/fdtdec.c
580@@ -1200,7 +1200,8 @@ int fdtdec_setup_mem_size_base(void)
581 }
582
583 gd->ram_size = (phys_size_t)(res.end - res.start + 1);
584- debug("%s: Initial DRAM size %llx\n", __func__, (u64)gd->ram_size);
585+ debug("%s: Initial DRAM size %llx\n", __func__,
586+ (unsigned long long)gd->ram_size);
587+
588diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c
589
590--
5912.7.4
592
593 '''
594 pstrm = PatchStream.process_text(text)
595 self.assertEqual(
596 ["Found possible blank line(s) at end of file 'lib/fdtdec.c'"],
597 pstrm.commit.warn)
Simon Glassbe051c02020-10-29 21:46:34 -0600598
599 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
600 def testNoUpstream(self):
601 """Test CountCommitsToBranch when there is no upstream"""
602 repo = self.make_git_tree()
603 target = repo.lookup_reference('refs/heads/base')
604 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
605
606 # Check that it can detect the current branch
607 try:
608 orig_dir = os.getcwd()
609 os.chdir(self.gitdir)
610 with self.assertRaises(ValueError) as exc:
611 gitutil.CountCommitsToBranch(None)
612 self.assertIn(
613 "Failed to determine upstream: fatal: no upstream configured for branch 'base'",
614 str(exc.exception))
615 finally:
616 os.chdir(orig_dir)
Simon Glassdc6df972020-10-29 21:46:35 -0600617
618 @staticmethod
619 def _fake_patchwork(subpath):
620 """Fake Patchwork server for the function below
621
622 This handles accessing a series, providing a list consisting of a
623 single patch
624 """
625 re_series = re.match(r'series/(\d*)/$', subpath)
626 if re_series:
627 series_num = re_series.group(1)
628 if series_num == '1234':
629 return {'patches': [
630 {'id': '1', 'name': 'Some patch'}]}
631 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
632
633 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
634 def testStatusMismatch(self):
635 """Test Patchwork patches not matching the series"""
636 series = Series()
637
638 with capture_sys_output() as (_, err):
639 status.collect_patches(series, 1234, self._fake_patchwork)
640 self.assertIn('Warning: Patchwork reports 1 patches, series has 0',
641 err.getvalue())
642
643 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
644 def testStatusReadPatch(self):
645 """Test handling a single patch in Patchwork"""
646 series = Series()
647 series.commits = [Commit('abcd')]
648
649 patches = status.collect_patches(series, 1234, self._fake_patchwork)
650 self.assertEqual(1, len(patches))
651 patch = patches[0]
652 self.assertEqual('1', patch.id)
653 self.assertEqual('Some patch', patch.raw_subject)
654
655 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
656 def testParseSubject(self):
657 """Test parsing of the patch subject"""
658 patch = status.Patch('1')
659
660 # Simple patch not in a series
661 patch.parse_subject('Testing')
662 self.assertEqual('Testing', patch.raw_subject)
663 self.assertEqual('Testing', patch.subject)
664 self.assertEqual(1, patch.seq)
665 self.assertEqual(1, patch.count)
666 self.assertEqual(None, patch.prefix)
667 self.assertEqual(None, patch.version)
668
669 # First patch in a series
670 patch.parse_subject('[1/2] Testing')
671 self.assertEqual('[1/2] Testing', patch.raw_subject)
672 self.assertEqual('Testing', patch.subject)
673 self.assertEqual(1, patch.seq)
674 self.assertEqual(2, patch.count)
675 self.assertEqual(None, patch.prefix)
676 self.assertEqual(None, patch.version)
677
678 # Second patch in a series
679 patch.parse_subject('[2/2] Testing')
680 self.assertEqual('Testing', patch.subject)
681 self.assertEqual(2, patch.seq)
682 self.assertEqual(2, patch.count)
683 self.assertEqual(None, patch.prefix)
684 self.assertEqual(None, patch.version)
685
686 # RFC patch
687 patch.parse_subject('[RFC,3/7] Testing')
688 self.assertEqual('Testing', patch.subject)
689 self.assertEqual(3, patch.seq)
690 self.assertEqual(7, patch.count)
691 self.assertEqual('RFC', patch.prefix)
692 self.assertEqual(None, patch.version)
693
694 # Version patch
695 patch.parse_subject('[v2,3/7] Testing')
696 self.assertEqual('Testing', patch.subject)
697 self.assertEqual(3, patch.seq)
698 self.assertEqual(7, patch.count)
699 self.assertEqual(None, patch.prefix)
700 self.assertEqual('v2', patch.version)
701
702 # All fields
703 patch.parse_subject('[RESEND,v2,3/7] Testing')
704 self.assertEqual('Testing', patch.subject)
705 self.assertEqual(3, patch.seq)
706 self.assertEqual(7, patch.count)
707 self.assertEqual('RESEND', patch.prefix)
708 self.assertEqual('v2', patch.version)
709
710 # RFC only
711 patch.parse_subject('[RESEND] Testing')
712 self.assertEqual('Testing', patch.subject)
713 self.assertEqual(1, patch.seq)
714 self.assertEqual(1, patch.count)
715 self.assertEqual('RESEND', patch.prefix)
716 self.assertEqual(None, patch.version)
717
718 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
719 def testCompareSeries(self):
720 """Test operation of compare_with_series()"""
721 commit1 = Commit('abcd')
722 commit1.subject = 'Subject 1'
723 commit2 = Commit('ef12')
724 commit2.subject = 'Subject 2'
725 commit3 = Commit('3456')
726 commit3.subject = 'Subject 2'
727
728 patch1 = status.Patch('1')
729 patch1.subject = 'Subject 1'
730 patch2 = status.Patch('2')
731 patch2.subject = 'Subject 2'
732 patch3 = status.Patch('3')
733 patch3.subject = 'Subject 2'
734
735 series = Series()
736 series.commits = [commit1]
737 patches = [patch1]
738 patch_for_commit, commit_for_patch, warnings = (
739 status.compare_with_series(series, patches))
740 self.assertEqual(1, len(patch_for_commit))
741 self.assertEqual(patch1, patch_for_commit[0])
742 self.assertEqual(1, len(commit_for_patch))
743 self.assertEqual(commit1, commit_for_patch[0])
744
745 series.commits = [commit1]
746 patches = [patch1, patch2]
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 self.assertEqual(["Cannot find commit for patch 2 ('Subject 2')"],
754 warnings)
755
756 series.commits = [commit1, commit2]
757 patches = [patch1]
758 patch_for_commit, commit_for_patch, warnings = (
759 status.compare_with_series(series, patches))
760 self.assertEqual(1, len(patch_for_commit))
761 self.assertEqual(patch1, patch_for_commit[0])
762 self.assertEqual(1, len(commit_for_patch))
763 self.assertEqual(commit1, commit_for_patch[0])
764 self.assertEqual(["Cannot find patch for commit 2 ('Subject 2')"],
765 warnings)
766
767 series.commits = [commit1, commit2, commit3]
768 patches = [patch1, patch2]
769 patch_for_commit, commit_for_patch, warnings = (
770 status.compare_with_series(series, patches))
771 self.assertEqual(2, len(patch_for_commit))
772 self.assertEqual(patch1, patch_for_commit[0])
773 self.assertEqual(patch2, patch_for_commit[1])
774 self.assertEqual(1, len(commit_for_patch))
775 self.assertEqual(commit1, commit_for_patch[0])
776 self.assertEqual(["Cannot find patch for commit 3 ('Subject 2')",
777 "Multiple commits match patch 2 ('Subject 2'):\n"
778 ' Subject 2\n Subject 2'],
779 warnings)
780
781 series.commits = [commit1, commit2]
782 patches = [patch1, patch2, patch3]
783 patch_for_commit, commit_for_patch, warnings = (
784 status.compare_with_series(series, patches))
785 self.assertEqual(1, len(patch_for_commit))
786 self.assertEqual(patch1, patch_for_commit[0])
787 self.assertEqual(2, len(commit_for_patch))
788 self.assertEqual(commit1, commit_for_patch[0])
789 self.assertEqual(["Multiple patches match commit 2 ('Subject 2'):\n"
790 ' Subject 2\n Subject 2',
791 "Cannot find commit for patch 3 ('Subject 2')"],
792 warnings)
793
794 def _fake_patchwork2(self, subpath):
795 """Fake Patchwork server for the function below
796
797 This handles accessing series, patches and comments, providing the data
798 in self.patches to the caller
799 """
800 re_series = re.match(r'series/(\d*)/$', subpath)
801 re_patch = re.match(r'patches/(\d*)/$', subpath)
802 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
803 if re_series:
804 series_num = re_series.group(1)
805 if series_num == '1234':
806 return {'patches': self.patches}
807 elif re_patch:
808 patch_num = int(re_patch.group(1))
809 patch = self.patches[patch_num - 1]
810 return patch
811 elif re_comments:
812 patch_num = int(re_comments.group(1))
813 patch = self.patches[patch_num - 1]
814 return patch.comments
815 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
816
817 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
818 def testFindNewResponses(self):
819 """Test operation of find_new_responses()"""
820 commit1 = Commit('abcd')
821 commit1.subject = 'Subject 1'
822 commit2 = Commit('ef12')
823 commit2.subject = 'Subject 2'
824
825 patch1 = status.Patch('1')
826 patch1.parse_subject('[1/2] Subject 1')
827 patch1.name = patch1.raw_subject
828 patch1.content = 'This is my patch content'
829 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
830
831 patch1.comments = [comment1a]
832
833 patch2 = status.Patch('2')
834 patch2.parse_subject('[2/2] Subject 2')
835 patch2.name = patch2.raw_subject
836 patch2.content = 'Some other patch content'
837 comment2a = {
838 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
839 (self.mary, self.leb)}
840 comment2b = {'content': 'Reviewed-by: %s' % self.fred}
841 patch2.comments = [comment2a, comment2b]
842
843 # This test works by setting up commits and patch for use by the fake
844 # Rest API function _fake_patchwork2(). It calls various functions in
845 # the status module after setting up tags in the commits, checking that
846 # things behaves as expected
847 self.commits = [commit1, commit2]
848 self.patches = [patch1, patch2]
849 count = 2
850 new_rtag_list = [None] * count
851
852 # Check that the tags are picked up on the first patch
853 status.find_new_responses(new_rtag_list, 0, commit1, patch1,
854 self._fake_patchwork2)
855 self.assertEqual(new_rtag_list[0], {'Reviewed-by': {self.joe}})
856
857 # Now the second patch
858 status.find_new_responses(new_rtag_list, 1, commit2, patch2,
859 self._fake_patchwork2)
860 self.assertEqual(new_rtag_list[1], {
861 'Reviewed-by': {self.mary, self.fred},
862 'Tested-by': {self.leb}})
863
864 # Now add some tags to the commit, which means they should not appear as
865 # 'new' tags when scanning comments
866 new_rtag_list = [None] * count
867 commit1.rtags = {'Reviewed-by': {self.joe}}
868 status.find_new_responses(new_rtag_list, 0, commit1, patch1,
869 self._fake_patchwork2)
870 self.assertEqual(new_rtag_list[0], {})
871
872 # For the second commit, add Ed and Fred, so only Mary should be left
873 commit2.rtags = {
874 'Tested-by': {self.leb},
875 'Reviewed-by': {self.fred}}
876 status.find_new_responses(new_rtag_list, 1, commit2, patch2,
877 self._fake_patchwork2)
878 self.assertEqual(new_rtag_list[1], {'Reviewed-by': {self.mary}})
879
880 # Check that the output patches expectations:
881 # 1 Subject 1
882 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
883 # 2 Subject 2
884 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
885 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
886 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
887 # 1 new response available in patchwork
888
889 series = Series()
890 series.commits = [commit1, commit2]
891 terminal.SetPrintTestMode()
892 status.check_patchwork_status(series, '1234', self._fake_patchwork2)
893 lines = iter(terminal.GetPrintTestLines())
894 col = terminal.Color()
895 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
896 next(lines))
897 self.assertEqual(
898 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
899 bright=False),
900 next(lines))
901 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE, bright=False),
902 next(lines))
903
904 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
905 next(lines))
906 self.assertEqual(
907 terminal.PrintLine(' Tested-by: ', col.GREEN, newline=False,
908 bright=False),
909 next(lines))
910 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE, bright=False),
911 next(lines))
912 self.assertEqual(
913 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
914 bright=False),
915 next(lines))
916 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE, bright=False),
917 next(lines))
918 self.assertEqual(
919 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
920 next(lines))
921 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
922 next(lines))
923 self.assertEqual(terminal.PrintLine(
924 '1 new response available in patchwork', None), next(lines))