blob: 720746e21f5407f11b79c71db142cf5a9409351b [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 patchstream
Simon Glassa7fadab2020-10-29 21:46:26 -060022from patman.patchstream import PatchStream
Simon Glass3db916d2020-10-29 21:46:35 -060023from patman.series import Series
Simon Glassa997ea52020-04-17 18:09:04 -060024from patman import settings
Simon Glassba1b3b92025-02-09 14:26:00 -070025from u_boot_pylib import gitutil
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 Glass414f1e02025-02-27 12:27:30 -0700219 series.base_commit = Commit('1a44532')
220 series.branch = 'mybranch'
Simon Glasseb209e52020-10-29 21:46:15 -0600221 cover_fname, args = self._create_patches_for_test(series)
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500222 get_maintainer_script = str(pathlib.Path(__file__).parent.parent.parent
223 / 'get_maintainer.pl') + ' --norolestats'
Simon Glass59a70bb2020-10-29 21:46:14 -0600224 with capture_sys_output() as out:
Simon Glass93f61c02020-10-29 21:46:19 -0600225 patchstream.fix_patches(series, args)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600226 if cover_fname and series.get('cover'):
Simon Glass93f61c02020-10-29 21:46:19 -0600227 patchstream.insert_cover_letter(cover_fname, series, count)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600228 series.DoChecks()
229 cc_file = series.MakeCcFile(process_tags, cover_fname,
Chris Packhamb84fb482018-06-07 20:45:06 +1200230 not ignore_bad_tags, add_maintainers,
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500231 None, get_maintainer_script)
Simon Glass761648b2022-01-29 14:14:11 -0700232 cmd = gitutil.email_patches(
Simon Glass95745aa2020-10-29 21:46:13 -0600233 series, cover_fname, args, dry_run, not ignore_bad_tags,
234 cc_file, in_reply_to=in_reply_to, thread=None)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600235 series.ShowActions(args, cmd, process_tags)
Simon Glassf544a2d2019-10-31 07:42:51 -0600236 cc_lines = open(cc_file, encoding='utf-8').read().splitlines()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600237 os.remove(cc_file)
238
Simon Glass42e3d392020-10-29 21:46:29 -0600239 lines = iter(out[0].getvalue().splitlines())
240 self.assertEqual('Cleaned %s patches' % len(series.commits),
241 next(lines))
242 self.assertEqual('Change log missing for v2', next(lines))
243 self.assertEqual('Change log missing for v3', next(lines))
244 self.assertEqual('Change log for unknown version v4', next(lines))
245 self.assertEqual("Alias 'pci' not found", next(lines))
Simon Glass620639c2023-03-08 10:52:54 -0800246 while next(lines) != 'Cc processing complete':
247 pass
Simon Glass42e3d392020-10-29 21:46:29 -0600248 self.assertIn('Dry run', next(lines))
249 self.assertEqual('', next(lines))
250 self.assertIn('Send a total of %d patches' % count, next(lines))
251 prev = next(lines)
252 for i, commit in enumerate(series.commits):
253 self.assertEqual(' %s' % args[i], prev)
254 while True:
255 prev = next(lines)
256 if 'Cc:' not in prev:
257 break
258 self.assertEqual('To: u-boot@lists.denx.de', prev)
Simon Glass9dfb3112020-11-08 20:36:18 -0700259 self.assertEqual('Cc: %s' % stefan, next(lines))
Simon Glass42e3d392020-10-29 21:46:29 -0600260 self.assertEqual('Version: 3', next(lines))
261 self.assertEqual('Prefix:\t RFC', next(lines))
Sean Andersondc1cd132021-10-22 19:07:04 -0400262 self.assertEqual('Postfix:\t some-branch', next(lines))
Simon Glass42e3d392020-10-29 21:46:29 -0600263 self.assertEqual('Cover: 4 lines', next(lines))
264 self.assertEqual(' Cc: %s' % self.fred, next(lines))
Sean Anderson25978092024-04-18 22:36:31 -0400265 self.assertEqual(' Cc: %s' % self.joe, next(lines))
Simon Glass9dfb3112020-11-08 20:36:18 -0700266 self.assertEqual(' Cc: %s' % self.leb,
Simon Glass42e3d392020-10-29 21:46:29 -0600267 next(lines))
Simon Glass9dfb3112020-11-08 20:36:18 -0700268 self.assertEqual(' Cc: %s' % mel, next(lines))
Simon Glass42e3d392020-10-29 21:46:29 -0600269 self.assertEqual(' Cc: %s' % rick, next(lines))
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600270 expected = ('Git command: git send-email --annotate '
271 '--in-reply-to="%s" --to "u-boot@lists.denx.de" '
Simon Glass1ee91c12020-11-03 13:54:10 -0700272 '--cc "%s" --cc-cmd "%s send --cc-cmd %s" %s %s'
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600273 % (in_reply_to, stefan, sys.argv[0], cc_file, cover_fname,
Simon Glass4f817892019-05-14 15:53:53 -0600274 ' '.join(args)))
Simon Glass9dfb3112020-11-08 20:36:18 -0700275 self.assertEqual(expected, next(lines))
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600276
Simon Glass9dfb3112020-11-08 20:36:18 -0700277 self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)), cc_lines[0])
Simon Glass95745aa2020-10-29 21:46:13 -0600278 self.assertEqual(
Sean Anderson25978092024-04-18 22:36:31 -0400279 '%s %s\0%s\0%s\0%s\0%s' % (args[1], self.fred, self.joe, self.leb,
280 rick, stefan),
Simon Glass9dfb3112020-11-08 20:36:18 -0700281 cc_lines[1])
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600282
283 expected = '''
284This is a test of how the cover
Sean Andersoncf13b862020-05-04 16:28:36 -0400285letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600286works
287
288some notes
289about some things
290from the first commit
291
292Changes in v4:
Sean Andersoncf13b862020-05-04 16:28:36 -0400293- Multi
294 line
295 change
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600296- Some changes
Sean Andersoncf13b862020-05-04 16:28:36 -0400297- Some notes for the cover letter
Sean Andersone45678c2024-04-18 22:36:32 -0400298- fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600299
300Simon Glass (2):
301 pci: Correct cast for sandbox
Siva Durga Prasad Paladugub3d55ea2018-07-16 15:56:11 +0530302 fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600303
304 cmd/pci.c | 3 ++-
305 fs/fat/fat.c | 1 +
306 lib/efi_loader/efi_memory.c | 1 +
307 lib/fdtdec.c | 3 ++-
308 4 files changed, 6 insertions(+), 2 deletions(-)
309
310--\x20
3112.7.4
312
Simon Glass414f1e02025-02-27 12:27:30 -0700313base-commit: 1a44532
314branch: mybranch
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600315'''
Simon Glassf544a2d2019-10-31 07:42:51 -0600316 lines = open(cover_fname, encoding='utf-8').read().splitlines()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600317 self.assertEqual(
Sean Andersondc1cd132021-10-22 19:07:04 -0400318 'Subject: [RFC PATCH some-branch v3 0/2] test: A test patch series',
Simon Glass95745aa2020-10-29 21:46:13 -0600319 lines[3])
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600320 self.assertEqual(expected.splitlines(), lines[7:])
321
322 for i, fname in enumerate(args):
Simon Glassf544a2d2019-10-31 07:42:51 -0600323 lines = open(fname, encoding='utf-8').read().splitlines()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600324 subject = [line for line in lines if line.startswith('Subject')]
325 self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count),
326 subject[0][:18])
Sean Andersoncf13b862020-05-04 16:28:36 -0400327
328 # Check that we got our commit notes
329 start = 0
330 expected = ''
331
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600332 if i == 0:
Sean Andersoncf13b862020-05-04 16:28:36 -0400333 start = 17
334 expected = '''---
335Some notes about
336the first commit
337
338(no changes since v2)
339
340Changes in v2:
341- second revision change'''
342 elif i == 1:
343 start = 17
344 expected = '''---
345
346Changes in v4:
347- Multi
348 line
349 change
Sean Andersone45678c2024-04-18 22:36:32 -0400350- New
Sean Andersoncf13b862020-05-04 16:28:36 -0400351- Some changes
352
353Changes in v2:
354- Changes only for this commit'''
355
356 if expected:
357 expected = expected.splitlines()
358 self.assertEqual(expected, lines[start:(start+len(expected))])
Simon Glass54f1c5b2020-07-05 21:41:50 -0600359
Simon Glassda1a6ec2025-03-28 07:02:20 -0600360 def test_base_commit(self):
361 """Test adding a base commit with no cover letter"""
362 orig_text = self._get_text('test01.txt')
363 pos = orig_text.index('commit 5ab48490f03051875ab13d288a4bf32b507d76fd')
364 text = orig_text[:pos]
365 series = patchstream.get_metadata_for_test(text)
366 series.base_commit = Commit('1a44532')
367 series.branch = 'mybranch'
368 cover_fname, args = self._create_patches_for_test(series)
369 self.assertFalse(cover_fname)
370 with capture_sys_output() as out:
371 patchstream.fix_patches(series, args, insert_base_commit=True)
372 self.assertEqual('Cleaned 1 patch\n', out[0].getvalue())
373 lines = tools.read_file(args[0], binary=False).splitlines()
374 pos = lines.index('-- ')
375
376 # We expect these lines at the end:
377 # -- (with trailing space)
378 # 2.7.4
379 # (empty)
380 # base-commit: xxx
381 # branch: xxx
382 self.assertEqual('base-commit: 1a44532', lines[pos + 3])
383 self.assertEqual('branch: mybranch', lines[pos + 4])
384
Simon Glass54f1c5b2020-07-05 21:41:50 -0600385 def make_commit_with_file(self, subject, body, fname, text):
386 """Create a file and add it to the git repo with a new commit
387
388 Args:
389 subject (str): Subject for the commit
390 body (str): Body text of the commit
391 fname (str): Filename of file to create
392 text (str): Text to put into the file
393 """
394 path = os.path.join(self.gitdir, fname)
Simon Glass80025522022-01-29 14:14:04 -0700395 tools.write_file(path, text, binary=False)
Simon Glass54f1c5b2020-07-05 21:41:50 -0600396 index = self.repo.index
397 index.add(fname)
Simon Glass547cba62022-02-11 13:23:18 -0700398 # pylint doesn't seem to find this
399 # pylint: disable=E1101
Simon Glass95745aa2020-10-29 21:46:13 -0600400 author = pygit2.Signature('Test user', 'test@email.com')
Simon Glass54f1c5b2020-07-05 21:41:50 -0600401 committer = author
402 tree = index.write_tree()
403 message = subject + '\n' + body
404 self.repo.create_commit('HEAD', author, committer, message, tree,
405 [self.repo.head.target])
406
407 def make_git_tree(self):
408 """Make a simple git tree suitable for testing
409
410 It has three branches:
411 'base' has two commits: PCI, main
412 'first' has base as upstream and two more commits: I2C, SPI
413 'second' has base as upstream and three more: video, serial, bootm
414
415 Returns:
Simon Glasseb209e52020-10-29 21:46:15 -0600416 pygit2.Repository: repository
Simon Glass54f1c5b2020-07-05 21:41:50 -0600417 """
418 repo = pygit2.init_repository(self.gitdir)
419 self.repo = repo
420 new_tree = repo.TreeBuilder().write()
421
Simon Glass547cba62022-02-11 13:23:18 -0700422 # pylint doesn't seem to find this
423 # pylint: disable=E1101
Simon Glass54f1c5b2020-07-05 21:41:50 -0600424 author = pygit2.Signature('Test user', 'test@email.com')
425 committer = author
Simon Glasseb209e52020-10-29 21:46:15 -0600426 _ = repo.create_commit('HEAD', author, committer, 'Created master',
427 new_tree, [])
Simon Glass54f1c5b2020-07-05 21:41:50 -0600428
429 self.make_commit_with_file('Initial commit', '''
430Add a README
431
432''', 'README', '''This is the README file
433describing this project
434in very little detail''')
435
436 self.make_commit_with_file('pci: PCI implementation', '''
437Here is a basic PCI implementation
438
439''', 'pci.c', '''This is a file
440it has some contents
441and some more things''')
442 self.make_commit_with_file('main: Main program', '''
443Hello here is the second commit.
444''', 'main.c', '''This is the main file
445there is very little here
446but we can always add more later
447if we want to
448
449Series-to: u-boot
450Series-cc: Barry Crump <bcrump@whataroa.nz>
451''')
452 base_target = repo.revparse_single('HEAD')
453 self.make_commit_with_file('i2c: I2C things', '''
454This has some stuff to do with I2C
455''', 'i2c.c', '''And this is the file contents
456with some I2C-related things in it''')
457 self.make_commit_with_file('spi: SPI fixes', '''
458SPI needs some fixes
459and here they are
Simon Glassd0a0a582020-10-29 21:46:36 -0600460
461Signed-off-by: %s
462
463Series-to: u-boot
464Commit-notes:
465title of the series
466This is the cover letter for the series
467with various details
468END
469''' % self.leb, 'spi.c', '''Some fixes for SPI in this
Simon Glass54f1c5b2020-07-05 21:41:50 -0600470file to make SPI work
471better than before''')
472 first_target = repo.revparse_single('HEAD')
473
474 target = repo.revparse_single('HEAD~2')
Simon Glass547cba62022-02-11 13:23:18 -0700475 # pylint doesn't seem to find this
476 # pylint: disable=E1101
Simon Glass54f1c5b2020-07-05 21:41:50 -0600477 repo.reset(target.oid, pygit2.GIT_CHECKOUT_FORCE)
478 self.make_commit_with_file('video: Some video improvements', '''
479Fix up the video so that
480it looks more purple. Purple is
481a very nice colour.
482''', 'video.c', '''More purple here
483Purple and purple
484Even more purple
485Could not be any more purple''')
486 self.make_commit_with_file('serial: Add a serial driver', '''
487Here is the serial driver
488for my chip.
489
490Cover-letter:
491Series for my board
492This series implements support
493for my glorious board.
494END
Simon Glassa80986c2020-10-29 21:46:16 -0600495Series-links: 183237
Simon Glass54f1c5b2020-07-05 21:41:50 -0600496''', 'serial.c', '''The code for the
497serial driver is here''')
498 self.make_commit_with_file('bootm: Make it boot', '''
499This makes my board boot
500with a fix to the bootm
501command
502''', 'bootm.c', '''Fix up the bootm
503command to make the code as
504complicated as possible''')
505 second_target = repo.revparse_single('HEAD')
506
507 repo.branches.local.create('first', first_target)
508 repo.config.set_multivar('branch.first.remote', '', '.')
509 repo.config.set_multivar('branch.first.merge', '', 'refs/heads/base')
510
511 repo.branches.local.create('second', second_target)
512 repo.config.set_multivar('branch.second.remote', '', '.')
513 repo.config.set_multivar('branch.second.merge', '', 'refs/heads/base')
514
515 repo.branches.local.create('base', base_target)
516 return repo
517
Simon Glassd85bb8f2022-01-29 14:14:09 -0700518 def test_branch(self):
Simon Glass54f1c5b2020-07-05 21:41:50 -0600519 """Test creating patches from a branch"""
520 repo = self.make_git_tree()
521 target = repo.lookup_reference('refs/heads/first')
Simon Glass547cba62022-02-11 13:23:18 -0700522 # pylint doesn't seem to find this
523 # pylint: disable=E1101
Simon Glass54f1c5b2020-07-05 21:41:50 -0600524 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
525 control.setup()
Heinrich Schuchardtd01d6672023-04-20 20:07:29 +0200526 orig_dir = os.getcwd()
Simon Glass54f1c5b2020-07-05 21:41:50 -0600527 try:
Simon Glass54f1c5b2020-07-05 21:41:50 -0600528 os.chdir(self.gitdir)
529
530 # Check that it can detect the current branch
Simon Glass761648b2022-01-29 14:14:11 -0700531 self.assertEqual(2, gitutil.count_commits_to_branch(None))
Simon Glass54f1c5b2020-07-05 21:41:50 -0600532 col = terminal.Color()
533 with capture_sys_output() as _:
534 _, cover_fname, patch_files = control.prepare_patches(
Simon Glassb3bf4e12020-07-05 21:41:52 -0600535 col, branch=None, count=-1, start=0, end=0,
Philipp Tomsich858531a2020-11-24 18:14:52 +0100536 ignore_binary=False, signoff=True)
Simon Glass54f1c5b2020-07-05 21:41:50 -0600537 self.assertIsNone(cover_fname)
538 self.assertEqual(2, len(patch_files))
Simon Glass2eb4da72020-07-05 21:41:51 -0600539
540 # Check that it can detect a different branch
Simon Glass761648b2022-01-29 14:14:11 -0700541 self.assertEqual(3, gitutil.count_commits_to_branch('second'))
Simon Glass2eb4da72020-07-05 21:41:51 -0600542 with capture_sys_output() as _:
Simon Glass414f1e02025-02-27 12:27:30 -0700543 series, cover_fname, patch_files = control.prepare_patches(
Simon Glassb3bf4e12020-07-05 21:41:52 -0600544 col, branch='second', count=-1, start=0, end=0,
Philipp Tomsich858531a2020-11-24 18:14:52 +0100545 ignore_binary=False, signoff=True)
Simon Glass2eb4da72020-07-05 21:41:51 -0600546 self.assertIsNotNone(cover_fname)
547 self.assertEqual(3, len(patch_files))
Simon Glassb3bf4e12020-07-05 21:41:52 -0600548
Simon Glass414f1e02025-02-27 12:27:30 -0700549 cover = tools.read_file(cover_fname, binary=False)
550 lines = cover.splitlines()[-2:]
551 base = repo.lookup_reference('refs/heads/base').target
552 self.assertEqual(f'base-commit: {base}', lines[0])
553 self.assertEqual('branch: second', lines[1])
554
Simon Glassda1a6ec2025-03-28 07:02:20 -0600555 # Make sure that the base-commit is not present when it is in the
556 # cover letter
557 for fname in patch_files:
558 self.assertNotIn(b'base-commit:', tools.read_file(fname))
559
Simon Glassb3bf4e12020-07-05 21:41:52 -0600560 # Check that it can skip patches at the end
561 with capture_sys_output() as _:
562 _, cover_fname, patch_files = control.prepare_patches(
563 col, branch='second', count=-1, start=0, end=1,
Philipp Tomsich858531a2020-11-24 18:14:52 +0100564 ignore_binary=False, signoff=True)
Simon Glassb3bf4e12020-07-05 21:41:52 -0600565 self.assertIsNotNone(cover_fname)
566 self.assertEqual(2, len(patch_files))
Simon Glass414f1e02025-02-27 12:27:30 -0700567
568 cover = tools.read_file(cover_fname, binary=False)
569 lines = cover.splitlines()[-2:]
570 base2 = repo.lookup_reference('refs/heads/second')
571 ref = base2.peel(pygit2.GIT_OBJ_COMMIT).parents[0].parents[0].id
572 self.assertEqual(f'base-commit: {ref}', lines[0])
573 self.assertEqual('branch: second', lines[1])
Simon Glass54f1c5b2020-07-05 21:41:50 -0600574 finally:
575 os.chdir(orig_dir)
Simon Glass06202d62020-10-29 21:46:27 -0600576
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500577 def test_custom_get_maintainer_script(self):
578 """Validate that a custom get_maintainer script gets used."""
579 self.make_git_tree()
580 with directory_excursion(self.gitdir):
581 # Setup git.
582 os.environ['GIT_CONFIG_GLOBAL'] = '/dev/null'
583 os.environ['GIT_CONFIG_SYSTEM'] = '/dev/null'
584 tools.run('git', 'config', 'user.name', 'Dummy')
585 tools.run('git', 'config', 'user.email', 'dumdum@dummy.com')
586 tools.run('git', 'branch', 'upstream')
587 tools.run('git', 'branch', '--set-upstream-to=upstream')
588 tools.run('git', 'add', '.')
589 tools.run('git', 'commit', '-m', 'new commit')
590
591 # Setup patman configuration.
592 with open('.patman', 'w', buffering=1) as f:
593 f.write('[settings]\n'
594 'get_maintainer_script: dummy-script.sh\n'
Sean Andersona06df742024-04-18 22:36:30 -0400595 'check_patch: False\n'
596 'add_maintainers: True\n')
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500597 with open('dummy-script.sh', 'w', buffering=1) as f:
598 f.write('#!/usr/bin/env python\n'
599 'print("hello@there.com")\n')
600 os.chmod('dummy-script.sh', 0x555)
601
602 # Finally, do the test
603 with capture_sys_output():
604 output = tools.run(PATMAN_DIR / 'patman', '--dry-run')
605 # Assert the email address is part of the dry-run
606 # output.
607 self.assertIn('hello@there.com', output)
608
Simon Glassd85bb8f2022-01-29 14:14:09 -0700609 def test_tags(self):
Simon Glass06202d62020-10-29 21:46:27 -0600610 """Test collection of tags in a patchstream"""
611 text = '''This is a patch
612
613Signed-off-by: Terminator
Simon Glass3b762cc2020-10-29 21:46:28 -0600614Reviewed-by: %s
615Reviewed-by: %s
Simon Glass06202d62020-10-29 21:46:27 -0600616Tested-by: %s
Simon Glass3b762cc2020-10-29 21:46:28 -0600617''' % (self.joe, self.mary, self.leb)
Simon Glass06202d62020-10-29 21:46:27 -0600618 pstrm = PatchStream.process_text(text)
619 self.assertEqual(pstrm.commit.rtags, {
Simon Glass3b762cc2020-10-29 21:46:28 -0600620 'Reviewed-by': {self.joe, self.mary},
Simon Glass06202d62020-10-29 21:46:27 -0600621 'Tested-by': {self.leb}})
Simon Glass3b762cc2020-10-29 21:46:28 -0600622
Simon Glassd85bb8f2022-01-29 14:14:09 -0700623 def test_invalid_tag(self):
Patrick Delaunay6bbdd0c2021-07-22 16:51:42 +0200624 """Test invalid tag in a patchstream"""
625 text = '''This is a patch
626
627Serie-version: 2
628'''
629 with self.assertRaises(ValueError) as exc:
630 pstrm = PatchStream.process_text(text)
631 self.assertEqual("Line 3: Invalid tag = 'Serie-version: 2'",
632 str(exc.exception))
633
Simon Glassd85bb8f2022-01-29 14:14:09 -0700634 def test_missing_end(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600635 """Test a missing END tag"""
636 text = '''This is a patch
637
638Cover-letter:
639This is the title
640missing END after this line
641Signed-off-by: Fred
642'''
643 pstrm = PatchStream.process_text(text)
644 self.assertEqual(["Missing 'END' in section 'cover'"],
645 pstrm.commit.warn)
646
Simon Glassd85bb8f2022-01-29 14:14:09 -0700647 def test_missing_blank_line(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600648 """Test a missing blank line after a tag"""
649 text = '''This is a patch
650
651Series-changes: 2
652- First line of changes
653- Missing blank line after this line
654Signed-off-by: Fred
655'''
656 pstrm = PatchStream.process_text(text)
657 self.assertEqual(["Missing 'blank line' in section 'Series-changes'"],
658 pstrm.commit.warn)
659
Simon Glassd85bb8f2022-01-29 14:14:09 -0700660 def test_invalid_commit_tag(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600661 """Test an invalid Commit-xxx tag"""
662 text = '''This is a patch
663
664Commit-fred: testing
665'''
666 pstrm = PatchStream.process_text(text)
667 self.assertEqual(["Line 3: Ignoring Commit-fred"], pstrm.commit.warn)
668
Simon Glassd85bb8f2022-01-29 14:14:09 -0700669 def test_self_test(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600670 """Test a tested by tag by this user"""
671 test_line = 'Tested-by: %s@napier.com' % os.getenv('USER')
672 text = '''This is a patch
673
674%s
675''' % test_line
676 pstrm = PatchStream.process_text(text)
677 self.assertEqual(["Ignoring '%s'" % test_line], pstrm.commit.warn)
678
Simon Glassd85bb8f2022-01-29 14:14:09 -0700679 def test_space_before_tab(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600680 """Test a space before a tab"""
681 text = '''This is a patch
682
683+ \tSomething
684'''
685 pstrm = PatchStream.process_text(text)
686 self.assertEqual(["Line 3/0 has space before tab"], pstrm.commit.warn)
687
Simon Glassd85bb8f2022-01-29 14:14:09 -0700688 def test_lines_after_test(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600689 """Test detecting lines after TEST= line"""
690 text = '''This is a patch
691
692TEST=sometest
693more lines
694here
695'''
696 pstrm = PatchStream.process_text(text)
697 self.assertEqual(["Found 2 lines after TEST="], pstrm.commit.warn)
698
Simon Glassd85bb8f2022-01-29 14:14:09 -0700699 def test_blank_line_at_end(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600700 """Test detecting a blank line at the end of a file"""
701 text = '''This is a patch
702
703diff --git a/lib/fdtdec.c b/lib/fdtdec.c
704index c072e54..942244f 100644
705--- a/lib/fdtdec.c
706+++ b/lib/fdtdec.c
707@@ -1200,7 +1200,8 @@ int fdtdec_setup_mem_size_base(void)
708 }
709
710 gd->ram_size = (phys_size_t)(res.end - res.start + 1);
711- debug("%s: Initial DRAM size %llx\n", __func__, (u64)gd->ram_size);
712+ debug("%s: Initial DRAM size %llx\n", __func__,
713+ (unsigned long long)gd->ram_size);
714+
715diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c
716
717--
7182.7.4
719
720 '''
721 pstrm = PatchStream.process_text(text)
722 self.assertEqual(
723 ["Found possible blank line(s) at end of file 'lib/fdtdec.c'"],
724 pstrm.commit.warn)
Simon Glass1c1f2072020-10-29 21:46:34 -0600725
Simon Glassd85bb8f2022-01-29 14:14:09 -0700726 def test_no_upstream(self):
Simon Glass1c1f2072020-10-29 21:46:34 -0600727 """Test CountCommitsToBranch when there is no upstream"""
728 repo = self.make_git_tree()
729 target = repo.lookup_reference('refs/heads/base')
Simon Glass547cba62022-02-11 13:23:18 -0700730 # pylint doesn't seem to find this
731 # pylint: disable=E1101
Simon Glass1c1f2072020-10-29 21:46:34 -0600732 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
733
734 # Check that it can detect the current branch
Heinrich Schuchardtd01d6672023-04-20 20:07:29 +0200735 orig_dir = os.getcwd()
Simon Glass1c1f2072020-10-29 21:46:34 -0600736 try:
Simon Glass1c1f2072020-10-29 21:46:34 -0600737 os.chdir(self.gitdir)
738 with self.assertRaises(ValueError) as exc:
Simon Glass761648b2022-01-29 14:14:11 -0700739 gitutil.count_commits_to_branch(None)
Simon Glass1c1f2072020-10-29 21:46:34 -0600740 self.assertIn(
741 "Failed to determine upstream: fatal: no upstream configured for branch 'base'",
742 str(exc.exception))
743 finally:
744 os.chdir(orig_dir)
Simon Glass3db916d2020-10-29 21:46:35 -0600745
746 @staticmethod
Simon Glassf9b03cf2020-11-03 13:54:14 -0700747 def _fake_patchwork(url, subpath):
Simon Glass3db916d2020-10-29 21:46:35 -0600748 """Fake Patchwork server for the function below
749
750 This handles accessing a series, providing a list consisting of a
751 single patch
Simon Glassf9b03cf2020-11-03 13:54:14 -0700752
753 Args:
754 url (str): URL of patchwork server
755 subpath (str): URL subpath to use
Simon Glass3db916d2020-10-29 21:46:35 -0600756 """
757 re_series = re.match(r'series/(\d*)/$', subpath)
758 if re_series:
759 series_num = re_series.group(1)
760 if series_num == '1234':
761 return {'patches': [
762 {'id': '1', 'name': 'Some patch'}]}
763 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
764
Simon Glassd85bb8f2022-01-29 14:14:09 -0700765 def test_status_mismatch(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600766 """Test Patchwork patches not matching the series"""
767 series = Series()
768
769 with capture_sys_output() as (_, err):
Simon Glassf9b03cf2020-11-03 13:54:14 -0700770 status.collect_patches(series, 1234, None, self._fake_patchwork)
Simon Glass3db916d2020-10-29 21:46:35 -0600771 self.assertIn('Warning: Patchwork reports 1 patches, series has 0',
772 err.getvalue())
773
Simon Glassd85bb8f2022-01-29 14:14:09 -0700774 def test_status_read_patch(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600775 """Test handling a single patch in Patchwork"""
776 series = Series()
777 series.commits = [Commit('abcd')]
778
Simon Glassf9b03cf2020-11-03 13:54:14 -0700779 patches = status.collect_patches(series, 1234, None,
780 self._fake_patchwork)
Simon Glass3db916d2020-10-29 21:46:35 -0600781 self.assertEqual(1, len(patches))
782 patch = patches[0]
783 self.assertEqual('1', patch.id)
784 self.assertEqual('Some patch', patch.raw_subject)
785
Simon Glassd85bb8f2022-01-29 14:14:09 -0700786 def test_parse_subject(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600787 """Test parsing of the patch subject"""
788 patch = status.Patch('1')
789
790 # Simple patch not in a series
791 patch.parse_subject('Testing')
792 self.assertEqual('Testing', patch.raw_subject)
793 self.assertEqual('Testing', patch.subject)
794 self.assertEqual(1, patch.seq)
795 self.assertEqual(1, patch.count)
796 self.assertEqual(None, patch.prefix)
797 self.assertEqual(None, patch.version)
798
799 # First patch in a series
800 patch.parse_subject('[1/2] Testing')
801 self.assertEqual('[1/2] Testing', patch.raw_subject)
802 self.assertEqual('Testing', patch.subject)
803 self.assertEqual(1, patch.seq)
804 self.assertEqual(2, patch.count)
805 self.assertEqual(None, patch.prefix)
806 self.assertEqual(None, patch.version)
807
808 # Second patch in a series
809 patch.parse_subject('[2/2] Testing')
810 self.assertEqual('Testing', patch.subject)
811 self.assertEqual(2, patch.seq)
812 self.assertEqual(2, patch.count)
813 self.assertEqual(None, patch.prefix)
814 self.assertEqual(None, patch.version)
815
816 # RFC patch
817 patch.parse_subject('[RFC,3/7] Testing')
818 self.assertEqual('Testing', patch.subject)
819 self.assertEqual(3, patch.seq)
820 self.assertEqual(7, patch.count)
821 self.assertEqual('RFC', patch.prefix)
822 self.assertEqual(None, patch.version)
823
824 # Version patch
825 patch.parse_subject('[v2,3/7] Testing')
826 self.assertEqual('Testing', patch.subject)
827 self.assertEqual(3, patch.seq)
828 self.assertEqual(7, patch.count)
829 self.assertEqual(None, patch.prefix)
830 self.assertEqual('v2', patch.version)
831
832 # All fields
833 patch.parse_subject('[RESEND,v2,3/7] Testing')
834 self.assertEqual('Testing', patch.subject)
835 self.assertEqual(3, patch.seq)
836 self.assertEqual(7, patch.count)
837 self.assertEqual('RESEND', patch.prefix)
838 self.assertEqual('v2', patch.version)
839
840 # RFC only
841 patch.parse_subject('[RESEND] Testing')
842 self.assertEqual('Testing', patch.subject)
843 self.assertEqual(1, patch.seq)
844 self.assertEqual(1, patch.count)
845 self.assertEqual('RESEND', patch.prefix)
846 self.assertEqual(None, patch.version)
847
Simon Glassd85bb8f2022-01-29 14:14:09 -0700848 def test_compare_series(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600849 """Test operation of compare_with_series()"""
850 commit1 = Commit('abcd')
851 commit1.subject = 'Subject 1'
852 commit2 = Commit('ef12')
853 commit2.subject = 'Subject 2'
854 commit3 = Commit('3456')
855 commit3.subject = 'Subject 2'
856
857 patch1 = status.Patch('1')
858 patch1.subject = 'Subject 1'
859 patch2 = status.Patch('2')
860 patch2.subject = 'Subject 2'
861 patch3 = status.Patch('3')
862 patch3.subject = 'Subject 2'
863
864 series = Series()
865 series.commits = [commit1]
866 patches = [patch1]
867 patch_for_commit, commit_for_patch, warnings = (
868 status.compare_with_series(series, patches))
869 self.assertEqual(1, len(patch_for_commit))
870 self.assertEqual(patch1, patch_for_commit[0])
871 self.assertEqual(1, len(commit_for_patch))
872 self.assertEqual(commit1, commit_for_patch[0])
873
874 series.commits = [commit1]
875 patches = [patch1, patch2]
876 patch_for_commit, commit_for_patch, warnings = (
877 status.compare_with_series(series, patches))
878 self.assertEqual(1, len(patch_for_commit))
879 self.assertEqual(patch1, patch_for_commit[0])
880 self.assertEqual(1, len(commit_for_patch))
881 self.assertEqual(commit1, commit_for_patch[0])
882 self.assertEqual(["Cannot find commit for patch 2 ('Subject 2')"],
883 warnings)
884
885 series.commits = [commit1, commit2]
886 patches = [patch1]
887 patch_for_commit, commit_for_patch, warnings = (
888 status.compare_with_series(series, patches))
889 self.assertEqual(1, len(patch_for_commit))
890 self.assertEqual(patch1, patch_for_commit[0])
891 self.assertEqual(1, len(commit_for_patch))
892 self.assertEqual(commit1, commit_for_patch[0])
893 self.assertEqual(["Cannot find patch for commit 2 ('Subject 2')"],
894 warnings)
895
896 series.commits = [commit1, commit2, commit3]
897 patches = [patch1, patch2]
898 patch_for_commit, commit_for_patch, warnings = (
899 status.compare_with_series(series, patches))
900 self.assertEqual(2, len(patch_for_commit))
901 self.assertEqual(patch1, patch_for_commit[0])
902 self.assertEqual(patch2, patch_for_commit[1])
903 self.assertEqual(1, len(commit_for_patch))
904 self.assertEqual(commit1, commit_for_patch[0])
905 self.assertEqual(["Cannot find patch for commit 3 ('Subject 2')",
906 "Multiple commits match patch 2 ('Subject 2'):\n"
907 ' Subject 2\n Subject 2'],
908 warnings)
909
910 series.commits = [commit1, commit2]
911 patches = [patch1, patch2, patch3]
912 patch_for_commit, commit_for_patch, warnings = (
913 status.compare_with_series(series, patches))
914 self.assertEqual(1, len(patch_for_commit))
915 self.assertEqual(patch1, patch_for_commit[0])
916 self.assertEqual(2, len(commit_for_patch))
917 self.assertEqual(commit1, commit_for_patch[0])
918 self.assertEqual(["Multiple patches match commit 2 ('Subject 2'):\n"
919 ' Subject 2\n Subject 2',
920 "Cannot find commit for patch 3 ('Subject 2')"],
921 warnings)
922
Simon Glassf9b03cf2020-11-03 13:54:14 -0700923 def _fake_patchwork2(self, url, subpath):
Simon Glass3db916d2020-10-29 21:46:35 -0600924 """Fake Patchwork server for the function below
925
926 This handles accessing series, patches and comments, providing the data
927 in self.patches to the caller
Simon Glassf9b03cf2020-11-03 13:54:14 -0700928
929 Args:
930 url (str): URL of patchwork server
931 subpath (str): URL subpath to use
Simon Glass3db916d2020-10-29 21:46:35 -0600932 """
933 re_series = re.match(r'series/(\d*)/$', subpath)
934 re_patch = re.match(r'patches/(\d*)/$', subpath)
935 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
936 if re_series:
937 series_num = re_series.group(1)
938 if series_num == '1234':
939 return {'patches': self.patches}
940 elif re_patch:
941 patch_num = int(re_patch.group(1))
942 patch = self.patches[patch_num - 1]
943 return patch
944 elif re_comments:
945 patch_num = int(re_comments.group(1))
946 patch = self.patches[patch_num - 1]
947 return patch.comments
948 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
949
Simon Glassd85bb8f2022-01-29 14:14:09 -0700950 def test_find_new_responses(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600951 """Test operation of find_new_responses()"""
952 commit1 = Commit('abcd')
953 commit1.subject = 'Subject 1'
954 commit2 = Commit('ef12')
955 commit2.subject = 'Subject 2'
956
957 patch1 = status.Patch('1')
958 patch1.parse_subject('[1/2] Subject 1')
959 patch1.name = patch1.raw_subject
960 patch1.content = 'This is my patch content'
961 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
962
963 patch1.comments = [comment1a]
964
965 patch2 = status.Patch('2')
966 patch2.parse_subject('[2/2] Subject 2')
967 patch2.name = patch2.raw_subject
968 patch2.content = 'Some other patch content'
969 comment2a = {
970 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
971 (self.mary, self.leb)}
972 comment2b = {'content': 'Reviewed-by: %s' % self.fred}
973 patch2.comments = [comment2a, comment2b]
974
975 # This test works by setting up commits and patch for use by the fake
976 # Rest API function _fake_patchwork2(). It calls various functions in
977 # the status module after setting up tags in the commits, checking that
978 # things behaves as expected
979 self.commits = [commit1, commit2]
980 self.patches = [patch1, patch2]
981 count = 2
982 new_rtag_list = [None] * count
Simon Glass2112d072020-10-29 21:46:38 -0600983 review_list = [None, None]
Simon Glass3db916d2020-10-29 21:46:35 -0600984
985 # Check that the tags are picked up on the first patch
Simon Glass2112d072020-10-29 21:46:38 -0600986 status.find_new_responses(new_rtag_list, review_list, 0, commit1,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700987 patch1, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -0600988 self.assertEqual(new_rtag_list[0], {'Reviewed-by': {self.joe}})
989
990 # Now the second patch
Simon Glass2112d072020-10-29 21:46:38 -0600991 status.find_new_responses(new_rtag_list, review_list, 1, commit2,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700992 patch2, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -0600993 self.assertEqual(new_rtag_list[1], {
994 'Reviewed-by': {self.mary, self.fred},
995 'Tested-by': {self.leb}})
996
997 # Now add some tags to the commit, which means they should not appear as
998 # 'new' tags when scanning comments
999 new_rtag_list = [None] * count
1000 commit1.rtags = {'Reviewed-by': {self.joe}}
Simon Glass2112d072020-10-29 21:46:38 -06001001 status.find_new_responses(new_rtag_list, review_list, 0, commit1,
Simon Glassf9b03cf2020-11-03 13:54:14 -07001002 patch1, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -06001003 self.assertEqual(new_rtag_list[0], {})
1004
1005 # For the second commit, add Ed and Fred, so only Mary should be left
1006 commit2.rtags = {
1007 'Tested-by': {self.leb},
1008 'Reviewed-by': {self.fred}}
Simon Glass2112d072020-10-29 21:46:38 -06001009 status.find_new_responses(new_rtag_list, review_list, 1, commit2,
Simon Glassf9b03cf2020-11-03 13:54:14 -07001010 patch2, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -06001011 self.assertEqual(new_rtag_list[1], {'Reviewed-by': {self.mary}})
1012
1013 # Check that the output patches expectations:
1014 # 1 Subject 1
1015 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1016 # 2 Subject 2
1017 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1018 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1019 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1020 # 1 new response available in patchwork
1021
1022 series = Series()
1023 series.commits = [commit1, commit2]
Simon Glass02811582022-01-29 14:14:18 -07001024 terminal.set_print_test_mode()
Simon Glass2112d072020-10-29 21:46:38 -06001025 status.check_patchwork_status(series, '1234', None, None, False, False,
Simon Glassf9b03cf2020-11-03 13:54:14 -07001026 None, self._fake_patchwork2)
Simon Glass02811582022-01-29 14:14:18 -07001027 lines = iter(terminal.get_print_test_lines())
Simon Glass3db916d2020-10-29 21:46:35 -06001028 col = terminal.Color()
1029 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
1030 next(lines))
1031 self.assertEqual(
1032 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
1033 bright=False),
1034 next(lines))
1035 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE, bright=False),
1036 next(lines))
1037
1038 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
1039 next(lines))
1040 self.assertEqual(
Simon Glass2112d072020-10-29 21:46:38 -06001041 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
Simon Glass3db916d2020-10-29 21:46:35 -06001042 bright=False),
1043 next(lines))
Simon Glass2112d072020-10-29 21:46:38 -06001044 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE, bright=False),
Simon Glass3db916d2020-10-29 21:46:35 -06001045 next(lines))
1046 self.assertEqual(
Simon Glass2112d072020-10-29 21:46:38 -06001047 terminal.PrintLine(' Tested-by: ', col.GREEN, newline=False,
Simon Glass3db916d2020-10-29 21:46:35 -06001048 bright=False),
1049 next(lines))
Simon Glass2112d072020-10-29 21:46:38 -06001050 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE, bright=False),
Simon Glass3db916d2020-10-29 21:46:35 -06001051 next(lines))
1052 self.assertEqual(
1053 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1054 next(lines))
1055 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
1056 next(lines))
1057 self.assertEqual(terminal.PrintLine(
Simon Glassd0a0a582020-10-29 21:46:36 -06001058 '1 new response available in patchwork (use -d to write them to a new branch)',
1059 None), next(lines))
1060
Simon Glassf9b03cf2020-11-03 13:54:14 -07001061 def _fake_patchwork3(self, url, subpath):
Simon Glassd0a0a582020-10-29 21:46:36 -06001062 """Fake Patchwork server for the function below
1063
1064 This handles accessing series, patches and comments, providing the data
1065 in self.patches to the caller
Simon Glassf9b03cf2020-11-03 13:54:14 -07001066
1067 Args:
1068 url (str): URL of patchwork server
1069 subpath (str): URL subpath to use
Simon Glassd0a0a582020-10-29 21:46:36 -06001070 """
1071 re_series = re.match(r'series/(\d*)/$', subpath)
1072 re_patch = re.match(r'patches/(\d*)/$', subpath)
1073 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
1074 if re_series:
1075 series_num = re_series.group(1)
1076 if series_num == '1234':
1077 return {'patches': self.patches}
1078 elif re_patch:
1079 patch_num = int(re_patch.group(1))
1080 patch = self.patches[patch_num - 1]
1081 return patch
1082 elif re_comments:
1083 patch_num = int(re_comments.group(1))
1084 patch = self.patches[patch_num - 1]
1085 return patch.comments
1086 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
1087
Simon Glassd85bb8f2022-01-29 14:14:09 -07001088 def test_create_branch(self):
Simon Glassd0a0a582020-10-29 21:46:36 -06001089 """Test operation of create_branch()"""
1090 repo = self.make_git_tree()
1091 branch = 'first'
1092 dest_branch = 'first2'
1093 count = 2
1094 gitdir = os.path.join(self.gitdir, '.git')
1095
1096 # Set up the test git tree. We use branch 'first' which has two commits
1097 # in it
1098 series = patchstream.get_metadata_for_list(branch, gitdir, count)
1099 self.assertEqual(2, len(series.commits))
1100
1101 patch1 = status.Patch('1')
1102 patch1.parse_subject('[1/2] %s' % series.commits[0].subject)
1103 patch1.name = patch1.raw_subject
1104 patch1.content = 'This is my patch content'
1105 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
1106
1107 patch1.comments = [comment1a]
1108
1109 patch2 = status.Patch('2')
1110 patch2.parse_subject('[2/2] %s' % series.commits[1].subject)
1111 patch2.name = patch2.raw_subject
1112 patch2.content = 'Some other patch content'
1113 comment2a = {
1114 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1115 (self.mary, self.leb)}
1116 comment2b = {
1117 'content': 'Reviewed-by: %s' % self.fred}
1118 patch2.comments = [comment2a, comment2b]
1119
1120 # This test works by setting up patches for use by the fake Rest API
1121 # function _fake_patchwork3(). The fake patch comments above should
1122 # result in new review tags that are collected and added to the commits
1123 # created in the destination branch.
1124 self.patches = [patch1, patch2]
1125 count = 2
1126
1127 # Expected output:
1128 # 1 i2c: I2C things
1129 # + Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1130 # 2 spi: SPI fixes
1131 # + Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1132 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1133 # + Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1134 # 4 new responses available in patchwork
1135 # 4 responses added from patchwork into new branch 'first2'
1136 # <unittest.result.TestResult run=8 errors=0 failures=0>
1137
Simon Glass02811582022-01-29 14:14:18 -07001138 terminal.set_print_test_mode()
Simon Glassd0a0a582020-10-29 21:46:36 -06001139 status.check_patchwork_status(series, '1234', branch, dest_branch,
Simon Glassf9b03cf2020-11-03 13:54:14 -07001140 False, False, None, self._fake_patchwork3,
1141 repo)
Simon Glass02811582022-01-29 14:14:18 -07001142 lines = terminal.get_print_test_lines()
Simon Glassd0a0a582020-10-29 21:46:36 -06001143 self.assertEqual(12, len(lines))
1144 self.assertEqual(
1145 "4 responses added from patchwork into new branch 'first2'",
1146 lines[11].text)
1147
1148 # Check that the destination branch has the new tags
1149 new_series = patchstream.get_metadata_for_list(dest_branch, gitdir,
1150 count)
1151 self.assertEqual(
1152 {'Reviewed-by': {self.joe}},
1153 new_series.commits[0].rtags)
1154 self.assertEqual(
1155 {'Tested-by': {self.leb},
1156 'Reviewed-by': {self.fred, self.mary}},
1157 new_series.commits[1].rtags)
1158
1159 # Now check the actual test of the first commit message. We expect to
1160 # see the new tags immediately below the old ones.
1161 stdout = patchstream.get_list(dest_branch, count=count, git_dir=gitdir)
1162 lines = iter([line.strip() for line in stdout.splitlines()
1163 if '-by:' in line])
1164
1165 # First patch should have the review tag
1166 self.assertEqual('Reviewed-by: %s' % self.joe, next(lines))
1167
1168 # Second patch should have the sign-off then the tested-by and two
1169 # reviewed-by tags
1170 self.assertEqual('Signed-off-by: %s' % self.leb, next(lines))
1171 self.assertEqual('Reviewed-by: %s' % self.fred, next(lines))
1172 self.assertEqual('Reviewed-by: %s' % self.mary, next(lines))
1173 self.assertEqual('Tested-by: %s' % self.leb, next(lines))
Simon Glassda8a2922020-10-29 21:46:37 -06001174
Simon Glassd85bb8f2022-01-29 14:14:09 -07001175 def test_parse_snippets(self):
Simon Glassda8a2922020-10-29 21:46:37 -06001176 """Test parsing of review snippets"""
1177 text = '''Hi Fred,
1178
1179This is a comment from someone.
1180
1181Something else
1182
1183On some recent date, Fred wrote:
1184> This is why I wrote the patch
1185> so here it is
1186
1187Now a comment about the commit message
1188A little more to say
1189
1190Even more
1191
1192> diff --git a/file.c b/file.c
1193> Some more code
1194> Code line 2
1195> Code line 3
1196> Code line 4
1197> Code line 5
1198> Code line 6
1199> Code line 7
1200> Code line 8
1201> Code line 9
1202
1203And another comment
1204
Simon Glassd85bb8f2022-01-29 14:14:09 -07001205> @@ -153,8 +143,13 @@ def check_patch(fname, show_types=False):
Simon Glassda8a2922020-10-29 21:46:37 -06001206> further down on the file
1207> and more code
1208> +Addition here
1209> +Another addition here
1210> codey
1211> more codey
1212
1213and another thing in same file
1214
1215> @@ -253,8 +243,13 @@
1216> with no function context
1217
1218one more thing
1219
1220> diff --git a/tools/patman/main.py b/tools/patman/main.py
1221> +line of code
1222now a very long comment in a different file
1223line2
1224line3
1225line4
1226line5
1227line6
1228line7
1229line8
1230'''
1231 pstrm = PatchStream.process_text(text, True)
1232 self.assertEqual([], pstrm.commit.warn)
1233
1234 # We expect to the filename and up to 5 lines of code context before
1235 # each comment. The 'On xxx wrote:' bit should be removed.
1236 self.assertEqual(
1237 [['Hi Fred,',
1238 'This is a comment from someone.',
1239 'Something else'],
1240 ['> This is why I wrote the patch',
1241 '> so here it is',
1242 'Now a comment about the commit message',
1243 'A little more to say', 'Even more'],
1244 ['> File: file.c', '> Code line 5', '> Code line 6',
1245 '> Code line 7', '> Code line 8', '> Code line 9',
1246 'And another comment'],
1247 ['> File: file.c',
Simon Glassd85bb8f2022-01-29 14:14:09 -07001248 '> Line: 153 / 143: def check_patch(fname, show_types=False):',
Simon Glassda8a2922020-10-29 21:46:37 -06001249 '> and more code', '> +Addition here', '> +Another addition here',
1250 '> codey', '> more codey', 'and another thing in same file'],
1251 ['> File: file.c', '> Line: 253 / 243',
1252 '> with no function context', 'one more thing'],
1253 ['> File: tools/patman/main.py', '> +line of code',
1254 'now a very long comment in a different file',
1255 'line2', 'line3', 'line4', 'line5', 'line6', 'line7', 'line8']],
1256 pstrm.snippets)
Simon Glass2112d072020-10-29 21:46:38 -06001257
Simon Glassd85bb8f2022-01-29 14:14:09 -07001258 def test_review_snippets(self):
Simon Glass2112d072020-10-29 21:46:38 -06001259 """Test showing of review snippets"""
1260 def _to_submitter(who):
1261 m_who = re.match('(.*) <(.*)>', who)
1262 return {
1263 'name': m_who.group(1),
1264 'email': m_who.group(2)
1265 }
1266
1267 commit1 = Commit('abcd')
1268 commit1.subject = 'Subject 1'
1269 commit2 = Commit('ef12')
1270 commit2.subject = 'Subject 2'
1271
1272 patch1 = status.Patch('1')
1273 patch1.parse_subject('[1/2] Subject 1')
1274 patch1.name = patch1.raw_subject
1275 patch1.content = 'This is my patch content'
1276 comment1a = {'submitter': _to_submitter(self.joe),
1277 'content': '''Hi Fred,
1278
1279On some date Fred wrote:
1280
1281> diff --git a/file.c b/file.c
1282> Some code
1283> and more code
1284
1285Here is my comment above the above...
1286
1287
1288Reviewed-by: %s
1289''' % self.joe}
1290
1291 patch1.comments = [comment1a]
1292
1293 patch2 = status.Patch('2')
1294 patch2.parse_subject('[2/2] Subject 2')
1295 patch2.name = patch2.raw_subject
1296 patch2.content = 'Some other patch content'
1297 comment2a = {
1298 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1299 (self.mary, self.leb)}
1300 comment2b = {'submitter': _to_submitter(self.fred),
1301 'content': '''Hi Fred,
1302
1303On some date Fred wrote:
1304
1305> diff --git a/tools/patman/commit.py b/tools/patman/commit.py
1306> @@ -41,6 +41,9 @@ class Commit:
1307> self.rtags = collections.defaultdict(set)
1308> self.warn = []
1309>
1310> + def __str__(self):
1311> + return self.subject
1312> +
Simon Glassd85bb8f2022-01-29 14:14:09 -07001313> def add_change(self, version, info):
Simon Glass2112d072020-10-29 21:46:38 -06001314> """Add a new change line to the change list for a version.
1315>
1316A comment
1317
1318Reviewed-by: %s
1319''' % self.fred}
1320 patch2.comments = [comment2a, comment2b]
1321
1322 # This test works by setting up commits and patch for use by the fake
1323 # Rest API function _fake_patchwork2(). It calls various functions in
1324 # the status module after setting up tags in the commits, checking that
1325 # things behaves as expected
1326 self.commits = [commit1, commit2]
1327 self.patches = [patch1, patch2]
1328
1329 # Check that the output patches expectations:
1330 # 1 Subject 1
1331 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1332 # 2 Subject 2
1333 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1334 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1335 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1336 # 1 new response available in patchwork
1337
1338 series = Series()
1339 series.commits = [commit1, commit2]
Simon Glass02811582022-01-29 14:14:18 -07001340 terminal.set_print_test_mode()
Simon Glass2112d072020-10-29 21:46:38 -06001341 status.check_patchwork_status(series, '1234', None, None, False, True,
Simon Glassf9b03cf2020-11-03 13:54:14 -07001342 None, self._fake_patchwork2)
Simon Glass02811582022-01-29 14:14:18 -07001343 lines = iter(terminal.get_print_test_lines())
Simon Glass2112d072020-10-29 21:46:38 -06001344 col = terminal.Color()
1345 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
1346 next(lines))
1347 self.assertEqual(
1348 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1349 next(lines))
1350 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE), next(lines))
1351
1352 self.assertEqual(terminal.PrintLine('Review: %s' % self.joe, col.RED),
1353 next(lines))
1354 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(lines))
1355 self.assertEqual(terminal.PrintLine('', None), next(lines))
1356 self.assertEqual(terminal.PrintLine(' > File: file.c', col.MAGENTA),
1357 next(lines))
1358 self.assertEqual(terminal.PrintLine(' > Some code', col.MAGENTA),
1359 next(lines))
1360 self.assertEqual(terminal.PrintLine(' > and more code', col.MAGENTA),
1361 next(lines))
1362 self.assertEqual(terminal.PrintLine(
1363 ' Here is my comment above the above...', None), next(lines))
1364 self.assertEqual(terminal.PrintLine('', None), next(lines))
1365
1366 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
1367 next(lines))
1368 self.assertEqual(
1369 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1370 next(lines))
1371 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE),
1372 next(lines))
1373 self.assertEqual(
1374 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1375 next(lines))
1376 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
1377 next(lines))
1378 self.assertEqual(
1379 terminal.PrintLine(' + Tested-by: ', col.GREEN, newline=False),
1380 next(lines))
1381 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE),
1382 next(lines))
1383
1384 self.assertEqual(terminal.PrintLine('Review: %s' % self.fred, col.RED),
1385 next(lines))
1386 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(lines))
1387 self.assertEqual(terminal.PrintLine('', None), next(lines))
1388 self.assertEqual(terminal.PrintLine(
1389 ' > File: tools/patman/commit.py', col.MAGENTA), next(lines))
1390 self.assertEqual(terminal.PrintLine(
1391 ' > Line: 41 / 41: class Commit:', col.MAGENTA), next(lines))
1392 self.assertEqual(terminal.PrintLine(
1393 ' > + return self.subject', col.MAGENTA), next(lines))
1394 self.assertEqual(terminal.PrintLine(
1395 ' > +', col.MAGENTA), next(lines))
1396 self.assertEqual(
Simon Glassd85bb8f2022-01-29 14:14:09 -07001397 terminal.PrintLine(' > def add_change(self, version, info):',
Simon Glass2112d072020-10-29 21:46:38 -06001398 col.MAGENTA),
1399 next(lines))
1400 self.assertEqual(terminal.PrintLine(
1401 ' > """Add a new change line to the change list for a version.',
1402 col.MAGENTA), next(lines))
1403 self.assertEqual(terminal.PrintLine(
1404 ' >', col.MAGENTA), next(lines))
1405 self.assertEqual(terminal.PrintLine(
1406 ' A comment', None), next(lines))
1407 self.assertEqual(terminal.PrintLine('', None), next(lines))
1408
1409 self.assertEqual(terminal.PrintLine(
1410 '4 new responses available in patchwork (use -d to write them to a new branch)',
1411 None), next(lines))
Simon Glass6a222e62021-08-01 16:02:39 -06001412
Simon Glassd85bb8f2022-01-29 14:14:09 -07001413 def test_insert_tags(self):
Simon Glass6a222e62021-08-01 16:02:39 -06001414 """Test inserting of review tags"""
1415 msg = '''first line
1416second line.'''
1417 tags = [
1418 'Reviewed-by: Bin Meng <bmeng.cn@gmail.com>',
1419 'Tested-by: Bin Meng <bmeng.cn@gmail.com>'
1420 ]
1421 signoff = 'Signed-off-by: Simon Glass <sjg@chromium.com>'
1422 tag_str = '\n'.join(tags)
1423
1424 new_msg = patchstream.insert_tags(msg, tags)
1425 self.assertEqual(msg + '\n\n' + tag_str, new_msg)
1426
1427 new_msg = patchstream.insert_tags(msg + '\n', tags)
1428 self.assertEqual(msg + '\n\n' + tag_str, new_msg)
1429
1430 msg += '\n\n' + signoff
1431 new_msg = patchstream.insert_tags(msg, tags)
1432 self.assertEqual(msg + '\n' + tag_str, new_msg)