blob: 2a0da8b3ccf4b57428872c5cb17b11c640a86488 [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 Glassfca99112020-10-29 21:46:15 -06007"""Functional tests for checking that patman behaves correctly"""
8
Simon Glass6e87ae12017-05-29 15:31:31 -06009import os
10import re
11import shutil
12import sys
13import tempfile
14import unittest
15
Simon Glassfd709862020-07-05 21:41:50 -060016from patman import control
Simon Glassbf776672020-04-17 18:09:04 -060017from patman import gitutil
18from patman import patchstream
Simon Glass47f62952020-10-29 21:46:26 -060019from patman.patchstream import PatchStream
Simon Glassbf776672020-04-17 18:09:04 -060020from 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
Simon Glass6e87ae12017-05-29 15:31:31 -060032class TestFunctional(unittest.TestCase):
Simon Glassfca99112020-10-29 21:46:15 -060033 """Functional tests for checking that patman behaves correctly"""
Simon Glass74570512020-10-29 21:46:27 -060034 leb = (b'Lord Edmund Blackadd\xc3\xabr <weasel@blackadder.org>'.
35 decode('utf-8'))
36
Simon Glass6e87ae12017-05-29 15:31:31 -060037 def setUp(self):
38 self.tmpdir = tempfile.mkdtemp(prefix='patman.')
Simon Glassfd709862020-07-05 21:41:50 -060039 self.gitdir = os.path.join(self.tmpdir, 'git')
40 self.repo = None
Simon Glass6e87ae12017-05-29 15:31:31 -060041
42 def tearDown(self):
43 shutil.rmtree(self.tmpdir)
44
45 @staticmethod
Simon Glassfca99112020-10-29 21:46:15 -060046 def _get_path(fname):
47 """Get the path to a test file
48
49 Args:
50 fname (str): Filename to obtain
51
52 Returns:
53 str: Full path to file in the test directory
54 """
Simon Glass6e87ae12017-05-29 15:31:31 -060055 return os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
56 'test', fname)
57
58 @classmethod
Simon Glassfca99112020-10-29 21:46:15 -060059 def _get_text(cls, fname):
60 """Read a file as text
61
62 Args:
63 fname (str): Filename to read
64
65 Returns:
66 str: Contents of file
67 """
68 return open(cls._get_path(fname), encoding='utf-8').read()
Simon Glass6e87ae12017-05-29 15:31:31 -060069
70 @classmethod
Simon Glassfca99112020-10-29 21:46:15 -060071 def _get_patch_name(cls, subject):
72 """Get the filename of a patch given its subject
73
74 Args:
75 subject (str): Patch subject
76
77 Returns:
78 str: Filename for that patch
79 """
Simon Glass6e87ae12017-05-29 15:31:31 -060080 fname = re.sub('[ :]', '-', subject)
81 return fname.replace('--', '-')
82
Simon Glassfca99112020-10-29 21:46:15 -060083 def _create_patches_for_test(self, series):
84 """Create patch files for use by tests
85
86 This copies patch files from the test directory as needed by the series
87
88 Args:
89 series (Series): Series containing commits to convert
90
91 Returns:
92 tuple:
93 str: Cover-letter filename, or None if none
94 fname_list: list of str, each a patch filename
95 """
Simon Glass6e87ae12017-05-29 15:31:31 -060096 cover_fname = None
97 fname_list = []
98 for i, commit in enumerate(series.commits):
Simon Glassfca99112020-10-29 21:46:15 -060099 clean_subject = self._get_patch_name(commit.subject)
Simon Glass6e87ae12017-05-29 15:31:31 -0600100 src_fname = '%04d-%s.patch' % (i + 1, clean_subject[:52])
101 fname = os.path.join(self.tmpdir, src_fname)
Simon Glassfca99112020-10-29 21:46:15 -0600102 shutil.copy(self._get_path(src_fname), fname)
Simon Glass6e87ae12017-05-29 15:31:31 -0600103 fname_list.append(fname)
104 if series.get('cover'):
105 src_fname = '0000-cover-letter.patch'
106 cover_fname = os.path.join(self.tmpdir, src_fname)
107 fname = os.path.join(self.tmpdir, src_fname)
Simon Glassfca99112020-10-29 21:46:15 -0600108 shutil.copy(self._get_path(src_fname), fname)
Simon Glass6e87ae12017-05-29 15:31:31 -0600109
110 return cover_fname, fname_list
111
112 def testBasic(self):
113 """Tests the basic flow of patman
114
115 This creates a series from some hard-coded patches build from a simple
116 tree with the following metadata in the top commit:
117
118 Series-to: u-boot
119 Series-prefix: RFC
120 Series-cc: Stefan Brüns <stefan.bruens@rwth-aachen.de>
121 Cover-letter-cc: Lord Mëlchett <clergy@palace.gov>
Sean Andersondc03ba42020-05-04 16:28:36 -0400122 Series-version: 3
123 Patch-cc: fred
124 Series-process-log: sort, uniq
Simon Glass6e87ae12017-05-29 15:31:31 -0600125 Series-changes: 4
126 - Some changes
Sean Andersondc03ba42020-05-04 16:28:36 -0400127 - Multi
128 line
129 change
130
131 Commit-changes: 2
132 - Changes only for this commit
133
134 Cover-changes: 4
135 - Some notes for the cover letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600136
137 Cover-letter:
138 test: A test patch series
139 This is a test of how the cover
Sean Andersondc03ba42020-05-04 16:28:36 -0400140 letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600141 works
142 END
143
144 and this in the first commit:
145
Sean Andersondc03ba42020-05-04 16:28:36 -0400146 Commit-changes: 2
147 - second revision change
148
Simon Glass6e87ae12017-05-29 15:31:31 -0600149 Series-notes:
150 some notes
151 about some things
152 from the first commit
153 END
154
155 Commit-notes:
156 Some notes about
157 the first commit
158 END
159
160 with the following commands:
161
162 git log -n2 --reverse >/path/to/tools/patman/test/test01.txt
163 git format-patch --subject-prefix RFC --cover-letter HEAD~2
164 mv 00* /path/to/tools/patman/test
165
166 It checks these aspects:
167 - git log can be processed by patchstream
168 - emailing patches uses the correct command
169 - CC file has information on each commit
170 - cover letter has the expected text and subject
171 - each patch has the correct subject
172 - dry-run information prints out correctly
173 - unicode is handled correctly
174 - Series-to, Series-cc, Series-prefix, Cover-letter
175 - Cover-letter-cc, Series-version, Series-changes, Series-notes
176 - Commit-notes
177 """
178 process_tags = True
179 ignore_bad_tags = True
Simon Glasse6dca5e2019-05-14 15:53:53 -0600180 stefan = b'Stefan Br\xc3\xbcns <stefan.bruens@rwth-aachen.de>'.decode('utf-8')
Simon Glass6e87ae12017-05-29 15:31:31 -0600181 rick = 'Richard III <richard@palace.gov>'
Simon Glasse6dca5e2019-05-14 15:53:53 -0600182 mel = b'Lord M\xc3\xablchett <clergy@palace.gov>'.decode('utf-8')
Simon Glass6e87ae12017-05-29 15:31:31 -0600183 fred = 'Fred Bloggs <f.bloggs@napier.net>'
184 add_maintainers = [stefan, rick]
185 dry_run = True
186 in_reply_to = mel
187 count = 2
188 settings.alias = {
Simon Glass427b0282020-10-29 21:46:13 -0600189 'fdt': ['simon'],
190 'u-boot': ['u-boot@lists.denx.de'],
Simon Glass74570512020-10-29 21:46:27 -0600191 'simon': [self.leb],
Simon Glass427b0282020-10-29 21:46:13 -0600192 'fred': [fred],
Simon Glass6e87ae12017-05-29 15:31:31 -0600193 }
194
Simon Glassfca99112020-10-29 21:46:15 -0600195 text = self._get_text('test01.txt')
Simon Glassd93720e2020-10-29 21:46:19 -0600196 series = patchstream.get_metadata_for_test(text)
Simon Glassfca99112020-10-29 21:46:15 -0600197 cover_fname, args = self._create_patches_for_test(series)
Simon Glass366954f2020-10-29 21:46:14 -0600198 with capture_sys_output() as out:
Simon Glassd93720e2020-10-29 21:46:19 -0600199 patchstream.fix_patches(series, args)
Simon Glass6e87ae12017-05-29 15:31:31 -0600200 if cover_fname and series.get('cover'):
Simon Glassd93720e2020-10-29 21:46:19 -0600201 patchstream.insert_cover_letter(cover_fname, series, count)
Simon Glass6e87ae12017-05-29 15:31:31 -0600202 series.DoChecks()
203 cc_file = series.MakeCcFile(process_tags, cover_fname,
Chris Packham4fb35022018-06-07 20:45:06 +1200204 not ignore_bad_tags, add_maintainers,
205 None)
Simon Glass427b0282020-10-29 21:46:13 -0600206 cmd = gitutil.EmailPatches(
207 series, cover_fname, args, dry_run, not ignore_bad_tags,
208 cc_file, in_reply_to=in_reply_to, thread=None)
Simon Glass6e87ae12017-05-29 15:31:31 -0600209 series.ShowActions(args, cmd, process_tags)
Simon Glass272cd852019-10-31 07:42:51 -0600210 cc_lines = open(cc_file, encoding='utf-8').read().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600211 os.remove(cc_file)
212
Simon Glass366954f2020-10-29 21:46:14 -0600213 lines = out[0].getvalue().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600214 self.assertEqual('Cleaned %s patches' % len(series.commits), lines[0])
215 self.assertEqual('Change log missing for v2', lines[1])
216 self.assertEqual('Change log missing for v3', lines[2])
217 self.assertEqual('Change log for unknown version v4', lines[3])
218 self.assertEqual("Alias 'pci' not found", lines[4])
219 self.assertIn('Dry run', lines[5])
220 self.assertIn('Send a total of %d patches' % count, lines[7])
221 line = 8
Simon Glassfca99112020-10-29 21:46:15 -0600222 for i in range(len(series.commits)):
Simon Glass6e87ae12017-05-29 15:31:31 -0600223 self.assertEqual(' %s' % args[i], lines[line + 0])
224 line += 1
225 while 'Cc:' in lines[line]:
226 line += 1
227 self.assertEqual('To: u-boot@lists.denx.de', lines[line])
Simon Glasse6dca5e2019-05-14 15:53:53 -0600228 self.assertEqual('Cc: %s' % tools.FromUnicode(stefan),
229 lines[line + 1])
Simon Glass6e87ae12017-05-29 15:31:31 -0600230 self.assertEqual('Version: 3', lines[line + 2])
231 self.assertEqual('Prefix:\t RFC', lines[line + 3])
232 self.assertEqual('Cover: 4 lines', lines[line + 4])
233 line += 5
Simon Glassb644c662019-05-14 15:53:51 -0600234 self.assertEqual(' Cc: %s' % fred, lines[line + 0])
Simon Glass74570512020-10-29 21:46:27 -0600235 self.assertEqual(' Cc: %s' % tools.FromUnicode(self.leb),
Simon Glasse6dca5e2019-05-14 15:53:53 -0600236 lines[line + 1])
237 self.assertEqual(' Cc: %s' % tools.FromUnicode(mel),
238 lines[line + 2])
Simon Glassb644c662019-05-14 15:53:51 -0600239 self.assertEqual(' Cc: %s' % rick, lines[line + 3])
Simon Glass6e87ae12017-05-29 15:31:31 -0600240 expected = ('Git command: git send-email --annotate '
241 '--in-reply-to="%s" --to "u-boot@lists.denx.de" '
242 '--cc "%s" --cc-cmd "%s --cc-cmd %s" %s %s'
243 % (in_reply_to, stefan, sys.argv[0], cc_file, cover_fname,
Simon Glasse6dca5e2019-05-14 15:53:53 -0600244 ' '.join(args)))
Simon Glass6e87ae12017-05-29 15:31:31 -0600245 line += 4
Simon Glasse6dca5e2019-05-14 15:53:53 -0600246 self.assertEqual(expected, tools.ToUnicode(lines[line]))
Simon Glass6e87ae12017-05-29 15:31:31 -0600247
Dmitry Torokhov8ab452d2019-10-21 20:09:56 -0700248 self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)),
Simon Glasse6dca5e2019-05-14 15:53:53 -0600249 tools.ToUnicode(cc_lines[0]))
Simon Glass427b0282020-10-29 21:46:13 -0600250 self.assertEqual(
Simon Glass74570512020-10-29 21:46:27 -0600251 '%s %s\0%s\0%s\0%s' % (args[1], fred, self.leb, rick, stefan),
Simon Glass427b0282020-10-29 21:46:13 -0600252 tools.ToUnicode(cc_lines[1]))
Simon Glass6e87ae12017-05-29 15:31:31 -0600253
254 expected = '''
255This is a test of how the cover
Sean Andersondc03ba42020-05-04 16:28:36 -0400256letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600257works
258
259some notes
260about some things
261from the first commit
262
263Changes in v4:
Sean Andersondc03ba42020-05-04 16:28:36 -0400264- Multi
265 line
266 change
Simon Glass6e87ae12017-05-29 15:31:31 -0600267- Some changes
Sean Andersondc03ba42020-05-04 16:28:36 -0400268- Some notes for the cover letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600269
270Simon Glass (2):
271 pci: Correct cast for sandbox
Siva Durga Prasad Paladugu12308b12018-07-16 15:56:11 +0530272 fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
Simon Glass6e87ae12017-05-29 15:31:31 -0600273
274 cmd/pci.c | 3 ++-
275 fs/fat/fat.c | 1 +
276 lib/efi_loader/efi_memory.c | 1 +
277 lib/fdtdec.c | 3 ++-
278 4 files changed, 6 insertions(+), 2 deletions(-)
279
280--\x20
2812.7.4
282
283'''
Simon Glass272cd852019-10-31 07:42:51 -0600284 lines = open(cover_fname, encoding='utf-8').read().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600285 self.assertEqual(
Simon Glass427b0282020-10-29 21:46:13 -0600286 'Subject: [RFC PATCH v3 0/2] test: A test patch series',
287 lines[3])
Simon Glass6e87ae12017-05-29 15:31:31 -0600288 self.assertEqual(expected.splitlines(), lines[7:])
289
290 for i, fname in enumerate(args):
Simon Glass272cd852019-10-31 07:42:51 -0600291 lines = open(fname, encoding='utf-8').read().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600292 subject = [line for line in lines if line.startswith('Subject')]
293 self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count),
294 subject[0][:18])
Sean Andersondc03ba42020-05-04 16:28:36 -0400295
296 # Check that we got our commit notes
297 start = 0
298 expected = ''
299
Simon Glass6e87ae12017-05-29 15:31:31 -0600300 if i == 0:
Sean Andersondc03ba42020-05-04 16:28:36 -0400301 start = 17
302 expected = '''---
303Some notes about
304the first commit
305
306(no changes since v2)
307
308Changes in v2:
309- second revision change'''
310 elif i == 1:
311 start = 17
312 expected = '''---
313
314Changes in v4:
315- Multi
316 line
317 change
318- Some changes
319
320Changes in v2:
321- Changes only for this commit'''
322
323 if expected:
324 expected = expected.splitlines()
325 self.assertEqual(expected, lines[start:(start+len(expected))])
Simon Glassfd709862020-07-05 21:41:50 -0600326
327 def make_commit_with_file(self, subject, body, fname, text):
328 """Create a file and add it to the git repo with a new commit
329
330 Args:
331 subject (str): Subject for the commit
332 body (str): Body text of the commit
333 fname (str): Filename of file to create
334 text (str): Text to put into the file
335 """
336 path = os.path.join(self.gitdir, fname)
337 tools.WriteFile(path, text, binary=False)
338 index = self.repo.index
339 index.add(fname)
Simon Glass427b0282020-10-29 21:46:13 -0600340 author = pygit2.Signature('Test user', 'test@email.com')
Simon Glassfd709862020-07-05 21:41:50 -0600341 committer = author
342 tree = index.write_tree()
343 message = subject + '\n' + body
344 self.repo.create_commit('HEAD', author, committer, message, tree,
345 [self.repo.head.target])
346
347 def make_git_tree(self):
348 """Make a simple git tree suitable for testing
349
350 It has three branches:
351 'base' has two commits: PCI, main
352 'first' has base as upstream and two more commits: I2C, SPI
353 'second' has base as upstream and three more: video, serial, bootm
354
355 Returns:
Simon Glassfca99112020-10-29 21:46:15 -0600356 pygit2.Repository: repository
Simon Glassfd709862020-07-05 21:41:50 -0600357 """
358 repo = pygit2.init_repository(self.gitdir)
359 self.repo = repo
360 new_tree = repo.TreeBuilder().write()
361
362 author = pygit2.Signature('Test user', 'test@email.com')
363 committer = author
Simon Glassfca99112020-10-29 21:46:15 -0600364 _ = repo.create_commit('HEAD', author, committer, 'Created master',
365 new_tree, [])
Simon Glassfd709862020-07-05 21:41:50 -0600366
367 self.make_commit_with_file('Initial commit', '''
368Add a README
369
370''', 'README', '''This is the README file
371describing this project
372in very little detail''')
373
374 self.make_commit_with_file('pci: PCI implementation', '''
375Here is a basic PCI implementation
376
377''', 'pci.c', '''This is a file
378it has some contents
379and some more things''')
380 self.make_commit_with_file('main: Main program', '''
381Hello here is the second commit.
382''', 'main.c', '''This is the main file
383there is very little here
384but we can always add more later
385if we want to
386
387Series-to: u-boot
388Series-cc: Barry Crump <bcrump@whataroa.nz>
389''')
390 base_target = repo.revparse_single('HEAD')
391 self.make_commit_with_file('i2c: I2C things', '''
392This has some stuff to do with I2C
393''', 'i2c.c', '''And this is the file contents
394with some I2C-related things in it''')
395 self.make_commit_with_file('spi: SPI fixes', '''
396SPI needs some fixes
397and here they are
398''', 'spi.c', '''Some fixes for SPI in this
399file to make SPI work
400better than before''')
401 first_target = repo.revparse_single('HEAD')
402
403 target = repo.revparse_single('HEAD~2')
404 repo.reset(target.oid, pygit2.GIT_CHECKOUT_FORCE)
405 self.make_commit_with_file('video: Some video improvements', '''
406Fix up the video so that
407it looks more purple. Purple is
408a very nice colour.
409''', 'video.c', '''More purple here
410Purple and purple
411Even more purple
412Could not be any more purple''')
413 self.make_commit_with_file('serial: Add a serial driver', '''
414Here is the serial driver
415for my chip.
416
417Cover-letter:
418Series for my board
419This series implements support
420for my glorious board.
421END
Simon Glassf9e42842020-10-29 21:46:16 -0600422Series-links: 183237
Simon Glassfd709862020-07-05 21:41:50 -0600423''', 'serial.c', '''The code for the
424serial driver is here''')
425 self.make_commit_with_file('bootm: Make it boot', '''
426This makes my board boot
427with a fix to the bootm
428command
429''', 'bootm.c', '''Fix up the bootm
430command to make the code as
431complicated as possible''')
432 second_target = repo.revparse_single('HEAD')
433
434 repo.branches.local.create('first', first_target)
435 repo.config.set_multivar('branch.first.remote', '', '.')
436 repo.config.set_multivar('branch.first.merge', '', 'refs/heads/base')
437
438 repo.branches.local.create('second', second_target)
439 repo.config.set_multivar('branch.second.remote', '', '.')
440 repo.config.set_multivar('branch.second.merge', '', 'refs/heads/base')
441
442 repo.branches.local.create('base', base_target)
443 return repo
444
445 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
446 def testBranch(self):
447 """Test creating patches from a branch"""
448 repo = self.make_git_tree()
449 target = repo.lookup_reference('refs/heads/first')
450 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
451 control.setup()
452 try:
453 orig_dir = os.getcwd()
454 os.chdir(self.gitdir)
455
456 # Check that it can detect the current branch
Simon Glass262130f2020-07-05 21:41:51 -0600457 self.assertEqual(2, gitutil.CountCommitsToBranch(None))
Simon Glassfd709862020-07-05 21:41:50 -0600458 col = terminal.Color()
459 with capture_sys_output() as _:
460 _, cover_fname, patch_files = control.prepare_patches(
Simon Glass137947e2020-07-05 21:41:52 -0600461 col, branch=None, count=-1, start=0, end=0,
462 ignore_binary=False)
Simon Glassfd709862020-07-05 21:41:50 -0600463 self.assertIsNone(cover_fname)
464 self.assertEqual(2, len(patch_files))
Simon Glass262130f2020-07-05 21:41:51 -0600465
466 # Check that it can detect a different branch
467 self.assertEqual(3, gitutil.CountCommitsToBranch('second'))
468 with capture_sys_output() as _:
469 _, cover_fname, patch_files = control.prepare_patches(
Simon Glass137947e2020-07-05 21:41:52 -0600470 col, branch='second', count=-1, start=0, end=0,
Simon Glass262130f2020-07-05 21:41:51 -0600471 ignore_binary=False)
472 self.assertIsNotNone(cover_fname)
473 self.assertEqual(3, len(patch_files))
Simon Glass137947e2020-07-05 21:41:52 -0600474
475 # Check that it can skip patches at the end
476 with capture_sys_output() as _:
477 _, cover_fname, patch_files = control.prepare_patches(
478 col, branch='second', count=-1, start=0, end=1,
479 ignore_binary=False)
480 self.assertIsNotNone(cover_fname)
481 self.assertEqual(2, len(patch_files))
Simon Glassfd709862020-07-05 21:41:50 -0600482 finally:
483 os.chdir(orig_dir)
Simon Glass74570512020-10-29 21:46:27 -0600484
485 def testTags(self):
486 """Test collection of tags in a patchstream"""
487 text = '''This is a patch
488
489Signed-off-by: Terminator
490Reviewed-by: Joe Bloggs <joe@napierwallies.co.nz>
491Reviewed-by: Mary Bloggs <mary@napierwallies.co.nz>
492Tested-by: %s
493''' % self.leb
494 pstrm = PatchStream.process_text(text)
495 self.assertEqual(pstrm.commit.rtags, {
496 'Reviewed-by': {'Mary Bloggs <mary@napierwallies.co.nz>',
497 'Joe Bloggs <joe@napierwallies.co.nz>'},
498 'Tested-by': {self.leb}})