blob: af6c025a44101109a55abe94ac7838b198c570a8 [file] [log] [blame]
Simon Glassdf1bc5c2017-05-29 15:31:31 -06001# -*- coding: utf-8 -*-
Tom Rini10e47792018-05-06 17:58:06 -04002# SPDX-License-Identifier: GPL-2.0+
Simon Glassdf1bc5c2017-05-29 15:31:31 -06003#
4# Copyright 2017 Google, Inc
5#
Simon Glassdf1bc5c2017-05-29 15:31:31 -06006
Simon Glasseb209e52020-10-29 21:46:15 -06007"""Functional tests for checking that patman behaves correctly"""
8
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -05009import contextlib
Simon Glassdf1bc5c2017-05-29 15:31:31 -060010import os
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050011import pathlib
Simon Glassdf1bc5c2017-05-29 15:31:31 -060012import re
13import shutil
14import sys
15import tempfile
16import unittest
17
Simon Glass3db916d2020-10-29 21:46:35 -060018
19from patman.commit import Commit
Simon Glass54f1c5b2020-07-05 21:41:50 -060020from patman import control
Simon Glassa997ea52020-04-17 18:09:04 -060021from patman import gitutil
22from patman import patchstream
Simon Glassa7fadab2020-10-29 21:46:26 -060023from patman.patchstream import PatchStream
Simon Glass3db916d2020-10-29 21:46:35 -060024from patman.series import Series
Simon Glassa997ea52020-04-17 18:09:04 -060025from patman import settings
Simon Glass131444f2023-02-23 18:18:04 -070026from u_boot_pylib import terminal
27from u_boot_pylib import tools
28from u_boot_pylib.test_util import capture_sys_output
Simon Glassdf1bc5c2017-05-29 15:31:31 -060029
Tom Rini488ea972021-02-26 07:52:31 -050030import pygit2
31from patman import status
Simon Glassdf1bc5c2017-05-29 15:31:31 -060032
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -050033PATMAN_DIR = pathlib.Path(__file__).parent
34TEST_DATA_DIR = PATMAN_DIR / 'test/'
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050035
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050036
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -050037@contextlib.contextmanager
38def directory_excursion(directory):
39 """Change directory to `directory` for a limited to the context block."""
40 current = os.getcwd()
41 try:
42 os.chdir(directory)
43 yield
44 finally:
45 os.chdir(current)
46
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050047
Simon Glassdf1bc5c2017-05-29 15:31:31 -060048class TestFunctional(unittest.TestCase):
Simon Glasseb209e52020-10-29 21:46:15 -060049 """Functional tests for checking that patman behaves correctly"""
Simon Glass06202d62020-10-29 21:46:27 -060050 leb = (b'Lord Edmund Blackadd\xc3\xabr <weasel@blackadder.org>'.
51 decode('utf-8'))
Simon Glass3b762cc2020-10-29 21:46:28 -060052 fred = 'Fred Bloggs <f.bloggs@napier.net>'
53 joe = 'Joe Bloggs <joe@napierwallies.co.nz>'
54 mary = 'Mary Bloggs <mary@napierwallies.co.nz>'
Simon Glass3db916d2020-10-29 21:46:35 -060055 commits = None
56 patches = None
Simon Glass06202d62020-10-29 21:46:27 -060057
Simon Glassdf1bc5c2017-05-29 15:31:31 -060058 def setUp(self):
59 self.tmpdir = tempfile.mkdtemp(prefix='patman.')
Simon Glass54f1c5b2020-07-05 21:41:50 -060060 self.gitdir = os.path.join(self.tmpdir, 'git')
61 self.repo = None
Simon Glassdf1bc5c2017-05-29 15:31:31 -060062
63 def tearDown(self):
64 shutil.rmtree(self.tmpdir)
Simon Glass02811582022-01-29 14:14:18 -070065 terminal.set_print_test_mode(False)
Simon Glassdf1bc5c2017-05-29 15:31:31 -060066
67 @staticmethod
Simon Glasseb209e52020-10-29 21:46:15 -060068 def _get_path(fname):
69 """Get the path to a test file
70
71 Args:
72 fname (str): Filename to obtain
73
74 Returns:
75 str: Full path to file in the test directory
76 """
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050077 return TEST_DATA_DIR / fname
Simon Glassdf1bc5c2017-05-29 15:31:31 -060078
79 @classmethod
Simon Glasseb209e52020-10-29 21:46:15 -060080 def _get_text(cls, fname):
81 """Read a file as text
82
83 Args:
84 fname (str): Filename to read
85
86 Returns:
87 str: Contents of file
88 """
89 return open(cls._get_path(fname), encoding='utf-8').read()
Simon Glassdf1bc5c2017-05-29 15:31:31 -060090
91 @classmethod
Simon Glasseb209e52020-10-29 21:46:15 -060092 def _get_patch_name(cls, subject):
93 """Get the filename of a patch given its subject
94
95 Args:
96 subject (str): Patch subject
97
98 Returns:
99 str: Filename for that patch
100 """
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600101 fname = re.sub('[ :]', '-', subject)
102 return fname.replace('--', '-')
103
Simon Glasseb209e52020-10-29 21:46:15 -0600104 def _create_patches_for_test(self, series):
105 """Create patch files for use by tests
106
107 This copies patch files from the test directory as needed by the series
108
109 Args:
110 series (Series): Series containing commits to convert
111
112 Returns:
113 tuple:
114 str: Cover-letter filename, or None if none
115 fname_list: list of str, each a patch filename
116 """
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600117 cover_fname = None
118 fname_list = []
119 for i, commit in enumerate(series.commits):
Simon Glasseb209e52020-10-29 21:46:15 -0600120 clean_subject = self._get_patch_name(commit.subject)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600121 src_fname = '%04d-%s.patch' % (i + 1, clean_subject[:52])
122 fname = os.path.join(self.tmpdir, src_fname)
Simon Glasseb209e52020-10-29 21:46:15 -0600123 shutil.copy(self._get_path(src_fname), fname)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600124 fname_list.append(fname)
125 if series.get('cover'):
126 src_fname = '0000-cover-letter.patch'
127 cover_fname = os.path.join(self.tmpdir, src_fname)
128 fname = os.path.join(self.tmpdir, src_fname)
Simon Glasseb209e52020-10-29 21:46:15 -0600129 shutil.copy(self._get_path(src_fname), fname)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600130
131 return cover_fname, fname_list
132
Simon Glassd85bb8f2022-01-29 14:14:09 -0700133 def test_basic(self):
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600134 """Tests the basic flow of patman
135
136 This creates a series from some hard-coded patches build from a simple
137 tree with the following metadata in the top commit:
138
139 Series-to: u-boot
140 Series-prefix: RFC
Sean Andersondc1cd132021-10-22 19:07:04 -0400141 Series-postfix: some-branch
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600142 Series-cc: Stefan Brüns <stefan.bruens@rwth-aachen.de>
143 Cover-letter-cc: Lord Mëlchett <clergy@palace.gov>
Sean Andersoncf13b862020-05-04 16:28:36 -0400144 Series-version: 3
145 Patch-cc: fred
146 Series-process-log: sort, uniq
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600147 Series-changes: 4
148 - Some changes
Sean Andersoncf13b862020-05-04 16:28:36 -0400149 - Multi
150 line
151 change
152
153 Commit-changes: 2
154 - Changes only for this commit
155
Simon Glass6a222e62021-08-01 16:02:39 -0600156' Cover-changes: 4
Sean Andersoncf13b862020-05-04 16:28:36 -0400157 - Some notes for the cover letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600158
159 Cover-letter:
160 test: A test patch series
161 This is a test of how the cover
Sean Andersoncf13b862020-05-04 16:28:36 -0400162 letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600163 works
164 END
165
166 and this in the first commit:
167
Sean Andersoncf13b862020-05-04 16:28:36 -0400168 Commit-changes: 2
169 - second revision change
170
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600171 Series-notes:
172 some notes
173 about some things
174 from the first commit
175 END
176
177 Commit-notes:
178 Some notes about
179 the first commit
180 END
181
182 with the following commands:
183
184 git log -n2 --reverse >/path/to/tools/patman/test/test01.txt
185 git format-patch --subject-prefix RFC --cover-letter HEAD~2
186 mv 00* /path/to/tools/patman/test
187
188 It checks these aspects:
189 - git log can be processed by patchstream
190 - emailing patches uses the correct command
191 - CC file has information on each commit
192 - cover letter has the expected text and subject
193 - each patch has the correct subject
194 - dry-run information prints out correctly
195 - unicode is handled correctly
Sean Andersondc1cd132021-10-22 19:07:04 -0400196 - Series-to, Series-cc, Series-prefix, Series-postfix, Cover-letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600197 - Cover-letter-cc, Series-version, Series-changes, Series-notes
198 - Commit-notes
199 """
200 process_tags = True
Simon Glass1f975b92021-01-23 08:56:15 -0700201 ignore_bad_tags = False
Simon Glass4f817892019-05-14 15:53:53 -0600202 stefan = b'Stefan Br\xc3\xbcns <stefan.bruens@rwth-aachen.de>'.decode('utf-8')
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600203 rick = 'Richard III <richard@palace.gov>'
Simon Glass4f817892019-05-14 15:53:53 -0600204 mel = b'Lord M\xc3\xablchett <clergy@palace.gov>'.decode('utf-8')
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600205 add_maintainers = [stefan, rick]
206 dry_run = True
207 in_reply_to = mel
208 count = 2
209 settings.alias = {
Simon Glass95745aa2020-10-29 21:46:13 -0600210 'fdt': ['simon'],
211 'u-boot': ['u-boot@lists.denx.de'],
Simon Glass06202d62020-10-29 21:46:27 -0600212 'simon': [self.leb],
Simon Glass3b762cc2020-10-29 21:46:28 -0600213 'fred': [self.fred],
Sean Anderson25978092024-04-18 22:36:31 -0400214 'joe': [self.joe],
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600215 }
216
Simon Glasseb209e52020-10-29 21:46:15 -0600217 text = self._get_text('test01.txt')
Simon Glass93f61c02020-10-29 21:46:19 -0600218 series = patchstream.get_metadata_for_test(text)
Simon Glasseb209e52020-10-29 21:46:15 -0600219 cover_fname, args = self._create_patches_for_test(series)
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500220 get_maintainer_script = str(pathlib.Path(__file__).parent.parent.parent
221 / 'get_maintainer.pl') + ' --norolestats'
Simon Glass59a70bb2020-10-29 21:46:14 -0600222 with capture_sys_output() as out:
Simon Glass93f61c02020-10-29 21:46:19 -0600223 patchstream.fix_patches(series, args)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600224 if cover_fname and series.get('cover'):
Simon Glass93f61c02020-10-29 21:46:19 -0600225 patchstream.insert_cover_letter(cover_fname, series, count)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600226 series.DoChecks()
227 cc_file = series.MakeCcFile(process_tags, cover_fname,
Chris Packhamb84fb482018-06-07 20:45:06 +1200228 not ignore_bad_tags, add_maintainers,
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500229 None, get_maintainer_script)
Simon Glass761648b2022-01-29 14:14:11 -0700230 cmd = gitutil.email_patches(
Simon Glass95745aa2020-10-29 21:46:13 -0600231 series, cover_fname, args, dry_run, not ignore_bad_tags,
232 cc_file, in_reply_to=in_reply_to, thread=None)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600233 series.ShowActions(args, cmd, process_tags)
Simon Glassf544a2d2019-10-31 07:42:51 -0600234 cc_lines = open(cc_file, encoding='utf-8').read().splitlines()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600235 os.remove(cc_file)
236
Simon Glass42e3d392020-10-29 21:46:29 -0600237 lines = iter(out[0].getvalue().splitlines())
238 self.assertEqual('Cleaned %s patches' % len(series.commits),
239 next(lines))
240 self.assertEqual('Change log missing for v2', next(lines))
241 self.assertEqual('Change log missing for v3', next(lines))
242 self.assertEqual('Change log for unknown version v4', next(lines))
243 self.assertEqual("Alias 'pci' not found", next(lines))
Simon Glass620639c2023-03-08 10:52:54 -0800244 while next(lines) != 'Cc processing complete':
245 pass
Simon Glass42e3d392020-10-29 21:46:29 -0600246 self.assertIn('Dry run', next(lines))
247 self.assertEqual('', next(lines))
248 self.assertIn('Send a total of %d patches' % count, next(lines))
249 prev = next(lines)
250 for i, commit in enumerate(series.commits):
251 self.assertEqual(' %s' % args[i], prev)
252 while True:
253 prev = next(lines)
254 if 'Cc:' not in prev:
255 break
256 self.assertEqual('To: u-boot@lists.denx.de', prev)
Simon Glass9dfb3112020-11-08 20:36:18 -0700257 self.assertEqual('Cc: %s' % stefan, next(lines))
Simon Glass42e3d392020-10-29 21:46:29 -0600258 self.assertEqual('Version: 3', next(lines))
259 self.assertEqual('Prefix:\t RFC', next(lines))
Sean Andersondc1cd132021-10-22 19:07:04 -0400260 self.assertEqual('Postfix:\t some-branch', next(lines))
Simon Glass42e3d392020-10-29 21:46:29 -0600261 self.assertEqual('Cover: 4 lines', next(lines))
262 self.assertEqual(' Cc: %s' % self.fred, next(lines))
Sean Anderson25978092024-04-18 22:36:31 -0400263 self.assertEqual(' Cc: %s' % self.joe, next(lines))
Simon Glass9dfb3112020-11-08 20:36:18 -0700264 self.assertEqual(' Cc: %s' % self.leb,
Simon Glass42e3d392020-10-29 21:46:29 -0600265 next(lines))
Simon Glass9dfb3112020-11-08 20:36:18 -0700266 self.assertEqual(' Cc: %s' % mel, next(lines))
Simon Glass42e3d392020-10-29 21:46:29 -0600267 self.assertEqual(' Cc: %s' % rick, next(lines))
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600268 expected = ('Git command: git send-email --annotate '
269 '--in-reply-to="%s" --to "u-boot@lists.denx.de" '
Simon Glass1ee91c12020-11-03 13:54:10 -0700270 '--cc "%s" --cc-cmd "%s send --cc-cmd %s" %s %s'
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600271 % (in_reply_to, stefan, sys.argv[0], cc_file, cover_fname,
Simon Glass4f817892019-05-14 15:53:53 -0600272 ' '.join(args)))
Simon Glass9dfb3112020-11-08 20:36:18 -0700273 self.assertEqual(expected, next(lines))
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600274
Simon Glass9dfb3112020-11-08 20:36:18 -0700275 self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)), cc_lines[0])
Simon Glass95745aa2020-10-29 21:46:13 -0600276 self.assertEqual(
Sean Anderson25978092024-04-18 22:36:31 -0400277 '%s %s\0%s\0%s\0%s\0%s' % (args[1], self.fred, self.joe, self.leb,
278 rick, stefan),
Simon Glass9dfb3112020-11-08 20:36:18 -0700279 cc_lines[1])
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600280
281 expected = '''
282This is a test of how the cover
Sean Andersoncf13b862020-05-04 16:28:36 -0400283letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600284works
285
286some notes
287about some things
288from the first commit
289
290Changes in v4:
Sean Andersoncf13b862020-05-04 16:28:36 -0400291- Multi
292 line
293 change
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600294- Some changes
Sean Andersoncf13b862020-05-04 16:28:36 -0400295- Some notes for the cover letter
Sean Andersone45678c2024-04-18 22:36:32 -0400296- fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600297
298Simon Glass (2):
299 pci: Correct cast for sandbox
Siva Durga Prasad Paladugub3d55ea2018-07-16 15:56:11 +0530300 fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600301
302 cmd/pci.c | 3 ++-
303 fs/fat/fat.c | 1 +
304 lib/efi_loader/efi_memory.c | 1 +
305 lib/fdtdec.c | 3 ++-
306 4 files changed, 6 insertions(+), 2 deletions(-)
307
308--\x20
3092.7.4
310
311'''
Simon Glassf544a2d2019-10-31 07:42:51 -0600312 lines = open(cover_fname, encoding='utf-8').read().splitlines()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600313 self.assertEqual(
Sean Andersondc1cd132021-10-22 19:07:04 -0400314 'Subject: [RFC PATCH some-branch v3 0/2] test: A test patch series',
Simon Glass95745aa2020-10-29 21:46:13 -0600315 lines[3])
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600316 self.assertEqual(expected.splitlines(), lines[7:])
317
318 for i, fname in enumerate(args):
Simon Glassf544a2d2019-10-31 07:42:51 -0600319 lines = open(fname, encoding='utf-8').read().splitlines()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600320 subject = [line for line in lines if line.startswith('Subject')]
321 self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count),
322 subject[0][:18])
Sean Andersoncf13b862020-05-04 16:28:36 -0400323
324 # Check that we got our commit notes
325 start = 0
326 expected = ''
327
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600328 if i == 0:
Sean Andersoncf13b862020-05-04 16:28:36 -0400329 start = 17
330 expected = '''---
331Some notes about
332the first commit
333
334(no changes since v2)
335
336Changes in v2:
337- second revision change'''
338 elif i == 1:
339 start = 17
340 expected = '''---
341
342Changes in v4:
343- Multi
344 line
345 change
Sean Andersone45678c2024-04-18 22:36:32 -0400346- New
Sean Andersoncf13b862020-05-04 16:28:36 -0400347- Some changes
348
349Changes in v2:
350- Changes only for this commit'''
351
352 if expected:
353 expected = expected.splitlines()
354 self.assertEqual(expected, lines[start:(start+len(expected))])
Simon Glass54f1c5b2020-07-05 21:41:50 -0600355
356 def make_commit_with_file(self, subject, body, fname, text):
357 """Create a file and add it to the git repo with a new commit
358
359 Args:
360 subject (str): Subject for the commit
361 body (str): Body text of the commit
362 fname (str): Filename of file to create
363 text (str): Text to put into the file
364 """
365 path = os.path.join(self.gitdir, fname)
Simon Glass80025522022-01-29 14:14:04 -0700366 tools.write_file(path, text, binary=False)
Simon Glass54f1c5b2020-07-05 21:41:50 -0600367 index = self.repo.index
368 index.add(fname)
Simon Glass547cba62022-02-11 13:23:18 -0700369 # pylint doesn't seem to find this
370 # pylint: disable=E1101
Simon Glass95745aa2020-10-29 21:46:13 -0600371 author = pygit2.Signature('Test user', 'test@email.com')
Simon Glass54f1c5b2020-07-05 21:41:50 -0600372 committer = author
373 tree = index.write_tree()
374 message = subject + '\n' + body
375 self.repo.create_commit('HEAD', author, committer, message, tree,
376 [self.repo.head.target])
377
378 def make_git_tree(self):
379 """Make a simple git tree suitable for testing
380
381 It has three branches:
382 'base' has two commits: PCI, main
383 'first' has base as upstream and two more commits: I2C, SPI
384 'second' has base as upstream and three more: video, serial, bootm
385
386 Returns:
Simon Glasseb209e52020-10-29 21:46:15 -0600387 pygit2.Repository: repository
Simon Glass54f1c5b2020-07-05 21:41:50 -0600388 """
389 repo = pygit2.init_repository(self.gitdir)
390 self.repo = repo
391 new_tree = repo.TreeBuilder().write()
392
Simon Glass547cba62022-02-11 13:23:18 -0700393 # pylint doesn't seem to find this
394 # pylint: disable=E1101
Simon Glass54f1c5b2020-07-05 21:41:50 -0600395 author = pygit2.Signature('Test user', 'test@email.com')
396 committer = author
Simon Glasseb209e52020-10-29 21:46:15 -0600397 _ = repo.create_commit('HEAD', author, committer, 'Created master',
398 new_tree, [])
Simon Glass54f1c5b2020-07-05 21:41:50 -0600399
400 self.make_commit_with_file('Initial commit', '''
401Add a README
402
403''', 'README', '''This is the README file
404describing this project
405in very little detail''')
406
407 self.make_commit_with_file('pci: PCI implementation', '''
408Here is a basic PCI implementation
409
410''', 'pci.c', '''This is a file
411it has some contents
412and some more things''')
413 self.make_commit_with_file('main: Main program', '''
414Hello here is the second commit.
415''', 'main.c', '''This is the main file
416there is very little here
417but we can always add more later
418if we want to
419
420Series-to: u-boot
421Series-cc: Barry Crump <bcrump@whataroa.nz>
422''')
423 base_target = repo.revparse_single('HEAD')
424 self.make_commit_with_file('i2c: I2C things', '''
425This has some stuff to do with I2C
426''', 'i2c.c', '''And this is the file contents
427with some I2C-related things in it''')
428 self.make_commit_with_file('spi: SPI fixes', '''
429SPI needs some fixes
430and here they are
Simon Glassd0a0a582020-10-29 21:46:36 -0600431
432Signed-off-by: %s
433
434Series-to: u-boot
435Commit-notes:
436title of the series
437This is the cover letter for the series
438with various details
439END
440''' % self.leb, 'spi.c', '''Some fixes for SPI in this
Simon Glass54f1c5b2020-07-05 21:41:50 -0600441file to make SPI work
442better than before''')
443 first_target = repo.revparse_single('HEAD')
444
445 target = repo.revparse_single('HEAD~2')
Simon Glass547cba62022-02-11 13:23:18 -0700446 # pylint doesn't seem to find this
447 # pylint: disable=E1101
Simon Glass54f1c5b2020-07-05 21:41:50 -0600448 repo.reset(target.oid, pygit2.GIT_CHECKOUT_FORCE)
449 self.make_commit_with_file('video: Some video improvements', '''
450Fix up the video so that
451it looks more purple. Purple is
452a very nice colour.
453''', 'video.c', '''More purple here
454Purple and purple
455Even more purple
456Could not be any more purple''')
457 self.make_commit_with_file('serial: Add a serial driver', '''
458Here is the serial driver
459for my chip.
460
461Cover-letter:
462Series for my board
463This series implements support
464for my glorious board.
465END
Simon Glassa80986c2020-10-29 21:46:16 -0600466Series-links: 183237
Simon Glass54f1c5b2020-07-05 21:41:50 -0600467''', 'serial.c', '''The code for the
468serial driver is here''')
469 self.make_commit_with_file('bootm: Make it boot', '''
470This makes my board boot
471with a fix to the bootm
472command
473''', 'bootm.c', '''Fix up the bootm
474command to make the code as
475complicated as possible''')
476 second_target = repo.revparse_single('HEAD')
477
478 repo.branches.local.create('first', first_target)
479 repo.config.set_multivar('branch.first.remote', '', '.')
480 repo.config.set_multivar('branch.first.merge', '', 'refs/heads/base')
481
482 repo.branches.local.create('second', second_target)
483 repo.config.set_multivar('branch.second.remote', '', '.')
484 repo.config.set_multivar('branch.second.merge', '', 'refs/heads/base')
485
486 repo.branches.local.create('base', base_target)
487 return repo
488
Simon Glassd85bb8f2022-01-29 14:14:09 -0700489 def test_branch(self):
Simon Glass54f1c5b2020-07-05 21:41:50 -0600490 """Test creating patches from a branch"""
491 repo = self.make_git_tree()
492 target = repo.lookup_reference('refs/heads/first')
Simon Glass547cba62022-02-11 13:23:18 -0700493 # pylint doesn't seem to find this
494 # pylint: disable=E1101
Simon Glass54f1c5b2020-07-05 21:41:50 -0600495 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
496 control.setup()
Heinrich Schuchardtd01d6672023-04-20 20:07:29 +0200497 orig_dir = os.getcwd()
Simon Glass54f1c5b2020-07-05 21:41:50 -0600498 try:
Simon Glass54f1c5b2020-07-05 21:41:50 -0600499 os.chdir(self.gitdir)
500
501 # Check that it can detect the current branch
Simon Glass761648b2022-01-29 14:14:11 -0700502 self.assertEqual(2, gitutil.count_commits_to_branch(None))
Simon Glass54f1c5b2020-07-05 21:41:50 -0600503 col = terminal.Color()
504 with capture_sys_output() as _:
505 _, cover_fname, patch_files = control.prepare_patches(
Simon Glassb3bf4e12020-07-05 21:41:52 -0600506 col, branch=None, count=-1, start=0, end=0,
Philipp Tomsich858531a2020-11-24 18:14:52 +0100507 ignore_binary=False, signoff=True)
Simon Glass54f1c5b2020-07-05 21:41:50 -0600508 self.assertIsNone(cover_fname)
509 self.assertEqual(2, len(patch_files))
Simon Glass2eb4da72020-07-05 21:41:51 -0600510
511 # Check that it can detect a different branch
Simon Glass761648b2022-01-29 14:14:11 -0700512 self.assertEqual(3, gitutil.count_commits_to_branch('second'))
Simon Glass2eb4da72020-07-05 21:41:51 -0600513 with capture_sys_output() as _:
514 _, cover_fname, patch_files = control.prepare_patches(
Simon Glassb3bf4e12020-07-05 21:41:52 -0600515 col, branch='second', count=-1, start=0, end=0,
Philipp Tomsich858531a2020-11-24 18:14:52 +0100516 ignore_binary=False, signoff=True)
Simon Glass2eb4da72020-07-05 21:41:51 -0600517 self.assertIsNotNone(cover_fname)
518 self.assertEqual(3, len(patch_files))
Simon Glassb3bf4e12020-07-05 21:41:52 -0600519
520 # Check that it can skip patches at the end
521 with capture_sys_output() as _:
522 _, cover_fname, patch_files = control.prepare_patches(
523 col, branch='second', count=-1, start=0, end=1,
Philipp Tomsich858531a2020-11-24 18:14:52 +0100524 ignore_binary=False, signoff=True)
Simon Glassb3bf4e12020-07-05 21:41:52 -0600525 self.assertIsNotNone(cover_fname)
526 self.assertEqual(2, len(patch_files))
Simon Glass54f1c5b2020-07-05 21:41:50 -0600527 finally:
528 os.chdir(orig_dir)
Simon Glass06202d62020-10-29 21:46:27 -0600529
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500530 def test_custom_get_maintainer_script(self):
531 """Validate that a custom get_maintainer script gets used."""
532 self.make_git_tree()
533 with directory_excursion(self.gitdir):
534 # Setup git.
535 os.environ['GIT_CONFIG_GLOBAL'] = '/dev/null'
536 os.environ['GIT_CONFIG_SYSTEM'] = '/dev/null'
537 tools.run('git', 'config', 'user.name', 'Dummy')
538 tools.run('git', 'config', 'user.email', 'dumdum@dummy.com')
539 tools.run('git', 'branch', 'upstream')
540 tools.run('git', 'branch', '--set-upstream-to=upstream')
541 tools.run('git', 'add', '.')
542 tools.run('git', 'commit', '-m', 'new commit')
543
544 # Setup patman configuration.
545 with open('.patman', 'w', buffering=1) as f:
546 f.write('[settings]\n'
547 'get_maintainer_script: dummy-script.sh\n'
Sean Andersona06df742024-04-18 22:36:30 -0400548 'check_patch: False\n'
549 'add_maintainers: True\n')
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500550 with open('dummy-script.sh', 'w', buffering=1) as f:
551 f.write('#!/usr/bin/env python\n'
552 'print("hello@there.com")\n')
553 os.chmod('dummy-script.sh', 0x555)
554
555 # Finally, do the test
556 with capture_sys_output():
557 output = tools.run(PATMAN_DIR / 'patman', '--dry-run')
558 # Assert the email address is part of the dry-run
559 # output.
560 self.assertIn('hello@there.com', output)
561
Simon Glassd85bb8f2022-01-29 14:14:09 -0700562 def test_tags(self):
Simon Glass06202d62020-10-29 21:46:27 -0600563 """Test collection of tags in a patchstream"""
564 text = '''This is a patch
565
566Signed-off-by: Terminator
Simon Glass3b762cc2020-10-29 21:46:28 -0600567Reviewed-by: %s
568Reviewed-by: %s
Simon Glass06202d62020-10-29 21:46:27 -0600569Tested-by: %s
Simon Glass3b762cc2020-10-29 21:46:28 -0600570''' % (self.joe, self.mary, self.leb)
Simon Glass06202d62020-10-29 21:46:27 -0600571 pstrm = PatchStream.process_text(text)
572 self.assertEqual(pstrm.commit.rtags, {
Simon Glass3b762cc2020-10-29 21:46:28 -0600573 'Reviewed-by': {self.joe, self.mary},
Simon Glass06202d62020-10-29 21:46:27 -0600574 'Tested-by': {self.leb}})
Simon Glass3b762cc2020-10-29 21:46:28 -0600575
Simon Glassd85bb8f2022-01-29 14:14:09 -0700576 def test_invalid_tag(self):
Patrick Delaunay6bbdd0c2021-07-22 16:51:42 +0200577 """Test invalid tag in a patchstream"""
578 text = '''This is a patch
579
580Serie-version: 2
581'''
582 with self.assertRaises(ValueError) as exc:
583 pstrm = PatchStream.process_text(text)
584 self.assertEqual("Line 3: Invalid tag = 'Serie-version: 2'",
585 str(exc.exception))
586
Simon Glassd85bb8f2022-01-29 14:14:09 -0700587 def test_missing_end(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600588 """Test a missing END tag"""
589 text = '''This is a patch
590
591Cover-letter:
592This is the title
593missing END after this line
594Signed-off-by: Fred
595'''
596 pstrm = PatchStream.process_text(text)
597 self.assertEqual(["Missing 'END' in section 'cover'"],
598 pstrm.commit.warn)
599
Simon Glassd85bb8f2022-01-29 14:14:09 -0700600 def test_missing_blank_line(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600601 """Test a missing blank line after a tag"""
602 text = '''This is a patch
603
604Series-changes: 2
605- First line of changes
606- Missing blank line after this line
607Signed-off-by: Fred
608'''
609 pstrm = PatchStream.process_text(text)
610 self.assertEqual(["Missing 'blank line' in section 'Series-changes'"],
611 pstrm.commit.warn)
612
Simon Glassd85bb8f2022-01-29 14:14:09 -0700613 def test_invalid_commit_tag(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600614 """Test an invalid Commit-xxx tag"""
615 text = '''This is a patch
616
617Commit-fred: testing
618'''
619 pstrm = PatchStream.process_text(text)
620 self.assertEqual(["Line 3: Ignoring Commit-fred"], pstrm.commit.warn)
621
Simon Glassd85bb8f2022-01-29 14:14:09 -0700622 def test_self_test(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600623 """Test a tested by tag by this user"""
624 test_line = 'Tested-by: %s@napier.com' % os.getenv('USER')
625 text = '''This is a patch
626
627%s
628''' % test_line
629 pstrm = PatchStream.process_text(text)
630 self.assertEqual(["Ignoring '%s'" % test_line], pstrm.commit.warn)
631
Simon Glassd85bb8f2022-01-29 14:14:09 -0700632 def test_space_before_tab(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600633 """Test a space before a tab"""
634 text = '''This is a patch
635
636+ \tSomething
637'''
638 pstrm = PatchStream.process_text(text)
639 self.assertEqual(["Line 3/0 has space before tab"], pstrm.commit.warn)
640
Simon Glassd85bb8f2022-01-29 14:14:09 -0700641 def test_lines_after_test(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600642 """Test detecting lines after TEST= line"""
643 text = '''This is a patch
644
645TEST=sometest
646more lines
647here
648'''
649 pstrm = PatchStream.process_text(text)
650 self.assertEqual(["Found 2 lines after TEST="], pstrm.commit.warn)
651
Simon Glassd85bb8f2022-01-29 14:14:09 -0700652 def test_blank_line_at_end(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600653 """Test detecting a blank line at the end of a file"""
654 text = '''This is a patch
655
656diff --git a/lib/fdtdec.c b/lib/fdtdec.c
657index c072e54..942244f 100644
658--- a/lib/fdtdec.c
659+++ b/lib/fdtdec.c
660@@ -1200,7 +1200,8 @@ int fdtdec_setup_mem_size_base(void)
661 }
662
663 gd->ram_size = (phys_size_t)(res.end - res.start + 1);
664- debug("%s: Initial DRAM size %llx\n", __func__, (u64)gd->ram_size);
665+ debug("%s: Initial DRAM size %llx\n", __func__,
666+ (unsigned long long)gd->ram_size);
667+
668diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c
669
670--
6712.7.4
672
673 '''
674 pstrm = PatchStream.process_text(text)
675 self.assertEqual(
676 ["Found possible blank line(s) at end of file 'lib/fdtdec.c'"],
677 pstrm.commit.warn)
Simon Glass1c1f2072020-10-29 21:46:34 -0600678
Simon Glassd85bb8f2022-01-29 14:14:09 -0700679 def test_no_upstream(self):
Simon Glass1c1f2072020-10-29 21:46:34 -0600680 """Test CountCommitsToBranch when there is no upstream"""
681 repo = self.make_git_tree()
682 target = repo.lookup_reference('refs/heads/base')
Simon Glass547cba62022-02-11 13:23:18 -0700683 # pylint doesn't seem to find this
684 # pylint: disable=E1101
Simon Glass1c1f2072020-10-29 21:46:34 -0600685 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
686
687 # Check that it can detect the current branch
Heinrich Schuchardtd01d6672023-04-20 20:07:29 +0200688 orig_dir = os.getcwd()
Simon Glass1c1f2072020-10-29 21:46:34 -0600689 try:
Simon Glass1c1f2072020-10-29 21:46:34 -0600690 os.chdir(self.gitdir)
691 with self.assertRaises(ValueError) as exc:
Simon Glass761648b2022-01-29 14:14:11 -0700692 gitutil.count_commits_to_branch(None)
Simon Glass1c1f2072020-10-29 21:46:34 -0600693 self.assertIn(
694 "Failed to determine upstream: fatal: no upstream configured for branch 'base'",
695 str(exc.exception))
696 finally:
697 os.chdir(orig_dir)
Simon Glass3db916d2020-10-29 21:46:35 -0600698
699 @staticmethod
Simon Glassf9b03cf2020-11-03 13:54:14 -0700700 def _fake_patchwork(url, subpath):
Simon Glass3db916d2020-10-29 21:46:35 -0600701 """Fake Patchwork server for the function below
702
703 This handles accessing a series, providing a list consisting of a
704 single patch
Simon Glassf9b03cf2020-11-03 13:54:14 -0700705
706 Args:
707 url (str): URL of patchwork server
708 subpath (str): URL subpath to use
Simon Glass3db916d2020-10-29 21:46:35 -0600709 """
710 re_series = re.match(r'series/(\d*)/$', subpath)
711 if re_series:
712 series_num = re_series.group(1)
713 if series_num == '1234':
714 return {'patches': [
715 {'id': '1', 'name': 'Some patch'}]}
716 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
717
Simon Glassd85bb8f2022-01-29 14:14:09 -0700718 def test_status_mismatch(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600719 """Test Patchwork patches not matching the series"""
720 series = Series()
721
722 with capture_sys_output() as (_, err):
Simon Glassf9b03cf2020-11-03 13:54:14 -0700723 status.collect_patches(series, 1234, None, self._fake_patchwork)
Simon Glass3db916d2020-10-29 21:46:35 -0600724 self.assertIn('Warning: Patchwork reports 1 patches, series has 0',
725 err.getvalue())
726
Simon Glassd85bb8f2022-01-29 14:14:09 -0700727 def test_status_read_patch(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600728 """Test handling a single patch in Patchwork"""
729 series = Series()
730 series.commits = [Commit('abcd')]
731
Simon Glassf9b03cf2020-11-03 13:54:14 -0700732 patches = status.collect_patches(series, 1234, None,
733 self._fake_patchwork)
Simon Glass3db916d2020-10-29 21:46:35 -0600734 self.assertEqual(1, len(patches))
735 patch = patches[0]
736 self.assertEqual('1', patch.id)
737 self.assertEqual('Some patch', patch.raw_subject)
738
Simon Glassd85bb8f2022-01-29 14:14:09 -0700739 def test_parse_subject(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600740 """Test parsing of the patch subject"""
741 patch = status.Patch('1')
742
743 # Simple patch not in a series
744 patch.parse_subject('Testing')
745 self.assertEqual('Testing', patch.raw_subject)
746 self.assertEqual('Testing', patch.subject)
747 self.assertEqual(1, patch.seq)
748 self.assertEqual(1, patch.count)
749 self.assertEqual(None, patch.prefix)
750 self.assertEqual(None, patch.version)
751
752 # First patch in a series
753 patch.parse_subject('[1/2] Testing')
754 self.assertEqual('[1/2] Testing', patch.raw_subject)
755 self.assertEqual('Testing', patch.subject)
756 self.assertEqual(1, patch.seq)
757 self.assertEqual(2, patch.count)
758 self.assertEqual(None, patch.prefix)
759 self.assertEqual(None, patch.version)
760
761 # Second patch in a series
762 patch.parse_subject('[2/2] Testing')
763 self.assertEqual('Testing', patch.subject)
764 self.assertEqual(2, patch.seq)
765 self.assertEqual(2, patch.count)
766 self.assertEqual(None, patch.prefix)
767 self.assertEqual(None, patch.version)
768
769 # RFC patch
770 patch.parse_subject('[RFC,3/7] Testing')
771 self.assertEqual('Testing', patch.subject)
772 self.assertEqual(3, patch.seq)
773 self.assertEqual(7, patch.count)
774 self.assertEqual('RFC', patch.prefix)
775 self.assertEqual(None, patch.version)
776
777 # Version patch
778 patch.parse_subject('[v2,3/7] Testing')
779 self.assertEqual('Testing', patch.subject)
780 self.assertEqual(3, patch.seq)
781 self.assertEqual(7, patch.count)
782 self.assertEqual(None, patch.prefix)
783 self.assertEqual('v2', patch.version)
784
785 # All fields
786 patch.parse_subject('[RESEND,v2,3/7] Testing')
787 self.assertEqual('Testing', patch.subject)
788 self.assertEqual(3, patch.seq)
789 self.assertEqual(7, patch.count)
790 self.assertEqual('RESEND', patch.prefix)
791 self.assertEqual('v2', patch.version)
792
793 # RFC only
794 patch.parse_subject('[RESEND] Testing')
795 self.assertEqual('Testing', patch.subject)
796 self.assertEqual(1, patch.seq)
797 self.assertEqual(1, patch.count)
798 self.assertEqual('RESEND', patch.prefix)
799 self.assertEqual(None, patch.version)
800
Simon Glassd85bb8f2022-01-29 14:14:09 -0700801 def test_compare_series(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600802 """Test operation of compare_with_series()"""
803 commit1 = Commit('abcd')
804 commit1.subject = 'Subject 1'
805 commit2 = Commit('ef12')
806 commit2.subject = 'Subject 2'
807 commit3 = Commit('3456')
808 commit3.subject = 'Subject 2'
809
810 patch1 = status.Patch('1')
811 patch1.subject = 'Subject 1'
812 patch2 = status.Patch('2')
813 patch2.subject = 'Subject 2'
814 patch3 = status.Patch('3')
815 patch3.subject = 'Subject 2'
816
817 series = Series()
818 series.commits = [commit1]
819 patches = [patch1]
820 patch_for_commit, commit_for_patch, warnings = (
821 status.compare_with_series(series, patches))
822 self.assertEqual(1, len(patch_for_commit))
823 self.assertEqual(patch1, patch_for_commit[0])
824 self.assertEqual(1, len(commit_for_patch))
825 self.assertEqual(commit1, commit_for_patch[0])
826
827 series.commits = [commit1]
828 patches = [patch1, patch2]
829 patch_for_commit, commit_for_patch, warnings = (
830 status.compare_with_series(series, patches))
831 self.assertEqual(1, len(patch_for_commit))
832 self.assertEqual(patch1, patch_for_commit[0])
833 self.assertEqual(1, len(commit_for_patch))
834 self.assertEqual(commit1, commit_for_patch[0])
835 self.assertEqual(["Cannot find commit for patch 2 ('Subject 2')"],
836 warnings)
837
838 series.commits = [commit1, commit2]
839 patches = [patch1]
840 patch_for_commit, commit_for_patch, warnings = (
841 status.compare_with_series(series, patches))
842 self.assertEqual(1, len(patch_for_commit))
843 self.assertEqual(patch1, patch_for_commit[0])
844 self.assertEqual(1, len(commit_for_patch))
845 self.assertEqual(commit1, commit_for_patch[0])
846 self.assertEqual(["Cannot find patch for commit 2 ('Subject 2')"],
847 warnings)
848
849 series.commits = [commit1, commit2, commit3]
850 patches = [patch1, patch2]
851 patch_for_commit, commit_for_patch, warnings = (
852 status.compare_with_series(series, patches))
853 self.assertEqual(2, len(patch_for_commit))
854 self.assertEqual(patch1, patch_for_commit[0])
855 self.assertEqual(patch2, patch_for_commit[1])
856 self.assertEqual(1, len(commit_for_patch))
857 self.assertEqual(commit1, commit_for_patch[0])
858 self.assertEqual(["Cannot find patch for commit 3 ('Subject 2')",
859 "Multiple commits match patch 2 ('Subject 2'):\n"
860 ' Subject 2\n Subject 2'],
861 warnings)
862
863 series.commits = [commit1, commit2]
864 patches = [patch1, patch2, patch3]
865 patch_for_commit, commit_for_patch, warnings = (
866 status.compare_with_series(series, patches))
867 self.assertEqual(1, len(patch_for_commit))
868 self.assertEqual(patch1, patch_for_commit[0])
869 self.assertEqual(2, len(commit_for_patch))
870 self.assertEqual(commit1, commit_for_patch[0])
871 self.assertEqual(["Multiple patches match commit 2 ('Subject 2'):\n"
872 ' Subject 2\n Subject 2',
873 "Cannot find commit for patch 3 ('Subject 2')"],
874 warnings)
875
Simon Glassf9b03cf2020-11-03 13:54:14 -0700876 def _fake_patchwork2(self, url, subpath):
Simon Glass3db916d2020-10-29 21:46:35 -0600877 """Fake Patchwork server for the function below
878
879 This handles accessing series, patches and comments, providing the data
880 in self.patches to the caller
Simon Glassf9b03cf2020-11-03 13:54:14 -0700881
882 Args:
883 url (str): URL of patchwork server
884 subpath (str): URL subpath to use
Simon Glass3db916d2020-10-29 21:46:35 -0600885 """
886 re_series = re.match(r'series/(\d*)/$', subpath)
887 re_patch = re.match(r'patches/(\d*)/$', subpath)
888 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
889 if re_series:
890 series_num = re_series.group(1)
891 if series_num == '1234':
892 return {'patches': self.patches}
893 elif re_patch:
894 patch_num = int(re_patch.group(1))
895 patch = self.patches[patch_num - 1]
896 return patch
897 elif re_comments:
898 patch_num = int(re_comments.group(1))
899 patch = self.patches[patch_num - 1]
900 return patch.comments
901 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
902
Simon Glassd85bb8f2022-01-29 14:14:09 -0700903 def test_find_new_responses(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600904 """Test operation of find_new_responses()"""
905 commit1 = Commit('abcd')
906 commit1.subject = 'Subject 1'
907 commit2 = Commit('ef12')
908 commit2.subject = 'Subject 2'
909
910 patch1 = status.Patch('1')
911 patch1.parse_subject('[1/2] Subject 1')
912 patch1.name = patch1.raw_subject
913 patch1.content = 'This is my patch content'
914 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
915
916 patch1.comments = [comment1a]
917
918 patch2 = status.Patch('2')
919 patch2.parse_subject('[2/2] Subject 2')
920 patch2.name = patch2.raw_subject
921 patch2.content = 'Some other patch content'
922 comment2a = {
923 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
924 (self.mary, self.leb)}
925 comment2b = {'content': 'Reviewed-by: %s' % self.fred}
926 patch2.comments = [comment2a, comment2b]
927
928 # This test works by setting up commits and patch for use by the fake
929 # Rest API function _fake_patchwork2(). It calls various functions in
930 # the status module after setting up tags in the commits, checking that
931 # things behaves as expected
932 self.commits = [commit1, commit2]
933 self.patches = [patch1, patch2]
934 count = 2
935 new_rtag_list = [None] * count
Simon Glass2112d072020-10-29 21:46:38 -0600936 review_list = [None, None]
Simon Glass3db916d2020-10-29 21:46:35 -0600937
938 # Check that the tags are picked up on the first patch
Simon Glass2112d072020-10-29 21:46:38 -0600939 status.find_new_responses(new_rtag_list, review_list, 0, commit1,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700940 patch1, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -0600941 self.assertEqual(new_rtag_list[0], {'Reviewed-by': {self.joe}})
942
943 # Now the second patch
Simon Glass2112d072020-10-29 21:46:38 -0600944 status.find_new_responses(new_rtag_list, review_list, 1, commit2,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700945 patch2, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -0600946 self.assertEqual(new_rtag_list[1], {
947 'Reviewed-by': {self.mary, self.fred},
948 'Tested-by': {self.leb}})
949
950 # Now add some tags to the commit, which means they should not appear as
951 # 'new' tags when scanning comments
952 new_rtag_list = [None] * count
953 commit1.rtags = {'Reviewed-by': {self.joe}}
Simon Glass2112d072020-10-29 21:46:38 -0600954 status.find_new_responses(new_rtag_list, review_list, 0, commit1,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700955 patch1, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -0600956 self.assertEqual(new_rtag_list[0], {})
957
958 # For the second commit, add Ed and Fred, so only Mary should be left
959 commit2.rtags = {
960 'Tested-by': {self.leb},
961 'Reviewed-by': {self.fred}}
Simon Glass2112d072020-10-29 21:46:38 -0600962 status.find_new_responses(new_rtag_list, review_list, 1, commit2,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700963 patch2, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -0600964 self.assertEqual(new_rtag_list[1], {'Reviewed-by': {self.mary}})
965
966 # Check that the output patches expectations:
967 # 1 Subject 1
968 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
969 # 2 Subject 2
970 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
971 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
972 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
973 # 1 new response available in patchwork
974
975 series = Series()
976 series.commits = [commit1, commit2]
Simon Glass02811582022-01-29 14:14:18 -0700977 terminal.set_print_test_mode()
Simon Glass2112d072020-10-29 21:46:38 -0600978 status.check_patchwork_status(series, '1234', None, None, False, False,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700979 None, self._fake_patchwork2)
Simon Glass02811582022-01-29 14:14:18 -0700980 lines = iter(terminal.get_print_test_lines())
Simon Glass3db916d2020-10-29 21:46:35 -0600981 col = terminal.Color()
982 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
983 next(lines))
984 self.assertEqual(
985 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
986 bright=False),
987 next(lines))
988 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE, bright=False),
989 next(lines))
990
991 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
992 next(lines))
993 self.assertEqual(
Simon Glass2112d072020-10-29 21:46:38 -0600994 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
Simon Glass3db916d2020-10-29 21:46:35 -0600995 bright=False),
996 next(lines))
Simon Glass2112d072020-10-29 21:46:38 -0600997 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE, bright=False),
Simon Glass3db916d2020-10-29 21:46:35 -0600998 next(lines))
999 self.assertEqual(
Simon Glass2112d072020-10-29 21:46:38 -06001000 terminal.PrintLine(' Tested-by: ', col.GREEN, newline=False,
Simon Glass3db916d2020-10-29 21:46:35 -06001001 bright=False),
1002 next(lines))
Simon Glass2112d072020-10-29 21:46:38 -06001003 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE, bright=False),
Simon Glass3db916d2020-10-29 21:46:35 -06001004 next(lines))
1005 self.assertEqual(
1006 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1007 next(lines))
1008 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
1009 next(lines))
1010 self.assertEqual(terminal.PrintLine(
Simon Glassd0a0a582020-10-29 21:46:36 -06001011 '1 new response available in patchwork (use -d to write them to a new branch)',
1012 None), next(lines))
1013
Simon Glassf9b03cf2020-11-03 13:54:14 -07001014 def _fake_patchwork3(self, url, subpath):
Simon Glassd0a0a582020-10-29 21:46:36 -06001015 """Fake Patchwork server for the function below
1016
1017 This handles accessing series, patches and comments, providing the data
1018 in self.patches to the caller
Simon Glassf9b03cf2020-11-03 13:54:14 -07001019
1020 Args:
1021 url (str): URL of patchwork server
1022 subpath (str): URL subpath to use
Simon Glassd0a0a582020-10-29 21:46:36 -06001023 """
1024 re_series = re.match(r'series/(\d*)/$', subpath)
1025 re_patch = re.match(r'patches/(\d*)/$', subpath)
1026 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
1027 if re_series:
1028 series_num = re_series.group(1)
1029 if series_num == '1234':
1030 return {'patches': self.patches}
1031 elif re_patch:
1032 patch_num = int(re_patch.group(1))
1033 patch = self.patches[patch_num - 1]
1034 return patch
1035 elif re_comments:
1036 patch_num = int(re_comments.group(1))
1037 patch = self.patches[patch_num - 1]
1038 return patch.comments
1039 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
1040
Simon Glassd85bb8f2022-01-29 14:14:09 -07001041 def test_create_branch(self):
Simon Glassd0a0a582020-10-29 21:46:36 -06001042 """Test operation of create_branch()"""
1043 repo = self.make_git_tree()
1044 branch = 'first'
1045 dest_branch = 'first2'
1046 count = 2
1047 gitdir = os.path.join(self.gitdir, '.git')
1048
1049 # Set up the test git tree. We use branch 'first' which has two commits
1050 # in it
1051 series = patchstream.get_metadata_for_list(branch, gitdir, count)
1052 self.assertEqual(2, len(series.commits))
1053
1054 patch1 = status.Patch('1')
1055 patch1.parse_subject('[1/2] %s' % series.commits[0].subject)
1056 patch1.name = patch1.raw_subject
1057 patch1.content = 'This is my patch content'
1058 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
1059
1060 patch1.comments = [comment1a]
1061
1062 patch2 = status.Patch('2')
1063 patch2.parse_subject('[2/2] %s' % series.commits[1].subject)
1064 patch2.name = patch2.raw_subject
1065 patch2.content = 'Some other patch content'
1066 comment2a = {
1067 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1068 (self.mary, self.leb)}
1069 comment2b = {
1070 'content': 'Reviewed-by: %s' % self.fred}
1071 patch2.comments = [comment2a, comment2b]
1072
1073 # This test works by setting up patches for use by the fake Rest API
1074 # function _fake_patchwork3(). The fake patch comments above should
1075 # result in new review tags that are collected and added to the commits
1076 # created in the destination branch.
1077 self.patches = [patch1, patch2]
1078 count = 2
1079
1080 # Expected output:
1081 # 1 i2c: I2C things
1082 # + Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1083 # 2 spi: SPI fixes
1084 # + Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1085 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1086 # + Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1087 # 4 new responses available in patchwork
1088 # 4 responses added from patchwork into new branch 'first2'
1089 # <unittest.result.TestResult run=8 errors=0 failures=0>
1090
Simon Glass02811582022-01-29 14:14:18 -07001091 terminal.set_print_test_mode()
Simon Glassd0a0a582020-10-29 21:46:36 -06001092 status.check_patchwork_status(series, '1234', branch, dest_branch,
Simon Glassf9b03cf2020-11-03 13:54:14 -07001093 False, False, None, self._fake_patchwork3,
1094 repo)
Simon Glass02811582022-01-29 14:14:18 -07001095 lines = terminal.get_print_test_lines()
Simon Glassd0a0a582020-10-29 21:46:36 -06001096 self.assertEqual(12, len(lines))
1097 self.assertEqual(
1098 "4 responses added from patchwork into new branch 'first2'",
1099 lines[11].text)
1100
1101 # Check that the destination branch has the new tags
1102 new_series = patchstream.get_metadata_for_list(dest_branch, gitdir,
1103 count)
1104 self.assertEqual(
1105 {'Reviewed-by': {self.joe}},
1106 new_series.commits[0].rtags)
1107 self.assertEqual(
1108 {'Tested-by': {self.leb},
1109 'Reviewed-by': {self.fred, self.mary}},
1110 new_series.commits[1].rtags)
1111
1112 # Now check the actual test of the first commit message. We expect to
1113 # see the new tags immediately below the old ones.
1114 stdout = patchstream.get_list(dest_branch, count=count, git_dir=gitdir)
1115 lines = iter([line.strip() for line in stdout.splitlines()
1116 if '-by:' in line])
1117
1118 # First patch should have the review tag
1119 self.assertEqual('Reviewed-by: %s' % self.joe, next(lines))
1120
1121 # Second patch should have the sign-off then the tested-by and two
1122 # reviewed-by tags
1123 self.assertEqual('Signed-off-by: %s' % self.leb, next(lines))
1124 self.assertEqual('Reviewed-by: %s' % self.fred, next(lines))
1125 self.assertEqual('Reviewed-by: %s' % self.mary, next(lines))
1126 self.assertEqual('Tested-by: %s' % self.leb, next(lines))
Simon Glassda8a2922020-10-29 21:46:37 -06001127
Simon Glassd85bb8f2022-01-29 14:14:09 -07001128 def test_parse_snippets(self):
Simon Glassda8a2922020-10-29 21:46:37 -06001129 """Test parsing of review snippets"""
1130 text = '''Hi Fred,
1131
1132This is a comment from someone.
1133
1134Something else
1135
1136On some recent date, Fred wrote:
1137> This is why I wrote the patch
1138> so here it is
1139
1140Now a comment about the commit message
1141A little more to say
1142
1143Even more
1144
1145> diff --git a/file.c b/file.c
1146> Some more code
1147> Code line 2
1148> Code line 3
1149> Code line 4
1150> Code line 5
1151> Code line 6
1152> Code line 7
1153> Code line 8
1154> Code line 9
1155
1156And another comment
1157
Simon Glassd85bb8f2022-01-29 14:14:09 -07001158> @@ -153,8 +143,13 @@ def check_patch(fname, show_types=False):
Simon Glassda8a2922020-10-29 21:46:37 -06001159> further down on the file
1160> and more code
1161> +Addition here
1162> +Another addition here
1163> codey
1164> more codey
1165
1166and another thing in same file
1167
1168> @@ -253,8 +243,13 @@
1169> with no function context
1170
1171one more thing
1172
1173> diff --git a/tools/patman/main.py b/tools/patman/main.py
1174> +line of code
1175now a very long comment in a different file
1176line2
1177line3
1178line4
1179line5
1180line6
1181line7
1182line8
1183'''
1184 pstrm = PatchStream.process_text(text, True)
1185 self.assertEqual([], pstrm.commit.warn)
1186
1187 # We expect to the filename and up to 5 lines of code context before
1188 # each comment. The 'On xxx wrote:' bit should be removed.
1189 self.assertEqual(
1190 [['Hi Fred,',
1191 'This is a comment from someone.',
1192 'Something else'],
1193 ['> This is why I wrote the patch',
1194 '> so here it is',
1195 'Now a comment about the commit message',
1196 'A little more to say', 'Even more'],
1197 ['> File: file.c', '> Code line 5', '> Code line 6',
1198 '> Code line 7', '> Code line 8', '> Code line 9',
1199 'And another comment'],
1200 ['> File: file.c',
Simon Glassd85bb8f2022-01-29 14:14:09 -07001201 '> Line: 153 / 143: def check_patch(fname, show_types=False):',
Simon Glassda8a2922020-10-29 21:46:37 -06001202 '> and more code', '> +Addition here', '> +Another addition here',
1203 '> codey', '> more codey', 'and another thing in same file'],
1204 ['> File: file.c', '> Line: 253 / 243',
1205 '> with no function context', 'one more thing'],
1206 ['> File: tools/patman/main.py', '> +line of code',
1207 'now a very long comment in a different file',
1208 'line2', 'line3', 'line4', 'line5', 'line6', 'line7', 'line8']],
1209 pstrm.snippets)
Simon Glass2112d072020-10-29 21:46:38 -06001210
Simon Glassd85bb8f2022-01-29 14:14:09 -07001211 def test_review_snippets(self):
Simon Glass2112d072020-10-29 21:46:38 -06001212 """Test showing of review snippets"""
1213 def _to_submitter(who):
1214 m_who = re.match('(.*) <(.*)>', who)
1215 return {
1216 'name': m_who.group(1),
1217 'email': m_who.group(2)
1218 }
1219
1220 commit1 = Commit('abcd')
1221 commit1.subject = 'Subject 1'
1222 commit2 = Commit('ef12')
1223 commit2.subject = 'Subject 2'
1224
1225 patch1 = status.Patch('1')
1226 patch1.parse_subject('[1/2] Subject 1')
1227 patch1.name = patch1.raw_subject
1228 patch1.content = 'This is my patch content'
1229 comment1a = {'submitter': _to_submitter(self.joe),
1230 'content': '''Hi Fred,
1231
1232On some date Fred wrote:
1233
1234> diff --git a/file.c b/file.c
1235> Some code
1236> and more code
1237
1238Here is my comment above the above...
1239
1240
1241Reviewed-by: %s
1242''' % self.joe}
1243
1244 patch1.comments = [comment1a]
1245
1246 patch2 = status.Patch('2')
1247 patch2.parse_subject('[2/2] Subject 2')
1248 patch2.name = patch2.raw_subject
1249 patch2.content = 'Some other patch content'
1250 comment2a = {
1251 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1252 (self.mary, self.leb)}
1253 comment2b = {'submitter': _to_submitter(self.fred),
1254 'content': '''Hi Fred,
1255
1256On some date Fred wrote:
1257
1258> diff --git a/tools/patman/commit.py b/tools/patman/commit.py
1259> @@ -41,6 +41,9 @@ class Commit:
1260> self.rtags = collections.defaultdict(set)
1261> self.warn = []
1262>
1263> + def __str__(self):
1264> + return self.subject
1265> +
Simon Glassd85bb8f2022-01-29 14:14:09 -07001266> def add_change(self, version, info):
Simon Glass2112d072020-10-29 21:46:38 -06001267> """Add a new change line to the change list for a version.
1268>
1269A comment
1270
1271Reviewed-by: %s
1272''' % self.fred}
1273 patch2.comments = [comment2a, comment2b]
1274
1275 # This test works by setting up commits and patch for use by the fake
1276 # Rest API function _fake_patchwork2(). It calls various functions in
1277 # the status module after setting up tags in the commits, checking that
1278 # things behaves as expected
1279 self.commits = [commit1, commit2]
1280 self.patches = [patch1, patch2]
1281
1282 # Check that the output patches expectations:
1283 # 1 Subject 1
1284 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1285 # 2 Subject 2
1286 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1287 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1288 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1289 # 1 new response available in patchwork
1290
1291 series = Series()
1292 series.commits = [commit1, commit2]
Simon Glass02811582022-01-29 14:14:18 -07001293 terminal.set_print_test_mode()
Simon Glass2112d072020-10-29 21:46:38 -06001294 status.check_patchwork_status(series, '1234', None, None, False, True,
Simon Glassf9b03cf2020-11-03 13:54:14 -07001295 None, self._fake_patchwork2)
Simon Glass02811582022-01-29 14:14:18 -07001296 lines = iter(terminal.get_print_test_lines())
Simon Glass2112d072020-10-29 21:46:38 -06001297 col = terminal.Color()
1298 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
1299 next(lines))
1300 self.assertEqual(
1301 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1302 next(lines))
1303 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE), next(lines))
1304
1305 self.assertEqual(terminal.PrintLine('Review: %s' % self.joe, col.RED),
1306 next(lines))
1307 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(lines))
1308 self.assertEqual(terminal.PrintLine('', None), next(lines))
1309 self.assertEqual(terminal.PrintLine(' > File: file.c', col.MAGENTA),
1310 next(lines))
1311 self.assertEqual(terminal.PrintLine(' > Some code', col.MAGENTA),
1312 next(lines))
1313 self.assertEqual(terminal.PrintLine(' > and more code', col.MAGENTA),
1314 next(lines))
1315 self.assertEqual(terminal.PrintLine(
1316 ' Here is my comment above the above...', None), next(lines))
1317 self.assertEqual(terminal.PrintLine('', None), next(lines))
1318
1319 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
1320 next(lines))
1321 self.assertEqual(
1322 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1323 next(lines))
1324 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE),
1325 next(lines))
1326 self.assertEqual(
1327 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1328 next(lines))
1329 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
1330 next(lines))
1331 self.assertEqual(
1332 terminal.PrintLine(' + Tested-by: ', col.GREEN, newline=False),
1333 next(lines))
1334 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE),
1335 next(lines))
1336
1337 self.assertEqual(terminal.PrintLine('Review: %s' % self.fred, col.RED),
1338 next(lines))
1339 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(lines))
1340 self.assertEqual(terminal.PrintLine('', None), next(lines))
1341 self.assertEqual(terminal.PrintLine(
1342 ' > File: tools/patman/commit.py', col.MAGENTA), next(lines))
1343 self.assertEqual(terminal.PrintLine(
1344 ' > Line: 41 / 41: class Commit:', col.MAGENTA), next(lines))
1345 self.assertEqual(terminal.PrintLine(
1346 ' > + return self.subject', col.MAGENTA), next(lines))
1347 self.assertEqual(terminal.PrintLine(
1348 ' > +', col.MAGENTA), next(lines))
1349 self.assertEqual(
Simon Glassd85bb8f2022-01-29 14:14:09 -07001350 terminal.PrintLine(' > def add_change(self, version, info):',
Simon Glass2112d072020-10-29 21:46:38 -06001351 col.MAGENTA),
1352 next(lines))
1353 self.assertEqual(terminal.PrintLine(
1354 ' > """Add a new change line to the change list for a version.',
1355 col.MAGENTA), next(lines))
1356 self.assertEqual(terminal.PrintLine(
1357 ' >', col.MAGENTA), next(lines))
1358 self.assertEqual(terminal.PrintLine(
1359 ' A comment', None), next(lines))
1360 self.assertEqual(terminal.PrintLine('', None), next(lines))
1361
1362 self.assertEqual(terminal.PrintLine(
1363 '4 new responses available in patchwork (use -d to write them to a new branch)',
1364 None), next(lines))
Simon Glass6a222e62021-08-01 16:02:39 -06001365
Simon Glassd85bb8f2022-01-29 14:14:09 -07001366 def test_insert_tags(self):
Simon Glass6a222e62021-08-01 16:02:39 -06001367 """Test inserting of review tags"""
1368 msg = '''first line
1369second line.'''
1370 tags = [
1371 'Reviewed-by: Bin Meng <bmeng.cn@gmail.com>',
1372 'Tested-by: Bin Meng <bmeng.cn@gmail.com>'
1373 ]
1374 signoff = 'Signed-off-by: Simon Glass <sjg@chromium.com>'
1375 tag_str = '\n'.join(tags)
1376
1377 new_msg = patchstream.insert_tags(msg, tags)
1378 self.assertEqual(msg + '\n\n' + tag_str, new_msg)
1379
1380 new_msg = patchstream.insert_tags(msg + '\n', tags)
1381 self.assertEqual(msg + '\n\n' + tag_str, new_msg)
1382
1383 msg += '\n\n' + signoff
1384 new_msg = patchstream.insert_tags(msg, tags)
1385 self.assertEqual(msg + '\n' + tag_str, new_msg)