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