blob: 161b5ad428878f2f0eaee4f1a854e24a410c967f [file] [log] [blame]
Simon Glassdf1bc5c2017-05-29 15:31:31 -06001# -*- coding: utf-8 -*-
Tom Rini10e47792018-05-06 17:58:06 -04002# SPDX-License-Identifier: GPL-2.0+
Simon Glassdf1bc5c2017-05-29 15:31:31 -06003#
4# Copyright 2017 Google, Inc
5#
Simon Glassdf1bc5c2017-05-29 15:31:31 -06006
Simon Glasseb209e52020-10-29 21:46:15 -06007"""Functional tests for checking that patman behaves correctly"""
8
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -05009import contextlib
Simon Glassdf1bc5c2017-05-29 15:31:31 -060010import os
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050011import pathlib
Simon Glassdf1bc5c2017-05-29 15:31:31 -060012import re
13import shutil
14import sys
15import tempfile
16import unittest
17
Simon Glass3db916d2020-10-29 21:46:35 -060018
19from patman.commit import Commit
Simon Glass54f1c5b2020-07-05 21:41:50 -060020from patman import control
Simon Glassa997ea52020-04-17 18:09:04 -060021from patman import patchstream
Simon Glassa7fadab2020-10-29 21:46:26 -060022from patman.patchstream import PatchStream
Simon Glass3db916d2020-10-29 21:46:35 -060023from patman.series import Series
Simon Glassa997ea52020-04-17 18:09:04 -060024from patman import settings
Simon Glassba1b3b92025-02-09 14:26:00 -070025from u_boot_pylib import gitutil
Simon Glass131444f2023-02-23 18:18:04 -070026from u_boot_pylib import terminal
27from u_boot_pylib import tools
Simon Glassdf1bc5c2017-05-29 15:31:31 -060028
Tom Rini488ea972021-02-26 07:52:31 -050029import pygit2
30from patman import status
Simon Glassdf1bc5c2017-05-29 15:31:31 -060031
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -050032PATMAN_DIR = pathlib.Path(__file__).parent
33TEST_DATA_DIR = PATMAN_DIR / 'test/'
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050034
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050035
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -050036@contextlib.contextmanager
37def directory_excursion(directory):
38 """Change directory to `directory` for a limited to the context block."""
39 current = os.getcwd()
40 try:
41 os.chdir(directory)
42 yield
43 finally:
44 os.chdir(current)
45
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050046
Simon Glassdf1bc5c2017-05-29 15:31:31 -060047class TestFunctional(unittest.TestCase):
Simon Glasseb209e52020-10-29 21:46:15 -060048 """Functional tests for checking that patman behaves correctly"""
Simon Glass06202d62020-10-29 21:46:27 -060049 leb = (b'Lord Edmund Blackadd\xc3\xabr <weasel@blackadder.org>'.
50 decode('utf-8'))
Simon Glass3b762cc2020-10-29 21:46:28 -060051 fred = 'Fred Bloggs <f.bloggs@napier.net>'
52 joe = 'Joe Bloggs <joe@napierwallies.co.nz>'
53 mary = 'Mary Bloggs <mary@napierwallies.co.nz>'
Simon Glass3db916d2020-10-29 21:46:35 -060054 commits = None
55 patches = None
Simon Glass06202d62020-10-29 21:46:27 -060056
Simon Glassdf1bc5c2017-05-29 15:31:31 -060057 def setUp(self):
58 self.tmpdir = tempfile.mkdtemp(prefix='patman.')
Simon Glass54f1c5b2020-07-05 21:41:50 -060059 self.gitdir = os.path.join(self.tmpdir, 'git')
60 self.repo = None
Simon Glassdf1bc5c2017-05-29 15:31:31 -060061
62 def tearDown(self):
63 shutil.rmtree(self.tmpdir)
Simon Glass02811582022-01-29 14:14:18 -070064 terminal.set_print_test_mode(False)
Simon Glassdf1bc5c2017-05-29 15:31:31 -060065
66 @staticmethod
Simon Glasseb209e52020-10-29 21:46:15 -060067 def _get_path(fname):
68 """Get the path to a test file
69
70 Args:
71 fname (str): Filename to obtain
72
73 Returns:
74 str: Full path to file in the test directory
75 """
Maxim Cournoyer0331edb2022-12-19 17:32:39 -050076 return TEST_DATA_DIR / fname
Simon Glassdf1bc5c2017-05-29 15:31:31 -060077
78 @classmethod
Simon Glasseb209e52020-10-29 21:46:15 -060079 def _get_text(cls, fname):
80 """Read a file as text
81
82 Args:
83 fname (str): Filename to read
84
85 Returns:
86 str: Contents of file
87 """
88 return open(cls._get_path(fname), encoding='utf-8').read()
Simon Glassdf1bc5c2017-05-29 15:31:31 -060089
90 @classmethod
Simon Glasseb209e52020-10-29 21:46:15 -060091 def _get_patch_name(cls, subject):
92 """Get the filename of a patch given its subject
93
94 Args:
95 subject (str): Patch subject
96
97 Returns:
98 str: Filename for that patch
99 """
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600100 fname = re.sub('[ :]', '-', subject)
101 return fname.replace('--', '-')
102
Simon Glasseb209e52020-10-29 21:46:15 -0600103 def _create_patches_for_test(self, series):
104 """Create patch files for use by tests
105
106 This copies patch files from the test directory as needed by the series
107
108 Args:
109 series (Series): Series containing commits to convert
110
111 Returns:
112 tuple:
113 str: Cover-letter filename, or None if none
114 fname_list: list of str, each a patch filename
115 """
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600116 cover_fname = None
117 fname_list = []
118 for i, commit in enumerate(series.commits):
Simon Glasseb209e52020-10-29 21:46:15 -0600119 clean_subject = self._get_patch_name(commit.subject)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600120 src_fname = '%04d-%s.patch' % (i + 1, clean_subject[:52])
121 fname = os.path.join(self.tmpdir, src_fname)
Simon Glasseb209e52020-10-29 21:46:15 -0600122 shutil.copy(self._get_path(src_fname), fname)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600123 fname_list.append(fname)
124 if series.get('cover'):
125 src_fname = '0000-cover-letter.patch'
126 cover_fname = os.path.join(self.tmpdir, src_fname)
127 fname = os.path.join(self.tmpdir, src_fname)
Simon Glasseb209e52020-10-29 21:46:15 -0600128 shutil.copy(self._get_path(src_fname), fname)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600129
130 return cover_fname, fname_list
131
Simon Glassd85bb8f2022-01-29 14:14:09 -0700132 def test_basic(self):
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600133 """Tests the basic flow of patman
134
135 This creates a series from some hard-coded patches build from a simple
136 tree with the following metadata in the top commit:
137
138 Series-to: u-boot
139 Series-prefix: RFC
Sean Andersondc1cd132021-10-22 19:07:04 -0400140 Series-postfix: some-branch
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600141 Series-cc: Stefan Brüns <stefan.bruens@rwth-aachen.de>
142 Cover-letter-cc: Lord Mëlchett <clergy@palace.gov>
Sean Andersoncf13b862020-05-04 16:28:36 -0400143 Series-version: 3
144 Patch-cc: fred
145 Series-process-log: sort, uniq
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600146 Series-changes: 4
147 - Some changes
Sean Andersoncf13b862020-05-04 16:28:36 -0400148 - Multi
149 line
150 change
151
152 Commit-changes: 2
153 - Changes only for this commit
154
Simon Glassf1aab6f2025-04-29 07:22:07 -0600155 Cover-changes: 4
Sean Andersoncf13b862020-05-04 16:28:36 -0400156 - Some notes for the cover letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600157
158 Cover-letter:
159 test: A test patch series
160 This is a test of how the cover
Sean Andersoncf13b862020-05-04 16:28:36 -0400161 letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600162 works
163 END
164
165 and this in the first commit:
166
Sean Andersoncf13b862020-05-04 16:28:36 -0400167 Commit-changes: 2
168 - second revision change
169
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600170 Series-notes:
171 some notes
172 about some things
173 from the first commit
174 END
175
176 Commit-notes:
177 Some notes about
178 the first commit
179 END
180
181 with the following commands:
182
183 git log -n2 --reverse >/path/to/tools/patman/test/test01.txt
184 git format-patch --subject-prefix RFC --cover-letter HEAD~2
185 mv 00* /path/to/tools/patman/test
186
187 It checks these aspects:
188 - git log can be processed by patchstream
189 - emailing patches uses the correct command
190 - CC file has information on each commit
191 - cover letter has the expected text and subject
192 - each patch has the correct subject
193 - dry-run information prints out correctly
194 - unicode is handled correctly
Sean Andersondc1cd132021-10-22 19:07:04 -0400195 - Series-to, Series-cc, Series-prefix, Series-postfix, Cover-letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600196 - Cover-letter-cc, Series-version, Series-changes, Series-notes
197 - Commit-notes
198 """
199 process_tags = True
Simon Glass1f975b92021-01-23 08:56:15 -0700200 ignore_bad_tags = False
Simon Glass4f817892019-05-14 15:53:53 -0600201 stefan = b'Stefan Br\xc3\xbcns <stefan.bruens@rwth-aachen.de>'.decode('utf-8')
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600202 rick = 'Richard III <richard@palace.gov>'
Simon Glass4f817892019-05-14 15:53:53 -0600203 mel = b'Lord M\xc3\xablchett <clergy@palace.gov>'.decode('utf-8')
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600204 add_maintainers = [stefan, rick]
205 dry_run = True
206 in_reply_to = mel
207 count = 2
208 settings.alias = {
Simon Glass95745aa2020-10-29 21:46:13 -0600209 'fdt': ['simon'],
210 'u-boot': ['u-boot@lists.denx.de'],
Simon Glass06202d62020-10-29 21:46:27 -0600211 'simon': [self.leb],
Simon Glass3b762cc2020-10-29 21:46:28 -0600212 'fred': [self.fred],
Sean Anderson25978092024-04-18 22:36:31 -0400213 'joe': [self.joe],
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 Glass414f1e02025-02-27 12:27:30 -0700218 series.base_commit = Commit('1a44532')
219 series.branch = 'mybranch'
Simon Glasseb209e52020-10-29 21:46:15 -0600220 cover_fname, args = self._create_patches_for_test(series)
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500221 get_maintainer_script = str(pathlib.Path(__file__).parent.parent.parent
222 / 'get_maintainer.pl') + ' --norolestats'
Simon Glass14d64e32025-04-29 07:21:59 -0600223 with terminal.capture() as out:
Simon Glass93f61c02020-10-29 21:46:19 -0600224 patchstream.fix_patches(series, args)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600225 if cover_fname and series.get('cover'):
Simon Glass93f61c02020-10-29 21:46:19 -0600226 patchstream.insert_cover_letter(cover_fname, series, count)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600227 series.DoChecks()
228 cc_file = series.MakeCcFile(process_tags, cover_fname,
Chris Packhamb84fb482018-06-07 20:45:06 +1200229 not ignore_bad_tags, add_maintainers,
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500230 None, get_maintainer_script)
Simon Glass761648b2022-01-29 14:14:11 -0700231 cmd = gitutil.email_patches(
Simon Glass95745aa2020-10-29 21:46:13 -0600232 series, cover_fname, args, dry_run, not ignore_bad_tags,
233 cc_file, in_reply_to=in_reply_to, thread=None)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600234 series.ShowActions(args, cmd, process_tags)
Simon Glassf544a2d2019-10-31 07:42:51 -0600235 cc_lines = open(cc_file, encoding='utf-8').read().splitlines()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600236 os.remove(cc_file)
237
Simon Glass42e3d392020-10-29 21:46:29 -0600238 lines = iter(out[0].getvalue().splitlines())
239 self.assertEqual('Cleaned %s patches' % len(series.commits),
240 next(lines))
241 self.assertEqual('Change log missing for v2', next(lines))
242 self.assertEqual('Change log missing for v3', next(lines))
243 self.assertEqual('Change log for unknown version v4', next(lines))
244 self.assertEqual("Alias 'pci' not found", next(lines))
Simon Glass620639c2023-03-08 10:52:54 -0800245 while next(lines) != 'Cc processing complete':
246 pass
Simon Glass42e3d392020-10-29 21:46:29 -0600247 self.assertIn('Dry run', next(lines))
248 self.assertEqual('', next(lines))
249 self.assertIn('Send a total of %d patches' % count, next(lines))
250 prev = next(lines)
251 for i, commit in enumerate(series.commits):
252 self.assertEqual(' %s' % args[i], prev)
253 while True:
254 prev = next(lines)
255 if 'Cc:' not in prev:
256 break
257 self.assertEqual('To: u-boot@lists.denx.de', prev)
Simon Glass9dfb3112020-11-08 20:36:18 -0700258 self.assertEqual('Cc: %s' % stefan, next(lines))
Simon Glass42e3d392020-10-29 21:46:29 -0600259 self.assertEqual('Version: 3', next(lines))
260 self.assertEqual('Prefix:\t RFC', next(lines))
Sean Andersondc1cd132021-10-22 19:07:04 -0400261 self.assertEqual('Postfix:\t some-branch', next(lines))
Simon Glass42e3d392020-10-29 21:46:29 -0600262 self.assertEqual('Cover: 4 lines', next(lines))
263 self.assertEqual(' Cc: %s' % self.fred, next(lines))
Sean Anderson25978092024-04-18 22:36:31 -0400264 self.assertEqual(' Cc: %s' % self.joe, next(lines))
Simon Glass9dfb3112020-11-08 20:36:18 -0700265 self.assertEqual(' Cc: %s' % self.leb,
Simon Glass42e3d392020-10-29 21:46:29 -0600266 next(lines))
Simon Glass9dfb3112020-11-08 20:36:18 -0700267 self.assertEqual(' Cc: %s' % mel, next(lines))
Simon Glass42e3d392020-10-29 21:46:29 -0600268 self.assertEqual(' Cc: %s' % rick, next(lines))
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600269 expected = ('Git command: git send-email --annotate '
270 '--in-reply-to="%s" --to "u-boot@lists.denx.de" '
Simon Glass1ee91c12020-11-03 13:54:10 -0700271 '--cc "%s" --cc-cmd "%s send --cc-cmd %s" %s %s'
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600272 % (in_reply_to, stefan, sys.argv[0], cc_file, cover_fname,
Simon Glass4f817892019-05-14 15:53:53 -0600273 ' '.join(args)))
Simon Glass9dfb3112020-11-08 20:36:18 -0700274 self.assertEqual(expected, next(lines))
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600275
Simon Glass9dfb3112020-11-08 20:36:18 -0700276 self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)), cc_lines[0])
Simon Glass95745aa2020-10-29 21:46:13 -0600277 self.assertEqual(
Sean Anderson25978092024-04-18 22:36:31 -0400278 '%s %s\0%s\0%s\0%s\0%s' % (args[1], self.fred, self.joe, self.leb,
279 rick, stefan),
Simon Glass9dfb3112020-11-08 20:36:18 -0700280 cc_lines[1])
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600281
282 expected = '''
283This is a test of how the cover
Sean Andersoncf13b862020-05-04 16:28:36 -0400284letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600285works
286
287some notes
288about some things
289from the first commit
290
291Changes in v4:
Sean Andersoncf13b862020-05-04 16:28:36 -0400292- Multi
293 line
294 change
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600295- Some changes
Sean Andersoncf13b862020-05-04 16:28:36 -0400296- Some notes for the cover letter
Sean Andersone45678c2024-04-18 22:36:32 -0400297- fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600298
299Simon Glass (2):
300 pci: Correct cast for sandbox
Siva Durga Prasad Paladugub3d55ea2018-07-16 15:56:11 +0530301 fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600302
303 cmd/pci.c | 3 ++-
304 fs/fat/fat.c | 1 +
305 lib/efi_loader/efi_memory.c | 1 +
306 lib/fdtdec.c | 3 ++-
307 4 files changed, 6 insertions(+), 2 deletions(-)
308
309--\x20
3102.7.4
311
Simon Glass414f1e02025-02-27 12:27:30 -0700312base-commit: 1a44532
313branch: mybranch
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600314'''
Simon Glassf544a2d2019-10-31 07:42:51 -0600315 lines = open(cover_fname, encoding='utf-8').read().splitlines()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600316 self.assertEqual(
Sean Andersondc1cd132021-10-22 19:07:04 -0400317 'Subject: [RFC PATCH some-branch v3 0/2] test: A test patch series',
Simon Glass95745aa2020-10-29 21:46:13 -0600318 lines[3])
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600319 self.assertEqual(expected.splitlines(), lines[7:])
320
321 for i, fname in enumerate(args):
Simon Glassf544a2d2019-10-31 07:42:51 -0600322 lines = open(fname, encoding='utf-8').read().splitlines()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600323 subject = [line for line in lines if line.startswith('Subject')]
324 self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count),
325 subject[0][:18])
Sean Andersoncf13b862020-05-04 16:28:36 -0400326
327 # Check that we got our commit notes
328 start = 0
329 expected = ''
330
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600331 if i == 0:
Sean Andersoncf13b862020-05-04 16:28:36 -0400332 start = 17
333 expected = '''---
334Some notes about
335the first commit
336
337(no changes since v2)
338
339Changes in v2:
340- second revision change'''
341 elif i == 1:
342 start = 17
343 expected = '''---
344
345Changes in v4:
346- Multi
347 line
348 change
Sean Andersone45678c2024-04-18 22:36:32 -0400349- New
Sean Andersoncf13b862020-05-04 16:28:36 -0400350- Some changes
351
352Changes in v2:
353- Changes only for this commit'''
354
355 if expected:
356 expected = expected.splitlines()
357 self.assertEqual(expected, lines[start:(start+len(expected))])
Simon Glass54f1c5b2020-07-05 21:41:50 -0600358
Simon Glassda1a6ec2025-03-28 07:02:20 -0600359 def test_base_commit(self):
360 """Test adding a base commit with no cover letter"""
361 orig_text = self._get_text('test01.txt')
362 pos = orig_text.index('commit 5ab48490f03051875ab13d288a4bf32b507d76fd')
363 text = orig_text[:pos]
364 series = patchstream.get_metadata_for_test(text)
365 series.base_commit = Commit('1a44532')
366 series.branch = 'mybranch'
367 cover_fname, args = self._create_patches_for_test(series)
368 self.assertFalse(cover_fname)
Simon Glass14d64e32025-04-29 07:21:59 -0600369 with terminal.capture() as out:
Simon Glassda1a6ec2025-03-28 07:02:20 -0600370 patchstream.fix_patches(series, args, insert_base_commit=True)
371 self.assertEqual('Cleaned 1 patch\n', out[0].getvalue())
372 lines = tools.read_file(args[0], binary=False).splitlines()
373 pos = lines.index('-- ')
374
375 # We expect these lines at the end:
376 # -- (with trailing space)
377 # 2.7.4
378 # (empty)
379 # base-commit: xxx
380 # branch: xxx
381 self.assertEqual('base-commit: 1a44532', lines[pos + 3])
382 self.assertEqual('branch: mybranch', lines[pos + 4])
383
Simon Glass54f1c5b2020-07-05 21:41:50 -0600384 def make_commit_with_file(self, subject, body, fname, text):
385 """Create a file and add it to the git repo with a new commit
386
387 Args:
388 subject (str): Subject for the commit
389 body (str): Body text of the commit
390 fname (str): Filename of file to create
391 text (str): Text to put into the file
392 """
393 path = os.path.join(self.gitdir, fname)
Simon Glass80025522022-01-29 14:14:04 -0700394 tools.write_file(path, text, binary=False)
Simon Glass54f1c5b2020-07-05 21:41:50 -0600395 index = self.repo.index
396 index.add(fname)
Simon Glass547cba62022-02-11 13:23:18 -0700397 # pylint doesn't seem to find this
398 # pylint: disable=E1101
Simon Glass95745aa2020-10-29 21:46:13 -0600399 author = pygit2.Signature('Test user', 'test@email.com')
Simon Glass54f1c5b2020-07-05 21:41:50 -0600400 committer = author
401 tree = index.write_tree()
402 message = subject + '\n' + body
403 self.repo.create_commit('HEAD', author, committer, message, tree,
404 [self.repo.head.target])
405
406 def make_git_tree(self):
407 """Make a simple git tree suitable for testing
408
409 It has three branches:
410 'base' has two commits: PCI, main
411 'first' has base as upstream and two more commits: I2C, SPI
412 'second' has base as upstream and three more: video, serial, bootm
413
414 Returns:
Simon Glasseb209e52020-10-29 21:46:15 -0600415 pygit2.Repository: repository
Simon Glass54f1c5b2020-07-05 21:41:50 -0600416 """
417 repo = pygit2.init_repository(self.gitdir)
418 self.repo = repo
419 new_tree = repo.TreeBuilder().write()
420
Simon Glass547cba62022-02-11 13:23:18 -0700421 # pylint doesn't seem to find this
422 # pylint: disable=E1101
Simon Glass54f1c5b2020-07-05 21:41:50 -0600423 author = pygit2.Signature('Test user', 'test@email.com')
424 committer = author
Simon Glasseb209e52020-10-29 21:46:15 -0600425 _ = repo.create_commit('HEAD', author, committer, 'Created master',
426 new_tree, [])
Simon Glass54f1c5b2020-07-05 21:41:50 -0600427
428 self.make_commit_with_file('Initial commit', '''
429Add a README
430
431''', 'README', '''This is the README file
432describing this project
433in very little detail''')
434
435 self.make_commit_with_file('pci: PCI implementation', '''
436Here is a basic PCI implementation
437
438''', 'pci.c', '''This is a file
439it has some contents
440and some more things''')
441 self.make_commit_with_file('main: Main program', '''
442Hello here is the second commit.
443''', 'main.c', '''This is the main file
444there is very little here
445but we can always add more later
446if we want to
447
448Series-to: u-boot
449Series-cc: Barry Crump <bcrump@whataroa.nz>
450''')
451 base_target = repo.revparse_single('HEAD')
452 self.make_commit_with_file('i2c: I2C things', '''
453This has some stuff to do with I2C
454''', 'i2c.c', '''And this is the file contents
455with some I2C-related things in it''')
456 self.make_commit_with_file('spi: SPI fixes', '''
457SPI needs some fixes
458and here they are
Simon Glassd0a0a582020-10-29 21:46:36 -0600459
460Signed-off-by: %s
461
462Series-to: u-boot
463Commit-notes:
464title of the series
465This is the cover letter for the series
466with various details
467END
468''' % self.leb, 'spi.c', '''Some fixes for SPI in this
Simon Glass54f1c5b2020-07-05 21:41:50 -0600469file to make SPI work
470better than before''')
471 first_target = repo.revparse_single('HEAD')
472
473 target = repo.revparse_single('HEAD~2')
Simon Glass547cba62022-02-11 13:23:18 -0700474 # pylint doesn't seem to find this
475 # pylint: disable=E1101
Simon Glass54f1c5b2020-07-05 21:41:50 -0600476 repo.reset(target.oid, pygit2.GIT_CHECKOUT_FORCE)
477 self.make_commit_with_file('video: Some video improvements', '''
478Fix up the video so that
479it looks more purple. Purple is
480a very nice colour.
481''', 'video.c', '''More purple here
482Purple and purple
483Even more purple
484Could not be any more purple''')
485 self.make_commit_with_file('serial: Add a serial driver', '''
486Here is the serial driver
487for my chip.
488
489Cover-letter:
490Series for my board
491This series implements support
492for my glorious board.
493END
Simon Glassa80986c2020-10-29 21:46:16 -0600494Series-links: 183237
Simon Glass54f1c5b2020-07-05 21:41:50 -0600495''', 'serial.c', '''The code for the
496serial driver is here''')
497 self.make_commit_with_file('bootm: Make it boot', '''
498This makes my board boot
499with a fix to the bootm
500command
501''', 'bootm.c', '''Fix up the bootm
502command to make the code as
503complicated as possible''')
504 second_target = repo.revparse_single('HEAD')
505
506 repo.branches.local.create('first', first_target)
507 repo.config.set_multivar('branch.first.remote', '', '.')
508 repo.config.set_multivar('branch.first.merge', '', 'refs/heads/base')
509
510 repo.branches.local.create('second', second_target)
511 repo.config.set_multivar('branch.second.remote', '', '.')
512 repo.config.set_multivar('branch.second.merge', '', 'refs/heads/base')
513
514 repo.branches.local.create('base', base_target)
515 return repo
516
Simon Glassd85bb8f2022-01-29 14:14:09 -0700517 def test_branch(self):
Simon Glass54f1c5b2020-07-05 21:41:50 -0600518 """Test creating patches from a branch"""
519 repo = self.make_git_tree()
520 target = repo.lookup_reference('refs/heads/first')
Simon Glass547cba62022-02-11 13:23:18 -0700521 # pylint doesn't seem to find this
522 # pylint: disable=E1101
Simon Glass54f1c5b2020-07-05 21:41:50 -0600523 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
524 control.setup()
Heinrich Schuchardtd01d6672023-04-20 20:07:29 +0200525 orig_dir = os.getcwd()
Simon Glass54f1c5b2020-07-05 21:41:50 -0600526 try:
Simon Glass54f1c5b2020-07-05 21:41:50 -0600527 os.chdir(self.gitdir)
528
529 # Check that it can detect the current branch
Simon Glass761648b2022-01-29 14:14:11 -0700530 self.assertEqual(2, gitutil.count_commits_to_branch(None))
Simon Glass54f1c5b2020-07-05 21:41:50 -0600531 col = terminal.Color()
Simon Glass14d64e32025-04-29 07:21:59 -0600532 with terminal.capture() as _:
Simon Glass54f1c5b2020-07-05 21:41:50 -0600533 _, cover_fname, patch_files = control.prepare_patches(
Simon Glassb3bf4e12020-07-05 21:41:52 -0600534 col, branch=None, count=-1, start=0, end=0,
Philipp Tomsich858531a2020-11-24 18:14:52 +0100535 ignore_binary=False, signoff=True)
Simon Glass54f1c5b2020-07-05 21:41:50 -0600536 self.assertIsNone(cover_fname)
537 self.assertEqual(2, len(patch_files))
Simon Glass2eb4da72020-07-05 21:41:51 -0600538
539 # Check that it can detect a different branch
Simon Glass761648b2022-01-29 14:14:11 -0700540 self.assertEqual(3, gitutil.count_commits_to_branch('second'))
Simon Glass14d64e32025-04-29 07:21:59 -0600541 with terminal.capture() as _:
Simon Glass414f1e02025-02-27 12:27:30 -0700542 series, cover_fname, patch_files = control.prepare_patches(
Simon Glassb3bf4e12020-07-05 21:41:52 -0600543 col, branch='second', count=-1, start=0, end=0,
Philipp Tomsich858531a2020-11-24 18:14:52 +0100544 ignore_binary=False, signoff=True)
Simon Glass2eb4da72020-07-05 21:41:51 -0600545 self.assertIsNotNone(cover_fname)
546 self.assertEqual(3, len(patch_files))
Simon Glassb3bf4e12020-07-05 21:41:52 -0600547
Simon Glass414f1e02025-02-27 12:27:30 -0700548 cover = tools.read_file(cover_fname, binary=False)
549 lines = cover.splitlines()[-2:]
550 base = repo.lookup_reference('refs/heads/base').target
551 self.assertEqual(f'base-commit: {base}', lines[0])
552 self.assertEqual('branch: second', lines[1])
553
Simon Glassda1a6ec2025-03-28 07:02:20 -0600554 # Make sure that the base-commit is not present when it is in the
555 # cover letter
556 for fname in patch_files:
557 self.assertNotIn(b'base-commit:', tools.read_file(fname))
558
Simon Glassb3bf4e12020-07-05 21:41:52 -0600559 # Check that it can skip patches at the end
Simon Glass14d64e32025-04-29 07:21:59 -0600560 with terminal.capture() as _:
Simon Glassb3bf4e12020-07-05 21:41:52 -0600561 _, cover_fname, patch_files = control.prepare_patches(
562 col, branch='second', count=-1, start=0, end=1,
Philipp Tomsich858531a2020-11-24 18:14:52 +0100563 ignore_binary=False, signoff=True)
Simon Glassb3bf4e12020-07-05 21:41:52 -0600564 self.assertIsNotNone(cover_fname)
565 self.assertEqual(2, len(patch_files))
Simon Glass414f1e02025-02-27 12:27:30 -0700566
567 cover = tools.read_file(cover_fname, binary=False)
568 lines = cover.splitlines()[-2:]
569 base2 = repo.lookup_reference('refs/heads/second')
570 ref = base2.peel(pygit2.GIT_OBJ_COMMIT).parents[0].parents[0].id
571 self.assertEqual(f'base-commit: {ref}', lines[0])
572 self.assertEqual('branch: second', lines[1])
Simon Glass54f1c5b2020-07-05 21:41:50 -0600573 finally:
574 os.chdir(orig_dir)
Simon Glass06202d62020-10-29 21:46:27 -0600575
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500576 def test_custom_get_maintainer_script(self):
577 """Validate that a custom get_maintainer script gets used."""
578 self.make_git_tree()
579 with directory_excursion(self.gitdir):
580 # Setup git.
581 os.environ['GIT_CONFIG_GLOBAL'] = '/dev/null'
582 os.environ['GIT_CONFIG_SYSTEM'] = '/dev/null'
583 tools.run('git', 'config', 'user.name', 'Dummy')
584 tools.run('git', 'config', 'user.email', 'dumdum@dummy.com')
585 tools.run('git', 'branch', 'upstream')
586 tools.run('git', 'branch', '--set-upstream-to=upstream')
587 tools.run('git', 'add', '.')
588 tools.run('git', 'commit', '-m', 'new commit')
589
590 # Setup patman configuration.
591 with open('.patman', 'w', buffering=1) as f:
592 f.write('[settings]\n'
593 'get_maintainer_script: dummy-script.sh\n'
Sean Andersona06df742024-04-18 22:36:30 -0400594 'check_patch: False\n'
595 'add_maintainers: True\n')
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500596 with open('dummy-script.sh', 'w', buffering=1) as f:
597 f.write('#!/usr/bin/env python\n'
598 'print("hello@there.com")\n')
599 os.chmod('dummy-script.sh', 0x555)
600
601 # Finally, do the test
Simon Glass14d64e32025-04-29 07:21:59 -0600602 with terminal.capture():
Maxim Cournoyer3ef23e92022-12-20 00:28:46 -0500603 output = tools.run(PATMAN_DIR / 'patman', '--dry-run')
604 # Assert the email address is part of the dry-run
605 # output.
606 self.assertIn('hello@there.com', output)
607
Simon Glassd85bb8f2022-01-29 14:14:09 -0700608 def test_tags(self):
Simon Glass06202d62020-10-29 21:46:27 -0600609 """Test collection of tags in a patchstream"""
610 text = '''This is a patch
611
612Signed-off-by: Terminator
Simon Glass3b762cc2020-10-29 21:46:28 -0600613Reviewed-by: %s
614Reviewed-by: %s
Simon Glass06202d62020-10-29 21:46:27 -0600615Tested-by: %s
Simon Glass3b762cc2020-10-29 21:46:28 -0600616''' % (self.joe, self.mary, self.leb)
Simon Glass06202d62020-10-29 21:46:27 -0600617 pstrm = PatchStream.process_text(text)
618 self.assertEqual(pstrm.commit.rtags, {
Simon Glass3b762cc2020-10-29 21:46:28 -0600619 'Reviewed-by': {self.joe, self.mary},
Simon Glass06202d62020-10-29 21:46:27 -0600620 'Tested-by': {self.leb}})
Simon Glass3b762cc2020-10-29 21:46:28 -0600621
Simon Glassd85bb8f2022-01-29 14:14:09 -0700622 def test_invalid_tag(self):
Patrick Delaunay6bbdd0c2021-07-22 16:51:42 +0200623 """Test invalid tag in a patchstream"""
624 text = '''This is a patch
625
626Serie-version: 2
627'''
628 with self.assertRaises(ValueError) as exc:
629 pstrm = PatchStream.process_text(text)
630 self.assertEqual("Line 3: Invalid tag = 'Serie-version: 2'",
631 str(exc.exception))
632
Simon Glassd85bb8f2022-01-29 14:14:09 -0700633 def test_missing_end(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600634 """Test a missing END tag"""
635 text = '''This is a patch
636
637Cover-letter:
638This is the title
639missing END after this line
640Signed-off-by: Fred
641'''
642 pstrm = PatchStream.process_text(text)
643 self.assertEqual(["Missing 'END' in section 'cover'"],
644 pstrm.commit.warn)
645
Simon Glassd85bb8f2022-01-29 14:14:09 -0700646 def test_missing_blank_line(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600647 """Test a missing blank line after a tag"""
648 text = '''This is a patch
649
650Series-changes: 2
651- First line of changes
652- Missing blank line after this line
653Signed-off-by: Fred
654'''
655 pstrm = PatchStream.process_text(text)
656 self.assertEqual(["Missing 'blank line' in section 'Series-changes'"],
657 pstrm.commit.warn)
658
Simon Glassd85bb8f2022-01-29 14:14:09 -0700659 def test_invalid_commit_tag(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600660 """Test an invalid Commit-xxx tag"""
661 text = '''This is a patch
662
663Commit-fred: testing
664'''
665 pstrm = PatchStream.process_text(text)
666 self.assertEqual(["Line 3: Ignoring Commit-fred"], pstrm.commit.warn)
667
Simon Glassd85bb8f2022-01-29 14:14:09 -0700668 def test_self_test(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600669 """Test a tested by tag by this user"""
670 test_line = 'Tested-by: %s@napier.com' % os.getenv('USER')
671 text = '''This is a patch
672
673%s
674''' % test_line
675 pstrm = PatchStream.process_text(text)
676 self.assertEqual(["Ignoring '%s'" % test_line], pstrm.commit.warn)
677
Simon Glassd85bb8f2022-01-29 14:14:09 -0700678 def test_space_before_tab(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600679 """Test a space before a tab"""
680 text = '''This is a patch
681
682+ \tSomething
683'''
684 pstrm = PatchStream.process_text(text)
685 self.assertEqual(["Line 3/0 has space before tab"], pstrm.commit.warn)
686
Simon Glassd85bb8f2022-01-29 14:14:09 -0700687 def test_lines_after_test(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600688 """Test detecting lines after TEST= line"""
689 text = '''This is a patch
690
691TEST=sometest
692more lines
693here
694'''
695 pstrm = PatchStream.process_text(text)
696 self.assertEqual(["Found 2 lines after TEST="], pstrm.commit.warn)
697
Simon Glassd85bb8f2022-01-29 14:14:09 -0700698 def test_blank_line_at_end(self):
Simon Glass3b762cc2020-10-29 21:46:28 -0600699 """Test detecting a blank line at the end of a file"""
700 text = '''This is a patch
701
702diff --git a/lib/fdtdec.c b/lib/fdtdec.c
703index c072e54..942244f 100644
704--- a/lib/fdtdec.c
705+++ b/lib/fdtdec.c
706@@ -1200,7 +1200,8 @@ int fdtdec_setup_mem_size_base(void)
707 }
708
709 gd->ram_size = (phys_size_t)(res.end - res.start + 1);
710- debug("%s: Initial DRAM size %llx\n", __func__, (u64)gd->ram_size);
711+ debug("%s: Initial DRAM size %llx\n", __func__,
712+ (unsigned long long)gd->ram_size);
713+
714diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c
715
716--
7172.7.4
718
719 '''
720 pstrm = PatchStream.process_text(text)
721 self.assertEqual(
722 ["Found possible blank line(s) at end of file 'lib/fdtdec.c'"],
723 pstrm.commit.warn)
Simon Glass1c1f2072020-10-29 21:46:34 -0600724
Simon Glassd85bb8f2022-01-29 14:14:09 -0700725 def test_no_upstream(self):
Simon Glass1c1f2072020-10-29 21:46:34 -0600726 """Test CountCommitsToBranch when there is no upstream"""
727 repo = self.make_git_tree()
728 target = repo.lookup_reference('refs/heads/base')
Simon Glass547cba62022-02-11 13:23:18 -0700729 # pylint doesn't seem to find this
730 # pylint: disable=E1101
Simon Glass1c1f2072020-10-29 21:46:34 -0600731 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
732
733 # Check that it can detect the current branch
Heinrich Schuchardtd01d6672023-04-20 20:07:29 +0200734 orig_dir = os.getcwd()
Simon Glass1c1f2072020-10-29 21:46:34 -0600735 try:
Simon Glass1c1f2072020-10-29 21:46:34 -0600736 os.chdir(self.gitdir)
737 with self.assertRaises(ValueError) as exc:
Simon Glass761648b2022-01-29 14:14:11 -0700738 gitutil.count_commits_to_branch(None)
Simon Glass1c1f2072020-10-29 21:46:34 -0600739 self.assertIn(
740 "Failed to determine upstream: fatal: no upstream configured for branch 'base'",
741 str(exc.exception))
742 finally:
743 os.chdir(orig_dir)
Simon Glass3db916d2020-10-29 21:46:35 -0600744
745 @staticmethod
Simon Glassf9b03cf2020-11-03 13:54:14 -0700746 def _fake_patchwork(url, subpath):
Simon Glass3db916d2020-10-29 21:46:35 -0600747 """Fake Patchwork server for the function below
748
749 This handles accessing a series, providing a list consisting of a
750 single patch
Simon Glassf9b03cf2020-11-03 13:54:14 -0700751
752 Args:
753 url (str): URL of patchwork server
754 subpath (str): URL subpath to use
Simon Glass3db916d2020-10-29 21:46:35 -0600755 """
756 re_series = re.match(r'series/(\d*)/$', subpath)
757 if re_series:
758 series_num = re_series.group(1)
759 if series_num == '1234':
760 return {'patches': [
761 {'id': '1', 'name': 'Some patch'}]}
762 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
763
Simon Glassd85bb8f2022-01-29 14:14:09 -0700764 def test_status_mismatch(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600765 """Test Patchwork patches not matching the series"""
766 series = Series()
767
Simon Glass14d64e32025-04-29 07:21:59 -0600768 with terminal.capture() as (_, err):
Simon Glassf9b03cf2020-11-03 13:54:14 -0700769 status.collect_patches(series, 1234, None, self._fake_patchwork)
Simon Glass3db916d2020-10-29 21:46:35 -0600770 self.assertIn('Warning: Patchwork reports 1 patches, series has 0',
771 err.getvalue())
772
Simon Glassd85bb8f2022-01-29 14:14:09 -0700773 def test_status_read_patch(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600774 """Test handling a single patch in Patchwork"""
775 series = Series()
776 series.commits = [Commit('abcd')]
777
Simon Glassf9b03cf2020-11-03 13:54:14 -0700778 patches = status.collect_patches(series, 1234, None,
779 self._fake_patchwork)
Simon Glass3db916d2020-10-29 21:46:35 -0600780 self.assertEqual(1, len(patches))
781 patch = patches[0]
782 self.assertEqual('1', patch.id)
783 self.assertEqual('Some patch', patch.raw_subject)
784
Simon Glassd85bb8f2022-01-29 14:14:09 -0700785 def test_parse_subject(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600786 """Test parsing of the patch subject"""
787 patch = status.Patch('1')
788
789 # Simple patch not in a series
790 patch.parse_subject('Testing')
791 self.assertEqual('Testing', patch.raw_subject)
792 self.assertEqual('Testing', patch.subject)
793 self.assertEqual(1, patch.seq)
794 self.assertEqual(1, patch.count)
795 self.assertEqual(None, patch.prefix)
796 self.assertEqual(None, patch.version)
797
798 # First patch in a series
799 patch.parse_subject('[1/2] Testing')
800 self.assertEqual('[1/2] Testing', patch.raw_subject)
801 self.assertEqual('Testing', patch.subject)
802 self.assertEqual(1, patch.seq)
803 self.assertEqual(2, patch.count)
804 self.assertEqual(None, patch.prefix)
805 self.assertEqual(None, patch.version)
806
807 # Second patch in a series
808 patch.parse_subject('[2/2] Testing')
809 self.assertEqual('Testing', patch.subject)
810 self.assertEqual(2, patch.seq)
811 self.assertEqual(2, patch.count)
812 self.assertEqual(None, patch.prefix)
813 self.assertEqual(None, patch.version)
814
815 # RFC patch
816 patch.parse_subject('[RFC,3/7] Testing')
817 self.assertEqual('Testing', patch.subject)
818 self.assertEqual(3, patch.seq)
819 self.assertEqual(7, patch.count)
820 self.assertEqual('RFC', patch.prefix)
821 self.assertEqual(None, patch.version)
822
823 # Version patch
824 patch.parse_subject('[v2,3/7] Testing')
825 self.assertEqual('Testing', patch.subject)
826 self.assertEqual(3, patch.seq)
827 self.assertEqual(7, patch.count)
828 self.assertEqual(None, patch.prefix)
829 self.assertEqual('v2', patch.version)
830
831 # All fields
832 patch.parse_subject('[RESEND,v2,3/7] Testing')
833 self.assertEqual('Testing', patch.subject)
834 self.assertEqual(3, patch.seq)
835 self.assertEqual(7, patch.count)
836 self.assertEqual('RESEND', patch.prefix)
837 self.assertEqual('v2', patch.version)
838
839 # RFC only
840 patch.parse_subject('[RESEND] Testing')
841 self.assertEqual('Testing', patch.subject)
842 self.assertEqual(1, patch.seq)
843 self.assertEqual(1, patch.count)
844 self.assertEqual('RESEND', patch.prefix)
845 self.assertEqual(None, patch.version)
846
Simon Glassd85bb8f2022-01-29 14:14:09 -0700847 def test_compare_series(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600848 """Test operation of compare_with_series()"""
849 commit1 = Commit('abcd')
850 commit1.subject = 'Subject 1'
851 commit2 = Commit('ef12')
852 commit2.subject = 'Subject 2'
853 commit3 = Commit('3456')
854 commit3.subject = 'Subject 2'
855
856 patch1 = status.Patch('1')
857 patch1.subject = 'Subject 1'
858 patch2 = status.Patch('2')
859 patch2.subject = 'Subject 2'
860 patch3 = status.Patch('3')
861 patch3.subject = 'Subject 2'
862
863 series = Series()
864 series.commits = [commit1]
865 patches = [patch1]
866 patch_for_commit, commit_for_patch, warnings = (
867 status.compare_with_series(series, patches))
868 self.assertEqual(1, len(patch_for_commit))
869 self.assertEqual(patch1, patch_for_commit[0])
870 self.assertEqual(1, len(commit_for_patch))
871 self.assertEqual(commit1, commit_for_patch[0])
872
873 series.commits = [commit1]
874 patches = [patch1, patch2]
875 patch_for_commit, commit_for_patch, warnings = (
876 status.compare_with_series(series, patches))
877 self.assertEqual(1, len(patch_for_commit))
878 self.assertEqual(patch1, patch_for_commit[0])
879 self.assertEqual(1, len(commit_for_patch))
880 self.assertEqual(commit1, commit_for_patch[0])
881 self.assertEqual(["Cannot find commit for patch 2 ('Subject 2')"],
882 warnings)
883
884 series.commits = [commit1, commit2]
885 patches = [patch1]
886 patch_for_commit, commit_for_patch, warnings = (
887 status.compare_with_series(series, patches))
888 self.assertEqual(1, len(patch_for_commit))
889 self.assertEqual(patch1, patch_for_commit[0])
890 self.assertEqual(1, len(commit_for_patch))
891 self.assertEqual(commit1, commit_for_patch[0])
892 self.assertEqual(["Cannot find patch for commit 2 ('Subject 2')"],
893 warnings)
894
895 series.commits = [commit1, commit2, commit3]
896 patches = [patch1, patch2]
897 patch_for_commit, commit_for_patch, warnings = (
898 status.compare_with_series(series, patches))
899 self.assertEqual(2, len(patch_for_commit))
900 self.assertEqual(patch1, patch_for_commit[0])
901 self.assertEqual(patch2, patch_for_commit[1])
902 self.assertEqual(1, len(commit_for_patch))
903 self.assertEqual(commit1, commit_for_patch[0])
904 self.assertEqual(["Cannot find patch for commit 3 ('Subject 2')",
905 "Multiple commits match patch 2 ('Subject 2'):\n"
906 ' Subject 2\n Subject 2'],
907 warnings)
908
909 series.commits = [commit1, commit2]
910 patches = [patch1, patch2, patch3]
911 patch_for_commit, commit_for_patch, warnings = (
912 status.compare_with_series(series, patches))
913 self.assertEqual(1, len(patch_for_commit))
914 self.assertEqual(patch1, patch_for_commit[0])
915 self.assertEqual(2, len(commit_for_patch))
916 self.assertEqual(commit1, commit_for_patch[0])
917 self.assertEqual(["Multiple patches match commit 2 ('Subject 2'):\n"
918 ' Subject 2\n Subject 2',
919 "Cannot find commit for patch 3 ('Subject 2')"],
920 warnings)
921
Simon Glassf9b03cf2020-11-03 13:54:14 -0700922 def _fake_patchwork2(self, url, subpath):
Simon Glass3db916d2020-10-29 21:46:35 -0600923 """Fake Patchwork server for the function below
924
925 This handles accessing series, patches and comments, providing the data
926 in self.patches to the caller
Simon Glassf9b03cf2020-11-03 13:54:14 -0700927
928 Args:
929 url (str): URL of patchwork server
930 subpath (str): URL subpath to use
Simon Glass3db916d2020-10-29 21:46:35 -0600931 """
932 re_series = re.match(r'series/(\d*)/$', subpath)
933 re_patch = re.match(r'patches/(\d*)/$', subpath)
934 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
935 if re_series:
936 series_num = re_series.group(1)
937 if series_num == '1234':
938 return {'patches': self.patches}
939 elif re_patch:
940 patch_num = int(re_patch.group(1))
941 patch = self.patches[patch_num - 1]
942 return patch
943 elif re_comments:
944 patch_num = int(re_comments.group(1))
945 patch = self.patches[patch_num - 1]
946 return patch.comments
947 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
948
Simon Glassd85bb8f2022-01-29 14:14:09 -0700949 def test_find_new_responses(self):
Simon Glass3db916d2020-10-29 21:46:35 -0600950 """Test operation of find_new_responses()"""
951 commit1 = Commit('abcd')
952 commit1.subject = 'Subject 1'
953 commit2 = Commit('ef12')
954 commit2.subject = 'Subject 2'
955
956 patch1 = status.Patch('1')
957 patch1.parse_subject('[1/2] Subject 1')
958 patch1.name = patch1.raw_subject
959 patch1.content = 'This is my patch content'
960 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
961
962 patch1.comments = [comment1a]
963
964 patch2 = status.Patch('2')
965 patch2.parse_subject('[2/2] Subject 2')
966 patch2.name = patch2.raw_subject
967 patch2.content = 'Some other patch content'
968 comment2a = {
969 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
970 (self.mary, self.leb)}
971 comment2b = {'content': 'Reviewed-by: %s' % self.fred}
972 patch2.comments = [comment2a, comment2b]
973
974 # This test works by setting up commits and patch for use by the fake
975 # Rest API function _fake_patchwork2(). It calls various functions in
976 # the status module after setting up tags in the commits, checking that
977 # things behaves as expected
978 self.commits = [commit1, commit2]
979 self.patches = [patch1, patch2]
980 count = 2
981 new_rtag_list = [None] * count
Simon Glass2112d072020-10-29 21:46:38 -0600982 review_list = [None, None]
Simon Glass3db916d2020-10-29 21:46:35 -0600983
984 # Check that the tags are picked up on the first patch
Simon Glass2112d072020-10-29 21:46:38 -0600985 status.find_new_responses(new_rtag_list, review_list, 0, commit1,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700986 patch1, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -0600987 self.assertEqual(new_rtag_list[0], {'Reviewed-by': {self.joe}})
988
989 # Now the second patch
Simon Glass2112d072020-10-29 21:46:38 -0600990 status.find_new_responses(new_rtag_list, review_list, 1, commit2,
Simon Glassf9b03cf2020-11-03 13:54:14 -0700991 patch2, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -0600992 self.assertEqual(new_rtag_list[1], {
993 'Reviewed-by': {self.mary, self.fred},
994 'Tested-by': {self.leb}})
995
996 # Now add some tags to the commit, which means they should not appear as
997 # 'new' tags when scanning comments
998 new_rtag_list = [None] * count
999 commit1.rtags = {'Reviewed-by': {self.joe}}
Simon Glass2112d072020-10-29 21:46:38 -06001000 status.find_new_responses(new_rtag_list, review_list, 0, commit1,
Simon Glassf9b03cf2020-11-03 13:54:14 -07001001 patch1, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -06001002 self.assertEqual(new_rtag_list[0], {})
1003
1004 # For the second commit, add Ed and Fred, so only Mary should be left
1005 commit2.rtags = {
1006 'Tested-by': {self.leb},
1007 'Reviewed-by': {self.fred}}
Simon Glass2112d072020-10-29 21:46:38 -06001008 status.find_new_responses(new_rtag_list, review_list, 1, commit2,
Simon Glassf9b03cf2020-11-03 13:54:14 -07001009 patch2, None, self._fake_patchwork2)
Simon Glass3db916d2020-10-29 21:46:35 -06001010 self.assertEqual(new_rtag_list[1], {'Reviewed-by': {self.mary}})
1011
1012 # Check that the output patches expectations:
1013 # 1 Subject 1
1014 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1015 # 2 Subject 2
1016 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1017 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1018 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1019 # 1 new response available in patchwork
1020
1021 series = Series()
1022 series.commits = [commit1, commit2]
Simon Glass02811582022-01-29 14:14:18 -07001023 terminal.set_print_test_mode()
Simon Glass2112d072020-10-29 21:46:38 -06001024 status.check_patchwork_status(series, '1234', None, None, False, False,
Simon Glassf9b03cf2020-11-03 13:54:14 -07001025 None, self._fake_patchwork2)
Simon Glass02811582022-01-29 14:14:18 -07001026 lines = iter(terminal.get_print_test_lines())
Simon Glass3db916d2020-10-29 21:46:35 -06001027 col = terminal.Color()
1028 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
1029 next(lines))
1030 self.assertEqual(
1031 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
1032 bright=False),
1033 next(lines))
1034 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE, bright=False),
1035 next(lines))
1036
1037 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
1038 next(lines))
1039 self.assertEqual(
Simon Glass2112d072020-10-29 21:46:38 -06001040 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
Simon Glass3db916d2020-10-29 21:46:35 -06001041 bright=False),
1042 next(lines))
Simon Glass2112d072020-10-29 21:46:38 -06001043 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE, bright=False),
Simon Glass3db916d2020-10-29 21:46:35 -06001044 next(lines))
1045 self.assertEqual(
Simon Glass2112d072020-10-29 21:46:38 -06001046 terminal.PrintLine(' Tested-by: ', col.GREEN, newline=False,
Simon Glass3db916d2020-10-29 21:46:35 -06001047 bright=False),
1048 next(lines))
Simon Glass2112d072020-10-29 21:46:38 -06001049 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE, bright=False),
Simon Glass3db916d2020-10-29 21:46:35 -06001050 next(lines))
1051 self.assertEqual(
1052 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1053 next(lines))
1054 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
1055 next(lines))
1056 self.assertEqual(terminal.PrintLine(
Simon Glassd0a0a582020-10-29 21:46:36 -06001057 '1 new response available in patchwork (use -d to write them to a new branch)',
1058 None), next(lines))
1059
Simon Glassf9b03cf2020-11-03 13:54:14 -07001060 def _fake_patchwork3(self, url, subpath):
Simon Glassd0a0a582020-10-29 21:46:36 -06001061 """Fake Patchwork server for the function below
1062
1063 This handles accessing series, patches and comments, providing the data
1064 in self.patches to the caller
Simon Glassf9b03cf2020-11-03 13:54:14 -07001065
1066 Args:
1067 url (str): URL of patchwork server
1068 subpath (str): URL subpath to use
Simon Glassd0a0a582020-10-29 21:46:36 -06001069 """
1070 re_series = re.match(r'series/(\d*)/$', subpath)
1071 re_patch = re.match(r'patches/(\d*)/$', subpath)
1072 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
1073 if re_series:
1074 series_num = re_series.group(1)
1075 if series_num == '1234':
1076 return {'patches': self.patches}
1077 elif re_patch:
1078 patch_num = int(re_patch.group(1))
1079 patch = self.patches[patch_num - 1]
1080 return patch
1081 elif re_comments:
1082 patch_num = int(re_comments.group(1))
1083 patch = self.patches[patch_num - 1]
1084 return patch.comments
1085 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
1086
Simon Glassd85bb8f2022-01-29 14:14:09 -07001087 def test_create_branch(self):
Simon Glassd0a0a582020-10-29 21:46:36 -06001088 """Test operation of create_branch()"""
1089 repo = self.make_git_tree()
1090 branch = 'first'
1091 dest_branch = 'first2'
1092 count = 2
1093 gitdir = os.path.join(self.gitdir, '.git')
1094
1095 # Set up the test git tree. We use branch 'first' which has two commits
1096 # in it
1097 series = patchstream.get_metadata_for_list(branch, gitdir, count)
1098 self.assertEqual(2, len(series.commits))
1099
1100 patch1 = status.Patch('1')
1101 patch1.parse_subject('[1/2] %s' % series.commits[0].subject)
1102 patch1.name = patch1.raw_subject
1103 patch1.content = 'This is my patch content'
1104 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
1105
1106 patch1.comments = [comment1a]
1107
1108 patch2 = status.Patch('2')
1109 patch2.parse_subject('[2/2] %s' % series.commits[1].subject)
1110 patch2.name = patch2.raw_subject
1111 patch2.content = 'Some other patch content'
1112 comment2a = {
1113 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1114 (self.mary, self.leb)}
1115 comment2b = {
1116 'content': 'Reviewed-by: %s' % self.fred}
1117 patch2.comments = [comment2a, comment2b]
1118
1119 # This test works by setting up patches for use by the fake Rest API
1120 # function _fake_patchwork3(). The fake patch comments above should
1121 # result in new review tags that are collected and added to the commits
1122 # created in the destination branch.
1123 self.patches = [patch1, patch2]
1124 count = 2
1125
1126 # Expected output:
1127 # 1 i2c: I2C things
1128 # + Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1129 # 2 spi: SPI fixes
1130 # + Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1131 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1132 # + Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1133 # 4 new responses available in patchwork
1134 # 4 responses added from patchwork into new branch 'first2'
1135 # <unittest.result.TestResult run=8 errors=0 failures=0>
1136
Simon Glass02811582022-01-29 14:14:18 -07001137 terminal.set_print_test_mode()
Simon Glassd0a0a582020-10-29 21:46:36 -06001138 status.check_patchwork_status(series, '1234', branch, dest_branch,
Simon Glassf9b03cf2020-11-03 13:54:14 -07001139 False, False, None, self._fake_patchwork3,
1140 repo)
Simon Glass02811582022-01-29 14:14:18 -07001141 lines = terminal.get_print_test_lines()
Simon Glassd0a0a582020-10-29 21:46:36 -06001142 self.assertEqual(12, len(lines))
1143 self.assertEqual(
1144 "4 responses added from patchwork into new branch 'first2'",
1145 lines[11].text)
1146
1147 # Check that the destination branch has the new tags
1148 new_series = patchstream.get_metadata_for_list(dest_branch, gitdir,
1149 count)
1150 self.assertEqual(
1151 {'Reviewed-by': {self.joe}},
1152 new_series.commits[0].rtags)
1153 self.assertEqual(
1154 {'Tested-by': {self.leb},
1155 'Reviewed-by': {self.fred, self.mary}},
1156 new_series.commits[1].rtags)
1157
1158 # Now check the actual test of the first commit message. We expect to
1159 # see the new tags immediately below the old ones.
1160 stdout = patchstream.get_list(dest_branch, count=count, git_dir=gitdir)
1161 lines = iter([line.strip() for line in stdout.splitlines()
1162 if '-by:' in line])
1163
1164 # First patch should have the review tag
1165 self.assertEqual('Reviewed-by: %s' % self.joe, next(lines))
1166
1167 # Second patch should have the sign-off then the tested-by and two
1168 # reviewed-by tags
1169 self.assertEqual('Signed-off-by: %s' % self.leb, next(lines))
1170 self.assertEqual('Reviewed-by: %s' % self.fred, next(lines))
1171 self.assertEqual('Reviewed-by: %s' % self.mary, next(lines))
1172 self.assertEqual('Tested-by: %s' % self.leb, next(lines))
Simon Glassda8a2922020-10-29 21:46:37 -06001173
Simon Glassd85bb8f2022-01-29 14:14:09 -07001174 def test_parse_snippets(self):
Simon Glassda8a2922020-10-29 21:46:37 -06001175 """Test parsing of review snippets"""
1176 text = '''Hi Fred,
1177
1178This is a comment from someone.
1179
1180Something else
1181
1182On some recent date, Fred wrote:
1183> This is why I wrote the patch
1184> so here it is
1185
1186Now a comment about the commit message
1187A little more to say
1188
1189Even more
1190
1191> diff --git a/file.c b/file.c
1192> Some more code
1193> Code line 2
1194> Code line 3
1195> Code line 4
1196> Code line 5
1197> Code line 6
1198> Code line 7
1199> Code line 8
1200> Code line 9
1201
1202And another comment
1203
Simon Glassd85bb8f2022-01-29 14:14:09 -07001204> @@ -153,8 +143,13 @@ def check_patch(fname, show_types=False):
Simon Glassda8a2922020-10-29 21:46:37 -06001205> further down on the file
1206> and more code
1207> +Addition here
1208> +Another addition here
1209> codey
1210> more codey
1211
1212and another thing in same file
1213
1214> @@ -253,8 +243,13 @@
1215> with no function context
1216
1217one more thing
1218
1219> diff --git a/tools/patman/main.py b/tools/patman/main.py
1220> +line of code
1221now a very long comment in a different file
1222line2
1223line3
1224line4
1225line5
1226line6
1227line7
1228line8
1229'''
1230 pstrm = PatchStream.process_text(text, True)
1231 self.assertEqual([], pstrm.commit.warn)
1232
1233 # We expect to the filename and up to 5 lines of code context before
1234 # each comment. The 'On xxx wrote:' bit should be removed.
1235 self.assertEqual(
1236 [['Hi Fred,',
1237 'This is a comment from someone.',
1238 'Something else'],
1239 ['> This is why I wrote the patch',
1240 '> so here it is',
1241 'Now a comment about the commit message',
1242 'A little more to say', 'Even more'],
1243 ['> File: file.c', '> Code line 5', '> Code line 6',
1244 '> Code line 7', '> Code line 8', '> Code line 9',
1245 'And another comment'],
1246 ['> File: file.c',
Simon Glassd85bb8f2022-01-29 14:14:09 -07001247 '> Line: 153 / 143: def check_patch(fname, show_types=False):',
Simon Glassda8a2922020-10-29 21:46:37 -06001248 '> and more code', '> +Addition here', '> +Another addition here',
1249 '> codey', '> more codey', 'and another thing in same file'],
1250 ['> File: file.c', '> Line: 253 / 243',
1251 '> with no function context', 'one more thing'],
1252 ['> File: tools/patman/main.py', '> +line of code',
1253 'now a very long comment in a different file',
1254 'line2', 'line3', 'line4', 'line5', 'line6', 'line7', 'line8']],
1255 pstrm.snippets)
Simon Glass2112d072020-10-29 21:46:38 -06001256
Simon Glassd85bb8f2022-01-29 14:14:09 -07001257 def test_review_snippets(self):
Simon Glass2112d072020-10-29 21:46:38 -06001258 """Test showing of review snippets"""
1259 def _to_submitter(who):
1260 m_who = re.match('(.*) <(.*)>', who)
1261 return {
1262 'name': m_who.group(1),
1263 'email': m_who.group(2)
1264 }
1265
1266 commit1 = Commit('abcd')
1267 commit1.subject = 'Subject 1'
1268 commit2 = Commit('ef12')
1269 commit2.subject = 'Subject 2'
1270
1271 patch1 = status.Patch('1')
1272 patch1.parse_subject('[1/2] Subject 1')
1273 patch1.name = patch1.raw_subject
1274 patch1.content = 'This is my patch content'
1275 comment1a = {'submitter': _to_submitter(self.joe),
1276 'content': '''Hi Fred,
1277
1278On some date Fred wrote:
1279
1280> diff --git a/file.c b/file.c
1281> Some code
1282> and more code
1283
1284Here is my comment above the above...
1285
1286
1287Reviewed-by: %s
1288''' % self.joe}
1289
1290 patch1.comments = [comment1a]
1291
1292 patch2 = status.Patch('2')
1293 patch2.parse_subject('[2/2] Subject 2')
1294 patch2.name = patch2.raw_subject
1295 patch2.content = 'Some other patch content'
1296 comment2a = {
1297 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1298 (self.mary, self.leb)}
1299 comment2b = {'submitter': _to_submitter(self.fred),
1300 'content': '''Hi Fred,
1301
1302On some date Fred wrote:
1303
1304> diff --git a/tools/patman/commit.py b/tools/patman/commit.py
1305> @@ -41,6 +41,9 @@ class Commit:
1306> self.rtags = collections.defaultdict(set)
1307> self.warn = []
1308>
1309> + def __str__(self):
1310> + return self.subject
1311> +
Simon Glassd85bb8f2022-01-29 14:14:09 -07001312> def add_change(self, version, info):
Simon Glass2112d072020-10-29 21:46:38 -06001313> """Add a new change line to the change list for a version.
1314>
1315A comment
1316
1317Reviewed-by: %s
1318''' % self.fred}
1319 patch2.comments = [comment2a, comment2b]
1320
1321 # This test works by setting up commits and patch for use by the fake
1322 # Rest API function _fake_patchwork2(). It calls various functions in
1323 # the status module after setting up tags in the commits, checking that
1324 # things behaves as expected
1325 self.commits = [commit1, commit2]
1326 self.patches = [patch1, patch2]
1327
1328 # Check that the output patches expectations:
1329 # 1 Subject 1
1330 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1331 # 2 Subject 2
1332 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1333 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1334 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1335 # 1 new response available in patchwork
1336
1337 series = Series()
1338 series.commits = [commit1, commit2]
Simon Glass02811582022-01-29 14:14:18 -07001339 terminal.set_print_test_mode()
Simon Glass2112d072020-10-29 21:46:38 -06001340 status.check_patchwork_status(series, '1234', None, None, False, True,
Simon Glassf9b03cf2020-11-03 13:54:14 -07001341 None, self._fake_patchwork2)
Simon Glass02811582022-01-29 14:14:18 -07001342 lines = iter(terminal.get_print_test_lines())
Simon Glass2112d072020-10-29 21:46:38 -06001343 col = terminal.Color()
1344 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
1345 next(lines))
1346 self.assertEqual(
1347 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1348 next(lines))
1349 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE), next(lines))
1350
1351 self.assertEqual(terminal.PrintLine('Review: %s' % self.joe, col.RED),
1352 next(lines))
1353 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(lines))
1354 self.assertEqual(terminal.PrintLine('', None), next(lines))
1355 self.assertEqual(terminal.PrintLine(' > File: file.c', col.MAGENTA),
1356 next(lines))
1357 self.assertEqual(terminal.PrintLine(' > Some code', col.MAGENTA),
1358 next(lines))
1359 self.assertEqual(terminal.PrintLine(' > and more code', col.MAGENTA),
1360 next(lines))
1361 self.assertEqual(terminal.PrintLine(
1362 ' Here is my comment above the above...', None), next(lines))
1363 self.assertEqual(terminal.PrintLine('', None), next(lines))
1364
1365 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
1366 next(lines))
1367 self.assertEqual(
1368 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1369 next(lines))
1370 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE),
1371 next(lines))
1372 self.assertEqual(
1373 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1374 next(lines))
1375 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
1376 next(lines))
1377 self.assertEqual(
1378 terminal.PrintLine(' + Tested-by: ', col.GREEN, newline=False),
1379 next(lines))
1380 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE),
1381 next(lines))
1382
1383 self.assertEqual(terminal.PrintLine('Review: %s' % self.fred, col.RED),
1384 next(lines))
1385 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(lines))
1386 self.assertEqual(terminal.PrintLine('', None), next(lines))
1387 self.assertEqual(terminal.PrintLine(
1388 ' > File: tools/patman/commit.py', col.MAGENTA), next(lines))
1389 self.assertEqual(terminal.PrintLine(
1390 ' > Line: 41 / 41: class Commit:', col.MAGENTA), next(lines))
1391 self.assertEqual(terminal.PrintLine(
1392 ' > + return self.subject', col.MAGENTA), next(lines))
1393 self.assertEqual(terminal.PrintLine(
1394 ' > +', col.MAGENTA), next(lines))
1395 self.assertEqual(
Simon Glassd85bb8f2022-01-29 14:14:09 -07001396 terminal.PrintLine(' > def add_change(self, version, info):',
Simon Glass2112d072020-10-29 21:46:38 -06001397 col.MAGENTA),
1398 next(lines))
1399 self.assertEqual(terminal.PrintLine(
1400 ' > """Add a new change line to the change list for a version.',
1401 col.MAGENTA), next(lines))
1402 self.assertEqual(terminal.PrintLine(
1403 ' >', col.MAGENTA), next(lines))
1404 self.assertEqual(terminal.PrintLine(
1405 ' A comment', None), next(lines))
1406 self.assertEqual(terminal.PrintLine('', None), next(lines))
1407
1408 self.assertEqual(terminal.PrintLine(
1409 '4 new responses available in patchwork (use -d to write them to a new branch)',
1410 None), next(lines))
Simon Glass6a222e62021-08-01 16:02:39 -06001411
Simon Glassd85bb8f2022-01-29 14:14:09 -07001412 def test_insert_tags(self):
Simon Glass6a222e62021-08-01 16:02:39 -06001413 """Test inserting of review tags"""
1414 msg = '''first line
1415second line.'''
1416 tags = [
1417 'Reviewed-by: Bin Meng <bmeng.cn@gmail.com>',
1418 'Tested-by: Bin Meng <bmeng.cn@gmail.com>'
1419 ]
1420 signoff = 'Signed-off-by: Simon Glass <sjg@chromium.com>'
1421 tag_str = '\n'.join(tags)
1422
1423 new_msg = patchstream.insert_tags(msg, tags)
1424 self.assertEqual(msg + '\n\n' + tag_str, new_msg)
1425
1426 new_msg = patchstream.insert_tags(msg + '\n', tags)
1427 self.assertEqual(msg + '\n\n' + tag_str, new_msg)
1428
1429 msg += '\n\n' + signoff
1430 new_msg = patchstream.insert_tags(msg, tags)
1431 self.assertEqual(msg + '\n' + tag_str, new_msg)