1# -*- coding: utf-8 -*- 2# SPDX-License-Identifier: GPL-2.0+ 3# 4# Copyright 2017 Google, Inc 5# 6 7import contextlib 8import os 9import re 10import shutil 11import sys 12import tempfile 13import unittest 14 15try: 16 from StringIO import StringIO 17except ImportError: 18 from io import StringIO 19 20import gitutil 21import patchstream 22import settings 23import tools 24 25 26@contextlib.contextmanager 27def capture(): 28 import sys 29 oldout,olderr = sys.stdout, sys.stderr 30 try: 31 out=[StringIO(), StringIO()] 32 sys.stdout,sys.stderr = out 33 yield out 34 finally: 35 sys.stdout,sys.stderr = oldout, olderr 36 out[0] = out[0].getvalue() 37 out[1] = out[1].getvalue() 38 39 40class TestFunctional(unittest.TestCase): 41 def setUp(self): 42 self.tmpdir = tempfile.mkdtemp(prefix='patman.') 43 44 def tearDown(self): 45 shutil.rmtree(self.tmpdir) 46 47 @staticmethod 48 def GetPath(fname): 49 return os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), 50 'test', fname) 51 52 @classmethod 53 def GetText(self, fname): 54 return open(self.GetPath(fname), encoding='utf-8').read() 55 56 @classmethod 57 def GetPatchName(self, subject): 58 fname = re.sub('[ :]', '-', subject) 59 return fname.replace('--', '-') 60 61 def CreatePatchesForTest(self, series): 62 cover_fname = None 63 fname_list = [] 64 for i, commit in enumerate(series.commits): 65 clean_subject = self.GetPatchName(commit.subject) 66 src_fname = '%04d-%s.patch' % (i + 1, clean_subject[:52]) 67 fname = os.path.join(self.tmpdir, src_fname) 68 shutil.copy(self.GetPath(src_fname), fname) 69 fname_list.append(fname) 70 if series.get('cover'): 71 src_fname = '0000-cover-letter.patch' 72 cover_fname = os.path.join(self.tmpdir, src_fname) 73 fname = os.path.join(self.tmpdir, src_fname) 74 shutil.copy(self.GetPath(src_fname), fname) 75 76 return cover_fname, fname_list 77 78 def testBasic(self): 79 """Tests the basic flow of patman 80 81 This creates a series from some hard-coded patches build from a simple 82 tree with the following metadata in the top commit: 83 84 Series-to: u-boot 85 Series-prefix: RFC 86 Series-cc: Stefan Brüns <stefan.bruens@rwth-aachen.de> 87 Cover-letter-cc: Lord Mëlchett <clergy@palace.gov> 88 Series-version: 2 89 Series-changes: 4 90 - Some changes 91 92 Cover-letter: 93 test: A test patch series 94 This is a test of how the cover 95 leter 96 works 97 END 98 99 and this in the first commit: 100 101 Series-notes: 102 some notes 103 about some things 104 from the first commit 105 END 106 107 Commit-notes: 108 Some notes about 109 the first commit 110 END 111 112 with the following commands: 113 114 git log -n2 --reverse >/path/to/tools/patman/test/test01.txt 115 git format-patch --subject-prefix RFC --cover-letter HEAD~2 116 mv 00* /path/to/tools/patman/test 117 118 It checks these aspects: 119 - git log can be processed by patchstream 120 - emailing patches uses the correct command 121 - CC file has information on each commit 122 - cover letter has the expected text and subject 123 - each patch has the correct subject 124 - dry-run information prints out correctly 125 - unicode is handled correctly 126 - Series-to, Series-cc, Series-prefix, Cover-letter 127 - Cover-letter-cc, Series-version, Series-changes, Series-notes 128 - Commit-notes 129 """ 130 process_tags = True 131 ignore_bad_tags = True 132 stefan = b'Stefan Br\xc3\xbcns <stefan.bruens@rwth-aachen.de>'.decode('utf-8') 133 rick = 'Richard III <richard@palace.gov>' 134 mel = b'Lord M\xc3\xablchett <clergy@palace.gov>'.decode('utf-8') 135 ed = b'Lond Edmund Blackadd\xc3\xabr <weasel@blackadder.org'.decode('utf-8') 136 fred = 'Fred Bloggs <f.bloggs@napier.net>' 137 add_maintainers = [stefan, rick] 138 dry_run = True 139 in_reply_to = mel 140 count = 2 141 settings.alias = { 142 'fdt': ['simon'], 143 'u-boot': ['u-boot@lists.denx.de'], 144 'simon': [ed], 145 'fred': [fred], 146 } 147 148 text = self.GetText('test01.txt') 149 series = patchstream.GetMetaDataForTest(text) 150 cover_fname, args = self.CreatePatchesForTest(series) 151 with capture() as out: 152 patchstream.FixPatches(series, args) 153 if cover_fname and series.get('cover'): 154 patchstream.InsertCoverLetter(cover_fname, series, count) 155 series.DoChecks() 156 cc_file = series.MakeCcFile(process_tags, cover_fname, 157 not ignore_bad_tags, add_maintainers, 158 None) 159 cmd = gitutil.EmailPatches(series, cover_fname, args, 160 dry_run, not ignore_bad_tags, cc_file, 161 in_reply_to=in_reply_to, thread=None) 162 series.ShowActions(args, cmd, process_tags) 163 cc_lines = open(cc_file, encoding='utf-8').read().splitlines() 164 os.remove(cc_file) 165 166 lines = out[0].splitlines() 167 self.assertEqual('Cleaned %s patches' % len(series.commits), lines[0]) 168 self.assertEqual('Change log missing for v2', lines[1]) 169 self.assertEqual('Change log missing for v3', lines[2]) 170 self.assertEqual('Change log for unknown version v4', lines[3]) 171 self.assertEqual("Alias 'pci' not found", lines[4]) 172 self.assertIn('Dry run', lines[5]) 173 self.assertIn('Send a total of %d patches' % count, lines[7]) 174 line = 8 175 for i, commit in enumerate(series.commits): 176 self.assertEqual(' %s' % args[i], lines[line + 0]) 177 line += 1 178 while 'Cc:' in lines[line]: 179 line += 1 180 self.assertEqual('To: u-boot@lists.denx.de', lines[line]) 181 self.assertEqual('Cc: %s' % tools.FromUnicode(stefan), 182 lines[line + 1]) 183 self.assertEqual('Version: 3', lines[line + 2]) 184 self.assertEqual('Prefix:\t RFC', lines[line + 3]) 185 self.assertEqual('Cover: 4 lines', lines[line + 4]) 186 line += 5 187 self.assertEqual(' Cc: %s' % fred, lines[line + 0]) 188 self.assertEqual(' Cc: %s' % tools.FromUnicode(ed), 189 lines[line + 1]) 190 self.assertEqual(' Cc: %s' % tools.FromUnicode(mel), 191 lines[line + 2]) 192 self.assertEqual(' Cc: %s' % rick, lines[line + 3]) 193 expected = ('Git command: git send-email --annotate ' 194 '--in-reply-to="%s" --to "u-boot@lists.denx.de" ' 195 '--cc "%s" --cc-cmd "%s --cc-cmd %s" %s %s' 196 % (in_reply_to, stefan, sys.argv[0], cc_file, cover_fname, 197 ' '.join(args))) 198 line += 4 199 self.assertEqual(expected, tools.ToUnicode(lines[line])) 200 201 self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)), 202 tools.ToUnicode(cc_lines[0])) 203 self.assertEqual(('%s %s\0%s\0%s\0%s' % (args[1], fred, ed, rick, 204 stefan)), tools.ToUnicode(cc_lines[1])) 205 206 expected = ''' 207This is a test of how the cover 208leter 209works 210 211some notes 212about some things 213from the first commit 214 215Changes in v4: 216- Some changes 217 218Simon Glass (2): 219 pci: Correct cast for sandbox 220 fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base() 221 222 cmd/pci.c | 3 ++- 223 fs/fat/fat.c | 1 + 224 lib/efi_loader/efi_memory.c | 1 + 225 lib/fdtdec.c | 3 ++- 226 4 files changed, 6 insertions(+), 2 deletions(-) 227 228--\x20 2292.7.4 230 231''' 232 lines = open(cover_fname, encoding='utf-8').read().splitlines() 233 self.assertEqual( 234 'Subject: [RFC PATCH v3 0/2] test: A test patch series', 235 lines[3]) 236 self.assertEqual(expected.splitlines(), lines[7:]) 237 238 for i, fname in enumerate(args): 239 lines = open(fname, encoding='utf-8').read().splitlines() 240 subject = [line for line in lines if line.startswith('Subject')] 241 self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count), 242 subject[0][:18]) 243 if i == 0: 244 # Check that we got our commit notes 245 self.assertEqual('---', lines[17]) 246 self.assertEqual('Some notes about', lines[18]) 247 self.assertEqual('the first commit', lines[19]) 248