patman: Fix whitespace errors in func_test
[platform/kernel/u-boot.git] / tools / patman / func_test.py
1 # -*- coding: utf-8 -*-
2 # SPDX-License-Identifier:      GPL-2.0+
3 #
4 # Copyright 2017 Google, Inc
5 #
6
7 import contextlib
8 import os
9 import re
10 import shutil
11 import sys
12 import tempfile
13 import unittest
14
15 from io import StringIO
16
17 from patman import control
18 from patman import gitutil
19 from patman import patchstream
20 from patman import settings
21 from patman import terminal
22 from patman import tools
23 from patman.test_util import capture_sys_output
24
25 try:
26     import pygit2
27     HAVE_PYGIT2 = True
28 except ModuleNotFoundError:
29     HAVE_PYGIT2 = False
30
31
32 @contextlib.contextmanager
33 def capture():
34     oldout, olderr = sys.stdout, sys.stderr
35     try:
36         out = [StringIO(), StringIO()]
37         sys.stdout, sys.stderr = out
38         yield out
39     finally:
40         sys.stdout, sys.stderr = oldout, olderr
41         out[0] = out[0].getvalue()
42         out[1] = out[1].getvalue()
43
44
45 class TestFunctional(unittest.TestCase):
46     def setUp(self):
47         self.tmpdir = tempfile.mkdtemp(prefix='patman.')
48         self.gitdir = os.path.join(self.tmpdir, 'git')
49         self.repo = None
50
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):
61         return open(self.GetPath(fname), encoding='utf-8').read()
62
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>
95             Series-version: 3
96             Patch-cc: fred
97             Series-process-log: sort, uniq
98             Series-changes: 4
99             - Some changes
100             - 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
109
110             Cover-letter:
111             test: A test patch series
112             This is a test of how the cover
113             letter
114             works
115             END
116
117         and this in the first commit:
118
119             Commit-changes: 2
120             - second revision change
121
122             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
153         stefan = b'Stefan Br\xc3\xbcns <stefan.bruens@rwth-aachen.de>'.decode('utf-8')
154         rick = 'Richard III <richard@palace.gov>'
155         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')
157         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 = {
163             'fdt': ['simon'],
164             'u-boot': ['u-boot@lists.denx.de'],
165             'simon': [ed],
166             'fred': [fred],
167         }
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,
178                                         not ignore_bad_tags, add_maintainers,
179                                         None)
180             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)
183             series.ShowActions(args, cmd, process_tags)
184         cc_lines = open(cc_file, encoding='utf-8').read().splitlines()
185         os.remove(cc_file)
186
187         lines = out[0].splitlines()
188         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])
202         self.assertEqual('Cc:     %s' % tools.FromUnicode(stefan),
203                          lines[line + 1])
204         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
208         self.assertEqual('      Cc:  %s' % fred, lines[line + 0])
209         self.assertEqual('      Cc:  %s' % tools.FromUnicode(ed),
210                          lines[line + 1])
211         self.assertEqual('      Cc:  %s' % tools.FromUnicode(mel),
212                          lines[line + 2])
213         self.assertEqual('      Cc:  %s' % rick, lines[line + 3])
214         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,
218                        ' '.join(args)))
219         line += 4
220         self.assertEqual(expected, tools.ToUnicode(lines[line]))
221
222         self.assertEqual(('%s %s\0%s' % (args[0], rick, stefan)),
223                          tools.ToUnicode(cc_lines[0]))
224         self.assertEqual(
225             '%s %s\0%s\0%s\0%s' % (args[1], fred, ed, rick, stefan),
226             tools.ToUnicode(cc_lines[1]))
227
228         expected = '''
229 This is a test of how the cover
230 letter
231 works
232
233 some notes
234 about some things
235 from the first commit
236
237 Changes in v4:
238 - Multi
239   line
240   change
241 - Some changes
242 - Some notes for the cover letter
243
244 Simon Glass (2):
245   pci: Correct cast for sandbox
246   fdt: Correct cast for sandbox in fdtdec_setup_mem_size_base()
247
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
255 2.7.4
256
257 '''
258         lines = open(cover_fname, encoding='utf-8').read().splitlines()
259         self.assertEqual(
260             'Subject: [RFC PATCH v3 0/2] test: A test patch series',
261             lines[3])
262         self.assertEqual(expected.splitlines(), lines[7:])
263
264         for i, fname in enumerate(args):
265             lines = open(fname, encoding='utf-8').read().splitlines()
266             subject = [line for line in lines if line.startswith('Subject')]
267             self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count),
268                              subject[0][:18])
269
270             # Check that we got our commit notes
271             start = 0
272             expected = ''
273
274             if i == 0:
275                 start = 17
276                 expected = '''---
277 Some notes about
278 the first commit
279
280 (no changes since v2)
281
282 Changes in v2:
283 - second revision change'''
284             elif i == 1:
285                 start = 17
286                 expected = '''---
287
288 Changes in v4:
289 - Multi
290   line
291   change
292 - Some changes
293
294 Changes 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))])
300
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)
314         author = pygit2.Signature('Test user', 'test@email.com')
315         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,
339                                     'Created master', new_tree, [])
340
341         self.make_commit_with_file('Initial commit', '''
342 Add a README
343
344 ''', 'README', '''This is the README file
345 describing this project
346 in very little detail''')
347
348         self.make_commit_with_file('pci: PCI implementation', '''
349 Here is a basic PCI implementation
350
351 ''', 'pci.c', '''This is a file
352 it has some contents
353 and some more things''')
354         self.make_commit_with_file('main: Main program', '''
355 Hello here is the second commit.
356 ''', 'main.c', '''This is the main file
357 there is very little here
358 but we can always add more later
359 if we want to
360
361 Series-to: u-boot
362 Series-cc: Barry Crump <bcrump@whataroa.nz>
363 ''')
364         base_target = repo.revparse_single('HEAD')
365         self.make_commit_with_file('i2c: I2C things', '''
366 This has some stuff to do with I2C
367 ''', 'i2c.c', '''And this is the file contents
368 with some I2C-related things in it''')
369         self.make_commit_with_file('spi: SPI fixes', '''
370 SPI needs some fixes
371 and here they are
372 ''', 'spi.c', '''Some fixes for SPI in this
373 file to make SPI work
374 better 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', '''
380 Fix up the video so that
381 it looks more purple. Purple is
382 a very nice colour.
383 ''', 'video.c', '''More purple here
384 Purple and purple
385 Even more purple
386 Could not be any more purple''')
387         self.make_commit_with_file('serial: Add a serial driver', '''
388 Here is the serial driver
389 for my chip.
390
391 Cover-letter:
392 Series for my board
393 This series implements support
394 for my glorious board.
395 END
396 ''', 'serial.c', '''The code for the
397 serial driver is here''')
398         self.make_commit_with_file('bootm: Make it boot', '''
399 This makes my board boot
400 with a fix to the bootm
401 command
402 ''', 'bootm.c', '''Fix up the bootm
403 command to make the code as
404 complicated 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
430             self.assertEqual(2, gitutil.CountCommitsToBranch(None))
431             col = terminal.Color()
432             with capture_sys_output() as _:
433                 _, cover_fname, patch_files = control.prepare_patches(
434                     col, branch=None, count=-1, start=0, end=0,
435                     ignore_binary=False)
436             self.assertIsNone(cover_fname)
437             self.assertEqual(2, len(patch_files))
438
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(
443                     col, branch='second', count=-1, start=0, end=0,
444                     ignore_binary=False)
445             self.assertIsNotNone(cover_fname)
446             self.assertEqual(3, len(patch_files))
447
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))
455         finally:
456             os.chdir(orig_dir)