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
31 from patman import status
32 except ModuleNotFoundError:
36 class TestFunctional(unittest.TestCase):
37 """Functional tests for checking that patman behaves correctly"""
38 leb = (b'Lord Edmund Blackadd\xc3\xabr <weasel@blackadder.org>'.
40 fred = 'Fred Bloggs <f.bloggs@napier.net>'
41 joe = 'Joe Bloggs <joe@napierwallies.co.nz>'
42 mary = 'Mary Bloggs <mary@napierwallies.co.nz>'
47 self.tmpdir = tempfile.mkdtemp(prefix='patman.')
48 self.gitdir = os.path.join(self.tmpdir, 'git')
52 shutil.rmtree(self.tmpdir)
53 terminal.SetPrintTestMode(False)
57 """Get the path to a test file
60 fname (str): Filename to obtain
63 str: Full path to file in the test directory
65 return os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
69 def _get_text(cls, fname):
70 """Read a file as text
73 fname (str): Filename to read
78 return open(cls._get_path(fname), encoding='utf-8').read()
81 def _get_patch_name(cls, subject):
82 """Get the filename of a patch given its subject
85 subject (str): Patch subject
88 str: Filename for that patch
90 fname = re.sub('[ :]', '-', subject)
91 return fname.replace('--', '-')
93 def _create_patches_for_test(self, series):
94 """Create patch files for use by tests
96 This copies patch files from the test directory as needed by the series
99 series (Series): Series containing commits to convert
103 str: Cover-letter filename, or None if none
104 fname_list: list of str, each a patch filename
108 for i, commit in enumerate(series.commits):
109 clean_subject = self._get_patch_name(commit.subject)
110 src_fname = '%04d-%s.patch' % (i + 1, clean_subject[:52])
111 fname = os.path.join(self.tmpdir, src_fname)
112 shutil.copy(self._get_path(src_fname), fname)
113 fname_list.append(fname)
114 if series.get('cover'):
115 src_fname = '0000-cover-letter.patch'
116 cover_fname = os.path.join(self.tmpdir, src_fname)
117 fname = os.path.join(self.tmpdir, src_fname)
118 shutil.copy(self._get_path(src_fname), fname)
120 return cover_fname, fname_list
123 """Tests the basic flow of patman
125 This creates a series from some hard-coded patches build from a simple
126 tree with the following metadata in the top commit:
130 Series-cc: Stefan Brüns <stefan.bruens@rwth-aachen.de>
131 Cover-letter-cc: Lord Mëlchett <clergy@palace.gov>
134 Series-process-log: sort, uniq
142 - Changes only for this commit
145 - Some notes for the cover letter
148 test: A test patch series
149 This is a test of how the cover
154 and this in the first commit:
157 - second revision change
162 from the first commit
170 with the following commands:
172 git log -n2 --reverse >/path/to/tools/patman/test/test01.txt
173 git format-patch --subject-prefix RFC --cover-letter HEAD~2
174 mv 00* /path/to/tools/patman/test
176 It checks these aspects:
177 - git log can be processed by patchstream
178 - emailing patches uses the correct command
179 - CC file has information on each commit
180 - cover letter has the expected text and subject
181 - each patch has the correct subject
182 - dry-run information prints out correctly
183 - unicode is handled correctly
184 - Series-to, Series-cc, Series-prefix, Cover-letter
185 - Cover-letter-cc, Series-version, Series-changes, Series-notes
189 ignore_bad_tags = True
190 stefan = b'Stefan Br\xc3\xbcns <stefan.bruens@rwth-aachen.de>'.decode('utf-8')
191 rick = 'Richard III <richard@palace.gov>'
192 mel = b'Lord M\xc3\xablchett <clergy@palace.gov>'.decode('utf-8')
193 add_maintainers = [stefan, rick]
199 'u-boot': ['u-boot@lists.denx.de'],
204 text = self._get_text('test01.txt')
205 series = patchstream.get_metadata_for_test(text)
206 cover_fname, args = self._create_patches_for_test(series)
207 with capture_sys_output() as out:
208 patchstream.fix_patches(series, args)
209 if cover_fname and series.get('cover'):
210 patchstream.insert_cover_letter(cover_fname, series, count)
212 cc_file = series.MakeCcFile(process_tags, cover_fname,
213 not ignore_bad_tags, add_maintainers,
215 cmd = gitutil.EmailPatches(
216 series, cover_fname, args, dry_run, not ignore_bad_tags,
217 cc_file, in_reply_to=in_reply_to, thread=None)
218 series.ShowActions(args, cmd, process_tags)
219 cc_lines = open(cc_file, encoding='utf-8').read().splitlines()
222 lines = iter(out[0].getvalue().splitlines())
223 self.assertEqual('Cleaned %s patches' % len(series.commits),
225 self.assertEqual('Change log missing for v2', next(lines))
226 self.assertEqual('Change log missing for v3', next(lines))
227 self.assertEqual('Change log for unknown version v4', next(lines))
228 self.assertEqual("Alias 'pci' not found", next(lines))
229 self.assertIn('Dry run', next(lines))
230 self.assertEqual('', next(lines))
231 self.assertIn('Send a total of %d patches' % count, next(lines))
233 for i, commit in enumerate(series.commits):
234 self.assertEqual(' %s' % args[i], prev)
237 if 'Cc:' not in prev:
239 self.assertEqual('To: u-boot@lists.denx.de', prev)
240 self.assertEqual('Cc: %s' % tools.FromUnicode(stefan), next(lines))
241 self.assertEqual('Version: 3', next(lines))
242 self.assertEqual('Prefix:\t RFC', next(lines))
243 self.assertEqual('Cover: 4 lines', next(lines))
244 self.assertEqual(' Cc: %s' % self.fred, next(lines))
245 self.assertEqual(' Cc: %s' % tools.FromUnicode(self.leb),
247 self.assertEqual(' Cc: %s' % tools.FromUnicode(mel), next(lines))
248 self.assertEqual(' Cc: %s' % rick, next(lines))
249 expected = ('Git command: git send-email --annotate '
250 '--in-reply-to="%s" --to "u-boot@lists.denx.de" '
251 '--cc "%s" --cc-cmd "%s --cc-cmd %s" %s %s'
252 % (in_reply_to, stefan, sys.argv[0], cc_file, cover_fname,
254 self.assertEqual(expected, tools.ToUnicode(next(lines)))
256 self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)),
257 tools.ToUnicode(cc_lines[0]))
259 '%s %s\0%s\0%s\0%s' % (args[1], self.fred, self.leb, rick, stefan),
260 tools.ToUnicode(cc_lines[1]))
263 This is a test of how the cover
269 from the first commit
276 - Some notes for the cover letter
279 pci: Correct cast for sandbox
280 fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
284 lib/efi_loader/efi_memory.c | 1 +
286 4 files changed, 6 insertions(+), 2 deletions(-)
292 lines = open(cover_fname, encoding='utf-8').read().splitlines()
294 'Subject: [RFC PATCH v3 0/2] test: A test patch series',
296 self.assertEqual(expected.splitlines(), lines[7:])
298 for i, fname in enumerate(args):
299 lines = open(fname, encoding='utf-8').read().splitlines()
300 subject = [line for line in lines if line.startswith('Subject')]
301 self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count),
304 # Check that we got our commit notes
314 (no changes since v2)
317 - second revision change'''
329 - Changes only for this commit'''
332 expected = expected.splitlines()
333 self.assertEqual(expected, lines[start:(start+len(expected))])
335 def make_commit_with_file(self, subject, body, fname, text):
336 """Create a file and add it to the git repo with a new commit
339 subject (str): Subject for the commit
340 body (str): Body text of the commit
341 fname (str): Filename of file to create
342 text (str): Text to put into the file
344 path = os.path.join(self.gitdir, fname)
345 tools.WriteFile(path, text, binary=False)
346 index = self.repo.index
348 author = pygit2.Signature('Test user', 'test@email.com')
350 tree = index.write_tree()
351 message = subject + '\n' + body
352 self.repo.create_commit('HEAD', author, committer, message, tree,
353 [self.repo.head.target])
355 def make_git_tree(self):
356 """Make a simple git tree suitable for testing
358 It has three branches:
359 'base' has two commits: PCI, main
360 'first' has base as upstream and two more commits: I2C, SPI
361 'second' has base as upstream and three more: video, serial, bootm
364 pygit2.Repository: repository
366 repo = pygit2.init_repository(self.gitdir)
368 new_tree = repo.TreeBuilder().write()
370 author = pygit2.Signature('Test user', 'test@email.com')
372 _ = repo.create_commit('HEAD', author, committer, 'Created master',
375 self.make_commit_with_file('Initial commit', '''
378 ''', 'README', '''This is the README file
379 describing this project
380 in very little detail''')
382 self.make_commit_with_file('pci: PCI implementation', '''
383 Here is a basic PCI implementation
385 ''', 'pci.c', '''This is a file
387 and some more things''')
388 self.make_commit_with_file('main: Main program', '''
389 Hello here is the second commit.
390 ''', 'main.c', '''This is the main file
391 there is very little here
392 but we can always add more later
396 Series-cc: Barry Crump <bcrump@whataroa.nz>
398 base_target = repo.revparse_single('HEAD')
399 self.make_commit_with_file('i2c: I2C things', '''
400 This has some stuff to do with I2C
401 ''', 'i2c.c', '''And this is the file contents
402 with some I2C-related things in it''')
403 self.make_commit_with_file('spi: SPI fixes', '''
412 This is the cover letter for the series
415 ''' % self.leb, 'spi.c', '''Some fixes for SPI in this
416 file to make SPI work
417 better than before''')
418 first_target = repo.revparse_single('HEAD')
420 target = repo.revparse_single('HEAD~2')
421 repo.reset(target.oid, pygit2.GIT_CHECKOUT_FORCE)
422 self.make_commit_with_file('video: Some video improvements', '''
423 Fix up the video so that
424 it looks more purple. Purple is
426 ''', 'video.c', '''More purple here
429 Could not be any more purple''')
430 self.make_commit_with_file('serial: Add a serial driver', '''
431 Here is the serial driver
436 This series implements support
437 for my glorious board.
440 ''', 'serial.c', '''The code for the
441 serial driver is here''')
442 self.make_commit_with_file('bootm: Make it boot', '''
443 This makes my board boot
444 with a fix to the bootm
446 ''', 'bootm.c', '''Fix up the bootm
447 command to make the code as
448 complicated as possible''')
449 second_target = repo.revparse_single('HEAD')
451 repo.branches.local.create('first', first_target)
452 repo.config.set_multivar('branch.first.remote', '', '.')
453 repo.config.set_multivar('branch.first.merge', '', 'refs/heads/base')
455 repo.branches.local.create('second', second_target)
456 repo.config.set_multivar('branch.second.remote', '', '.')
457 repo.config.set_multivar('branch.second.merge', '', 'refs/heads/base')
459 repo.branches.local.create('base', base_target)
462 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
463 def testBranch(self):
464 """Test creating patches from a branch"""
465 repo = self.make_git_tree()
466 target = repo.lookup_reference('refs/heads/first')
467 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
470 orig_dir = os.getcwd()
471 os.chdir(self.gitdir)
473 # Check that it can detect the current branch
474 self.assertEqual(2, gitutil.CountCommitsToBranch(None))
475 col = terminal.Color()
476 with capture_sys_output() as _:
477 _, cover_fname, patch_files = control.prepare_patches(
478 col, branch=None, count=-1, start=0, end=0,
480 self.assertIsNone(cover_fname)
481 self.assertEqual(2, len(patch_files))
483 # Check that it can detect a different branch
484 self.assertEqual(3, gitutil.CountCommitsToBranch('second'))
485 with capture_sys_output() as _:
486 _, cover_fname, patch_files = control.prepare_patches(
487 col, branch='second', count=-1, start=0, end=0,
489 self.assertIsNotNone(cover_fname)
490 self.assertEqual(3, len(patch_files))
492 # Check that it can skip patches at the end
493 with capture_sys_output() as _:
494 _, cover_fname, patch_files = control.prepare_patches(
495 col, branch='second', count=-1, start=0, end=1,
497 self.assertIsNotNone(cover_fname)
498 self.assertEqual(2, len(patch_files))
503 """Test collection of tags in a patchstream"""
504 text = '''This is a patch
506 Signed-off-by: Terminator
510 ''' % (self.joe, self.mary, self.leb)
511 pstrm = PatchStream.process_text(text)
512 self.assertEqual(pstrm.commit.rtags, {
513 'Reviewed-by': {self.joe, self.mary},
514 'Tested-by': {self.leb}})
516 def testMissingEnd(self):
517 """Test a missing END tag"""
518 text = '''This is a patch
522 missing END after this line
525 pstrm = PatchStream.process_text(text)
526 self.assertEqual(["Missing 'END' in section 'cover'"],
529 def testMissingBlankLine(self):
530 """Test a missing blank line after a tag"""
531 text = '''This is a patch
534 - First line of changes
535 - Missing blank line after this line
538 pstrm = PatchStream.process_text(text)
539 self.assertEqual(["Missing 'blank line' in section 'Series-changes'"],
542 def testInvalidCommitTag(self):
543 """Test an invalid Commit-xxx tag"""
544 text = '''This is a patch
548 pstrm = PatchStream.process_text(text)
549 self.assertEqual(["Line 3: Ignoring Commit-fred"], pstrm.commit.warn)
551 def testSelfTest(self):
552 """Test a tested by tag by this user"""
553 test_line = 'Tested-by: %s@napier.com' % os.getenv('USER')
554 text = '''This is a patch
558 pstrm = PatchStream.process_text(text)
559 self.assertEqual(["Ignoring '%s'" % test_line], pstrm.commit.warn)
561 def testSpaceBeforeTab(self):
562 """Test a space before a tab"""
563 text = '''This is a patch
567 pstrm = PatchStream.process_text(text)
568 self.assertEqual(["Line 3/0 has space before tab"], pstrm.commit.warn)
570 def testLinesAfterTest(self):
571 """Test detecting lines after TEST= line"""
572 text = '''This is a patch
578 pstrm = PatchStream.process_text(text)
579 self.assertEqual(["Found 2 lines after TEST="], pstrm.commit.warn)
581 def testBlankLineAtEnd(self):
582 """Test detecting a blank line at the end of a file"""
583 text = '''This is a patch
585 diff --git a/lib/fdtdec.c b/lib/fdtdec.c
586 index c072e54..942244f 100644
589 @@ -1200,7 +1200,8 @@ int fdtdec_setup_mem_size_base(void)
592 gd->ram_size = (phys_size_t)(res.end - res.start + 1);
593 - debug("%s: Initial DRAM size %llx\n", __func__, (u64)gd->ram_size);
594 + debug("%s: Initial DRAM size %llx\n", __func__,
595 + (unsigned long long)gd->ram_size);
597 diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c
603 pstrm = PatchStream.process_text(text)
605 ["Found possible blank line(s) at end of file 'lib/fdtdec.c'"],
608 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
609 def testNoUpstream(self):
610 """Test CountCommitsToBranch when there is no upstream"""
611 repo = self.make_git_tree()
612 target = repo.lookup_reference('refs/heads/base')
613 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
615 # Check that it can detect the current branch
617 orig_dir = os.getcwd()
618 os.chdir(self.gitdir)
619 with self.assertRaises(ValueError) as exc:
620 gitutil.CountCommitsToBranch(None)
622 "Failed to determine upstream: fatal: no upstream configured for branch 'base'",
628 def _fake_patchwork(subpath):
629 """Fake Patchwork server for the function below
631 This handles accessing a series, providing a list consisting of a
634 re_series = re.match(r'series/(\d*)/$', subpath)
636 series_num = re_series.group(1)
637 if series_num == '1234':
639 {'id': '1', 'name': 'Some patch'}]}
640 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
642 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
643 def testStatusMismatch(self):
644 """Test Patchwork patches not matching the series"""
647 with capture_sys_output() as (_, err):
648 status.collect_patches(series, 1234, self._fake_patchwork)
649 self.assertIn('Warning: Patchwork reports 1 patches, series has 0',
652 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
653 def testStatusReadPatch(self):
654 """Test handling a single patch in Patchwork"""
656 series.commits = [Commit('abcd')]
658 patches = status.collect_patches(series, 1234, self._fake_patchwork)
659 self.assertEqual(1, len(patches))
661 self.assertEqual('1', patch.id)
662 self.assertEqual('Some patch', patch.raw_subject)
664 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
665 def testParseSubject(self):
666 """Test parsing of the patch subject"""
667 patch = status.Patch('1')
669 # Simple patch not in a series
670 patch.parse_subject('Testing')
671 self.assertEqual('Testing', patch.raw_subject)
672 self.assertEqual('Testing', patch.subject)
673 self.assertEqual(1, patch.seq)
674 self.assertEqual(1, patch.count)
675 self.assertEqual(None, patch.prefix)
676 self.assertEqual(None, patch.version)
678 # First patch in a series
679 patch.parse_subject('[1/2] Testing')
680 self.assertEqual('[1/2] Testing', patch.raw_subject)
681 self.assertEqual('Testing', patch.subject)
682 self.assertEqual(1, patch.seq)
683 self.assertEqual(2, patch.count)
684 self.assertEqual(None, patch.prefix)
685 self.assertEqual(None, patch.version)
687 # Second patch in a series
688 patch.parse_subject('[2/2] Testing')
689 self.assertEqual('Testing', patch.subject)
690 self.assertEqual(2, patch.seq)
691 self.assertEqual(2, patch.count)
692 self.assertEqual(None, patch.prefix)
693 self.assertEqual(None, patch.version)
696 patch.parse_subject('[RFC,3/7] Testing')
697 self.assertEqual('Testing', patch.subject)
698 self.assertEqual(3, patch.seq)
699 self.assertEqual(7, patch.count)
700 self.assertEqual('RFC', patch.prefix)
701 self.assertEqual(None, patch.version)
704 patch.parse_subject('[v2,3/7] Testing')
705 self.assertEqual('Testing', patch.subject)
706 self.assertEqual(3, patch.seq)
707 self.assertEqual(7, patch.count)
708 self.assertEqual(None, patch.prefix)
709 self.assertEqual('v2', patch.version)
712 patch.parse_subject('[RESEND,v2,3/7] Testing')
713 self.assertEqual('Testing', patch.subject)
714 self.assertEqual(3, patch.seq)
715 self.assertEqual(7, patch.count)
716 self.assertEqual('RESEND', patch.prefix)
717 self.assertEqual('v2', patch.version)
720 patch.parse_subject('[RESEND] Testing')
721 self.assertEqual('Testing', patch.subject)
722 self.assertEqual(1, patch.seq)
723 self.assertEqual(1, patch.count)
724 self.assertEqual('RESEND', patch.prefix)
725 self.assertEqual(None, patch.version)
727 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
728 def testCompareSeries(self):
729 """Test operation of compare_with_series()"""
730 commit1 = Commit('abcd')
731 commit1.subject = 'Subject 1'
732 commit2 = Commit('ef12')
733 commit2.subject = 'Subject 2'
734 commit3 = Commit('3456')
735 commit3.subject = 'Subject 2'
737 patch1 = status.Patch('1')
738 patch1.subject = 'Subject 1'
739 patch2 = status.Patch('2')
740 patch2.subject = 'Subject 2'
741 patch3 = status.Patch('3')
742 patch3.subject = 'Subject 2'
745 series.commits = [commit1]
747 patch_for_commit, commit_for_patch, warnings = (
748 status.compare_with_series(series, patches))
749 self.assertEqual(1, len(patch_for_commit))
750 self.assertEqual(patch1, patch_for_commit[0])
751 self.assertEqual(1, len(commit_for_patch))
752 self.assertEqual(commit1, commit_for_patch[0])
754 series.commits = [commit1]
755 patches = [patch1, patch2]
756 patch_for_commit, commit_for_patch, warnings = (
757 status.compare_with_series(series, patches))
758 self.assertEqual(1, len(patch_for_commit))
759 self.assertEqual(patch1, patch_for_commit[0])
760 self.assertEqual(1, len(commit_for_patch))
761 self.assertEqual(commit1, commit_for_patch[0])
762 self.assertEqual(["Cannot find commit for patch 2 ('Subject 2')"],
765 series.commits = [commit1, commit2]
767 patch_for_commit, commit_for_patch, warnings = (
768 status.compare_with_series(series, patches))
769 self.assertEqual(1, len(patch_for_commit))
770 self.assertEqual(patch1, patch_for_commit[0])
771 self.assertEqual(1, len(commit_for_patch))
772 self.assertEqual(commit1, commit_for_patch[0])
773 self.assertEqual(["Cannot find patch for commit 2 ('Subject 2')"],
776 series.commits = [commit1, commit2, commit3]
777 patches = [patch1, patch2]
778 patch_for_commit, commit_for_patch, warnings = (
779 status.compare_with_series(series, patches))
780 self.assertEqual(2, len(patch_for_commit))
781 self.assertEqual(patch1, patch_for_commit[0])
782 self.assertEqual(patch2, patch_for_commit[1])
783 self.assertEqual(1, len(commit_for_patch))
784 self.assertEqual(commit1, commit_for_patch[0])
785 self.assertEqual(["Cannot find patch for commit 3 ('Subject 2')",
786 "Multiple commits match patch 2 ('Subject 2'):\n"
787 ' Subject 2\n Subject 2'],
790 series.commits = [commit1, commit2]
791 patches = [patch1, patch2, patch3]
792 patch_for_commit, commit_for_patch, warnings = (
793 status.compare_with_series(series, patches))
794 self.assertEqual(1, len(patch_for_commit))
795 self.assertEqual(patch1, patch_for_commit[0])
796 self.assertEqual(2, len(commit_for_patch))
797 self.assertEqual(commit1, commit_for_patch[0])
798 self.assertEqual(["Multiple patches match commit 2 ('Subject 2'):\n"
799 ' Subject 2\n Subject 2',
800 "Cannot find commit for patch 3 ('Subject 2')"],
803 def _fake_patchwork2(self, subpath):
804 """Fake Patchwork server for the function below
806 This handles accessing series, patches and comments, providing the data
807 in self.patches to the caller
809 re_series = re.match(r'series/(\d*)/$', subpath)
810 re_patch = re.match(r'patches/(\d*)/$', subpath)
811 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
813 series_num = re_series.group(1)
814 if series_num == '1234':
815 return {'patches': self.patches}
817 patch_num = int(re_patch.group(1))
818 patch = self.patches[patch_num - 1]
821 patch_num = int(re_comments.group(1))
822 patch = self.patches[patch_num - 1]
823 return patch.comments
824 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
826 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
827 def testFindNewResponses(self):
828 """Test operation of find_new_responses()"""
829 commit1 = Commit('abcd')
830 commit1.subject = 'Subject 1'
831 commit2 = Commit('ef12')
832 commit2.subject = 'Subject 2'
834 patch1 = status.Patch('1')
835 patch1.parse_subject('[1/2] Subject 1')
836 patch1.name = patch1.raw_subject
837 patch1.content = 'This is my patch content'
838 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
840 patch1.comments = [comment1a]
842 patch2 = status.Patch('2')
843 patch2.parse_subject('[2/2] Subject 2')
844 patch2.name = patch2.raw_subject
845 patch2.content = 'Some other patch content'
847 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
848 (self.mary, self.leb)}
849 comment2b = {'content': 'Reviewed-by: %s' % self.fred}
850 patch2.comments = [comment2a, comment2b]
852 # This test works by setting up commits and patch for use by the fake
853 # Rest API function _fake_patchwork2(). It calls various functions in
854 # the status module after setting up tags in the commits, checking that
855 # things behaves as expected
856 self.commits = [commit1, commit2]
857 self.patches = [patch1, patch2]
859 new_rtag_list = [None] * count
860 review_list = [None, None]
862 # Check that the tags are picked up on the first patch
863 status.find_new_responses(new_rtag_list, review_list, 0, commit1,
864 patch1, self._fake_patchwork2)
865 self.assertEqual(new_rtag_list[0], {'Reviewed-by': {self.joe}})
867 # Now the second patch
868 status.find_new_responses(new_rtag_list, review_list, 1, commit2,
869 patch2, self._fake_patchwork2)
870 self.assertEqual(new_rtag_list[1], {
871 'Reviewed-by': {self.mary, self.fred},
872 'Tested-by': {self.leb}})
874 # Now add some tags to the commit, which means they should not appear as
875 # 'new' tags when scanning comments
876 new_rtag_list = [None] * count
877 commit1.rtags = {'Reviewed-by': {self.joe}}
878 status.find_new_responses(new_rtag_list, review_list, 0, commit1,
879 patch1, self._fake_patchwork2)
880 self.assertEqual(new_rtag_list[0], {})
882 # For the second commit, add Ed and Fred, so only Mary should be left
884 'Tested-by': {self.leb},
885 'Reviewed-by': {self.fred}}
886 status.find_new_responses(new_rtag_list, review_list, 1, commit2,
887 patch2, self._fake_patchwork2)
888 self.assertEqual(new_rtag_list[1], {'Reviewed-by': {self.mary}})
890 # Check that the output patches expectations:
892 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
894 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
895 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
896 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
897 # 1 new response available in patchwork
900 series.commits = [commit1, commit2]
901 terminal.SetPrintTestMode()
902 status.check_patchwork_status(series, '1234', None, None, False, False,
903 self._fake_patchwork2)
904 lines = iter(terminal.GetPrintTestLines())
905 col = terminal.Color()
906 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
909 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
912 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE, bright=False),
915 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
918 terminal.PrintLine(' Reviewed-by: ', col.GREEN, newline=False,
921 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE, bright=False),
924 terminal.PrintLine(' Tested-by: ', col.GREEN, newline=False,
927 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE, bright=False),
930 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
932 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
934 self.assertEqual(terminal.PrintLine(
935 '1 new response available in patchwork (use -d to write them to a new branch)',
938 def _fake_patchwork3(self, subpath):
939 """Fake Patchwork server for the function below
941 This handles accessing series, patches and comments, providing the data
942 in self.patches to the caller
944 re_series = re.match(r'series/(\d*)/$', subpath)
945 re_patch = re.match(r'patches/(\d*)/$', subpath)
946 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
948 series_num = re_series.group(1)
949 if series_num == '1234':
950 return {'patches': self.patches}
952 patch_num = int(re_patch.group(1))
953 patch = self.patches[patch_num - 1]
956 patch_num = int(re_comments.group(1))
957 patch = self.patches[patch_num - 1]
958 return patch.comments
959 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
961 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
962 def testCreateBranch(self):
963 """Test operation of create_branch()"""
964 repo = self.make_git_tree()
966 dest_branch = 'first2'
968 gitdir = os.path.join(self.gitdir, '.git')
970 # Set up the test git tree. We use branch 'first' which has two commits
972 series = patchstream.get_metadata_for_list(branch, gitdir, count)
973 self.assertEqual(2, len(series.commits))
975 patch1 = status.Patch('1')
976 patch1.parse_subject('[1/2] %s' % series.commits[0].subject)
977 patch1.name = patch1.raw_subject
978 patch1.content = 'This is my patch content'
979 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
981 patch1.comments = [comment1a]
983 patch2 = status.Patch('2')
984 patch2.parse_subject('[2/2] %s' % series.commits[1].subject)
985 patch2.name = patch2.raw_subject
986 patch2.content = 'Some other patch content'
988 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
989 (self.mary, self.leb)}
991 'content': 'Reviewed-by: %s' % self.fred}
992 patch2.comments = [comment2a, comment2b]
994 # This test works by setting up patches for use by the fake Rest API
995 # function _fake_patchwork3(). The fake patch comments above should
996 # result in new review tags that are collected and added to the commits
997 # created in the destination branch.
998 self.patches = [patch1, patch2]
1003 # + Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1005 # + Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1006 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1007 # + Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1008 # 4 new responses available in patchwork
1009 # 4 responses added from patchwork into new branch 'first2'
1010 # <unittest.result.TestResult run=8 errors=0 failures=0>
1012 terminal.SetPrintTestMode()
1013 status.check_patchwork_status(series, '1234', branch, dest_branch,
1014 False, False, self._fake_patchwork3, repo)
1015 lines = terminal.GetPrintTestLines()
1016 self.assertEqual(12, len(lines))
1018 "4 responses added from patchwork into new branch 'first2'",
1021 # Check that the destination branch has the new tags
1022 new_series = patchstream.get_metadata_for_list(dest_branch, gitdir,
1025 {'Reviewed-by': {self.joe}},
1026 new_series.commits[0].rtags)
1028 {'Tested-by': {self.leb},
1029 'Reviewed-by': {self.fred, self.mary}},
1030 new_series.commits[1].rtags)
1032 # Now check the actual test of the first commit message. We expect to
1033 # see the new tags immediately below the old ones.
1034 stdout = patchstream.get_list(dest_branch, count=count, git_dir=gitdir)
1035 lines = iter([line.strip() for line in stdout.splitlines()
1038 # First patch should have the review tag
1039 self.assertEqual('Reviewed-by: %s' % self.joe, next(lines))
1041 # Second patch should have the sign-off then the tested-by and two
1043 self.assertEqual('Signed-off-by: %s' % self.leb, next(lines))
1044 self.assertEqual('Reviewed-by: %s' % self.fred, next(lines))
1045 self.assertEqual('Reviewed-by: %s' % self.mary, next(lines))
1046 self.assertEqual('Tested-by: %s' % self.leb, next(lines))
1048 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
1049 def testParseSnippets(self):
1050 """Test parsing of review snippets"""
1053 This is a comment from someone.
1057 On some recent date, Fred wrote:
1058 > This is why I wrote the patch
1061 Now a comment about the commit message
1062 A little more to say
1066 > diff --git a/file.c b/file.c
1079 > @@ -153,8 +143,13 @@ def CheckPatch(fname, show_types=False):
1080 > further down on the file
1083 > +Another addition here
1087 and another thing in same file
1089 > @@ -253,8 +243,13 @@
1090 > with no function context
1094 > diff --git a/tools/patman/main.py b/tools/patman/main.py
1096 now a very long comment in a different file
1105 pstrm = PatchStream.process_text(text, True)
1106 self.assertEqual([], pstrm.commit.warn)
1108 # We expect to the filename and up to 5 lines of code context before
1109 # each comment. The 'On xxx wrote:' bit should be removed.
1112 'This is a comment from someone.',
1114 ['> This is why I wrote the patch',
1116 'Now a comment about the commit message',
1117 'A little more to say', 'Even more'],
1118 ['> File: file.c', '> Code line 5', '> Code line 6',
1119 '> Code line 7', '> Code line 8', '> Code line 9',
1120 'And another comment'],
1122 '> Line: 153 / 143: def CheckPatch(fname, show_types=False):',
1123 '> and more code', '> +Addition here', '> +Another addition here',
1124 '> codey', '> more codey', 'and another thing in same file'],
1125 ['> File: file.c', '> Line: 253 / 243',
1126 '> with no function context', 'one more thing'],
1127 ['> File: tools/patman/main.py', '> +line of code',
1128 'now a very long comment in a different file',
1129 'line2', 'line3', 'line4', 'line5', 'line6', 'line7', 'line8']],
1132 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
1133 def testReviewSnippets(self):
1134 """Test showing of review snippets"""
1135 def _to_submitter(who):
1136 m_who = re.match('(.*) <(.*)>', who)
1138 'name': m_who.group(1),
1139 'email': m_who.group(2)
1142 commit1 = Commit('abcd')
1143 commit1.subject = 'Subject 1'
1144 commit2 = Commit('ef12')
1145 commit2.subject = 'Subject 2'
1147 patch1 = status.Patch('1')
1148 patch1.parse_subject('[1/2] Subject 1')
1149 patch1.name = patch1.raw_subject
1150 patch1.content = 'This is my patch content'
1151 comment1a = {'submitter': _to_submitter(self.joe),
1152 'content': '''Hi Fred,
1154 On some date Fred wrote:
1156 > diff --git a/file.c b/file.c
1160 Here is my comment above the above...
1166 patch1.comments = [comment1a]
1168 patch2 = status.Patch('2')
1169 patch2.parse_subject('[2/2] Subject 2')
1170 patch2.name = patch2.raw_subject
1171 patch2.content = 'Some other patch content'
1173 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1174 (self.mary, self.leb)}
1175 comment2b = {'submitter': _to_submitter(self.fred),
1176 'content': '''Hi Fred,
1178 On some date Fred wrote:
1180 > diff --git a/tools/patman/commit.py b/tools/patman/commit.py
1181 > @@ -41,6 +41,9 @@ class Commit:
1182 > self.rtags = collections.defaultdict(set)
1185 > + def __str__(self):
1186 > + return self.subject
1188 > def AddChange(self, version, info):
1189 > """Add a new change line to the change list for a version.
1195 patch2.comments = [comment2a, comment2b]
1197 # This test works by setting up commits and patch for use by the fake
1198 # Rest API function _fake_patchwork2(). It calls various functions in
1199 # the status module after setting up tags in the commits, checking that
1200 # things behaves as expected
1201 self.commits = [commit1, commit2]
1202 self.patches = [patch1, patch2]
1204 # Check that the output patches expectations:
1206 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1208 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1209 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1210 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1211 # 1 new response available in patchwork
1214 series.commits = [commit1, commit2]
1215 terminal.SetPrintTestMode()
1216 status.check_patchwork_status(series, '1234', None, None, False, True,
1217 self._fake_patchwork2)
1218 lines = iter(terminal.GetPrintTestLines())
1219 col = terminal.Color()
1220 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
1223 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1225 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE), next(lines))
1227 self.assertEqual(terminal.PrintLine('Review: %s' % self.joe, col.RED),
1229 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(lines))
1230 self.assertEqual(terminal.PrintLine('', None), next(lines))
1231 self.assertEqual(terminal.PrintLine(' > File: file.c', col.MAGENTA),
1233 self.assertEqual(terminal.PrintLine(' > Some code', col.MAGENTA),
1235 self.assertEqual(terminal.PrintLine(' > and more code', col.MAGENTA),
1237 self.assertEqual(terminal.PrintLine(
1238 ' Here is my comment above the above...', None), next(lines))
1239 self.assertEqual(terminal.PrintLine('', None), next(lines))
1241 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
1244 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1246 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE),
1249 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1251 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
1254 terminal.PrintLine(' + Tested-by: ', col.GREEN, newline=False),
1256 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE),
1259 self.assertEqual(terminal.PrintLine('Review: %s' % self.fred, col.RED),
1261 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(lines))
1262 self.assertEqual(terminal.PrintLine('', None), next(lines))
1263 self.assertEqual(terminal.PrintLine(
1264 ' > File: tools/patman/commit.py', col.MAGENTA), next(lines))
1265 self.assertEqual(terminal.PrintLine(
1266 ' > Line: 41 / 41: class Commit:', col.MAGENTA), next(lines))
1267 self.assertEqual(terminal.PrintLine(
1268 ' > + return self.subject', col.MAGENTA), next(lines))
1269 self.assertEqual(terminal.PrintLine(
1270 ' > +', col.MAGENTA), next(lines))
1272 terminal.PrintLine(' > def AddChange(self, version, info):',
1275 self.assertEqual(terminal.PrintLine(
1276 ' > """Add a new change line to the change list for a version.',
1277 col.MAGENTA), next(lines))
1278 self.assertEqual(terminal.PrintLine(
1279 ' >', col.MAGENTA), next(lines))
1280 self.assertEqual(terminal.PrintLine(
1281 ' A comment', None), next(lines))
1282 self.assertEqual(terminal.PrintLine('', None), next(lines))
1284 self.assertEqual(terminal.PrintLine(
1285 '4 new responses available in patchwork (use -d to write them to a new branch)',