From patchwork Wed Dec 17 02:25:50 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 928 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1765938401; bh=t0VUOuMYlaMxj8xgX3OoHstNu2iIQpPgtTVTipz8jzQ=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=hnyd0X8vD9pqtPn6cUhmDDr+cTA055hOiA+J9kKMXe6gfh6QWPDBcEs8b311g4L8k jvsXMweAY1YYt2sgiY0J23ZjAmABESN5ULG24R7rMYqf3WV6Op/rIfG3PIiaSxfi9C +id5ADml+Ylh4n5U3GmJaUnl2CdqP1ePnSiumt77OMu/TKNocuvNZtntfsniEEi7il S6u/7gm4qsPgvkZtDr3P1xKo10EsiYf9c/USd8IGjI3k3Xxm6prkrTZTFq52cU5zjd uXapDpfOENrFenCKnO9nLnxpQPVj9DY70Xoc2NRfLTq+ai2GF3FapPgF6ZEt1hRXvu POodLBd430kgw== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 655A768AEE for ; Tue, 16 Dec 2025 19:26:41 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id sLxG96Yx8tfM for ; Tue, 16 Dec 2025 19:26:41 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1765938401; bh=t0VUOuMYlaMxj8xgX3OoHstNu2iIQpPgtTVTipz8jzQ=; h=From:To:Date:In-Reply-To:References:CC:Subject:List-Id: List-Archive:List-Help:List-Owner:List-Post:List-Subscribe: List-Unsubscribe:From; b=hnyd0X8vD9pqtPn6cUhmDDr+cTA055hOiA+J9kKMXe6gfh6QWPDBcEs8b311g4L8k jvsXMweAY1YYt2sgiY0J23ZjAmABESN5ULG24R7rMYqf3WV6Op/rIfG3PIiaSxfi9C +id5ADml+Ylh4n5U3GmJaUnl2CdqP1ePnSiumt77OMu/TKNocuvNZtntfsniEEi7il S6u/7gm4qsPgvkZtDr3P1xKo10EsiYf9c/USd8IGjI3k3Xxm6prkrTZTFq52cU5zjd uXapDpfOENrFenCKnO9nLnxpQPVj9DY70Xoc2NRfLTq+ai2GF3FapPgF6ZEt1hRXvu POodLBd430kgw== Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 4FE7168AFB for ; Tue, 16 Dec 2025 19:26:41 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1765938398; bh=OtYt+WrmMgnMChQarrLWf6hnaHWHcvUWlFl+KM3f/og=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=OCCazqUwHbrdANo23GY0EDYJCY6FLHyB2w+CgPbeLHLq0x8gs9s2J5C3ryexxQmbQ z+ONAsp9t9GvfXih6sOoDBfIN5aNNfeUZKOHxXwQEwld6q8gpQx/0mqRfIvZlNgMmm rduBTYm9ZuxCNwbTUz+LpfY72vBk7+v831RPpTG8neRmeJ9KQby0dNdxj7B3MAFhp7 WLGJ0ZzLgp+AuQPkVT0zJGdC7UTZwtrEf0FUNzTFPXou79O/Xwid4+/Xlcg9bIn+Zm 9bypADuisS5q9xtpYNpDl/FuHWXmN2xrRftdchw1eN9yxm5bQPD5g22oVRu2moetf5 0WJ3t3gjL6RpA== Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 4750868AEE; Tue, 16 Dec 2025 19:26:38 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10026) with ESMTP id V8WGqsppwGuM; Tue, 16 Dec 2025 19:26:38 -0700 (MST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=u-boot.org; s=default; t=1765938394; bh=wgXyhC4faFpknmj9qp7n6tEjpJU+lQ+Fx8B13Recu/g=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gdiLx2ad/XNBQyNiafE7dWFs2Ol+Xpwfzf0gpWH+GnfCGins0A2XLjV2RUBN5NMFa GdbpzC95WLPdEix4gl7Kmd86BoIItdBT7+pdJ/jl80Ed3IxFJqW6Q0QYsaD8o1pi7U GFKaLZkvkghfuE/nL8jVIsYKdi0TViIDKrLOPXY2VTgGz81GDj9wrXxApdp0X4sj0H IwwiPL/9BUTy75E9sQbY5wXktUS6yQsNSsO+je+HCFLG0okEoEuGKt6FzdOlAT1Mlp vRZKxogZ6bmUhX5E7w2tHf8d/piTNGoK+VGnbOrsMNx5BXanN3UqRIbogDVA8b2d/1 S/va80coCNXnQ== Received: from u-boot.org (unknown [73.34.74.121]) by mail.u-boot.org (Postfix) with ESMTPSA id A8A506892F; Tue, 16 Dec 2025 19:26:33 -0700 (MST) From: Simon Glass To: U-Boot Concept Date: Tue, 16 Dec 2025 19:25:50 -0700 Message-ID: <20251217022611.389379-3-sjg@u-boot.org> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20251217022611.389379-1-sjg@u-boot.org> References: <20251217022611.389379-1-sjg@u-boot.org> MIME-Version: 1.0 Message-ID-Hash: MZDXDDT42UTOLWJZORGY6RIHFKF3TPKW X-Message-ID-Hash: MZDXDDT42UTOLWJZORGY6RIHFKF3TPKW X-MailFrom: sjg@u-boot.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Heinrich Schuchardt , Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 02/17] pickman: Add argument parsing with compare and test commands List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: Simon Glass Add subcommand support: - compare: Compare branches (existing functionality) - test: Run the functional tests Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- tools/pickman/__main__.py | 32 +++++++++++++++++++--- tools/pickman/control.py | 48 ++++++++++++++++++++++++++++----- tools/pickman/ftest.py | 57 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 126 insertions(+), 11 deletions(-) diff --git a/tools/pickman/__main__.py b/tools/pickman/__main__.py index eb0d6e226cc..0984c62d3e6 100755 --- a/tools/pickman/__main__.py +++ b/tools/pickman/__main__.py @@ -4,8 +4,9 @@ # Copyright 2025 Canonical Ltd. # Written by Simon Glass # -"""Entry point for pickman - dispatches to control module.""" +"""Entry point for pickman - parses arguments and dispatches to control.""" +import argparse import os import sys @@ -17,9 +18,32 @@ sys.path.insert(0, os.path.join(our_path, '..')) from pickman import control -def main(): - """Main function.""" - return control.do_pickman() +def parse_args(argv): + """Parse command line arguments. + + Args: + argv (list): Command line arguments + + Returns: + Namespace: Parsed arguments + """ + parser = argparse.ArgumentParser(description='Check commit differences') + subparsers = parser.add_subparsers(dest='cmd', required=True) + + subparsers.add_parser('compare', help='Compare branches') + subparsers.add_parser('test', help='Run tests') + + return parser.parse_args(argv) + + +def main(argv=None): + """Main function to parse args and run commands. + + Args: + argv (list): Command line arguments (None for sys.argv[1:]) + """ + args = parse_args(argv) + return control.do_pickman(args) if __name__ == '__main__': diff --git a/tools/pickman/control.py b/tools/pickman/control.py index 990fa1b0729..0ed54dd724c 100644 --- a/tools/pickman/control.py +++ b/tools/pickman/control.py @@ -8,12 +8,14 @@ from collections import namedtuple import os import sys +import unittest # Allow 'from pickman import xxx' to work via symlink our_path = os.path.dirname(os.path.realpath(__file__)) sys.path.insert(0, os.path.join(our_path, '..')) # pylint: disable=wrong-import-position,import-error +from pickman import ftest from u_boot_pylib import command from u_boot_pylib import tout @@ -54,14 +56,12 @@ def compare_branches(master, source): return count, Commit(full_hash, short_hash, subject, date) -def do_pickman(): - """Main entry point for pickman. +def do_compare(args): # pylint: disable=unused-argument + """Compare branches and print results. - Returns: - int: 0 on success + Args: + args (Namespace): Parsed arguments """ - tout.init(tout.INFO) - count, base = compare_branches(BRANCH_MASTER, BRANCH_SOURCE) tout.info(f'Commits in {BRANCH_SOURCE} not in {BRANCH_MASTER}: {count}') @@ -72,3 +72,39 @@ def do_pickman(): tout.info(f' Date: {base.date}') return 0 + + +def do_test(args): # pylint: disable=unused-argument + """Run tests for this module. + + Args: + args (Namespace): Parsed arguments + + Returns: + int: 0 if tests passed, 1 otherwise + """ + loader = unittest.TestLoader() + suite = loader.loadTestsFromModule(ftest) + runner = unittest.TextTestRunner() + result = runner.run(suite) + + return 0 if result.wasSuccessful() else 1 + + +def do_pickman(args): + """Main entry point for pickman commands. + + Args: + args (Namespace): Parsed arguments + + Returns: + int: 0 on success, 1 on failure + """ + tout.init(tout.INFO) + + if args.cmd == 'compare': + return do_compare(args) + if args.cmd == 'test': + return do_test(args) + + return 1 diff --git a/tools/pickman/ftest.py b/tools/pickman/ftest.py index 7b34a260659..eeb19926f76 100644 --- a/tools/pickman/ftest.py +++ b/tools/pickman/ftest.py @@ -13,9 +13,11 @@ import unittest our_path = os.path.dirname(os.path.realpath(__file__)) sys.path.insert(0, os.path.join(our_path, '..')) -# pylint: disable=wrong-import-position,import-error +# pylint: disable=wrong-import-position,import-error,cyclic-import from u_boot_pylib import command +from u_boot_pylib import terminal +from pickman import __main__ as pickman from pickman import control @@ -97,5 +99,58 @@ class TestCompareBranches(unittest.TestCase): command.TEST_RESULT = None +class TestParseArgs(unittest.TestCase): + """Tests for parse_args function.""" + + def test_parse_compare(self): + """Test parsing compare command.""" + args = pickman.parse_args(['compare']) + self.assertEqual(args.cmd, 'compare') + + def test_parse_test(self): + """Test parsing test command.""" + args = pickman.parse_args(['test']) + self.assertEqual(args.cmd, 'test') + + def test_parse_no_command(self): + """Test parsing with no command raises error.""" + with terminal.capture(): + with self.assertRaises(SystemExit): + pickman.parse_args([]) + + +class TestMain(unittest.TestCase): + """Tests for main function.""" + + def test_main_compare(self): + """Test main with compare command.""" + results = iter([ + '10', + 'abc123', + 'abc123\nabc\nSubject\n2024-01-01 00:00:00 -0000', + ]) + + def handle_command(**_): + return command.CommandResult(stdout=next(results)) + + command.TEST_RESULT = handle_command + try: + with terminal.capture() as (stdout, _): + ret = pickman.main(['compare']) + self.assertEqual(ret, 0) + lines = iter(stdout.getvalue().splitlines()) + self.assertEqual('Commits in us/next not in ci/master: 10', + next(lines)) + self.assertEqual('', next(lines)) + self.assertEqual('Last common commit:', next(lines)) + self.assertEqual(' Hash: abc', next(lines)) + self.assertEqual(' Subject: Subject', next(lines)) + self.assertEqual(' Date: 2024-01-01 00:00:00 -0000', + next(lines)) + self.assertRaises(StopIteration, next, lines) + finally: + command.TEST_RESULT = None + + if __name__ == '__main__': unittest.main()