blob: 44eba2cdbb79fe7cc3cf6afc176366b285682dde [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
360 def make_commit_with_file(self, subject, body, fname, text):
361 """Create a file and add it to the git repo with a new commit
362
363 Args:
364 subject (str): Subject for the commit
365 body (str): Body text of the commit
366 fname (str): Filename of file to create
367 text (str): Text to put into the file
368 """
369 path = os.path.join(self.gitdir, fname)
Simon Glass80025522022-01-29 14:14:04 -0700370 tools.write_file(path, text, binary=False)
Simon Glass54f1c5b2020-07-05 21:41:50 -0600371 index = self.repo.index
372 index.add(fname)
Simon Glass547cba62022-02-11 13:23:18 -0700373 # pylint doesn't seem to find this
374 # pylint: disable=E1101
Simon Glass95745aa2020-10-29 21:46:13 -0600375 author = pygit2.Signature('Test user', 'test@email.com')
Simon Glass54f1c5b2020-07-05 21:41:50 -0600376 committer = author
377 tree = index.write_tree()
378 message = subject + '\n' + body
379 self.repo.create_commit('HEAD', author, committer, message, tree,
380 [self.repo.head.target])
381
382 def make_git_tree(self):
383 """Make a simple git tree suitable for testing
384
385 It has three branches:
386 'base' has two commits: PCI, main
387 'first' has base as upstream and two more commits: I2C, SPI
388 'second' has base as upstream and three more: video, serial, bootm
389
390 Returns:
Simon Glasseb209e52020-10-29 21:46:15 -0600391 pygit2.Repository: repository
Simon Glass54f1c5b2020-07-05 21:41:50 -0600392 """
393 repo = pygit2.init_repository(self.gitdir)
394 self.repo = repo
395 new_tree = repo.TreeBuilder().write()
396
Simon Glass547cba62022-02-11 13:23:18 -0700397 # pylint doesn't seem to find this
398 # pylint: disable=E1101
Simon Glass54f1c5b2020-07-05 21:41:50 -0600399 author = pygit2.Signature('Test user', 'test@email.com')
400 committer = author
Simon Glasseb209e52020-10-29 21:46:15 -0600401 _ = repo.create_commit('HEAD', author, committer, 'Created master',
402 new_tree, [])
Simon Glass54f1c5b2020-07-05 21:41:50 -0600403
404 self.make_commit_with_file('Initial commit', '''
405Add a README
406
407''', 'README', '''This is the README file
408describing this project
409in very little detail''')
410
411 self.make_commit_with_file('pci: PCI implementation', '''
412Here is a basic PCI implementation
413
414''', 'pci.c', '''This is a file
415it has some contents
416and some more things''')
417 self.make_commit_with_file('main: Main program', '''
418Hello here is the second commit.
419''', 'main.c', '''This is the main file
420there is very little here
421but we can always add more later
422if we want to
423
424Series-to: u-boot
425Series-cc: Barry Crump <bcrump@whataroa.nz>
426''')
427 base_target = repo.revparse_single('HEAD')
428 self.make_commit_with_file('i2c: I2C things', '''
429This has some stuff to do with I2C
430''', 'i2c.c', '''And this is the file contents
431with some I2C-related things in it''')
432 self.make_commit_with_file('spi: SPI fixes', '''
433SPI needs some fixes
434and here they are
Simon Glassd0a0a582020-10-29 21:46:36 -0600435
436Signed-off-by: %s
437
438Series-to: u-boot
439Commit-notes:
440title of the series
441This is the cover letter for the series
442with various details
443END
444''' % self.leb, 'spi.c', '''Some fixes for SPI in this
Simon Glass54f1c5b2020-07-05 21:41:50 -0600445file to make SPI work
446better than before''')
447 first_target = repo.revparse_single('HEAD')
448
449 target = repo.revparse_single('HEAD~2')
Simon Glass547cba62022-02-11 13:23:18 -0700450 # pylint doesn't seem to find this
451 # pylint: disable=E1101
Simon Glass54f1c5b2020-07-05 21:41:50 -0600452 repo.reset(target.oid, pygit2.GIT_CHECKOUT_FORCE)
453 self.make_commit_with_file('video: Some video improvements', '''
454Fix up the video so that
455it looks more purple. Purple is
456a very nice colour.
457''', 'video.c', '''More purple here
458Purple and purple
459Even more purple
460Could not be any more purple''')
461 self.make_commit_with_file('serial: Add a serial driver', '''
462Here is the serial driver
463for my chip.
464
465Cover-letter:
466Series for my board
467This series implements support
468for my glorious board.
469END
Simon Glassa80986c2020-10-29 21:46:16 -0600470Series-links: 183237
Simon Glass54f1c5b2020-07-05 21:41:50 -0600471''', 'serial.c', '''The code for the
472serial driver is here''')
473 self.make_commit_with_file('bootm: Make it boot', '''
474This makes my board boot
475with a fix to the bootm
476command
477''', 'bootm.c', '''Fix up the bootm
478command to make the code as
479complicated as possible''')
480 second_target = repo.revparse_single('HEAD')
481
482 repo.branches.local.create('first', first_target)
483 repo.config.set_multivar('branch.first.remote', '', '.')
484 repo.config.set_multivar('branch.first.merge', '', 'refs/heads/base')
485
486 repo.branches.local.create('second', second_target)
487 repo.config.set_multivar('branch.second.remote', '', '.')
488 repo.config.set_multivar('branch.second.merge', '', 'refs/heads/base')
489
490 repo.branches.local.create('base', base_target)
491 return repo
492
Simon Glassd85bb8f2022-01-29 14:14:09 -0700493 def test_branch(self):
Simon Glass54f1c5b2020-07-05 21:41:50 -0600494 """Test creating patches from a branch"""
495 repo = self.make_git_tree()
496 target = repo.lookup_reference('refs/heads/first')
Simon Glass547cba62022-02-11 13:23:18 -0700497 # pylint doesn't seem to find this
498 # pylint: disable=E1101
Simon Glass54f1c5b2020-07-05 21:41:50 -0600499 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
500 control.setup()
Heinrich Schuchardtd01d6672023-04-20 20:07:29 +0200501 orig_dir = os.getcwd()
Simon Glass54f1c5b2020-07-05 21:41:50 -0600502 try:
Simon Glass54f1c5b2020-07-05 21:41:50 -0600503 os.chdir(self.gitdir)
504
505 # Check that it can detect the current branch
Simon Glass761648b2022-01-29 14:14:11 -0700506 self.assertEqual(2, gitutil.count_commits_to_branch(None))
Simon Glass54f1c5b2020-07-05 21:41:50 -0600507 col = terminal.Color()
508 with capture_sys_output() as _:
509 _, cover_fname, patch_files = control.prepare_patches(
Simon Glassb3bf4e12020-07-05 21:41:52 -0600510 col, branch=None, count=-1, start=0, end=0,
Philipp Tomsich858531a2020-11-24 18:14:52 +0100511 ignore_binary=False, signoff=True)
Simon Glass54f1c5b2020-07-05 21:41:50 -0600512 self.assertIsNone(cover_fname)
513 self.assertEqual(2, len(patch_files))
Simon Glass2eb4da72020-07-05 21:41:51 -0600514
515 # Check that it can detect a different branch
Simon Glass761648b2022-01-29 14:14:11 -0700516 self.assertEqual(3, gitutil.count_commits_to_branch('second'))
Simon Glass2eb4da72020-07-05 21:41:51 -0600517 with capture_sys_output() as _:
Simon Glass414f1e02025-02-27 12:27:30 -0700518 series, cover_fname, patch_files = control.prepare_patches(
Simon Glassb3bf4e12020-07-05 21:41:52 -0600519 col, branch='second', count=-1, start=0, end=0,
Philipp Tomsich858531a2020-11-24 18:14:52 +0100520 ignore_binary=False, signoff=True)
Simon Glass2eb4da72020-07-05 21:41:51 -0600521 self.assertIsNotNone(cover_fname)
522 self.assertEqual(3, len(patch_files))
Simon Glassb3bf4e12020-07-05 21:41:52 -0600523
Simon Glass414f1e02025-02-27 12:27:30 -0700524 cover = tools.read_file(cover_fname, binary=False)
525 lines = cover.splitlines()[-2:]
526 base = repo.lookup_reference('refs/heads/base').target
527 self.assertEqual(f'base-commit: {base}', lines[0])
528 self.assertEqual('branch: second', lines[1])
529
Simon Glassb3bf4e12020-07-05 21:41:52 -0600530 # Check that it can skip patches at the end
531 with capture_sys_output() as _:
532 _, cover_fname, patch_files = control.prepare_patches(
533 col, branch='second', count=-1, start=0, end=1,
Philipp Tomsich858531a2020-11-24 18:14:52 +0100534 ignore_binary=False, signoff=True)
Simon Glassb3bf4e12020-07-05 21:41:52 -0600535 self.assertIsNotNone(cover_fname)
536 self.assertEqual(2, len(patch_files))
Simon Glass414f1e02025-02-27 12:27:30 -0700537
538 cover = tools.read_file(cover_fname, binary=False)
539 lines = cover.splitlines()[-2:]
540 base2 = repo.lookup_reference('refs/heads/second')
541 ref = base2.peel(pygit2.GIT_OBJ_COMMIT).parents[0].parents[0].id
542 self.assertEqual(f'base-commit: {ref}', lines[0])
543 self.assertEqual('branch: second', lines[1])
Simon Glass54f1c5b2020-07-05 21:41:50 -0600544 finally:
545 os.chdir(orig_dir)
Simon Glass06202d62020-10-29 21:46:27 -0600546
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500547 def test_custom_get_maintainer_script(self):
548 """Validate that a custom get_maintainer script gets used."""
549 self.make_git_tree()
550 with directory_excursion(self.gitdir):
551 # Setup git.
552 os.environ['GIT_CONFIG_GLOBAL'] = '/dev/null'
553 os.environ['GIT_CONFIG_SYSTEM'] = '/dev/null'
554 tools.run('git', 'config', 'user.name', 'Dummy')
555 tools.run('git', 'config', 'user.email', 'dumdum@dummy.com')
556 tools.run('git', 'branch', 'upstream')
557 tools.run('git', 'branch', '--set-upstream-to=upstream')
558 tools.run('git', 'add', '.')
559 tools.run('git', 'commit', '-m', 'new commit')
560
561 # Setup patman configuration.
562 with open('.patman', 'w', buffering=1) as f:
563 f.write('[settings]\n'
564 'get_maintainer_script: dummy-script.sh\n'
Sean Andersona06df742024-04-18 22:36:30 -0400565 'check_patch: False\n'
566 'add_maintainers: True\n')
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500567 with open('dummy-script.sh', 'w', buffering=1) as f:
568 f.write('#!/usr/bin/env python\n'
569 'print("hello@there.com")\n')
570 os.chmod('dummy-script.sh', 0x555)
571
572 # Finally, do the test
573 with capture_sys_output():
574 output = tools.run(PATMAN_DIR / 'patman', '--dry-run')
575 # Assert the email address is part of the dry-run
576 # output.
577 self.assertIn('hello@there.com', output)
578
Simon Glassd85bb8f2022-01-29 14:14:09 -0700579 def test_tags(self):
Simon Glass06202d62020-10-29 21:46:27 -0600580 """Test collection of tags in a patchstream"""
581 text = '''This is a patch
582
583Signed-off-by: Terminator
Simon Glass3b762cc2020-10-29 21:46:28 -0600584Reviewed-by: %s
585Reviewed-by: %s
Simon Glass06202d62020-10-29 21:46:27 -0600586Tested-by: %s
Simon Glass3b762cc2020-10-29 21:46:28 -0600587''' % (self.joe, self.mary, self.leb)
Simon Glass06202d62020-10-29 21:46:27 -0600588 pstrm = PatchStream.process_text(text)
589 self.assertEqual(pstrm.commit.rtags, {
Simon Glass3b762cc2020-10-29 21:46:28 -0600590 'Reviewed-by': {self.joe, self.mary},
Simon Glass06202d62020-10-29 21:46:27 -0600591 'Tested-by': {self.leb}})
Simon Glass3b762cc2020-10-29 21:46:28 -0600592
Simon Glassd85bb8f2022-01-29 14:14:09 -0700593 def test_invalid_tag(self):
Patrick Delaunay6bbdd0c2021-07-22 16:51:42 +0200594 """Test invalid tag in a patchstream"""
595 text = '''This is a patch
596
597Serie-version: 2
598'''
599 with self.assertRaises(ValueError) as exc:
600 pstrm = PatchStream.process_text(text)
601 self.assertEqual("Line 3: Invalid tag = 'Serie-version: 2'",
602 str(exc.exception))
603
Simon Glassd85bb8f2022-01-29 14:14:09 -0700604 def test_missing_end(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600605 """Test a missing END tag"""
606 text = '''This is a patch
607
608Cover-letter:
609This is the title
610missing END after this line
611Signed-off-by: Fred
612'''
613 pstrm = PatchStream.process_text(text)
614 self.assertEqual(["Missing 'END' in section 'cover'"],
615 pstrm.commit.warn)
616
Simon Glassd85bb8f2022-01-29 14:14:09 -0700617 def test_missing_blank_line(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600618 """Test a missing blank line after a tag"""
619 text = '''This is a patch
620
621Series-changes: 2
622- First line of changes
623- Missing blank line after this line
624Signed-off-by: Fred
625'''
626 pstrm = PatchStream.process_text(text)
627 self.assertEqual(["Missing 'blank line' in section 'Series-changes'"],
628 pstrm.commit.warn)
629
Simon Glassd85bb8f2022-01-29 14:14:09 -0700630 def test_invalid_commit_tag(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600631 """Test an invalid Commit-xxx tag"""
632 text = '''This is a patch
633
634Commit-fred: testing
635'''
636 pstrm = PatchStream.process_text(text)
637 self.assertEqual(["Line 3: Ignoring Commit-fred"], pstrm.commit.warn)
638
Simon Glassd85bb8f2022-01-29 14:14:09 -0700639 def test_self_test(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600640 """Test a tested by tag by this user"""
641 test_line = 'Tested-by: %s@napier.com' % os.getenv('USER')
642 text = '''This is a patch
643
644%s
645''' % test_line
646 pstrm = PatchStream.process_text(text)
647 self.assertEqual(["Ignoring '%s'" % test_line], pstrm.commit.warn)
648
Simon Glassd85bb8f2022-01-29 14:14:09 -0700649 def test_space_before_tab(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600650 """Test a space before a tab"""
651 text = '''This is a patch
652
653+ \tSomething
654'''
655 pstrm = PatchStream.process_text(text)
656 self.assertEqual(["Line 3/0 has space before tab"], pstrm.commit.warn)
657
Simon Glassd85bb8f2022-01-29 14:14:09 -0700658 def test_lines_after_test(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600659 """Test detecting lines after TEST= line"""
660 text = '''This is a patch
661
662TEST=sometest
663more lines
664here
665'''
666 pstrm = PatchStream.process_text(text)
667 self.assertEqual(["Found 2 lines after TEST="], pstrm.commit.warn)
668
Simon Glassd85bb8f2022-01-29 14:14:09 -0700669 def test_blank_line_at_end(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600670 """Test detecting a blank line at the end of a file"""
671 text = '''This is a patch
672
673diff --git a/lib/fdtdec.c b/lib/fdtdec.c
674index c072e54..942244f 100644
675--- a/lib/fdtdec.c
676+++ b/lib/fdtdec.c
677@@ -1200,7 +1200,8 @@ int fdtdec_setup_mem_size_base(void)
678 }
679
680 gd->ram_size = (phys_size_t)(res.end - res.start + 1);
681- debug("%s: Initial DRAM size %llx\n", __func__, (u64)gd->ram_size);
682+ debug("%s: Initial DRAM size %llx\n", __func__,
683+ (unsigned long long)gd->ram_size);
684+
685diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c
686
687--
6882.7.4
689
690 '''
691 pstrm = PatchStream.process_text(text)
692 self.assertEqual(
693 ["Found possible blank line(s) at end of file 'lib/fdtdec.c'"],
694 pstrm.commit.warn)
Simon Glass1c1f2072020-10-29 21:46:34 -0600695
Simon Glassd85bb8f2022-01-29 14:14:09 -0700696 def test_no_upstream(self):
Simon Glass1c1f2072020-10-29 21:46:34 -0600697 """Test CountCommitsToBranch when there is no upstream"""
698 repo = self.make_git_tree()
699 target = repo.lookup_reference('refs/heads/base')
Simon Glass547cba62022-02-11 13:23:18 -0700700 # pylint doesn't seem to find this
701 # pylint: disable=E1101
Simon Glass1c1f2072020-10-29 21:46:34 -0600702 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
703
704 # Check that it can detect the current branch
Heinrich Schuchardtd01d6672023-04-20 20:07:29 +0200705 orig_dir = os.getcwd()
Simon Glass1c1f2072020-10-29 21:46:34 -0600706 try:
Simon Glass1c1f2072020-10-29 21:46:34 -0600707 os.chdir(self.gitdir)
708 with self.assertRaises(ValueError) as exc:
Simon Glass761648b2022-01-29 14:14:11 -0700709 gitutil.count_commits_to_branch(None)
Simon Glass1c1f2072020-10-29 21:46:34 -0600710 self.assertIn(
711 "Failed to determine upstream: fatal: no upstream configured for branch 'base'",
712 str(exc.exception))
713 finally:
714 os.chdir(orig_dir)
Simon Glass3db916d2020-10-29 21:46:35 -0600715
716 @staticmethod
Simon Glassf9b03cf2020-11-03 13:54:14 -0700717 def _fake_patchwork(url, subpath):
Simon Glass3db916d2020-10-29 21:46:35 -0600718 """Fake Patchwork server for the function below
719
720 This handles accessing a series, providing a list consisting of a
721 single patch
Simon Glassf9b03cf2020-11-03 13:54:14 -0700722
723 Args:
724 url (str): URL of patchwork server
725 subpath (str): URL subpath to use
Simon Glass3db916d2020-10-29 21:46:35 -0600726 """
727 re_series = re.match(r'series/(\d*)/$', subpath)
728 if re_series:
729 series_num = re_series.group(1)
730 if series_num == '1234':
731 return {'patches': [
732 {'id': '1', 'name': 'Some patch'}]}
733 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
734
Simon Glassd85bb8f2022-01-29 14:14:09 -0700735 def test_status_mismatch(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600736 """Test Patchwork patches not matching the series"""
737 series = Series()
738
739 with capture_sys_output() as (_, err):
Simon Glassf9b03cf2020-11-03 13:54:14 -0700740 status.collect_patches(series, 1234, None, self._fake_patchwork)
Simon Glass3db916d2020-10-29 21:46:35 -0600741 self.assertIn('Warning: Patchwork reports 1 patches, series has 0',
742 err.getvalue())
743
Simon Glassd85bb8f2022-01-29 14:14:09 -0700744 def test_status_read_patch(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600745 """Test handling a single patch in Patchwork"""
746 series = Series()
747 series.commits = [Commit('abcd')]
748
Simon Glassf9b03cf2020-11-03 13:54:14 -0700749 patches = status.collect_patches(series, 1234, None,
750 self._fake_patchwork)
Simon Glass3db916d2020-10-29 21:46:35 -0600751 self.assertEqual(1, len(patches))
752 patch = patches[0]
753 self.assertEqual('1', patch.id)
754 self.assertEqual('Some patch', patch.raw_subject)
755
Simon Glassd85bb8f2022-01-29 14:14:09 -0700756 def test_parse_subject(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600757 """Test parsing of the patch subject"""
758 patch = status.Patch('1')
759
760 # Simple patch not in a series
761 patch.parse_subject('Testing')
762 self.assertEqual('Testing', patch.raw_subject)
763 self.assertEqual('Testing', patch.subject)
764 self.assertEqual(1, patch.seq)
765 self.assertEqual(1, patch.count)
766 self.assertEqual(None, patch.prefix)
767 self.assertEqual(None, patch.version)
768
769 # First patch in a series
770 patch.parse_subject('[1/2] Testing')
771 self.assertEqual('[1/2] Testing', patch.raw_subject)
772 self.assertEqual('Testing', patch.subject)
773 self.assertEqual(1, patch.seq)
774 self.assertEqual(2, patch.count)
775 self.assertEqual(None, patch.prefix)
776 self.assertEqual(None, patch.version)
777
778 # Second patch in a series
779 patch.parse_subject('[2/2] Testing')
780 self.assertEqual('Testing', patch.subject)
781 self.assertEqual(2, patch.seq)
782 self.assertEqual(2, patch.count)
783 self.assertEqual(None, patch.prefix)
784 self.assertEqual(None, patch.version)
785
786 # RFC patch
787 patch.parse_subject('[RFC,3/7] Testing')
788 self.assertEqual('Testing', patch.subject)
789 self.assertEqual(3, patch.seq)
790 self.assertEqual(7, patch.count)
791 self.assertEqual('RFC', patch.prefix)
792 self.assertEqual(None, patch.version)
793
794 # Version patch
795 patch.parse_subject('[v2,3/7] Testing')
796 self.assertEqual('Testing', patch.subject)
797 self.assertEqual(3, patch.seq)
798 self.assertEqual(7, patch.count)
799 self.assertEqual(None, patch.prefix)
800 self.assertEqual('v2', patch.version)
801
802 # All fields
803 patch.parse_subject('[RESEND,v2,3/7] Testing')
804 self.assertEqual('Testing', patch.subject)
805 self.assertEqual(3, patch.seq)
806 self.assertEqual(7, patch.count)
807 self.assertEqual('RESEND', patch.prefix)
808 self.assertEqual('v2', patch.version)
809
810 # RFC only
811 patch.parse_subject('[RESEND] Testing')
812 self.assertEqual('Testing', patch.subject)
813 self.assertEqual(1, patch.seq)
814 self.assertEqual(1, patch.count)
815 self.assertEqual('RESEND', patch.prefix)
816 self.assertEqual(None, patch.version)
817
Simon Glassd85bb8f2022-01-29 14:14:09 -0700818 def test_compare_series(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600819 """Test operation of compare_with_series()"""
820 commit1 = Commit('abcd')
821 commit1.subject = 'Subject 1'
822 commit2 = Commit('ef12')
823 commit2.subject = 'Subject 2'
824 commit3 = Commit('3456')
825 commit3.subject = 'Subject 2'
826
827 patch1 = status.Patch('1')
828 patch1.subject = 'Subject 1'
829 patch2 = status.Patch('2')
830 patch2.subject = 'Subject 2'
831 patch3 = status.Patch('3')
832 patch3.subject = 'Subject 2'
833
834 series = Series()
835 series.commits = [commit1]
836 patches = [patch1]
837 patch_for_commit, commit_for_patch, warnings = (
838 status.compare_with_series(series, patches))
839 self.assertEqual(1, len(patch_for_commit))
840 self.assertEqual(patch1, patch_for_commit[0])
841 self.assertEqual(1, len(commit_for_patch))
842 self.assertEqual(commit1, commit_for_patch[0])
843
844 series.commits = [commit1]
845 patches = [patch1, patch2]
846 patch_for_commit, commit_for_patch, warnings = (
847 status.compare_with_series(series, patches))
848 self.assertEqual(1, len(patch_for_commit))
849 self.assertEqual(patch1, patch_for_commit[0])
850 self.assertEqual(1, len(commit_for_patch))
851 self.assertEqual(commit1, commit_for_patch[0])
852 self.assertEqual(["Cannot find commit for patch 2 ('Subject 2')"],
853 warnings)
854
855 series.commits = [commit1, commit2]
856 patches = [patch1]
857 patch_for_commit, commit_for_patch, warnings = (
858 status.compare_with_series(series, patches))
859 self.assertEqual(1, len(patch_for_commit))
860 self.assertEqual(patch1, patch_for_commit[0])
861 self.assertEqual(1, len(commit_for_patch))
862 self.assertEqual(commit1, commit_for_patch[0])
863 self.assertEqual(["Cannot find patch for commit 2 ('Subject 2')"],
864 warnings)
865
866 series.commits = [commit1, commit2, commit3]
867 patches = [patch1, patch2]
868 patch_for_commit, commit_for_patch, warnings = (
869 status.compare_with_series(series, patches))
870 self.assertEqual(2, len(patch_for_commit))
871 self.assertEqual(patch1, patch_for_commit[0])
872 self.assertEqual(patch2, patch_for_commit[1])
873 self.assertEqual(1, len(commit_for_patch))
874 self.assertEqual(commit1, commit_for_patch[0])
875 self.assertEqual(["Cannot find patch for commit 3 ('Subject 2')",
876 "Multiple commits match patch 2 ('Subject 2'):\n"
877 ' Subject 2\n Subject 2'],
878 warnings)
879
880 series.commits = [commit1, commit2]
881 patches = [patch1, patch2, patch3]
882 patch_for_commit, commit_for_patch, warnings = (
883 status.compare_with_series(series, patches))
884 self.assertEqual(1, len(patch_for_commit))
885 self.assertEqual(patch1, patch_for_commit[0])
886 self.assertEqual(2, len(commit_for_patch))
887 self.assertEqual(commit1, commit_for_patch[0])
888 self.assertEqual(["Multiple patches match commit 2 ('Subject 2'):\n"
889 ' Subject 2\n Subject 2',
890 "Cannot find commit for patch 3 ('Subject 2')"],
891 warnings)
892
Simon Glassf9b03cf2020-11-03 13:54:14 -0700893 def _fake_patchwork2(self, url, subpath):
Simon Glass3db916d2020-10-29 21:46:35 -0600894 """Fake Patchwork server for the function below
895
896 This handles accessing series, patches and comments, providing the data
897 in self.patches to the caller
Simon Glassf9b03cf2020-11-03 13:54:14 -0700898
899 Args:
900 url (str): URL of patchwork server
901 subpath (str): URL subpath to use
Simon Glass3db916d2020-10-29 21:46:35 -0600902 """
903 re_series = re.match(r'series/(\d*)/$', subpath)
904 re_patch = re.match(r'patches/(\d*)/$', subpath)
905 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
906 if re_series:
907 series_num = re_series.group(1)
908 if series_num == '1234':
909 return {'patches': self.patches}
910 elif re_patch:
911 patch_num = int(re_patch.group(1))
912 patch = self.patches[patch_num - 1]
913 return patch
914 elif re_comments:
915 patch_num = int(re_comments.group(1))
916 patch = self.patches[patch_num - 1]
917 return patch.comments
918 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
919
Simon Glassd85bb8f2022-01-29 14:14:09 -0700920 def test_find_new_responses(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600921 """Test operation of find_new_responses()"""
922 commit1 = Commit('abcd')
923 commit1.subject = 'Subject 1'
924 commit2 = Commit('ef12')
925 commit2.subject = 'Subject 2'
926
927 patch1 = status.Patch('1')
928 patch1.parse_subject('[1/2] Subject 1')
929 patch1.name = patch1.raw_subject
930 patch1.content = 'This is my patch content'
931 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
932
933 patch1.comments = [comment1a]
934
935 patch2 = status.Patch('2')
936 patch2.parse_subject('[2/2] Subject 2')
937 patch2.name = patch2.raw_subject
938 patch2.content = 'Some other patch content'
939 comment2a = {
940 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
941 (self.mary, self.leb)}
942 comment2b = {'content': 'Reviewed-by: %s' % self.fred}
943 patch2.comments = [comment2a, comment2b]
944
945 # This test works by setting up commits and patch for use by the fake
946 # Rest API function _fake_patchwork2(). It calls various functions in
947 # the status module after setting up tags in the commits, checking that
948 # things behaves as expected
949 self.commits = [commit1, commit2]
950 self.patches = [patch1, patch2]
951 count = 2
952 new_rtag_list = [None] * count
Simon Glass2112d072020-10-29 21:46:38 -0600953 review_list = [None, None]
Simon Glass3db916d2020-10-29 21:46:35 -0600954
955 # Check that the tags are picked up on the first patch
Simon Glass2112d072020-10-29 21:46:38 -0600956 status.find_new_responses(new_rtag_list, review_list, 0, commit1,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700957 patch1, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -0600958 self.assertEqual(new_rtag_list[0], {'Reviewed-by': {self.joe}})
959
960 # Now the second patch
Simon Glass2112d072020-10-29 21:46:38 -0600961 status.find_new_responses(new_rtag_list, review_list, 1, commit2,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700962 patch2, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -0600963 self.assertEqual(new_rtag_list[1], {
964 'Reviewed-by': {self.mary, self.fred},
965 'Tested-by': {self.leb}})
966
967 # Now add some tags to the commit, which means they should not appear as
968 # 'new' tags when scanning comments
969 new_rtag_list = [None] * count
970 commit1.rtags = {'Reviewed-by': {self.joe}}
Simon Glass2112d072020-10-29 21:46:38 -0600971 status.find_new_responses(new_rtag_list, review_list, 0, commit1,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700972 patch1, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -0600973 self.assertEqual(new_rtag_list[0], {})
974
975 # For the second commit, add Ed and Fred, so only Mary should be left
976 commit2.rtags = {
977 'Tested-by': {self.leb},
978 'Reviewed-by': {self.fred}}
Simon Glass2112d072020-10-29 21:46:38 -0600979 status.find_new_responses(new_rtag_list, review_list, 1, commit2,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700980 patch2, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -0600981 self.assertEqual(new_rtag_list[1], {'Reviewed-by': {self.mary}})
982
983 # Check that the output patches expectations:
984 # 1 Subject 1
985 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
986 # 2 Subject 2
987 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
988 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
989 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
990 # 1 new response available in patchwork
991
992 series = Series()
993 series.commits = [commit1, commit2]
Simon Glass02811582022-01-29 14:14:18 -0700994 terminal.set_print_test_mode()
Simon Glass2112d072020-10-29 21:46:38 -0600995 status.check_patchwork_status(series, '1234', None, None, False, False,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700996 None, self._fake_patchwork2)
Simon Glass02811582022-01-29 14:14:18 -0700997 lines = iter(terminal.get_print_test_lines())
Simon Glass3db916d2020-10-29 21:46:35 -0600998 col = terminal.Color()
999 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
1000 next(lines))
1001 self.assertEqual(
1002 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
1003 bright=False),
1004 next(lines))
1005 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE, bright=False),
1006 next(lines))
1007
1008 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
1009 next(lines))
1010 self.assertEqual(
Simon Glass2112d072020-10-29 21:46:38 -06001011 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
Simon Glass3db916d2020-10-29 21:46:35 -06001012 bright=False),
1013 next(lines))
Simon Glass2112d072020-10-29 21:46:38 -06001014 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE, bright=False),
Simon Glass3db916d2020-10-29 21:46:35 -06001015 next(lines))
1016 self.assertEqual(
Simon Glass2112d072020-10-29 21:46:38 -06001017 terminal.PrintLine(' Tested-by: ', col.GREEN, newline=False,
Simon Glass3db916d2020-10-29 21:46:35 -06001018 bright=False),
1019 next(lines))
Simon Glass2112d072020-10-29 21:46:38 -06001020 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE, bright=False),
Simon Glass3db916d2020-10-29 21:46:35 -06001021 next(lines))
1022 self.assertEqual(
1023 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1024 next(lines))
1025 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
1026 next(lines))
1027 self.assertEqual(terminal.PrintLine(
Simon Glassd0a0a582020-10-29 21:46:36 -06001028 '1 new response available in patchwork (use -d to write them to a new branch)',
1029 None), next(lines))
1030
Simon Glassf9b03cf2020-11-03 13:54:14 -07001031 def _fake_patchwork3(self, url, subpath):
Simon Glassd0a0a582020-10-29 21:46:36 -06001032 """Fake Patchwork server for the function below
1033
1034 This handles accessing series, patches and comments, providing the data
1035 in self.patches to the caller
Simon Glassf9b03cf2020-11-03 13:54:14 -07001036
1037 Args:
1038 url (str): URL of patchwork server
1039 subpath (str): URL subpath to use
Simon Glassd0a0a582020-10-29 21:46:36 -06001040 """
1041 re_series = re.match(r'series/(\d*)/$', subpath)
1042 re_patch = re.match(r'patches/(\d*)/$', subpath)
1043 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
1044 if re_series:
1045 series_num = re_series.group(1)
1046 if series_num == '1234':
1047 return {'patches': self.patches}
1048 elif re_patch:
1049 patch_num = int(re_patch.group(1))
1050 patch = self.patches[patch_num - 1]
1051 return patch
1052 elif re_comments:
1053 patch_num = int(re_comments.group(1))
1054 patch = self.patches[patch_num - 1]
1055 return patch.comments
1056 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
1057
Simon Glassd85bb8f2022-01-29 14:14:09 -07001058 def test_create_branch(self):
Simon Glassd0a0a582020-10-29 21:46:36 -06001059 """Test operation of create_branch()"""
1060 repo = self.make_git_tree()
1061 branch = 'first'
1062 dest_branch = 'first2'
1063 count = 2
1064 gitdir = os.path.join(self.gitdir, '.git')
1065
1066 # Set up the test git tree. We use branch 'first' which has two commits
1067 # in it
1068 series = patchstream.get_metadata_for_list(branch, gitdir, count)
1069 self.assertEqual(2, len(series.commits))
1070
1071 patch1 = status.Patch('1')
1072 patch1.parse_subject('[1/2] %s' % series.commits[0].subject)
1073 patch1.name = patch1.raw_subject
1074 patch1.content = 'This is my patch content'
1075 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
1076
1077 patch1.comments = [comment1a]
1078
1079 patch2 = status.Patch('2')
1080 patch2.parse_subject('[2/2] %s' % series.commits[1].subject)
1081 patch2.name = patch2.raw_subject
1082 patch2.content = 'Some other patch content'
1083 comment2a = {
1084 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1085 (self.mary, self.leb)}
1086 comment2b = {
1087 'content': 'Reviewed-by: %s' % self.fred}
1088 patch2.comments = [comment2a, comment2b]
1089
1090 # This test works by setting up patches for use by the fake Rest API
1091 # function _fake_patchwork3(). The fake patch comments above should
1092 # result in new review tags that are collected and added to the commits
1093 # created in the destination branch.
1094 self.patches = [patch1, patch2]
1095 count = 2
1096
1097 # Expected output:
1098 # 1 i2c: I2C things
1099 # + Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1100 # 2 spi: SPI fixes
1101 # + Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1102 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1103 # + Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1104 # 4 new responses available in patchwork
1105 # 4 responses added from patchwork into new branch 'first2'
1106 # <unittest.result.TestResult run=8 errors=0 failures=0>
1107
Simon Glass02811582022-01-29 14:14:18 -07001108 terminal.set_print_test_mode()
Simon Glassd0a0a582020-10-29 21:46:36 -06001109 status.check_patchwork_status(series, '1234', branch, dest_branch,
Simon Glassf9b03cf2020-11-03 13:54:14 -07001110 False, False, None, self._fake_patchwork3,
1111 repo)
Simon Glass02811582022-01-29 14:14:18 -07001112 lines = terminal.get_print_test_lines()
Simon Glassd0a0a582020-10-29 21:46:36 -06001113 self.assertEqual(12, len(lines))
1114 self.assertEqual(
1115 "4 responses added from patchwork into new branch 'first2'",
1116 lines[11].text)
1117
1118 # Check that the destination branch has the new tags
1119 new_series = patchstream.get_metadata_for_list(dest_branch, gitdir,
1120 count)
1121 self.assertEqual(
1122 {'Reviewed-by': {self.joe}},
1123 new_series.commits[0].rtags)
1124 self.assertEqual(
1125 {'Tested-by': {self.leb},
1126 'Reviewed-by': {self.fred, self.mary}},
1127 new_series.commits[1].rtags)
1128
1129 # Now check the actual test of the first commit message. We expect to
1130 # see the new tags immediately below the old ones.
1131 stdout = patchstream.get_list(dest_branch, count=count, git_dir=gitdir)
1132 lines = iter([line.strip() for line in stdout.splitlines()
1133 if '-by:' in line])
1134
1135 # First patch should have the review tag
1136 self.assertEqual('Reviewed-by: %s' % self.joe, next(lines))
1137
1138 # Second patch should have the sign-off then the tested-by and two
1139 # reviewed-by tags
1140 self.assertEqual('Signed-off-by: %s' % self.leb, next(lines))
1141 self.assertEqual('Reviewed-by: %s' % self.fred, next(lines))
1142 self.assertEqual('Reviewed-by: %s' % self.mary, next(lines))
1143 self.assertEqual('Tested-by: %s' % self.leb, next(lines))
Simon Glassda8a2922020-10-29 21:46:37 -06001144
Simon Glassd85bb8f2022-01-29 14:14:09 -07001145 def test_parse_snippets(self):
Simon Glassda8a2922020-10-29 21:46:37 -06001146 """Test parsing of review snippets"""
1147 text = '''Hi Fred,
1148
1149This is a comment from someone.
1150
1151Something else
1152
1153On some recent date, Fred wrote:
1154> This is why I wrote the patch
1155> so here it is
1156
1157Now a comment about the commit message
1158A little more to say
1159
1160Even more
1161
1162> diff --git a/file.c b/file.c
1163> Some more code
1164> Code line 2
1165> Code line 3
1166> Code line 4
1167> Code line 5
1168> Code line 6
1169> Code line 7
1170> Code line 8
1171> Code line 9
1172
1173And another comment
1174
Simon Glassd85bb8f2022-01-29 14:14:09 -07001175> @@ -153,8 +143,13 @@ def check_patch(fname, show_types=False):
Simon Glassda8a2922020-10-29 21:46:37 -06001176> further down on the file
1177> and more code
1178> +Addition here
1179> +Another addition here
1180> codey
1181> more codey
1182
1183and another thing in same file
1184
1185> @@ -253,8 +243,13 @@
1186> with no function context
1187
1188one more thing
1189
1190> diff --git a/tools/patman/main.py b/tools/patman/main.py
1191> +line of code
1192now a very long comment in a different file
1193line2
1194line3
1195line4
1196line5
1197line6
1198line7
1199line8
1200'''
1201 pstrm = PatchStream.process_text(text, True)
1202 self.assertEqual([], pstrm.commit.warn)
1203
1204 # We expect to the filename and up to 5 lines of code context before
1205 # each comment. The 'On xxx wrote:' bit should be removed.
1206 self.assertEqual(
1207 [['Hi Fred,',
1208 'This is a comment from someone.',
1209 'Something else'],
1210 ['> This is why I wrote the patch',
1211 '> so here it is',
1212 'Now a comment about the commit message',
1213 'A little more to say', 'Even more'],
1214 ['> File: file.c', '> Code line 5', '> Code line 6',
1215 '> Code line 7', '> Code line 8', '> Code line 9',
1216 'And another comment'],
1217 ['> File: file.c',
Simon Glassd85bb8f2022-01-29 14:14:09 -07001218 '> Line: 153 / 143: def check_patch(fname, show_types=False):',
Simon Glassda8a2922020-10-29 21:46:37 -06001219 '> and more code', '> +Addition here', '> +Another addition here',
1220 '> codey', '> more codey', 'and another thing in same file'],
1221 ['> File: file.c', '> Line: 253 / 243',
1222 '> with no function context', 'one more thing'],
1223 ['> File: tools/patman/main.py', '> +line of code',
1224 'now a very long comment in a different file',
1225 'line2', 'line3', 'line4', 'line5', 'line6', 'line7', 'line8']],
1226 pstrm.snippets)
Simon Glass2112d072020-10-29 21:46:38 -06001227
Simon Glassd85bb8f2022-01-29 14:14:09 -07001228 def test_review_snippets(self):
Simon Glass2112d072020-10-29 21:46:38 -06001229 """Test showing of review snippets"""
1230 def _to_submitter(who):
1231 m_who = re.match('(.*) <(.*)>', who)
1232 return {
1233 'name': m_who.group(1),
1234 'email': m_who.group(2)
1235 }
1236
1237 commit1 = Commit('abcd')
1238 commit1.subject = 'Subject 1'
1239 commit2 = Commit('ef12')
1240 commit2.subject = 'Subject 2'
1241
1242 patch1 = status.Patch('1')
1243 patch1.parse_subject('[1/2] Subject 1')
1244 patch1.name = patch1.raw_subject
1245 patch1.content = 'This is my patch content'
1246 comment1a = {'submitter': _to_submitter(self.joe),
1247 'content': '''Hi Fred,
1248
1249On some date Fred wrote:
1250
1251> diff --git a/file.c b/file.c
1252> Some code
1253> and more code
1254
1255Here is my comment above the above...
1256
1257
1258Reviewed-by: %s
1259''' % self.joe}
1260
1261 patch1.comments = [comment1a]
1262
1263 patch2 = status.Patch('2')
1264 patch2.parse_subject('[2/2] Subject 2')
1265 patch2.name = patch2.raw_subject
1266 patch2.content = 'Some other patch content'
1267 comment2a = {
1268 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1269 (self.mary, self.leb)}
1270 comment2b = {'submitter': _to_submitter(self.fred),
1271 'content': '''Hi Fred,
1272
1273On some date Fred wrote:
1274
1275> diff --git a/tools/patman/commit.py b/tools/patman/commit.py
1276> @@ -41,6 +41,9 @@ class Commit:
1277> self.rtags = collections.defaultdict(set)
1278> self.warn = []
1279>
1280> + def __str__(self):
1281> + return self.subject
1282> +
Simon Glassd85bb8f2022-01-29 14:14:09 -07001283> def add_change(self, version, info):
Simon Glass2112d072020-10-29 21:46:38 -06001284> """Add a new change line to the change list for a version.
1285>
1286A comment
1287
1288Reviewed-by: %s
1289''' % self.fred}
1290 patch2.comments = [comment2a, comment2b]
1291
1292 # This test works by setting up commits and patch for use by the fake
1293 # Rest API function _fake_patchwork2(). It calls various functions in
1294 # the status module after setting up tags in the commits, checking that
1295 # things behaves as expected
1296 self.commits = [commit1, commit2]
1297 self.patches = [patch1, patch2]
1298
1299 # Check that the output patches expectations:
1300 # 1 Subject 1
1301 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1302 # 2 Subject 2
1303 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1304 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1305 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1306 # 1 new response available in patchwork
1307
1308 series = Series()
1309 series.commits = [commit1, commit2]
Simon Glass02811582022-01-29 14:14:18 -07001310 terminal.set_print_test_mode()
Simon Glass2112d072020-10-29 21:46:38 -06001311 status.check_patchwork_status(series, '1234', None, None, False, True,
Simon Glassf9b03cf2020-11-03 13:54:14 -07001312 None, self._fake_patchwork2)
Simon Glass02811582022-01-29 14:14:18 -07001313 lines = iter(terminal.get_print_test_lines())
Simon Glass2112d072020-10-29 21:46:38 -06001314 col = terminal.Color()
1315 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
1316 next(lines))
1317 self.assertEqual(
1318 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1319 next(lines))
1320 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE), next(lines))
1321
1322 self.assertEqual(terminal.PrintLine('Review: %s' % self.joe, col.RED),
1323 next(lines))
1324 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(lines))
1325 self.assertEqual(terminal.PrintLine('', None), next(lines))
1326 self.assertEqual(terminal.PrintLine(' > File: file.c', col.MAGENTA),
1327 next(lines))
1328 self.assertEqual(terminal.PrintLine(' > Some code', col.MAGENTA),
1329 next(lines))
1330 self.assertEqual(terminal.PrintLine(' > and more code', col.MAGENTA),
1331 next(lines))
1332 self.assertEqual(terminal.PrintLine(
1333 ' Here is my comment above the above...', None), next(lines))
1334 self.assertEqual(terminal.PrintLine('', None), next(lines))
1335
1336 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
1337 next(lines))
1338 self.assertEqual(
1339 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1340 next(lines))
1341 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE),
1342 next(lines))
1343 self.assertEqual(
1344 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1345 next(lines))
1346 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
1347 next(lines))
1348 self.assertEqual(
1349 terminal.PrintLine(' + Tested-by: ', col.GREEN, newline=False),
1350 next(lines))
1351 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE),
1352 next(lines))
1353
1354 self.assertEqual(terminal.PrintLine('Review: %s' % self.fred, col.RED),
1355 next(lines))
1356 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(lines))
1357 self.assertEqual(terminal.PrintLine('', None), next(lines))
1358 self.assertEqual(terminal.PrintLine(
1359 ' > File: tools/patman/commit.py', col.MAGENTA), next(lines))
1360 self.assertEqual(terminal.PrintLine(
1361 ' > Line: 41 / 41: class Commit:', col.MAGENTA), next(lines))
1362 self.assertEqual(terminal.PrintLine(
1363 ' > + return self.subject', col.MAGENTA), next(lines))
1364 self.assertEqual(terminal.PrintLine(
1365 ' > +', col.MAGENTA), next(lines))
1366 self.assertEqual(
Simon Glassd85bb8f2022-01-29 14:14:09 -07001367 terminal.PrintLine(' > def add_change(self, version, info):',
Simon Glass2112d072020-10-29 21:46:38 -06001368 col.MAGENTA),
1369 next(lines))
1370 self.assertEqual(terminal.PrintLine(
1371 ' > """Add a new change line to the change list for a version.',
1372 col.MAGENTA), next(lines))
1373 self.assertEqual(terminal.PrintLine(
1374 ' >', col.MAGENTA), next(lines))
1375 self.assertEqual(terminal.PrintLine(
1376 ' A comment', None), next(lines))
1377 self.assertEqual(terminal.PrintLine('', None), next(lines))
1378
1379 self.assertEqual(terminal.PrintLine(
1380 '4 new responses available in patchwork (use -d to write them to a new branch)',
1381 None), next(lines))
Simon Glass6a222e62021-08-01 16:02:39 -06001382
Simon Glassd85bb8f2022-01-29 14:14:09 -07001383 def test_insert_tags(self):
Simon Glass6a222e62021-08-01 16:02:39 -06001384 """Test inserting of review tags"""
1385 msg = '''first line
1386second line.'''
1387 tags = [
1388 'Reviewed-by: Bin Meng <bmeng.cn@gmail.com>',
1389 'Tested-by: Bin Meng <bmeng.cn@gmail.com>'
1390 ]
1391 signoff = 'Signed-off-by: Simon Glass <sjg@chromium.com>'
1392 tag_str = '\n'.join(tags)
1393
1394 new_msg = patchstream.insert_tags(msg, tags)
1395 self.assertEqual(msg + '\n\n' + tag_str, new_msg)
1396
1397 new_msg = patchstream.insert_tags(msg + '\n', tags)
1398 self.assertEqual(msg + '\n\n' + tag_str, new_msg)
1399
1400 msg += '\n\n' + signoff
1401 new_msg = patchstream.insert_tags(msg, tags)
1402 self.assertEqual(msg + '\n' + tag_str, new_msg)