blob: 9c016fb5e9a60c1f5eb58bca9e9e1313a0c2c409 [file] [log] [blame]
Simon Glass6e87ae12017-05-29 15:31:31 -06001# -*- coding: utf-8 -*-
Tom Rini83d290c2018-05-06 17:58:06 -04002# SPDX-License-Identifier: GPL-2.0+
Simon Glass6e87ae12017-05-29 15:31:31 -06003#
4# Copyright 2017 Google, Inc
5#
Simon Glass6e87ae12017-05-29 15:31:31 -06006
Simon Glassfca99112020-10-29 21:46:15 -06007"""Functional tests for checking that patman behaves correctly"""
8
Maxim Cournoyer8c042fb2022-12-20 00:28:46 -05009import contextlib
Simon Glass6e87ae12017-05-29 15:31:31 -060010import os
Maxim Cournoyer1c775982022-12-19 17:32:39 -050011import pathlib
Simon Glass6e87ae12017-05-29 15:31:31 -060012import re
13import shutil
14import sys
15import tempfile
16import unittest
17
Simon Glassdc6df972020-10-29 21:46:35 -060018
19from patman.commit import Commit
Simon Glassfd709862020-07-05 21:41:50 -060020from patman import control
Simon Glassbf776672020-04-17 18:09:04 -060021from patman import gitutil
22from patman import patchstream
Simon Glass47f62952020-10-29 21:46:26 -060023from patman.patchstream import PatchStream
Simon Glassdc6df972020-10-29 21:46:35 -060024from patman.series import Series
Simon Glassbf776672020-04-17 18:09:04 -060025from patman import settings
Simon Glass4583c002023-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 Glassfd709862020-07-05 21:41:50 -060029
Tom Rini2959a8e2021-02-26 07:52:31 -050030import pygit2
31from patman import status
Simon Glass6e87ae12017-05-29 15:31:31 -060032
Maxim Cournoyer8c042fb2022-12-20 00:28:46 -050033PATMAN_DIR = pathlib.Path(__file__).parent
34TEST_DATA_DIR = PATMAN_DIR / 'test/'
Maxim Cournoyer1c775982022-12-19 17:32:39 -050035
Maxim Cournoyer8c042fb2022-12-20 00:28:46 -050036
37@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)
Maxim Cournoyer1c775982022-12-19 17:32:39 -050046
47
Simon Glass6e87ae12017-05-29 15:31:31 -060048class TestFunctional(unittest.TestCase):
Simon Glassfca99112020-10-29 21:46:15 -060049 """Functional tests for checking that patman behaves correctly"""
Simon Glass74570512020-10-29 21:46:27 -060050 leb = (b'Lord Edmund Blackadd\xc3\xabr <weasel@blackadder.org>'.
51 decode('utf-8'))
Simon Glass4af99872020-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 Glassdc6df972020-10-29 21:46:35 -060055 commits = None
56 patches = None
Simon Glass74570512020-10-29 21:46:27 -060057
Simon Glass6e87ae12017-05-29 15:31:31 -060058 def setUp(self):
59 self.tmpdir = tempfile.mkdtemp(prefix='patman.')
Simon Glassfd709862020-07-05 21:41:50 -060060 self.gitdir = os.path.join(self.tmpdir, 'git')
61 self.repo = None
Simon Glass6e87ae12017-05-29 15:31:31 -060062
63 def tearDown(self):
64 shutil.rmtree(self.tmpdir)
Simon Glass098b10f2022-01-29 14:14:18 -070065 terminal.set_print_test_mode(False)
Simon Glass6e87ae12017-05-29 15:31:31 -060066
67 @staticmethod
Simon Glassfca99112020-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 Cournoyer1c775982022-12-19 17:32:39 -050077 return TEST_DATA_DIR / fname
Simon Glass6e87ae12017-05-29 15:31:31 -060078
79 @classmethod
Simon Glassfca99112020-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 Glass6e87ae12017-05-29 15:31:31 -060090
91 @classmethod
Simon Glassfca99112020-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 Glass6e87ae12017-05-29 15:31:31 -0600101 fname = re.sub('[ :]', '-', subject)
102 return fname.replace('--', '-')
103
Simon Glassfca99112020-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 Glass6e87ae12017-05-29 15:31:31 -0600117 cover_fname = None
118 fname_list = []
119 for i, commit in enumerate(series.commits):
Simon Glassfca99112020-10-29 21:46:15 -0600120 clean_subject = self._get_patch_name(commit.subject)
Simon Glass6e87ae12017-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 Glassfca99112020-10-29 21:46:15 -0600123 shutil.copy(self._get_path(src_fname), fname)
Simon Glass6e87ae12017-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 Glassfca99112020-10-29 21:46:15 -0600129 shutil.copy(self._get_path(src_fname), fname)
Simon Glass6e87ae12017-05-29 15:31:31 -0600130
131 return cover_fname, fname_list
132
Simon Glassc3aaa052022-01-29 14:14:09 -0700133 def test_basic(self):
Simon Glass6e87ae12017-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 Anderson082c1192021-10-22 19:07:04 -0400141 Series-postfix: some-branch
Simon Glass6e87ae12017-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 Andersondc03ba42020-05-04 16:28:36 -0400144 Series-version: 3
145 Patch-cc: fred
146 Series-process-log: sort, uniq
Simon Glass6e87ae12017-05-29 15:31:31 -0600147 Series-changes: 4
148 - Some changes
Sean Andersondc03ba42020-05-04 16:28:36 -0400149 - Multi
150 line
151 change
152
153 Commit-changes: 2
154 - Changes only for this commit
155
Simon Glass59747182021-08-01 16:02:39 -0600156' Cover-changes: 4
Sean Andersondc03ba42020-05-04 16:28:36 -0400157 - Some notes for the cover letter
Simon Glass6e87ae12017-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 Andersondc03ba42020-05-04 16:28:36 -0400162 letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600163 works
164 END
165
166 and this in the first commit:
167
Sean Andersondc03ba42020-05-04 16:28:36 -0400168 Commit-changes: 2
169 - second revision change
170
Simon Glass6e87ae12017-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 Anderson082c1192021-10-22 19:07:04 -0400196 - Series-to, Series-cc, Series-prefix, Series-postfix, Cover-letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600197 - Cover-letter-cc, Series-version, Series-changes, Series-notes
198 - Commit-notes
199 """
200 process_tags = True
Simon Glass0fb560d2021-01-23 08:56:15 -0700201 ignore_bad_tags = False
Simon Glasse6dca5e2019-05-14 15:53:53 -0600202 stefan = b'Stefan Br\xc3\xbcns <stefan.bruens@rwth-aachen.de>'.decode('utf-8')
Simon Glass6e87ae12017-05-29 15:31:31 -0600203 rick = 'Richard III <richard@palace.gov>'
Simon Glasse6dca5e2019-05-14 15:53:53 -0600204 mel = b'Lord M\xc3\xablchett <clergy@palace.gov>'.decode('utf-8')
Simon Glass6e87ae12017-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 Glass427b0282020-10-29 21:46:13 -0600210 'fdt': ['simon'],
211 'u-boot': ['u-boot@lists.denx.de'],
Simon Glass74570512020-10-29 21:46:27 -0600212 'simon': [self.leb],
Simon Glass4af99872020-10-29 21:46:28 -0600213 'fred': [self.fred],
Simon Glass6e87ae12017-05-29 15:31:31 -0600214 }
215
Simon Glassfca99112020-10-29 21:46:15 -0600216 text = self._get_text('test01.txt')
Simon Glassd93720e2020-10-29 21:46:19 -0600217 series = patchstream.get_metadata_for_test(text)
Simon Glassfca99112020-10-29 21:46:15 -0600218 cover_fname, args = self._create_patches_for_test(series)
Maxim Cournoyer8c042fb2022-12-20 00:28:46 -0500219 get_maintainer_script = str(pathlib.Path(__file__).parent.parent.parent
220 / 'get_maintainer.pl') + ' --norolestats'
Simon Glass366954f2020-10-29 21:46:14 -0600221 with capture_sys_output() as out:
Simon Glassd93720e2020-10-29 21:46:19 -0600222 patchstream.fix_patches(series, args)
Simon Glass6e87ae12017-05-29 15:31:31 -0600223 if cover_fname and series.get('cover'):
Simon Glassd93720e2020-10-29 21:46:19 -0600224 patchstream.insert_cover_letter(cover_fname, series, count)
Simon Glass6e87ae12017-05-29 15:31:31 -0600225 series.DoChecks()
226 cc_file = series.MakeCcFile(process_tags, cover_fname,
Chris Packham4fb35022018-06-07 20:45:06 +1200227 not ignore_bad_tags, add_maintainers,
Maxim Cournoyer8c042fb2022-12-20 00:28:46 -0500228 None, get_maintainer_script)
Simon Glass0157b182022-01-29 14:14:11 -0700229 cmd = gitutil.email_patches(
Simon Glass427b0282020-10-29 21:46:13 -0600230 series, cover_fname, args, dry_run, not ignore_bad_tags,
231 cc_file, in_reply_to=in_reply_to, thread=None)
Simon Glass6e87ae12017-05-29 15:31:31 -0600232 series.ShowActions(args, cmd, process_tags)
Simon Glass272cd852019-10-31 07:42:51 -0600233 cc_lines = open(cc_file, encoding='utf-8').read().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600234 os.remove(cc_file)
235
Simon Glass8c17f8c2020-10-29 21:46:29 -0600236 lines = iter(out[0].getvalue().splitlines())
237 self.assertEqual('Cleaned %s patches' % len(series.commits),
238 next(lines))
239 self.assertEqual('Change log missing for v2', next(lines))
240 self.assertEqual('Change log missing for v3', next(lines))
241 self.assertEqual('Change log for unknown version v4', next(lines))
242 self.assertEqual("Alias 'pci' not found", next(lines))
Simon Glass27409e32023-03-08 10:52:54 -0800243 while next(lines) != 'Cc processing complete':
244 pass
Simon Glass8c17f8c2020-10-29 21:46:29 -0600245 self.assertIn('Dry run', next(lines))
246 self.assertEqual('', next(lines))
247 self.assertIn('Send a total of %d patches' % count, next(lines))
248 prev = next(lines)
249 for i, commit in enumerate(series.commits):
250 self.assertEqual(' %s' % args[i], prev)
251 while True:
252 prev = next(lines)
253 if 'Cc:' not in prev:
254 break
255 self.assertEqual('To: u-boot@lists.denx.de', prev)
Simon Glassfc0056e2020-11-08 20:36:18 -0700256 self.assertEqual('Cc: %s' % stefan, next(lines))
Simon Glass8c17f8c2020-10-29 21:46:29 -0600257 self.assertEqual('Version: 3', next(lines))
258 self.assertEqual('Prefix:\t RFC', next(lines))
Sean Anderson082c1192021-10-22 19:07:04 -0400259 self.assertEqual('Postfix:\t some-branch', next(lines))
Simon Glass8c17f8c2020-10-29 21:46:29 -0600260 self.assertEqual('Cover: 4 lines', next(lines))
261 self.assertEqual(' Cc: %s' % self.fred, next(lines))
Simon Glassfc0056e2020-11-08 20:36:18 -0700262 self.assertEqual(' Cc: %s' % self.leb,
Simon Glass8c17f8c2020-10-29 21:46:29 -0600263 next(lines))
Simon Glassfc0056e2020-11-08 20:36:18 -0700264 self.assertEqual(' Cc: %s' % mel, next(lines))
Simon Glass8c17f8c2020-10-29 21:46:29 -0600265 self.assertEqual(' Cc: %s' % rick, next(lines))
Simon Glass6e87ae12017-05-29 15:31:31 -0600266 expected = ('Git command: git send-email --annotate '
267 '--in-reply-to="%s" --to "u-boot@lists.denx.de" '
Simon Glass46007672020-11-03 13:54:10 -0700268 '--cc "%s" --cc-cmd "%s send --cc-cmd %s" %s %s'
Simon Glass6e87ae12017-05-29 15:31:31 -0600269 % (in_reply_to, stefan, sys.argv[0], cc_file, cover_fname,
Simon Glasse6dca5e2019-05-14 15:53:53 -0600270 ' '.join(args)))
Simon Glassfc0056e2020-11-08 20:36:18 -0700271 self.assertEqual(expected, next(lines))
Simon Glass6e87ae12017-05-29 15:31:31 -0600272
Simon Glassfc0056e2020-11-08 20:36:18 -0700273 self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)), cc_lines[0])
Simon Glass427b0282020-10-29 21:46:13 -0600274 self.assertEqual(
Simon Glass4af99872020-10-29 21:46:28 -0600275 '%s %s\0%s\0%s\0%s' % (args[1], self.fred, self.leb, rick, stefan),
Simon Glassfc0056e2020-11-08 20:36:18 -0700276 cc_lines[1])
Simon Glass6e87ae12017-05-29 15:31:31 -0600277
278 expected = '''
279This is a test of how the cover
Sean Andersondc03ba42020-05-04 16:28:36 -0400280letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600281works
282
283some notes
284about some things
285from the first commit
286
287Changes in v4:
Sean Andersondc03ba42020-05-04 16:28:36 -0400288- Multi
289 line
290 change
Simon Glass6e87ae12017-05-29 15:31:31 -0600291- Some changes
Sean Andersondc03ba42020-05-04 16:28:36 -0400292- Some notes for the cover letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600293
294Simon Glass (2):
295 pci: Correct cast for sandbox
Siva Durga Prasad Paladugu12308b12018-07-16 15:56:11 +0530296 fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
Simon Glass6e87ae12017-05-29 15:31:31 -0600297
298 cmd/pci.c | 3 ++-
299 fs/fat/fat.c | 1 +
300 lib/efi_loader/efi_memory.c | 1 +
301 lib/fdtdec.c | 3 ++-
302 4 files changed, 6 insertions(+), 2 deletions(-)
303
304--\x20
3052.7.4
306
307'''
Simon Glass272cd852019-10-31 07:42:51 -0600308 lines = open(cover_fname, encoding='utf-8').read().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600309 self.assertEqual(
Sean Anderson082c1192021-10-22 19:07:04 -0400310 'Subject: [RFC PATCH some-branch v3 0/2] test: A test patch series',
Simon Glass427b0282020-10-29 21:46:13 -0600311 lines[3])
Simon Glass6e87ae12017-05-29 15:31:31 -0600312 self.assertEqual(expected.splitlines(), lines[7:])
313
314 for i, fname in enumerate(args):
Simon Glass272cd852019-10-31 07:42:51 -0600315 lines = open(fname, encoding='utf-8').read().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600316 subject = [line for line in lines if line.startswith('Subject')]
317 self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count),
318 subject[0][:18])
Sean Andersondc03ba42020-05-04 16:28:36 -0400319
320 # Check that we got our commit notes
321 start = 0
322 expected = ''
323
Simon Glass6e87ae12017-05-29 15:31:31 -0600324 if i == 0:
Sean Andersondc03ba42020-05-04 16:28:36 -0400325 start = 17
326 expected = '''---
327Some notes about
328the first commit
329
330(no changes since v2)
331
332Changes in v2:
333- second revision change'''
334 elif i == 1:
335 start = 17
336 expected = '''---
337
338Changes in v4:
339- Multi
340 line
341 change
342- Some changes
343
344Changes in v2:
345- Changes only for this commit'''
346
347 if expected:
348 expected = expected.splitlines()
349 self.assertEqual(expected, lines[start:(start+len(expected))])
Simon Glassfd709862020-07-05 21:41:50 -0600350
351 def make_commit_with_file(self, subject, body, fname, text):
352 """Create a file and add it to the git repo with a new commit
353
354 Args:
355 subject (str): Subject for the commit
356 body (str): Body text of the commit
357 fname (str): Filename of file to create
358 text (str): Text to put into the file
359 """
360 path = os.path.join(self.gitdir, fname)
Simon Glassc1aa66e2022-01-29 14:14:04 -0700361 tools.write_file(path, text, binary=False)
Simon Glassfd709862020-07-05 21:41:50 -0600362 index = self.repo.index
363 index.add(fname)
Simon Glass32cc6ae2022-02-11 13:23:18 -0700364 # pylint doesn't seem to find this
365 # pylint: disable=E1101
Simon Glass427b0282020-10-29 21:46:13 -0600366 author = pygit2.Signature('Test user', 'test@email.com')
Simon Glassfd709862020-07-05 21:41:50 -0600367 committer = author
368 tree = index.write_tree()
369 message = subject + '\n' + body
370 self.repo.create_commit('HEAD', author, committer, message, tree,
371 [self.repo.head.target])
372
373 def make_git_tree(self):
374 """Make a simple git tree suitable for testing
375
376 It has three branches:
377 'base' has two commits: PCI, main
378 'first' has base as upstream and two more commits: I2C, SPI
379 'second' has base as upstream and three more: video, serial, bootm
380
381 Returns:
Simon Glassfca99112020-10-29 21:46:15 -0600382 pygit2.Repository: repository
Simon Glassfd709862020-07-05 21:41:50 -0600383 """
384 repo = pygit2.init_repository(self.gitdir)
385 self.repo = repo
386 new_tree = repo.TreeBuilder().write()
387
Simon Glass32cc6ae2022-02-11 13:23:18 -0700388 # pylint doesn't seem to find this
389 # pylint: disable=E1101
Simon Glassfd709862020-07-05 21:41:50 -0600390 author = pygit2.Signature('Test user', 'test@email.com')
391 committer = author
Simon Glassfca99112020-10-29 21:46:15 -0600392 _ = repo.create_commit('HEAD', author, committer, 'Created master',
393 new_tree, [])
Simon Glassfd709862020-07-05 21:41:50 -0600394
395 self.make_commit_with_file('Initial commit', '''
396Add a README
397
398''', 'README', '''This is the README file
399describing this project
400in very little detail''')
401
402 self.make_commit_with_file('pci: PCI implementation', '''
403Here is a basic PCI implementation
404
405''', 'pci.c', '''This is a file
406it has some contents
407and some more things''')
408 self.make_commit_with_file('main: Main program', '''
409Hello here is the second commit.
410''', 'main.c', '''This is the main file
411there is very little here
412but we can always add more later
413if we want to
414
415Series-to: u-boot
416Series-cc: Barry Crump <bcrump@whataroa.nz>
417''')
418 base_target = repo.revparse_single('HEAD')
419 self.make_commit_with_file('i2c: I2C things', '''
420This has some stuff to do with I2C
421''', 'i2c.c', '''And this is the file contents
422with some I2C-related things in it''')
423 self.make_commit_with_file('spi: SPI fixes', '''
424SPI needs some fixes
425and here they are
Simon Glass8f9ba3a2020-10-29 21:46:36 -0600426
427Signed-off-by: %s
428
429Series-to: u-boot
430Commit-notes:
431title of the series
432This is the cover letter for the series
433with various details
434END
435''' % self.leb, 'spi.c', '''Some fixes for SPI in this
Simon Glassfd709862020-07-05 21:41:50 -0600436file to make SPI work
437better than before''')
438 first_target = repo.revparse_single('HEAD')
439
440 target = repo.revparse_single('HEAD~2')
Simon Glass32cc6ae2022-02-11 13:23:18 -0700441 # pylint doesn't seem to find this
442 # pylint: disable=E1101
Simon Glassfd709862020-07-05 21:41:50 -0600443 repo.reset(target.oid, pygit2.GIT_CHECKOUT_FORCE)
444 self.make_commit_with_file('video: Some video improvements', '''
445Fix up the video so that
446it looks more purple. Purple is
447a very nice colour.
448''', 'video.c', '''More purple here
449Purple and purple
450Even more purple
451Could not be any more purple''')
452 self.make_commit_with_file('serial: Add a serial driver', '''
453Here is the serial driver
454for my chip.
455
456Cover-letter:
457Series for my board
458This series implements support
459for my glorious board.
460END
Simon Glassf9e42842020-10-29 21:46:16 -0600461Series-links: 183237
Simon Glassfd709862020-07-05 21:41:50 -0600462''', 'serial.c', '''The code for the
463serial driver is here''')
464 self.make_commit_with_file('bootm: Make it boot', '''
465This makes my board boot
466with a fix to the bootm
467command
468''', 'bootm.c', '''Fix up the bootm
469command to make the code as
470complicated as possible''')
471 second_target = repo.revparse_single('HEAD')
472
473 repo.branches.local.create('first', first_target)
474 repo.config.set_multivar('branch.first.remote', '', '.')
475 repo.config.set_multivar('branch.first.merge', '', 'refs/heads/base')
476
477 repo.branches.local.create('second', second_target)
478 repo.config.set_multivar('branch.second.remote', '', '.')
479 repo.config.set_multivar('branch.second.merge', '', 'refs/heads/base')
480
481 repo.branches.local.create('base', base_target)
482 return repo
483
Simon Glassc3aaa052022-01-29 14:14:09 -0700484 def test_branch(self):
Simon Glassfd709862020-07-05 21:41:50 -0600485 """Test creating patches from a branch"""
486 repo = self.make_git_tree()
487 target = repo.lookup_reference('refs/heads/first')
Simon Glass32cc6ae2022-02-11 13:23:18 -0700488 # pylint doesn't seem to find this
489 # pylint: disable=E1101
Simon Glassfd709862020-07-05 21:41:50 -0600490 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
491 control.setup()
Heinrich Schuchardtc25be4f2023-04-20 20:07:29 +0200492 orig_dir = os.getcwd()
Simon Glassfd709862020-07-05 21:41:50 -0600493 try:
Simon Glassfd709862020-07-05 21:41:50 -0600494 os.chdir(self.gitdir)
495
496 # Check that it can detect the current branch
Simon Glass0157b182022-01-29 14:14:11 -0700497 self.assertEqual(2, gitutil.count_commits_to_branch(None))
Simon Glassfd709862020-07-05 21:41:50 -0600498 col = terminal.Color()
499 with capture_sys_output() as _:
500 _, cover_fname, patch_files = control.prepare_patches(
Simon Glass137947e2020-07-05 21:41:52 -0600501 col, branch=None, count=-1, start=0, end=0,
Philipp Tomsichb3aff152020-11-24 18:14:52 +0100502 ignore_binary=False, signoff=True)
Simon Glassfd709862020-07-05 21:41:50 -0600503 self.assertIsNone(cover_fname)
504 self.assertEqual(2, len(patch_files))
Simon Glass262130f2020-07-05 21:41:51 -0600505
506 # Check that it can detect a different branch
Simon Glass0157b182022-01-29 14:14:11 -0700507 self.assertEqual(3, gitutil.count_commits_to_branch('second'))
Simon Glass262130f2020-07-05 21:41:51 -0600508 with capture_sys_output() as _:
509 _, cover_fname, patch_files = control.prepare_patches(
Simon Glass137947e2020-07-05 21:41:52 -0600510 col, branch='second', count=-1, start=0, end=0,
Philipp Tomsichb3aff152020-11-24 18:14:52 +0100511 ignore_binary=False, signoff=True)
Simon Glass262130f2020-07-05 21:41:51 -0600512 self.assertIsNotNone(cover_fname)
513 self.assertEqual(3, len(patch_files))
Simon Glass137947e2020-07-05 21:41:52 -0600514
515 # Check that it can skip patches at the end
516 with capture_sys_output() as _:
517 _, cover_fname, patch_files = control.prepare_patches(
518 col, branch='second', count=-1, start=0, end=1,
Philipp Tomsichb3aff152020-11-24 18:14:52 +0100519 ignore_binary=False, signoff=True)
Simon Glass137947e2020-07-05 21:41:52 -0600520 self.assertIsNotNone(cover_fname)
521 self.assertEqual(2, len(patch_files))
Simon Glassfd709862020-07-05 21:41:50 -0600522 finally:
523 os.chdir(orig_dir)
Simon Glass74570512020-10-29 21:46:27 -0600524
Maxim Cournoyer8c042fb2022-12-20 00:28:46 -0500525 def test_custom_get_maintainer_script(self):
526 """Validate that a custom get_maintainer script gets used."""
527 self.make_git_tree()
528 with directory_excursion(self.gitdir):
529 # Setup git.
530 os.environ['GIT_CONFIG_GLOBAL'] = '/dev/null'
531 os.environ['GIT_CONFIG_SYSTEM'] = '/dev/null'
532 tools.run('git', 'config', 'user.name', 'Dummy')
533 tools.run('git', 'config', 'user.email', 'dumdum@dummy.com')
534 tools.run('git', 'branch', 'upstream')
535 tools.run('git', 'branch', '--set-upstream-to=upstream')
536 tools.run('git', 'add', '.')
537 tools.run('git', 'commit', '-m', 'new commit')
538
539 # Setup patman configuration.
540 with open('.patman', 'w', buffering=1) as f:
541 f.write('[settings]\n'
542 'get_maintainer_script: dummy-script.sh\n'
Sean Andersoneba80852024-04-18 22:36:30 -0400543 'check_patch: False\n'
544 'add_maintainers: True\n')
Maxim Cournoyer8c042fb2022-12-20 00:28:46 -0500545 with open('dummy-script.sh', 'w', buffering=1) as f:
546 f.write('#!/usr/bin/env python\n'
547 'print("hello@there.com")\n')
548 os.chmod('dummy-script.sh', 0x555)
549
550 # Finally, do the test
551 with capture_sys_output():
552 output = tools.run(PATMAN_DIR / 'patman', '--dry-run')
553 # Assert the email address is part of the dry-run
554 # output.
555 self.assertIn('hello@there.com', output)
556
Simon Glassc3aaa052022-01-29 14:14:09 -0700557 def test_tags(self):
Simon Glass74570512020-10-29 21:46:27 -0600558 """Test collection of tags in a patchstream"""
559 text = '''This is a patch
560
561Signed-off-by: Terminator
Simon Glass4af99872020-10-29 21:46:28 -0600562Reviewed-by: %s
563Reviewed-by: %s
Simon Glass74570512020-10-29 21:46:27 -0600564Tested-by: %s
Simon Glass4af99872020-10-29 21:46:28 -0600565''' % (self.joe, self.mary, self.leb)
Simon Glass74570512020-10-29 21:46:27 -0600566 pstrm = PatchStream.process_text(text)
567 self.assertEqual(pstrm.commit.rtags, {
Simon Glass4af99872020-10-29 21:46:28 -0600568 'Reviewed-by': {self.joe, self.mary},
Simon Glass74570512020-10-29 21:46:27 -0600569 'Tested-by': {self.leb}})
Simon Glass4af99872020-10-29 21:46:28 -0600570
Simon Glassc3aaa052022-01-29 14:14:09 -0700571 def test_invalid_tag(self):
Patrick Delaunaya6123332021-07-22 16:51:42 +0200572 """Test invalid tag in a patchstream"""
573 text = '''This is a patch
574
575Serie-version: 2
576'''
577 with self.assertRaises(ValueError) as exc:
578 pstrm = PatchStream.process_text(text)
579 self.assertEqual("Line 3: Invalid tag = 'Serie-version: 2'",
580 str(exc.exception))
581
Simon Glassc3aaa052022-01-29 14:14:09 -0700582 def test_missing_end(self):
Simon Glass4af99872020-10-29 21:46:28 -0600583 """Test a missing END tag"""
584 text = '''This is a patch
585
586Cover-letter:
587This is the title
588missing END after this line
589Signed-off-by: Fred
590'''
591 pstrm = PatchStream.process_text(text)
592 self.assertEqual(["Missing 'END' in section 'cover'"],
593 pstrm.commit.warn)
594
Simon Glassc3aaa052022-01-29 14:14:09 -0700595 def test_missing_blank_line(self):
Simon Glass4af99872020-10-29 21:46:28 -0600596 """Test a missing blank line after a tag"""
597 text = '''This is a patch
598
599Series-changes: 2
600- First line of changes
601- Missing blank line after this line
602Signed-off-by: Fred
603'''
604 pstrm = PatchStream.process_text(text)
605 self.assertEqual(["Missing 'blank line' in section 'Series-changes'"],
606 pstrm.commit.warn)
607
Simon Glassc3aaa052022-01-29 14:14:09 -0700608 def test_invalid_commit_tag(self):
Simon Glass4af99872020-10-29 21:46:28 -0600609 """Test an invalid Commit-xxx tag"""
610 text = '''This is a patch
611
612Commit-fred: testing
613'''
614 pstrm = PatchStream.process_text(text)
615 self.assertEqual(["Line 3: Ignoring Commit-fred"], pstrm.commit.warn)
616
Simon Glassc3aaa052022-01-29 14:14:09 -0700617 def test_self_test(self):
Simon Glass4af99872020-10-29 21:46:28 -0600618 """Test a tested by tag by this user"""
619 test_line = 'Tested-by: %s@napier.com' % os.getenv('USER')
620 text = '''This is a patch
621
622%s
623''' % test_line
624 pstrm = PatchStream.process_text(text)
625 self.assertEqual(["Ignoring '%s'" % test_line], pstrm.commit.warn)
626
Simon Glassc3aaa052022-01-29 14:14:09 -0700627 def test_space_before_tab(self):
Simon Glass4af99872020-10-29 21:46:28 -0600628 """Test a space before a tab"""
629 text = '''This is a patch
630
631+ \tSomething
632'''
633 pstrm = PatchStream.process_text(text)
634 self.assertEqual(["Line 3/0 has space before tab"], pstrm.commit.warn)
635
Simon Glassc3aaa052022-01-29 14:14:09 -0700636 def test_lines_after_test(self):
Simon Glass4af99872020-10-29 21:46:28 -0600637 """Test detecting lines after TEST= line"""
638 text = '''This is a patch
639
640TEST=sometest
641more lines
642here
643'''
644 pstrm = PatchStream.process_text(text)
645 self.assertEqual(["Found 2 lines after TEST="], pstrm.commit.warn)
646
Simon Glassc3aaa052022-01-29 14:14:09 -0700647 def test_blank_line_at_end(self):
Simon Glass4af99872020-10-29 21:46:28 -0600648 """Test detecting a blank line at the end of a file"""
649 text = '''This is a patch
650
651diff --git a/lib/fdtdec.c b/lib/fdtdec.c
652index c072e54..942244f 100644
653--- a/lib/fdtdec.c
654+++ b/lib/fdtdec.c
655@@ -1200,7 +1200,8 @@ int fdtdec_setup_mem_size_base(void)
656 }
657
658 gd->ram_size = (phys_size_t)(res.end - res.start + 1);
659- debug("%s: Initial DRAM size %llx\n", __func__, (u64)gd->ram_size);
660+ debug("%s: Initial DRAM size %llx\n", __func__,
661+ (unsigned long long)gd->ram_size);
662+
663diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c
664
665--
6662.7.4
667
668 '''
669 pstrm = PatchStream.process_text(text)
670 self.assertEqual(
671 ["Found possible blank line(s) at end of file 'lib/fdtdec.c'"],
672 pstrm.commit.warn)
Simon Glassbe051c02020-10-29 21:46:34 -0600673
Simon Glassc3aaa052022-01-29 14:14:09 -0700674 def test_no_upstream(self):
Simon Glassbe051c02020-10-29 21:46:34 -0600675 """Test CountCommitsToBranch when there is no upstream"""
676 repo = self.make_git_tree()
677 target = repo.lookup_reference('refs/heads/base')
Simon Glass32cc6ae2022-02-11 13:23:18 -0700678 # pylint doesn't seem to find this
679 # pylint: disable=E1101
Simon Glassbe051c02020-10-29 21:46:34 -0600680 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
681
682 # Check that it can detect the current branch
Heinrich Schuchardtc25be4f2023-04-20 20:07:29 +0200683 orig_dir = os.getcwd()
Simon Glassbe051c02020-10-29 21:46:34 -0600684 try:
Simon Glassbe051c02020-10-29 21:46:34 -0600685 os.chdir(self.gitdir)
686 with self.assertRaises(ValueError) as exc:
Simon Glass0157b182022-01-29 14:14:11 -0700687 gitutil.count_commits_to_branch(None)
Simon Glassbe051c02020-10-29 21:46:34 -0600688 self.assertIn(
689 "Failed to determine upstream: fatal: no upstream configured for branch 'base'",
690 str(exc.exception))
691 finally:
692 os.chdir(orig_dir)
Simon Glassdc6df972020-10-29 21:46:35 -0600693
694 @staticmethod
Simon Glass7cbf02e2020-11-03 13:54:14 -0700695 def _fake_patchwork(url, subpath):
Simon Glassdc6df972020-10-29 21:46:35 -0600696 """Fake Patchwork server for the function below
697
698 This handles accessing a series, providing a list consisting of a
699 single patch
Simon Glass7cbf02e2020-11-03 13:54:14 -0700700
701 Args:
702 url (str): URL of patchwork server
703 subpath (str): URL subpath to use
Simon Glassdc6df972020-10-29 21:46:35 -0600704 """
705 re_series = re.match(r'series/(\d*)/$', subpath)
706 if re_series:
707 series_num = re_series.group(1)
708 if series_num == '1234':
709 return {'patches': [
710 {'id': '1', 'name': 'Some patch'}]}
711 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
712
Simon Glassc3aaa052022-01-29 14:14:09 -0700713 def test_status_mismatch(self):
Simon Glassdc6df972020-10-29 21:46:35 -0600714 """Test Patchwork patches not matching the series"""
715 series = Series()
716
717 with capture_sys_output() as (_, err):
Simon Glass7cbf02e2020-11-03 13:54:14 -0700718 status.collect_patches(series, 1234, None, self._fake_patchwork)
Simon Glassdc6df972020-10-29 21:46:35 -0600719 self.assertIn('Warning: Patchwork reports 1 patches, series has 0',
720 err.getvalue())
721
Simon Glassc3aaa052022-01-29 14:14:09 -0700722 def test_status_read_patch(self):
Simon Glassdc6df972020-10-29 21:46:35 -0600723 """Test handling a single patch in Patchwork"""
724 series = Series()
725 series.commits = [Commit('abcd')]
726
Simon Glass7cbf02e2020-11-03 13:54:14 -0700727 patches = status.collect_patches(series, 1234, None,
728 self._fake_patchwork)
Simon Glassdc6df972020-10-29 21:46:35 -0600729 self.assertEqual(1, len(patches))
730 patch = patches[0]
731 self.assertEqual('1', patch.id)
732 self.assertEqual('Some patch', patch.raw_subject)
733
Simon Glassc3aaa052022-01-29 14:14:09 -0700734 def test_parse_subject(self):
Simon Glassdc6df972020-10-29 21:46:35 -0600735 """Test parsing of the patch subject"""
736 patch = status.Patch('1')
737
738 # Simple patch not in a series
739 patch.parse_subject('Testing')
740 self.assertEqual('Testing', patch.raw_subject)
741 self.assertEqual('Testing', patch.subject)
742 self.assertEqual(1, patch.seq)
743 self.assertEqual(1, patch.count)
744 self.assertEqual(None, patch.prefix)
745 self.assertEqual(None, patch.version)
746
747 # First patch in a series
748 patch.parse_subject('[1/2] Testing')
749 self.assertEqual('[1/2] Testing', patch.raw_subject)
750 self.assertEqual('Testing', patch.subject)
751 self.assertEqual(1, patch.seq)
752 self.assertEqual(2, patch.count)
753 self.assertEqual(None, patch.prefix)
754 self.assertEqual(None, patch.version)
755
756 # Second patch in a series
757 patch.parse_subject('[2/2] Testing')
758 self.assertEqual('Testing', patch.subject)
759 self.assertEqual(2, patch.seq)
760 self.assertEqual(2, patch.count)
761 self.assertEqual(None, patch.prefix)
762 self.assertEqual(None, patch.version)
763
764 # RFC patch
765 patch.parse_subject('[RFC,3/7] Testing')
766 self.assertEqual('Testing', patch.subject)
767 self.assertEqual(3, patch.seq)
768 self.assertEqual(7, patch.count)
769 self.assertEqual('RFC', patch.prefix)
770 self.assertEqual(None, patch.version)
771
772 # Version patch
773 patch.parse_subject('[v2,3/7] Testing')
774 self.assertEqual('Testing', patch.subject)
775 self.assertEqual(3, patch.seq)
776 self.assertEqual(7, patch.count)
777 self.assertEqual(None, patch.prefix)
778 self.assertEqual('v2', patch.version)
779
780 # All fields
781 patch.parse_subject('[RESEND,v2,3/7] Testing')
782 self.assertEqual('Testing', patch.subject)
783 self.assertEqual(3, patch.seq)
784 self.assertEqual(7, patch.count)
785 self.assertEqual('RESEND', patch.prefix)
786 self.assertEqual('v2', patch.version)
787
788 # RFC only
789 patch.parse_subject('[RESEND] Testing')
790 self.assertEqual('Testing', patch.subject)
791 self.assertEqual(1, patch.seq)
792 self.assertEqual(1, patch.count)
793 self.assertEqual('RESEND', patch.prefix)
794 self.assertEqual(None, patch.version)
795
Simon Glassc3aaa052022-01-29 14:14:09 -0700796 def test_compare_series(self):
Simon Glassdc6df972020-10-29 21:46:35 -0600797 """Test operation of compare_with_series()"""
798 commit1 = Commit('abcd')
799 commit1.subject = 'Subject 1'
800 commit2 = Commit('ef12')
801 commit2.subject = 'Subject 2'
802 commit3 = Commit('3456')
803 commit3.subject = 'Subject 2'
804
805 patch1 = status.Patch('1')
806 patch1.subject = 'Subject 1'
807 patch2 = status.Patch('2')
808 patch2.subject = 'Subject 2'
809 patch3 = status.Patch('3')
810 patch3.subject = 'Subject 2'
811
812 series = Series()
813 series.commits = [commit1]
814 patches = [patch1]
815 patch_for_commit, commit_for_patch, warnings = (
816 status.compare_with_series(series, patches))
817 self.assertEqual(1, len(patch_for_commit))
818 self.assertEqual(patch1, patch_for_commit[0])
819 self.assertEqual(1, len(commit_for_patch))
820 self.assertEqual(commit1, commit_for_patch[0])
821
822 series.commits = [commit1]
823 patches = [patch1, patch2]
824 patch_for_commit, commit_for_patch, warnings = (
825 status.compare_with_series(series, patches))
826 self.assertEqual(1, len(patch_for_commit))
827 self.assertEqual(patch1, patch_for_commit[0])
828 self.assertEqual(1, len(commit_for_patch))
829 self.assertEqual(commit1, commit_for_patch[0])
830 self.assertEqual(["Cannot find commit for patch 2 ('Subject 2')"],
831 warnings)
832
833 series.commits = [commit1, commit2]
834 patches = [patch1]
835 patch_for_commit, commit_for_patch, warnings = (
836 status.compare_with_series(series, patches))
837 self.assertEqual(1, len(patch_for_commit))
838 self.assertEqual(patch1, patch_for_commit[0])
839 self.assertEqual(1, len(commit_for_patch))
840 self.assertEqual(commit1, commit_for_patch[0])
841 self.assertEqual(["Cannot find patch for commit 2 ('Subject 2')"],
842 warnings)
843
844 series.commits = [commit1, commit2, commit3]
845 patches = [patch1, patch2]
846 patch_for_commit, commit_for_patch, warnings = (
847 status.compare_with_series(series, patches))
848 self.assertEqual(2, len(patch_for_commit))
849 self.assertEqual(patch1, patch_for_commit[0])
850 self.assertEqual(patch2, patch_for_commit[1])
851 self.assertEqual(1, len(commit_for_patch))
852 self.assertEqual(commit1, commit_for_patch[0])
853 self.assertEqual(["Cannot find patch for commit 3 ('Subject 2')",
854 "Multiple commits match patch 2 ('Subject 2'):\n"
855 ' Subject 2\n Subject 2'],
856 warnings)
857
858 series.commits = [commit1, commit2]
859 patches = [patch1, patch2, patch3]
860 patch_for_commit, commit_for_patch, warnings = (
861 status.compare_with_series(series, patches))
862 self.assertEqual(1, len(patch_for_commit))
863 self.assertEqual(patch1, patch_for_commit[0])
864 self.assertEqual(2, len(commit_for_patch))
865 self.assertEqual(commit1, commit_for_patch[0])
866 self.assertEqual(["Multiple patches match commit 2 ('Subject 2'):\n"
867 ' Subject 2\n Subject 2',
868 "Cannot find commit for patch 3 ('Subject 2')"],
869 warnings)
870
Simon Glass7cbf02e2020-11-03 13:54:14 -0700871 def _fake_patchwork2(self, url, subpath):
Simon Glassdc6df972020-10-29 21:46:35 -0600872 """Fake Patchwork server for the function below
873
874 This handles accessing series, patches and comments, providing the data
875 in self.patches to the caller
Simon Glass7cbf02e2020-11-03 13:54:14 -0700876
877 Args:
878 url (str): URL of patchwork server
879 subpath (str): URL subpath to use
Simon Glassdc6df972020-10-29 21:46:35 -0600880 """
881 re_series = re.match(r'series/(\d*)/$', subpath)
882 re_patch = re.match(r'patches/(\d*)/$', subpath)
883 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
884 if re_series:
885 series_num = re_series.group(1)
886 if series_num == '1234':
887 return {'patches': self.patches}
888 elif re_patch:
889 patch_num = int(re_patch.group(1))
890 patch = self.patches[patch_num - 1]
891 return patch
892 elif re_comments:
893 patch_num = int(re_comments.group(1))
894 patch = self.patches[patch_num - 1]
895 return patch.comments
896 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
897
Simon Glassc3aaa052022-01-29 14:14:09 -0700898 def test_find_new_responses(self):
Simon Glassdc6df972020-10-29 21:46:35 -0600899 """Test operation of find_new_responses()"""
900 commit1 = Commit('abcd')
901 commit1.subject = 'Subject 1'
902 commit2 = Commit('ef12')
903 commit2.subject = 'Subject 2'
904
905 patch1 = status.Patch('1')
906 patch1.parse_subject('[1/2] Subject 1')
907 patch1.name = patch1.raw_subject
908 patch1.content = 'This is my patch content'
909 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
910
911 patch1.comments = [comment1a]
912
913 patch2 = status.Patch('2')
914 patch2.parse_subject('[2/2] Subject 2')
915 patch2.name = patch2.raw_subject
916 patch2.content = 'Some other patch content'
917 comment2a = {
918 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
919 (self.mary, self.leb)}
920 comment2b = {'content': 'Reviewed-by: %s' % self.fred}
921 patch2.comments = [comment2a, comment2b]
922
923 # This test works by setting up commits and patch for use by the fake
924 # Rest API function _fake_patchwork2(). It calls various functions in
925 # the status module after setting up tags in the commits, checking that
926 # things behaves as expected
927 self.commits = [commit1, commit2]
928 self.patches = [patch1, patch2]
929 count = 2
930 new_rtag_list = [None] * count
Simon Glassdc4b2a92020-10-29 21:46:38 -0600931 review_list = [None, None]
Simon Glassdc6df972020-10-29 21:46:35 -0600932
933 # Check that the tags are picked up on the first patch
Simon Glassdc4b2a92020-10-29 21:46:38 -0600934 status.find_new_responses(new_rtag_list, review_list, 0, commit1,
Simon Glass7cbf02e2020-11-03 13:54:14 -0700935 patch1, None, self._fake_patchwork2)
Simon Glassdc6df972020-10-29 21:46:35 -0600936 self.assertEqual(new_rtag_list[0], {'Reviewed-by': {self.joe}})
937
938 # Now the second patch
Simon Glassdc4b2a92020-10-29 21:46:38 -0600939 status.find_new_responses(new_rtag_list, review_list, 1, commit2,
Simon Glass7cbf02e2020-11-03 13:54:14 -0700940 patch2, None, self._fake_patchwork2)
Simon Glassdc6df972020-10-29 21:46:35 -0600941 self.assertEqual(new_rtag_list[1], {
942 'Reviewed-by': {self.mary, self.fred},
943 'Tested-by': {self.leb}})
944
945 # Now add some tags to the commit, which means they should not appear as
946 # 'new' tags when scanning comments
947 new_rtag_list = [None] * count
948 commit1.rtags = {'Reviewed-by': {self.joe}}
Simon Glassdc4b2a92020-10-29 21:46:38 -0600949 status.find_new_responses(new_rtag_list, review_list, 0, commit1,
Simon Glass7cbf02e2020-11-03 13:54:14 -0700950 patch1, None, self._fake_patchwork2)
Simon Glassdc6df972020-10-29 21:46:35 -0600951 self.assertEqual(new_rtag_list[0], {})
952
953 # For the second commit, add Ed and Fred, so only Mary should be left
954 commit2.rtags = {
955 'Tested-by': {self.leb},
956 'Reviewed-by': {self.fred}}
Simon Glassdc4b2a92020-10-29 21:46:38 -0600957 status.find_new_responses(new_rtag_list, review_list, 1, commit2,
Simon Glass7cbf02e2020-11-03 13:54:14 -0700958 patch2, None, self._fake_patchwork2)
Simon Glassdc6df972020-10-29 21:46:35 -0600959 self.assertEqual(new_rtag_list[1], {'Reviewed-by': {self.mary}})
960
961 # Check that the output patches expectations:
962 # 1 Subject 1
963 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
964 # 2 Subject 2
965 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
966 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
967 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
968 # 1 new response available in patchwork
969
970 series = Series()
971 series.commits = [commit1, commit2]
Simon Glass098b10f2022-01-29 14:14:18 -0700972 terminal.set_print_test_mode()
Simon Glassdc4b2a92020-10-29 21:46:38 -0600973 status.check_patchwork_status(series, '1234', None, None, False, False,
Simon Glass7cbf02e2020-11-03 13:54:14 -0700974 None, self._fake_patchwork2)
Simon Glass098b10f2022-01-29 14:14:18 -0700975 lines = iter(terminal.get_print_test_lines())
Simon Glassdc6df972020-10-29 21:46:35 -0600976 col = terminal.Color()
977 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
978 next(lines))
979 self.assertEqual(
980 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
981 bright=False),
982 next(lines))
983 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE, bright=False),
984 next(lines))
985
986 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
987 next(lines))
988 self.assertEqual(
Simon Glassdc6df972020-10-29 21:46:35 -0600989 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
990 bright=False),
991 next(lines))
992 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE, bright=False),
993 next(lines))
994 self.assertEqual(
Simon Glassdc4b2a92020-10-29 21:46:38 -0600995 terminal.PrintLine(' Tested-by: ', col.GREEN, newline=False,
996 bright=False),
997 next(lines))
998 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE, bright=False),
999 next(lines))
1000 self.assertEqual(
Simon Glassdc6df972020-10-29 21:46:35 -06001001 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1002 next(lines))
1003 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
1004 next(lines))
1005 self.assertEqual(terminal.PrintLine(
Simon Glass8f9ba3a2020-10-29 21:46:36 -06001006 '1 new response available in patchwork (use -d to write them to a new branch)',
1007 None), next(lines))
1008
Simon Glass7cbf02e2020-11-03 13:54:14 -07001009 def _fake_patchwork3(self, url, subpath):
Simon Glass8f9ba3a2020-10-29 21:46:36 -06001010 """Fake Patchwork server for the function below
1011
1012 This handles accessing series, patches and comments, providing the data
1013 in self.patches to the caller
Simon Glass7cbf02e2020-11-03 13:54:14 -07001014
1015 Args:
1016 url (str): URL of patchwork server
1017 subpath (str): URL subpath to use
Simon Glass8f9ba3a2020-10-29 21:46:36 -06001018 """
1019 re_series = re.match(r'series/(\d*)/$', subpath)
1020 re_patch = re.match(r'patches/(\d*)/$', subpath)
1021 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
1022 if re_series:
1023 series_num = re_series.group(1)
1024 if series_num == '1234':
1025 return {'patches': self.patches}
1026 elif re_patch:
1027 patch_num = int(re_patch.group(1))
1028 patch = self.patches[patch_num - 1]
1029 return patch
1030 elif re_comments:
1031 patch_num = int(re_comments.group(1))
1032 patch = self.patches[patch_num - 1]
1033 return patch.comments
1034 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
1035
Simon Glassc3aaa052022-01-29 14:14:09 -07001036 def test_create_branch(self):
Simon Glass8f9ba3a2020-10-29 21:46:36 -06001037 """Test operation of create_branch()"""
1038 repo = self.make_git_tree()
1039 branch = 'first'
1040 dest_branch = 'first2'
1041 count = 2
1042 gitdir = os.path.join(self.gitdir, '.git')
1043
1044 # Set up the test git tree. We use branch 'first' which has two commits
1045 # in it
1046 series = patchstream.get_metadata_for_list(branch, gitdir, count)
1047 self.assertEqual(2, len(series.commits))
1048
1049 patch1 = status.Patch('1')
1050 patch1.parse_subject('[1/2] %s' % series.commits[0].subject)
1051 patch1.name = patch1.raw_subject
1052 patch1.content = 'This is my patch content'
1053 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
1054
1055 patch1.comments = [comment1a]
1056
1057 patch2 = status.Patch('2')
1058 patch2.parse_subject('[2/2] %s' % series.commits[1].subject)
1059 patch2.name = patch2.raw_subject
1060 patch2.content = 'Some other patch content'
1061 comment2a = {
1062 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1063 (self.mary, self.leb)}
1064 comment2b = {
1065 'content': 'Reviewed-by: %s' % self.fred}
1066 patch2.comments = [comment2a, comment2b]
1067
1068 # This test works by setting up patches for use by the fake Rest API
1069 # function _fake_patchwork3(). The fake patch comments above should
1070 # result in new review tags that are collected and added to the commits
1071 # created in the destination branch.
1072 self.patches = [patch1, patch2]
1073 count = 2
1074
1075 # Expected output:
1076 # 1 i2c: I2C things
1077 # + Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1078 # 2 spi: SPI fixes
1079 # + Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1080 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1081 # + Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1082 # 4 new responses available in patchwork
1083 # 4 responses added from patchwork into new branch 'first2'
1084 # <unittest.result.TestResult run=8 errors=0 failures=0>
1085
Simon Glass098b10f2022-01-29 14:14:18 -07001086 terminal.set_print_test_mode()
Simon Glass8f9ba3a2020-10-29 21:46:36 -06001087 status.check_patchwork_status(series, '1234', branch, dest_branch,
Simon Glass7cbf02e2020-11-03 13:54:14 -07001088 False, False, None, self._fake_patchwork3,
1089 repo)
Simon Glass098b10f2022-01-29 14:14:18 -07001090 lines = terminal.get_print_test_lines()
Simon Glass8f9ba3a2020-10-29 21:46:36 -06001091 self.assertEqual(12, len(lines))
1092 self.assertEqual(
1093 "4 responses added from patchwork into new branch 'first2'",
1094 lines[11].text)
1095
1096 # Check that the destination branch has the new tags
1097 new_series = patchstream.get_metadata_for_list(dest_branch, gitdir,
1098 count)
1099 self.assertEqual(
1100 {'Reviewed-by': {self.joe}},
1101 new_series.commits[0].rtags)
1102 self.assertEqual(
1103 {'Tested-by': {self.leb},
1104 'Reviewed-by': {self.fred, self.mary}},
1105 new_series.commits[1].rtags)
1106
1107 # Now check the actual test of the first commit message. We expect to
1108 # see the new tags immediately below the old ones.
1109 stdout = patchstream.get_list(dest_branch, count=count, git_dir=gitdir)
1110 lines = iter([line.strip() for line in stdout.splitlines()
1111 if '-by:' in line])
1112
1113 # First patch should have the review tag
1114 self.assertEqual('Reviewed-by: %s' % self.joe, next(lines))
1115
1116 # Second patch should have the sign-off then the tested-by and two
1117 # reviewed-by tags
1118 self.assertEqual('Signed-off-by: %s' % self.leb, next(lines))
1119 self.assertEqual('Reviewed-by: %s' % self.fred, next(lines))
1120 self.assertEqual('Reviewed-by: %s' % self.mary, next(lines))
1121 self.assertEqual('Tested-by: %s' % self.leb, next(lines))
Simon Glass6b3252e2020-10-29 21:46:37 -06001122
Simon Glassc3aaa052022-01-29 14:14:09 -07001123 def test_parse_snippets(self):
Simon Glass6b3252e2020-10-29 21:46:37 -06001124 """Test parsing of review snippets"""
1125 text = '''Hi Fred,
1126
1127This is a comment from someone.
1128
1129Something else
1130
1131On some recent date, Fred wrote:
1132> This is why I wrote the patch
1133> so here it is
1134
1135Now a comment about the commit message
1136A little more to say
1137
1138Even more
1139
1140> diff --git a/file.c b/file.c
1141> Some more code
1142> Code line 2
1143> Code line 3
1144> Code line 4
1145> Code line 5
1146> Code line 6
1147> Code line 7
1148> Code line 8
1149> Code line 9
1150
1151And another comment
1152
Simon Glassc3aaa052022-01-29 14:14:09 -07001153> @@ -153,8 +143,13 @@ def check_patch(fname, show_types=False):
Simon Glass6b3252e2020-10-29 21:46:37 -06001154> further down on the file
1155> and more code
1156> +Addition here
1157> +Another addition here
1158> codey
1159> more codey
1160
1161and another thing in same file
1162
1163> @@ -253,8 +243,13 @@
1164> with no function context
1165
1166one more thing
1167
1168> diff --git a/tools/patman/main.py b/tools/patman/main.py
1169> +line of code
1170now a very long comment in a different file
1171line2
1172line3
1173line4
1174line5
1175line6
1176line7
1177line8
1178'''
1179 pstrm = PatchStream.process_text(text, True)
1180 self.assertEqual([], pstrm.commit.warn)
1181
1182 # We expect to the filename and up to 5 lines of code context before
1183 # each comment. The 'On xxx wrote:' bit should be removed.
1184 self.assertEqual(
1185 [['Hi Fred,',
1186 'This is a comment from someone.',
1187 'Something else'],
1188 ['> This is why I wrote the patch',
1189 '> so here it is',
1190 'Now a comment about the commit message',
1191 'A little more to say', 'Even more'],
1192 ['> File: file.c', '> Code line 5', '> Code line 6',
1193 '> Code line 7', '> Code line 8', '> Code line 9',
1194 'And another comment'],
1195 ['> File: file.c',
Simon Glassc3aaa052022-01-29 14:14:09 -07001196 '> Line: 153 / 143: def check_patch(fname, show_types=False):',
Simon Glass6b3252e2020-10-29 21:46:37 -06001197 '> and more code', '> +Addition here', '> +Another addition here',
1198 '> codey', '> more codey', 'and another thing in same file'],
1199 ['> File: file.c', '> Line: 253 / 243',
1200 '> with no function context', 'one more thing'],
1201 ['> File: tools/patman/main.py', '> +line of code',
1202 'now a very long comment in a different file',
1203 'line2', 'line3', 'line4', 'line5', 'line6', 'line7', 'line8']],
1204 pstrm.snippets)
Simon Glassdc4b2a92020-10-29 21:46:38 -06001205
Simon Glassc3aaa052022-01-29 14:14:09 -07001206 def test_review_snippets(self):
Simon Glassdc4b2a92020-10-29 21:46:38 -06001207 """Test showing of review snippets"""
1208 def _to_submitter(who):
1209 m_who = re.match('(.*) <(.*)>', who)
1210 return {
1211 'name': m_who.group(1),
1212 'email': m_who.group(2)
1213 }
1214
1215 commit1 = Commit('abcd')
1216 commit1.subject = 'Subject 1'
1217 commit2 = Commit('ef12')
1218 commit2.subject = 'Subject 2'
1219
1220 patch1 = status.Patch('1')
1221 patch1.parse_subject('[1/2] Subject 1')
1222 patch1.name = patch1.raw_subject
1223 patch1.content = 'This is my patch content'
1224 comment1a = {'submitter': _to_submitter(self.joe),
1225 'content': '''Hi Fred,
1226
1227On some date Fred wrote:
1228
1229> diff --git a/file.c b/file.c
1230> Some code
1231> and more code
1232
1233Here is my comment above the above...
1234
1235
1236Reviewed-by: %s
1237''' % self.joe}
1238
1239 patch1.comments = [comment1a]
1240
1241 patch2 = status.Patch('2')
1242 patch2.parse_subject('[2/2] Subject 2')
1243 patch2.name = patch2.raw_subject
1244 patch2.content = 'Some other patch content'
1245 comment2a = {
1246 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1247 (self.mary, self.leb)}
1248 comment2b = {'submitter': _to_submitter(self.fred),
1249 'content': '''Hi Fred,
1250
1251On some date Fred wrote:
1252
1253> diff --git a/tools/patman/commit.py b/tools/patman/commit.py
1254> @@ -41,6 +41,9 @@ class Commit:
1255> self.rtags = collections.defaultdict(set)
1256> self.warn = []
1257>
1258> + def __str__(self):
1259> + return self.subject
1260> +
Simon Glassc3aaa052022-01-29 14:14:09 -07001261> def add_change(self, version, info):
Simon Glassdc4b2a92020-10-29 21:46:38 -06001262> """Add a new change line to the change list for a version.
1263>
1264A comment
1265
1266Reviewed-by: %s
1267''' % self.fred}
1268 patch2.comments = [comment2a, comment2b]
1269
1270 # This test works by setting up commits and patch for use by the fake
1271 # Rest API function _fake_patchwork2(). It calls various functions in
1272 # the status module after setting up tags in the commits, checking that
1273 # things behaves as expected
1274 self.commits = [commit1, commit2]
1275 self.patches = [patch1, patch2]
1276
1277 # Check that the output patches expectations:
1278 # 1 Subject 1
1279 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1280 # 2 Subject 2
1281 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1282 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1283 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1284 # 1 new response available in patchwork
1285
1286 series = Series()
1287 series.commits = [commit1, commit2]
Simon Glass098b10f2022-01-29 14:14:18 -07001288 terminal.set_print_test_mode()
Simon Glassdc4b2a92020-10-29 21:46:38 -06001289 status.check_patchwork_status(series, '1234', None, None, False, True,
Simon Glass7cbf02e2020-11-03 13:54:14 -07001290 None, self._fake_patchwork2)
Simon Glass098b10f2022-01-29 14:14:18 -07001291 lines = iter(terminal.get_print_test_lines())
Simon Glassdc4b2a92020-10-29 21:46:38 -06001292 col = terminal.Color()
1293 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
1294 next(lines))
1295 self.assertEqual(
1296 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1297 next(lines))
1298 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE), next(lines))
1299
1300 self.assertEqual(terminal.PrintLine('Review: %s' % self.joe, col.RED),
1301 next(lines))
1302 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(lines))
1303 self.assertEqual(terminal.PrintLine('', None), next(lines))
1304 self.assertEqual(terminal.PrintLine(' > File: file.c', col.MAGENTA),
1305 next(lines))
1306 self.assertEqual(terminal.PrintLine(' > Some code', col.MAGENTA),
1307 next(lines))
1308 self.assertEqual(terminal.PrintLine(' > and more code', col.MAGENTA),
1309 next(lines))
1310 self.assertEqual(terminal.PrintLine(
1311 ' Here is my comment above the above...', None), next(lines))
1312 self.assertEqual(terminal.PrintLine('', None), next(lines))
1313
1314 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
1315 next(lines))
1316 self.assertEqual(
1317 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1318 next(lines))
1319 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE),
1320 next(lines))
1321 self.assertEqual(
1322 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1323 next(lines))
1324 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
1325 next(lines))
1326 self.assertEqual(
1327 terminal.PrintLine(' + Tested-by: ', col.GREEN, newline=False),
1328 next(lines))
1329 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE),
1330 next(lines))
1331
1332 self.assertEqual(terminal.PrintLine('Review: %s' % self.fred, col.RED),
1333 next(lines))
1334 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(lines))
1335 self.assertEqual(terminal.PrintLine('', None), next(lines))
1336 self.assertEqual(terminal.PrintLine(
1337 ' > File: tools/patman/commit.py', col.MAGENTA), next(lines))
1338 self.assertEqual(terminal.PrintLine(
1339 ' > Line: 41 / 41: class Commit:', col.MAGENTA), next(lines))
1340 self.assertEqual(terminal.PrintLine(
1341 ' > + return self.subject', col.MAGENTA), next(lines))
1342 self.assertEqual(terminal.PrintLine(
1343 ' > +', col.MAGENTA), next(lines))
1344 self.assertEqual(
Simon Glassc3aaa052022-01-29 14:14:09 -07001345 terminal.PrintLine(' > def add_change(self, version, info):',
Simon Glassdc4b2a92020-10-29 21:46:38 -06001346 col.MAGENTA),
1347 next(lines))
1348 self.assertEqual(terminal.PrintLine(
1349 ' > """Add a new change line to the change list for a version.',
1350 col.MAGENTA), next(lines))
1351 self.assertEqual(terminal.PrintLine(
1352 ' >', col.MAGENTA), next(lines))
1353 self.assertEqual(terminal.PrintLine(
1354 ' A comment', None), next(lines))
1355 self.assertEqual(terminal.PrintLine('', None), next(lines))
1356
1357 self.assertEqual(terminal.PrintLine(
1358 '4 new responses available in patchwork (use -d to write them to a new branch)',
1359 None), next(lines))
Simon Glass59747182021-08-01 16:02:39 -06001360
Simon Glassc3aaa052022-01-29 14:14:09 -07001361 def test_insert_tags(self):
Simon Glass59747182021-08-01 16:02:39 -06001362 """Test inserting of review tags"""
1363 msg = '''first line
1364second line.'''
1365 tags = [
1366 'Reviewed-by: Bin Meng <bmeng.cn@gmail.com>',
1367 'Tested-by: Bin Meng <bmeng.cn@gmail.com>'
1368 ]
1369 signoff = 'Signed-off-by: Simon Glass <sjg@chromium.com>'
1370 tag_str = '\n'.join(tags)
1371
1372 new_msg = patchstream.insert_tags(msg, tags)
1373 self.assertEqual(msg + '\n\n' + tag_str, new_msg)
1374
1375 new_msg = patchstream.insert_tags(msg + '\n', tags)
1376 self.assertEqual(msg + '\n\n' + tag_str, new_msg)
1377
1378 msg += '\n\n' + signoff
1379 new_msg = patchstream.insert_tags(msg, tags)
1380 self.assertEqual(msg + '\n' + tag_str, new_msg)