1 # -*- coding: utf-8 -*-
2 # SPDX-License-Identifier: GPL-2.0+
4 # Copyright 2017 Google, Inc
7 """Functional tests for checking that patman behaves correctly"""
17 from patman.commit import Commit
18 from patman import control
19 from patman import gitutil
20 from patman import patchstream
21 from patman.patchstream import PatchStream
22 from patman.series import Series
23 from patman import settings
24 from patman import terminal
25 from patman import tools
26 from patman.test_util import capture_sys_output
29 from patman import status
31 class TestFunctional(unittest.TestCase):
32 """Functional tests for checking that patman behaves correctly"""
33 leb = (b'Lord Edmund Blackadd\xc3\xabr <weasel@blackadder.org>'.
35 fred = 'Fred Bloggs <f.bloggs@napier.net>'
36 joe = 'Joe Bloggs <joe@napierwallies.co.nz>'
37 mary = 'Mary Bloggs <mary@napierwallies.co.nz>'
42 self.tmpdir = tempfile.mkdtemp(prefix='patman.')
43 self.gitdir = os.path.join(self.tmpdir, 'git')
47 shutil.rmtree(self.tmpdir)
48 terminal.SetPrintTestMode(False)
52 """Get the path to a test file
55 fname (str): Filename to obtain
58 str: Full path to file in the test directory
60 return os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
64 def _get_text(cls, fname):
65 """Read a file as text
68 fname (str): Filename to read
73 return open(cls._get_path(fname), encoding='utf-8').read()
76 def _get_patch_name(cls, subject):
77 """Get the filename of a patch given its subject
80 subject (str): Patch subject
83 str: Filename for that patch
85 fname = re.sub('[ :]', '-', subject)
86 return fname.replace('--', '-')
88 def _create_patches_for_test(self, series):
89 """Create patch files for use by tests
91 This copies patch files from the test directory as needed by the series
94 series (Series): Series containing commits to convert
98 str: Cover-letter filename, or None if none
99 fname_list: list of str, each a patch filename
103 for i, commit in enumerate(series.commits):
104 clean_subject = self._get_patch_name(commit.subject)
105 src_fname = '%04d-%s.patch' % (i + 1, clean_subject[:52])
106 fname = os.path.join(self.tmpdir, src_fname)
107 shutil.copy(self._get_path(src_fname), fname)
108 fname_list.append(fname)
109 if series.get('cover'):
110 src_fname = '0000-cover-letter.patch'
111 cover_fname = os.path.join(self.tmpdir, src_fname)
112 fname = os.path.join(self.tmpdir, src_fname)
113 shutil.copy(self._get_path(src_fname), fname)
115 return cover_fname, fname_list
118 """Tests the basic flow of patman
120 This creates a series from some hard-coded patches build from a simple
121 tree with the following metadata in the top commit:
125 Series-postfix: some-branch
126 Series-cc: Stefan Brüns <stefan.bruens@rwth-aachen.de>
127 Cover-letter-cc: Lord Mëlchett <clergy@palace.gov>
130 Series-process-log: sort, uniq
138 - Changes only for this commit
141 - Some notes for the cover letter
144 test: A test patch series
145 This is a test of how the cover
150 and this in the first commit:
153 - second revision change
158 from the first commit
166 with the following commands:
168 git log -n2 --reverse >/path/to/tools/patman/test/test01.txt
169 git format-patch --subject-prefix RFC --cover-letter HEAD~2
170 mv 00* /path/to/tools/patman/test
172 It checks these aspects:
173 - git log can be processed by patchstream
174 - emailing patches uses the correct command
175 - CC file has information on each commit
176 - cover letter has the expected text and subject
177 - each patch has the correct subject
178 - dry-run information prints out correctly
179 - unicode is handled correctly
180 - Series-to, Series-cc, Series-prefix, Series-postfix, Cover-letter
181 - Cover-letter-cc, Series-version, Series-changes, Series-notes
185 ignore_bad_tags = False
186 stefan = b'Stefan Br\xc3\xbcns <stefan.bruens@rwth-aachen.de>'.decode('utf-8')
187 rick = 'Richard III <richard@palace.gov>'
188 mel = b'Lord M\xc3\xablchett <clergy@palace.gov>'.decode('utf-8')
189 add_maintainers = [stefan, rick]
195 'u-boot': ['u-boot@lists.denx.de'],
200 text = self._get_text('test01.txt')
201 series = patchstream.get_metadata_for_test(text)
202 cover_fname, args = self._create_patches_for_test(series)
203 with capture_sys_output() as out:
204 patchstream.fix_patches(series, args)
205 if cover_fname and series.get('cover'):
206 patchstream.insert_cover_letter(cover_fname, series, count)
208 cc_file = series.MakeCcFile(process_tags, cover_fname,
209 not ignore_bad_tags, add_maintainers,
211 cmd = gitutil.EmailPatches(
212 series, cover_fname, args, dry_run, not ignore_bad_tags,
213 cc_file, in_reply_to=in_reply_to, thread=None)
214 series.ShowActions(args, cmd, process_tags)
215 cc_lines = open(cc_file, encoding='utf-8').read().splitlines()
218 lines = iter(out[0].getvalue().splitlines())
219 self.assertEqual('Cleaned %s patches' % len(series.commits),
221 self.assertEqual('Change log missing for v2', next(lines))
222 self.assertEqual('Change log missing for v3', next(lines))
223 self.assertEqual('Change log for unknown version v4', next(lines))
224 self.assertEqual("Alias 'pci' not found", next(lines))
225 self.assertIn('Dry run', next(lines))
226 self.assertEqual('', next(lines))
227 self.assertIn('Send a total of %d patches' % count, next(lines))
229 for i, commit in enumerate(series.commits):
230 self.assertEqual(' %s' % args[i], prev)
233 if 'Cc:' not in prev:
235 self.assertEqual('To: u-boot@lists.denx.de', prev)
236 self.assertEqual('Cc: %s' % stefan, next(lines))
237 self.assertEqual('Version: 3', next(lines))
238 self.assertEqual('Prefix:\t RFC', next(lines))
239 self.assertEqual('Postfix:\t some-branch', next(lines))
240 self.assertEqual('Cover: 4 lines', next(lines))
241 self.assertEqual(' Cc: %s' % self.fred, next(lines))
242 self.assertEqual(' Cc: %s' % self.leb,
244 self.assertEqual(' Cc: %s' % mel, next(lines))
245 self.assertEqual(' Cc: %s' % rick, next(lines))
246 expected = ('Git command: git send-email --annotate '
247 '--in-reply-to="%s" --to "u-boot@lists.denx.de" '
248 '--cc "%s" --cc-cmd "%s send --cc-cmd %s" %s %s'
249 % (in_reply_to, stefan, sys.argv[0], cc_file, cover_fname,
251 self.assertEqual(expected, next(lines))
253 self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)), cc_lines[0])
255 '%s %s\0%s\0%s\0%s' % (args[1], self.fred, self.leb, rick, stefan),
259 This is a test of how the cover
265 from the first commit
272 - Some notes for the cover letter
275 pci: Correct cast for sandbox
276 fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
280 lib/efi_loader/efi_memory.c | 1 +
282 4 files changed, 6 insertions(+), 2 deletions(-)
288 lines = open(cover_fname, encoding='utf-8').read().splitlines()
290 'Subject: [RFC PATCH some-branch v3 0/2] test: A test patch series',
292 self.assertEqual(expected.splitlines(), lines[7:])
294 for i, fname in enumerate(args):
295 lines = open(fname, encoding='utf-8').read().splitlines()
296 subject = [line for line in lines if line.startswith('Subject')]
297 self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count),
300 # Check that we got our commit notes
310 (no changes since v2)
313 - second revision change'''
325 - Changes only for this commit'''
328 expected = expected.splitlines()
329 self.assertEqual(expected, lines[start:(start+len(expected))])
331 def make_commit_with_file(self, subject, body, fname, text):
332 """Create a file and add it to the git repo with a new commit
335 subject (str): Subject for the commit
336 body (str): Body text of the commit
337 fname (str): Filename of file to create
338 text (str): Text to put into the file
340 path = os.path.join(self.gitdir, fname)
341 tools.write_file(path, text, binary=False)
342 index = self.repo.index
344 author = pygit2.Signature('Test user', 'test@email.com')
346 tree = index.write_tree()
347 message = subject + '\n' + body
348 self.repo.create_commit('HEAD', author, committer, message, tree,
349 [self.repo.head.target])
351 def make_git_tree(self):
352 """Make a simple git tree suitable for testing
354 It has three branches:
355 'base' has two commits: PCI, main
356 'first' has base as upstream and two more commits: I2C, SPI
357 'second' has base as upstream and three more: video, serial, bootm
360 pygit2.Repository: repository
362 repo = pygit2.init_repository(self.gitdir)
364 new_tree = repo.TreeBuilder().write()
366 author = pygit2.Signature('Test user', 'test@email.com')
368 _ = repo.create_commit('HEAD', author, committer, 'Created master',
371 self.make_commit_with_file('Initial commit', '''
374 ''', 'README', '''This is the README file
375 describing this project
376 in very little detail''')
378 self.make_commit_with_file('pci: PCI implementation', '''
379 Here is a basic PCI implementation
381 ''', 'pci.c', '''This is a file
383 and some more things''')
384 self.make_commit_with_file('main: Main program', '''
385 Hello here is the second commit.
386 ''', 'main.c', '''This is the main file
387 there is very little here
388 but we can always add more later
392 Series-cc: Barry Crump <bcrump@whataroa.nz>
394 base_target = repo.revparse_single('HEAD')
395 self.make_commit_with_file('i2c: I2C things', '''
396 This has some stuff to do with I2C
397 ''', 'i2c.c', '''And this is the file contents
398 with some I2C-related things in it''')
399 self.make_commit_with_file('spi: SPI fixes', '''
408 This is the cover letter for the series
411 ''' % self.leb, 'spi.c', '''Some fixes for SPI in this
412 file to make SPI work
413 better than before''')
414 first_target = repo.revparse_single('HEAD')
416 target = repo.revparse_single('HEAD~2')
417 repo.reset(target.oid, pygit2.GIT_CHECKOUT_FORCE)
418 self.make_commit_with_file('video: Some video improvements', '''
419 Fix up the video so that
420 it looks more purple. Purple is
422 ''', 'video.c', '''More purple here
425 Could not be any more purple''')
426 self.make_commit_with_file('serial: Add a serial driver', '''
427 Here is the serial driver
432 This series implements support
433 for my glorious board.
436 ''', 'serial.c', '''The code for the
437 serial driver is here''')
438 self.make_commit_with_file('bootm: Make it boot', '''
439 This makes my board boot
440 with a fix to the bootm
442 ''', 'bootm.c', '''Fix up the bootm
443 command to make the code as
444 complicated as possible''')
445 second_target = repo.revparse_single('HEAD')
447 repo.branches.local.create('first', first_target)
448 repo.config.set_multivar('branch.first.remote', '', '.')
449 repo.config.set_multivar('branch.first.merge', '', 'refs/heads/base')
451 repo.branches.local.create('second', second_target)
452 repo.config.set_multivar('branch.second.remote', '', '.')
453 repo.config.set_multivar('branch.second.merge', '', 'refs/heads/base')
455 repo.branches.local.create('base', base_target)
458 def testBranch(self):
459 """Test creating patches from a branch"""
460 repo = self.make_git_tree()
461 target = repo.lookup_reference('refs/heads/first')
462 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
465 orig_dir = os.getcwd()
466 os.chdir(self.gitdir)
468 # Check that it can detect the current branch
469 self.assertEqual(2, gitutil.CountCommitsToBranch(None))
470 col = terminal.Color()
471 with capture_sys_output() as _:
472 _, cover_fname, patch_files = control.prepare_patches(
473 col, branch=None, count=-1, start=0, end=0,
474 ignore_binary=False, signoff=True)
475 self.assertIsNone(cover_fname)
476 self.assertEqual(2, len(patch_files))
478 # Check that it can detect a different branch
479 self.assertEqual(3, gitutil.CountCommitsToBranch('second'))
480 with capture_sys_output() as _:
481 _, cover_fname, patch_files = control.prepare_patches(
482 col, branch='second', count=-1, start=0, end=0,
483 ignore_binary=False, signoff=True)
484 self.assertIsNotNone(cover_fname)
485 self.assertEqual(3, len(patch_files))
487 # Check that it can skip patches at the end
488 with capture_sys_output() as _:
489 _, cover_fname, patch_files = control.prepare_patches(
490 col, branch='second', count=-1, start=0, end=1,
491 ignore_binary=False, signoff=True)
492 self.assertIsNotNone(cover_fname)
493 self.assertEqual(2, len(patch_files))
498 """Test collection of tags in a patchstream"""
499 text = '''This is a patch
501 Signed-off-by: Terminator
505 ''' % (self.joe, self.mary, self.leb)
506 pstrm = PatchStream.process_text(text)
507 self.assertEqual(pstrm.commit.rtags, {
508 'Reviewed-by': {self.joe, self.mary},
509 'Tested-by': {self.leb}})
511 def testInvalidTag(self):
512 """Test invalid tag in a patchstream"""
513 text = '''This is a patch
517 with self.assertRaises(ValueError) as exc:
518 pstrm = PatchStream.process_text(text)
519 self.assertEqual("Line 3: Invalid tag = 'Serie-version: 2'",
522 def testMissingEnd(self):
523 """Test a missing END tag"""
524 text = '''This is a patch
528 missing END after this line
531 pstrm = PatchStream.process_text(text)
532 self.assertEqual(["Missing 'END' in section 'cover'"],
535 def testMissingBlankLine(self):
536 """Test a missing blank line after a tag"""
537 text = '''This is a patch
540 - First line of changes
541 - Missing blank line after this line
544 pstrm = PatchStream.process_text(text)
545 self.assertEqual(["Missing 'blank line' in section 'Series-changes'"],
548 def testInvalidCommitTag(self):
549 """Test an invalid Commit-xxx tag"""
550 text = '''This is a patch
554 pstrm = PatchStream.process_text(text)
555 self.assertEqual(["Line 3: Ignoring Commit-fred"], pstrm.commit.warn)
557 def testSelfTest(self):
558 """Test a tested by tag by this user"""
559 test_line = 'Tested-by: %s@napier.com' % os.getenv('USER')
560 text = '''This is a patch
564 pstrm = PatchStream.process_text(text)
565 self.assertEqual(["Ignoring '%s'" % test_line], pstrm.commit.warn)
567 def testSpaceBeforeTab(self):
568 """Test a space before a tab"""
569 text = '''This is a patch
573 pstrm = PatchStream.process_text(text)
574 self.assertEqual(["Line 3/0 has space before tab"], pstrm.commit.warn)
576 def testLinesAfterTest(self):
577 """Test detecting lines after TEST= line"""
578 text = '''This is a patch
584 pstrm = PatchStream.process_text(text)
585 self.assertEqual(["Found 2 lines after TEST="], pstrm.commit.warn)
587 def testBlankLineAtEnd(self):
588 """Test detecting a blank line at the end of a file"""
589 text = '''This is a patch
591 diff --git a/lib/fdtdec.c b/lib/fdtdec.c
592 index c072e54..942244f 100644
595 @@ -1200,7 +1200,8 @@ int fdtdec_setup_mem_size_base(void)
598 gd->ram_size = (phys_size_t)(res.end - res.start + 1);
599 - debug("%s: Initial DRAM size %llx\n", __func__, (u64)gd->ram_size);
600 + debug("%s: Initial DRAM size %llx\n", __func__,
601 + (unsigned long long)gd->ram_size);
603 diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c
609 pstrm = PatchStream.process_text(text)
611 ["Found possible blank line(s) at end of file 'lib/fdtdec.c'"],
614 def testNoUpstream(self):
615 """Test CountCommitsToBranch when there is no upstream"""
616 repo = self.make_git_tree()
617 target = repo.lookup_reference('refs/heads/base')
618 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
620 # Check that it can detect the current branch
622 orig_dir = os.getcwd()
623 os.chdir(self.gitdir)
624 with self.assertRaises(ValueError) as exc:
625 gitutil.CountCommitsToBranch(None)
627 "Failed to determine upstream: fatal: no upstream configured for branch 'base'",
633 def _fake_patchwork(url, subpath):
634 """Fake Patchwork server for the function below
636 This handles accessing a series, providing a list consisting of a
640 url (str): URL of patchwork server
641 subpath (str): URL subpath to use
643 re_series = re.match(r'series/(\d*)/$', subpath)
645 series_num = re_series.group(1)
646 if series_num == '1234':
648 {'id': '1', 'name': 'Some patch'}]}
649 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
651 def testStatusMismatch(self):
652 """Test Patchwork patches not matching the series"""
655 with capture_sys_output() as (_, err):
656 status.collect_patches(series, 1234, None, self._fake_patchwork)
657 self.assertIn('Warning: Patchwork reports 1 patches, series has 0',
660 def testStatusReadPatch(self):
661 """Test handling a single patch in Patchwork"""
663 series.commits = [Commit('abcd')]
665 patches = status.collect_patches(series, 1234, None,
666 self._fake_patchwork)
667 self.assertEqual(1, len(patches))
669 self.assertEqual('1', patch.id)
670 self.assertEqual('Some patch', patch.raw_subject)
672 def testParseSubject(self):
673 """Test parsing of the patch subject"""
674 patch = status.Patch('1')
676 # Simple patch not in a series
677 patch.parse_subject('Testing')
678 self.assertEqual('Testing', patch.raw_subject)
679 self.assertEqual('Testing', patch.subject)
680 self.assertEqual(1, patch.seq)
681 self.assertEqual(1, patch.count)
682 self.assertEqual(None, patch.prefix)
683 self.assertEqual(None, patch.version)
685 # First patch in a series
686 patch.parse_subject('[1/2] Testing')
687 self.assertEqual('[1/2] Testing', patch.raw_subject)
688 self.assertEqual('Testing', patch.subject)
689 self.assertEqual(1, patch.seq)
690 self.assertEqual(2, patch.count)
691 self.assertEqual(None, patch.prefix)
692 self.assertEqual(None, patch.version)
694 # Second patch in a series
695 patch.parse_subject('[2/2] Testing')
696 self.assertEqual('Testing', patch.subject)
697 self.assertEqual(2, patch.seq)
698 self.assertEqual(2, patch.count)
699 self.assertEqual(None, patch.prefix)
700 self.assertEqual(None, patch.version)
703 patch.parse_subject('[RFC,3/7] Testing')
704 self.assertEqual('Testing', patch.subject)
705 self.assertEqual(3, patch.seq)
706 self.assertEqual(7, patch.count)
707 self.assertEqual('RFC', patch.prefix)
708 self.assertEqual(None, patch.version)
711 patch.parse_subject('[v2,3/7] Testing')
712 self.assertEqual('Testing', patch.subject)
713 self.assertEqual(3, patch.seq)
714 self.assertEqual(7, patch.count)
715 self.assertEqual(None, patch.prefix)
716 self.assertEqual('v2', patch.version)
719 patch.parse_subject('[RESEND,v2,3/7] Testing')
720 self.assertEqual('Testing', patch.subject)
721 self.assertEqual(3, patch.seq)
722 self.assertEqual(7, patch.count)
723 self.assertEqual('RESEND', patch.prefix)
724 self.assertEqual('v2', patch.version)
727 patch.parse_subject('[RESEND] Testing')
728 self.assertEqual('Testing', patch.subject)
729 self.assertEqual(1, patch.seq)
730 self.assertEqual(1, patch.count)
731 self.assertEqual('RESEND', patch.prefix)
732 self.assertEqual(None, patch.version)
734 def testCompareSeries(self):
735 """Test operation of compare_with_series()"""
736 commit1 = Commit('abcd')
737 commit1.subject = 'Subject 1'
738 commit2 = Commit('ef12')
739 commit2.subject = 'Subject 2'
740 commit3 = Commit('3456')
741 commit3.subject = 'Subject 2'
743 patch1 = status.Patch('1')
744 patch1.subject = 'Subject 1'
745 patch2 = status.Patch('2')
746 patch2.subject = 'Subject 2'
747 patch3 = status.Patch('3')
748 patch3.subject = 'Subject 2'
751 series.commits = [commit1]
753 patch_for_commit, commit_for_patch, warnings = (
754 status.compare_with_series(series, patches))
755 self.assertEqual(1, len(patch_for_commit))
756 self.assertEqual(patch1, patch_for_commit[0])
757 self.assertEqual(1, len(commit_for_patch))
758 self.assertEqual(commit1, commit_for_patch[0])
760 series.commits = [commit1]
761 patches = [patch1, patch2]
762 patch_for_commit, commit_for_patch, warnings = (
763 status.compare_with_series(series, patches))
764 self.assertEqual(1, len(patch_for_commit))
765 self.assertEqual(patch1, patch_for_commit[0])
766 self.assertEqual(1, len(commit_for_patch))
767 self.assertEqual(commit1, commit_for_patch[0])
768 self.assertEqual(["Cannot find commit for patch 2 ('Subject 2')"],
771 series.commits = [commit1, commit2]
773 patch_for_commit, commit_for_patch, warnings = (
774 status.compare_with_series(series, patches))
775 self.assertEqual(1, len(patch_for_commit))
776 self.assertEqual(patch1, patch_for_commit[0])
777 self.assertEqual(1, len(commit_for_patch))
778 self.assertEqual(commit1, commit_for_patch[0])
779 self.assertEqual(["Cannot find patch for commit 2 ('Subject 2')"],
782 series.commits = [commit1, commit2, commit3]
783 patches = [patch1, patch2]
784 patch_for_commit, commit_for_patch, warnings = (
785 status.compare_with_series(series, patches))
786 self.assertEqual(2, len(patch_for_commit))
787 self.assertEqual(patch1, patch_for_commit[0])
788 self.assertEqual(patch2, patch_for_commit[1])
789 self.assertEqual(1, len(commit_for_patch))
790 self.assertEqual(commit1, commit_for_patch[0])
791 self.assertEqual(["Cannot find patch for commit 3 ('Subject 2')",
792 "Multiple commits match patch 2 ('Subject 2'):\n"
793 ' Subject 2\n Subject 2'],
796 series.commits = [commit1, commit2]
797 patches = [patch1, patch2, patch3]
798 patch_for_commit, commit_for_patch, warnings = (
799 status.compare_with_series(series, patches))
800 self.assertEqual(1, len(patch_for_commit))
801 self.assertEqual(patch1, patch_for_commit[0])
802 self.assertEqual(2, len(commit_for_patch))
803 self.assertEqual(commit1, commit_for_patch[0])
804 self.assertEqual(["Multiple patches match commit 2 ('Subject 2'):\n"
805 ' Subject 2\n Subject 2',
806 "Cannot find commit for patch 3 ('Subject 2')"],
809 def _fake_patchwork2(self, url, subpath):
810 """Fake Patchwork server for the function below
812 This handles accessing series, patches and comments, providing the data
813 in self.patches to the caller
816 url (str): URL of patchwork server
817 subpath (str): URL subpath to use
819 re_series = re.match(r'series/(\d*)/$', subpath)
820 re_patch = re.match(r'patches/(\d*)/$', subpath)
821 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
823 series_num = re_series.group(1)
824 if series_num == '1234':
825 return {'patches': self.patches}
827 patch_num = int(re_patch.group(1))
828 patch = self.patches[patch_num - 1]
831 patch_num = int(re_comments.group(1))
832 patch = self.patches[patch_num - 1]
833 return patch.comments
834 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
836 def testFindNewResponses(self):
837 """Test operation of find_new_responses()"""
838 commit1 = Commit('abcd')
839 commit1.subject = 'Subject 1'
840 commit2 = Commit('ef12')
841 commit2.subject = 'Subject 2'
843 patch1 = status.Patch('1')
844 patch1.parse_subject('[1/2] Subject 1')
845 patch1.name = patch1.raw_subject
846 patch1.content = 'This is my patch content'
847 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
849 patch1.comments = [comment1a]
851 patch2 = status.Patch('2')
852 patch2.parse_subject('[2/2] Subject 2')
853 patch2.name = patch2.raw_subject
854 patch2.content = 'Some other patch content'
856 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
857 (self.mary, self.leb)}
858 comment2b = {'content': 'Reviewed-by: %s' % self.fred}
859 patch2.comments = [comment2a, comment2b]
861 # This test works by setting up commits and patch for use by the fake
862 # Rest API function _fake_patchwork2(). It calls various functions in
863 # the status module after setting up tags in the commits, checking that
864 # things behaves as expected
865 self.commits = [commit1, commit2]
866 self.patches = [patch1, patch2]
868 new_rtag_list = [None] * count
869 review_list = [None, None]
871 # Check that the tags are picked up on the first patch
872 status.find_new_responses(new_rtag_list, review_list, 0, commit1,
873 patch1, None, self._fake_patchwork2)
874 self.assertEqual(new_rtag_list[0], {'Reviewed-by': {self.joe}})
876 # Now the second patch
877 status.find_new_responses(new_rtag_list, review_list, 1, commit2,
878 patch2, None, self._fake_patchwork2)
879 self.assertEqual(new_rtag_list[1], {
880 'Reviewed-by': {self.mary, self.fred},
881 'Tested-by': {self.leb}})
883 # Now add some tags to the commit, which means they should not appear as
884 # 'new' tags when scanning comments
885 new_rtag_list = [None] * count
886 commit1.rtags = {'Reviewed-by': {self.joe}}
887 status.find_new_responses(new_rtag_list, review_list, 0, commit1,
888 patch1, None, self._fake_patchwork2)
889 self.assertEqual(new_rtag_list[0], {})
891 # For the second commit, add Ed and Fred, so only Mary should be left
893 'Tested-by': {self.leb},
894 'Reviewed-by': {self.fred}}
895 status.find_new_responses(new_rtag_list, review_list, 1, commit2,
896 patch2, None, self._fake_patchwork2)
897 self.assertEqual(new_rtag_list[1], {'Reviewed-by': {self.mary}})
899 # Check that the output patches expectations:
901 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
903 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
904 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
905 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
906 # 1 new response available in patchwork
909 series.commits = [commit1, commit2]
910 terminal.SetPrintTestMode()
911 status.check_patchwork_status(series, '1234', None, None, False, False,
912 None, self._fake_patchwork2)
913 lines = iter(terminal.GetPrintTestLines())
914 col = terminal.Color()
915 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
918 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
921 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE, bright=False),
924 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
927 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
930 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE, bright=False),
933 terminal.PrintLine(' Tested-by: ', col.GREEN, newline=False,
936 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE, bright=False),
939 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
941 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
943 self.assertEqual(terminal.PrintLine(
944 '1 new response available in patchwork (use -d to write them to a new branch)',
947 def _fake_patchwork3(self, url, subpath):
948 """Fake Patchwork server for the function below
950 This handles accessing series, patches and comments, providing the data
951 in self.patches to the caller
954 url (str): URL of patchwork server
955 subpath (str): URL subpath to use
957 re_series = re.match(r'series/(\d*)/$', subpath)
958 re_patch = re.match(r'patches/(\d*)/$', subpath)
959 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
961 series_num = re_series.group(1)
962 if series_num == '1234':
963 return {'patches': self.patches}
965 patch_num = int(re_patch.group(1))
966 patch = self.patches[patch_num - 1]
969 patch_num = int(re_comments.group(1))
970 patch = self.patches[patch_num - 1]
971 return patch.comments
972 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
974 def testCreateBranch(self):
975 """Test operation of create_branch()"""
976 repo = self.make_git_tree()
978 dest_branch = 'first2'
980 gitdir = os.path.join(self.gitdir, '.git')
982 # Set up the test git tree. We use branch 'first' which has two commits
984 series = patchstream.get_metadata_for_list(branch, gitdir, count)
985 self.assertEqual(2, len(series.commits))
987 patch1 = status.Patch('1')
988 patch1.parse_subject('[1/2] %s' % series.commits[0].subject)
989 patch1.name = patch1.raw_subject
990 patch1.content = 'This is my patch content'
991 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
993 patch1.comments = [comment1a]
995 patch2 = status.Patch('2')
996 patch2.parse_subject('[2/2] %s' % series.commits[1].subject)
997 patch2.name = patch2.raw_subject
998 patch2.content = 'Some other patch content'
1000 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1001 (self.mary, self.leb)}
1003 'content': 'Reviewed-by: %s' % self.fred}
1004 patch2.comments = [comment2a, comment2b]
1006 # This test works by setting up patches for use by the fake Rest API
1007 # function _fake_patchwork3(). The fake patch comments above should
1008 # result in new review tags that are collected and added to the commits
1009 # created in the destination branch.
1010 self.patches = [patch1, patch2]
1015 # + Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1017 # + Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1018 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1019 # + Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1020 # 4 new responses available in patchwork
1021 # 4 responses added from patchwork into new branch 'first2'
1022 # <unittest.result.TestResult run=8 errors=0 failures=0>
1024 terminal.SetPrintTestMode()
1025 status.check_patchwork_status(series, '1234', branch, dest_branch,
1026 False, False, None, self._fake_patchwork3,
1028 lines = terminal.GetPrintTestLines()
1029 self.assertEqual(12, len(lines))
1031 "4 responses added from patchwork into new branch 'first2'",
1034 # Check that the destination branch has the new tags
1035 new_series = patchstream.get_metadata_for_list(dest_branch, gitdir,
1038 {'Reviewed-by': {self.joe}},
1039 new_series.commits[0].rtags)
1041 {'Tested-by': {self.leb},
1042 'Reviewed-by': {self.fred, self.mary}},
1043 new_series.commits[1].rtags)
1045 # Now check the actual test of the first commit message. We expect to
1046 # see the new tags immediately below the old ones.
1047 stdout = patchstream.get_list(dest_branch, count=count, git_dir=gitdir)
1048 lines = iter([line.strip() for line in stdout.splitlines()
1051 # First patch should have the review tag
1052 self.assertEqual('Reviewed-by: %s' % self.joe, next(lines))
1054 # Second patch should have the sign-off then the tested-by and two
1056 self.assertEqual('Signed-off-by: %s' % self.leb, next(lines))
1057 self.assertEqual('Reviewed-by: %s' % self.fred, next(lines))
1058 self.assertEqual('Reviewed-by: %s' % self.mary, next(lines))
1059 self.assertEqual('Tested-by: %s' % self.leb, next(lines))
1061 def testParseSnippets(self):
1062 """Test parsing of review snippets"""
1065 This is a comment from someone.
1069 On some recent date, Fred wrote:
1070 > This is why I wrote the patch
1073 Now a comment about the commit message
1074 A little more to say
1078 > diff --git a/file.c b/file.c
1091 > @@ -153,8 +143,13 @@ def CheckPatch(fname, show_types=False):
1092 > further down on the file
1095 > +Another addition here
1099 and another thing in same file
1101 > @@ -253,8 +243,13 @@
1102 > with no function context
1106 > diff --git a/tools/patman/main.py b/tools/patman/main.py
1108 now a very long comment in a different file
1117 pstrm = PatchStream.process_text(text, True)
1118 self.assertEqual([], pstrm.commit.warn)
1120 # We expect to the filename and up to 5 lines of code context before
1121 # each comment. The 'On xxx wrote:' bit should be removed.
1124 'This is a comment from someone.',
1126 ['> This is why I wrote the patch',
1128 'Now a comment about the commit message',
1129 'A little more to say', 'Even more'],
1130 ['> File: file.c', '> Code line 5', '> Code line 6',
1131 '> Code line 7', '> Code line 8', '> Code line 9',
1132 'And another comment'],
1134 '> Line: 153 / 143: def CheckPatch(fname, show_types=False):',
1135 '> and more code', '> +Addition here', '> +Another addition here',
1136 '> codey', '> more codey', 'and another thing in same file'],
1137 ['> File: file.c', '> Line: 253 / 243',
1138 '> with no function context', 'one more thing'],
1139 ['> File: tools/patman/main.py', '> +line of code',
1140 'now a very long comment in a different file',
1141 'line2', 'line3', 'line4', 'line5', 'line6', 'line7', 'line8']],
1144 def testReviewSnippets(self):
1145 """Test showing of review snippets"""
1146 def _to_submitter(who):
1147 m_who = re.match('(.*) <(.*)>', who)
1149 'name': m_who.group(1),
1150 'email': m_who.group(2)
1153 commit1 = Commit('abcd')
1154 commit1.subject = 'Subject 1'
1155 commit2 = Commit('ef12')
1156 commit2.subject = 'Subject 2'
1158 patch1 = status.Patch('1')
1159 patch1.parse_subject('[1/2] Subject 1')
1160 patch1.name = patch1.raw_subject
1161 patch1.content = 'This is my patch content'
1162 comment1a = {'submitter': _to_submitter(self.joe),
1163 'content': '''Hi Fred,
1165 On some date Fred wrote:
1167 > diff --git a/file.c b/file.c
1171 Here is my comment above the above...
1177 patch1.comments = [comment1a]
1179 patch2 = status.Patch('2')
1180 patch2.parse_subject('[2/2] Subject 2')
1181 patch2.name = patch2.raw_subject
1182 patch2.content = 'Some other patch content'
1184 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1185 (self.mary, self.leb)}
1186 comment2b = {'submitter': _to_submitter(self.fred),
1187 'content': '''Hi Fred,
1189 On some date Fred wrote:
1191 > diff --git a/tools/patman/commit.py b/tools/patman/commit.py
1192 > @@ -41,6 +41,9 @@ class Commit:
1193 > self.rtags = collections.defaultdict(set)
1196 > + def __str__(self):
1197 > + return self.subject
1199 > def AddChange(self, version, info):
1200 > """Add a new change line to the change list for a version.
1206 patch2.comments = [comment2a, comment2b]
1208 # This test works by setting up commits and patch for use by the fake
1209 # Rest API function _fake_patchwork2(). It calls various functions in
1210 # the status module after setting up tags in the commits, checking that
1211 # things behaves as expected
1212 self.commits = [commit1, commit2]
1213 self.patches = [patch1, patch2]
1215 # Check that the output patches expectations:
1217 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1219 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1220 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1221 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1222 # 1 new response available in patchwork
1225 series.commits = [commit1, commit2]
1226 terminal.SetPrintTestMode()
1227 status.check_patchwork_status(series, '1234', None, None, False, True,
1228 None, self._fake_patchwork2)
1229 lines = iter(terminal.GetPrintTestLines())
1230 col = terminal.Color()
1231 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
1234 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1236 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE), next(lines))
1238 self.assertEqual(terminal.PrintLine('Review: %s' % self.joe, col.RED),
1240 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(lines))
1241 self.assertEqual(terminal.PrintLine('', None), next(lines))
1242 self.assertEqual(terminal.PrintLine(' > File: file.c', col.MAGENTA),
1244 self.assertEqual(terminal.PrintLine(' > Some code', col.MAGENTA),
1246 self.assertEqual(terminal.PrintLine(' > and more code', col.MAGENTA),
1248 self.assertEqual(terminal.PrintLine(
1249 ' Here is my comment above the above...', None), next(lines))
1250 self.assertEqual(terminal.PrintLine('', None), next(lines))
1252 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
1255 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1257 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE),
1260 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1262 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
1265 terminal.PrintLine(' + Tested-by: ', col.GREEN, newline=False),
1267 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE),
1270 self.assertEqual(terminal.PrintLine('Review: %s' % self.fred, col.RED),
1272 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(lines))
1273 self.assertEqual(terminal.PrintLine('', None), next(lines))
1274 self.assertEqual(terminal.PrintLine(
1275 ' > File: tools/patman/commit.py', col.MAGENTA), next(lines))
1276 self.assertEqual(terminal.PrintLine(
1277 ' > Line: 41 / 41: class Commit:', col.MAGENTA), next(lines))
1278 self.assertEqual(terminal.PrintLine(
1279 ' > + return self.subject', col.MAGENTA), next(lines))
1280 self.assertEqual(terminal.PrintLine(
1281 ' > +', col.MAGENTA), next(lines))
1283 terminal.PrintLine(' > def AddChange(self, version, info):',
1286 self.assertEqual(terminal.PrintLine(
1287 ' > """Add a new change line to the change list for a version.',
1288 col.MAGENTA), next(lines))
1289 self.assertEqual(terminal.PrintLine(
1290 ' >', col.MAGENTA), next(lines))
1291 self.assertEqual(terminal.PrintLine(
1292 ' A comment', None), next(lines))
1293 self.assertEqual(terminal.PrintLine('', None), next(lines))
1295 self.assertEqual(terminal.PrintLine(
1296 '4 new responses available in patchwork (use -d to write them to a new branch)',
1299 def testInsertTags(self):
1300 """Test inserting of review tags"""
1304 'Reviewed-by: Bin Meng <bmeng.cn@gmail.com>',
1305 'Tested-by: Bin Meng <bmeng.cn@gmail.com>'
1307 signoff = 'Signed-off-by: Simon Glass <sjg@chromium.com>'
1308 tag_str = '\n'.join(tags)
1310 new_msg = patchstream.insert_tags(msg, tags)
1311 self.assertEqual(msg + '\n\n' + tag_str, new_msg)
1313 new_msg = patchstream.insert_tags(msg + '\n', tags)
1314 self.assertEqual(msg + '\n\n' + tag_str, new_msg)
1316 msg += '\n\n' + signoff
1317 new_msg = patchstream.insert_tags(msg, tags)
1318 self.assertEqual(msg + '\n' + tag_str, new_msg)