diff --git a/setup.cfg b/setup.cfg index fcaa250..9d0ca1c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -25,6 +25,7 @@ packages = scripts = tools/oslo_debug_helper tools/oslo_run_cross_tests + tools/oslo_run_pre_release_tests [global] setup-hooks = diff --git a/tools/oslo_run_pre_release_tests b/tools/oslo_run_pre_release_tests new file mode 100755 index 0000000..2ff69c6 --- /dev/null +++ b/tools/oslo_run_pre_release_tests @@ -0,0 +1,211 @@ +#!/usr/bin/env python +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +"""Run unit tests for projects that use a library. +""" + +from __future__ import print_function + +import glob +import os +import subprocess +import sys + +from oslo_config import cfg +import oslo_tool_config as tconfig +from pbr import packaging +import pkg_resources + + +def find_all_projects(repo_root): + """Scan the checked out repositories for all available projects. + """ + pattern = os.path.join(repo_root, 'openstack/*') + candidates = glob.glob(pattern) + prefix_len = len(repo_root) + return [ + c[prefix_len:].lstrip('/') + for c in candidates + if os.path.isdir(c) + ] + + +def find_consuming_projects(lib_name, repo_root, projects): + """Filter the list of projects to only include entries that use the library. + """ + for p in projects: + consumer = False + for base in packaging.get_requirements_files(): + req_file = os.path.join(repo_root, p, base) + for req in packaging.parse_requirements([req_file]): + try: + parsed_req = pkg_resources.Requirement.parse(req) + req_name = parsed_req.project_name + except ValueError: + continue + if req_name == lib_name: + consumer = True + yield p + break + if consumer: + break + + +def main(): + conf = tconfig.get_config_parser() + conf.register_cli_opt( + cfg.StrOpt( + 'library-under-test', + short='l', + default='', + help=('the name of the library being tested; ' + 'defaults to current dir'), + ) + ) + conf.register_cli_opt( + cfg.BoolOpt( + 'update', + short='u', + default=False, + help='update consumers before running tests', + ) + ) + conf.register_cli_opt( + cfg.BoolOpt( + 'verbose', + short='v', + default=False, + help='print verbose output', + ) + ) + conf.register_cli_opt( + cfg.StrOpt( + 'ref', + short='r', + default='HEAD', + help='the commit reference to test; defaults to HEAD', + ) + ) + conf.register_cli_opt( + cfg.MultiStrOpt( + 'env', + short='e', + default=['py27', 'pep8'], + help=('the name of the tox environment to test; ' + 'defaults to py27 and pep8'), + ) + ) + conf.register_cli_opt( + cfg.MultiStrOpt( + 'consumer', + positional=True, + default=[], + help='the name of a project to test with; may be repeated', + ) + ) + tconfig.parse_arguments(conf) + + repo_root = os.path.expanduser(conf.repo_root) + + # Figure out which library is being tested + lib_name = conf.library_under_test + if not lib_name: + if conf.verbose: + print('finding library name') + lib_name = subprocess.check_output( + ['python', 'setup.py', '--name'] + ).strip() + lib_dir = os.getcwd() + else: + lib_dir = os.path.join(repo_root, 'openstack', lib_name) + print('testing %s in %s' % (lib_name, lib_dir)) + + projects = set(conf.consumer) + if not projects: + # TODO(dhellmann): Need to update this to look at gerrit, so + # we can check out the projects we want to test with. + if conf.verbose: + print('defaulting to all projects under %s/openstack' % repo_root) + projects = find_all_projects(repo_root) + + # Filter out projects that do not require the library under test + before = len(projects) + projects = list(find_consuming_projects(lib_name, repo_root, projects)) + after = len(projects) + if (after < before) and conf.verbose: + print('ignoring %s projects that do not use %s' + % (before - after, lib_name)) + + projects = list(sorted(projects)) + if not projects: + print('ERROR: found no projects using %s' % lib_name) + return 1 + if conf.verbose: + print('preparing to test %s projects' % after) + + # Make sure the lib being tested is set to the reference intended. + if conf.ref != 'HEAD': + if conf.verbose: + print('ensuring %s is updated to %s' % (lib_name, conf.ref)) + subprocess.check_call( + ['git', 'checkout', conf.ref], + cwd=lib_dir, + ) + + git_quiet = ['-q'] if not conf.verbose else [] + + failures = [] + for p in projects: + if conf.verbose: + print() + proj_dir = os.path.join(repo_root, p) + if conf.update: + if conf.verbose: + print('updating %s with "git pull"' % p) + subprocess.Popen( + ['git', 'pull'] + git_quiet, + cwd=proj_dir, + ).communicate() + p_log_name = p.split('/')[-1].replace('.', '-') + for e in conf.env: + log_name = 'cross-test-%s-%s.log' % (p_log_name, e) + with open(log_name, 'w') as log_file: + print('testing %s in %s, logging to %s' % (e, p, log_name), + end=' ') + sys.stdout.flush() + command = ['oslo_run_cross_tests', proj_dir, e] + log_file.write('running: %s\n' % ' '.join(command)) + log_file.flush() # since Popen is going to use the fd directly + cmd = subprocess.Popen( + command, + cwd=lib_dir, + stdout=log_file, + stderr=log_file + ) + cmd.communicate() + log_file.write('\nexit code: %s\n' % cmd.returncode) + if cmd.returncode: + print('FAIL') + failures.append((p, e, cmd.returncode)) + else: + print('PASS') + + if failures: + print('\nFAILED %d jobs' % len(failures)) + return 1 + print('\nPASSED all jobs') + return 0 + + +if __name__ == '__main__': + sys.exit(main())