1# -*- coding: utf-8 -*- 2# SPDX-License-Identifier: GPL-2.0+ 3# 4# Copyright 2017 Google, Inc 5# 6 7"""Functional tests for checking that patman behaves correctly""" 8 9import asyncio 10import contextlib 11import os 12import pathlib 13import re 14import shutil 15import sys 16import unittest 17 18import pygit2 19 20from u_boot_pylib import command 21from u_boot_pylib import gitutil 22from u_boot_pylib import terminal 23from u_boot_pylib import tools 24 25from patman.commit import Commit 26from patman import control 27from patman import patchstream 28from patman.patchstream import PatchStream 29from patman import patchwork 30from patman import send 31from patman.series import Series 32from patman import status 33from patman.test_common import TestCommon 34 35PATMAN_DIR = pathlib.Path(__file__).parent 36TEST_DATA_DIR = PATMAN_DIR / 'test/' 37 38 39@contextlib.contextmanager 40def directory_excursion(directory): 41 """Change directory to `directory` for a limited to the context block.""" 42 current = os.getcwd() 43 try: 44 os.chdir(directory) 45 yield 46 finally: 47 os.chdir(current) 48 49 50class TestFunctional(unittest.TestCase, TestCommon): 51 """Functional tests for checking that patman behaves correctly""" 52 fred = 'Fred Bloggs <f.bloggs@napier.net>' 53 joe = 'Joe Bloggs <joe@napierwallies.co.nz>' 54 mary = 'Mary Bloggs <mary@napierwallies.co.nz>' 55 commits = None 56 patches = None 57 58 def setUp(self): 59 TestCommon.setUp(self) 60 self.repo = None 61 self._patman_pathname = sys.argv[0] 62 self._patman_dir = os.path.dirname(os.path.realpath(sys.argv[0])) 63 64 def tearDown(self): 65 TestCommon.tearDown(self) 66 67 @staticmethod 68 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 """ 77 return TEST_DATA_DIR / fname 78 79 @classmethod 80 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() 90 91 @classmethod 92 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 """ 101 fname = re.sub('[ :]', '-', subject) 102 return fname.replace('--', '-') 103 104 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 """ 117 cover_fname = None 118 fname_list = [] 119 for i, commit in enumerate(series.commits): 120 clean_subject = self._get_patch_name(commit.subject) 121 src_fname = '%04d-%s.patch' % (i + 1, clean_subject[:52]) 122 fname = os.path.join(self.tmpdir, src_fname) 123 shutil.copy(self._get_path(src_fname), fname) 124 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) 129 shutil.copy(self._get_path(src_fname), fname) 130 131 return cover_fname, fname_list 132 133 def test_basic(self): 134 """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 141 Series-postfix: some-branch 142 Series-cc: Stefan Brüns <stefan.bruens@rwth-aachen.de> 143 Cover-letter-cc: Lord Mëlchett <clergy@palace.gov> 144 Series-version: 3 145 Patch-cc: fred 146 Series-process-log: sort, uniq 147 Series-changes: 4 148 - Some changes 149 - Multi 150 line 151 change 152 153 Commit-changes: 2 154 - Changes only for this commit 155 156 Cover-changes: 4 157 - Some notes for the cover letter 158 159 Cover-letter: 160 test: A test patch series 161 This is a test of how the cover 162 letter 163 works 164 END 165 166 and this in the first commit: 167 168 Commit-changes: 2 169 - second revision change 170 171 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 196 - Series-to, Series-cc, Series-prefix, Series-postfix, Cover-letter 197 - Cover-letter-cc, Series-version, Series-changes, Series-notes 198 - Commit-notes 199 """ 200 process_tags = True 201 ignore_bad_tags = False 202 stefan = (b'Stefan Br\xc3\xbcns <stefan.bruens@rwth-aachen.de>' 203 .decode('utf-8')) 204 rick = 'Richard III <richard@palace.gov>' 205 mel = b'Lord M\xc3\xablchett <clergy@palace.gov>'.decode('utf-8') 206 add_maintainers = [stefan, rick] 207 dry_run = True 208 in_reply_to = mel 209 count = 2 210 alias = { 211 'fdt': ['simon'], 212 'u-boot': ['u-boot@lists.denx.de'], 213 'simon': [self.leb], 214 'fred': [self.fred], 215 'joe': [self.joe], 216 } 217 218 text = self._get_text('test01.txt') 219 series = patchstream.get_metadata_for_test(text) 220 series.base_commit = Commit('1a44532') 221 series.branch = 'mybranch' 222 cover_fname, args = self._create_patches_for_test(series) 223 get_maintainer_script = str(pathlib.Path(__file__).parent.parent.parent 224 / 'get_maintainer.pl') + ' --norolestats' 225 with terminal.capture() as out: 226 patchstream.fix_patches(series, args) 227 if cover_fname and series.get('cover'): 228 patchstream.insert_cover_letter(cover_fname, series, count) 229 series.DoChecks() 230 cc_file = series.MakeCcFile(process_tags, cover_fname, 231 not ignore_bad_tags, add_maintainers, 232 None, get_maintainer_script, alias) 233 cmd = gitutil.email_patches( 234 series, cover_fname, args, dry_run, not ignore_bad_tags, 235 cc_file, alias, in_reply_to=in_reply_to, thread=None) 236 series.ShowActions(args, cmd, process_tags, alias) 237 cc_lines = tools.read_file(cc_file, binary=False).splitlines() 238 os.remove(cc_file) 239 240 itr = iter(out[0].getvalue().splitlines()) 241 self.assertEqual('Cleaned %s patches' % len(series.commits), 242 next(itr)) 243 self.assertEqual('Change log missing for v2', next(itr)) 244 self.assertEqual('Change log missing for v3', next(itr)) 245 self.assertEqual('Change log for unknown version v4', next(itr)) 246 self.assertEqual("Alias 'pci' not found", next(itr)) 247 while next(itr) != 'Cc processing complete': 248 pass 249 self.assertIn('Dry run', next(itr)) 250 self.assertEqual('', next(itr)) 251 self.assertIn('Send a total of %d patches' % count, next(itr)) 252 prev = next(itr) 253 for i in range(len(series.commits)): 254 self.assertEqual(' %s' % args[i], prev) 255 while True: 256 prev = next(itr) 257 if 'Cc:' not in prev: 258 break 259 self.assertEqual('To: u-boot@lists.denx.de', prev) 260 self.assertEqual('Cc: %s' % stefan, next(itr)) 261 self.assertEqual('Version: 3', next(itr)) 262 self.assertEqual('Prefix:\t RFC', next(itr)) 263 self.assertEqual('Postfix:\t some-branch', next(itr)) 264 self.assertEqual('Cover: 4 lines', next(itr)) 265 self.assertEqual(' Cc: %s' % self.fred, next(itr)) 266 self.assertEqual(' Cc: %s' % self.joe, next(itr)) 267 self.assertEqual(' Cc: %s' % self.leb, 268 next(itr)) 269 self.assertEqual(' Cc: %s' % mel, next(itr)) 270 self.assertEqual(' Cc: %s' % rick, next(itr)) 271 expected = ('Git command: git send-email --annotate ' 272 '--in-reply-to="%s" --to u-boot@lists.denx.de ' 273 '--cc "%s" --cc-cmd "%s send --cc-cmd %s" %s %s' 274 % (in_reply_to, stefan, sys.argv[0], cc_file, cover_fname, 275 ' '.join(args))) 276 self.assertEqual(expected, next(itr)) 277 278 self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)), cc_lines[0]) 279 self.assertEqual( 280 '%s %s\0%s\0%s\0%s\0%s' % (args[1], self.fred, self.joe, self.leb, 281 rick, stefan), 282 cc_lines[1]) 283 284 expected = ''' 285This is a test of how the cover 286letter 287works 288 289some notes 290about some things 291from the first commit 292 293Changes in v4: 294- Multi 295 line 296 change 297- Some changes 298- Some notes for the cover letter 299- fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base() 300 301Simon Glass (2): 302 pci: Correct cast for sandbox 303 fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base() 304 305 cmd/pci.c | 3 ++- 306 fs/fat/fat.c | 1 + 307 lib/efi_loader/efi_memory.c | 1 + 308 lib/fdtdec.c | 3 ++- 309 4 files changed, 6 insertions(+), 2 deletions(-) 310 311--\x20 3122.7.4 313 314base-commit: 1a44532 315branch: mybranch 316''' 317 lines = tools.read_file(cover_fname, binary=False).splitlines() 318 self.assertEqual( 319 'Subject: [RFC PATCH some-branch v3 0/2] test: A test patch series', 320 lines[3]) 321 self.assertEqual(expected.splitlines(), lines[7:]) 322 323 for i, fname in enumerate(args): 324 lines = tools.read_file(fname, binary=False).splitlines() 325 subject = [line for line in lines if line.startswith('Subject')] 326 self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count), 327 subject[0][:18]) 328 329 # Check that we got our commit notes 330 start = 0 331 expected = '' 332 333 if i == 0: 334 start = 17 335 expected = '''--- 336Some notes about 337the first commit 338 339(no changes since v2) 340 341Changes in v2: 342- second revision change''' 343 elif i == 1: 344 start = 17 345 expected = '''--- 346 347Changes in v4: 348- Multi 349 line 350 change 351- New 352- Some changes 353 354Changes in v2: 355- Changes only for this commit''' 356 357 if expected: 358 expected = expected.splitlines() 359 self.assertEqual(expected, lines[start:(start+len(expected))]) 360 361 def test_base_commit(self): 362 """Test adding a base commit with no cover letter""" 363 orig_text = self._get_text('test01.txt') 364 pos = orig_text.index( 365 'commit 5ab48490f03051875ab13d288a4bf32b507d76fd') 366 text = orig_text[:pos] 367 series = patchstream.get_metadata_for_test(text) 368 series.base_commit = Commit('1a44532') 369 series.branch = 'mybranch' 370 cover_fname, args = self._create_patches_for_test(series) 371 self.assertFalse(cover_fname) 372 with terminal.capture() as out: 373 patchstream.fix_patches(series, args, insert_base_commit=True) 374 self.assertEqual('Cleaned 1 patch\n', out[0].getvalue()) 375 lines = tools.read_file(args[0], binary=False).splitlines() 376 pos = lines.index('-- ') 377 378 # We expect these lines at the end: 379 # -- (with trailing space) 380 # 2.7.4 381 # (empty) 382 # base-commit: xxx 383 # branch: xxx 384 self.assertEqual('base-commit: 1a44532', lines[pos + 3]) 385 self.assertEqual('branch: mybranch', lines[pos + 4]) 386 387 def test_branch(self): 388 """Test creating patches from a branch""" 389 repo = self.make_git_tree() 390 target = repo.lookup_reference('refs/heads/first') 391 # pylint doesn't seem to find this 392 # pylint: disable=E1101 393 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE) 394 control.setup() 395 orig_dir = os.getcwd() 396 try: 397 os.chdir(self.tmpdir) 398 399 # Check that it can detect the current branch 400 self.assertEqual(2, gitutil.count_commits_to_branch(None)) 401 col = terminal.Color() 402 with terminal.capture() as _: 403 _, cover_fname, patch_files = send.prepare_patches( 404 col, branch=None, count=-1, start=0, end=0, 405 ignore_binary=False, signoff=True) 406 self.assertIsNone(cover_fname) 407 self.assertEqual(2, len(patch_files)) 408 409 # Check that it can detect a different branch 410 self.assertEqual(3, gitutil.count_commits_to_branch('second')) 411 with terminal.capture() as _: 412 _, cover_fname, patch_files = send.prepare_patches( 413 col, branch='second', count=-1, start=0, end=0, 414 ignore_binary=False, signoff=True) 415 self.assertIsNotNone(cover_fname) 416 self.assertEqual(3, len(patch_files)) 417 418 cover = tools.read_file(cover_fname, binary=False) 419 lines = cover.splitlines()[-2:] 420 base = repo.lookup_reference('refs/heads/base').target 421 self.assertEqual(f'base-commit: {base}', lines[0]) 422 self.assertEqual('branch: second', lines[1]) 423 424 # Make sure that the base-commit is not present when it is in the 425 # cover letter 426 for fname in patch_files: 427 self.assertNotIn(b'base-commit:', tools.read_file(fname)) 428 429 # Check that it can skip patches at the end 430 with terminal.capture() as _: 431 _, cover_fname, patch_files = send.prepare_patches( 432 col, branch='second', count=-1, start=0, end=1, 433 ignore_binary=False, signoff=True) 434 self.assertIsNotNone(cover_fname) 435 self.assertEqual(2, len(patch_files)) 436 437 cover = tools.read_file(cover_fname, binary=False) 438 lines = cover.splitlines()[-2:] 439 base2 = repo.lookup_reference('refs/heads/second') 440 ref = base2.peel(pygit2.GIT_OBJ_COMMIT).parents[0].parents[0].id 441 self.assertEqual(f'base-commit: {ref}', lines[0]) 442 self.assertEqual('branch: second', lines[1]) 443 finally: 444 os.chdir(orig_dir) 445 446 def test_custom_get_maintainer_script(self): 447 """Validate that a custom get_maintainer script gets used.""" 448 self.make_git_tree() 449 with directory_excursion(self.tmpdir): 450 # Setup git. 451 os.environ['GIT_CONFIG_GLOBAL'] = '/dev/null' 452 os.environ['GIT_CONFIG_SYSTEM'] = '/dev/null' 453 tools.run('git', 'config', 'user.name', 'Dummy') 454 tools.run('git', 'config', 'user.email', 'dumdum@dummy.com') 455 tools.run('git', 'branch', 'upstream') 456 tools.run('git', 'branch', '--set-upstream-to=upstream') 457 458 # Setup patman configuration. 459 tools.write_file('.patman', '[settings]\n' 460 'get_maintainer_script: dummy-script.sh\n' 461 'check_patch: False\n' 462 'add_maintainers: True\n', binary=False) 463 tools.write_file('dummy-script.sh', 464 '#!/usr/bin/env python3\n' 465 'print("hello@there.com")\n', binary=False) 466 os.chmod('dummy-script.sh', 0x555) 467 tools.run('git', 'add', '.') 468 tools.run('git', 'commit', '-m', 'new commit') 469 470 # Finally, do the test 471 with terminal.capture(): 472 output = tools.run(PATMAN_DIR / 'patman', '--dry-run') 473 # Assert the email address is part of the dry-run 474 # output. 475 self.assertIn('hello@there.com', output) 476 477 def test_tags(self): 478 """Test collection of tags in a patchstream""" 479 text = '''This is a patch 480 481Signed-off-by: Terminator 482Reviewed-by: %s 483Reviewed-by: %s 484Tested-by: %s 485''' % (self.joe, self.mary, self.leb) 486 pstrm = PatchStream.process_text(text) 487 self.assertEqual(pstrm.commit.rtags, { 488 'Reviewed-by': {self.joe, self.mary}, 489 'Tested-by': {self.leb}}) 490 491 def test_invalid_tag(self): 492 """Test invalid tag in a patchstream""" 493 text = '''This is a patch 494 495Serie-version: 2 496''' 497 with self.assertRaises(ValueError) as exc: 498 PatchStream.process_text(text) 499 self.assertEqual("Line 3: Invalid tag = 'Serie-version: 2'", 500 str(exc.exception)) 501 502 def test_missing_end(self): 503 """Test a missing END tag""" 504 text = '''This is a patch 505 506Cover-letter: 507This is the title 508missing END after this line 509Signed-off-by: Fred 510''' 511 pstrm = PatchStream.process_text(text) 512 self.assertEqual(["Missing 'END' in section 'cover'"], 513 pstrm.commit.warn) 514 515 def test_missing_blank_line(self): 516 """Test a missing blank line after a tag""" 517 text = '''This is a patch 518 519Series-changes: 2 520- First line of changes 521- Missing blank line after this line 522Signed-off-by: Fred 523''' 524 pstrm = PatchStream.process_text(text) 525 self.assertEqual(["Missing 'blank line' in section 'Series-changes'"], 526 pstrm.commit.warn) 527 528 def test_invalid_commit_tag(self): 529 """Test an invalid Commit-xxx tag""" 530 text = '''This is a patch 531 532Commit-fred: testing 533''' 534 pstrm = PatchStream.process_text(text) 535 self.assertEqual(["Line 3: Ignoring Commit-fred"], pstrm.commit.warn) 536 537 def test_self_test(self): 538 """Test a tested by tag by this user""" 539 test_line = 'Tested-by: %s@napier.com' % os.getenv('USER') 540 text = '''This is a patch 541 542%s 543''' % test_line 544 pstrm = PatchStream.process_text(text) 545 self.assertEqual(["Ignoring '%s'" % test_line], pstrm.commit.warn) 546 547 def test_space_before_tab(self): 548 """Test a space before a tab""" 549 text = '''This is a patch 550 551+ \tSomething 552''' 553 pstrm = PatchStream.process_text(text) 554 self.assertEqual(["Line 3/0 has space before tab"], pstrm.commit.warn) 555 556 def test_lines_after_test(self): 557 """Test detecting lines after TEST= line""" 558 text = '''This is a patch 559 560TEST=sometest 561more lines 562here 563''' 564 pstrm = PatchStream.process_text(text) 565 self.assertEqual(["Found 2 lines after TEST="], pstrm.commit.warn) 566 567 def test_blank_line_at_end(self): 568 """Test detecting a blank line at the end of a file""" 569 text = '''This is a patch 570 571diff --git a/lib/fdtdec.c b/lib/fdtdec.c 572index c072e54..942244f 100644 573--- a/lib/fdtdec.c 574+++ b/lib/fdtdec.c 575@@ -1200,7 +1200,8 @@ int fdtdec_setup_mem_size_base(void) 576 \t} 577 578 \tgd->ram_size = (phys_size_t)(res.end - res.start + 1); 579- debug("%s: Initial DRAM size %llx\n", __func__, (u64)gd->ram_size); 580+ debug("%s: Initial DRAM size %llx\n", __func__, 581+ (unsigned long long)gd->ram_size); 582+ 583diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c 584 585-- 5862.7.4 587 588 ''' 589 pstrm = PatchStream.process_text(text) 590 self.assertEqual( 591 ["Found possible blank line(s) at end of file 'lib/fdtdec.c'"], 592 pstrm.commit.warn) 593 594 def test_no_upstream(self): 595 """Test CountCommitsToBranch when there is no upstream""" 596 repo = self.make_git_tree() 597 target = repo.lookup_reference('refs/heads/base') 598 # pylint doesn't seem to find this 599 # pylint: disable=E1101 600 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE) 601 602 # Check that it can detect the current branch 603 orig_dir = os.getcwd() 604 try: 605 os.chdir(self.gitdir) 606 with self.assertRaises(ValueError) as exc: 607 gitutil.count_commits_to_branch(None) 608 self.assertIn( 609 "Failed to determine upstream: fatal: no upstream configured for branch 'base'", 610 str(exc.exception)) 611 finally: 612 os.chdir(orig_dir) 613 614 def run_patman(self, *args): 615 """Run patman using the provided arguments 616 617 This runs the patman executable from scratch, as opposed to calling 618 the control.do_patman() function. 619 620 Args: 621 args (list of str): Arguments to pass (excluding argv[0]) 622 623 Return: 624 CommandResult: Result of execution 625 """ 626 all_args = [self._patman_pathname] + list(args) 627 return command.run_one(*all_args, capture=True, capture_stderr=True) 628 629 def test_full_help(self): 630 """Test getting full help""" 631 command.TEST_RESULT = None 632 result = self.run_patman('-H') 633 help_file = os.path.join(self._patman_dir, 'README.rst') 634 # Remove possible extraneous strings 635 extra = '::::::::::::::\n' + help_file + '\n::::::::::::::\n' 636 gothelp = result.stdout.replace(extra, '') 637 self.assertEqual(len(gothelp), os.path.getsize(help_file)) 638 self.assertEqual(0, len(result.stderr)) 639 self.assertEqual(0, result.return_code) 640 641 def test_help(self): 642 """Test getting help with commands and arguments""" 643 command.TEST_RESULT = None 644 result = self.run_patman('-h') 645 self.assertTrue(len(result.stdout) > 1000) 646 self.assertEqual(0, len(result.stderr)) 647 self.assertEqual(0, result.return_code) 648 649 @staticmethod 650 def _fake_patchwork(subpath): 651 """Fake Patchwork server for the function below 652 653 This handles accessing a series, providing a list consisting of a 654 single patch 655 656 Args: 657 subpath (str): URL subpath to use 658 """ 659 re_series = re.match(r'series/(\d*)/$', subpath) 660 if re_series: 661 series_num = re_series.group(1) 662 if series_num == '1234': 663 return {'patches': [ 664 {'id': '1', 'name': 'Some patch'}]} 665 raise ValueError('Fake Patchwork does not understand: %s' % subpath) 666 667 def test_status_mismatch(self): 668 """Test Patchwork patches not matching the series""" 669 pwork = patchwork.Patchwork.for_testing(self._fake_patchwork) 670 with terminal.capture() as (_, err): 671 loop = asyncio.get_event_loop() 672 _, patches = loop.run_until_complete(status.check_status(1234, 673 pwork)) 674 status.check_patch_count(0, len(patches)) 675 self.assertIn('Warning: Patchwork reports 1 patches, series has 0', 676 err.getvalue()) 677 678 def test_status_read_patch(self): 679 """Test handling a single patch in Patchwork""" 680 pwork = patchwork.Patchwork.for_testing(self._fake_patchwork) 681 loop = asyncio.get_event_loop() 682 _, patches = loop.run_until_complete(status.check_status(1234, pwork)) 683 self.assertEqual(1, len(patches)) 684 patch = patches[0] 685 self.assertEqual('1', patch.id) 686 self.assertEqual('Some patch', patch.raw_subject) 687 688 def test_parse_subject(self): 689 """Test parsing of the patch subject""" 690 patch = patchwork.Patch('1') 691 692 # Simple patch not in a series 693 patch.parse_subject('Testing') 694 self.assertEqual('Testing', patch.raw_subject) 695 self.assertEqual('Testing', patch.subject) 696 self.assertEqual(1, patch.seq) 697 self.assertEqual(1, patch.count) 698 self.assertEqual(None, patch.prefix) 699 self.assertEqual(None, patch.version) 700 701 # First patch in a series 702 patch.parse_subject('[1/2] Testing') 703 self.assertEqual('[1/2] Testing', patch.raw_subject) 704 self.assertEqual('Testing', patch.subject) 705 self.assertEqual(1, patch.seq) 706 self.assertEqual(2, patch.count) 707 self.assertEqual(None, patch.prefix) 708 self.assertEqual(None, patch.version) 709 710 # Second patch in a series 711 patch.parse_subject('[2/2] Testing') 712 self.assertEqual('Testing', patch.subject) 713 self.assertEqual(2, patch.seq) 714 self.assertEqual(2, patch.count) 715 self.assertEqual(None, patch.prefix) 716 self.assertEqual(None, patch.version) 717 718 # With PATCH prefix 719 patch.parse_subject('[PATCH,2/5] Testing') 720 self.assertEqual('Testing', patch.subject) 721 self.assertEqual(2, patch.seq) 722 self.assertEqual(5, patch.count) 723 self.assertEqual('PATCH', patch.prefix) 724 self.assertEqual(None, patch.version) 725 726 # RFC patch 727 patch.parse_subject('[RFC,3/7] Testing') 728 self.assertEqual('Testing', patch.subject) 729 self.assertEqual(3, patch.seq) 730 self.assertEqual(7, patch.count) 731 self.assertEqual('RFC', patch.prefix) 732 self.assertEqual(None, patch.version) 733 734 # Version patch 735 patch.parse_subject('[v2,3/7] Testing') 736 self.assertEqual('Testing', patch.subject) 737 self.assertEqual(3, patch.seq) 738 self.assertEqual(7, patch.count) 739 self.assertEqual(None, patch.prefix) 740 self.assertEqual('v2', patch.version) 741 742 # All fields 743 patch.parse_subject('[RESEND,v2,3/7] Testing') 744 self.assertEqual('Testing', patch.subject) 745 self.assertEqual(3, patch.seq) 746 self.assertEqual(7, patch.count) 747 self.assertEqual('RESEND', patch.prefix) 748 self.assertEqual('v2', patch.version) 749 750 # RFC only 751 patch.parse_subject('[RESEND] Testing') 752 self.assertEqual('Testing', patch.subject) 753 self.assertEqual(1, patch.seq) 754 self.assertEqual(1, patch.count) 755 self.assertEqual('RESEND', patch.prefix) 756 self.assertEqual(None, patch.version) 757 758 def test_compare_series(self): 759 """Test operation of compare_with_series()""" 760 commit1 = Commit('abcd') 761 commit1.subject = 'Subject 1' 762 commit2 = Commit('ef12') 763 commit2.subject = 'Subject 2' 764 commit3 = Commit('3456') 765 commit3.subject = 'Subject 2' 766 767 patch1 = patchwork.Patch('1') 768 patch1.subject = 'Subject 1' 769 patch2 = patchwork.Patch('2') 770 patch2.subject = 'Subject 2' 771 patch3 = patchwork.Patch('3') 772 patch3.subject = 'Subject 2' 773 774 series = Series() 775 series.commits = [commit1] 776 patches = [patch1] 777 patch_for_commit, commit_for_patch, warnings = ( 778 status.compare_with_series(series, patches)) 779 self.assertEqual(1, len(patch_for_commit)) 780 self.assertEqual(patch1, patch_for_commit[0]) 781 self.assertEqual(1, len(commit_for_patch)) 782 self.assertEqual(commit1, commit_for_patch[0]) 783 784 series.commits = [commit1] 785 patches = [patch1, patch2] 786 patch_for_commit, commit_for_patch, warnings = ( 787 status.compare_with_series(series, patches)) 788 self.assertEqual(1, len(patch_for_commit)) 789 self.assertEqual(patch1, patch_for_commit[0]) 790 self.assertEqual(1, len(commit_for_patch)) 791 self.assertEqual(commit1, commit_for_patch[0]) 792 self.assertEqual(["Cannot find commit for patch 2 ('Subject 2')"], 793 warnings) 794 795 series.commits = [commit1, commit2] 796 patches = [patch1] 797 patch_for_commit, commit_for_patch, warnings = ( 798 status.compare_with_series(series, patches)) 799 self.assertEqual(1, len(patch_for_commit)) 800 self.assertEqual(patch1, patch_for_commit[0]) 801 self.assertEqual(1, len(commit_for_patch)) 802 self.assertEqual(commit1, commit_for_patch[0]) 803 self.assertEqual(["Cannot find patch for commit 2 ('Subject 2')"], 804 warnings) 805 806 series.commits = [commit1, commit2, commit3] 807 patches = [patch1, patch2] 808 patch_for_commit, commit_for_patch, warnings = ( 809 status.compare_with_series(series, patches)) 810 self.assertEqual(2, len(patch_for_commit)) 811 self.assertEqual(patch1, patch_for_commit[0]) 812 self.assertEqual(patch2, patch_for_commit[1]) 813 self.assertEqual(1, len(commit_for_patch)) 814 self.assertEqual(commit1, commit_for_patch[0]) 815 self.assertEqual(["Cannot find patch for commit 3 ('Subject 2')", 816 "Multiple commits match patch 2 ('Subject 2'):\n" 817 ' Subject 2\n Subject 2'], 818 warnings) 819 820 series.commits = [commit1, commit2] 821 patches = [patch1, patch2, patch3] 822 patch_for_commit, commit_for_patch, warnings = ( 823 status.compare_with_series(series, patches)) 824 self.assertEqual(1, len(patch_for_commit)) 825 self.assertEqual(patch1, patch_for_commit[0]) 826 self.assertEqual(2, len(commit_for_patch)) 827 self.assertEqual(commit1, commit_for_patch[0]) 828 self.assertEqual(["Multiple patches match commit 2 ('Subject 2'):\n" 829 ' Subject 2\n Subject 2', 830 "Cannot find commit for patch 3 ('Subject 2')"], 831 warnings) 832 833 def _fake_patchwork2(self, subpath): 834 """Fake Patchwork server for the function below 835 836 This handles accessing series, patches and comments, providing the data 837 in self.patches to the caller 838 839 Args: 840 subpath (str): URL subpath to use 841 """ 842 re_series = re.match(r'series/(\d*)/$', subpath) 843 re_patch = re.match(r'patches/(\d*)/$', subpath) 844 re_comments = re.match(r'patches/(\d*)/comments/$', subpath) 845 if re_series: 846 series_num = re_series.group(1) 847 if series_num == '1234': 848 return {'patches': self.patches} 849 elif re_patch: 850 patch_num = int(re_patch.group(1)) 851 patch = self.patches[patch_num - 1] 852 return patch 853 elif re_comments: 854 patch_num = int(re_comments.group(1)) 855 patch = self.patches[patch_num - 1] 856 return patch.comments 857 raise ValueError('Fake Patchwork does not understand: %s' % subpath) 858 859 def test_find_new_responses(self): 860 """Test operation of find_new_responses()""" 861 commit1 = Commit('abcd') 862 commit1.subject = 'Subject 1' 863 commit2 = Commit('ef12') 864 commit2.subject = 'Subject 2' 865 866 patch1 = patchwork.Patch('1') 867 patch1.parse_subject('[1/2] Subject 1') 868 patch1.name = patch1.raw_subject 869 patch1.content = 'This is my patch content' 870 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe} 871 872 patch1.comments = [comment1a] 873 874 patch2 = patchwork.Patch('2') 875 patch2.parse_subject('[2/2] Subject 2') 876 patch2.name = patch2.raw_subject 877 patch2.content = 'Some other patch content' 878 comment2a = { 879 'content': 'Reviewed-by: %s\nTested-by: %s\n' % 880 (self.mary, self.leb)} 881 comment2b = {'content': 'Reviewed-by: %s' % self.fred} 882 patch2.comments = [comment2a, comment2b] 883 884 # This test works by setting up commits and patch for use by the fake 885 # Rest API function _fake_patchwork2(). It calls various functions in 886 # the status module after setting up tags in the commits, checking that 887 # things behaves as expected 888 self.commits = [commit1, commit2] 889 self.patches = [patch1, patch2] 890 891 # Check that the tags are picked up on the first patch 892 new_rtags, _ = status.process_reviews(patch1.content, patch1.comments, 893 commit1.rtags) 894 self.assertEqual(new_rtags, {'Reviewed-by': {self.joe}}) 895 896 # Now the second patch 897 new_rtags, _ = status.process_reviews(patch2.content, patch2.comments, 898 commit2.rtags) 899 self.assertEqual(new_rtags, { 900 'Reviewed-by': {self.mary, self.fred}, 901 'Tested-by': {self.leb}}) 902 903 # Now add some tags to the commit, which means they should not appear as 904 # 'new' tags when scanning comments 905 commit1.rtags = {'Reviewed-by': {self.joe}} 906 new_rtags, _ = status.process_reviews(patch1.content, patch1.comments, 907 commit1.rtags) 908 self.assertEqual(new_rtags, {}) 909 910 # For the second commit, add Ed and Fred, so only Mary should be left 911 commit2.rtags = { 912 'Tested-by': {self.leb}, 913 'Reviewed-by': {self.fred}} 914 new_rtags, _ = status.process_reviews(patch2.content, patch2.comments, 915 commit2.rtags) 916 self.assertEqual(new_rtags, {'Reviewed-by': {self.mary}}) 917 918 # Check that the output patches expectations: 919 # 1 Subject 1 920 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz> 921 # 2 Subject 2 922 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org> 923 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net> 924 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz> 925 # 1 new response available in patchwork 926 927 series = Series() 928 series.commits = [commit1, commit2] 929 terminal.set_print_test_mode() 930 pwork = patchwork.Patchwork.for_testing(self._fake_patchwork2) 931 status.check_and_show_status(series, '1234', None, None, False, False, 932 False, pwork) 933 itr = iter(terminal.get_print_test_lines()) 934 col = terminal.Color() 935 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.YELLOW), 936 next(itr)) 937 self.assertEqual( 938 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False, 939 bright=False), 940 next(itr)) 941 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE, bright=False), 942 next(itr)) 943 944 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.YELLOW), 945 next(itr)) 946 self.assertEqual( 947 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False, 948 bright=False), 949 next(itr)) 950 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE, 951 bright=False), next(itr)) 952 self.assertEqual( 953 terminal.PrintLine(' Tested-by: ', col.GREEN, newline=False, 954 bright=False), 955 next(itr)) 956 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE, bright=False), 957 next(itr)) 958 self.assertEqual( 959 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False), 960 next(itr)) 961 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE), 962 next(itr)) 963 self.assertEqual(terminal.PrintLine( 964 '1 new response available in patchwork (use -d to write them to a new branch)', 965 None), next(itr)) 966 967 def _fake_patchwork3(self, subpath): 968 """Fake Patchwork server for the function below 969 970 This handles accessing series, patches and comments, providing the data 971 in self.patches to the caller 972 973 Args: 974 subpath (str): URL subpath to use 975 """ 976 re_series = re.match(r'series/(\d*)/$', subpath) 977 re_patch = re.match(r'patches/(\d*)/$', subpath) 978 re_comments = re.match(r'patches/(\d*)/comments/$', subpath) 979 if re_series: 980 series_num = re_series.group(1) 981 if series_num == '1234': 982 return {'patches': self.patches} 983 elif re_patch: 984 patch_num = int(re_patch.group(1)) 985 patch = self.patches[patch_num - 1] 986 return patch 987 elif re_comments: 988 patch_num = int(re_comments.group(1)) 989 patch = self.patches[patch_num - 1] 990 return patch.comments 991 raise ValueError('Fake Patchwork does not understand: %s' % subpath) 992 993 def test_create_branch(self): 994 """Test operation of create_branch()""" 995 repo = self.make_git_tree() 996 branch = 'first' 997 dest_branch = 'first2' 998 count = 2 999 gitdir = self.gitdir 1000 1001 # Set up the test git tree. We use branch 'first' which has two commits 1002 # in it 1003 series = patchstream.get_metadata_for_list(branch, gitdir, count) 1004 self.assertEqual(2, len(series.commits)) 1005 1006 patch1 = patchwork.Patch('1') 1007 patch1.parse_subject('[1/2] %s' % series.commits[0].subject) 1008 patch1.name = patch1.raw_subject 1009 patch1.content = 'This is my patch content' 1010 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe} 1011 1012 patch1.comments = [comment1a] 1013 1014 patch2 = patchwork.Patch('2') 1015 patch2.parse_subject('[2/2] %s' % series.commits[1].subject) 1016 patch2.name = patch2.raw_subject 1017 patch2.content = 'Some other patch content' 1018 comment2a = { 1019 'content': 'Reviewed-by: %s\nTested-by: %s\n' % 1020 (self.mary, self.leb)} 1021 comment2b = { 1022 'content': 'Reviewed-by: %s' % self.fred} 1023 patch2.comments = [comment2a, comment2b] 1024 1025 # This test works by setting up patches for use by the fake Rest API 1026 # function _fake_patchwork3(). The fake patch comments above should 1027 # result in new review tags that are collected and added to the commits 1028 # created in the destination branch. 1029 self.patches = [patch1, patch2] 1030 count = 2 1031 1032 # Expected output: 1033 # 1 i2c: I2C things 1034 # + Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz> 1035 # 2 spi: SPI fixes 1036 # + Reviewed-by: Fred Bloggs <f.bloggs@napier.net> 1037 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz> 1038 # + Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org> 1039 # 4 new responses available in patchwork 1040 # 4 responses added from patchwork into new branch 'first2' 1041 # <unittest.result.TestResult run=8 errors=0 failures=0> 1042 1043 terminal.set_print_test_mode() 1044 pwork = patchwork.Patchwork.for_testing(self._fake_patchwork3) 1045 status.check_and_show_status( 1046 series, '1234', branch, dest_branch, False, False, False, pwork, 1047 repo) 1048 lines = terminal.get_print_test_lines() 1049 self.assertEqual(12, len(lines)) 1050 self.assertEqual( 1051 "4 responses added from patchwork into new branch 'first2'", 1052 lines[11].text) 1053 1054 # Check that the destination branch has the new tags 1055 new_series = patchstream.get_metadata_for_list(dest_branch, gitdir, 1056 count) 1057 self.assertEqual( 1058 {'Reviewed-by': {self.joe}}, 1059 new_series.commits[0].rtags) 1060 self.assertEqual( 1061 {'Tested-by': {self.leb}, 1062 'Reviewed-by': {self.fred, self.mary}}, 1063 new_series.commits[1].rtags) 1064 1065 # Now check the actual test of the first commit message. We expect to 1066 # see the new tags immediately below the old ones. 1067 stdout = patchstream.get_list(dest_branch, count=count, git_dir=gitdir) 1068 itr = iter([line.strip() for line in stdout.splitlines() 1069 if '-by:' in line]) 1070 1071 # First patch should have the review tag 1072 self.assertEqual('Reviewed-by: %s' % self.joe, next(itr)) 1073 1074 # Second patch should have the sign-off then the tested-by and two 1075 # reviewed-by tags 1076 self.assertEqual('Signed-off-by: %s' % self.leb, next(itr)) 1077 self.assertEqual('Reviewed-by: %s' % self.fred, next(itr)) 1078 self.assertEqual('Reviewed-by: %s' % self.mary, next(itr)) 1079 self.assertEqual('Tested-by: %s' % self.leb, next(itr)) 1080 1081 def test_parse_snippets(self): 1082 """Test parsing of review snippets""" 1083 text = '''Hi Fred, 1084 1085This is a comment from someone. 1086 1087Something else 1088 1089On some recent date, Fred wrote: 1090> This is why I wrote the patch 1091> so here it is 1092 1093Now a comment about the commit message 1094A little more to say 1095 1096Even more 1097 1098> diff --git a/file.c b/file.c 1099> Some more code 1100> Code line 2 1101> Code line 3 1102> Code line 4 1103> Code line 5 1104> Code line 6 1105> Code line 7 1106> Code line 8 1107> Code line 9 1108 1109And another comment 1110 1111> @@ -153,8 +143,13 @@ def check_patch(fname, show_types=False): 1112> further down on the file 1113> and more code 1114> +Addition here 1115> +Another addition here 1116> codey 1117> more codey 1118 1119and another thing in same file 1120 1121> @@ -253,8 +243,13 @@ 1122> with no function context 1123 1124one more thing 1125 1126> diff --git a/tools/patman/main.py b/tools/patman/main.py 1127> +line of code 1128now a very long comment in a different file 1129line2 1130line3 1131line4 1132line5 1133line6 1134line7 1135line8 1136''' 1137 pstrm = PatchStream.process_text(text, True) 1138 self.assertEqual([], pstrm.commit.warn) 1139 1140 # We expect to the filename and up to 5 lines of code context before 1141 # each comment. The 'On xxx wrote:' bit should be removed. 1142 self.assertEqual( 1143 [['Hi Fred,', 1144 'This is a comment from someone.', 1145 'Something else'], 1146 ['> This is why I wrote the patch', 1147 '> so here it is', 1148 'Now a comment about the commit message', 1149 'A little more to say', 'Even more'], 1150 ['> File: file.c', '> Code line 5', '> Code line 6', 1151 '> Code line 7', '> Code line 8', '> Code line 9', 1152 'And another comment'], 1153 ['> File: file.c', 1154 '> Line: 153 / 143: def check_patch(fname, show_types=False):', 1155 '> and more code', '> +Addition here', 1156 '> +Another addition here', '> codey', '> more codey', 1157 'and another thing in same file'], 1158 ['> File: file.c', '> Line: 253 / 243', 1159 '> with no function context', 'one more thing'], 1160 ['> File: tools/patman/main.py', '> +line of code', 1161 'now a very long comment in a different file', 1162 'line2', 'line3', 'line4', 'line5', 'line6', 'line7', 'line8']], 1163 pstrm.snippets) 1164 1165 def test_review_snippets(self): 1166 """Test showing of review snippets""" 1167 def _to_submitter(who): 1168 m_who = re.match('(.*) <(.*)>', who) 1169 return { 1170 'name': m_who.group(1), 1171 'email': m_who.group(2) 1172 } 1173 1174 commit1 = Commit('abcd') 1175 commit1.subject = 'Subject 1' 1176 commit2 = Commit('ef12') 1177 commit2.subject = 'Subject 2' 1178 1179 patch1 = patchwork.Patch('1') 1180 patch1.parse_subject('[1/2] Subject 1') 1181 patch1.name = patch1.raw_subject 1182 patch1.content = 'This is my patch content' 1183 comment1a = {'submitter': _to_submitter(self.joe), 1184 'content': '''Hi Fred, 1185 1186On some date Fred wrote: 1187 1188> diff --git a/file.c b/file.c 1189> Some code 1190> and more code 1191 1192Here is my comment above the above... 1193 1194 1195Reviewed-by: %s 1196''' % self.joe} 1197 1198 patch1.comments = [comment1a] 1199 1200 patch2 = patchwork.Patch('2') 1201 patch2.parse_subject('[2/2] Subject 2') 1202 patch2.name = patch2.raw_subject 1203 patch2.content = 'Some other patch content' 1204 comment2a = { 1205 'content': 'Reviewed-by: %s\nTested-by: %s\n' % 1206 (self.mary, self.leb)} 1207 comment2b = {'submitter': _to_submitter(self.fred), 1208 'content': '''Hi Fred, 1209 1210On some date Fred wrote: 1211 1212> diff --git a/tools/patman/commit.py b/tools/patman/commit.py 1213> @@ -41,6 +41,9 @@ class Commit: 1214> self.rtags = collections.defaultdict(set) 1215> self.warn = [] 1216> 1217> + def __str__(self): 1218> + return self.subject 1219> + 1220> def add_change(self, version, info): 1221> """Add a new change line to the change list for a version. 1222> 1223A comment 1224 1225Reviewed-by: %s 1226''' % self.fred} 1227 patch2.comments = [comment2a, comment2b] 1228 1229 # This test works by setting up commits and patch for use by the fake 1230 # Rest API function _fake_patchwork2(). It calls various functions in 1231 # the status module after setting up tags in the commits, checking that 1232 # things behaves as expected 1233 self.commits = [commit1, commit2] 1234 self.patches = [patch1, patch2] 1235 1236 # Check that the output patches expectations: 1237 # 1 Subject 1 1238 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz> 1239 # 2 Subject 2 1240 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org> 1241 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net> 1242 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz> 1243 # 1 new response available in patchwork 1244 1245 series = Series() 1246 series.commits = [commit1, commit2] 1247 terminal.set_print_test_mode() 1248 pwork = patchwork.Patchwork.for_testing(self._fake_patchwork2) 1249 status.check_and_show_status( 1250 series, '1234', None, None, False, True, False, pwork) 1251 itr = iter(terminal.get_print_test_lines()) 1252 col = terminal.Color() 1253 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.YELLOW), 1254 next(itr)) 1255 self.assertEqual( 1256 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False), 1257 next(itr)) 1258 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE), next(itr)) 1259 1260 self.assertEqual(terminal.PrintLine('Review: %s' % self.joe, col.RED), 1261 next(itr)) 1262 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(itr)) 1263 self.assertEqual(terminal.PrintLine('', None), next(itr)) 1264 self.assertEqual(terminal.PrintLine(' > File: file.c', col.MAGENTA), 1265 next(itr)) 1266 self.assertEqual(terminal.PrintLine(' > Some code', col.MAGENTA), 1267 next(itr)) 1268 self.assertEqual(terminal.PrintLine(' > and more code', 1269 col.MAGENTA), 1270 next(itr)) 1271 self.assertEqual(terminal.PrintLine( 1272 ' Here is my comment above the above...', None), next(itr)) 1273 self.assertEqual(terminal.PrintLine('', None), next(itr)) 1274 1275 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.YELLOW), 1276 next(itr)) 1277 self.assertEqual( 1278 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False), 1279 next(itr)) 1280 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE), 1281 next(itr)) 1282 self.assertEqual( 1283 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False), 1284 next(itr)) 1285 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE), 1286 next(itr)) 1287 self.assertEqual( 1288 terminal.PrintLine(' + Tested-by: ', col.GREEN, newline=False), 1289 next(itr)) 1290 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE), 1291 next(itr)) 1292 1293 self.assertEqual(terminal.PrintLine('Review: %s' % self.fred, col.RED), 1294 next(itr)) 1295 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(itr)) 1296 self.assertEqual(terminal.PrintLine('', None), next(itr)) 1297 self.assertEqual(terminal.PrintLine( 1298 ' > File: tools/patman/commit.py', col.MAGENTA), next(itr)) 1299 self.assertEqual(terminal.PrintLine( 1300 ' > Line: 41 / 41: class Commit:', col.MAGENTA), next(itr)) 1301 self.assertEqual(terminal.PrintLine( 1302 ' > + return self.subject', col.MAGENTA), next(itr)) 1303 self.assertEqual(terminal.PrintLine( 1304 ' > +', col.MAGENTA), next(itr)) 1305 self.assertEqual( 1306 terminal.PrintLine( 1307 ' > def add_change(self, version, info):', 1308 col.MAGENTA), 1309 next(itr)) 1310 self.assertEqual(terminal.PrintLine( 1311 ' > """Add a new change line to the change list for a version.', 1312 col.MAGENTA), next(itr)) 1313 self.assertEqual(terminal.PrintLine( 1314 ' >', col.MAGENTA), next(itr)) 1315 self.assertEqual(terminal.PrintLine( 1316 ' A comment', None), next(itr)) 1317 self.assertEqual(terminal.PrintLine('', None), next(itr)) 1318 1319 self.assertEqual(terminal.PrintLine( 1320 '4 new responses available in patchwork (use -d to write them to a new branch)', 1321 None), next(itr)) 1322 1323 def test_insert_tags(self): 1324 """Test inserting of review tags""" 1325 msg = '''first line 1326second line.''' 1327 tags = [ 1328 'Reviewed-by: Bin Meng <bmeng.cn@gmail.com>', 1329 'Tested-by: Bin Meng <bmeng.cn@gmail.com>' 1330 ] 1331 signoff = 'Signed-off-by: Simon Glass <sjg@chromium.com>' 1332 tag_str = '\n'.join(tags) 1333 1334 new_msg = patchstream.insert_tags(msg, tags) 1335 self.assertEqual(msg + '\n\n' + tag_str, new_msg) 1336 1337 new_msg = patchstream.insert_tags(msg + '\n', tags) 1338 self.assertEqual(msg + '\n\n' + tag_str, new_msg) 1339 1340 msg += '\n\n' + signoff 1341 new_msg = patchstream.insert_tags(msg, tags) 1342 self.assertEqual(msg + '\n' + tag_str, new_msg) 1343