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 send --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(url, subpath):
629 """Fake Patchwork server for the function below
631 This handles accessing a series, providing a list consisting of a
635 url (str): URL of patchwork server
636 subpath (str): URL subpath to use
638 re_series = re.match(r'series/(\d*)/$', subpath)
640 series_num = re_series.group(1)
641 if series_num == '1234':
643 {'id': '1', 'name': 'Some patch'}]}
644 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
646 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
647 def testStatusMismatch(self):
648 """Test Patchwork patches not matching the series"""
651 with capture_sys_output() as (_, err):
652 status.collect_patches(series, 1234, None, self._fake_patchwork)
653 self.assertIn('Warning: Patchwork reports 1 patches, series has 0',
656 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
657 def testStatusReadPatch(self):
658 """Test handling a single patch in Patchwork"""
660 series.commits = [Commit('abcd')]
662 patches = status.collect_patches(series, 1234, None,
663 self._fake_patchwork)
664 self.assertEqual(1, len(patches))
666 self.assertEqual('1', patch.id)
667 self.assertEqual('Some patch', patch.raw_subject)
669 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
670 def testParseSubject(self):
671 """Test parsing of the patch subject"""
672 patch = status.Patch('1')
674 # Simple patch not in a series
675 patch.parse_subject('Testing')
676 self.assertEqual('Testing', patch.raw_subject)
677 self.assertEqual('Testing', patch.subject)
678 self.assertEqual(1, patch.seq)
679 self.assertEqual(1, patch.count)
680 self.assertEqual(None, patch.prefix)
681 self.assertEqual(None, patch.version)
683 # First patch in a series
684 patch.parse_subject('[1/2] Testing')
685 self.assertEqual('[1/2] Testing', patch.raw_subject)
686 self.assertEqual('Testing', patch.subject)
687 self.assertEqual(1, patch.seq)
688 self.assertEqual(2, patch.count)
689 self.assertEqual(None, patch.prefix)
690 self.assertEqual(None, patch.version)
692 # Second patch in a series
693 patch.parse_subject('[2/2] Testing')
694 self.assertEqual('Testing', patch.subject)
695 self.assertEqual(2, patch.seq)
696 self.assertEqual(2, patch.count)
697 self.assertEqual(None, patch.prefix)
698 self.assertEqual(None, patch.version)
701 patch.parse_subject('[RFC,3/7] Testing')
702 self.assertEqual('Testing', patch.subject)
703 self.assertEqual(3, patch.seq)
704 self.assertEqual(7, patch.count)
705 self.assertEqual('RFC', patch.prefix)
706 self.assertEqual(None, patch.version)
709 patch.parse_subject('[v2,3/7] Testing')
710 self.assertEqual('Testing', patch.subject)
711 self.assertEqual(3, patch.seq)
712 self.assertEqual(7, patch.count)
713 self.assertEqual(None, patch.prefix)
714 self.assertEqual('v2', patch.version)
717 patch.parse_subject('[RESEND,v2,3/7] Testing')
718 self.assertEqual('Testing', patch.subject)
719 self.assertEqual(3, patch.seq)
720 self.assertEqual(7, patch.count)
721 self.assertEqual('RESEND', patch.prefix)
722 self.assertEqual('v2', patch.version)
725 patch.parse_subject('[RESEND] Testing')
726 self.assertEqual('Testing', patch.subject)
727 self.assertEqual(1, patch.seq)
728 self.assertEqual(1, patch.count)
729 self.assertEqual('RESEND', patch.prefix)
730 self.assertEqual(None, patch.version)
732 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
733 def testCompareSeries(self):
734 """Test operation of compare_with_series()"""
735 commit1 = Commit('abcd')
736 commit1.subject = 'Subject 1'
737 commit2 = Commit('ef12')
738 commit2.subject = 'Subject 2'
739 commit3 = Commit('3456')
740 commit3.subject = 'Subject 2'
742 patch1 = status.Patch('1')
743 patch1.subject = 'Subject 1'
744 patch2 = status.Patch('2')
745 patch2.subject = 'Subject 2'
746 patch3 = status.Patch('3')
747 patch3.subject = 'Subject 2'
750 series.commits = [commit1]
752 patch_for_commit, commit_for_patch, warnings = (
753 status.compare_with_series(series, patches))
754 self.assertEqual(1, len(patch_for_commit))
755 self.assertEqual(patch1, patch_for_commit[0])
756 self.assertEqual(1, len(commit_for_patch))
757 self.assertEqual(commit1, commit_for_patch[0])
759 series.commits = [commit1]
760 patches = [patch1, patch2]
761 patch_for_commit, commit_for_patch, warnings = (
762 status.compare_with_series(series, patches))
763 self.assertEqual(1, len(patch_for_commit))
764 self.assertEqual(patch1, patch_for_commit[0])
765 self.assertEqual(1, len(commit_for_patch))
766 self.assertEqual(commit1, commit_for_patch[0])
767 self.assertEqual(["Cannot find commit for patch 2 ('Subject 2')"],
770 series.commits = [commit1, commit2]
772 patch_for_commit, commit_for_patch, warnings = (
773 status.compare_with_series(series, patches))
774 self.assertEqual(1, len(patch_for_commit))
775 self.assertEqual(patch1, patch_for_commit[0])
776 self.assertEqual(1, len(commit_for_patch))
777 self.assertEqual(commit1, commit_for_patch[0])
778 self.assertEqual(["Cannot find patch for commit 2 ('Subject 2')"],
781 series.commits = [commit1, commit2, commit3]
782 patches = [patch1, patch2]
783 patch_for_commit, commit_for_patch, warnings = (
784 status.compare_with_series(series, patches))
785 self.assertEqual(2, len(patch_for_commit))
786 self.assertEqual(patch1, patch_for_commit[0])
787 self.assertEqual(patch2, patch_for_commit[1])
788 self.assertEqual(1, len(commit_for_patch))
789 self.assertEqual(commit1, commit_for_patch[0])
790 self.assertEqual(["Cannot find patch for commit 3 ('Subject 2')",
791 "Multiple commits match patch 2 ('Subject 2'):\n"
792 ' Subject 2\n Subject 2'],
795 series.commits = [commit1, commit2]
796 patches = [patch1, patch2, patch3]
797 patch_for_commit, commit_for_patch, warnings = (
798 status.compare_with_series(series, patches))
799 self.assertEqual(1, len(patch_for_commit))
800 self.assertEqual(patch1, patch_for_commit[0])
801 self.assertEqual(2, len(commit_for_patch))
802 self.assertEqual(commit1, commit_for_patch[0])
803 self.assertEqual(["Multiple patches match commit 2 ('Subject 2'):\n"
804 ' Subject 2\n Subject 2',
805 "Cannot find commit for patch 3 ('Subject 2')"],
808 def _fake_patchwork2(self, url, subpath):
809 """Fake Patchwork server for the function below
811 This handles accessing series, patches and comments, providing the data
812 in self.patches to the caller
815 url (str): URL of patchwork server
816 subpath (str): URL subpath to use
818 re_series = re.match(r'series/(\d*)/$', subpath)
819 re_patch = re.match(r'patches/(\d*)/$', subpath)
820 re_comments = re.match(r'patches/(\d*)/comments/$', subpath)
822 series_num = re_series.group(1)
823 if series_num == '1234':
824 return {'patches': self.patches}
826 patch_num = int(re_patch.group(1))
827 patch = self.patches[patch_num - 1]
830 patch_num = int(re_comments.group(1))
831 patch = self.patches[patch_num - 1]
832 return patch.comments
833 raise ValueError('Fake Patchwork does not understand: %s' % subpath)
835 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
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 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
975 def testCreateBranch(self):
976 """Test operation of create_branch()"""
977 repo = self.make_git_tree()
979 dest_branch = 'first2'
981 gitdir = os.path.join(self.gitdir, '.git')
983 # Set up the test git tree. We use branch 'first' which has two commits
985 series = patchstream.get_metadata_for_list(branch, gitdir, count)
986 self.assertEqual(2, len(series.commits))
988 patch1 = status.Patch('1')
989 patch1.parse_subject('[1/2] %s' % series.commits[0].subject)
990 patch1.name = patch1.raw_subject
991 patch1.content = 'This is my patch content'
992 comment1a = {'content': 'Reviewed-by: %s\n' % self.joe}
994 patch1.comments = [comment1a]
996 patch2 = status.Patch('2')
997 patch2.parse_subject('[2/2] %s' % series.commits[1].subject)
998 patch2.name = patch2.raw_subject
999 patch2.content = 'Some other patch content'
1001 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1002 (self.mary, self.leb)}
1004 'content': 'Reviewed-by: %s' % self.fred}
1005 patch2.comments = [comment2a, comment2b]
1007 # This test works by setting up patches for use by the fake Rest API
1008 # function _fake_patchwork3(). The fake patch comments above should
1009 # result in new review tags that are collected and added to the commits
1010 # created in the destination branch.
1011 self.patches = [patch1, patch2]
1016 # + Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1018 # + Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1019 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1020 # + Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1021 # 4 new responses available in patchwork
1022 # 4 responses added from patchwork into new branch 'first2'
1023 # <unittest.result.TestResult run=8 errors=0 failures=0>
1025 terminal.SetPrintTestMode()
1026 status.check_patchwork_status(series, '1234', branch, dest_branch,
1027 False, False, None, self._fake_patchwork3,
1029 lines = terminal.GetPrintTestLines()
1030 self.assertEqual(12, len(lines))
1032 "4 responses added from patchwork into new branch 'first2'",
1035 # Check that the destination branch has the new tags
1036 new_series = patchstream.get_metadata_for_list(dest_branch, gitdir,
1039 {'Reviewed-by': {self.joe}},
1040 new_series.commits[0].rtags)
1042 {'Tested-by': {self.leb},
1043 'Reviewed-by': {self.fred, self.mary}},
1044 new_series.commits[1].rtags)
1046 # Now check the actual test of the first commit message. We expect to
1047 # see the new tags immediately below the old ones.
1048 stdout = patchstream.get_list(dest_branch, count=count, git_dir=gitdir)
1049 lines = iter([line.strip() for line in stdout.splitlines()
1052 # First patch should have the review tag
1053 self.assertEqual('Reviewed-by: %s' % self.joe, next(lines))
1055 # Second patch should have the sign-off then the tested-by and two
1057 self.assertEqual('Signed-off-by: %s' % self.leb, next(lines))
1058 self.assertEqual('Reviewed-by: %s' % self.fred, next(lines))
1059 self.assertEqual('Reviewed-by: %s' % self.mary, next(lines))
1060 self.assertEqual('Tested-by: %s' % self.leb, next(lines))
1062 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
1063 def testParseSnippets(self):
1064 """Test parsing of review snippets"""
1067 This is a comment from someone.
1071 On some recent date, Fred wrote:
1072 > This is why I wrote the patch
1075 Now a comment about the commit message
1076 A little more to say
1080 > diff --git a/file.c b/file.c
1093 > @@ -153,8 +143,13 @@ def CheckPatch(fname, show_types=False):
1094 > further down on the file
1097 > +Another addition here
1101 and another thing in same file
1103 > @@ -253,8 +243,13 @@
1104 > with no function context
1108 > diff --git a/tools/patman/main.py b/tools/patman/main.py
1110 now a very long comment in a different file
1119 pstrm = PatchStream.process_text(text, True)
1120 self.assertEqual([], pstrm.commit.warn)
1122 # We expect to the filename and up to 5 lines of code context before
1123 # each comment. The 'On xxx wrote:' bit should be removed.
1126 'This is a comment from someone.',
1128 ['> This is why I wrote the patch',
1130 'Now a comment about the commit message',
1131 'A little more to say', 'Even more'],
1132 ['> File: file.c', '> Code line 5', '> Code line 6',
1133 '> Code line 7', '> Code line 8', '> Code line 9',
1134 'And another comment'],
1136 '> Line: 153 / 143: def CheckPatch(fname, show_types=False):',
1137 '> and more code', '> +Addition here', '> +Another addition here',
1138 '> codey', '> more codey', 'and another thing in same file'],
1139 ['> File: file.c', '> Line: 253 / 243',
1140 '> with no function context', 'one more thing'],
1141 ['> File: tools/patman/main.py', '> +line of code',
1142 'now a very long comment in a different file',
1143 'line2', 'line3', 'line4', 'line5', 'line6', 'line7', 'line8']],
1146 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
1147 def testReviewSnippets(self):
1148 """Test showing of review snippets"""
1149 def _to_submitter(who):
1150 m_who = re.match('(.*) <(.*)>', who)
1152 'name': m_who.group(1),
1153 'email': m_who.group(2)
1156 commit1 = Commit('abcd')
1157 commit1.subject = 'Subject 1'
1158 commit2 = Commit('ef12')
1159 commit2.subject = 'Subject 2'
1161 patch1 = status.Patch('1')
1162 patch1.parse_subject('[1/2] Subject 1')
1163 patch1.name = patch1.raw_subject
1164 patch1.content = 'This is my patch content'
1165 comment1a = {'submitter': _to_submitter(self.joe),
1166 'content': '''Hi Fred,
1168 On some date Fred wrote:
1170 > diff --git a/file.c b/file.c
1174 Here is my comment above the above...
1180 patch1.comments = [comment1a]
1182 patch2 = status.Patch('2')
1183 patch2.parse_subject('[2/2] Subject 2')
1184 patch2.name = patch2.raw_subject
1185 patch2.content = 'Some other patch content'
1187 'content': 'Reviewed-by: %s\nTested-by: %s\n' %
1188 (self.mary, self.leb)}
1189 comment2b = {'submitter': _to_submitter(self.fred),
1190 'content': '''Hi Fred,
1192 On some date Fred wrote:
1194 > diff --git a/tools/patman/commit.py b/tools/patman/commit.py
1195 > @@ -41,6 +41,9 @@ class Commit:
1196 > self.rtags = collections.defaultdict(set)
1199 > + def __str__(self):
1200 > + return self.subject
1202 > def AddChange(self, version, info):
1203 > """Add a new change line to the change list for a version.
1209 patch2.comments = [comment2a, comment2b]
1211 # This test works by setting up commits and patch for use by the fake
1212 # Rest API function _fake_patchwork2(). It calls various functions in
1213 # the status module after setting up tags in the commits, checking that
1214 # things behaves as expected
1215 self.commits = [commit1, commit2]
1216 self.patches = [patch1, patch2]
1218 # Check that the output patches expectations:
1220 # Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
1222 # Tested-by: Lord Edmund Blackaddër <weasel@blackadder.org>
1223 # Reviewed-by: Fred Bloggs <f.bloggs@napier.net>
1224 # + Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
1225 # 1 new response available in patchwork
1228 series.commits = [commit1, commit2]
1229 terminal.SetPrintTestMode()
1230 status.check_patchwork_status(series, '1234', None, None, False, True,
1231 None, self._fake_patchwork2)
1232 lines = iter(terminal.GetPrintTestLines())
1233 col = terminal.Color()
1234 self.assertEqual(terminal.PrintLine(' 1 Subject 1', col.BLUE),
1237 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1239 self.assertEqual(terminal.PrintLine(self.joe, col.WHITE), next(lines))
1241 self.assertEqual(terminal.PrintLine('Review: %s' % self.joe, col.RED),
1243 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(lines))
1244 self.assertEqual(terminal.PrintLine('', None), next(lines))
1245 self.assertEqual(terminal.PrintLine(' > File: file.c', col.MAGENTA),
1247 self.assertEqual(terminal.PrintLine(' > Some code', col.MAGENTA),
1249 self.assertEqual(terminal.PrintLine(' > and more code', col.MAGENTA),
1251 self.assertEqual(terminal.PrintLine(
1252 ' Here is my comment above the above...', None), next(lines))
1253 self.assertEqual(terminal.PrintLine('', None), next(lines))
1255 self.assertEqual(terminal.PrintLine(' 2 Subject 2', col.BLUE),
1258 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1260 self.assertEqual(terminal.PrintLine(self.fred, col.WHITE),
1263 terminal.PrintLine(' + Reviewed-by: ', col.GREEN, newline=False),
1265 self.assertEqual(terminal.PrintLine(self.mary, col.WHITE),
1268 terminal.PrintLine(' + Tested-by: ', col.GREEN, newline=False),
1270 self.assertEqual(terminal.PrintLine(self.leb, col.WHITE),
1273 self.assertEqual(terminal.PrintLine('Review: %s' % self.fred, col.RED),
1275 self.assertEqual(terminal.PrintLine(' Hi Fred,', None), next(lines))
1276 self.assertEqual(terminal.PrintLine('', None), next(lines))
1277 self.assertEqual(terminal.PrintLine(
1278 ' > File: tools/patman/commit.py', col.MAGENTA), next(lines))
1279 self.assertEqual(terminal.PrintLine(
1280 ' > Line: 41 / 41: class Commit:', col.MAGENTA), next(lines))
1281 self.assertEqual(terminal.PrintLine(
1282 ' > + return self.subject', col.MAGENTA), next(lines))
1283 self.assertEqual(terminal.PrintLine(
1284 ' > +', col.MAGENTA), next(lines))
1286 terminal.PrintLine(' > def AddChange(self, version, info):',
1289 self.assertEqual(terminal.PrintLine(
1290 ' > """Add a new change line to the change list for a version.',
1291 col.MAGENTA), next(lines))
1292 self.assertEqual(terminal.PrintLine(
1293 ' >', col.MAGENTA), next(lines))
1294 self.assertEqual(terminal.PrintLine(
1295 ' A comment', None), next(lines))
1296 self.assertEqual(terminal.PrintLine('', None), next(lines))
1298 self.assertEqual(terminal.PrintLine(
1299 '4 new responses available in patchwork (use -d to write them to a new branch)',