blob: 2290ba95e9d5450bf3679c031960faa27e296f55 [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 Glass6e87ae12017-05-29 15:31:31 -060034 def setUp(self):
35 self.tmpdir = tempfile.mkdtemp(prefix='patman.')
Simon Glassfd709862020-07-05 21:41:50 -060036 self.gitdir = os.path.join(self.tmpdir, 'git')
37 self.repo = None
Simon Glass6e87ae12017-05-29 15:31:31 -060038
39 def tearDown(self):
40 shutil.rmtree(self.tmpdir)
41
42 @staticmethod
Simon Glassfca99112020-10-29 21:46:15 -060043 def _get_path(fname):
44 """Get the path to a test file
45
46 Args:
47 fname (str): Filename to obtain
48
49 Returns:
50 str: Full path to file in the test directory
51 """
Simon Glass6e87ae12017-05-29 15:31:31 -060052 return os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
53 'test', fname)
54
55 @classmethod
Simon Glassfca99112020-10-29 21:46:15 -060056 def _get_text(cls, fname):
57 """Read a file as text
58
59 Args:
60 fname (str): Filename to read
61
62 Returns:
63 str: Contents of file
64 """
65 return open(cls._get_path(fname), encoding='utf-8').read()
Simon Glass6e87ae12017-05-29 15:31:31 -060066
67 @classmethod
Simon Glassfca99112020-10-29 21:46:15 -060068 def _get_patch_name(cls, subject):
69 """Get the filename of a patch given its subject
70
71 Args:
72 subject (str): Patch subject
73
74 Returns:
75 str: Filename for that patch
76 """
Simon Glass6e87ae12017-05-29 15:31:31 -060077 fname = re.sub('[ :]', '-', subject)
78 return fname.replace('--', '-')
79
Simon Glassfca99112020-10-29 21:46:15 -060080 def _create_patches_for_test(self, series):
81 """Create patch files for use by tests
82
83 This copies patch files from the test directory as needed by the series
84
85 Args:
86 series (Series): Series containing commits to convert
87
88 Returns:
89 tuple:
90 str: Cover-letter filename, or None if none
91 fname_list: list of str, each a patch filename
92 """
Simon Glass6e87ae12017-05-29 15:31:31 -060093 cover_fname = None
94 fname_list = []
95 for i, commit in enumerate(series.commits):
Simon Glassfca99112020-10-29 21:46:15 -060096 clean_subject = self._get_patch_name(commit.subject)
Simon Glass6e87ae12017-05-29 15:31:31 -060097 src_fname = '%04d-%s.patch' % (i + 1, clean_subject[:52])
98 fname = os.path.join(self.tmpdir, src_fname)
Simon Glassfca99112020-10-29 21:46:15 -060099 shutil.copy(self._get_path(src_fname), fname)
Simon Glass6e87ae12017-05-29 15:31:31 -0600100 fname_list.append(fname)
101 if series.get('cover'):
102 src_fname = '0000-cover-letter.patch'
103 cover_fname = os.path.join(self.tmpdir, src_fname)
104 fname = os.path.join(self.tmpdir, src_fname)
Simon Glassfca99112020-10-29 21:46:15 -0600105 shutil.copy(self._get_path(src_fname), fname)
Simon Glass6e87ae12017-05-29 15:31:31 -0600106
107 return cover_fname, fname_list
108
109 def testBasic(self):
110 """Tests the basic flow of patman
111
112 This creates a series from some hard-coded patches build from a simple
113 tree with the following metadata in the top commit:
114
115 Series-to: u-boot
116 Series-prefix: RFC
117 Series-cc: Stefan Brüns <stefan.bruens@rwth-aachen.de>
118 Cover-letter-cc: Lord Mëlchett <clergy@palace.gov>
Sean Andersondc03ba42020-05-04 16:28:36 -0400119 Series-version: 3
120 Patch-cc: fred
121 Series-process-log: sort, uniq
Simon Glass6e87ae12017-05-29 15:31:31 -0600122 Series-changes: 4
123 - Some changes
Sean Andersondc03ba42020-05-04 16:28:36 -0400124 - Multi
125 line
126 change
127
128 Commit-changes: 2
129 - Changes only for this commit
130
131 Cover-changes: 4
132 - Some notes for the cover letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600133
134 Cover-letter:
135 test: A test patch series
136 This is a test of how the cover
Sean Andersondc03ba42020-05-04 16:28:36 -0400137 letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600138 works
139 END
140
141 and this in the first commit:
142
Sean Andersondc03ba42020-05-04 16:28:36 -0400143 Commit-changes: 2
144 - second revision change
145
Simon Glass6e87ae12017-05-29 15:31:31 -0600146 Series-notes:
147 some notes
148 about some things
149 from the first commit
150 END
151
152 Commit-notes:
153 Some notes about
154 the first commit
155 END
156
157 with the following commands:
158
159 git log -n2 --reverse >/path/to/tools/patman/test/test01.txt
160 git format-patch --subject-prefix RFC --cover-letter HEAD~2
161 mv 00* /path/to/tools/patman/test
162
163 It checks these aspects:
164 - git log can be processed by patchstream
165 - emailing patches uses the correct command
166 - CC file has information on each commit
167 - cover letter has the expected text and subject
168 - each patch has the correct subject
169 - dry-run information prints out correctly
170 - unicode is handled correctly
171 - Series-to, Series-cc, Series-prefix, Cover-letter
172 - Cover-letter-cc, Series-version, Series-changes, Series-notes
173 - Commit-notes
174 """
175 process_tags = True
176 ignore_bad_tags = True
Simon Glasse6dca5e2019-05-14 15:53:53 -0600177 stefan = b'Stefan Br\xc3\xbcns <stefan.bruens@rwth-aachen.de>'.decode('utf-8')
Simon Glass6e87ae12017-05-29 15:31:31 -0600178 rick = 'Richard III <richard@palace.gov>'
Simon Glasse6dca5e2019-05-14 15:53:53 -0600179 mel = b'Lord M\xc3\xablchett <clergy@palace.gov>'.decode('utf-8')
Simon Glassfca99112020-10-29 21:46:15 -0600180 leb = (b'Lond Edmund Blackadd\xc3\xabr <weasel@blackadder.org'.
181 decode('utf-8'))
Simon Glass6e87ae12017-05-29 15:31:31 -0600182 fred = 'Fred Bloggs <f.bloggs@napier.net>'
183 add_maintainers = [stefan, rick]
184 dry_run = True
185 in_reply_to = mel
186 count = 2
187 settings.alias = {
Simon Glass427b0282020-10-29 21:46:13 -0600188 'fdt': ['simon'],
189 'u-boot': ['u-boot@lists.denx.de'],
Simon Glassfca99112020-10-29 21:46:15 -0600190 'simon': [leb],
Simon Glass427b0282020-10-29 21:46:13 -0600191 'fred': [fred],
Simon Glass6e87ae12017-05-29 15:31:31 -0600192 }
193
Simon Glassfca99112020-10-29 21:46:15 -0600194 text = self._get_text('test01.txt')
Simon Glassd93720e2020-10-29 21:46:19 -0600195 series = patchstream.get_metadata_for_test(text)
Simon Glassfca99112020-10-29 21:46:15 -0600196 cover_fname, args = self._create_patches_for_test(series)
Simon Glass366954f2020-10-29 21:46:14 -0600197 with capture_sys_output() as out:
Simon Glassd93720e2020-10-29 21:46:19 -0600198 patchstream.fix_patches(series, args)
Simon Glass6e87ae12017-05-29 15:31:31 -0600199 if cover_fname and series.get('cover'):
Simon Glassd93720e2020-10-29 21:46:19 -0600200 patchstream.insert_cover_letter(cover_fname, series, count)
Simon Glass6e87ae12017-05-29 15:31:31 -0600201 series.DoChecks()
202 cc_file = series.MakeCcFile(process_tags, cover_fname,
Chris Packham4fb35022018-06-07 20:45:06 +1200203 not ignore_bad_tags, add_maintainers,
204 None)
Simon Glass427b0282020-10-29 21:46:13 -0600205 cmd = gitutil.EmailPatches(
206 series, cover_fname, args, dry_run, not ignore_bad_tags,
207 cc_file, in_reply_to=in_reply_to, thread=None)
Simon Glass6e87ae12017-05-29 15:31:31 -0600208 series.ShowActions(args, cmd, process_tags)
Simon Glass272cd852019-10-31 07:42:51 -0600209 cc_lines = open(cc_file, encoding='utf-8').read().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600210 os.remove(cc_file)
211
Simon Glass366954f2020-10-29 21:46:14 -0600212 lines = out[0].getvalue().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600213 self.assertEqual('Cleaned %s patches' % len(series.commits), lines[0])
214 self.assertEqual('Change log missing for v2', lines[1])
215 self.assertEqual('Change log missing for v3', lines[2])
216 self.assertEqual('Change log for unknown version v4', lines[3])
217 self.assertEqual("Alias 'pci' not found", lines[4])
218 self.assertIn('Dry run', lines[5])
219 self.assertIn('Send a total of %d patches' % count, lines[7])
220 line = 8
Simon Glassfca99112020-10-29 21:46:15 -0600221 for i in range(len(series.commits)):
Simon Glass6e87ae12017-05-29 15:31:31 -0600222 self.assertEqual(' %s' % args[i], lines[line + 0])
223 line += 1
224 while 'Cc:' in lines[line]:
225 line += 1
226 self.assertEqual('To: u-boot@lists.denx.de', lines[line])
Simon Glasse6dca5e2019-05-14 15:53:53 -0600227 self.assertEqual('Cc: %s' % tools.FromUnicode(stefan),
228 lines[line + 1])
Simon Glass6e87ae12017-05-29 15:31:31 -0600229 self.assertEqual('Version: 3', lines[line + 2])
230 self.assertEqual('Prefix:\t RFC', lines[line + 3])
231 self.assertEqual('Cover: 4 lines', lines[line + 4])
232 line += 5
Simon Glassb644c662019-05-14 15:53:51 -0600233 self.assertEqual(' Cc: %s' % fred, lines[line + 0])
Simon Glassfca99112020-10-29 21:46:15 -0600234 self.assertEqual(' Cc: %s' % tools.FromUnicode(leb),
Simon Glasse6dca5e2019-05-14 15:53:53 -0600235 lines[line + 1])
236 self.assertEqual(' Cc: %s' % tools.FromUnicode(mel),
237 lines[line + 2])
Simon Glassb644c662019-05-14 15:53:51 -0600238 self.assertEqual(' Cc: %s' % rick, lines[line + 3])
Simon Glass6e87ae12017-05-29 15:31:31 -0600239 expected = ('Git command: git send-email --annotate '
240 '--in-reply-to="%s" --to "u-boot@lists.denx.de" '
241 '--cc "%s" --cc-cmd "%s --cc-cmd %s" %s %s'
242 % (in_reply_to, stefan, sys.argv[0], cc_file, cover_fname,
Simon Glasse6dca5e2019-05-14 15:53:53 -0600243 ' '.join(args)))
Simon Glass6e87ae12017-05-29 15:31:31 -0600244 line += 4
Simon Glasse6dca5e2019-05-14 15:53:53 -0600245 self.assertEqual(expected, tools.ToUnicode(lines[line]))
Simon Glass6e87ae12017-05-29 15:31:31 -0600246
Dmitry Torokhov8ab452d2019-10-21 20:09:56 -0700247 self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)),
Simon Glasse6dca5e2019-05-14 15:53:53 -0600248 tools.ToUnicode(cc_lines[0]))
Simon Glass427b0282020-10-29 21:46:13 -0600249 self.assertEqual(
Simon Glassfca99112020-10-29 21:46:15 -0600250 '%s %s\0%s\0%s\0%s' % (args[1], fred, leb, rick, stefan),
Simon Glass427b0282020-10-29 21:46:13 -0600251 tools.ToUnicode(cc_lines[1]))
Simon Glass6e87ae12017-05-29 15:31:31 -0600252
253 expected = '''
254This is a test of how the cover
Sean Andersondc03ba42020-05-04 16:28:36 -0400255letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600256works
257
258some notes
259about some things
260from the first commit
261
262Changes in v4:
Sean Andersondc03ba42020-05-04 16:28:36 -0400263- Multi
264 line
265 change
Simon Glass6e87ae12017-05-29 15:31:31 -0600266- Some changes
Sean Andersondc03ba42020-05-04 16:28:36 -0400267- Some notes for the cover letter
Simon Glass6e87ae12017-05-29 15:31:31 -0600268
269Simon Glass (2):
270 pci: Correct cast for sandbox
Siva Durga Prasad Paladugu12308b12018-07-16 15:56:11 +0530271 fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
Simon Glass6e87ae12017-05-29 15:31:31 -0600272
273 cmd/pci.c | 3 ++-
274 fs/fat/fat.c | 1 +
275 lib/efi_loader/efi_memory.c | 1 +
276 lib/fdtdec.c | 3 ++-
277 4 files changed, 6 insertions(+), 2 deletions(-)
278
279--\x20
2802.7.4
281
282'''
Simon Glass272cd852019-10-31 07:42:51 -0600283 lines = open(cover_fname, encoding='utf-8').read().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600284 self.assertEqual(
Simon Glass427b0282020-10-29 21:46:13 -0600285 'Subject: [RFC PATCH v3 0/2] test: A test patch series',
286 lines[3])
Simon Glass6e87ae12017-05-29 15:31:31 -0600287 self.assertEqual(expected.splitlines(), lines[7:])
288
289 for i, fname in enumerate(args):
Simon Glass272cd852019-10-31 07:42:51 -0600290 lines = open(fname, encoding='utf-8').read().splitlines()
Simon Glass6e87ae12017-05-29 15:31:31 -0600291 subject = [line for line in lines if line.startswith('Subject')]
292 self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count),
293 subject[0][:18])
Sean Andersondc03ba42020-05-04 16:28:36 -0400294
295 # Check that we got our commit notes
296 start = 0
297 expected = ''
298
Simon Glass6e87ae12017-05-29 15:31:31 -0600299 if i == 0:
Sean Andersondc03ba42020-05-04 16:28:36 -0400300 start = 17
301 expected = '''---
302Some notes about
303the first commit
304
305(no changes since v2)
306
307Changes in v2:
308- second revision change'''
309 elif i == 1:
310 start = 17
311 expected = '''---
312
313Changes in v4:
314- Multi
315 line
316 change
317- Some changes
318
319Changes in v2:
320- Changes only for this commit'''
321
322 if expected:
323 expected = expected.splitlines()
324 self.assertEqual(expected, lines[start:(start+len(expected))])
Simon Glassfd709862020-07-05 21:41:50 -0600325
326 def make_commit_with_file(self, subject, body, fname, text):
327 """Create a file and add it to the git repo with a new commit
328
329 Args:
330 subject (str): Subject for the commit
331 body (str): Body text of the commit
332 fname (str): Filename of file to create
333 text (str): Text to put into the file
334 """
335 path = os.path.join(self.gitdir, fname)
336 tools.WriteFile(path, text, binary=False)
337 index = self.repo.index
338 index.add(fname)
Simon Glass427b0282020-10-29 21:46:13 -0600339 author = pygit2.Signature('Test user', 'test@email.com')
Simon Glassfd709862020-07-05 21:41:50 -0600340 committer = author
341 tree = index.write_tree()
342 message = subject + '\n' + body
343 self.repo.create_commit('HEAD', author, committer, message, tree,
344 [self.repo.head.target])
345
346 def make_git_tree(self):
347 """Make a simple git tree suitable for testing
348
349 It has three branches:
350 'base' has two commits: PCI, main
351 'first' has base as upstream and two more commits: I2C, SPI
352 'second' has base as upstream and three more: video, serial, bootm
353
354 Returns:
Simon Glassfca99112020-10-29 21:46:15 -0600355 pygit2.Repository: repository
Simon Glassfd709862020-07-05 21:41:50 -0600356 """
357 repo = pygit2.init_repository(self.gitdir)
358 self.repo = repo
359 new_tree = repo.TreeBuilder().write()
360
361 author = pygit2.Signature('Test user', 'test@email.com')
362 committer = author
Simon Glassfca99112020-10-29 21:46:15 -0600363 _ = repo.create_commit('HEAD', author, committer, 'Created master',
364 new_tree, [])
Simon Glassfd709862020-07-05 21:41:50 -0600365
366 self.make_commit_with_file('Initial commit', '''
367Add a README
368
369''', 'README', '''This is the README file
370describing this project
371in very little detail''')
372
373 self.make_commit_with_file('pci: PCI implementation', '''
374Here is a basic PCI implementation
375
376''', 'pci.c', '''This is a file
377it has some contents
378and some more things''')
379 self.make_commit_with_file('main: Main program', '''
380Hello here is the second commit.
381''', 'main.c', '''This is the main file
382there is very little here
383but we can always add more later
384if we want to
385
386Series-to: u-boot
387Series-cc: Barry Crump <bcrump@whataroa.nz>
388''')
389 base_target = repo.revparse_single('HEAD')
390 self.make_commit_with_file('i2c: I2C things', '''
391This has some stuff to do with I2C
392''', 'i2c.c', '''And this is the file contents
393with some I2C-related things in it''')
394 self.make_commit_with_file('spi: SPI fixes', '''
395SPI needs some fixes
396and here they are
397''', 'spi.c', '''Some fixes for SPI in this
398file to make SPI work
399better than before''')
400 first_target = repo.revparse_single('HEAD')
401
402 target = repo.revparse_single('HEAD~2')
403 repo.reset(target.oid, pygit2.GIT_CHECKOUT_FORCE)
404 self.make_commit_with_file('video: Some video improvements', '''
405Fix up the video so that
406it looks more purple. Purple is
407a very nice colour.
408''', 'video.c', '''More purple here
409Purple and purple
410Even more purple
411Could not be any more purple''')
412 self.make_commit_with_file('serial: Add a serial driver', '''
413Here is the serial driver
414for my chip.
415
416Cover-letter:
417Series for my board
418This series implements support
419for my glorious board.
420END
Simon Glassf9e42842020-10-29 21:46:16 -0600421Series-links: 183237
Simon Glassfd709862020-07-05 21:41:50 -0600422''', 'serial.c', '''The code for the
423serial driver is here''')
424 self.make_commit_with_file('bootm: Make it boot', '''
425This makes my board boot
426with a fix to the bootm
427command
428''', 'bootm.c', '''Fix up the bootm
429command to make the code as
430complicated as possible''')
431 second_target = repo.revparse_single('HEAD')
432
433 repo.branches.local.create('first', first_target)
434 repo.config.set_multivar('branch.first.remote', '', '.')
435 repo.config.set_multivar('branch.first.merge', '', 'refs/heads/base')
436
437 repo.branches.local.create('second', second_target)
438 repo.config.set_multivar('branch.second.remote', '', '.')
439 repo.config.set_multivar('branch.second.merge', '', 'refs/heads/base')
440
441 repo.branches.local.create('base', base_target)
442 return repo
443
444 @unittest.skipIf(not HAVE_PYGIT2, 'Missing python3-pygit2')
445 def testBranch(self):
446 """Test creating patches from a branch"""
447 repo = self.make_git_tree()
448 target = repo.lookup_reference('refs/heads/first')
449 self.repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE)
450 control.setup()
451 try:
452 orig_dir = os.getcwd()
453 os.chdir(self.gitdir)
454
455 # Check that it can detect the current branch
Simon Glass262130f2020-07-05 21:41:51 -0600456 self.assertEqual(2, gitutil.CountCommitsToBranch(None))
Simon Glassfd709862020-07-05 21:41:50 -0600457 col = terminal.Color()
458 with capture_sys_output() as _:
459 _, cover_fname, patch_files = control.prepare_patches(
Simon Glass137947e2020-07-05 21:41:52 -0600460 col, branch=None, count=-1, start=0, end=0,
461 ignore_binary=False)
Simon Glassfd709862020-07-05 21:41:50 -0600462 self.assertIsNone(cover_fname)
463 self.assertEqual(2, len(patch_files))
Simon Glass262130f2020-07-05 21:41:51 -0600464
465 # Check that it can detect a different branch
466 self.assertEqual(3, gitutil.CountCommitsToBranch('second'))
467 with capture_sys_output() as _:
468 _, cover_fname, patch_files = control.prepare_patches(
Simon Glass137947e2020-07-05 21:41:52 -0600469 col, branch='second', count=-1, start=0, end=0,
Simon Glass262130f2020-07-05 21:41:51 -0600470 ignore_binary=False)
471 self.assertIsNotNone(cover_fname)
472 self.assertEqual(3, len(patch_files))
Simon Glass137947e2020-07-05 21:41:52 -0600473
474 # Check that it can skip patches at the end
475 with capture_sys_output() as _:
476 _, cover_fname, patch_files = control.prepare_patches(
477 col, branch='second', count=-1, start=0, end=1,
478 ignore_binary=False)
479 self.assertIsNotNone(cover_fname)
480 self.assertEqual(2, len(patch_files))
Simon Glassfd709862020-07-05 21:41:50 -0600481 finally:
482 os.chdir(orig_dir)