blob: 7fffe64997402cd7527ae642c4977bb1838ad5f5 [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
7import contextlib
8import os
9import re
10import shutil
11import sys
12import tempfile
13import unittest
14
Simon Glassc3a13cc2020-04-17 18:08:55 -060015from io import StringIO
Simon Glassade1e382019-05-14 15:53:49 -060016
Simon Glassfd709862020-07-05 21:41:50 -060017from patman import control
Simon Glassbf776672020-04-17 18:09:04 -060018from patman import gitutil
19from patman import patchstream
20from patman import settings
Simon Glassfd709862020-07-05 21:41:50 -060021from patman import terminal
Simon Glassbf776672020-04-17 18:09:04 -060022from patman import tools
Simon Glassfd709862020-07-05 21:41:50 -060023from patman.test_util import capture_sys_output
24
25try:
26 import pygit2
Simon Glass427b0282020-10-29 21:46:13 -060027 HAVE_PYGIT2 = True
Simon Glassfd709862020-07-05 21:41:50 -060028except ModuleNotFoundError:
29 HAVE_PYGIT2 = False
Simon Glass6e87ae12017-05-29 15:31:31 -060030
31
32@contextlib.contextmanager
33def capture():
Simon Glass427b0282020-10-29 21:46:13 -060034 oldout, olderr = sys.stdout, sys.stderr
Simon Glass6e87ae12017-05-29 15:31:31 -060035 try:
Simon Glass427b0282020-10-29 21:46:13 -060036 out = [StringIO(), StringIO()]
37 sys.stdout, sys.stderr = out
Simon Glass6e87ae12017-05-29 15:31:31 -060038 yield out
39 finally:
Simon Glass427b0282020-10-29 21:46:13 -060040 sys.stdout, sys.stderr = oldout, olderr
Simon Glass6e87ae12017-05-29 15:31:31 -060041 out[0] = out[0].getvalue()
42 out[1] = out[1].getvalue()
43
44
45class TestFunctional(unittest.TestCase):
46 def setUp(self):
47 self.tmpdir = tempfile.mkdtemp(prefix='patman.')
Simon Glassfd709862020-07-05 21:41:50 -060048 self.gitdir = os.path.join(self.tmpdir, 'git')
49 self.repo = None
Simon Glass6e87ae12017-05-29 15:31:31 -060050
51 def tearDown(self):
52 shutil.rmtree(self.tmpdir)
53
54 @staticmethod
55 def GetPath(fname):
56 return os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
57 'test', fname)
58
59 @classmethod
60 def GetText(self, fname):
Simon Glass272cd852019-10-31 07:42:51 -060061 return open(self.GetPath(fname), encoding='utf-8').read()
Simon Glass6e87ae12017-05-29 15:31:31 -060062
63 @classmethod
64 def GetPatchName(self, subject):
65 fname = re.sub('[ :]', '-', subject)
66 return fname.replace('--', '-')
67
68 def CreatePatchesForTest(self, series):
69 cover_fname = None
70 fname_list = []
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)
82
83 return cover_fname, fname_list
84
85 def testBasic(self):
86 """Tests the basic flow of patman
87
88 This creates a series from some hard-coded patches build from a simple
89 tree with the following metadata in the top commit:
90
91 Series-to: u-boot
92 Series-prefix: RFC
93 Series-cc: Stefan Brüns <stefan.bruens@rwth-aachen.de>
94 Cover-letter-cc: Lord Mëlchett <clergy@palace.gov>
Sean Andersondc03ba42020-05-04 16:28:36 -040095 Series-version: 3
96 Patch-cc: fred
97 Series-process-log: sort, uniq
Simon Glass6e87ae12017-05-29 15:31:31 -060098 Series-changes: 4
99 - Some changes
Sean Andersondc03ba42020-05-04 16:28:36 -0400100 - Multi
101 line
102 change
103
104 Commit-changes: 2
105 - Changes only for this commit
106
107 Cover-changes: 4
108 - Some notes for the cover letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600109
110 Cover-letter:
111 test: A test patch series
112 This is a test of how the cover
Sean Andersondc03ba42020-05-04 16:28:36 -0400113 letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600114 works
115 END
116
117 and this in the first commit:
118
Sean Andersondc03ba42020-05-04 16:28:36 -0400119 Commit-changes: 2
120 - second revision change
121
Simon Glass6e87ae12017-05-29 15:31:31 -0600122 Series-notes:
123 some notes
124 about some things
125 from the first commit
126 END
127
128 Commit-notes:
129 Some notes about
130 the first commit
131 END
132
133 with the following commands:
134
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
138
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
149 - Commit-notes
150 """
151 process_tags = True
152 ignore_bad_tags = True
Simon Glasse6dca5e2019-05-14 15:53:53 -0600153 stefan = b'Stefan Br\xc3\xbcns <stefan.bruens@rwth-aachen.de>'.decode('utf-8')
Simon Glass6e87ae12017-05-29 15:31:31 -0600154 rick = 'Richard III <richard@palace.gov>'
Simon Glasse6dca5e2019-05-14 15:53:53 -0600155 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')
Simon Glass6e87ae12017-05-29 15:31:31 -0600157 fred = 'Fred Bloggs <f.bloggs@napier.net>'
158 add_maintainers = [stefan, rick]
159 dry_run = True
160 in_reply_to = mel
161 count = 2
162 settings.alias = {
Simon Glass427b0282020-10-29 21:46:13 -0600163 'fdt': ['simon'],
164 'u-boot': ['u-boot@lists.denx.de'],
165 'simon': [ed],
166 'fred': [fred],
Simon Glass6e87ae12017-05-29 15:31:31 -0600167 }
168
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)
176 series.DoChecks()
177 cc_file = series.MakeCcFile(process_tags, cover_fname,
Chris Packham4fb35022018-06-07 20:45:06 +1200178 not ignore_bad_tags, add_maintainers,
179 None)
Simon Glass427b0282020-10-29 21:46:13 -0600180 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)
Simon Glass6e87ae12017-05-29 15:31:31 -0600183 series.ShowActions(args, cmd, process_tags)
Simon Glass272cd852019-10-31 07:42:51 -0600184 cc_lines = open(cc_file, encoding='utf-8').read().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600185 os.remove(cc_file)
186
187 lines = out[0].splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600188 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])
195 line = 8
196 for i, commit in enumerate(series.commits):
197 self.assertEqual(' %s' % args[i], lines[line + 0])
198 line += 1
199 while 'Cc:' in lines[line]:
200 line += 1
201 self.assertEqual('To: u-boot@lists.denx.de', lines[line])
Simon Glasse6dca5e2019-05-14 15:53:53 -0600202 self.assertEqual('Cc: %s' % tools.FromUnicode(stefan),
203 lines[line + 1])
Simon Glass6e87ae12017-05-29 15:31:31 -0600204 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])
207 line += 5
Simon Glassb644c662019-05-14 15:53:51 -0600208 self.assertEqual(' Cc: %s' % fred, lines[line + 0])
Simon Glasse6dca5e2019-05-14 15:53:53 -0600209 self.assertEqual(' Cc: %s' % tools.FromUnicode(ed),
210 lines[line + 1])
211 self.assertEqual(' Cc: %s' % tools.FromUnicode(mel),
212 lines[line + 2])
Simon Glassb644c662019-05-14 15:53:51 -0600213 self.assertEqual(' Cc: %s' % rick, lines[line + 3])
Simon Glass6e87ae12017-05-29 15:31:31 -0600214 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,
Simon Glasse6dca5e2019-05-14 15:53:53 -0600218 ' '.join(args)))
Simon Glass6e87ae12017-05-29 15:31:31 -0600219 line += 4
Simon Glasse6dca5e2019-05-14 15:53:53 -0600220 self.assertEqual(expected, tools.ToUnicode(lines[line]))
Simon Glass6e87ae12017-05-29 15:31:31 -0600221
Dmitry Torokhov8ab452d2019-10-21 20:09:56 -0700222 self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)),
Simon Glasse6dca5e2019-05-14 15:53:53 -0600223 tools.ToUnicode(cc_lines[0]))
Simon Glass427b0282020-10-29 21:46:13 -0600224 self.assertEqual(
225 '%s %s\0%s\0%s\0%s' % (args[1], fred, ed, rick, stefan),
226 tools.ToUnicode(cc_lines[1]))
Simon Glass6e87ae12017-05-29 15:31:31 -0600227
228 expected = '''
229This is a test of how the cover
Sean Andersondc03ba42020-05-04 16:28:36 -0400230letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600231works
232
233some notes
234about some things
235from the first commit
236
237Changes in v4:
Sean Andersondc03ba42020-05-04 16:28:36 -0400238- Multi
239 line
240 change
Simon Glass6e87ae12017-05-29 15:31:31 -0600241- Some changes
Sean Andersondc03ba42020-05-04 16:28:36 -0400242- Some notes for the cover letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600243
244Simon Glass (2):
245 pci: Correct cast for sandbox
Siva Durga Prasad Paladugu12308b12018-07-16 15:56:11 +0530246 fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
Simon Glass6e87ae12017-05-29 15:31:31 -0600247
248 cmd/pci.c | 3 ++-
249 fs/fat/fat.c | 1 +
250 lib/efi_loader/efi_memory.c | 1 +
251 lib/fdtdec.c | 3 ++-
252 4 files changed, 6 insertions(+), 2 deletions(-)
253
254--\x20
2552.7.4
256
257'''
Simon Glass272cd852019-10-31 07:42:51 -0600258 lines = open(cover_fname, encoding='utf-8').read().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600259 self.assertEqual(
Simon Glass427b0282020-10-29 21:46:13 -0600260 'Subject: [RFC PATCH v3 0/2] test: A test patch series',
261 lines[3])
Simon Glass6e87ae12017-05-29 15:31:31 -0600262 self.assertEqual(expected.splitlines(), lines[7:])
263
264 for i, fname in enumerate(args):
Simon Glass272cd852019-10-31 07:42:51 -0600265 lines = open(fname, encoding='utf-8').read().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600266 subject = [line for line in lines if line.startswith('Subject')]
267 self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count),
268 subject[0][:18])
Sean Andersondc03ba42020-05-04 16:28:36 -0400269
270 # Check that we got our commit notes
271 start = 0
272 expected = ''
273
Simon Glass6e87ae12017-05-29 15:31:31 -0600274 if i == 0:
Sean Andersondc03ba42020-05-04 16:28:36 -0400275 start = 17
276 expected = '''---
277Some notes about
278the first commit
279
280(no changes since v2)
281
282Changes in v2:
283- second revision change'''
284 elif i == 1:
285 start = 17
286 expected = '''---
287
288Changes in v4:
289- Multi
290 line
291 change
292- Some changes
293
294Changes in v2:
295- Changes only for this commit'''
296
297 if expected:
298 expected = expected.splitlines()
299 self.assertEqual(expected, lines[start:(start+len(expected))])
Simon Glassfd709862020-07-05 21:41:50 -0600300
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
303
304 Args:
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
309 """
310 path = os.path.join(self.gitdir, fname)
311 tools.WriteFile(path, text, binary=False)
312 index = self.repo.index
313 index.add(fname)
Simon Glass427b0282020-10-29 21:46:13 -0600314 author = pygit2.Signature('Test user', 'test@email.com')
Simon Glassfd709862020-07-05 21:41:50 -0600315 committer = author
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])
320
321 def make_git_tree(self):
322 """Make a simple git tree suitable for testing
323
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
328
329 Returns:
330 pygit2 repository
331 """
332 repo = pygit2.init_repository(self.gitdir)
333 self.repo = repo
334 new_tree = repo.TreeBuilder().write()
335
336 author = pygit2.Signature('Test user', 'test@email.com')
337 committer = author
338 commit = repo.create_commit('HEAD', author, committer,
Simon Glass427b0282020-10-29 21:46:13 -0600339 'Created master', new_tree, [])
Simon Glassfd709862020-07-05 21:41:50 -0600340
341 self.make_commit_with_file('Initial commit', '''
342Add a README
343
344''', 'README', '''This is the README file
345describing this project
346in very little detail''')
347
348 self.make_commit_with_file('pci: PCI implementation', '''
349Here is a basic PCI implementation
350
351''', 'pci.c', '''This is a file
352it has some contents
353and some more things''')
354 self.make_commit_with_file('main: Main program', '''
355Hello here is the second commit.
356''', 'main.c', '''This is the main file
357there is very little here
358but we can always add more later
359if we want to
360
361Series-to: u-boot
362Series-cc: Barry Crump <bcrump@whataroa.nz>
363''')
364 base_target = repo.revparse_single('HEAD')
365 self.make_commit_with_file('i2c: I2C things', '''
366This has some stuff to do with I2C
367''', 'i2c.c', '''And this is the file contents
368with some I2C-related things in it''')
369 self.make_commit_with_file('spi: SPI fixes', '''
370SPI needs some fixes
371and here they are
372''', 'spi.c', '''Some fixes for SPI in this
373file to make SPI work
374better than before''')
375 first_target = repo.revparse_single('HEAD')
376
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', '''
380Fix up the video so that
381it looks more purple. Purple is
382a very nice colour.
383''', 'video.c', '''More purple here
384Purple and purple
385Even more purple
386Could not be any more purple''')
387 self.make_commit_with_file('serial: Add a serial driver', '''
388Here is the serial driver
389for my chip.
390
391Cover-letter:
392Series for my board
393This series implements support
394for my glorious board.
395END
396''', 'serial.c', '''The code for the
397serial driver is here''')
398 self.make_commit_with_file('bootm: Make it boot', '''
399This makes my board boot
400with a fix to the bootm
401command
402''', 'bootm.c', '''Fix up the bootm
403command to make the code as
404complicated as possible''')
405 second_target = repo.revparse_single('HEAD')
406
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')
410
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')
414
415 repo.branches.local.create('base', base_target)
416 return repo
417
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)
424 control.setup()
425 try:
426 orig_dir = os.getcwd()
427 os.chdir(self.gitdir)
428
429 # Check that it can detect the current branch
Simon Glass262130f2020-07-05 21:41:51 -0600430 self.assertEqual(2, gitutil.CountCommitsToBranch(None))
Simon Glassfd709862020-07-05 21:41:50 -0600431 col = terminal.Color()
432 with capture_sys_output() as _:
433 _, cover_fname, patch_files = control.prepare_patches(
Simon Glass137947e2020-07-05 21:41:52 -0600434 col, branch=None, count=-1, start=0, end=0,
435 ignore_binary=False)
Simon Glassfd709862020-07-05 21:41:50 -0600436 self.assertIsNone(cover_fname)
437 self.assertEqual(2, len(patch_files))
Simon Glass262130f2020-07-05 21:41:51 -0600438
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(
Simon Glass137947e2020-07-05 21:41:52 -0600443 col, branch='second', count=-1, start=0, end=0,
Simon Glass262130f2020-07-05 21:41:51 -0600444 ignore_binary=False)
445 self.assertIsNotNone(cover_fname)
446 self.assertEqual(3, len(patch_files))
Simon Glass137947e2020-07-05 21:41:52 -0600447
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,
452 ignore_binary=False)
453 self.assertIsNotNone(cover_fname)
454 self.assertEqual(2, len(patch_files))
Simon Glassfd709862020-07-05 21:41:50 -0600455 finally:
456 os.chdir(orig_dir)