blob: e3918497cf43a5c5621589c4d79a75edfd887626 [file] [log] [blame]
Simon Glassdf1bc5c2017-05-29 15:31:31 -06001# -*- coding: utf-8 -*-
Tom Rini10e47792018-05-06 17:58:06 -04002# SPDX-License-Identifier: GPL-2.0+
Simon Glassdf1bc5c2017-05-29 15:31:31 -06003#
4# Copyright 2017 Google, Inc
5#
Simon Glassdf1bc5c2017-05-29 15:31:31 -06006
Simon Glasseb209e52020-10-29 21:46:15 -06007"""Functional tests for checking that patman behaves correctly"""
8
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -05009import contextlib
Simon Glassdf1bc5c2017-05-29 15:31:31 -060010import os
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050011import pathlib
Simon Glassdf1bc5c2017-05-29 15:31:31 -060012import re
13import shutil
14import sys
15import tempfile
16import unittest
17
Simon Glass3db916d2020-10-29 21:46:35 -060018
19from patman.commit import Commit
Simon Glass54f1c5b2020-07-05 21:41:50 -060020from patman import control
Simon Glassa997ea52020-04-17 18:09:04 -060021from patman import gitutil
22from patman import patchstream
Simon Glassa7fadab2020-10-29 21:46:26 -060023from patman.patchstream import PatchStream
Simon Glass3db916d2020-10-29 21:46:35 -060024from patman.series import Series
Simon Glassa997ea52020-04-17 18:09:04 -060025from patman import settings
Simon Glass131444f2023-02-23 18:18:04 -070026from u_boot_pylib import terminal
27from u_boot_pylib import tools
28from u_boot_pylib.test_util import capture_sys_output
Simon Glassdf1bc5c2017-05-29 15:31:31 -060029
Tom Rini488ea972021-02-26 07:52:31 -050030import pygit2
31from patman import status
Simon Glassdf1bc5c2017-05-29 15:31:31 -060032
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -050033PATMAN_DIR = pathlib.Path(__file__).parent
34TEST_DATA_DIR = PATMAN_DIR / 'test/'
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050035
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050036
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -050037@contextlib.contextmanager
38def directory_excursion(directory):
39 """Change directory to `directory` for a limited to the context block."""
40 current = os.getcwd()
41 try:
42 os.chdir(directory)
43 yield
44 finally:
45 os.chdir(current)
46
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050047
Simon Glassdf1bc5c2017-05-29 15:31:31 -060048class TestFunctional(unittest.TestCase):
Simon Glasseb209e52020-10-29 21:46:15 -060049 """Functional tests for checking that patman behaves correctly"""
Simon Glass06202d62020-10-29 21:46:27 -060050 leb = (b'Lord Edmund Blackadd\xc3\xabr <weasel@blackadder.org>'.
51 decode('utf-8'))
Simon Glass3b762cc2020-10-29 21:46:28 -060052 fred = 'Fred Bloggs <f.bloggs@napier.net>'
53 joe = 'Joe Bloggs <joe@napierwallies.co.nz>'
54 mary = 'Mary Bloggs <mary@napierwallies.co.nz>'
Simon Glass3db916d2020-10-29 21:46:35 -060055 commits = None
56 patches = None
Simon Glass06202d62020-10-29 21:46:27 -060057
Simon Glassdf1bc5c2017-05-29 15:31:31 -060058 def setUp(self):
59 self.tmpdir = tempfile.mkdtemp(prefix='patman.')
Simon Glass54f1c5b2020-07-05 21:41:50 -060060 self.gitdir = os.path.join(self.tmpdir, 'git')
61 self.repo = None
Simon Glassdf1bc5c2017-05-29 15:31:31 -060062
63 def tearDown(self):
64 shutil.rmtree(self.tmpdir)
Simon Glass02811582022-01-29 14:14:18 -070065 terminal.set_print_test_mode(False)
Simon Glassdf1bc5c2017-05-29 15:31:31 -060066
67 @staticmethod
Simon Glasseb209e52020-10-29 21:46:15 -060068 def _get_path(fname):
69 """Get the path to a test file
70
71 Args:
72 fname (str): Filename to obtain
73
74 Returns:
75 str: Full path to file in the test directory
76 """
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050077 return TEST_DATA_DIR / fname
Simon Glassdf1bc5c2017-05-29 15:31:31 -060078
79 @classmethod
Simon Glasseb209e52020-10-29 21:46:15 -060080 def _get_text(cls, fname):
81 """Read a file as text
82
83 Args:
84 fname (str): Filename to read
85
86 Returns:
87 str: Contents of file
88 """
89 return open(cls._get_path(fname), encoding='utf-8').read()
Simon Glassdf1bc5c2017-05-29 15:31:31 -060090
91 @classmethod
Simon Glasseb209e52020-10-29 21:46:15 -060092 def _get_patch_name(cls, subject):
93 """Get the filename of a patch given its subject
94
95 Args:
96 subject (str): Patch subject
97
98 Returns:
99 str: Filename for that patch
100 """
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600101 fname = re.sub('[ :]', '-', subject)
102 return fname.replace('--', '-')
103
Simon Glasseb209e52020-10-29 21:46:15 -0600104 def _create_patches_for_test(self, series):
105 """Create patch files for use by tests
106
107 This copies patch files from the test directory as needed by the series
108
109 Args:
110 series (Series): Series containing commits to convert
111
112 Returns:
113 tuple:
114 str: Cover-letter filename, or None if none
115 fname_list: list of str, each a patch filename
116 """
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600117 cover_fname = None
118 fname_list = []
119 for i, commit in enumerate(series.commits):
Simon Glasseb209e52020-10-29 21:46:15 -0600120 clean_subject = self._get_patch_name(commit.subject)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600121 src_fname = '%04d-%s.patch' % (i + 1, clean_subject[:52])
122 fname = os.path.join(self.tmpdir, src_fname)
Simon Glasseb209e52020-10-29 21:46:15 -0600123 shutil.copy(self._get_path(src_fname), fname)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600124 fname_list.append(fname)
125 if series.get('cover'):
126 src_fname = '0000-cover-letter.patch'
127 cover_fname = os.path.join(self.tmpdir, src_fname)
128 fname = os.path.join(self.tmpdir, src_fname)
Simon Glasseb209e52020-10-29 21:46:15 -0600129 shutil.copy(self._get_path(src_fname), fname)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600130
131 return cover_fname, fname_list
132
Simon Glassd85bb8f2022-01-29 14:14:09 -0700133 def test_basic(self):
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600134 """Tests the basic flow of patman
135
136 This creates a series from some hard-coded patches build from a simple
137 tree with the following metadata in the top commit:
138
139 Series-to: u-boot
140 Series-prefix: RFC
Sean Andersondc1cd132021-10-22 19:07:04 -0400141 Series-postfix: some-branch
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600142 Series-cc: Stefan Brüns <stefan.bruens@rwth-aachen.de>
143 Cover-letter-cc: Lord Mëlchett <clergy@palace.gov>
Sean Andersoncf13b862020-05-04 16:28:36 -0400144 Series-version: 3
145 Patch-cc: fred
146 Series-process-log: sort, uniq
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600147 Series-changes: 4
148 - Some changes
Sean Andersoncf13b862020-05-04 16:28:36 -0400149 - Multi
150 line
151 change
152
153 Commit-changes: 2
154 - Changes only for this commit
155
Simon Glass6a222e62021-08-01 16:02:39 -0600156' Cover-changes: 4
Sean Andersoncf13b862020-05-04 16:28:36 -0400157 - Some notes for the cover letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600158
159 Cover-letter:
160 test: A test patch series
161 This is a test of how the cover
Sean Andersoncf13b862020-05-04 16:28:36 -0400162 letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600163 works
164 END
165
166 and this in the first commit:
167
Sean Andersoncf13b862020-05-04 16:28:36 -0400168 Commit-changes: 2
169 - second revision change
170
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600171 Series-notes:
172 some notes
173 about some things
174 from the first commit
175 END
176
177 Commit-notes:
178 Some notes about
179 the first commit
180 END
181
182 with the following commands:
183
184 git log -n2 --reverse >/path/to/tools/patman/test/test01.txt
185 git format-patch --subject-prefix RFC --cover-letter HEAD~2
186 mv 00* /path/to/tools/patman/test
187
188 It checks these aspects:
189 - git log can be processed by patchstream
190 - emailing patches uses the correct command
191 - CC file has information on each commit
192 - cover letter has the expected text and subject
193 - each patch has the correct subject
194 - dry-run information prints out correctly
195 - unicode is handled correctly
Sean Andersondc1cd132021-10-22 19:07:04 -0400196 - Series-to, Series-cc, Series-prefix, Series-postfix, Cover-letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600197 - Cover-letter-cc, Series-version, Series-changes, Series-notes
198 - Commit-notes
199 """
200 process_tags = True
Simon Glass1f975b92021-01-23 08:56:15 -0700201 ignore_bad_tags = False
Simon Glass4f817892019-05-14 15:53:53 -0600202 stefan = b'Stefan Br\xc3\xbcns <stefan.bruens@rwth-aachen.de>'.decode('utf-8')
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600203 rick = 'Richard III <richard@palace.gov>'
Simon Glass4f817892019-05-14 15:53:53 -0600204 mel = b'Lord M\xc3\xablchett <clergy@palace.gov>'.decode('utf-8')
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600205 add_maintainers = [stefan, rick]
206 dry_run = True
207 in_reply_to = mel
208 count = 2
209 settings.alias = {
Simon Glass95745aa2020-10-29 21:46:13 -0600210 'fdt': ['simon'],
211 'u-boot': ['u-boot@lists.denx.de'],
Simon Glass06202d62020-10-29 21:46:27 -0600212 'simon': [self.leb],
Simon Glass3b762cc2020-10-29 21:46:28 -0600213 'fred': [self.fred],
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600214 }
215
Simon Glasseb209e52020-10-29 21:46:15 -0600216 text = self._get_text('test01.txt')
Simon Glass93f61c02020-10-29 21:46:19 -0600217 series = patchstream.get_metadata_for_test(text)
Simon Glasseb209e52020-10-29 21:46:15 -0600218 cover_fname, args = self._create_patches_for_test(series)
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500219 get_maintainer_script = str(pathlib.Path(__file__).parent.parent.parent
220 / 'get_maintainer.pl') + ' --norolestats'
Simon Glass59a70bb2020-10-29 21:46:14 -0600221 with capture_sys_output() as out:
Simon Glass93f61c02020-10-29 21:46:19 -0600222 patchstream.fix_patches(series, args)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600223 if cover_fname and series.get('cover'):
Simon Glass93f61c02020-10-29 21:46:19 -0600224 patchstream.insert_cover_letter(cover_fname, series, count)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600225 series.DoChecks()
226 cc_file = series.MakeCcFile(process_tags, cover_fname,
Chris Packhamb84fb482018-06-07 20:45:06 +1200227 not ignore_bad_tags, add_maintainers,
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500228 None, get_maintainer_script)
Simon Glass761648b2022-01-29 14:14:11 -0700229 cmd = gitutil.email_patches(
Simon Glass95745aa2020-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 Glassdf1bc5c2017-05-29 15:31:31 -0600232 series.ShowActions(args, cmd, process_tags)
Simon Glassf544a2d2019-10-31 07:42:51 -0600233 cc_lines = open(cc_file, encoding='utf-8').read().splitlines()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600234 os.remove(cc_file)
235
Simon Glass42e3d392020-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 Glass620639c2023-03-08 10:52:54 -0800243 while next(lines) != 'Cc processing complete':
244 pass
Simon Glass42e3d392020-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 Glass9dfb3112020-11-08 20:36:18 -0700256 self.assertEqual('Cc: %s' % stefan, next(lines))
Simon Glass42e3d392020-10-29 21:46:29 -0600257 self.assertEqual('Version: 3', next(lines))
258 self.assertEqual('Prefix:\t RFC', next(lines))
Sean Andersondc1cd132021-10-22 19:07:04 -0400259 self.assertEqual('Postfix:\t some-branch', next(lines))
Simon Glass42e3d392020-10-29 21:46:29 -0600260 self.assertEqual('Cover: 4 lines', next(lines))
261 self.assertEqual(' Cc: %s' % self.fred, next(lines))
Simon Glass9dfb3112020-11-08 20:36:18 -0700262 self.assertEqual(' Cc: %s' % self.leb,
Simon Glass42e3d392020-10-29 21:46:29 -0600263 next(lines))
Simon Glass9dfb3112020-11-08 20:36:18 -0700264 self.assertEqual(' Cc: %s' % mel, next(lines))
Simon Glass42e3d392020-10-29 21:46:29 -0600265 self.assertEqual(' Cc: %s' % rick, next(lines))
Simon Glassdf1bc5c2017-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 Glass1ee91c12020-11-03 13:54:10 -0700268 '--cc "%s" --cc-cmd "%s send --cc-cmd %s" %s %s'
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600269 % (in_reply_to, stefan, sys.argv[0], cc_file, cover_fname,
Simon Glass4f817892019-05-14 15:53:53 -0600270 ' '.join(args)))
Simon Glass9dfb3112020-11-08 20:36:18 -0700271 self.assertEqual(expected, next(lines))
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600272
Simon Glass9dfb3112020-11-08 20:36:18 -0700273 self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)), cc_lines[0])
Simon Glass95745aa2020-10-29 21:46:13 -0600274 self.assertEqual(
Simon Glass3b762cc2020-10-29 21:46:28 -0600275 '%s %s\0%s\0%s\0%s' % (args[1], self.fred, self.leb, rick, stefan),
Simon Glass9dfb3112020-11-08 20:36:18 -0700276 cc_lines[1])
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600277
278 expected = '''
279This is a test of how the cover
Sean Andersoncf13b862020-05-04 16:28:36 -0400280letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600281works
282
283some notes
284about some things
285from the first commit
286
287Changes in v4:
Sean Andersoncf13b862020-05-04 16:28:36 -0400288- Multi
289 line
290 change
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600291- Some changes
Sean Andersoncf13b862020-05-04 16:28:36 -0400292- Some notes for the cover letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600293
294Simon Glass (2):
295 pci: Correct cast for sandbox
Siva Durga Prasad Paladugub3d55ea2018-07-16 15:56:11 +0530296 fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
Simon Glassdf1bc5c2017-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 Glassf544a2d2019-10-31 07:42:51 -0600308 lines = open(cover_fname, encoding='utf-8').read().splitlines()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600309 self.assertEqual(
Sean Andersondc1cd132021-10-22 19:07:04 -0400310 'Subject: [RFC PATCH some-branch v3 0/2] test: A test patch series',
Simon Glass95745aa2020-10-29 21:46:13 -0600311 lines[3])
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600312 self.assertEqual(expected.splitlines(), lines[7:])
313
314 for i, fname in enumerate(args):
Simon Glassf544a2d2019-10-31 07:42:51 -0600315 lines = open(fname, encoding='utf-8').read().splitlines()
Simon Glassdf1bc5c2017-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 Andersoncf13b862020-05-04 16:28:36 -0400319
320 # Check that we got our commit notes
321 start = 0
322 expected = ''
323
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600324 if i == 0:
Sean Andersoncf13b862020-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 Glass54f1c5b2020-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 Glass80025522022-01-29 14:14:04 -0700361 tools.write_file(path, text, binary=False)
Simon Glass54f1c5b2020-07-05 21:41:50 -0600362 index = self.repo.index
363 index.add(fname)
Simon Glass547cba62022-02-11 13:23:18 -0700364 # pylint doesn't seem to find this
365 # pylint: disable=E1101
Simon Glass95745aa2020-10-29 21:46:13 -0600366 author = pygit2.Signature('Test user', 'test@email.com')
Simon Glass54f1c5b2020-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 Glasseb209e52020-10-29 21:46:15 -0600382 pygit2.Repository: repository
Simon Glass54f1c5b2020-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 Glass547cba62022-02-11 13:23:18 -0700388 # pylint doesn't seem to find this
389 # pylint: disable=E1101
Simon Glass54f1c5b2020-07-05 21:41:50 -0600390 author = pygit2.Signature('Test user', 'test@email.com')
391 committer = author
Simon Glasseb209e52020-10-29 21:46:15 -0600392 _ = repo.create_commit('HEAD', author, committer, 'Created master',
393 new_tree, [])
Simon Glass54f1c5b2020-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 Glassd0a0a582020-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 Glass54f1c5b2020-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 Glass547cba62022-02-11 13:23:18 -0700441 # pylint doesn't seem to find this
442 # pylint: disable=E1101
Simon Glass54f1c5b2020-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 Glassa80986c2020-10-29 21:46:16 -0600461Series-links: 183237
Simon Glass54f1c5b2020-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 Glassd85bb8f2022-01-29 14:14:09 -0700484 def test_branch(self):
Simon Glass54f1c5b2020-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 Glass547cba62022-02-11 13:23:18 -0700488 # pylint doesn't seem to find this
489 # pylint: disable=E1101
Simon Glass54f1c5b2020-07-05 21:41:50 -0600490 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
491 control.setup()
Heinrich Schuchardtd01d6672023-04-20 20:07:29 +0200492 orig_dir = os.getcwd()
Simon Glass54f1c5b2020-07-05 21:41:50 -0600493 try:
Simon Glass54f1c5b2020-07-05 21:41:50 -0600494 os.chdir(self.gitdir)
495
496 # Check that it can detect the current branch
Simon Glass761648b2022-01-29 14:14:11 -0700497 self.assertEqual(2, gitutil.count_commits_to_branch(None))
Simon Glass54f1c5b2020-07-05 21:41:50 -0600498 col = terminal.Color()
499 with capture_sys_output() as _:
500 _, cover_fname, patch_files = control.prepare_patches(
Simon Glassb3bf4e12020-07-05 21:41:52 -0600501 col, branch=None, count=-1, start=0, end=0,
Philipp Tomsich858531a2020-11-24 18:14:52 +0100502 ignore_binary=False, signoff=True)
Simon Glass54f1c5b2020-07-05 21:41:50 -0600503 self.assertIsNone(cover_fname)
504 self.assertEqual(2, len(patch_files))
Simon Glass2eb4da72020-07-05 21:41:51 -0600505
506 # Check that it can detect a different branch
Simon Glass761648b2022-01-29 14:14:11 -0700507 self.assertEqual(3, gitutil.count_commits_to_branch('second'))
Simon Glass2eb4da72020-07-05 21:41:51 -0600508 with capture_sys_output() as _:
509 _, cover_fname, patch_files = control.prepare_patches(
Simon Glassb3bf4e12020-07-05 21:41:52 -0600510 col, branch='second', count=-1, start=0, end=0,
Philipp Tomsich858531a2020-11-24 18:14:52 +0100511 ignore_binary=False, signoff=True)
Simon Glass2eb4da72020-07-05 21:41:51 -0600512 self.assertIsNotNone(cover_fname)
513 self.assertEqual(3, len(patch_files))
Simon Glassb3bf4e12020-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 Tomsich858531a2020-11-24 18:14:52 +0100519 ignore_binary=False, signoff=True)
Simon Glassb3bf4e12020-07-05 21:41:52 -0600520 self.assertIsNotNone(cover_fname)
521 self.assertEqual(2, len(patch_files))
Simon Glass54f1c5b2020-07-05 21:41:50 -0600522 finally:
523 os.chdir(orig_dir)
Simon Glass06202d62020-10-29 21:46:27 -0600524
Maxim Cournoyer3ef23e92022-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'
543 'check_patch: False\n')
544 with open('dummy-script.sh', 'w', buffering=1) as f:
545 f.write('#!/usr/bin/env python\n'
546 'print("hello@there.com")\n')
547 os.chmod('dummy-script.sh', 0x555)
548
549 # Finally, do the test
550 with capture_sys_output():
551 output = tools.run(PATMAN_DIR / 'patman', '--dry-run')
552 # Assert the email address is part of the dry-run
553 # output.
554 self.assertIn('hello@there.com', output)
555
Simon Glassd85bb8f2022-01-29 14:14:09 -0700556 def test_tags(self):
Simon Glass06202d62020-10-29 21:46:27 -0600557 """Test collection of tags in a patchstream"""
558 text = '''This is a patch
559
560Signed-off-by: Terminator
Simon Glass3b762cc2020-10-29 21:46:28 -0600561Reviewed-by: %s
562Reviewed-by: %s
Simon Glass06202d62020-10-29 21:46:27 -0600563Tested-by: %s
Simon Glass3b762cc2020-10-29 21:46:28 -0600564''' % (self.joe, self.mary, self.leb)
Simon Glass06202d62020-10-29 21:46:27 -0600565 pstrm = PatchStream.process_text(text)
566 self.assertEqual(pstrm.commit.rtags, {
Simon Glass3b762cc2020-10-29 21:46:28 -0600567 'Reviewed-by': {self.joe, self.mary},
Simon Glass06202d62020-10-29 21:46:27 -0600568 'Tested-by': {self.leb}})
Simon Glass3b762cc2020-10-29 21:46:28 -0600569
Simon Glassd85bb8f2022-01-29 14:14:09 -0700570 def test_invalid_tag(self):
Patrick Delaunay6bbdd0c2021-07-22 16:51:42 +0200571 """Test invalid tag in a patchstream"""
572 text = '''This is a patch
573
574Serie-version: 2
575'''
576 with self.assertRaises(ValueError) as exc:
577 pstrm = PatchStream.process_text(text)
578 self.assertEqual("Line 3: Invalid tag = 'Serie-version: 2'",
579 str(exc.exception))
580
Simon Glassd85bb8f2022-01-29 14:14:09 -0700581 def test_missing_end(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600582 """Test a missing END tag"""
583 text = '''This is a patch
584
585Cover-letter:
586This is the title
587missing END after this line
588Signed-off-by: Fred
589'''
590 pstrm = PatchStream.process_text(text)
591 self.assertEqual(["Missing 'END' in section 'cover'"],
592 pstrm.commit.warn)
593
Simon Glassd85bb8f2022-01-29 14:14:09 -0700594 def test_missing_blank_line(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600595 """Test a missing blank line after a tag"""
596 text = '''This is a patch
597
598Series-changes: 2
599- First line of changes
600- Missing blank line after this line
601Signed-off-by: Fred
602'''
603 pstrm = PatchStream.process_text(text)
604 self.assertEqual(["Missing 'blank line' in section 'Series-changes'"],
605 pstrm.commit.warn)
606
Simon Glassd85bb8f2022-01-29 14:14:09 -0700607 def test_invalid_commit_tag(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600608 """Test an invalid Commit-xxx tag"""
609 text = '''This is a patch
610
611Commit-fred: testing
612'''
613 pstrm = PatchStream.process_text(text)
614 self.assertEqual(["Line 3: Ignoring Commit-fred"], pstrm.commit.warn)
615
Simon Glassd85bb8f2022-01-29 14:14:09 -0700616 def test_self_test(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600617 """Test a tested by tag by this user"""
618 test_line = 'Tested-by: %s@napier.com' % os.getenv('USER')
619 text = '''This is a patch
620
621%s
622''' % test_line
623 pstrm = PatchStream.process_text(text)
624 self.assertEqual(["Ignoring '%s'" % test_line], pstrm.commit.warn)
625
Simon Glassd85bb8f2022-01-29 14:14:09 -0700626 def test_space_before_tab(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600627 """Test a space before a tab"""
628 text = '''This is a patch
629
630+ \tSomething
631'''
632 pstrm = PatchStream.process_text(text)
633 self.assertEqual(["Line 3/0 has space before tab"], pstrm.commit.warn)
634
Simon Glassd85bb8f2022-01-29 14:14:09 -0700635 def test_lines_after_test(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600636 """Test detecting lines after TEST= line"""
637 text = '''This is a patch
638
639TEST=sometest
640more lines
641here
642'''
643 pstrm = PatchStream.process_text(text)
644 self.assertEqual(["Found 2 lines after TEST="], pstrm.commit.warn)
645
Simon Glassd85bb8f2022-01-29 14:14:09 -0700646 def test_blank_line_at_end(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600647 """Test detecting a blank line at the end of a file"""
648 text = '''This is a patch
649
650diff --git a/lib/fdtdec.c b/lib/fdtdec.c
651index c072e54..942244f 100644
652--- a/lib/fdtdec.c
653+++ b/lib/fdtdec.c
654@@ -1200,7 +1200,8 @@ int fdtdec_setup_mem_size_base(void)
655 }
656
657 gd->ram_size = (phys_size_t)(res.end - res.start + 1);
658- debug("%s: Initial DRAM size %llx\n", __func__, (u64)gd->ram_size);
659+ debug("%s: Initial DRAM size %llx\n", __func__,
660+ (unsigned long long)gd->ram_size);
661+
662diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c
663
664--
6652.7.4
666
667 '''
668 pstrm = PatchStream.process_text(text)
669 self.assertEqual(
670 ["Found possible blank line(s) at end of file 'lib/fdtdec.c'"],
671 pstrm.commit.warn)
Simon Glass1c1f2072020-10-29 21:46:34 -0600672
Simon Glassd85bb8f2022-01-29 14:14:09 -0700673 def test_no_upstream(self):
Simon Glass1c1f2072020-10-29 21:46:34 -0600674 """Test CountCommitsToBranch when there is no upstream"""
675 repo = self.make_git_tree()
676 target = repo.lookup_reference('refs/heads/base')
Simon Glass547cba62022-02-11 13:23:18 -0700677 # pylint doesn't seem to find this
678 # pylint: disable=E1101
Simon Glass1c1f2072020-10-29 21:46:34 -0600679 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
680
681 # Check that it can detect the current branch
Heinrich Schuchardtd01d6672023-04-20 20:07:29 +0200682 orig_dir = os.getcwd()
Simon Glass1c1f2072020-10-29 21:46:34 -0600683 try:
Simon Glass1c1f2072020-10-29 21:46:34 -0600684 os.chdir(self.gitdir)
685 with self.assertRaises(ValueError) as exc:
Simon Glass761648b2022-01-29 14:14:11 -0700686 gitutil.count_commits_to_branch(None)
Simon Glass1c1f2072020-10-29 21:46:34 -0600687 self.assertIn(
688 "Failed to determine upstream: fatal: no upstream configured for branch 'base'",
689 str(exc.exception))
690 finally:
691 os.chdir(orig_dir)
Simon Glass3db916d2020-10-29 21:46:35 -0600692
693 @staticmethod
Simon Glassf9b03cf2020-11-03 13:54:14 -0700694 def _fake_patchwork(url, subpath):
Simon Glass3db916d2020-10-29 21:46:35 -0600695 """Fake Patchwork server for the function below
696
697 This handles accessing a series, providing a list consisting of a
698 single patch
Simon Glassf9b03cf2020-11-03 13:54:14 -0700699
700 Args:
701 url (str): URL of patchwork server
702 subpath (str): URL subpath to use
Simon Glass3db916d2020-10-29 21:46:35 -0600703 """
704 re_series = re.match(r'series/(\d*)/$', subpath)
705 if re_series:
706 series_num = re_series.group(1)
707 if series_num == '1234':
708 return {'patches': [
709 {'id': '1', 'name': 'Some patch'}]}
710 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
711
Simon Glassd85bb8f2022-01-29 14:14:09 -0700712 def test_status_mismatch(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600713 """Test Patchwork patches not matching the series"""
714 series = Series()
715
716 with capture_sys_output() as (_, err):
Simon Glassf9b03cf2020-11-03 13:54:14 -0700717 status.collect_patches(series, 1234, None, self._fake_patchwork)
Simon Glass3db916d2020-10-29 21:46:35 -0600718 self.assertIn('Warning: Patchwork reports 1 patches, series has 0',
719 err.getvalue())
720
Simon Glassd85bb8f2022-01-29 14:14:09 -0700721 def test_status_read_patch(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600722 """Test handling a single patch in Patchwork"""
723 series = Series()
724 series.commits = [Commit('abcd')]
725
Simon Glassf9b03cf2020-11-03 13:54:14 -0700726 patches = status.collect_patches(series, 1234, None,
727 self._fake_patchwork)
Simon Glass3db916d2020-10-29 21:46:35 -0600728 self.assertEqual(1, len(patches))
729 patch = patches[0]
730 self.assertEqual('1', patch.id)
731 self.assertEqual('Some patch', patch.raw_subject)
732
Simon Glassd85bb8f2022-01-29 14:14:09 -0700733 def test_parse_subject(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600734 """Test parsing of the patch subject"""
735 patch = status.Patch('1')
736
737 # Simple patch not in a series
738 patch.parse_subject('Testing')
739 self.assertEqual('Testing', patch.raw_subject)
740 self.assertEqual('Testing', patch.subject)
741 self.assertEqual(1, patch.seq)
742 self.assertEqual(1, patch.count)
743 self.assertEqual(None, patch.prefix)
744 self.assertEqual(None, patch.version)
745
746 # First patch in a series
747 patch.parse_subject('[1/2] Testing')
748 self.assertEqual('[1/2] Testing', patch.raw_subject)
749 self.assertEqual('Testing', patch.subject)
750 self.assertEqual(1, patch.seq)
751 self.assertEqual(2, patch.count)
752 self.assertEqual(None, patch.prefix)
753 self.assertEqual(None, patch.version)
754
755 # Second patch in a series
756 patch.parse_subject('[2/2] Testing')
757 self.assertEqual('Testing', patch.subject)
758 self.assertEqual(2, patch.seq)
759 self.assertEqual(2, patch.count)
760 self.assertEqual(None, patch.prefix)
761 self.assertEqual(None, patch.version)
762
763 # RFC patch
764 patch.parse_subject('[RFC,3/7] Testing')
765 self.assertEqual('Testing', patch.subject)
766 self.assertEqual(3, patch.seq)
767 self.assertEqual(7, patch.count)
768 self.assertEqual('RFC', patch.prefix)
769 self.assertEqual(None, patch.version)
770
771 # Version patch
772 patch.parse_subject('[v2,3/7] Testing')
773 self.assertEqual('Testing', patch.subject)
774 self.assertEqual(3, patch.seq)
775 self.assertEqual(7, patch.count)
776 self.assertEqual(None, patch.prefix)
777 self.assertEqual('v2', patch.version)
778
779 # All fields
780 patch.parse_subject('[RESEND,v2,3/7] Testing')
781 self.assertEqual('Testing', patch.subject)
782 self.assertEqual(3, patch.seq)
783 self.assertEqual(7, patch.count)
784 self.assertEqual('RESEND', patch.prefix)
785 self.assertEqual('v2', patch.version)
786
787 # RFC only
788 patch.parse_subject('[RESEND] Testing')
789 self.assertEqual('Testing', patch.subject)
790 self.assertEqual(1, patch.seq)
791 self.assertEqual(1, patch.count)
792 self.assertEqual('RESEND', patch.prefix)
793 self.assertEqual(None, patch.version)
794
Simon Glassd85bb8f2022-01-29 14:14:09 -0700795 def test_compare_series(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600796 """Test operation of compare_with_series()"""
797 commit1 = Commit('abcd')
798 commit1.subject = 'Subject 1'
799 commit2 = Commit('ef12')
800 commit2.subject = 'Subject 2'
801 commit3 = Commit('3456')
802 commit3.subject = 'Subject 2'
803
804 patch1 = status.Patch('1')
805 patch1.subject = 'Subject 1'
806 patch2 = status.Patch('2')
807 patch2.subject = 'Subject 2'
808 patch3 = status.Patch('3')
809 patch3.subject = 'Subject 2'
810
811 series = Series()
812 series.commits = [commit1]
813 patches = [patch1]
814 patch_for_commit, commit_for_patch, warnings = (
815 status.compare_with_series(series, patches))
816 self.assertEqual(1, len(patch_for_commit))
817 self.assertEqual(patch1, patch_for_commit[0])
818 self.assertEqual(1, len(commit_for_patch))
819 self.assertEqual(commit1, commit_for_patch[0])
820
821 series.commits = [commit1]
822 patches = [patch1, patch2]
823 patch_for_commit, commit_for_patch, warnings = (
824 status.compare_with_series(series, patches))
825 self.assertEqual(1, len(patch_for_commit))
826 self.assertEqual(patch1, patch_for_commit[0])
827 self.assertEqual(1, len(commit_for_patch))
828 self.assertEqual(commit1, commit_for_patch[0])
829 self.assertEqual(["Cannot find commit for patch 2 ('Subject 2')"],
830 warnings)
831
832 series.commits = [commit1, commit2]
833 patches = [patch1]
834 patch_for_commit, commit_for_patch, warnings = (
835 status.compare_with_series(series, patches))
836 self.assertEqual(1, len(patch_for_commit))
837 self.assertEqual(patch1, patch_for_commit[0])
838 self.assertEqual(1, len(commit_for_patch))
839 self.assertEqual(commit1, commit_for_patch[0])
840 self.assertEqual(["Cannot find patch for commit 2 ('Subject 2')"],
841 warnings)
842
843 series.commits = [commit1, commit2, commit3]
844 patches = [patch1, patch2]
845 patch_for_commit, commit_for_patch, warnings = (
846 status.compare_with_series(series, patches))
847 self.assertEqual(2, len(patch_for_commit))
848 self.assertEqual(patch1, patch_for_commit[0])
849 self.assertEqual(patch2, patch_for_commit[1])
850 self.assertEqual(1, len(commit_for_patch))
851 self.assertEqual(commit1, commit_for_patch[0])
852 self.assertEqual(["Cannot find patch for commit 3 ('Subject 2')",
853 "Multiple commits match patch 2 ('Subject 2'):\n"
854 ' Subject 2\n Subject 2'],
855 warnings)
856
857 series.commits = [commit1, commit2]
858 patches = [patch1, patch2, patch3]
859 patch_for_commit, commit_for_patch, warnings = (
860 status.compare_with_series(series, patches))
861 self.assertEqual(1, len(patch_for_commit))
862 self.assertEqual(patch1, patch_for_commit[0])
863 self.assertEqual(2, len(commit_for_patch))
864 self.assertEqual(commit1, commit_for_patch[0])
865 self.assertEqual(["Multiple patches match commit 2 ('Subject 2'):\n"
866 ' Subject 2\n Subject 2',
867 "Cannot find commit for patch 3 ('Subject 2')"],
868 warnings)
869
Simon Glassf9b03cf2020-11-03 13:54:14 -0700870 def _fake_patchwork2(self, url, subpath):
Simon Glass3db916d2020-10-29 21:46:35 -0600871 """Fake Patchwork server for the function below
872
873 This handles accessing series, patches and comments, providing the data
874 in self.patches to the caller
Simon Glassf9b03cf2020-11-03 13:54:14 -0700875
876 Args:
877 url (str): URL of patchwork server
878 subpath (str): URL subpath to use
Simon Glass3db916d2020-10-29 21:46:35 -0600879 """
880 re_series = re.match(r'series/(\d*)/$', subpath)
881 re_patch = re.match(r'patches/(\d*)/$', subpath)
882 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
883 if re_series:
884 series_num = re_series.group(1)
885 if series_num == '1234':
886 return {'patches': self.patches}
887 elif re_patch:
888 patch_num = int(re_patch.group(1))
889 patch = self.patches[patch_num - 1]
890 return patch
891 elif re_comments:
892 patch_num = int(re_comments.group(1))
893 patch = self.patches[patch_num - 1]
894 return patch.comments
895 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
896
Simon Glassd85bb8f2022-01-29 14:14:09 -0700897 def test_find_new_responses(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600898 """Test operation of find_new_responses()"""
899 commit1 = Commit('abcd')
900 commit1.subject = 'Subject 1'
901 commit2 = Commit('ef12')
902 commit2.subject = 'Subject 2'
903
904 patch1 = status.Patch('1')
905 patch1.parse_subject('[1/2] Subject 1')
906 patch1.name = patch1.raw_subject
907 patch1.content = 'This is my patch content'
908 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
909
910 patch1.comments = [comment1a]
911
912 patch2 = status.Patch('2')
913 patch2.parse_subject('[2/2] Subject 2')
914 patch2.name = patch2.raw_subject
915 patch2.content = 'Some other patch content'
916 comment2a = {
917 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
918 (self.mary, self.leb)}
919 comment2b = {'content': 'Reviewed-by: %s' % self.fred}
920 patch2.comments = [comment2a, comment2b]
921
922 # This test works by setting up commits and patch for use by the fake
923 # Rest API function _fake_patchwork2(). It calls various functions in
924 # the status module after setting up tags in the commits, checking that
925 # things behaves as expected
926 self.commits = [commit1, commit2]
927 self.patches = [patch1, patch2]
928 count = 2
929 new_rtag_list = [None] * count
Simon Glass2112d072020-10-29 21:46:38 -0600930 review_list = [None, None]
Simon Glass3db916d2020-10-29 21:46:35 -0600931
932 # Check that the tags are picked up on the first patch
Simon Glass2112d072020-10-29 21:46:38 -0600933 status.find_new_responses(new_rtag_list, review_list, 0, commit1,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700934 patch1, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -0600935 self.assertEqual(new_rtag_list[0], {'Reviewed-by': {self.joe}})
936
937 # Now the second patch
Simon Glass2112d072020-10-29 21:46:38 -0600938 status.find_new_responses(new_rtag_list, review_list, 1, commit2,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700939 patch2, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -0600940 self.assertEqual(new_rtag_list[1], {
941 'Reviewed-by': {self.mary, self.fred},
942 'Tested-by': {self.leb}})
943
944 # Now add some tags to the commit, which means they should not appear as
945 # 'new' tags when scanning comments
946 new_rtag_list = [None] * count
947 commit1.rtags = {'Reviewed-by': {self.joe}}
Simon Glass2112d072020-10-29 21:46:38 -0600948 status.find_new_responses(new_rtag_list, review_list, 0, commit1,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700949 patch1, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -0600950 self.assertEqual(new_rtag_list[0], {})
951
952 # For the second commit, add Ed and Fred, so only Mary should be left
953 commit2.rtags = {
954 'Tested-by': {self.leb},
955 'Reviewed-by': {self.fred}}
Simon Glass2112d072020-10-29 21:46:38 -0600956 status.find_new_responses(new_rtag_list, review_list, 1, commit2,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700957 patch2, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -0600958 self.assertEqual(new_rtag_list[1], {'Reviewed-by': {self.mary}})
959
960 # Check that the output patches expectations:
961 # 1 Subject 1
962 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
963 # 2 Subject 2
964 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
965 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
966 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
967 # 1 new response available in patchwork
968
969 series = Series()
970 series.commits = [commit1, commit2]
Simon Glass02811582022-01-29 14:14:18 -0700971 terminal.set_print_test_mode()
Simon Glass2112d072020-10-29 21:46:38 -0600972 status.check_patchwork_status(series, '1234', None, None, False, False,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700973 None, self._fake_patchwork2)
Simon Glass02811582022-01-29 14:14:18 -0700974 lines = iter(terminal.get_print_test_lines())
Simon Glass3db916d2020-10-29 21:46:35 -0600975 col = terminal.Color()
976 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
977 next(lines))
978 self.assertEqual(
979 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
980 bright=False),
981 next(lines))
982 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE, bright=False),
983 next(lines))
984
985 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
986 next(lines))
987 self.assertEqual(
Simon Glass2112d072020-10-29 21:46:38 -0600988 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
Simon Glass3db916d2020-10-29 21:46:35 -0600989 bright=False),
990 next(lines))
Simon Glass2112d072020-10-29 21:46:38 -0600991 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE, bright=False),
Simon Glass3db916d2020-10-29 21:46:35 -0600992 next(lines))
993 self.assertEqual(
Simon Glass2112d072020-10-29 21:46:38 -0600994 terminal.PrintLine(' Tested-by: ', col.GREEN, newline=False,
Simon Glass3db916d2020-10-29 21:46:35 -0600995 bright=False),
996 next(lines))
Simon Glass2112d072020-10-29 21:46:38 -0600997 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE, bright=False),
Simon Glass3db916d2020-10-29 21:46:35 -0600998 next(lines))
999 self.assertEqual(
1000 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1001 next(lines))
1002 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
1003 next(lines))
1004 self.assertEqual(terminal.PrintLine(
Simon Glassd0a0a582020-10-29 21:46:36 -06001005 '1 new response available in patchwork (use -d to write them to a new branch)',
1006 None), next(lines))
1007
Simon Glassf9b03cf2020-11-03 13:54:14 -07001008 def _fake_patchwork3(self, url, subpath):
Simon Glassd0a0a582020-10-29 21:46:36 -06001009 """Fake Patchwork server for the function below
1010
1011 This handles accessing series, patches and comments, providing the data
1012 in self.patches to the caller
Simon Glassf9b03cf2020-11-03 13:54:14 -07001013
1014 Args:
1015 url (str): URL of patchwork server
1016 subpath (str): URL subpath to use
Simon Glassd0a0a582020-10-29 21:46:36 -06001017 """
1018 re_series = re.match(r'series/(\d*)/$', subpath)
1019 re_patch = re.match(r'patches/(\d*)/$', subpath)
1020 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
1021 if re_series:
1022 series_num = re_series.group(1)
1023 if series_num == '1234':
1024 return {'patches': self.patches}
1025 elif re_patch:
1026 patch_num = int(re_patch.group(1))
1027 patch = self.patches[patch_num - 1]
1028 return patch
1029 elif re_comments:
1030 patch_num = int(re_comments.group(1))
1031 patch = self.patches[patch_num - 1]
1032 return patch.comments
1033 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
1034
Simon Glassd85bb8f2022-01-29 14:14:09 -07001035 def test_create_branch(self):
Simon Glassd0a0a582020-10-29 21:46:36 -06001036 """Test operation of create_branch()"""
1037 repo = self.make_git_tree()
1038 branch = 'first'
1039 dest_branch = 'first2'
1040 count = 2
1041 gitdir = os.path.join(self.gitdir, '.git')
1042
1043 # Set up the test git tree. We use branch 'first' which has two commits
1044 # in it
1045 series = patchstream.get_metadata_for_list(branch, gitdir, count)
1046 self.assertEqual(2, len(series.commits))
1047
1048 patch1 = status.Patch('1')
1049 patch1.parse_subject('[1/2] %s' % series.commits[0].subject)
1050 patch1.name = patch1.raw_subject
1051 patch1.content = 'This is my patch content'
1052 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
1053
1054 patch1.comments = [comment1a]
1055
1056 patch2 = status.Patch('2')
1057 patch2.parse_subject('[2/2] %s' % series.commits[1].subject)
1058 patch2.name = patch2.raw_subject
1059 patch2.content = 'Some other patch content'
1060 comment2a = {
1061 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1062 (self.mary, self.leb)}
1063 comment2b = {
1064 'content': 'Reviewed-by: %s' % self.fred}
1065 patch2.comments = [comment2a, comment2b]
1066
1067 # This test works by setting up patches for use by the fake Rest API
1068 # function _fake_patchwork3(). The fake patch comments above should
1069 # result in new review tags that are collected and added to the commits
1070 # created in the destination branch.
1071 self.patches = [patch1, patch2]
1072 count = 2
1073
1074 # Expected output:
1075 # 1 i2c: I2C things
1076 # + Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1077 # 2 spi: SPI fixes
1078 # + Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1079 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1080 # + Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1081 # 4 new responses available in patchwork
1082 # 4 responses added from patchwork into new branch 'first2'
1083 # <unittest.result.TestResult run=8 errors=0 failures=0>
1084
Simon Glass02811582022-01-29 14:14:18 -07001085 terminal.set_print_test_mode()
Simon Glassd0a0a582020-10-29 21:46:36 -06001086 status.check_patchwork_status(series, '1234', branch, dest_branch,
Simon Glassf9b03cf2020-11-03 13:54:14 -07001087 False, False, None, self._fake_patchwork3,
1088 repo)
Simon Glass02811582022-01-29 14:14:18 -07001089 lines = terminal.get_print_test_lines()
Simon Glassd0a0a582020-10-29 21:46:36 -06001090 self.assertEqual(12, len(lines))
1091 self.assertEqual(
1092 "4 responses added from patchwork into new branch 'first2'",
1093 lines[11].text)
1094
1095 # Check that the destination branch has the new tags
1096 new_series = patchstream.get_metadata_for_list(dest_branch, gitdir,
1097 count)
1098 self.assertEqual(
1099 {'Reviewed-by': {self.joe}},
1100 new_series.commits[0].rtags)
1101 self.assertEqual(
1102 {'Tested-by': {self.leb},
1103 'Reviewed-by': {self.fred, self.mary}},
1104 new_series.commits[1].rtags)
1105
1106 # Now check the actual test of the first commit message. We expect to
1107 # see the new tags immediately below the old ones.
1108 stdout = patchstream.get_list(dest_branch, count=count, git_dir=gitdir)
1109 lines = iter([line.strip() for line in stdout.splitlines()
1110 if '-by:' in line])
1111
1112 # First patch should have the review tag
1113 self.assertEqual('Reviewed-by: %s' % self.joe, next(lines))
1114
1115 # Second patch should have the sign-off then the tested-by and two
1116 # reviewed-by tags
1117 self.assertEqual('Signed-off-by: %s' % self.leb, next(lines))
1118 self.assertEqual('Reviewed-by: %s' % self.fred, next(lines))
1119 self.assertEqual('Reviewed-by: %s' % self.mary, next(lines))
1120 self.assertEqual('Tested-by: %s' % self.leb, next(lines))
Simon Glassda8a2922020-10-29 21:46:37 -06001121
Simon Glassd85bb8f2022-01-29 14:14:09 -07001122 def test_parse_snippets(self):
Simon Glassda8a2922020-10-29 21:46:37 -06001123 """Test parsing of review snippets"""
1124 text = '''Hi Fred,
1125
1126This is a comment from someone.
1127
1128Something else
1129
1130On some recent date, Fred wrote:
1131> This is why I wrote the patch
1132> so here it is
1133
1134Now a comment about the commit message
1135A little more to say
1136
1137Even more
1138
1139> diff --git a/file.c b/file.c
1140> Some more code
1141> Code line 2
1142> Code line 3
1143> Code line 4
1144> Code line 5
1145> Code line 6
1146> Code line 7
1147> Code line 8
1148> Code line 9
1149
1150And another comment
1151
Simon Glassd85bb8f2022-01-29 14:14:09 -07001152> @@ -153,8 +143,13 @@ def check_patch(fname, show_types=False):
Simon Glassda8a2922020-10-29 21:46:37 -06001153> further down on the file
1154> and more code
1155> +Addition here
1156> +Another addition here
1157> codey
1158> more codey
1159
1160and another thing in same file
1161
1162> @@ -253,8 +243,13 @@
1163> with no function context
1164
1165one more thing
1166
1167> diff --git a/tools/patman/main.py b/tools/patman/main.py
1168> +line of code
1169now a very long comment in a different file
1170line2
1171line3
1172line4
1173line5
1174line6
1175line7
1176line8
1177'''
1178 pstrm = PatchStream.process_text(text, True)
1179 self.assertEqual([], pstrm.commit.warn)
1180
1181 # We expect to the filename and up to 5 lines of code context before
1182 # each comment. The 'On xxx wrote:' bit should be removed.
1183 self.assertEqual(
1184 [['Hi Fred,',
1185 'This is a comment from someone.',
1186 'Something else'],
1187 ['> This is why I wrote the patch',
1188 '> so here it is',
1189 'Now a comment about the commit message',
1190 'A little more to say', 'Even more'],
1191 ['> File: file.c', '> Code line 5', '> Code line 6',
1192 '> Code line 7', '> Code line 8', '> Code line 9',
1193 'And another comment'],
1194 ['> File: file.c',
Simon Glassd85bb8f2022-01-29 14:14:09 -07001195 '> Line: 153 / 143: def check_patch(fname, show_types=False):',
Simon Glassda8a2922020-10-29 21:46:37 -06001196 '> and more code', '> +Addition here', '> +Another addition here',
1197 '> codey', '> more codey', 'and another thing in same file'],
1198 ['> File: file.c', '> Line: 253 / 243',
1199 '> with no function context', 'one more thing'],
1200 ['> File: tools/patman/main.py', '> +line of code',
1201 'now a very long comment in a different file',
1202 'line2', 'line3', 'line4', 'line5', 'line6', 'line7', 'line8']],
1203 pstrm.snippets)
Simon Glass2112d072020-10-29 21:46:38 -06001204
Simon Glassd85bb8f2022-01-29 14:14:09 -07001205 def test_review_snippets(self):
Simon Glass2112d072020-10-29 21:46:38 -06001206 """Test showing of review snippets"""
1207 def _to_submitter(who):
1208 m_who = re.match('(.*) <(.*)>', who)
1209 return {
1210 'name': m_who.group(1),
1211 'email': m_who.group(2)
1212 }
1213
1214 commit1 = Commit('abcd')
1215 commit1.subject = 'Subject 1'
1216 commit2 = Commit('ef12')
1217 commit2.subject = 'Subject 2'
1218
1219 patch1 = status.Patch('1')
1220 patch1.parse_subject('[1/2] Subject 1')
1221 patch1.name = patch1.raw_subject
1222 patch1.content = 'This is my patch content'
1223 comment1a = {'submitter': _to_submitter(self.joe),
1224 'content': '''Hi Fred,
1225
1226On some date Fred wrote:
1227
1228> diff --git a/file.c b/file.c
1229> Some code
1230> and more code
1231
1232Here is my comment above the above...
1233
1234
1235Reviewed-by: %s
1236''' % self.joe}
1237
1238 patch1.comments = [comment1a]
1239
1240 patch2 = status.Patch('2')
1241 patch2.parse_subject('[2/2] Subject 2')
1242 patch2.name = patch2.raw_subject
1243 patch2.content = 'Some other patch content'
1244 comment2a = {
1245 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1246 (self.mary, self.leb)}
1247 comment2b = {'submitter': _to_submitter(self.fred),
1248 'content': '''Hi Fred,
1249
1250On some date Fred wrote:
1251
1252> diff --git a/tools/patman/commit.py b/tools/patman/commit.py
1253> @@ -41,6 +41,9 @@ class Commit:
1254> self.rtags = collections.defaultdict(set)
1255> self.warn = []
1256>
1257> + def __str__(self):
1258> + return self.subject
1259> +
Simon Glassd85bb8f2022-01-29 14:14:09 -07001260> def add_change(self, version, info):
Simon Glass2112d072020-10-29 21:46:38 -06001261> """Add a new change line to the change list for a version.
1262>
1263A comment
1264
1265Reviewed-by: %s
1266''' % self.fred}
1267 patch2.comments = [comment2a, comment2b]
1268
1269 # This test works by setting up commits and patch for use by the fake
1270 # Rest API function _fake_patchwork2(). It calls various functions in
1271 # the status module after setting up tags in the commits, checking that
1272 # things behaves as expected
1273 self.commits = [commit1, commit2]
1274 self.patches = [patch1, patch2]
1275
1276 # Check that the output patches expectations:
1277 # 1 Subject 1
1278 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1279 # 2 Subject 2
1280 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1281 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1282 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1283 # 1 new response available in patchwork
1284
1285 series = Series()
1286 series.commits = [commit1, commit2]
Simon Glass02811582022-01-29 14:14:18 -07001287 terminal.set_print_test_mode()
Simon Glass2112d072020-10-29 21:46:38 -06001288 status.check_patchwork_status(series, '1234', None, None, False, True,
Simon Glassf9b03cf2020-11-03 13:54:14 -07001289 None, self._fake_patchwork2)
Simon Glass02811582022-01-29 14:14:18 -07001290 lines = iter(terminal.get_print_test_lines())
Simon Glass2112d072020-10-29 21:46:38 -06001291 col = terminal.Color()
1292 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
1293 next(lines))
1294 self.assertEqual(
1295 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1296 next(lines))
1297 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE), next(lines))
1298
1299 self.assertEqual(terminal.PrintLine('Review: %s' % self.joe, col.RED),
1300 next(lines))
1301 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(lines))
1302 self.assertEqual(terminal.PrintLine('', None), next(lines))
1303 self.assertEqual(terminal.PrintLine(' > File: file.c', col.MAGENTA),
1304 next(lines))
1305 self.assertEqual(terminal.PrintLine(' > Some code', col.MAGENTA),
1306 next(lines))
1307 self.assertEqual(terminal.PrintLine(' > and more code', col.MAGENTA),
1308 next(lines))
1309 self.assertEqual(terminal.PrintLine(
1310 ' Here is my comment above the above...', None), next(lines))
1311 self.assertEqual(terminal.PrintLine('', None), next(lines))
1312
1313 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
1314 next(lines))
1315 self.assertEqual(
1316 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1317 next(lines))
1318 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE),
1319 next(lines))
1320 self.assertEqual(
1321 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1322 next(lines))
1323 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
1324 next(lines))
1325 self.assertEqual(
1326 terminal.PrintLine(' + Tested-by: ', col.GREEN, newline=False),
1327 next(lines))
1328 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE),
1329 next(lines))
1330
1331 self.assertEqual(terminal.PrintLine('Review: %s' % self.fred, col.RED),
1332 next(lines))
1333 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(lines))
1334 self.assertEqual(terminal.PrintLine('', None), next(lines))
1335 self.assertEqual(terminal.PrintLine(
1336 ' > File: tools/patman/commit.py', col.MAGENTA), next(lines))
1337 self.assertEqual(terminal.PrintLine(
1338 ' > Line: 41 / 41: class Commit:', col.MAGENTA), next(lines))
1339 self.assertEqual(terminal.PrintLine(
1340 ' > + return self.subject', col.MAGENTA), next(lines))
1341 self.assertEqual(terminal.PrintLine(
1342 ' > +', col.MAGENTA), next(lines))
1343 self.assertEqual(
Simon Glassd85bb8f2022-01-29 14:14:09 -07001344 terminal.PrintLine(' > def add_change(self, version, info):',
Simon Glass2112d072020-10-29 21:46:38 -06001345 col.MAGENTA),
1346 next(lines))
1347 self.assertEqual(terminal.PrintLine(
1348 ' > """Add a new change line to the change list for a version.',
1349 col.MAGENTA), next(lines))
1350 self.assertEqual(terminal.PrintLine(
1351 ' >', col.MAGENTA), next(lines))
1352 self.assertEqual(terminal.PrintLine(
1353 ' A comment', None), next(lines))
1354 self.assertEqual(terminal.PrintLine('', None), next(lines))
1355
1356 self.assertEqual(terminal.PrintLine(
1357 '4 new responses available in patchwork (use -d to write them to a new branch)',
1358 None), next(lines))
Simon Glass6a222e62021-08-01 16:02:39 -06001359
Simon Glassd85bb8f2022-01-29 14:14:09 -07001360 def test_insert_tags(self):
Simon Glass6a222e62021-08-01 16:02:39 -06001361 """Test inserting of review tags"""
1362 msg = '''first line
1363second line.'''
1364 tags = [
1365 'Reviewed-by: Bin Meng <bmeng.cn@gmail.com>',
1366 'Tested-by: Bin Meng <bmeng.cn@gmail.com>'
1367 ]
1368 signoff = 'Signed-off-by: Simon Glass <sjg@chromium.com>'
1369 tag_str = '\n'.join(tags)
1370
1371 new_msg = patchstream.insert_tags(msg, tags)
1372 self.assertEqual(msg + '\n\n' + tag_str, new_msg)
1373
1374 new_msg = patchstream.insert_tags(msg + '\n', tags)
1375 self.assertEqual(msg + '\n\n' + tag_str, new_msg)
1376
1377 msg += '\n\n' + signoff
1378 new_msg = patchstream.insert_tags(msg, tags)
1379 self.assertEqual(msg + '\n' + tag_str, new_msg)