1# SPDX-License-Identifier: GPL-2.0+ 2# 3# Copyright 2025 Simon Glass <sjg@chromium.org> 4# 5"""Functional tests for checking that patman behaves correctly""" 6 7import os 8import shutil 9import tempfile 10 11import pygit2 12 13from u_boot_pylib import gitutil 14from u_boot_pylib import terminal 15from u_boot_pylib import tools 16from u_boot_pylib import tout 17 18 19class TestCommon: 20 """Contains common test functions""" 21 leb = (b'Lord Edmund Blackadd\xc3\xabr <weasel@blackadder.org>'. 22 decode('utf-8')) 23 24 # Fake patchwork project ID for U-Boot 25 PROJ_ID = 6 26 PROJ_LINK_NAME = 'uboot' 27 SERIES_ID_FIRST_V3 = 31 28 SERIES_ID_SECOND_V1 = 456 29 SERIES_ID_SECOND_V2 = 457 30 TITLE_SECOND = 'Series for my board' 31 32 verbosity = False 33 preserve_outdirs = False 34 35 @classmethod 36 def setup_test_args(cls, preserve_indir=False, preserve_outdirs=False, 37 toolpath=None, verbosity=None, no_capture=False): 38 """Accept arguments controlling test execution 39 40 Args: 41 preserve_indir (bool): not used by patman 42 preserve_outdirs (bool): Preserve the output directories used by 43 tests. Each test has its own, so this is normally only useful 44 when running a single test. 45 toolpath (str): not used by patman 46 verbosity (int): verbosity to use (0 means tout.INIT, 1 means means 47 tout.DEBUG) 48 no_capture (bool): True to output all captured text after capturing 49 completes 50 """ 51 del preserve_indir 52 cls.preserve_outdirs = preserve_outdirs 53 cls.toolpath = toolpath 54 cls.verbosity = verbosity 55 cls.no_capture = no_capture 56 57 def __init__(self): 58 super().__init__() 59 self.repo = None 60 self.tmpdir = None 61 self.gitdir = None 62 63 def setUp(self): 64 """Set up the test temporary dir and git dir""" 65 self.tmpdir = tempfile.mkdtemp(prefix='patman.') 66 self.gitdir = os.path.join(self.tmpdir, '.git') 67 tout.init(tout.DEBUG if self.verbosity else tout.INFO, 68 allow_colour=False) 69 70 def tearDown(self): 71 """Delete the temporary dir""" 72 if self.preserve_outdirs: 73 print(f'Output dir: {self.tmpdir}') 74 else: 75 shutil.rmtree(self.tmpdir) 76 terminal.set_print_test_mode(False) 77 78 def make_commit_with_file(self, subject, body, fname, text): 79 """Create a file and add it to the git repo with a new commit 80 81 Args: 82 subject (str): Subject for the commit 83 body (str): Body text of the commit 84 fname (str): Filename of file to create 85 text (str): Text to put into the file 86 """ 87 path = os.path.join(self.tmpdir, fname) 88 tools.write_file(path, text, binary=False) 89 index = self.repo.index 90 index.add(fname) 91 # pylint doesn't seem to find this 92 # pylint: disable=E1101 93 author = pygit2.Signature('Test user', 'test@email.com') 94 committer = author 95 tree = index.write_tree() 96 message = subject + '\n' + body 97 self.repo.create_commit('HEAD', author, committer, message, tree, 98 [self.repo.head.target]) 99 100 def make_git_tree(self): 101 """Make a simple git tree suitable for testing 102 103 It has four branches: 104 'base' has two commits: PCI, main 105 'first' has base as upstream and two more commits: I2C, SPI 106 'second' has base as upstream and three more: video, serial, bootm 107 'third4' has second as upstream and four more: usb, main, test, lib 108 109 Returns: 110 pygit2.Repository: repository 111 """ 112 os.environ['GIT_CONFIG_GLOBAL'] = '/dev/null' 113 os.environ['GIT_CONFIG_SYSTEM'] = '/dev/null' 114 115 repo = pygit2.init_repository(self.gitdir) 116 self.repo = repo 117 new_tree = repo.TreeBuilder().write() 118 119 common = ['git', f'--git-dir={self.gitdir}', 'config'] 120 tools.run(*(common + ['user.name', 'Dummy']), cwd=self.gitdir) 121 tools.run(*(common + ['user.email', 'dumdum@dummy.com']), 122 cwd=self.gitdir) 123 124 # pylint doesn't seem to find this 125 # pylint: disable=E1101 126 author = pygit2.Signature('Test user', 'test@email.com') 127 committer = author 128 _ = repo.create_commit('HEAD', author, committer, 'Created master', 129 new_tree, []) 130 131 self.make_commit_with_file('Initial commit', ''' 132Add a README 133 134''', 'README', '''This is the README file 135describing this project 136in very little detail''') 137 138 self.make_commit_with_file('pci: PCI implementation', ''' 139Here is a basic PCI implementation 140 141''', 'pci.c', '''This is a file 142it has some contents 143and some more things''') 144 self.make_commit_with_file('main: Main program', ''' 145Hello here is the second commit. 146''', 'main.c', '''This is the main file 147there is very little here 148but we can always add more later 149if we want to 150 151Series-to: u-boot 152Series-cc: Barry Crump <bcrump@whataroa.nz> 153''') 154 base_target = repo.revparse_single('HEAD') 155 self.make_commit_with_file('i2c: I2C things', ''' 156This has some stuff to do with I2C 157''', 'i2c.c', '''And this is the file contents 158with some I2C-related things in it''') 159 self.make_commit_with_file('spi: SPI fixes', f''' 160SPI needs some fixes 161and here they are 162 163Signed-off-by: {self.leb} 164 165Series-to: u-boot 166Commit-notes: 167title of the series 168This is the cover letter for the series 169with various details 170END 171''', 'spi.c', '''Some fixes for SPI in this 172file to make SPI work 173better than before''') 174 first_target = repo.revparse_single('HEAD') 175 176 target = repo.revparse_single('HEAD~2') 177 # pylint doesn't seem to find this 178 # pylint: disable=E1101 179 repo.reset(target.oid, pygit2.enums.ResetMode.HARD) 180 self.make_commit_with_file('video: Some video improvements', ''' 181Fix up the video so that 182it looks more purple. Purple is 183a very nice colour. 184''', 'video.c', '''More purple here 185Purple and purple 186Even more purple 187Could not be any more purple''') 188 self.make_commit_with_file('serial: Add a serial driver', f''' 189Here is the serial driver 190for my chip. 191 192Cover-letter: 193{self.TITLE_SECOND} 194This series implements support 195for my glorious board. 196END 197Series-to: u-boot 198Series-links: {self.SERIES_ID_SECOND_V1} 199''', 'serial.c', '''The code for the 200serial driver is here''') 201 self.make_commit_with_file('bootm: Make it boot', ''' 202This makes my board boot 203with a fix to the bootm 204command 205''', 'bootm.c', '''Fix up the bootm 206command to make the code as 207complicated as possible''') 208 second_target = repo.revparse_single('HEAD') 209 210 self.make_commit_with_file('usb: Try out the new DMA feature', ''' 211This is just a fix that 212ensures that DMA is enabled 213''', 'usb-uclass.c', '''Here is the USB 214implementation and as you can see it 215it very nice''') 216 self.make_commit_with_file('main: Change to the main program', ''' 217Here we adjust the main 218program just a little bit 219''', 'main.c', '''This is the text of the main program''') 220 self.make_commit_with_file('test: Check that everything works', ''' 221This checks that all the 222various things we've been 223adding actually work. 224''', 'test.c', '''Here is the test code and it seems OK''') 225 self.make_commit_with_file('lib: Sort out the extra library', ''' 226The extra library is currently 227broken. Fix it so that we can 228use it in various place. 229''', 'lib.c', '''Some library code is here 230and a little more''') 231 third_target = repo.revparse_single('HEAD') 232 233 repo.branches.local.create('first', first_target) 234 repo.config.set_multivar('branch.first.remote', '', '.') 235 repo.config.set_multivar('branch.first.merge', '', 'refs/heads/base') 236 237 repo.branches.local.create('second', second_target) 238 repo.config.set_multivar('branch.second.remote', '', '.') 239 repo.config.set_multivar('branch.second.merge', '', 'refs/heads/base') 240 241 repo.branches.local.create('base', base_target) 242 243 repo.branches.local.create('third4', third_target) 244 repo.config.set_multivar('branch.third4.remote', '', '.') 245 repo.config.set_multivar('branch.third4.merge', '', 246 'refs/heads/second') 247 248 target = repo.lookup_reference('refs/heads/first') 249 repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE) 250 target = repo.revparse_single('HEAD') 251 repo.reset(target.oid, pygit2.enums.ResetMode.HARD) 252 253 self.assertFalse(gitutil.check_dirty(self.gitdir, self.tmpdir)) 254 return repo 255