blob: ea3c84632c02082038c86ae426ec403005bca107 [file] [log] [blame]
Simon Glassdf1bc5c2017-05-29 15:31:31 -06001# -*- coding: utf-8 -*-
Tom Rini10e47792018-05-06 17:58:06 -04002# SPDX-License-Identifier: GPL-2.0+
Simon Glassdf1bc5c2017-05-29 15:31:31 -06003#
4# Copyright 2017 Google, Inc
5#
Simon Glassdf1bc5c2017-05-29 15:31:31 -06006
Simon Glasseb209e52020-10-29 21:46:15 -06007"""Functional tests for checking that patman behaves correctly"""
8
Simon Glassdf1bc5c2017-05-29 15:31:31 -06009import os
10import re
11import shutil
12import sys
13import tempfile
14import unittest
15
Simon Glass54f1c5b2020-07-05 21:41:50 -060016from patman import control
Simon Glassa997ea52020-04-17 18:09:04 -060017from patman import gitutil
18from patman import patchstream
19from patman import settings
Simon Glass54f1c5b2020-07-05 21:41:50 -060020from patman import terminal
Simon Glassa997ea52020-04-17 18:09:04 -060021from patman import tools
Simon Glass54f1c5b2020-07-05 21:41:50 -060022from patman.test_util import capture_sys_output
Simon Glassdf1bc5c2017-05-29 15:31:31 -060023
Simon Glass54f1c5b2020-07-05 21:41:50 -060024try:
25 import pygit2
Simon Glass95745aa2020-10-29 21:46:13 -060026 HAVE_PYGIT2 = True
Simon Glass54f1c5b2020-07-05 21:41:50 -060027except ModuleNotFoundError:
28 HAVE_PYGIT2 = False
29
Simon Glassdf1bc5c2017-05-29 15:31:31 -060030
31class TestFunctional(unittest.TestCase):
Simon Glasseb209e52020-10-29 21:46:15 -060032 """Functional tests for checking that patman behaves correctly"""
Simon Glassdf1bc5c2017-05-29 15:31:31 -060033 def setUp(self):
34 self.tmpdir = tempfile.mkdtemp(prefix='patman.')
Simon Glass54f1c5b2020-07-05 21:41:50 -060035 self.gitdir = os.path.join(self.tmpdir, 'git')
36 self.repo = None
Simon Glassdf1bc5c2017-05-29 15:31:31 -060037
38 def tearDown(self):
39 shutil.rmtree(self.tmpdir)
40
41 @staticmethod
Simon Glasseb209e52020-10-29 21:46:15 -060042 def _get_path(fname):
43 """Get the path to a test file
44
45 Args:
46 fname (str): Filename to obtain
47
48 Returns:
49 str: Full path to file in the test directory
50 """
Simon Glassdf1bc5c2017-05-29 15:31:31 -060051 return os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
52 'test', fname)
53
54 @classmethod
Simon Glasseb209e52020-10-29 21:46:15 -060055 def _get_text(cls, fname):
56 """Read a file as text
57
58 Args:
59 fname (str): Filename to read
60
61 Returns:
62 str: Contents of file
63 """
64 return open(cls._get_path(fname), encoding='utf-8').read()
Simon Glassdf1bc5c2017-05-29 15:31:31 -060065
66 @classmethod
Simon Glasseb209e52020-10-29 21:46:15 -060067 def _get_patch_name(cls, subject):
68 """Get the filename of a patch given its subject
69
70 Args:
71 subject (str): Patch subject
72
73 Returns:
74 str: Filename for that patch
75 """
Simon Glassdf1bc5c2017-05-29 15:31:31 -060076 fname = re.sub('[ :]', '-', subject)
77 return fname.replace('--', '-')
78
Simon Glasseb209e52020-10-29 21:46:15 -060079 def _create_patches_for_test(self, series):
80 """Create patch files for use by tests
81
82 This copies patch files from the test directory as needed by the series
83
84 Args:
85 series (Series): Series containing commits to convert
86
87 Returns:
88 tuple:
89 str: Cover-letter filename, or None if none
90 fname_list: list of str, each a patch filename
91 """
Simon Glassdf1bc5c2017-05-29 15:31:31 -060092 cover_fname = None
93 fname_list = []
94 for i, commit in enumerate(series.commits):
Simon Glasseb209e52020-10-29 21:46:15 -060095 clean_subject = self._get_patch_name(commit.subject)
Simon Glassdf1bc5c2017-05-29 15:31:31 -060096 src_fname = '%04d-%s.patch' % (i + 1, clean_subject[:52])
97 fname = os.path.join(self.tmpdir, src_fname)
Simon Glasseb209e52020-10-29 21:46:15 -060098 shutil.copy(self._get_path(src_fname), fname)
Simon Glassdf1bc5c2017-05-29 15:31:31 -060099 fname_list.append(fname)
100 if series.get('cover'):
101 src_fname = '0000-cover-letter.patch'
102 cover_fname = os.path.join(self.tmpdir, src_fname)
103 fname = os.path.join(self.tmpdir, src_fname)
Simon Glasseb209e52020-10-29 21:46:15 -0600104 shutil.copy(self._get_path(src_fname), fname)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600105
106 return cover_fname, fname_list
107
108 def testBasic(self):
109 """Tests the basic flow of patman
110
111 This creates a series from some hard-coded patches build from a simple
112 tree with the following metadata in the top commit:
113
114 Series-to: u-boot
115 Series-prefix: RFC
116 Series-cc: Stefan Brüns <stefan.bruens@rwth-aachen.de>
117 Cover-letter-cc: Lord Mëlchett <clergy@palace.gov>
Sean Andersoncf13b862020-05-04 16:28:36 -0400118 Series-version: 3
119 Patch-cc: fred
120 Series-process-log: sort, uniq
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600121 Series-changes: 4
122 - Some changes
Sean Andersoncf13b862020-05-04 16:28:36 -0400123 - Multi
124 line
125 change
126
127 Commit-changes: 2
128 - Changes only for this commit
129
130 Cover-changes: 4
131 - Some notes for the cover letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600132
133 Cover-letter:
134 test: A test patch series
135 This is a test of how the cover
Sean Andersoncf13b862020-05-04 16:28:36 -0400136 letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600137 works
138 END
139
140 and this in the first commit:
141
Sean Andersoncf13b862020-05-04 16:28:36 -0400142 Commit-changes: 2
143 - second revision change
144
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600145 Series-notes:
146 some notes
147 about some things
148 from the first commit
149 END
150
151 Commit-notes:
152 Some notes about
153 the first commit
154 END
155
156 with the following commands:
157
158 git log -n2 --reverse >/path/to/tools/patman/test/test01.txt
159 git format-patch --subject-prefix RFC --cover-letter HEAD~2
160 mv 00* /path/to/tools/patman/test
161
162 It checks these aspects:
163 - git log can be processed by patchstream
164 - emailing patches uses the correct command
165 - CC file has information on each commit
166 - cover letter has the expected text and subject
167 - each patch has the correct subject
168 - dry-run information prints out correctly
169 - unicode is handled correctly
170 - Series-to, Series-cc, Series-prefix, Cover-letter
171 - Cover-letter-cc, Series-version, Series-changes, Series-notes
172 - Commit-notes
173 """
174 process_tags = True
175 ignore_bad_tags = True
Simon Glass4f817892019-05-14 15:53:53 -0600176 stefan = b'Stefan Br\xc3\xbcns <stefan.bruens@rwth-aachen.de>'.decode('utf-8')
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600177 rick = 'Richard III <richard@palace.gov>'
Simon Glass4f817892019-05-14 15:53:53 -0600178 mel = b'Lord M\xc3\xablchett <clergy@palace.gov>'.decode('utf-8')
Simon Glasseb209e52020-10-29 21:46:15 -0600179 leb = (b'Lond Edmund Blackadd\xc3\xabr <weasel@blackadder.org'.
180 decode('utf-8'))
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600181 fred = 'Fred Bloggs <f.bloggs@napier.net>'
182 add_maintainers = [stefan, rick]
183 dry_run = True
184 in_reply_to = mel
185 count = 2
186 settings.alias = {
Simon Glass95745aa2020-10-29 21:46:13 -0600187 'fdt': ['simon'],
188 'u-boot': ['u-boot@lists.denx.de'],
Simon Glasseb209e52020-10-29 21:46:15 -0600189 'simon': [leb],
Simon Glass95745aa2020-10-29 21:46:13 -0600190 'fred': [fred],
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600191 }
192
Simon Glasseb209e52020-10-29 21:46:15 -0600193 text = self._get_text('test01.txt')
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600194 series = patchstream.GetMetaDataForTest(text)
Simon Glasseb209e52020-10-29 21:46:15 -0600195 cover_fname, args = self._create_patches_for_test(series)
Simon Glass59a70bb2020-10-29 21:46:14 -0600196 with capture_sys_output() as out:
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600197 patchstream.FixPatches(series, args)
198 if cover_fname and series.get('cover'):
199 patchstream.InsertCoverLetter(cover_fname, series, count)
200 series.DoChecks()
201 cc_file = series.MakeCcFile(process_tags, cover_fname,
Chris Packhamb84fb482018-06-07 20:45:06 +1200202 not ignore_bad_tags, add_maintainers,
203 None)
Simon Glass95745aa2020-10-29 21:46:13 -0600204 cmd = gitutil.EmailPatches(
205 series, cover_fname, args, dry_run, not ignore_bad_tags,
206 cc_file, in_reply_to=in_reply_to, thread=None)
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600207 series.ShowActions(args, cmd, process_tags)
Simon Glassf544a2d2019-10-31 07:42:51 -0600208 cc_lines = open(cc_file, encoding='utf-8').read().splitlines()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600209 os.remove(cc_file)
210
Simon Glass59a70bb2020-10-29 21:46:14 -0600211 lines = out[0].getvalue().splitlines()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600212 self.assertEqual('Cleaned %s patches' % len(series.commits), lines[0])
213 self.assertEqual('Change log missing for v2', lines[1])
214 self.assertEqual('Change log missing for v3', lines[2])
215 self.assertEqual('Change log for unknown version v4', lines[3])
216 self.assertEqual("Alias 'pci' not found", lines[4])
217 self.assertIn('Dry run', lines[5])
218 self.assertIn('Send a total of %d patches' % count, lines[7])
219 line = 8
Simon Glasseb209e52020-10-29 21:46:15 -0600220 for i in range(len(series.commits)):
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600221 self.assertEqual(' %s' % args[i], lines[line + 0])
222 line += 1
223 while 'Cc:' in lines[line]:
224 line += 1
225 self.assertEqual('To: u-boot@lists.denx.de', lines[line])
Simon Glass4f817892019-05-14 15:53:53 -0600226 self.assertEqual('Cc: %s' % tools.FromUnicode(stefan),
227 lines[line + 1])
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600228 self.assertEqual('Version: 3', lines[line + 2])
229 self.assertEqual('Prefix:\t RFC', lines[line + 3])
230 self.assertEqual('Cover: 4 lines', lines[line + 4])
231 line += 5
Simon Glass3ab178f2019-05-14 15:53:51 -0600232 self.assertEqual(' Cc: %s' % fred, lines[line + 0])
Simon Glasseb209e52020-10-29 21:46:15 -0600233 self.assertEqual(' Cc: %s' % tools.FromUnicode(leb),
Simon Glass4f817892019-05-14 15:53:53 -0600234 lines[line + 1])
235 self.assertEqual(' Cc: %s' % tools.FromUnicode(mel),
236 lines[line + 2])
Simon Glass3ab178f2019-05-14 15:53:51 -0600237 self.assertEqual(' Cc: %s' % rick, lines[line + 3])
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600238 expected = ('Git command: git send-email --annotate '
239 '--in-reply-to="%s" --to "u-boot@lists.denx.de" '
240 '--cc "%s" --cc-cmd "%s --cc-cmd %s" %s %s'
241 % (in_reply_to, stefan, sys.argv[0], cc_file, cover_fname,
Simon Glass4f817892019-05-14 15:53:53 -0600242 ' '.join(args)))
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600243 line += 4
Simon Glass4f817892019-05-14 15:53:53 -0600244 self.assertEqual(expected, tools.ToUnicode(lines[line]))
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600245
Dmitry Torokhovef7f67d2019-10-21 20:09:56 -0700246 self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)),
Simon Glass4f817892019-05-14 15:53:53 -0600247 tools.ToUnicode(cc_lines[0]))
Simon Glass95745aa2020-10-29 21:46:13 -0600248 self.assertEqual(
Simon Glasseb209e52020-10-29 21:46:15 -0600249 '%s %s\0%s\0%s\0%s' % (args[1], fred, leb, rick, stefan),
Simon Glass95745aa2020-10-29 21:46:13 -0600250 tools.ToUnicode(cc_lines[1]))
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600251
252 expected = '''
253This is a test of how the cover
Sean Andersoncf13b862020-05-04 16:28:36 -0400254letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600255works
256
257some notes
258about some things
259from the first commit
260
261Changes in v4:
Sean Andersoncf13b862020-05-04 16:28:36 -0400262- Multi
263 line
264 change
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600265- Some changes
Sean Andersoncf13b862020-05-04 16:28:36 -0400266- Some notes for the cover letter
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600267
268Simon Glass (2):
269 pci: Correct cast for sandbox
Siva Durga Prasad Paladugub3d55ea2018-07-16 15:56:11 +0530270 fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600271
272 cmd/pci.c | 3 ++-
273 fs/fat/fat.c | 1 +
274 lib/efi_loader/efi_memory.c | 1 +
275 lib/fdtdec.c | 3 ++-
276 4 files changed, 6 insertions(+), 2 deletions(-)
277
278--\x20
2792.7.4
280
281'''
Simon Glassf544a2d2019-10-31 07:42:51 -0600282 lines = open(cover_fname, encoding='utf-8').read().splitlines()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600283 self.assertEqual(
Simon Glass95745aa2020-10-29 21:46:13 -0600284 'Subject: [RFC PATCH v3 0/2] test: A test patch series',
285 lines[3])
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600286 self.assertEqual(expected.splitlines(), lines[7:])
287
288 for i, fname in enumerate(args):
Simon Glassf544a2d2019-10-31 07:42:51 -0600289 lines = open(fname, encoding='utf-8').read().splitlines()
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600290 subject = [line for line in lines if line.startswith('Subject')]
291 self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count),
292 subject[0][:18])
Sean Andersoncf13b862020-05-04 16:28:36 -0400293
294 # Check that we got our commit notes
295 start = 0
296 expected = ''
297
Simon Glassdf1bc5c2017-05-29 15:31:31 -0600298 if i == 0:
Sean Andersoncf13b862020-05-04 16:28:36 -0400299 start = 17
300 expected = '''---
301Some notes about
302the first commit
303
304(no changes since v2)
305
306Changes in v2:
307- second revision change'''
308 elif i == 1:
309 start = 17
310 expected = '''---
311
312Changes in v4:
313- Multi
314 line
315 change
316- Some changes
317
318Changes in v2:
319- Changes only for this commit'''
320
321 if expected:
322 expected = expected.splitlines()
323 self.assertEqual(expected, lines[start:(start+len(expected))])
Simon Glass54f1c5b2020-07-05 21:41:50 -0600324
325 def make_commit_with_file(self, subject, body, fname, text):
326 """Create a file and add it to the git repo with a new commit
327
328 Args:
329 subject (str): Subject for the commit
330 body (str): Body text of the commit
331 fname (str): Filename of file to create
332 text (str): Text to put into the file
333 """
334 path = os.path.join(self.gitdir, fname)
335 tools.WriteFile(path, text, binary=False)
336 index = self.repo.index
337 index.add(fname)
Simon Glass95745aa2020-10-29 21:46:13 -0600338 author = pygit2.Signature('Test user', 'test@email.com')
Simon Glass54f1c5b2020-07-05 21:41:50 -0600339 committer = author
340 tree = index.write_tree()
341 message = subject + '\n' + body
342 self.repo.create_commit('HEAD', author, committer, message, tree,
343 [self.repo.head.target])
344
345 def make_git_tree(self):
346 """Make a simple git tree suitable for testing
347
348 It has three branches:
349 'base' has two commits: PCI, main
350 'first' has base as upstream and two more commits: I2C, SPI
351 'second' has base as upstream and three more: video, serial, bootm
352
353 Returns:
Simon Glasseb209e52020-10-29 21:46:15 -0600354 pygit2.Repository: repository
Simon Glass54f1c5b2020-07-05 21:41:50 -0600355 """
356 repo = pygit2.init_repository(self.gitdir)
357 self.repo = repo
358 new_tree = repo.TreeBuilder().write()
359
360 author = pygit2.Signature('Test user', 'test@email.com')
361 committer = author
Simon Glasseb209e52020-10-29 21:46:15 -0600362 _ = repo.create_commit('HEAD', author, committer, 'Created master',
363 new_tree, [])
Simon Glass54f1c5b2020-07-05 21:41:50 -0600364
365 self.make_commit_with_file('Initial commit', '''
366Add a README
367
368''', 'README', '''This is the README file
369describing this project
370in very little detail''')
371
372 self.make_commit_with_file('pci: PCI implementation', '''
373Here is a basic PCI implementation
374
375''', 'pci.c', '''This is a file
376it has some contents
377and some more things''')
378 self.make_commit_with_file('main: Main program', '''
379Hello here is the second commit.
380''', 'main.c', '''This is the main file
381there is very little here
382but we can always add more later
383if we want to
384
385Series-to: u-boot
386Series-cc: Barry Crump <bcrump@whataroa.nz>
387''')
388 base_target = repo.revparse_single('HEAD')
389 self.make_commit_with_file('i2c: I2C things', '''
390This has some stuff to do with I2C
391''', 'i2c.c', '''And this is the file contents
392with some I2C-related things in it''')
393 self.make_commit_with_file('spi: SPI fixes', '''
394SPI needs some fixes
395and here they are
396''', 'spi.c', '''Some fixes for SPI in this
397file to make SPI work
398better than before''')
399 first_target = repo.revparse_single('HEAD')
400
401 target = repo.revparse_single('HEAD~2')
402 repo.reset(target.oid, pygit2.GIT_CHECKOUT_FORCE)
403 self.make_commit_with_file('video: Some video improvements', '''
404Fix up the video so that
405it looks more purple. Purple is
406a very nice colour.
407''', 'video.c', '''More purple here
408Purple and purple
409Even more purple
410Could not be any more purple''')
411 self.make_commit_with_file('serial: Add a serial driver', '''
412Here is the serial driver
413for my chip.
414
415Cover-letter:
416Series for my board
417This series implements support
418for my glorious board.
419END
Simon Glassa80986c2020-10-29 21:46:16 -0600420Series-links: 183237
Simon Glass54f1c5b2020-07-05 21:41:50 -0600421''', 'serial.c', '''The code for the
422serial driver is here''')
423 self.make_commit_with_file('bootm: Make it boot', '''
424This makes my board boot
425with a fix to the bootm
426command
427''', 'bootm.c', '''Fix up the bootm
428command to make the code as
429complicated as possible''')
430 second_target = repo.revparse_single('HEAD')
431
432 repo.branches.local.create('first', first_target)
433 repo.config.set_multivar('branch.first.remote', '', '.')
434 repo.config.set_multivar('branch.first.merge', '', 'refs/heads/base')
435
436 repo.branches.local.create('second', second_target)
437 repo.config.set_multivar('branch.second.remote', '', '.')
438 repo.config.set_multivar('branch.second.merge', '', 'refs/heads/base')
439
440 repo.branches.local.create('base', base_target)
441 return repo
442
443 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
444 def testBranch(self):
445 """Test creating patches from a branch"""
446 repo = self.make_git_tree()
447 target = repo.lookup_reference('refs/heads/first')
448 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
449 control.setup()
450 try:
451 orig_dir = os.getcwd()
452 os.chdir(self.gitdir)
453
454 # Check that it can detect the current branch
Simon Glass2eb4da72020-07-05 21:41:51 -0600455 self.assertEqual(2, gitutil.CountCommitsToBranch(None))
Simon Glass54f1c5b2020-07-05 21:41:50 -0600456 col = terminal.Color()
457 with capture_sys_output() as _:
458 _, cover_fname, patch_files = control.prepare_patches(
Simon Glassb3bf4e12020-07-05 21:41:52 -0600459 col, branch=None, count=-1, start=0, end=0,
460 ignore_binary=False)
Simon Glass54f1c5b2020-07-05 21:41:50 -0600461 self.assertIsNone(cover_fname)
462 self.assertEqual(2, len(patch_files))
Simon Glass2eb4da72020-07-05 21:41:51 -0600463
464 # Check that it can detect a different branch
465 self.assertEqual(3, gitutil.CountCommitsToBranch('second'))
466 with capture_sys_output() as _:
467 _, cover_fname, patch_files = control.prepare_patches(
Simon Glassb3bf4e12020-07-05 21:41:52 -0600468 col, branch='second', count=-1, start=0, end=0,
Simon Glass2eb4da72020-07-05 21:41:51 -0600469 ignore_binary=False)
470 self.assertIsNotNone(cover_fname)
471 self.assertEqual(3, len(patch_files))
Simon Glassb3bf4e12020-07-05 21:41:52 -0600472
473 # Check that it can skip patches at the end
474 with capture_sys_output() as _:
475 _, cover_fname, patch_files = control.prepare_patches(
476 col, branch='second', count=-1, start=0, end=1,
477 ignore_binary=False)
478 self.assertIsNotNone(cover_fname)
479 self.assertEqual(2, len(patch_files))
Simon Glass54f1c5b2020-07-05 21:41:50 -0600480 finally:
481 os.chdir(orig_dir)