Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | #!/usr/bin/env python3 # SPDX-License-Identifier: GPL-2.0+ # # Copyright 2025 Canonical Ltd. # Written by Simon Glass <simon.glass@canonical.com> # """Entry point for pickman - parses arguments and dispatches to control.""" import argparse 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 control from pickman import ftest from u_boot_pylib import test_util 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) add_source = subparsers.add_parser('add-source', help='Add a source branch to track') add_source.add_argument('source', help='Source branch name') apply_cmd = subparsers.add_parser('apply', help='Apply next commits using Claude') apply_cmd.add_argument('source', help='Source branch name') apply_cmd.add_argument('-b', '--branch', help='Branch name to create') apply_cmd.add_argument('-p', '--push', action='store_true', help='Push branch and create GitLab MR') apply_cmd.add_argument('-r', '--remote', default='ci', help='Git remote for push (default: ci)') apply_cmd.add_argument('-t', '--target', default='master', help='Target branch for MR (default: master)') check_gl = subparsers.add_parser('check-gitlab', help='Check GitLab permissions') check_gl.add_argument('-r', '--remote', default='ci', help='Git remote (default: ci)') commit_src = subparsers.add_parser('commit-source', help='Update database with last commit') commit_src.add_argument('source', help='Source branch name') commit_src.add_argument('commit', help='Commit hash to record') subparsers.add_parser('compare', help='Compare branches') count_merges = subparsers.add_parser('count-merges', help='Count remaining merges to process') count_merges.add_argument('source', help='Source branch name') subparsers.add_parser('list-sources', help='List tracked source branches') next_merges = subparsers.add_parser('next-merges', help='Show next N merges to be applied') next_merges.add_argument('source', help='Source branch name') next_merges.add_argument('-c', '--count', type=int, default=10, help='Number of merges to show (default: 10)') next_set = subparsers.add_parser('next-set', help='Show next set of commits to cherry-pick') next_set.add_argument('source', help='Source branch name') review_cmd = subparsers.add_parser('review', help='Check open MRs and handle comments') review_cmd.add_argument('-r', '--remote', default='ci', help='Git remote (default: ci)') step_cmd = subparsers.add_parser('step', help='Create MR if none pending') step_cmd.add_argument('source', help='Source branch name') step_cmd.add_argument('-m', '--max-mrs', type=int, default=5, help='Max open MRs allowed (default: 5)') step_cmd.add_argument('-r', '--remote', default='ci', help='Git remote (default: ci)') step_cmd.add_argument('-t', '--target', default='master', help='Target branch for MR (default: master)') poll_cmd = subparsers.add_parser('poll', help='Run step repeatedly until stopped') poll_cmd.add_argument('source', help='Source branch name') poll_cmd.add_argument('-i', '--interval', type=int, default=300, help='Interval between steps in seconds (default: 300)') poll_cmd.add_argument('-m', '--max-mrs', type=int, default=5, help='Max open MRs allowed (default: 5)') poll_cmd.add_argument('-r', '--remote', default='ci', help='Git remote (default: ci)') poll_cmd.add_argument('-t', '--target', default='master', help='Target branch for MR (default: master)') push_cmd = subparsers.add_parser('push-branch', help='Push branch using GitLab API token') push_cmd.add_argument('branch', help='Branch name to push') push_cmd.add_argument('-r', '--remote', default='ci', help='Git remote (default: ci)') push_cmd.add_argument('-f', '--force', action='store_true', help='Force push (overwrite remote branch)') push_cmd.add_argument('--run-ci', action='store_true', help='Run CI pipeline (default: skip for new MRs)') test_cmd = subparsers.add_parser('test', help='Run tests') test_cmd.add_argument('-P', '--processes', type=int, help='Number of processes to run tests (default: all)') test_cmd.add_argument('-T', '--test-coverage', action='store_true', help='Run tests and check for 100%% coverage') test_cmd.add_argument('-v', '--verbosity', type=int, default=1, help='Verbosity level (0-4, default: 1)') test_cmd.add_argument('tests', nargs='*', help='Specific tests to run') return parser.parse_args(argv) def get_test_classes(): """Get all test classes from the ftest module. Returns: list: List of test class objects """ return [getattr(ftest, name) for name in dir(ftest) if name.startswith('Test') and isinstance(getattr(ftest, name), type) and issubclass(getattr(ftest, name), unittest.TestCase)] def run_tests(processes, verbosity, test_name): """Run the pickman test suite. Args: processes (int): Number of processes for concurrent tests verbosity (int): Verbosity level (0-4) test_name (str): Specific test to run, or None for all Returns: int: 0 if tests passed, 1 otherwise """ result = test_util.run_test_suites( 'pickman', False, verbosity, False, False, processes, test_name, None, get_test_classes()) return 0 if result.wasSuccessful() else 1 def run_test_coverage(args): """Run tests with coverage checking. Args: args (list): Specific tests to run, or None for all """ # agent.py and gitlab_api.py require external services (Claude, GitLab) # so they can't achieve 100% coverage in unit tests test_util.run_test_coverage( 'tools/pickman/pickman', None, ['*test*', '*__main__.py', 'tools/u_boot_pylib/*'], None, extra_args=None, args=args, allow_failures=['tools/pickman/agent.py', 'tools/pickman/gitlab_api.py', 'tools/pickman/control.py']) 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) if args.cmd == 'test': if args.test_coverage: run_test_coverage(args.tests or None) return 0 test_name = args.tests[0] if args.tests else None return run_tests(args.processes, args.verbosity, test_name) return control.do_pickman(args) if __name__ == '__main__': sys.exit(main()) |