blob: 263cb340ef56f3fc5d01392e0de1785cb4dc1ad4 [file] [log] [blame]
Simon Glass6e87ae12017-05-29 15:31:31 -06001# -*- coding: utf-8 -*-
Tom Rini83d290c2018-05-06 17:58:06 -04002# SPDX-License-Identifier: GPL-2.0+
Simon Glass6e87ae12017-05-29 15:31:31 -06003#
4# Copyright 2017 Google, Inc
5#
Simon Glass6e87ae12017-05-29 15:31:31 -06006
Simon Glass6e87ae12017-05-29 15:31:31 -06007import os
8import re
9import shutil
10import sys
11import tempfile
12import unittest
13
Simon Glassfd709862020-07-05 21:41:50 -060014from patman import control
Simon Glassbf776672020-04-17 18:09:04 -060015from patman import gitutil
16from patman import patchstream
17from patman import settings
Simon Glassfd709862020-07-05 21:41:50 -060018from patman import terminal
Simon Glassbf776672020-04-17 18:09:04 -060019from patman import tools
Simon Glassfd709862020-07-05 21:41:50 -060020from patman.test_util import capture_sys_output
21
22try:
23 import pygit2
Simon Glass427b0282020-10-29 21:46:13 -060024 HAVE_PYGIT2 = True
Simon Glassfd709862020-07-05 21:41:50 -060025except ModuleNotFoundError:
26 HAVE_PYGIT2 = False
Simon Glass6e87ae12017-05-29 15:31:31 -060027
28
Simon Glass6e87ae12017-05-29 15:31:31 -060029class TestFunctional(unittest.TestCase):
30 def setUp(self):
31 self.tmpdir = tempfile.mkdtemp(prefix='patman.')
Simon Glassfd709862020-07-05 21:41:50 -060032 self.gitdir = os.path.join(self.tmpdir, 'git')
33 self.repo = None
Simon Glass6e87ae12017-05-29 15:31:31 -060034
35 def tearDown(self):
36 shutil.rmtree(self.tmpdir)
37
38 @staticmethod
39 def GetPath(fname):
40 return os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
41 'test', fname)
42
43 @classmethod
44 def GetText(self, fname):
Simon Glass272cd852019-10-31 07:42:51 -060045 return open(self.GetPath(fname), encoding='utf-8').read()
Simon Glass6e87ae12017-05-29 15:31:31 -060046
47 @classmethod
48 def GetPatchName(self, subject):
49 fname = re.sub('[ :]', '-', subject)
50 return fname.replace('--', '-')
51
52 def CreatePatchesForTest(self, series):
53 cover_fname = None
54 fname_list = []
55 for i, commit in enumerate(series.commits):
56 clean_subject = self.GetPatchName(commit.subject)
57 src_fname = '%04d-%s.patch' % (i + 1, clean_subject[:52])
58 fname = os.path.join(self.tmpdir, src_fname)
59 shutil.copy(self.GetPath(src_fname), fname)
60 fname_list.append(fname)
61 if series.get('cover'):
62 src_fname = '0000-cover-letter.patch'
63 cover_fname = os.path.join(self.tmpdir, src_fname)
64 fname = os.path.join(self.tmpdir, src_fname)
65 shutil.copy(self.GetPath(src_fname), fname)
66
67 return cover_fname, fname_list
68
69 def testBasic(self):
70 """Tests the basic flow of patman
71
72 This creates a series from some hard-coded patches build from a simple
73 tree with the following metadata in the top commit:
74
75 Series-to: u-boot
76 Series-prefix: RFC
77 Series-cc: Stefan Brüns <stefan.bruens@rwth-aachen.de>
78 Cover-letter-cc: Lord Mëlchett <clergy@palace.gov>
Sean Andersondc03ba42020-05-04 16:28:36 -040079 Series-version: 3
80 Patch-cc: fred
81 Series-process-log: sort, uniq
Simon Glass6e87ae12017-05-29 15:31:31 -060082 Series-changes: 4
83 - Some changes
Sean Andersondc03ba42020-05-04 16:28:36 -040084 - Multi
85 line
86 change
87
88 Commit-changes: 2
89 - Changes only for this commit
90
91 Cover-changes: 4
92 - Some notes for the cover letter
Simon Glass6e87ae12017-05-29 15:31:31 -060093
94 Cover-letter:
95 test: A test patch series
96 This is a test of how the cover
Sean Andersondc03ba42020-05-04 16:28:36 -040097 letter
Simon Glass6e87ae12017-05-29 15:31:31 -060098 works
99 END
100
101 and this in the first commit:
102
Sean Andersondc03ba42020-05-04 16:28:36 -0400103 Commit-changes: 2
104 - second revision change
105
Simon Glass6e87ae12017-05-29 15:31:31 -0600106 Series-notes:
107 some notes
108 about some things
109 from the first commit
110 END
111
112 Commit-notes:
113 Some notes about
114 the first commit
115 END
116
117 with the following commands:
118
119 git log -n2 --reverse >/path/to/tools/patman/test/test01.txt
120 git format-patch --subject-prefix RFC --cover-letter HEAD~2
121 mv 00* /path/to/tools/patman/test
122
123 It checks these aspects:
124 - git log can be processed by patchstream
125 - emailing patches uses the correct command
126 - CC file has information on each commit
127 - cover letter has the expected text and subject
128 - each patch has the correct subject
129 - dry-run information prints out correctly
130 - unicode is handled correctly
131 - Series-to, Series-cc, Series-prefix, Cover-letter
132 - Cover-letter-cc, Series-version, Series-changes, Series-notes
133 - Commit-notes
134 """
135 process_tags = True
136 ignore_bad_tags = True
Simon Glasse6dca5e2019-05-14 15:53:53 -0600137 stefan = b'Stefan Br\xc3\xbcns <stefan.bruens@rwth-aachen.de>'.decode('utf-8')
Simon Glass6e87ae12017-05-29 15:31:31 -0600138 rick = 'Richard III <richard@palace.gov>'
Simon Glasse6dca5e2019-05-14 15:53:53 -0600139 mel = b'Lord M\xc3\xablchett <clergy@palace.gov>'.decode('utf-8')
140 ed = b'Lond Edmund Blackadd\xc3\xabr <weasel@blackadder.org'.decode('utf-8')
Simon Glass6e87ae12017-05-29 15:31:31 -0600141 fred = 'Fred Bloggs <f.bloggs@napier.net>'
142 add_maintainers = [stefan, rick]
143 dry_run = True
144 in_reply_to = mel
145 count = 2
146 settings.alias = {
Simon Glass427b0282020-10-29 21:46:13 -0600147 'fdt': ['simon'],
148 'u-boot': ['u-boot@lists.denx.de'],
149 'simon': [ed],
150 'fred': [fred],
Simon Glass6e87ae12017-05-29 15:31:31 -0600151 }
152
153 text = self.GetText('test01.txt')
154 series = patchstream.GetMetaDataForTest(text)
155 cover_fname, args = self.CreatePatchesForTest(series)
Simon Glass366954f2020-10-29 21:46:14 -0600156 with capture_sys_output() as out:
Simon Glass6e87ae12017-05-29 15:31:31 -0600157 patchstream.FixPatches(series, args)
158 if cover_fname and series.get('cover'):
159 patchstream.InsertCoverLetter(cover_fname, series, count)
160 series.DoChecks()
161 cc_file = series.MakeCcFile(process_tags, cover_fname,
Chris Packham4fb35022018-06-07 20:45:06 +1200162 not ignore_bad_tags, add_maintainers,
163 None)
Simon Glass427b0282020-10-29 21:46:13 -0600164 cmd = gitutil.EmailPatches(
165 series, cover_fname, args, dry_run, not ignore_bad_tags,
166 cc_file, in_reply_to=in_reply_to, thread=None)
Simon Glass6e87ae12017-05-29 15:31:31 -0600167 series.ShowActions(args, cmd, process_tags)
Simon Glass272cd852019-10-31 07:42:51 -0600168 cc_lines = open(cc_file, encoding='utf-8').read().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600169 os.remove(cc_file)
170
Simon Glass366954f2020-10-29 21:46:14 -0600171 lines = out[0].getvalue().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600172 self.assertEqual('Cleaned %s patches' % len(series.commits), lines[0])
173 self.assertEqual('Change log missing for v2', lines[1])
174 self.assertEqual('Change log missing for v3', lines[2])
175 self.assertEqual('Change log for unknown version v4', lines[3])
176 self.assertEqual("Alias 'pci' not found", lines[4])
177 self.assertIn('Dry run', lines[5])
178 self.assertIn('Send a total of %d patches' % count, lines[7])
179 line = 8
180 for i, commit in enumerate(series.commits):
181 self.assertEqual(' %s' % args[i], lines[line + 0])
182 line += 1
183 while 'Cc:' in lines[line]:
184 line += 1
185 self.assertEqual('To: u-boot@lists.denx.de', lines[line])
Simon Glasse6dca5e2019-05-14 15:53:53 -0600186 self.assertEqual('Cc: %s' % tools.FromUnicode(stefan),
187 lines[line + 1])
Simon Glass6e87ae12017-05-29 15:31:31 -0600188 self.assertEqual('Version: 3', lines[line + 2])
189 self.assertEqual('Prefix:\t RFC', lines[line + 3])
190 self.assertEqual('Cover: 4 lines', lines[line + 4])
191 line += 5
Simon Glassb644c662019-05-14 15:53:51 -0600192 self.assertEqual(' Cc: %s' % fred, lines[line + 0])
Simon Glasse6dca5e2019-05-14 15:53:53 -0600193 self.assertEqual(' Cc: %s' % tools.FromUnicode(ed),
194 lines[line + 1])
195 self.assertEqual(' Cc: %s' % tools.FromUnicode(mel),
196 lines[line + 2])
Simon Glassb644c662019-05-14 15:53:51 -0600197 self.assertEqual(' Cc: %s' % rick, lines[line + 3])
Simon Glass6e87ae12017-05-29 15:31:31 -0600198 expected = ('Git command: git send-email --annotate '
199 '--in-reply-to="%s" --to "u-boot@lists.denx.de" '
200 '--cc "%s" --cc-cmd "%s --cc-cmd %s" %s %s'
201 % (in_reply_to, stefan, sys.argv[0], cc_file, cover_fname,
Simon Glasse6dca5e2019-05-14 15:53:53 -0600202 ' '.join(args)))
Simon Glass6e87ae12017-05-29 15:31:31 -0600203 line += 4
Simon Glasse6dca5e2019-05-14 15:53:53 -0600204 self.assertEqual(expected, tools.ToUnicode(lines[line]))
Simon Glass6e87ae12017-05-29 15:31:31 -0600205
Dmitry Torokhov8ab452d2019-10-21 20:09:56 -0700206 self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)),
Simon Glasse6dca5e2019-05-14 15:53:53 -0600207 tools.ToUnicode(cc_lines[0]))
Simon Glass427b0282020-10-29 21:46:13 -0600208 self.assertEqual(
209 '%s %s\0%s\0%s\0%s' % (args[1], fred, ed, rick, stefan),
210 tools.ToUnicode(cc_lines[1]))
Simon Glass6e87ae12017-05-29 15:31:31 -0600211
212 expected = '''
213This is a test of how the cover
Sean Andersondc03ba42020-05-04 16:28:36 -0400214letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600215works
216
217some notes
218about some things
219from the first commit
220
221Changes in v4:
Sean Andersondc03ba42020-05-04 16:28:36 -0400222- Multi
223 line
224 change
Simon Glass6e87ae12017-05-29 15:31:31 -0600225- Some changes
Sean Andersondc03ba42020-05-04 16:28:36 -0400226- Some notes for the cover letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600227
228Simon Glass (2):
229 pci: Correct cast for sandbox
Siva Durga Prasad Paladugu12308b12018-07-16 15:56:11 +0530230 fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
Simon Glass6e87ae12017-05-29 15:31:31 -0600231
232 cmd/pci.c | 3 ++-
233 fs/fat/fat.c | 1 +
234 lib/efi_loader/efi_memory.c | 1 +
235 lib/fdtdec.c | 3 ++-
236 4 files changed, 6 insertions(+), 2 deletions(-)
237
238--\x20
2392.7.4
240
241'''
Simon Glass272cd852019-10-31 07:42:51 -0600242 lines = open(cover_fname, encoding='utf-8').read().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600243 self.assertEqual(
Simon Glass427b0282020-10-29 21:46:13 -0600244 'Subject: [RFC PATCH v3 0/2] test: A test patch series',
245 lines[3])
Simon Glass6e87ae12017-05-29 15:31:31 -0600246 self.assertEqual(expected.splitlines(), lines[7:])
247
248 for i, fname in enumerate(args):
Simon Glass272cd852019-10-31 07:42:51 -0600249 lines = open(fname, encoding='utf-8').read().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600250 subject = [line for line in lines if line.startswith('Subject')]
251 self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count),
252 subject[0][:18])
Sean Andersondc03ba42020-05-04 16:28:36 -0400253
254 # Check that we got our commit notes
255 start = 0
256 expected = ''
257
Simon Glass6e87ae12017-05-29 15:31:31 -0600258 if i == 0:
Sean Andersondc03ba42020-05-04 16:28:36 -0400259 start = 17
260 expected = '''---
261Some notes about
262the first commit
263
264(no changes since v2)
265
266Changes in v2:
267- second revision change'''
268 elif i == 1:
269 start = 17
270 expected = '''---
271
272Changes in v4:
273- Multi
274 line
275 change
276- Some changes
277
278Changes in v2:
279- Changes only for this commit'''
280
281 if expected:
282 expected = expected.splitlines()
283 self.assertEqual(expected, lines[start:(start+len(expected))])
Simon Glassfd709862020-07-05 21:41:50 -0600284
285 def make_commit_with_file(self, subject, body, fname, text):
286 """Create a file and add it to the git repo with a new commit
287
288 Args:
289 subject (str): Subject for the commit
290 body (str): Body text of the commit
291 fname (str): Filename of file to create
292 text (str): Text to put into the file
293 """
294 path = os.path.join(self.gitdir, fname)
295 tools.WriteFile(path, text, binary=False)
296 index = self.repo.index
297 index.add(fname)
Simon Glass427b0282020-10-29 21:46:13 -0600298 author = pygit2.Signature('Test user', 'test@email.com')
Simon Glassfd709862020-07-05 21:41:50 -0600299 committer = author
300 tree = index.write_tree()
301 message = subject + '\n' + body
302 self.repo.create_commit('HEAD', author, committer, message, tree,
303 [self.repo.head.target])
304
305 def make_git_tree(self):
306 """Make a simple git tree suitable for testing
307
308 It has three branches:
309 'base' has two commits: PCI, main
310 'first' has base as upstream and two more commits: I2C, SPI
311 'second' has base as upstream and three more: video, serial, bootm
312
313 Returns:
314 pygit2 repository
315 """
316 repo = pygit2.init_repository(self.gitdir)
317 self.repo = repo
318 new_tree = repo.TreeBuilder().write()
319
320 author = pygit2.Signature('Test user', 'test@email.com')
321 committer = author
322 commit = repo.create_commit('HEAD', author, committer,
Simon Glass427b0282020-10-29 21:46:13 -0600323 'Created master', new_tree, [])
Simon Glassfd709862020-07-05 21:41:50 -0600324
325 self.make_commit_with_file('Initial commit', '''
326Add a README
327
328''', 'README', '''This is the README file
329describing this project
330in very little detail''')
331
332 self.make_commit_with_file('pci: PCI implementation', '''
333Here is a basic PCI implementation
334
335''', 'pci.c', '''This is a file
336it has some contents
337and some more things''')
338 self.make_commit_with_file('main: Main program', '''
339Hello here is the second commit.
340''', 'main.c', '''This is the main file
341there is very little here
342but we can always add more later
343if we want to
344
345Series-to: u-boot
346Series-cc: Barry Crump <bcrump@whataroa.nz>
347''')
348 base_target = repo.revparse_single('HEAD')
349 self.make_commit_with_file('i2c: I2C things', '''
350This has some stuff to do with I2C
351''', 'i2c.c', '''And this is the file contents
352with some I2C-related things in it''')
353 self.make_commit_with_file('spi: SPI fixes', '''
354SPI needs some fixes
355and here they are
356''', 'spi.c', '''Some fixes for SPI in this
357file to make SPI work
358better than before''')
359 first_target = repo.revparse_single('HEAD')
360
361 target = repo.revparse_single('HEAD~2')
362 repo.reset(target.oid, pygit2.GIT_CHECKOUT_FORCE)
363 self.make_commit_with_file('video: Some video improvements', '''
364Fix up the video so that
365it looks more purple. Purple is
366a very nice colour.
367''', 'video.c', '''More purple here
368Purple and purple
369Even more purple
370Could not be any more purple''')
371 self.make_commit_with_file('serial: Add a serial driver', '''
372Here is the serial driver
373for my chip.
374
375Cover-letter:
376Series for my board
377This series implements support
378for my glorious board.
379END
380''', 'serial.c', '''The code for the
381serial driver is here''')
382 self.make_commit_with_file('bootm: Make it boot', '''
383This makes my board boot
384with a fix to the bootm
385command
386''', 'bootm.c', '''Fix up the bootm
387command to make the code as
388complicated as possible''')
389 second_target = repo.revparse_single('HEAD')
390
391 repo.branches.local.create('first', first_target)
392 repo.config.set_multivar('branch.first.remote', '', '.')
393 repo.config.set_multivar('branch.first.merge', '', 'refs/heads/base')
394
395 repo.branches.local.create('second', second_target)
396 repo.config.set_multivar('branch.second.remote', '', '.')
397 repo.config.set_multivar('branch.second.merge', '', 'refs/heads/base')
398
399 repo.branches.local.create('base', base_target)
400 return repo
401
402 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
403 def testBranch(self):
404 """Test creating patches from a branch"""
405 repo = self.make_git_tree()
406 target = repo.lookup_reference('refs/heads/first')
407 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
408 control.setup()
409 try:
410 orig_dir = os.getcwd()
411 os.chdir(self.gitdir)
412
413 # Check that it can detect the current branch
Simon Glass262130f2020-07-05 21:41:51 -0600414 self.assertEqual(2, gitutil.CountCommitsToBranch(None))
Simon Glassfd709862020-07-05 21:41:50 -0600415 col = terminal.Color()
416 with capture_sys_output() as _:
417 _, cover_fname, patch_files = control.prepare_patches(
Simon Glass137947e2020-07-05 21:41:52 -0600418 col, branch=None, count=-1, start=0, end=0,
419 ignore_binary=False)
Simon Glassfd709862020-07-05 21:41:50 -0600420 self.assertIsNone(cover_fname)
421 self.assertEqual(2, len(patch_files))
Simon Glass262130f2020-07-05 21:41:51 -0600422
423 # Check that it can detect a different branch
424 self.assertEqual(3, gitutil.CountCommitsToBranch('second'))
425 with capture_sys_output() as _:
426 _, cover_fname, patch_files = control.prepare_patches(
Simon Glass137947e2020-07-05 21:41:52 -0600427 col, branch='second', count=-1, start=0, end=0,
Simon Glass262130f2020-07-05 21:41:51 -0600428 ignore_binary=False)
429 self.assertIsNotNone(cover_fname)
430 self.assertEqual(3, len(patch_files))
Simon Glass137947e2020-07-05 21:41:52 -0600431
432 # Check that it can skip patches at the end
433 with capture_sys_output() as _:
434 _, cover_fname, patch_files = control.prepare_patches(
435 col, branch='second', count=-1, start=0, end=1,
436 ignore_binary=False)
437 self.assertIsNotNone(cover_fname)
438 self.assertEqual(2, len(patch_files))
Simon Glassfd709862020-07-05 21:41:50 -0600439 finally:
440 os.chdir(orig_dir)