| # SPDX-License-Identifier: GPL-2.0+ |
| # |
| # Copyright 2025 Simon Glass <sjg@chromium.org> |
| # |
| """Functional tests for checking that patman behaves correctly""" |
| |
| import os |
| import shutil |
| import tempfile |
| |
| import pygit2 |
| |
| from u_boot_pylib import gitutil |
| from u_boot_pylib import terminal |
| from u_boot_pylib import tools |
| from u_boot_pylib import tout |
| |
| |
| class TestCommon: |
| """Contains common test functions""" |
| leb = (b'Lord Edmund Blackadd\xc3\xabr <weasel@blackadder.org>'. |
| decode('utf-8')) |
| |
| # Fake patchwork project ID for U-Boot |
| PROJ_ID = 6 |
| PROJ_LINK_NAME = 'uboot' |
| SERIES_ID_FIRST_V3 = 31 |
| SERIES_ID_SECOND_V1 = 456 |
| SERIES_ID_SECOND_V2 = 457 |
| TITLE_SECOND = 'Series for my board' |
| |
| verbosity = False |
| preserve_outdirs = False |
| |
| @classmethod |
| def setup_test_args(cls, preserve_indir=False, preserve_outdirs=False, |
| toolpath=None, verbosity=None, no_capture=False): |
| """Accept arguments controlling test execution |
| |
| Args: |
| preserve_indir (bool): not used by patman |
| preserve_outdirs (bool): Preserve the output directories used by |
| tests. Each test has its own, so this is normally only useful |
| when running a single test. |
| toolpath (str): not used by patman |
| verbosity (int): verbosity to use (0 means tout.INIT, 1 means means |
| tout.DEBUG) |
| no_capture (bool): True to output all captured text after capturing |
| completes |
| """ |
| del preserve_indir |
| cls.preserve_outdirs = preserve_outdirs |
| cls.toolpath = toolpath |
| cls.verbosity = verbosity |
| cls.no_capture = no_capture |
| |
| def __init__(self): |
| super().__init__() |
| self.repo = None |
| self.tmpdir = None |
| self.gitdir = None |
| |
| def setUp(self): |
| """Set up the test temporary dir and git dir""" |
| self.tmpdir = tempfile.mkdtemp(prefix='patman.') |
| self.gitdir = os.path.join(self.tmpdir, '.git') |
| tout.init(tout.DEBUG if self.verbosity else tout.INFO, |
| allow_colour=False) |
| |
| def tearDown(self): |
| """Delete the temporary dir""" |
| if self.preserve_outdirs: |
| print(f'Output dir: {self.tmpdir}') |
| else: |
| shutil.rmtree(self.tmpdir) |
| terminal.set_print_test_mode(False) |
| |
| def make_commit_with_file(self, subject, body, fname, text): |
| """Create a file and add it to the git repo with a new commit |
| |
| Args: |
| subject (str): Subject for the commit |
| body (str): Body text of the commit |
| fname (str): Filename of file to create |
| text (str): Text to put into the file |
| """ |
| path = os.path.join(self.tmpdir, fname) |
| tools.write_file(path, text, binary=False) |
| index = self.repo.index |
| index.add(fname) |
| # pylint doesn't seem to find this |
| # pylint: disable=E1101 |
| author = pygit2.Signature('Test user', 'test@email.com') |
| committer = author |
| tree = index.write_tree() |
| message = subject + '\n' + body |
| self.repo.create_commit('HEAD', author, committer, message, tree, |
| [self.repo.head.target]) |
| |
| def make_git_tree(self): |
| """Make a simple git tree suitable for testing |
| |
| It has four branches: |
| 'base' has two commits: PCI, main |
| 'first' has base as upstream and two more commits: I2C, SPI |
| 'second' has base as upstream and three more: video, serial, bootm |
| 'third4' has second as upstream and four more: usb, main, test, lib |
| |
| Returns: |
| pygit2.Repository: repository |
| """ |
| os.environ['GIT_CONFIG_GLOBAL'] = '/dev/null' |
| os.environ['GIT_CONFIG_SYSTEM'] = '/dev/null' |
| |
| repo = pygit2.init_repository(self.gitdir) |
| self.repo = repo |
| new_tree = repo.TreeBuilder().write() |
| |
| common = ['git', f'--git-dir={self.gitdir}', 'config'] |
| tools.run(*(common + ['user.name', 'Dummy']), cwd=self.gitdir) |
| tools.run(*(common + ['user.email', 'dumdum@dummy.com']), |
| cwd=self.gitdir) |
| |
| # pylint doesn't seem to find this |
| # pylint: disable=E1101 |
| author = pygit2.Signature('Test user', 'test@email.com') |
| committer = author |
| _ = repo.create_commit('HEAD', author, committer, 'Created master', |
| new_tree, []) |
| |
| self.make_commit_with_file('Initial commit', ''' |
| Add a README |
| |
| ''', 'README', '''This is the README file |
| describing this project |
| in very little detail''') |
| |
| self.make_commit_with_file('pci: PCI implementation', ''' |
| Here is a basic PCI implementation |
| |
| ''', 'pci.c', '''This is a file |
| it has some contents |
| and some more things''') |
| self.make_commit_with_file('main: Main program', ''' |
| Hello here is the second commit. |
| ''', 'main.c', '''This is the main file |
| there is very little here |
| but we can always add more later |
| if we want to |
| |
| Series-to: u-boot |
| Series-cc: Barry Crump <bcrump@whataroa.nz> |
| ''') |
| base_target = repo.revparse_single('HEAD') |
| self.make_commit_with_file('i2c: I2C things', ''' |
| This has some stuff to do with I2C |
| ''', 'i2c.c', '''And this is the file contents |
| with some I2C-related things in it''') |
| self.make_commit_with_file('spi: SPI fixes', f''' |
| SPI needs some fixes |
| and here they are |
| |
| Signed-off-by: {self.leb} |
| |
| Series-to: u-boot |
| Commit-notes: |
| title of the series |
| This is the cover letter for the series |
| with various details |
| END |
| ''', 'spi.c', '''Some fixes for SPI in this |
| file to make SPI work |
| better than before''') |
| first_target = repo.revparse_single('HEAD') |
| |
| target = repo.revparse_single('HEAD~2') |
| # pylint doesn't seem to find this |
| # pylint: disable=E1101 |
| repo.reset(target.oid, pygit2.enums.ResetMode.HARD) |
| self.make_commit_with_file('video: Some video improvements', ''' |
| Fix up the video so that |
| it looks more purple. Purple is |
| a very nice colour. |
| ''', 'video.c', '''More purple here |
| Purple and purple |
| Even more purple |
| Could not be any more purple''') |
| self.make_commit_with_file('serial: Add a serial driver', f''' |
| Here is the serial driver |
| for my chip. |
| |
| Cover-letter: |
| {self.TITLE_SECOND} |
| This series implements support |
| for my glorious board. |
| END |
| Series-to: u-boot |
| Series-links: {self.SERIES_ID_SECOND_V1} |
| ''', 'serial.c', '''The code for the |
| serial driver is here''') |
| self.make_commit_with_file('bootm: Make it boot', ''' |
| This makes my board boot |
| with a fix to the bootm |
| command |
| ''', 'bootm.c', '''Fix up the bootm |
| command to make the code as |
| complicated as possible''') |
| second_target = repo.revparse_single('HEAD') |
| |
| self.make_commit_with_file('usb: Try out the new DMA feature', ''' |
| This is just a fix that |
| ensures that DMA is enabled |
| ''', 'usb-uclass.c', '''Here is the USB |
| implementation and as you can see it |
| it very nice''') |
| self.make_commit_with_file('main: Change to the main program', ''' |
| Here we adjust the main |
| program just a little bit |
| ''', 'main.c', '''This is the text of the main program''') |
| self.make_commit_with_file('test: Check that everything works', ''' |
| This checks that all the |
| various things we've been |
| adding actually work. |
| ''', 'test.c', '''Here is the test code and it seems OK''') |
| self.make_commit_with_file('lib: Sort out the extra library', ''' |
| The extra library is currently |
| broken. Fix it so that we can |
| use it in various place. |
| ''', 'lib.c', '''Some library code is here |
| and a little more''') |
| third_target = repo.revparse_single('HEAD') |
| |
| repo.branches.local.create('first', first_target) |
| repo.config.set_multivar('branch.first.remote', '', '.') |
| repo.config.set_multivar('branch.first.merge', '', 'refs/heads/base') |
| |
| repo.branches.local.create('second', second_target) |
| repo.config.set_multivar('branch.second.remote', '', '.') |
| repo.config.set_multivar('branch.second.merge', '', 'refs/heads/base') |
| |
| repo.branches.local.create('base', base_target) |
| |
| repo.branches.local.create('third4', third_target) |
| repo.config.set_multivar('branch.third4.remote', '', '.') |
| repo.config.set_multivar('branch.third4.merge', '', |
| 'refs/heads/second') |
| |
| target = repo.lookup_reference('refs/heads/first') |
| repo.checkout(target, strategy=pygit2.GIT_CHECKOUT_FORCE) |
| target = repo.revparse_single('HEAD') |
| repo.reset(target.oid, pygit2.enums.ResetMode.HARD) |
| |
| self.assertFalse(gitutil.check_dirty(self.gitdir, self.tmpdir)) |
| return repo |