#!/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 from oslotest.tools import 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())