blob: 9f3ab50fa4e0fcb37c46ca9cf94281ebcdf354f6 [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
Simon Glass25b91c12025-04-29 07:22:19 -06009import asyncio
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -050010import contextlib
Simon Glassdf1bc5c2017-05-29 15:31:31 -060011import os
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050012import pathlib
Simon Glassdf1bc5c2017-05-29 15:31:31 -060013import re
14import shutil
15import sys
16import tempfile
17import unittest
18
Simon Glass30b21792025-05-08 05:36:20 +020019import pygit2
20
21from u_boot_pylib import gitutil
22from u_boot_pylib import terminal
23from u_boot_pylib import tools
Simon Glass3db916d2020-10-29 21:46:35 -060024
25from patman.commit import Commit
Simon Glass54f1c5b2020-07-05 21:41:50 -060026from patman import control
Simon Glassa997ea52020-04-17 18:09:04 -060027from patman import patchstream
Simon Glassa7fadab2020-10-29 21:46:26 -060028from patman.patchstream import PatchStream
Simon Glass232eefd2025-04-29 07:22:14 -060029from patman import patchwork
Simon Glassc0257982025-04-29 07:22:11 -060030from patman import send
Simon Glass3db916d2020-10-29 21:46:35 -060031from patman.series import Series
Tom Rini488ea972021-02-26 07:52:31 -050032from patman import status
Simon Glassdf1bc5c2017-05-29 15:31:31 -060033
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -050034PATMAN_DIR = pathlib.Path(__file__).parent
35TEST_DATA_DIR = PATMAN_DIR / 'test/'
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050036
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050037
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -050038@contextlib.contextmanager
39def directory_excursion(directory):
40 """Change directory to `directory` for a limited to the context block."""
41 current = os.getcwd()
42 try:
43 os.chdir(directory)
44 yield
45 finally:
46 os.chdir(current)
47
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050048
Simon Glassdf1bc5c2017-05-29 15:31:31 -060049class TestFunctional(unittest.TestCase):
Simon Glasseb209e52020-10-29 21:46:15 -060050 """Functional tests for checking that patman behaves correctly"""
Simon Glass06202d62020-10-29 21:46:27 -060051 leb = (b'Lord Edmund Blackadd\xc3\xabr <weasel@blackadder.org>'.
52 decode('utf-8'))
Simon Glass3b762cc2020-10-29 21:46:28 -060053 fred = 'Fred Bloggs <f.bloggs@napier.net>'
54 joe = 'Joe Bloggs <joe@napierwallies.co.nz>'
55 mary = 'Mary Bloggs <mary@napierwallies.co.nz>'
Simon Glass3db916d2020-10-29 21:46:35 -060056 commits = None
57 patches = None
Simon Glassed831d12025-04-29 07:22:10 -060058 verbosity = False
59 preserve_outdirs = False
60
Simon Glassb8ca4692025-05-08 05:26:16 +020061 # Fake patchwork info for testing
62 SERIES_ID_SECOND_V1 = 456
63 TITLE_SECOND = 'Series for my board'
64
Simon Glassed831d12025-04-29 07:22:10 -060065 @classmethod
66 def setup_test_args(cls, preserve_indir=False, preserve_outdirs=False,
67 toolpath=None, verbosity=None, no_capture=False):
68 """Accept arguments controlling test execution
69
70 Args:
71 preserve_indir: not used
72 preserve_outdir: Preserve the output directories used by tests.
73 Each test has its own, so this is normally only useful when
74 running a single test.
75 toolpath: not used
76 """
77 cls.preserve_outdirs = preserve_outdirs
78 cls.toolpath = toolpath
79 cls.verbosity = verbosity
80 cls.no_capture = no_capture
Simon Glass06202d62020-10-29 21:46:27 -060081
Simon Glassdf1bc5c2017-05-29 15:31:31 -060082 def setUp(self):
83 self.tmpdir = tempfile.mkdtemp(prefix='patman.')
Simon Glass41dfb6e2025-05-08 05:13:35 +020084 self.gitdir = os.path.join(self.tmpdir, '.git')
Simon Glass54f1c5b2020-07-05 21:41:50 -060085 self.repo = None
Simon Glassdf1bc5c2017-05-29 15:31:31 -060086
87 def tearDown(self):
Simon Glassed831d12025-04-29 07:22:10 -060088 if self.preserve_outdirs:
89 print(f'Output dir: {self.tmpdir}')
90 else:
91 shutil.rmtree(self.tmpdir)
Simon Glass02811582022-01-29 14:14:18 -070092 terminal.set_print_test_mode(False)
Simon Glassdf1bc5c2017-05-29 15:31:31 -060093
94 @staticmethod
Simon Glasseb209e52020-10-29 21:46:15 -060095 def _get_path(fname):
96 """Get the path to a test file
97
98 Args:
99 fname (str): Filename to obtain
100
101 Returns:
102 str: Full path to file in the test directory
103 """
Maxim Cournoyer0331edb2022-12-19 17:32:39 -0500104 return TEST_DATA_DIR / fname
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600105
106 @classmethod
Simon Glasseb209e52020-10-29 21:46:15 -0600107 def _get_text(cls, fname):
108 """Read a file as text
109
110 Args:
111 fname (str): Filename to read
112
113 Returns:
114 str: Contents of file
115 """
116 return open(cls._get_path(fname), encoding='utf-8').read()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600117
118 @classmethod
Simon Glasseb209e52020-10-29 21:46:15 -0600119 def _get_patch_name(cls, subject):
120 """Get the filename of a patch given its subject
121
122 Args:
123 subject (str): Patch subject
124
125 Returns:
126 str: Filename for that patch
127 """
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600128 fname = re.sub('[ :]', '-', subject)
129 return fname.replace('--', '-')
130
Simon Glasseb209e52020-10-29 21:46:15 -0600131 def _create_patches_for_test(self, series):
132 """Create patch files for use by tests
133
134 This copies patch files from the test directory as needed by the series
135
136 Args:
137 series (Series): Series containing commits to convert
138
139 Returns:
140 tuple:
141 str: Cover-letter filename, or None if none
142 fname_list: list of str, each a patch filename
143 """
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600144 cover_fname = None
145 fname_list = []
146 for i, commit in enumerate(series.commits):
Simon Glasseb209e52020-10-29 21:46:15 -0600147 clean_subject = self._get_patch_name(commit.subject)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600148 src_fname = '%04d-%s.patch' % (i + 1, clean_subject[:52])
149 fname = os.path.join(self.tmpdir, src_fname)
Simon Glasseb209e52020-10-29 21:46:15 -0600150 shutil.copy(self._get_path(src_fname), fname)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600151 fname_list.append(fname)
152 if series.get('cover'):
153 src_fname = '0000-cover-letter.patch'
154 cover_fname = os.path.join(self.tmpdir, src_fname)
155 fname = os.path.join(self.tmpdir, src_fname)
Simon Glasseb209e52020-10-29 21:46:15 -0600156 shutil.copy(self._get_path(src_fname), fname)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600157
158 return cover_fname, fname_list
159
Simon Glassd85bb8f2022-01-29 14:14:09 -0700160 def test_basic(self):
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600161 """Tests the basic flow of patman
162
163 This creates a series from some hard-coded patches build from a simple
164 tree with the following metadata in the top commit:
165
166 Series-to: u-boot
167 Series-prefix: RFC
Sean Andersondc1cd132021-10-22 19:07:04 -0400168 Series-postfix: some-branch
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600169 Series-cc: Stefan Brüns <stefan.bruens@rwth-aachen.de>
170 Cover-letter-cc: Lord Mëlchett <clergy@palace.gov>
Sean Andersoncf13b862020-05-04 16:28:36 -0400171 Series-version: 3
172 Patch-cc: fred
173 Series-process-log: sort, uniq
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600174 Series-changes: 4
175 - Some changes
Sean Andersoncf13b862020-05-04 16:28:36 -0400176 - Multi
177 line
178 change
179
180 Commit-changes: 2
181 - Changes only for this commit
182
Simon Glassf1aab6f2025-04-29 07:22:07 -0600183 Cover-changes: 4
Sean Andersoncf13b862020-05-04 16:28:36 -0400184 - Some notes for the cover letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600185
186 Cover-letter:
187 test: A test patch series
188 This is a test of how the cover
Sean Andersoncf13b862020-05-04 16:28:36 -0400189 letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600190 works
191 END
192
193 and this in the first commit:
194
Sean Andersoncf13b862020-05-04 16:28:36 -0400195 Commit-changes: 2
196 - second revision change
197
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600198 Series-notes:
199 some notes
200 about some things
201 from the first commit
202 END
203
204 Commit-notes:
205 Some notes about
206 the first commit
207 END
208
209 with the following commands:
210
211 git log -n2 --reverse >/path/to/tools/patman/test/test01.txt
212 git format-patch --subject-prefix RFC --cover-letter HEAD~2
213 mv 00* /path/to/tools/patman/test
214
215 It checks these aspects:
216 - git log can be processed by patchstream
217 - emailing patches uses the correct command
218 - CC file has information on each commit
219 - cover letter has the expected text and subject
220 - each patch has the correct subject
221 - dry-run information prints out correctly
222 - unicode is handled correctly
Sean Andersondc1cd132021-10-22 19:07:04 -0400223 - Series-to, Series-cc, Series-prefix, Series-postfix, Cover-letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600224 - Cover-letter-cc, Series-version, Series-changes, Series-notes
225 - Commit-notes
226 """
227 process_tags = True
Simon Glass1f975b92021-01-23 08:56:15 -0700228 ignore_bad_tags = False
Simon Glassb3080ec2025-05-08 04:58:49 +0200229 stefan = (b'Stefan Br\xc3\xbcns <stefan.bruens@rwth-aachen.de>'
230 .decode('utf-8'))
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600231 rick = 'Richard III <richard@palace.gov>'
Simon Glass4f817892019-05-14 15:53:53 -0600232 mel = b'Lord M\xc3\xablchett <clergy@palace.gov>'.decode('utf-8')
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600233 add_maintainers = [stefan, rick]
234 dry_run = True
235 in_reply_to = mel
236 count = 2
Simon Glass5efa3662025-04-07 22:51:45 +1200237 alias = {
Simon Glass95745aa2020-10-29 21:46:13 -0600238 'fdt': ['simon'],
239 'u-boot': ['u-boot@lists.denx.de'],
Simon Glass06202d62020-10-29 21:46:27 -0600240 'simon': [self.leb],
Simon Glass3b762cc2020-10-29 21:46:28 -0600241 'fred': [self.fred],
Sean Anderson25978092024-04-18 22:36:31 -0400242 'joe': [self.joe],
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600243 }
244
Simon Glasseb209e52020-10-29 21:46:15 -0600245 text = self._get_text('test01.txt')
Simon Glass93f61c02020-10-29 21:46:19 -0600246 series = patchstream.get_metadata_for_test(text)
Simon Glass414f1e02025-02-27 12:27:30 -0700247 series.base_commit = Commit('1a44532')
248 series.branch = 'mybranch'
Simon Glasseb209e52020-10-29 21:46:15 -0600249 cover_fname, args = self._create_patches_for_test(series)
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500250 get_maintainer_script = str(pathlib.Path(__file__).parent.parent.parent
251 / 'get_maintainer.pl') + ' --norolestats'
Simon Glass14d64e32025-04-29 07:21:59 -0600252 with terminal.capture() as out:
Simon Glass93f61c02020-10-29 21:46:19 -0600253 patchstream.fix_patches(series, args)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600254 if cover_fname and series.get('cover'):
Simon Glass93f61c02020-10-29 21:46:19 -0600255 patchstream.insert_cover_letter(cover_fname, series, count)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600256 series.DoChecks()
257 cc_file = series.MakeCcFile(process_tags, cover_fname,
Chris Packhamb84fb482018-06-07 20:45:06 +1200258 not ignore_bad_tags, add_maintainers,
Simon Glass9938b7b2025-04-07 22:51:46 +1200259 None, get_maintainer_script, alias)
Simon Glass761648b2022-01-29 14:14:11 -0700260 cmd = gitutil.email_patches(
Simon Glass95745aa2020-10-29 21:46:13 -0600261 series, cover_fname, args, dry_run, not ignore_bad_tags,
Simon Glass5efa3662025-04-07 22:51:45 +1200262 cc_file, alias, in_reply_to=in_reply_to, thread=None)
Simon Glass32f12a7e2025-04-07 22:51:47 +1200263 series.ShowActions(args, cmd, process_tags, alias)
Simon Glassf544a2d2019-10-31 07:42:51 -0600264 cc_lines = open(cc_file, encoding='utf-8').read().splitlines()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600265 os.remove(cc_file)
266
Simon Glassb3080ec2025-05-08 04:58:49 +0200267 itr = iter(out[0].getvalue().splitlines())
Simon Glass42e3d392020-10-29 21:46:29 -0600268 self.assertEqual('Cleaned %s patches' % len(series.commits),
Simon Glassb3080ec2025-05-08 04:58:49 +0200269 next(itr))
270 self.assertEqual('Change log missing for v2', next(itr))
271 self.assertEqual('Change log missing for v3', next(itr))
272 self.assertEqual('Change log for unknown version v4', next(itr))
273 self.assertEqual("Alias 'pci' not found", next(itr))
274 while next(itr) != 'Cc processing complete':
Simon Glass620639c2023-03-08 10:52:54 -0800275 pass
Simon Glassb3080ec2025-05-08 04:58:49 +0200276 self.assertIn('Dry run', next(itr))
277 self.assertEqual('', next(itr))
278 self.assertIn('Send a total of %d patches' % count, next(itr))
279 prev = next(itr)
Simon Glass30b21792025-05-08 05:36:20 +0200280 for i in range(len(series.commits)):
Simon Glass42e3d392020-10-29 21:46:29 -0600281 self.assertEqual(' %s' % args[i], prev)
282 while True:
Simon Glassb3080ec2025-05-08 04:58:49 +0200283 prev = next(itr)
Simon Glass42e3d392020-10-29 21:46:29 -0600284 if 'Cc:' not in prev:
285 break
286 self.assertEqual('To: u-boot@lists.denx.de', prev)
Simon Glassb3080ec2025-05-08 04:58:49 +0200287 self.assertEqual('Cc: %s' % stefan, next(itr))
288 self.assertEqual('Version: 3', next(itr))
289 self.assertEqual('Prefix:\t RFC', next(itr))
290 self.assertEqual('Postfix:\t some-branch', next(itr))
291 self.assertEqual('Cover: 4 lines', next(itr))
292 self.assertEqual(' Cc: %s' % self.fred, next(itr))
293 self.assertEqual(' Cc: %s' % self.joe, next(itr))
Simon Glass9dfb3112020-11-08 20:36:18 -0700294 self.assertEqual(' Cc: %s' % self.leb,
Simon Glassb3080ec2025-05-08 04:58:49 +0200295 next(itr))
296 self.assertEqual(' Cc: %s' % mel, next(itr))
297 self.assertEqual(' Cc: %s' % rick, next(itr))
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600298 expected = ('Git command: git send-email --annotate '
Simon Glassa8ba0792025-05-08 04:38:30 +0200299 '--in-reply-to="%s" --to u-boot@lists.denx.de '
Simon Glass1ee91c12020-11-03 13:54:10 -0700300 '--cc "%s" --cc-cmd "%s send --cc-cmd %s" %s %s'
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600301 % (in_reply_to, stefan, sys.argv[0], cc_file, cover_fname,
Simon Glass4f817892019-05-14 15:53:53 -0600302 ' '.join(args)))
Simon Glassb3080ec2025-05-08 04:58:49 +0200303 self.assertEqual(expected, next(itr))
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600304
Simon Glass9dfb3112020-11-08 20:36:18 -0700305 self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)), cc_lines[0])
Simon Glass95745aa2020-10-29 21:46:13 -0600306 self.assertEqual(
Sean Anderson25978092024-04-18 22:36:31 -0400307 '%s %s\0%s\0%s\0%s\0%s' % (args[1], self.fred, self.joe, self.leb,
308 rick, stefan),
Simon Glass9dfb3112020-11-08 20:36:18 -0700309 cc_lines[1])
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600310
311 expected = '''
312This is a test of how the cover
Sean Andersoncf13b862020-05-04 16:28:36 -0400313letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600314works
315
316some notes
317about some things
318from the first commit
319
320Changes in v4:
Sean Andersoncf13b862020-05-04 16:28:36 -0400321- Multi
322 line
323 change
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600324- Some changes
Sean Andersoncf13b862020-05-04 16:28:36 -0400325- Some notes for the cover letter
Sean Andersone45678c2024-04-18 22:36:32 -0400326- fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600327
328Simon Glass (2):
329 pci: Correct cast for sandbox
Siva Durga Prasad Paladugub3d55ea2018-07-16 15:56:11 +0530330 fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600331
332 cmd/pci.c | 3 ++-
333 fs/fat/fat.c | 1 +
334 lib/efi_loader/efi_memory.c | 1 +
335 lib/fdtdec.c | 3 ++-
336 4 files changed, 6 insertions(+), 2 deletions(-)
337
338--\x20
3392.7.4
340
Simon Glass414f1e02025-02-27 12:27:30 -0700341base-commit: 1a44532
342branch: mybranch
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600343'''
Simon Glassf544a2d2019-10-31 07:42:51 -0600344 lines = open(cover_fname, encoding='utf-8').read().splitlines()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600345 self.assertEqual(
Sean Andersondc1cd132021-10-22 19:07:04 -0400346 'Subject: [RFC PATCH some-branch v3 0/2] test: A test patch series',
Simon Glass95745aa2020-10-29 21:46:13 -0600347 lines[3])
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600348 self.assertEqual(expected.splitlines(), lines[7:])
349
350 for i, fname in enumerate(args):
Simon Glassf544a2d2019-10-31 07:42:51 -0600351 lines = open(fname, encoding='utf-8').read().splitlines()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600352 subject = [line for line in lines if line.startswith('Subject')]
353 self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count),
354 subject[0][:18])
Sean Andersoncf13b862020-05-04 16:28:36 -0400355
356 # Check that we got our commit notes
357 start = 0
358 expected = ''
359
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600360 if i == 0:
Sean Andersoncf13b862020-05-04 16:28:36 -0400361 start = 17
362 expected = '''---
363Some notes about
364the first commit
365
366(no changes since v2)
367
368Changes in v2:
369- second revision change'''
370 elif i == 1:
371 start = 17
372 expected = '''---
373
374Changes in v4:
375- Multi
376 line
377 change
Sean Andersone45678c2024-04-18 22:36:32 -0400378- New
Sean Andersoncf13b862020-05-04 16:28:36 -0400379- Some changes
380
381Changes in v2:
382- Changes only for this commit'''
383
384 if expected:
385 expected = expected.splitlines()
386 self.assertEqual(expected, lines[start:(start+len(expected))])
Simon Glass54f1c5b2020-07-05 21:41:50 -0600387
Simon Glassda1a6ec2025-03-28 07:02:20 -0600388 def test_base_commit(self):
389 """Test adding a base commit with no cover letter"""
390 orig_text = self._get_text('test01.txt')
Simon Glass30b21792025-05-08 05:36:20 +0200391 pos = orig_text.index(
392 'commit 5ab48490f03051875ab13d288a4bf32b507d76fd')
Simon Glassda1a6ec2025-03-28 07:02:20 -0600393 text = orig_text[:pos]
394 series = patchstream.get_metadata_for_test(text)
395 series.base_commit = Commit('1a44532')
396 series.branch = 'mybranch'
397 cover_fname, args = self._create_patches_for_test(series)
398 self.assertFalse(cover_fname)
Simon Glass14d64e32025-04-29 07:21:59 -0600399 with terminal.capture() as out:
Simon Glassda1a6ec2025-03-28 07:02:20 -0600400 patchstream.fix_patches(series, args, insert_base_commit=True)
401 self.assertEqual('Cleaned 1 patch\n', out[0].getvalue())
402 lines = tools.read_file(args[0], binary=False).splitlines()
403 pos = lines.index('-- ')
404
405 # We expect these lines at the end:
406 # -- (with trailing space)
407 # 2.7.4
408 # (empty)
409 # base-commit: xxx
410 # branch: xxx
411 self.assertEqual('base-commit: 1a44532', lines[pos + 3])
412 self.assertEqual('branch: mybranch', lines[pos + 4])
413
Simon Glass54f1c5b2020-07-05 21:41:50 -0600414 def make_commit_with_file(self, subject, body, fname, text):
415 """Create a file and add it to the git repo with a new commit
416
417 Args:
418 subject (str): Subject for the commit
419 body (str): Body text of the commit
420 fname (str): Filename of file to create
421 text (str): Text to put into the file
422 """
Simon Glass41dfb6e2025-05-08 05:13:35 +0200423 path = os.path.join(self.tmpdir, fname)
Simon Glass80025522022-01-29 14:14:04 -0700424 tools.write_file(path, text, binary=False)
Simon Glass54f1c5b2020-07-05 21:41:50 -0600425 index = self.repo.index
426 index.add(fname)
Simon Glass547cba62022-02-11 13:23:18 -0700427 # pylint doesn't seem to find this
428 # pylint: disable=E1101
Simon Glass95745aa2020-10-29 21:46:13 -0600429 author = pygit2.Signature('Test user', 'test@email.com')
Simon Glass54f1c5b2020-07-05 21:41:50 -0600430 committer = author
431 tree = index.write_tree()
432 message = subject + '\n' + body
433 self.repo.create_commit('HEAD', author, committer, message, tree,
434 [self.repo.head.target])
435
436 def make_git_tree(self):
437 """Make a simple git tree suitable for testing
438
439 It has three branches:
440 'base' has two commits: PCI, main
441 'first' has base as upstream and two more commits: I2C, SPI
442 'second' has base as upstream and three more: video, serial, bootm
443
444 Returns:
Simon Glasseb209e52020-10-29 21:46:15 -0600445 pygit2.Repository: repository
Simon Glass54f1c5b2020-07-05 21:41:50 -0600446 """
447 repo = pygit2.init_repository(self.gitdir)
448 self.repo = repo
449 new_tree = repo.TreeBuilder().write()
450
Simon Glass7cb21f02025-05-08 05:02:07 +0200451 common = ['git', f'--git-dir={self.gitdir}', 'config']
452 tools.run(*(common + ['user.name', 'Dummy']), cwd=self.gitdir)
453 tools.run(*(common + ['user.email', 'dumdum@dummy.com']),
454 cwd=self.gitdir)
455
Simon Glass547cba62022-02-11 13:23:18 -0700456 # pylint doesn't seem to find this
457 # pylint: disable=E1101
Simon Glass54f1c5b2020-07-05 21:41:50 -0600458 author = pygit2.Signature('Test user', 'test@email.com')
459 committer = author
Simon Glasseb209e52020-10-29 21:46:15 -0600460 _ = repo.create_commit('HEAD', author, committer, 'Created master',
461 new_tree, [])
Simon Glass54f1c5b2020-07-05 21:41:50 -0600462
463 self.make_commit_with_file('Initial commit', '''
464Add a README
465
466''', 'README', '''This is the README file
467describing this project
468in very little detail''')
469
470 self.make_commit_with_file('pci: PCI implementation', '''
471Here is a basic PCI implementation
472
473''', 'pci.c', '''This is a file
474it has some contents
475and some more things''')
476 self.make_commit_with_file('main: Main program', '''
477Hello here is the second commit.
478''', 'main.c', '''This is the main file
479there is very little here
480but we can always add more later
481if we want to
482
483Series-to: u-boot
484Series-cc: Barry Crump <bcrump@whataroa.nz>
485''')
486 base_target = repo.revparse_single('HEAD')
487 self.make_commit_with_file('i2c: I2C things', '''
488This has some stuff to do with I2C
489''', 'i2c.c', '''And this is the file contents
490with some I2C-related things in it''')
491 self.make_commit_with_file('spi: SPI fixes', '''
492SPI needs some fixes
493and here they are
Simon Glassd0a0a582020-10-29 21:46:36 -0600494
495Signed-off-by: %s
496
497Series-to: u-boot
498Commit-notes:
499title of the series
500This is the cover letter for the series
501with various details
502END
503''' % self.leb, 'spi.c', '''Some fixes for SPI in this
Simon Glass54f1c5b2020-07-05 21:41:50 -0600504file to make SPI work
505better than before''')
506 first_target = repo.revparse_single('HEAD')
507
508 target = repo.revparse_single('HEAD~2')
Simon Glass547cba62022-02-11 13:23:18 -0700509 # pylint doesn't seem to find this
510 # pylint: disable=E1101
Simon Glass573abf82025-05-08 05:23:41 +0200511 repo.reset(target.oid, pygit2.enums.ResetMode.HARD)
Simon Glass54f1c5b2020-07-05 21:41:50 -0600512 self.make_commit_with_file('video: Some video improvements', '''
513Fix up the video so that
514it looks more purple. Purple is
515a very nice colour.
516''', 'video.c', '''More purple here
517Purple and purple
518Even more purple
519Could not be any more purple''')
Simon Glassb8ca4692025-05-08 05:26:16 +0200520 self.make_commit_with_file('serial: Add a serial driver', f'''
Simon Glass54f1c5b2020-07-05 21:41:50 -0600521Here is the serial driver
522for my chip.
523
524Cover-letter:
Simon Glassb8ca4692025-05-08 05:26:16 +0200525{self.TITLE_SECOND}
Simon Glass54f1c5b2020-07-05 21:41:50 -0600526This series implements support
527for my glorious board.
528END
Simon Glassb8ca4692025-05-08 05:26:16 +0200529Series-to: u-boot
530Series-links: {self.SERIES_ID_SECOND_V1}
Simon Glass54f1c5b2020-07-05 21:41:50 -0600531''', 'serial.c', '''The code for the
532serial driver is here''')
533 self.make_commit_with_file('bootm: Make it boot', '''
534This makes my board boot
535with a fix to the bootm
536command
537''', 'bootm.c', '''Fix up the bootm
538command to make the code as
539complicated as possible''')
540 second_target = repo.revparse_single('HEAD')
541
542 repo.branches.local.create('first', first_target)
543 repo.config.set_multivar('branch.first.remote', '', '.')
544 repo.config.set_multivar('branch.first.merge', '', 'refs/heads/base')
545
546 repo.branches.local.create('second', second_target)
547 repo.config.set_multivar('branch.second.remote', '', '.')
548 repo.config.set_multivar('branch.second.merge', '', 'refs/heads/base')
549
550 repo.branches.local.create('base', base_target)
Simon Glass573abf82025-05-08 05:23:41 +0200551
552 target = repo.lookup_reference('refs/heads/first')
553 repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
554 target = repo.revparse_single('HEAD')
555 repo.reset(target.oid, pygit2.enums.ResetMode.HARD)
556
557 self.assertFalse(gitutil.check_dirty(self.gitdir, self.tmpdir))
Simon Glass54f1c5b2020-07-05 21:41:50 -0600558 return repo
559
Simon Glassd85bb8f2022-01-29 14:14:09 -0700560 def test_branch(self):
Simon Glass54f1c5b2020-07-05 21:41:50 -0600561 """Test creating patches from a branch"""
562 repo = self.make_git_tree()
563 target = repo.lookup_reference('refs/heads/first')
Simon Glass547cba62022-02-11 13:23:18 -0700564 # pylint doesn't seem to find this
565 # pylint: disable=E1101
Simon Glass54f1c5b2020-07-05 21:41:50 -0600566 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
567 control.setup()
Heinrich Schuchardtd01d6672023-04-20 20:07:29 +0200568 orig_dir = os.getcwd()
Simon Glass54f1c5b2020-07-05 21:41:50 -0600569 try:
Simon Glass41dfb6e2025-05-08 05:13:35 +0200570 os.chdir(self.tmpdir)
Simon Glass54f1c5b2020-07-05 21:41:50 -0600571
572 # Check that it can detect the current branch
Simon Glass761648b2022-01-29 14:14:11 -0700573 self.assertEqual(2, gitutil.count_commits_to_branch(None))
Simon Glass54f1c5b2020-07-05 21:41:50 -0600574 col = terminal.Color()
Simon Glass14d64e32025-04-29 07:21:59 -0600575 with terminal.capture() as _:
Simon Glassc0257982025-04-29 07:22:11 -0600576 _, cover_fname, patch_files = send.prepare_patches(
Simon Glassb3bf4e12020-07-05 21:41:52 -0600577 col, branch=None, count=-1, start=0, end=0,
Philipp Tomsich858531a2020-11-24 18:14:52 +0100578 ignore_binary=False, signoff=True)
Simon Glass54f1c5b2020-07-05 21:41:50 -0600579 self.assertIsNone(cover_fname)
580 self.assertEqual(2, len(patch_files))
Simon Glass2eb4da72020-07-05 21:41:51 -0600581
582 # Check that it can detect a different branch
Simon Glass761648b2022-01-29 14:14:11 -0700583 self.assertEqual(3, gitutil.count_commits_to_branch('second'))
Simon Glass14d64e32025-04-29 07:21:59 -0600584 with terminal.capture() as _:
Simon Glass30b21792025-05-08 05:36:20 +0200585 _, cover_fname, patch_files = send.prepare_patches(
Simon Glassb3bf4e12020-07-05 21:41:52 -0600586 col, branch='second', count=-1, start=0, end=0,
Philipp Tomsich858531a2020-11-24 18:14:52 +0100587 ignore_binary=False, signoff=True)
Simon Glass2eb4da72020-07-05 21:41:51 -0600588 self.assertIsNotNone(cover_fname)
589 self.assertEqual(3, len(patch_files))
Simon Glassb3bf4e12020-07-05 21:41:52 -0600590
Simon Glass414f1e02025-02-27 12:27:30 -0700591 cover = tools.read_file(cover_fname, binary=False)
592 lines = cover.splitlines()[-2:]
593 base = repo.lookup_reference('refs/heads/base').target
594 self.assertEqual(f'base-commit: {base}', lines[0])
595 self.assertEqual('branch: second', lines[1])
596
Simon Glassda1a6ec2025-03-28 07:02:20 -0600597 # Make sure that the base-commit is not present when it is in the
598 # cover letter
599 for fname in patch_files:
600 self.assertNotIn(b'base-commit:', tools.read_file(fname))
601
Simon Glassb3bf4e12020-07-05 21:41:52 -0600602 # Check that it can skip patches at the end
Simon Glass14d64e32025-04-29 07:21:59 -0600603 with terminal.capture() as _:
Simon Glassc0257982025-04-29 07:22:11 -0600604 _, cover_fname, patch_files = send.prepare_patches(
Simon Glassb3bf4e12020-07-05 21:41:52 -0600605 col, branch='second', count=-1, start=0, end=1,
Philipp Tomsich858531a2020-11-24 18:14:52 +0100606 ignore_binary=False, signoff=True)
Simon Glassb3bf4e12020-07-05 21:41:52 -0600607 self.assertIsNotNone(cover_fname)
608 self.assertEqual(2, len(patch_files))
Simon Glass414f1e02025-02-27 12:27:30 -0700609
610 cover = tools.read_file(cover_fname, binary=False)
611 lines = cover.splitlines()[-2:]
612 base2 = repo.lookup_reference('refs/heads/second')
613 ref = base2.peel(pygit2.GIT_OBJ_COMMIT).parents[0].parents[0].id
614 self.assertEqual(f'base-commit: {ref}', lines[0])
615 self.assertEqual('branch: second', lines[1])
Simon Glass54f1c5b2020-07-05 21:41:50 -0600616 finally:
617 os.chdir(orig_dir)
Simon Glass06202d62020-10-29 21:46:27 -0600618
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500619 def test_custom_get_maintainer_script(self):
620 """Validate that a custom get_maintainer script gets used."""
621 self.make_git_tree()
Simon Glass41dfb6e2025-05-08 05:13:35 +0200622 with directory_excursion(self.tmpdir):
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500623 # Setup git.
624 os.environ['GIT_CONFIG_GLOBAL'] = '/dev/null'
625 os.environ['GIT_CONFIG_SYSTEM'] = '/dev/null'
626 tools.run('git', 'config', 'user.name', 'Dummy')
627 tools.run('git', 'config', 'user.email', 'dumdum@dummy.com')
628 tools.run('git', 'branch', 'upstream')
629 tools.run('git', 'branch', '--set-upstream-to=upstream')
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500630
631 # Setup patman configuration.
Simon Glass30b21792025-05-08 05:36:20 +0200632 tools.write_file('.patman', '[settings]\n'
633 'get_maintainer_script: dummy-script.sh\n'
634 'check_patch: False\n'
635 'add_maintainers: True\n', binary=False)
636 tools.write_file('dummy-script.sh',
637 '#!/usr/bin/env python\n'
638 'print("hello@there.com")\n', binary=False)
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500639 os.chmod('dummy-script.sh', 0x555)
Simon Glass41dfb6e2025-05-08 05:13:35 +0200640 tools.run('git', 'add', '.')
641 tools.run('git', 'commit', '-m', 'new commit')
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500642
643 # Finally, do the test
Simon Glass14d64e32025-04-29 07:21:59 -0600644 with terminal.capture():
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500645 output = tools.run(PATMAN_DIR / 'patman', '--dry-run')
646 # Assert the email address is part of the dry-run
647 # output.
648 self.assertIn('hello@there.com', output)
649
Simon Glassd85bb8f2022-01-29 14:14:09 -0700650 def test_tags(self):
Simon Glass06202d62020-10-29 21:46:27 -0600651 """Test collection of tags in a patchstream"""
652 text = '''This is a patch
653
654Signed-off-by: Terminator
Simon Glass3b762cc2020-10-29 21:46:28 -0600655Reviewed-by: %s
656Reviewed-by: %s
Simon Glass06202d62020-10-29 21:46:27 -0600657Tested-by: %s
Simon Glass3b762cc2020-10-29 21:46:28 -0600658''' % (self.joe, self.mary, self.leb)
Simon Glass06202d62020-10-29 21:46:27 -0600659 pstrm = PatchStream.process_text(text)
660 self.assertEqual(pstrm.commit.rtags, {
Simon Glass3b762cc2020-10-29 21:46:28 -0600661 'Reviewed-by': {self.joe, self.mary},
Simon Glass06202d62020-10-29 21:46:27 -0600662 'Tested-by': {self.leb}})
Simon Glass3b762cc2020-10-29 21:46:28 -0600663
Simon Glassd85bb8f2022-01-29 14:14:09 -0700664 def test_invalid_tag(self):
Patrick Delaunay6bbdd0c2021-07-22 16:51:42 +0200665 """Test invalid tag in a patchstream"""
666 text = '''This is a patch
667
668Serie-version: 2
669'''
670 with self.assertRaises(ValueError) as exc:
Simon Glass30b21792025-05-08 05:36:20 +0200671 PatchStream.process_text(text)
Patrick Delaunay6bbdd0c2021-07-22 16:51:42 +0200672 self.assertEqual("Line 3: Invalid tag = 'Serie-version: 2'",
673 str(exc.exception))
674
Simon Glassd85bb8f2022-01-29 14:14:09 -0700675 def test_missing_end(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600676 """Test a missing END tag"""
677 text = '''This is a patch
678
679Cover-letter:
680This is the title
681missing END after this line
682Signed-off-by: Fred
683'''
684 pstrm = PatchStream.process_text(text)
685 self.assertEqual(["Missing 'END' in section 'cover'"],
686 pstrm.commit.warn)
687
Simon Glassd85bb8f2022-01-29 14:14:09 -0700688 def test_missing_blank_line(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600689 """Test a missing blank line after a tag"""
690 text = '''This is a patch
691
692Series-changes: 2
693- First line of changes
694- Missing blank line after this line
695Signed-off-by: Fred
696'''
697 pstrm = PatchStream.process_text(text)
698 self.assertEqual(["Missing 'blank line' in section 'Series-changes'"],
699 pstrm.commit.warn)
700
Simon Glassd85bb8f2022-01-29 14:14:09 -0700701 def test_invalid_commit_tag(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600702 """Test an invalid Commit-xxx tag"""
703 text = '''This is a patch
704
705Commit-fred: testing
706'''
707 pstrm = PatchStream.process_text(text)
708 self.assertEqual(["Line 3: Ignoring Commit-fred"], pstrm.commit.warn)
709
Simon Glassd85bb8f2022-01-29 14:14:09 -0700710 def test_self_test(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600711 """Test a tested by tag by this user"""
712 test_line = 'Tested-by: %s@napier.com' % os.getenv('USER')
713 text = '''This is a patch
714
715%s
716''' % test_line
717 pstrm = PatchStream.process_text(text)
718 self.assertEqual(["Ignoring '%s'" % test_line], pstrm.commit.warn)
719
Simon Glassd85bb8f2022-01-29 14:14:09 -0700720 def test_space_before_tab(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600721 """Test a space before a tab"""
722 text = '''This is a patch
723
724+ \tSomething
725'''
726 pstrm = PatchStream.process_text(text)
727 self.assertEqual(["Line 3/0 has space before tab"], pstrm.commit.warn)
728
Simon Glassd85bb8f2022-01-29 14:14:09 -0700729 def test_lines_after_test(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600730 """Test detecting lines after TEST= line"""
731 text = '''This is a patch
732
733TEST=sometest
734more lines
735here
736'''
737 pstrm = PatchStream.process_text(text)
738 self.assertEqual(["Found 2 lines after TEST="], pstrm.commit.warn)
739
Simon Glassd85bb8f2022-01-29 14:14:09 -0700740 def test_blank_line_at_end(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600741 """Test detecting a blank line at the end of a file"""
742 text = '''This is a patch
743
744diff --git a/lib/fdtdec.c b/lib/fdtdec.c
745index c072e54..942244f 100644
746--- a/lib/fdtdec.c
747+++ b/lib/fdtdec.c
748@@ -1200,7 +1200,8 @@ int fdtdec_setup_mem_size_base(void)
Simon Glass30b21792025-05-08 05:36:20 +0200749 \t}
Simon Glass3b762cc2020-10-29 21:46:28 -0600750
Simon Glass30b21792025-05-08 05:36:20 +0200751 \tgd->ram_size = (phys_size_t)(res.end - res.start + 1);
Simon Glass3b762cc2020-10-29 21:46:28 -0600752- debug("%s: Initial DRAM size %llx\n", __func__, (u64)gd->ram_size);
753+ debug("%s: Initial DRAM size %llx\n", __func__,
754+ (unsigned long long)gd->ram_size);
755+
756diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c
757
758--
7592.7.4
760
761 '''
762 pstrm = PatchStream.process_text(text)
763 self.assertEqual(
764 ["Found possible blank line(s) at end of file 'lib/fdtdec.c'"],
765 pstrm.commit.warn)
Simon Glass1c1f2072020-10-29 21:46:34 -0600766
Simon Glassd85bb8f2022-01-29 14:14:09 -0700767 def test_no_upstream(self):
Simon Glass1c1f2072020-10-29 21:46:34 -0600768 """Test CountCommitsToBranch when there is no upstream"""
769 repo = self.make_git_tree()
770 target = repo.lookup_reference('refs/heads/base')
Simon Glass547cba62022-02-11 13:23:18 -0700771 # pylint doesn't seem to find this
772 # pylint: disable=E1101
Simon Glass1c1f2072020-10-29 21:46:34 -0600773 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
774
775 # Check that it can detect the current branch
Heinrich Schuchardtd01d6672023-04-20 20:07:29 +0200776 orig_dir = os.getcwd()
Simon Glass1c1f2072020-10-29 21:46:34 -0600777 try:
Simon Glass1c1f2072020-10-29 21:46:34 -0600778 os.chdir(self.gitdir)
779 with self.assertRaises(ValueError) as exc:
Simon Glass761648b2022-01-29 14:14:11 -0700780 gitutil.count_commits_to_branch(None)
Simon Glass1c1f2072020-10-29 21:46:34 -0600781 self.assertIn(
782 "Failed to determine upstream: fatal: no upstream configured for branch 'base'",
783 str(exc.exception))
784 finally:
785 os.chdir(orig_dir)
Simon Glass3db916d2020-10-29 21:46:35 -0600786
787 @staticmethod
Simon Glass25b91c12025-04-29 07:22:19 -0600788 def _fake_patchwork(subpath):
Simon Glass3db916d2020-10-29 21:46:35 -0600789 """Fake Patchwork server for the function below
790
791 This handles accessing a series, providing a list consisting of a
792 single patch
Simon Glassf9b03cf2020-11-03 13:54:14 -0700793
794 Args:
Simon Glassf9b03cf2020-11-03 13:54:14 -0700795 subpath (str): URL subpath to use
Simon Glass3db916d2020-10-29 21:46:35 -0600796 """
797 re_series = re.match(r'series/(\d*)/$', subpath)
798 if re_series:
799 series_num = re_series.group(1)
800 if series_num == '1234':
801 return {'patches': [
802 {'id': '1', 'name': 'Some patch'}]}
803 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
804
Simon Glassd85bb8f2022-01-29 14:14:09 -0700805 def test_status_mismatch(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600806 """Test Patchwork patches not matching the series"""
Simon Glass25b91c12025-04-29 07:22:19 -0600807 pwork = patchwork.Patchwork.for_testing(self._fake_patchwork)
Simon Glass14d64e32025-04-29 07:21:59 -0600808 with terminal.capture() as (_, err):
Simon Glass30b21792025-05-08 05:36:20 +0200809 loop = asyncio.get_event_loop()
810 patches = loop.run_until_complete(status.check_status(1234, pwork))
Simon Glass27280f42025-04-29 07:22:17 -0600811 status.check_patch_count(0, len(patches))
Simon Glass3db916d2020-10-29 21:46:35 -0600812 self.assertIn('Warning: Patchwork reports 1 patches, series has 0',
813 err.getvalue())
814
Simon Glassd85bb8f2022-01-29 14:14:09 -0700815 def test_status_read_patch(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600816 """Test handling a single patch in Patchwork"""
Simon Glass25b91c12025-04-29 07:22:19 -0600817 pwork = patchwork.Patchwork.for_testing(self._fake_patchwork)
Simon Glass30b21792025-05-08 05:36:20 +0200818 loop = asyncio.get_event_loop()
819 patches = loop.run_until_complete(status.check_status(1234, pwork))
Simon Glass3db916d2020-10-29 21:46:35 -0600820 self.assertEqual(1, len(patches))
821 patch = patches[0]
822 self.assertEqual('1', patch.id)
823 self.assertEqual('Some patch', patch.raw_subject)
824
Simon Glassd85bb8f2022-01-29 14:14:09 -0700825 def test_parse_subject(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600826 """Test parsing of the patch subject"""
Simon Glass232eefd2025-04-29 07:22:14 -0600827 patch = patchwork.Patch('1')
Simon Glass3db916d2020-10-29 21:46:35 -0600828
829 # Simple patch not in a series
830 patch.parse_subject('Testing')
831 self.assertEqual('Testing', patch.raw_subject)
832 self.assertEqual('Testing', patch.subject)
833 self.assertEqual(1, patch.seq)
834 self.assertEqual(1, patch.count)
835 self.assertEqual(None, patch.prefix)
836 self.assertEqual(None, patch.version)
837
838 # First patch in a series
839 patch.parse_subject('[1/2] Testing')
840 self.assertEqual('[1/2] Testing', patch.raw_subject)
841 self.assertEqual('Testing', patch.subject)
842 self.assertEqual(1, patch.seq)
843 self.assertEqual(2, patch.count)
844 self.assertEqual(None, patch.prefix)
845 self.assertEqual(None, patch.version)
846
847 # Second patch in a series
848 patch.parse_subject('[2/2] Testing')
849 self.assertEqual('Testing', patch.subject)
850 self.assertEqual(2, patch.seq)
851 self.assertEqual(2, patch.count)
852 self.assertEqual(None, patch.prefix)
853 self.assertEqual(None, patch.version)
854
855 # RFC patch
856 patch.parse_subject('[RFC,3/7] Testing')
857 self.assertEqual('Testing', patch.subject)
858 self.assertEqual(3, patch.seq)
859 self.assertEqual(7, patch.count)
860 self.assertEqual('RFC', patch.prefix)
861 self.assertEqual(None, patch.version)
862
863 # Version patch
864 patch.parse_subject('[v2,3/7] Testing')
865 self.assertEqual('Testing', patch.subject)
866 self.assertEqual(3, patch.seq)
867 self.assertEqual(7, patch.count)
868 self.assertEqual(None, patch.prefix)
869 self.assertEqual('v2', patch.version)
870
871 # All fields
872 patch.parse_subject('[RESEND,v2,3/7] Testing')
873 self.assertEqual('Testing', patch.subject)
874 self.assertEqual(3, patch.seq)
875 self.assertEqual(7, patch.count)
876 self.assertEqual('RESEND', patch.prefix)
877 self.assertEqual('v2', patch.version)
878
879 # RFC only
880 patch.parse_subject('[RESEND] Testing')
881 self.assertEqual('Testing', patch.subject)
882 self.assertEqual(1, patch.seq)
883 self.assertEqual(1, patch.count)
884 self.assertEqual('RESEND', patch.prefix)
885 self.assertEqual(None, patch.version)
886
Simon Glassd85bb8f2022-01-29 14:14:09 -0700887 def test_compare_series(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600888 """Test operation of compare_with_series()"""
889 commit1 = Commit('abcd')
890 commit1.subject = 'Subject 1'
891 commit2 = Commit('ef12')
892 commit2.subject = 'Subject 2'
893 commit3 = Commit('3456')
894 commit3.subject = 'Subject 2'
895
Simon Glass232eefd2025-04-29 07:22:14 -0600896 patch1 = patchwork.Patch('1')
Simon Glass3db916d2020-10-29 21:46:35 -0600897 patch1.subject = 'Subject 1'
Simon Glass232eefd2025-04-29 07:22:14 -0600898 patch2 = patchwork.Patch('2')
Simon Glass3db916d2020-10-29 21:46:35 -0600899 patch2.subject = 'Subject 2'
Simon Glass232eefd2025-04-29 07:22:14 -0600900 patch3 = patchwork.Patch('3')
Simon Glass3db916d2020-10-29 21:46:35 -0600901 patch3.subject = 'Subject 2'
902
903 series = Series()
904 series.commits = [commit1]
905 patches = [patch1]
906 patch_for_commit, commit_for_patch, warnings = (
907 status.compare_with_series(series, patches))
908 self.assertEqual(1, len(patch_for_commit))
909 self.assertEqual(patch1, patch_for_commit[0])
910 self.assertEqual(1, len(commit_for_patch))
911 self.assertEqual(commit1, commit_for_patch[0])
912
913 series.commits = [commit1]
914 patches = [patch1, patch2]
915 patch_for_commit, commit_for_patch, warnings = (
916 status.compare_with_series(series, patches))
917 self.assertEqual(1, len(patch_for_commit))
918 self.assertEqual(patch1, patch_for_commit[0])
919 self.assertEqual(1, len(commit_for_patch))
920 self.assertEqual(commit1, commit_for_patch[0])
921 self.assertEqual(["Cannot find commit for patch 2 ('Subject 2')"],
922 warnings)
923
924 series.commits = [commit1, commit2]
925 patches = [patch1]
926 patch_for_commit, commit_for_patch, warnings = (
927 status.compare_with_series(series, patches))
928 self.assertEqual(1, len(patch_for_commit))
929 self.assertEqual(patch1, patch_for_commit[0])
930 self.assertEqual(1, len(commit_for_patch))
931 self.assertEqual(commit1, commit_for_patch[0])
932 self.assertEqual(["Cannot find patch for commit 2 ('Subject 2')"],
933 warnings)
934
935 series.commits = [commit1, commit2, commit3]
936 patches = [patch1, patch2]
937 patch_for_commit, commit_for_patch, warnings = (
938 status.compare_with_series(series, patches))
939 self.assertEqual(2, len(patch_for_commit))
940 self.assertEqual(patch1, patch_for_commit[0])
941 self.assertEqual(patch2, patch_for_commit[1])
942 self.assertEqual(1, len(commit_for_patch))
943 self.assertEqual(commit1, commit_for_patch[0])
944 self.assertEqual(["Cannot find patch for commit 3 ('Subject 2')",
945 "Multiple commits match patch 2 ('Subject 2'):\n"
946 ' Subject 2\n Subject 2'],
947 warnings)
948
949 series.commits = [commit1, commit2]
950 patches = [patch1, patch2, patch3]
951 patch_for_commit, commit_for_patch, warnings = (
952 status.compare_with_series(series, patches))
953 self.assertEqual(1, len(patch_for_commit))
954 self.assertEqual(patch1, patch_for_commit[0])
955 self.assertEqual(2, len(commit_for_patch))
956 self.assertEqual(commit1, commit_for_patch[0])
957 self.assertEqual(["Multiple patches match commit 2 ('Subject 2'):\n"
958 ' Subject 2\n Subject 2',
959 "Cannot find commit for patch 3 ('Subject 2')"],
960 warnings)
961
Simon Glass25b91c12025-04-29 07:22:19 -0600962 def _fake_patchwork2(self, subpath):
Simon Glass3db916d2020-10-29 21:46:35 -0600963 """Fake Patchwork server for the function below
964
965 This handles accessing series, patches and comments, providing the data
966 in self.patches to the caller
Simon Glassf9b03cf2020-11-03 13:54:14 -0700967
968 Args:
Simon Glassf9b03cf2020-11-03 13:54:14 -0700969 subpath (str): URL subpath to use
Simon Glass3db916d2020-10-29 21:46:35 -0600970 """
971 re_series = re.match(r'series/(\d*)/$', subpath)
972 re_patch = re.match(r'patches/(\d*)/$', subpath)
973 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
974 if re_series:
975 series_num = re_series.group(1)
976 if series_num == '1234':
977 return {'patches': self.patches}
978 elif re_patch:
979 patch_num = int(re_patch.group(1))
980 patch = self.patches[patch_num - 1]
981 return patch
982 elif re_comments:
983 patch_num = int(re_comments.group(1))
984 patch = self.patches[patch_num - 1]
985 return patch.comments
986 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
987
Simon Glassd85bb8f2022-01-29 14:14:09 -0700988 def test_find_new_responses(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600989 """Test operation of find_new_responses()"""
990 commit1 = Commit('abcd')
991 commit1.subject = 'Subject 1'
992 commit2 = Commit('ef12')
993 commit2.subject = 'Subject 2'
994
Simon Glass232eefd2025-04-29 07:22:14 -0600995 patch1 = patchwork.Patch('1')
Simon Glass3db916d2020-10-29 21:46:35 -0600996 patch1.parse_subject('[1/2] Subject 1')
997 patch1.name = patch1.raw_subject
998 patch1.content = 'This is my patch content'
999 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
1000
1001 patch1.comments = [comment1a]
1002
Simon Glass232eefd2025-04-29 07:22:14 -06001003 patch2 = patchwork.Patch('2')
Simon Glass3db916d2020-10-29 21:46:35 -06001004 patch2.parse_subject('[2/2] Subject 2')
1005 patch2.name = patch2.raw_subject
1006 patch2.content = 'Some other patch content'
1007 comment2a = {
1008 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1009 (self.mary, self.leb)}
1010 comment2b = {'content': 'Reviewed-by: %s' % self.fred}
1011 patch2.comments = [comment2a, comment2b]
1012
1013 # This test works by setting up commits and patch for use by the fake
1014 # Rest API function _fake_patchwork2(). It calls various functions in
1015 # the status module after setting up tags in the commits, checking that
1016 # things behaves as expected
1017 self.commits = [commit1, commit2]
1018 self.patches = [patch1, patch2]
Simon Glass3db916d2020-10-29 21:46:35 -06001019
1020 # Check that the tags are picked up on the first patch
Simon Glass29771962025-04-29 07:22:20 -06001021 new_rtags, _ = status.process_reviews(patch1.content, patch1.comments,
1022 commit1.rtags)
1023 self.assertEqual(new_rtags, {'Reviewed-by': {self.joe}})
Simon Glass3db916d2020-10-29 21:46:35 -06001024
1025 # Now the second patch
Simon Glass29771962025-04-29 07:22:20 -06001026 new_rtags, _ = status.process_reviews(patch2.content, patch2.comments,
1027 commit2.rtags)
1028 self.assertEqual(new_rtags, {
Simon Glass3db916d2020-10-29 21:46:35 -06001029 'Reviewed-by': {self.mary, self.fred},
1030 'Tested-by': {self.leb}})
1031
1032 # Now add some tags to the commit, which means they should not appear as
1033 # 'new' tags when scanning comments
Simon Glass3db916d2020-10-29 21:46:35 -06001034 commit1.rtags = {'Reviewed-by': {self.joe}}
Simon Glass29771962025-04-29 07:22:20 -06001035 new_rtags, _ = status.process_reviews(patch1.content, patch1.comments,
1036 commit1.rtags)
1037 self.assertEqual(new_rtags, {})
Simon Glass3db916d2020-10-29 21:46:35 -06001038
1039 # For the second commit, add Ed and Fred, so only Mary should be left
1040 commit2.rtags = {
1041 'Tested-by': {self.leb},
1042 'Reviewed-by': {self.fred}}
Simon Glass29771962025-04-29 07:22:20 -06001043 new_rtags, _ = status.process_reviews(patch2.content, patch2.comments,
1044 commit2.rtags)
1045 self.assertEqual(new_rtags, {'Reviewed-by': {self.mary}})
Simon Glass3db916d2020-10-29 21:46:35 -06001046
1047 # Check that the output patches expectations:
1048 # 1 Subject 1
1049 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1050 # 2 Subject 2
1051 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1052 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1053 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1054 # 1 new response available in patchwork
1055
1056 series = Series()
1057 series.commits = [commit1, commit2]
Simon Glass02811582022-01-29 14:14:18 -07001058 terminal.set_print_test_mode()
Simon Glass25b91c12025-04-29 07:22:19 -06001059 pwork = patchwork.Patchwork.for_testing(self._fake_patchwork2)
Simon Glassc100b262025-04-29 07:22:16 -06001060 status.check_and_show_status(series, '1234', None, None, False, False,
Simon Glass25b91c12025-04-29 07:22:19 -06001061 pwork)
Simon Glassb3080ec2025-05-08 04:58:49 +02001062 itr = iter(terminal.get_print_test_lines())
Simon Glass3db916d2020-10-29 21:46:35 -06001063 col = terminal.Color()
Simon Glassd4d3fb42025-04-29 07:22:21 -06001064 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.YELLOW),
Simon Glassb3080ec2025-05-08 04:58:49 +02001065 next(itr))
Simon Glass3db916d2020-10-29 21:46:35 -06001066 self.assertEqual(
1067 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
1068 bright=False),
Simon Glassb3080ec2025-05-08 04:58:49 +02001069 next(itr))
Simon Glass3db916d2020-10-29 21:46:35 -06001070 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE, bright=False),
Simon Glassb3080ec2025-05-08 04:58:49 +02001071 next(itr))
Simon Glass3db916d2020-10-29 21:46:35 -06001072
Simon Glassd4d3fb42025-04-29 07:22:21 -06001073 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.YELLOW),
Simon Glassb3080ec2025-05-08 04:58:49 +02001074 next(itr))
Simon Glass3db916d2020-10-29 21:46:35 -06001075 self.assertEqual(
Simon Glass2112d072020-10-29 21:46:38 -06001076 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
Simon Glass3db916d2020-10-29 21:46:35 -06001077 bright=False),
Simon Glassb3080ec2025-05-08 04:58:49 +02001078 next(itr))
1079 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE,
1080 bright=False), next(itr))
Simon Glass3db916d2020-10-29 21:46:35 -06001081 self.assertEqual(
Simon Glass2112d072020-10-29 21:46:38 -06001082 terminal.PrintLine(' Tested-by: ', col.GREEN, newline=False,
Simon Glass3db916d2020-10-29 21:46:35 -06001083 bright=False),
Simon Glassb3080ec2025-05-08 04:58:49 +02001084 next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001085 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE, bright=False),
Simon Glassb3080ec2025-05-08 04:58:49 +02001086 next(itr))
Simon Glass3db916d2020-10-29 21:46:35 -06001087 self.assertEqual(
1088 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
Simon Glassb3080ec2025-05-08 04:58:49 +02001089 next(itr))
Simon Glass3db916d2020-10-29 21:46:35 -06001090 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
Simon Glassb3080ec2025-05-08 04:58:49 +02001091 next(itr))
Simon Glass3db916d2020-10-29 21:46:35 -06001092 self.assertEqual(terminal.PrintLine(
Simon Glassd0a0a582020-10-29 21:46:36 -06001093 '1 new response available in patchwork (use -d to write them to a new branch)',
Simon Glassb3080ec2025-05-08 04:58:49 +02001094 None), next(itr))
Simon Glassd0a0a582020-10-29 21:46:36 -06001095
Simon Glass25b91c12025-04-29 07:22:19 -06001096 def _fake_patchwork3(self, subpath):
Simon Glassd0a0a582020-10-29 21:46:36 -06001097 """Fake Patchwork server for the function below
1098
1099 This handles accessing series, patches and comments, providing the data
1100 in self.patches to the caller
Simon Glassf9b03cf2020-11-03 13:54:14 -07001101
1102 Args:
Simon Glassf9b03cf2020-11-03 13:54:14 -07001103 subpath (str): URL subpath to use
Simon Glassd0a0a582020-10-29 21:46:36 -06001104 """
1105 re_series = re.match(r'series/(\d*)/$', subpath)
1106 re_patch = re.match(r'patches/(\d*)/$', subpath)
1107 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
1108 if re_series:
1109 series_num = re_series.group(1)
1110 if series_num == '1234':
1111 return {'patches': self.patches}
1112 elif re_patch:
1113 patch_num = int(re_patch.group(1))
1114 patch = self.patches[patch_num - 1]
1115 return patch
1116 elif re_comments:
1117 patch_num = int(re_comments.group(1))
1118 patch = self.patches[patch_num - 1]
1119 return patch.comments
1120 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
1121
Simon Glassd85bb8f2022-01-29 14:14:09 -07001122 def test_create_branch(self):
Simon Glassd0a0a582020-10-29 21:46:36 -06001123 """Test operation of create_branch()"""
1124 repo = self.make_git_tree()
1125 branch = 'first'
1126 dest_branch = 'first2'
1127 count = 2
Simon Glass41dfb6e2025-05-08 05:13:35 +02001128 gitdir = self.gitdir
Simon Glassd0a0a582020-10-29 21:46:36 -06001129
1130 # Set up the test git tree. We use branch 'first' which has two commits
1131 # in it
1132 series = patchstream.get_metadata_for_list(branch, gitdir, count)
1133 self.assertEqual(2, len(series.commits))
1134
Simon Glass232eefd2025-04-29 07:22:14 -06001135 patch1 = patchwork.Patch('1')
Simon Glassd0a0a582020-10-29 21:46:36 -06001136 patch1.parse_subject('[1/2] %s' % series.commits[0].subject)
1137 patch1.name = patch1.raw_subject
1138 patch1.content = 'This is my patch content'
1139 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
1140
1141 patch1.comments = [comment1a]
1142
Simon Glass232eefd2025-04-29 07:22:14 -06001143 patch2 = patchwork.Patch('2')
Simon Glassd0a0a582020-10-29 21:46:36 -06001144 patch2.parse_subject('[2/2] %s' % series.commits[1].subject)
1145 patch2.name = patch2.raw_subject
1146 patch2.content = 'Some other patch content'
1147 comment2a = {
1148 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1149 (self.mary, self.leb)}
1150 comment2b = {
1151 'content': 'Reviewed-by: %s' % self.fred}
1152 patch2.comments = [comment2a, comment2b]
1153
1154 # This test works by setting up patches for use by the fake Rest API
1155 # function _fake_patchwork3(). The fake patch comments above should
1156 # result in new review tags that are collected and added to the commits
1157 # created in the destination branch.
1158 self.patches = [patch1, patch2]
1159 count = 2
1160
1161 # Expected output:
1162 # 1 i2c: I2C things
1163 # + Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1164 # 2 spi: SPI fixes
1165 # + Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1166 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1167 # + Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1168 # 4 new responses available in patchwork
1169 # 4 responses added from patchwork into new branch 'first2'
1170 # <unittest.result.TestResult run=8 errors=0 failures=0>
1171
Simon Glass02811582022-01-29 14:14:18 -07001172 terminal.set_print_test_mode()
Simon Glass25b91c12025-04-29 07:22:19 -06001173 pwork = patchwork.Patchwork.for_testing(self._fake_patchwork3)
Simon Glassc100b262025-04-29 07:22:16 -06001174 status.check_and_show_status(series, '1234', branch, dest_branch,
Simon Glass25b91c12025-04-29 07:22:19 -06001175 False, False, pwork, repo)
Simon Glass02811582022-01-29 14:14:18 -07001176 lines = terminal.get_print_test_lines()
Simon Glassd0a0a582020-10-29 21:46:36 -06001177 self.assertEqual(12, len(lines))
1178 self.assertEqual(
1179 "4 responses added from patchwork into new branch 'first2'",
1180 lines[11].text)
1181
1182 # Check that the destination branch has the new tags
1183 new_series = patchstream.get_metadata_for_list(dest_branch, gitdir,
1184 count)
1185 self.assertEqual(
1186 {'Reviewed-by': {self.joe}},
1187 new_series.commits[0].rtags)
1188 self.assertEqual(
1189 {'Tested-by': {self.leb},
1190 'Reviewed-by': {self.fred, self.mary}},
1191 new_series.commits[1].rtags)
1192
1193 # Now check the actual test of the first commit message. We expect to
1194 # see the new tags immediately below the old ones.
1195 stdout = patchstream.get_list(dest_branch, count=count, git_dir=gitdir)
Simon Glassb3080ec2025-05-08 04:58:49 +02001196 itr = iter([line.strip() for line in stdout.splitlines()
1197 if '-by:' in line])
Simon Glassd0a0a582020-10-29 21:46:36 -06001198
1199 # First patch should have the review tag
Simon Glassb3080ec2025-05-08 04:58:49 +02001200 self.assertEqual('Reviewed-by: %s' % self.joe, next(itr))
Simon Glassd0a0a582020-10-29 21:46:36 -06001201
1202 # Second patch should have the sign-off then the tested-by and two
1203 # reviewed-by tags
Simon Glassb3080ec2025-05-08 04:58:49 +02001204 self.assertEqual('Signed-off-by: %s' % self.leb, next(itr))
1205 self.assertEqual('Reviewed-by: %s' % self.fred, next(itr))
1206 self.assertEqual('Reviewed-by: %s' % self.mary, next(itr))
1207 self.assertEqual('Tested-by: %s' % self.leb, next(itr))
Simon Glassda8a2922020-10-29 21:46:37 -06001208
Simon Glassd85bb8f2022-01-29 14:14:09 -07001209 def test_parse_snippets(self):
Simon Glassda8a2922020-10-29 21:46:37 -06001210 """Test parsing of review snippets"""
1211 text = '''Hi Fred,
1212
1213This is a comment from someone.
1214
1215Something else
1216
1217On some recent date, Fred wrote:
1218> This is why I wrote the patch
1219> so here it is
1220
1221Now a comment about the commit message
1222A little more to say
1223
1224Even more
1225
1226> diff --git a/file.c b/file.c
1227> Some more code
1228> Code line 2
1229> Code line 3
1230> Code line 4
1231> Code line 5
1232> Code line 6
1233> Code line 7
1234> Code line 8
1235> Code line 9
1236
1237And another comment
1238
Simon Glassd85bb8f2022-01-29 14:14:09 -07001239> @@ -153,8 +143,13 @@ def check_patch(fname, show_types=False):
Simon Glassda8a2922020-10-29 21:46:37 -06001240> further down on the file
1241> and more code
1242> +Addition here
1243> +Another addition here
1244> codey
1245> more codey
1246
1247and another thing in same file
1248
1249> @@ -253,8 +243,13 @@
1250> with no function context
1251
1252one more thing
1253
1254> diff --git a/tools/patman/main.py b/tools/patman/main.py
1255> +line of code
1256now a very long comment in a different file
1257line2
1258line3
1259line4
1260line5
1261line6
1262line7
1263line8
1264'''
1265 pstrm = PatchStream.process_text(text, True)
1266 self.assertEqual([], pstrm.commit.warn)
1267
1268 # We expect to the filename and up to 5 lines of code context before
1269 # each comment. The 'On xxx wrote:' bit should be removed.
1270 self.assertEqual(
1271 [['Hi Fred,',
1272 'This is a comment from someone.',
1273 'Something else'],
1274 ['> This is why I wrote the patch',
1275 '> so here it is',
1276 'Now a comment about the commit message',
1277 'A little more to say', 'Even more'],
1278 ['> File: file.c', '> Code line 5', '> Code line 6',
1279 '> Code line 7', '> Code line 8', '> Code line 9',
1280 'And another comment'],
1281 ['> File: file.c',
Simon Glassd85bb8f2022-01-29 14:14:09 -07001282 '> Line: 153 / 143: def check_patch(fname, show_types=False):',
Simon Glass30b21792025-05-08 05:36:20 +02001283 '> and more code', '> +Addition here',
1284 '> +Another addition here', '> codey', '> more codey',
1285 'and another thing in same file'],
Simon Glassda8a2922020-10-29 21:46:37 -06001286 ['> File: file.c', '> Line: 253 / 243',
1287 '> with no function context', 'one more thing'],
1288 ['> File: tools/patman/main.py', '> +line of code',
1289 'now a very long comment in a different file',
1290 'line2', 'line3', 'line4', 'line5', 'line6', 'line7', 'line8']],
1291 pstrm.snippets)
Simon Glass2112d072020-10-29 21:46:38 -06001292
Simon Glassd85bb8f2022-01-29 14:14:09 -07001293 def test_review_snippets(self):
Simon Glass2112d072020-10-29 21:46:38 -06001294 """Test showing of review snippets"""
1295 def _to_submitter(who):
1296 m_who = re.match('(.*) <(.*)>', who)
1297 return {
1298 'name': m_who.group(1),
1299 'email': m_who.group(2)
1300 }
1301
1302 commit1 = Commit('abcd')
1303 commit1.subject = 'Subject 1'
1304 commit2 = Commit('ef12')
1305 commit2.subject = 'Subject 2'
1306
Simon Glass232eefd2025-04-29 07:22:14 -06001307 patch1 = patchwork.Patch('1')
Simon Glass2112d072020-10-29 21:46:38 -06001308 patch1.parse_subject('[1/2] Subject 1')
1309 patch1.name = patch1.raw_subject
1310 patch1.content = 'This is my patch content'
1311 comment1a = {'submitter': _to_submitter(self.joe),
1312 'content': '''Hi Fred,
1313
1314On some date Fred wrote:
1315
1316> diff --git a/file.c b/file.c
1317> Some code
1318> and more code
1319
1320Here is my comment above the above...
1321
1322
1323Reviewed-by: %s
1324''' % self.joe}
1325
1326 patch1.comments = [comment1a]
1327
Simon Glass232eefd2025-04-29 07:22:14 -06001328 patch2 = patchwork.Patch('2')
Simon Glass2112d072020-10-29 21:46:38 -06001329 patch2.parse_subject('[2/2] Subject 2')
1330 patch2.name = patch2.raw_subject
1331 patch2.content = 'Some other patch content'
1332 comment2a = {
1333 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1334 (self.mary, self.leb)}
1335 comment2b = {'submitter': _to_submitter(self.fred),
1336 'content': '''Hi Fred,
1337
1338On some date Fred wrote:
1339
1340> diff --git a/tools/patman/commit.py b/tools/patman/commit.py
1341> @@ -41,6 +41,9 @@ class Commit:
1342> self.rtags = collections.defaultdict(set)
1343> self.warn = []
1344>
1345> + def __str__(self):
1346> + return self.subject
1347> +
Simon Glassd85bb8f2022-01-29 14:14:09 -07001348> def add_change(self, version, info):
Simon Glass2112d072020-10-29 21:46:38 -06001349> """Add a new change line to the change list for a version.
1350>
1351A comment
1352
1353Reviewed-by: %s
1354''' % self.fred}
1355 patch2.comments = [comment2a, comment2b]
1356
1357 # This test works by setting up commits and patch for use by the fake
1358 # Rest API function _fake_patchwork2(). It calls various functions in
1359 # the status module after setting up tags in the commits, checking that
1360 # things behaves as expected
1361 self.commits = [commit1, commit2]
1362 self.patches = [patch1, patch2]
1363
1364 # Check that the output patches expectations:
1365 # 1 Subject 1
1366 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1367 # 2 Subject 2
1368 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1369 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1370 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1371 # 1 new response available in patchwork
1372
1373 series = Series()
1374 series.commits = [commit1, commit2]
Simon Glass02811582022-01-29 14:14:18 -07001375 terminal.set_print_test_mode()
Simon Glass25b91c12025-04-29 07:22:19 -06001376 pwork = patchwork.Patchwork.for_testing(self._fake_patchwork2)
Simon Glassc100b262025-04-29 07:22:16 -06001377 status.check_and_show_status(series, '1234', None, None, False, True,
Simon Glass25b91c12025-04-29 07:22:19 -06001378 pwork)
Simon Glassb3080ec2025-05-08 04:58:49 +02001379 itr = iter(terminal.get_print_test_lines())
Simon Glass2112d072020-10-29 21:46:38 -06001380 col = terminal.Color()
Simon Glassd4d3fb42025-04-29 07:22:21 -06001381 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.YELLOW),
Simon Glassb3080ec2025-05-08 04:58:49 +02001382 next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001383 self.assertEqual(
1384 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
Simon Glassb3080ec2025-05-08 04:58:49 +02001385 next(itr))
1386 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE), next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001387
1388 self.assertEqual(terminal.PrintLine('Review: %s' % self.joe, col.RED),
Simon Glassb3080ec2025-05-08 04:58:49 +02001389 next(itr))
1390 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(itr))
1391 self.assertEqual(terminal.PrintLine('', None), next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001392 self.assertEqual(terminal.PrintLine(' > File: file.c', col.MAGENTA),
Simon Glassb3080ec2025-05-08 04:58:49 +02001393 next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001394 self.assertEqual(terminal.PrintLine(' > Some code', col.MAGENTA),
Simon Glassb3080ec2025-05-08 04:58:49 +02001395 next(itr))
1396 self.assertEqual(terminal.PrintLine(' > and more code',
1397 col.MAGENTA),
1398 next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001399 self.assertEqual(terminal.PrintLine(
Simon Glassb3080ec2025-05-08 04:58:49 +02001400 ' Here is my comment above the above...', None), next(itr))
1401 self.assertEqual(terminal.PrintLine('', None), next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001402
Simon Glassd4d3fb42025-04-29 07:22:21 -06001403 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.YELLOW),
Simon Glassb3080ec2025-05-08 04:58:49 +02001404 next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001405 self.assertEqual(
1406 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
Simon Glassb3080ec2025-05-08 04:58:49 +02001407 next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001408 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE),
Simon Glassb3080ec2025-05-08 04:58:49 +02001409 next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001410 self.assertEqual(
1411 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
Simon Glassb3080ec2025-05-08 04:58:49 +02001412 next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001413 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
Simon Glassb3080ec2025-05-08 04:58:49 +02001414 next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001415 self.assertEqual(
1416 terminal.PrintLine(' + Tested-by: ', col.GREEN, newline=False),
Simon Glassb3080ec2025-05-08 04:58:49 +02001417 next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001418 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE),
Simon Glassb3080ec2025-05-08 04:58:49 +02001419 next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001420
1421 self.assertEqual(terminal.PrintLine('Review: %s' % self.fred, col.RED),
Simon Glassb3080ec2025-05-08 04:58:49 +02001422 next(itr))
1423 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(itr))
1424 self.assertEqual(terminal.PrintLine('', None), next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001425 self.assertEqual(terminal.PrintLine(
Simon Glassb3080ec2025-05-08 04:58:49 +02001426 ' > File: tools/patman/commit.py', col.MAGENTA), next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001427 self.assertEqual(terminal.PrintLine(
Simon Glassb3080ec2025-05-08 04:58:49 +02001428 ' > Line: 41 / 41: class Commit:', col.MAGENTA), next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001429 self.assertEqual(terminal.PrintLine(
Simon Glassb3080ec2025-05-08 04:58:49 +02001430 ' > + return self.subject', col.MAGENTA), next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001431 self.assertEqual(terminal.PrintLine(
Simon Glassb3080ec2025-05-08 04:58:49 +02001432 ' > +', col.MAGENTA), next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001433 self.assertEqual(
Simon Glassb3080ec2025-05-08 04:58:49 +02001434 terminal.PrintLine(
1435 ' > def add_change(self, version, info):',
1436 col.MAGENTA),
1437 next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001438 self.assertEqual(terminal.PrintLine(
1439 ' > """Add a new change line to the change list for a version.',
Simon Glassb3080ec2025-05-08 04:58:49 +02001440 col.MAGENTA), next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001441 self.assertEqual(terminal.PrintLine(
Simon Glassb3080ec2025-05-08 04:58:49 +02001442 ' >', col.MAGENTA), next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001443 self.assertEqual(terminal.PrintLine(
Simon Glassb3080ec2025-05-08 04:58:49 +02001444 ' A comment', None), next(itr))
1445 self.assertEqual(terminal.PrintLine('', None), next(itr))
Simon Glass2112d072020-10-29 21:46:38 -06001446
1447 self.assertEqual(terminal.PrintLine(
1448 '4 new responses available in patchwork (use -d to write them to a new branch)',
Simon Glassb3080ec2025-05-08 04:58:49 +02001449 None), next(itr))
Simon Glass6a222e62021-08-01 16:02:39 -06001450
Simon Glassd85bb8f2022-01-29 14:14:09 -07001451 def test_insert_tags(self):
Simon Glass6a222e62021-08-01 16:02:39 -06001452 """Test inserting of review tags"""
1453 msg = '''first line
1454second line.'''
1455 tags = [
1456 'Reviewed-by: Bin Meng <bmeng.cn@gmail.com>',
1457 'Tested-by: Bin Meng <bmeng.cn@gmail.com>'
1458 ]
1459 signoff = 'Signed-off-by: Simon Glass <sjg@chromium.com>'
1460 tag_str = '\n'.join(tags)
1461
1462 new_msg = patchstream.insert_tags(msg, tags)
1463 self.assertEqual(msg + '\n\n' + tag_str, new_msg)
1464
1465 new_msg = patchstream.insert_tags(msg + '\n', tags)
1466 self.assertEqual(msg + '\n\n' + tag_str, new_msg)
1467
1468 msg += '\n\n' + signoff
1469 new_msg = patchstream.insert_tags(msg, tags)
1470 self.assertEqual(msg + '\n' + tag_str, new_msg)