[PATCH 06/25] patman: Move common test code into a new module

Simon Glass sjg at chromium.org
Sat May 10 13:04:59 CEST 2025


The func_test file is quite large. In order to allow new tests to be
added to a separate file, move the common test code into a separate
class, to be inherited by other classes.

Drop unnecessary imports in func_test

Signed-off-by: Simon Glass <sjg at chromium.org>
---

 tools/patman/__init__.py    |   2 +-
 tools/patman/control.py     |   3 -
 tools/patman/func_test.py   | 213 ++++------------------------------
 tools/patman/test_common.py | 222 ++++++++++++++++++++++++++++++++++++
 4 files changed, 248 insertions(+), 192 deletions(-)
 create mode 100644 tools/patman/test_common.py

diff --git a/tools/patman/__init__.py b/tools/patman/__init__.py
index 14451e35f36..0faef0cfa75 100644
--- a/tools/patman/__init__.py
+++ b/tools/patman/__init__.py
@@ -4,5 +4,5 @@ __all__ = [
     'checkpatch', 'cmdline', 'commit', 'control', 'func_test',
     'get_maintainer', '__main__', 'patchstream', 'patchwork', 'project',
     'send', 'series', 'settings', 'setup', 'status', 'test_checkpatch',
-    'test_settings'
+    'test_common', 'test_settings'
 ]
diff --git a/tools/patman/control.py b/tools/patman/control.py
index 902b5092e9c..7bf0e7ff61a 100644
--- a/tools/patman/control.py
+++ b/tools/patman/control.py
@@ -8,9 +8,7 @@ This module provides various functions called by the main program to implement
 the features of patman.
 """
 
-import os
 import re
-import sys
 import traceback
 
 try:
@@ -22,7 +20,6 @@ except ImportError:
 from u_boot_pylib import gitutil
 from u_boot_pylib import terminal
 from u_boot_pylib import tools
-from patman import checkpatch
 from patman import patchstream
 from patman import patchwork
 from patman import send
diff --git a/tools/patman/func_test.py b/tools/patman/func_test.py
index 55abf52bdb5..2faff8019f6 100644
--- a/tools/patman/func_test.py
+++ b/tools/patman/func_test.py
@@ -13,7 +13,6 @@ import pathlib
 import re
 import shutil
 import sys
-import tempfile
 import unittest
 
 import pygit2
@@ -31,6 +30,7 @@ from patman import patchwork
 from patman import send
 from patman.series import Series
 from patman import status
+from patman.test_common import TestCommon
 
 PATMAN_DIR = pathlib.Path(__file__).parent
 TEST_DATA_DIR = PATMAN_DIR / 'test/'
@@ -47,52 +47,22 @@ def directory_excursion(directory):
         os.chdir(current)
 
 
-class TestFunctional(unittest.TestCase):
+class TestFunctional(unittest.TestCase, TestCommon):
     """Functional tests for checking that patman behaves correctly"""
-    leb = (b'Lord Edmund Blackadd\xc3\xabr <weasel at blackadder.org>'.
-           decode('utf-8'))
     fred = 'Fred Bloggs <f.bloggs at napier.net>'
     joe = 'Joe Bloggs <joe at napierwallies.co.nz>'
     mary = 'Mary Bloggs <mary at napierwallies.co.nz>'
     commits = None
     patches = None
-    verbosity = False
-    preserve_outdirs = False
-
-    # Fake patchwork info for testing
-    SERIES_ID_SECOND_V1 = 456
-    TITLE_SECOND = 'Series for my board'
-
-    @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: not used
-            preserve_outdir: Preserve the output directories used by tests.
-                Each test has its own, so this is normally only useful when
-                running a single test.
-            toolpath: not used
-        """
-        cls.preserve_outdirs = preserve_outdirs
-        cls.toolpath = toolpath
-        cls.verbosity = verbosity
-        cls.no_capture = no_capture
 
     def setUp(self):
-        self.tmpdir = tempfile.mkdtemp(prefix='patman.')
-        self.gitdir = os.path.join(self.tmpdir, '.git')
+        TestCommon.setUp(self)
         self.repo = None
         self._patman_pathname = sys.argv[0]
         self._patman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
 
     def tearDown(self):
-        if self.preserve_outdirs:
-            print(f'Output dir: {self.tmpdir}')
-        else:
-            shutil.rmtree(self.tmpdir)
-        terminal.set_print_test_mode(False)
+        TestCommon.tearDown(self)
 
     @staticmethod
     def _get_path(fname):
@@ -264,7 +234,7 @@ class TestFunctional(unittest.TestCase):
                 series, cover_fname, args, dry_run, not ignore_bad_tags,
                 cc_file, alias, in_reply_to=in_reply_to, thread=None)
             series.ShowActions(args, cmd, process_tags, alias)
-        cc_lines = open(cc_file, encoding='utf-8').read().splitlines()
+        cc_lines = tools.read_file(cc_file, binary=False).splitlines()
         os.remove(cc_file)
 
         itr = iter(out[0].getvalue().splitlines())
@@ -344,14 +314,14 @@ Simon Glass (2):
 base-commit: 1a44532
 branch: mybranch
 '''
-        lines = open(cover_fname, encoding='utf-8').read().splitlines()
+        lines = tools.read_file(cover_fname, binary=False).splitlines()
         self.assertEqual(
             'Subject: [RFC PATCH some-branch v3 0/2] test: A test patch series',
             lines[3])
         self.assertEqual(expected.splitlines(), lines[7:])
 
         for i, fname in enumerate(args):
-            lines = open(fname, encoding='utf-8').read().splitlines()
+            lines = tools.read_file(fname, binary=False).splitlines()
             subject = [line for line in lines if line.startswith('Subject')]
             self.assertEqual('Subject: [RFC %d/%d]' % (i + 1, count),
                              subject[0][:18])
@@ -414,152 +384,6 @@ Changes in v2:
         self.assertEqual('base-commit: 1a44532', lines[pos + 3])
         self.assertEqual('branch: mybranch', lines[pos + 4])
 
-    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 at 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 three 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
-
-        Returns:
-            pygit2.Repository: repository
-        """
-        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 at dummy.com']),
-                  cwd=self.gitdir)
-
-        # pylint doesn't seem to find this
-        # pylint: disable=E1101
-        author = pygit2.Signature('Test user', 'test at 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 at 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', '''
-SPI needs some fixes
-and here they are
-
-Signed-off-by: %s
-
-Series-to: u-boot
-Commit-notes:
-title of the series
-This is the cover letter for the series
-with various details
-END
-''' % self.leb, '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')
-
-        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)
-
-        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
-
     def test_branch(self):
         """Test creating patches from a branch"""
         repo = self.make_git_tree()
@@ -787,13 +611,25 @@ diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c
         finally:
             os.chdir(orig_dir)
 
-    def _RunPatman(self, *args):
+    def run_patman(self, *args):
+        """Run patman using the provided arguments
+
+        This runs the patman executable from scratch, as opposed to calling
+        the control.do_patman() function.
+
+        Args:
+            args (list of str): Arguments to pass (excluding argv[0])
+
+        Return:
+            CommandResult: Result of execution
+        """
         all_args = [self._patman_pathname] + list(args)
         return command.run_one(*all_args, capture=True, capture_stderr=True)
 
-    def testFullHelp(self):
+    def test_full_help(self):
+        """Test getting full help"""
         command.TEST_RESULT = None
-        result = self._RunPatman('-H')
+        result = self.run_patman('-H')
         help_file = os.path.join(self._patman_dir, 'README.rst')
         # Remove possible extraneous strings
         extra = '::::::::::::::\n' + help_file + '\n::::::::::::::\n'
@@ -802,9 +638,10 @@ diff --git a/lib/efi_loader/efi_memory.c b/lib/efi_loader/efi_memory.c
         self.assertEqual(0, len(result.stderr))
         self.assertEqual(0, result.return_code)
 
-    def testHelp(self):
+    def test_help(self):
+        """Test getting help with commands and arguments"""
         command.TEST_RESULT = None
-        result = self._RunPatman('-h')
+        result = self.run_patman('-h')
         self.assertTrue(len(result.stdout) > 1000)
         self.assertEqual(0, len(result.stderr))
         self.assertEqual(0, result.return_code)
diff --git a/tools/patman/test_common.py b/tools/patman/test_common.py
new file mode 100644
index 00000000000..fda62472320
--- /dev/null
+++ b/tools/patman/test_common.py
@@ -0,0 +1,222 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Copyright 2025 Simon Glass <sjg at 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 at 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 at 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 three 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
+
+        Returns:
+            pygit2.Repository: repository
+        """
+        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 at dummy.com']),
+                  cwd=self.gitdir)
+
+        # pylint doesn't seem to find this
+        # pylint: disable=E1101
+        author = pygit2.Signature('Test user', 'test at 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 at 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')
+
+        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)
+
+        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
-- 
2.43.0



More information about the U-Boot mailing list