1 # -*- coding: utf-8 -*-
2 # SPDX-License-Identifier: GPL-2.0+
4 # Copyright 2017 Google, Inc
15 from io import StringIO
17 from patman import control
18 from patman import gitutil
19 from patman import patchstream
20 from patman import settings
21 from patman import terminal
22 from patman import tools
23 from patman.test_util import capture_sys_output
28 except ModuleNotFoundError:
32 @contextlib.contextmanager
34 oldout, olderr = sys.stdout, sys.stderr
36 out = [StringIO(), StringIO()]
37 sys.stdout, sys.stderr = out
40 sys.stdout, sys.stderr = oldout, olderr
41 out[0] = out[0].getvalue()
42 out[1] = out[1].getvalue()
45 class TestFunctional(unittest.TestCase):
47 self.tmpdir = tempfile.mkdtemp(prefix='patman.')
48 self.gitdir = os.path.join(self.tmpdir, 'git')
52 shutil.rmtree(self.tmpdir)
56 return os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
60 def GetText(self, fname):
61 return open(self.GetPath(fname), encoding='utf-8').read()
64 def GetPatchName(self, subject):
65 fname = re.sub('[ :]', '-', subject)
66 return fname.replace('--', '-')
68 def CreatePatchesForTest(self, series):
71 for i, commit in enumerate(series.commits):
72 clean_subject = self.GetPatchName(commit.subject)
73 src_fname = '%04d-%s.patch' % (i + 1, clean_subject[:52])
74 fname = os.path.join(self.tmpdir, src_fname)
75 shutil.copy(self.GetPath(src_fname), fname)
76 fname_list.append(fname)
77 if series.get('cover'):
78 src_fname = '0000-cover-letter.patch'
79 cover_fname = os.path.join(self.tmpdir, src_fname)
80 fname = os.path.join(self.tmpdir, src_fname)
81 shutil.copy(self.GetPath(src_fname), fname)
83 return cover_fname, fname_list
86 """Tests the basic flow of patman
88 This creates a series from some hard-coded patches build from a simple
89 tree with the following metadata in the top commit:
93 Series-cc: Stefan Brüns <stefan.bruens@rwth-aachen.de>
94 Cover-letter-cc: Lord Mëlchett <clergy@palace.gov>
97 Series-process-log: sort, uniq
105 - Changes only for this commit
108 - Some notes for the cover letter
111 test: A test patch series
112 This is a test of how the cover
117 and this in the first commit:
120 - second revision change
125 from the first commit
133 with the following commands:
135 git log -n2 --reverse >/path/to/tools/patman/test/test01.txt
136 git format-patch --subject-prefix RFC --cover-letter HEAD~2
137 mv 00* /path/to/tools/patman/test
139 It checks these aspects:
140 - git log can be processed by patchstream
141 - emailing patches uses the correct command
142 - CC file has information on each commit
143 - cover letter has the expected text and subject
144 - each patch has the correct subject
145 - dry-run information prints out correctly
146 - unicode is handled correctly
147 - Series-to, Series-cc, Series-prefix, Cover-letter
148 - Cover-letter-cc, Series-version, Series-changes, Series-notes
152 ignore_bad_tags = True
153 stefan = b'Stefan Br\xc3\xbcns <stefan.bruens@rwth-aachen.de>'.decode('utf-8')
154 rick = 'Richard III <richard@palace.gov>'
155 mel = b'Lord M\xc3\xablchett <clergy@palace.gov>'.decode('utf-8')
156 ed = b'Lond Edmund Blackadd\xc3\xabr <weasel@blackadder.org'.decode('utf-8')
157 fred = 'Fred Bloggs <f.bloggs@napier.net>'
158 add_maintainers = [stefan, rick]
164 'u-boot': ['u-boot@lists.denx.de'],
169 text = self.GetText('test01.txt')
170 series = patchstream.GetMetaDataForTest(text)
171 cover_fname, args = self.CreatePatchesForTest(series)
172 with capture() as out:
173 patchstream.FixPatches(series, args)
174 if cover_fname and series.get('cover'):
175 patchstream.InsertCoverLetter(cover_fname, series, count)
177 cc_file = series.MakeCcFile(process_tags, cover_fname,
178 not ignore_bad_tags, add_maintainers,
180 cmd = gitutil.EmailPatches(
181 series, cover_fname, args, dry_run, not ignore_bad_tags,
182 cc_file, in_reply_to=in_reply_to, thread=None)
183 series.ShowActions(args, cmd, process_tags)
184 cc_lines = open(cc_file, encoding='utf-8').read().splitlines()
187 lines = out[0].splitlines()
188 self.assertEqual('Cleaned %s patches' % len(series.commits), lines[0])
189 self.assertEqual('Change log missing for v2', lines[1])
190 self.assertEqual('Change log missing for v3', lines[2])
191 self.assertEqual('Change log for unknown version v4', lines[3])
192 self.assertEqual("Alias 'pci' not found", lines[4])
193 self.assertIn('Dry run', lines[5])
194 self.assertIn('Send a total of %d patches' % count, lines[7])
196 for i, commit in enumerate(series.commits):
197 self.assertEqual(' %s' % args[i], lines[line + 0])
199 while 'Cc:' in lines[line]:
201 self.assertEqual('To: u-boot@lists.denx.de', lines[line])
202 self.assertEqual('Cc: %s' % tools.FromUnicode(stefan),
204 self.assertEqual('Version: 3', lines[line + 2])
205 self.assertEqual('Prefix:\t RFC', lines[line + 3])
206 self.assertEqual('Cover: 4 lines', lines[line + 4])
208 self.assertEqual(' Cc: %s' % fred, lines[line + 0])
209 self.assertEqual(' Cc: %s' % tools.FromUnicode(ed),
211 self.assertEqual(' Cc: %s' % tools.FromUnicode(mel),
213 self.assertEqual(' Cc: %s' % rick, lines[line + 3])
214 expected = ('Git command: git send-email --annotate '
215 '--in-reply-to="%s" --to "u-boot@lists.denx.de" '
216 '--cc "%s" --cc-cmd "%s --cc-cmd %s" %s %s'
217 % (in_reply_to, stefan, sys.argv[0], cc_file, cover_fname,
220 self.assertEqual(expected, tools.ToUnicode(lines[line]))
222 self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)),
223 tools.ToUnicode(cc_lines[0]))
225 '%s %s\0%s\0%s\0%s' % (args[1], fred, ed, rick, stefan),
226 tools.ToUnicode(cc_lines[1]))
229 This is a test of how the cover
235 from the first commit
242 - Some notes for the cover letter
245 pci: Correct cast for sandbox
246 fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
250 lib/efi_loader/efi_memory.c | 1 +
252 4 files changed, 6 insertions(+), 2 deletions(-)
258 lines = open(cover_fname, encoding='utf-8').read().splitlines()
260 'Subject: [RFC PATCH v3 0/2] test: A test patch series',
262 self.assertEqual(expected.splitlines(), lines[7:])
264 for i, fname in enumerate(args):
265 lines = open(fname, encoding='utf-8').read().splitlines()
266 subject = [line for line in lines if line.startswith('Subject')]
267 self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count),
270 # Check that we got our commit notes
280 (no changes since v2)
283 - second revision change'''
295 - Changes only for this commit'''
298 expected = expected.splitlines()
299 self.assertEqual(expected, lines[start:(start+len(expected))])
301 def make_commit_with_file(self, subject, body, fname, text):
302 """Create a file and add it to the git repo with a new commit
305 subject (str): Subject for the commit
306 body (str): Body text of the commit
307 fname (str): Filename of file to create
308 text (str): Text to put into the file
310 path = os.path.join(self.gitdir, fname)
311 tools.WriteFile(path, text, binary=False)
312 index = self.repo.index
314 author = pygit2.Signature('Test user', 'test@email.com')
316 tree = index.write_tree()
317 message = subject + '\n' + body
318 self.repo.create_commit('HEAD', author, committer, message, tree,
319 [self.repo.head.target])
321 def make_git_tree(self):
322 """Make a simple git tree suitable for testing
324 It has three branches:
325 'base' has two commits: PCI, main
326 'first' has base as upstream and two more commits: I2C, SPI
327 'second' has base as upstream and three more: video, serial, bootm
332 repo = pygit2.init_repository(self.gitdir)
334 new_tree = repo.TreeBuilder().write()
336 author = pygit2.Signature('Test user', 'test@email.com')
338 commit = repo.create_commit('HEAD', author, committer,
339 'Created master', new_tree, [])
341 self.make_commit_with_file('Initial commit', '''
344 ''', 'README', '''This is the README file
345 describing this project
346 in very little detail''')
348 self.make_commit_with_file('pci: PCI implementation', '''
349 Here is a basic PCI implementation
351 ''', 'pci.c', '''This is a file
353 and some more things''')
354 self.make_commit_with_file('main: Main program', '''
355 Hello here is the second commit.
356 ''', 'main.c', '''This is the main file
357 there is very little here
358 but we can always add more later
362 Series-cc: Barry Crump <bcrump@whataroa.nz>
364 base_target = repo.revparse_single('HEAD')
365 self.make_commit_with_file('i2c: I2C things', '''
366 This has some stuff to do with I2C
367 ''', 'i2c.c', '''And this is the file contents
368 with some I2C-related things in it''')
369 self.make_commit_with_file('spi: SPI fixes', '''
372 ''', 'spi.c', '''Some fixes for SPI in this
373 file to make SPI work
374 better than before''')
375 first_target = repo.revparse_single('HEAD')
377 target = repo.revparse_single('HEAD~2')
378 repo.reset(target.oid, pygit2.GIT_CHECKOUT_FORCE)
379 self.make_commit_with_file('video: Some video improvements', '''
380 Fix up the video so that
381 it looks more purple. Purple is
383 ''', 'video.c', '''More purple here
386 Could not be any more purple''')
387 self.make_commit_with_file('serial: Add a serial driver', '''
388 Here is the serial driver
393 This series implements support
394 for my glorious board.
396 ''', 'serial.c', '''The code for the
397 serial driver is here''')
398 self.make_commit_with_file('bootm: Make it boot', '''
399 This makes my board boot
400 with a fix to the bootm
402 ''', 'bootm.c', '''Fix up the bootm
403 command to make the code as
404 complicated as possible''')
405 second_target = repo.revparse_single('HEAD')
407 repo.branches.local.create('first', first_target)
408 repo.config.set_multivar('branch.first.remote', '', '.')
409 repo.config.set_multivar('branch.first.merge', '', 'refs/heads/base')
411 repo.branches.local.create('second', second_target)
412 repo.config.set_multivar('branch.second.remote', '', '.')
413 repo.config.set_multivar('branch.second.merge', '', 'refs/heads/base')
415 repo.branches.local.create('base', base_target)
418 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
419 def testBranch(self):
420 """Test creating patches from a branch"""
421 repo = self.make_git_tree()
422 target = repo.lookup_reference('refs/heads/first')
423 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
426 orig_dir = os.getcwd()
427 os.chdir(self.gitdir)
429 # Check that it can detect the current branch
430 self.assertEqual(2, gitutil.CountCommitsToBranch(None))
431 col = terminal.Color()
432 with capture_sys_output() as _:
433 _, cover_fname, patch_files = control.prepare_patches(
434 col, branch=None, count=-1, start=0, end=0,
436 self.assertIsNone(cover_fname)
437 self.assertEqual(2, len(patch_files))
439 # Check that it can detect a different branch
440 self.assertEqual(3, gitutil.CountCommitsToBranch('second'))
441 with capture_sys_output() as _:
442 _, cover_fname, patch_files = control.prepare_patches(
443 col, branch='second', count=-1, start=0, end=0,
445 self.assertIsNotNone(cover_fname)
446 self.assertEqual(3, len(patch_files))
448 # Check that it can skip patches at the end
449 with capture_sys_output() as _:
450 _, cover_fname, patch_files = control.prepare_patches(
451 col, branch='second', count=-1, start=0, end=1,
453 self.assertIsNotNone(cover_fname)
454 self.assertEqual(2, len(patch_files))