diff --git a/excuse.py b/excuse.py index a49786d..a44db68 100644 --- a/excuse.py +++ b/excuse.py @@ -281,10 +281,5 @@ class Excuse(object): else: excusedata["reason"] = sorted(list(self.reason.keys())) excusedata["is-candidate"] = self.is_valid - # TODO: backwards compatible excuses entry -- drop after porting bileto - # and kernel matrix - excusedata["tests"] = {} - if self.policy_info.get('autopkgtest'): - excusedata["tests"]["autopkgtest"] = self.policy_info["autopkgtest"] return excusedata diff --git a/policies/autopkgtest.py b/policies/autopkgtest.py index 3c96f8b..c7191a8 100644 --- a/policies/autopkgtest.py +++ b/policies/autopkgtest.py @@ -183,7 +183,8 @@ class AutopkgtestPolicy(BasePolicy): # add test result details to Excuse verdict = PolicyVerdict.PASS cloud_url = "http://autopkgtest.ubuntu.com/packages/%(h)s/%(s)s/%(r)s/%(a)s" - for ((testsrc, testver), arch_results) in pkg_arch_result.items(): + for (testsrc, testver) in sorted(pkg_arch_result): + arch_results = pkg_arch_result[(testsrc, testver)] r = set([v[0] for v in arch_results.values()]) if 'REGRESSION' in r: verdict = PolicyVerdict.REJECTED_PERMANENTLY @@ -194,7 +195,8 @@ class AutopkgtestPolicy(BasePolicy): testver = None html_archmsg = [] - for arch, (status, log_url) in arch_results.items(): + for arch in sorted(arch_results): + (status, log_url) = arch_results[arch] artifact_url = None retry_url = None history_url = None diff --git a/run-autopkgtest b/run-autopkgtest new file mode 100755 index 0000000..2c0c95f --- /dev/null +++ b/run-autopkgtest @@ -0,0 +1,141 @@ +#!/usr/bin/python3 +# Request re-runs of autopkgtests for packages + +import os +import sys +import argparse +import json +import urllib.parse + +import amqplib.client_0_8 as amqp + +my_dir = os.path.dirname(os.path.realpath(sys.argv[0])) + + +def parse_args(): + '''Parse command line arguments''' + + parser = argparse.ArgumentParser() + parser.add_argument('-c', '--config', + help='britney config file (default: britney.conf.ubuntu.)') + parser.add_argument('-s', '--series', required=True, + help='Distro series name (required).') + parser.add_argument('-a', '--architecture', action='append', default=[], + help='Only run test(s) on given architecture name(s). ' + 'Can be specified multiple times (default: all).') + parser.add_argument('--trigger', action='append', default=[], + metavar='SOURCE/VERSION', + help='Add triggering package to request. ' + 'Can be specified multiple times.') + parser.add_argument('--ppa', metavar='LPUSER/PPANAME', action='append', + default=[], + help='Enable PPA for requested test(s). ' + 'Can be specified multiple times.') + parser.add_argument('--env', metavar='KEY=VALUE', action='append', + default=[], + help='List of VAR=value strings. ' + 'This can be used to influence a test\'s behaviour ' + 'from a test request. ' + 'Can be specified multiple times.') + parser.add_argument('--test-git', + metavar='URL [branchname]', + help='A single URL or URL branchname. ' + 'The test will be git cloned from that URL and ran ' + 'from the checkout. This will not build binary ' + 'packages from the branch and run tests against ' + 'those, the test dependencies will be taken from the ' + 'archive, or PPA if given. In this case the ' + 'srcpkgname will only be used for the result path in ' + 'swift and be irrelevant for the actual test.') + parser.add_argument('--build-git', + metavar='URL [branchname]', + help='A single URL or URL branchname. ' + 'Like --test-git`, but will first build binary ' + 'packages from the branch and run tests against those.') + parser.add_argument('--test-bzr', + help='A single URL. ' + 'The test will be checked out with bzr from that URL. ' + 'Otherwise this has the same behaviour as test-git.') + parser.add_argument('--all-proposed', action='store_true', + help='Disable apt pinning and use all of -proposed') + parser.add_argument('package', nargs='+', + help='Source package name(s) whose tests to run.') + args = parser.parse_args() + + if not args.trigger and not args.ppa: + parser.error('One of --trigger or --ppa must be given') + + # verify syntax of triggers + for t in args.trigger: + try: + (src, ver) = t.split('/') + except ValueError: + parser.error('Invalid trigger format "%s", must be "sourcepkg/version"' % t) + + # verify syntax of PPAs + for t in args.ppa: + try: + (user, name) = t.split('/') + except ValueError: + parser.error('Invalid ppa format "%s", must be "lpuser/ppaname"' % t) + + return args + + +def parse_config(config_file): + '''Parse config file (like britney.py)''' + + config = argparse.Namespace() + with open(config_file) as f: + for k, v in [r.split('=', 1) for r in f if '=' in r and not r.strip().startswith('#')]: + k = k.strip() + if not getattr(config, k.lower(), None): + setattr(config, k.lower(), v.strip()) + return config + + +if __name__ == '__main__': + args = parse_args() + britney_conf = os.path.join(my_dir, 'britney.conf') + if args.config: + config = parse_config(args.config) + elif os.path.exists(britney_conf + '.ubuntu.' + args.series): + config = parse_config(britney_conf + '.ubuntu.' + args.series) + else: + config = parse_config(britney_conf) + if not args.architecture: + args.architecture = config.adt_arches.split() + + context = '' + params = {} + if args.trigger: + params['triggers'] = args.trigger + if args.ppa: + params['ppas'] = args.ppa + context = 'ppa-' + if args.env: + params['env'] = args.env + if args.test_git: + params['test-git'] = args.test_git + context = 'upstream-' + elif args.build_git: + params['build-git'] = args.build_git + context = 'upstream-' + if args.test_bzr: + params['test-bzr'] = args.test_bzr + context = 'upstream-' + if args.all_proposed: + params['all-proposed'] = True + params = '\n' + json.dumps(params) + + creds = urllib.parse.urlsplit(config.adt_amqp, allow_fragments=False) + assert creds.scheme == 'amqp' + + with amqp.Connection(creds.hostname, userid=creds.username, + password=creds.password) as amqp_con: + with amqp_con.channel() as ch: + for arch in args.architecture: + queue = 'debci-%s%s-%s' % (context, args.series, arch) + for pkg in args.package: + ch.basic_publish(amqp.Message(pkg + params), + routing_key=queue) diff --git a/tests/test_autopkgtest.py b/tests/test_autopkgtest.py index 7ea910e..53c91c5 100755 --- a/tests/test_autopkgtest.py +++ b/tests/test_autopkgtest.py @@ -1956,7 +1956,7 @@ class T(TestBase): '''Run without second stage upgrade tester''' for line in fileinput.input(self.britney_conf, inplace=True): - if not line.startswith('UPGRADE_OUTPUT'): + if not line.startswith('UPGRADE_OUTPUT') or line.startswith('HEIDI_OUTPUT'): sys.stdout.write(line) self.do_test( @@ -1964,6 +1964,11 @@ class T(TestBase): {})[1] self.assertFalse(os.path.exists(os.path.join(self.data.path, 'output', 'series', 'output.txt'))) + self.assertNotEqual(self.amqp_requests, set()) + # must still record pending tests + self.assertEqual(self.pending_requests, {'green/2': {'green': ['amd64', 'i386'], + 'darkgreen': ['amd64', 'i386'], + 'lightgreen': ['amd64', 'i386']}}) def test_shared_results_cache(self): '''Run with shared r/o results.cache'''