def check_style(modules=None, pylint=False, pep8=False): heading('Style Check') selected_modules = get_path_table(include_only=modules) pep8_result = None pylint_result = None if pylint: try: require_azure_cli() except CLIError: raise CLIError( 'usage error: --pylint requires Azure CLI to be installed.') if not selected_modules: raise CLIError('No modules selected.') mod_names = list(selected_modules['mod'].keys()) + list( selected_modules['core'].keys()) ext_names = list(selected_modules['ext'].keys()) if mod_names: display('Modules: {}\n'.format(', '.join(mod_names))) if ext_names: display('Extensions: {}\n'.format(', '.join(ext_names))) # if neither flag provided, same as if both were provided if not any([pylint, pep8]): pep8 = True pylint = True exit_code_sum = 0 if pep8: pep8_result = _run_pep8(selected_modules) exit_code_sum += pep8_result.exit_code if pylint: pylint_result = _run_pylint(selected_modules) exit_code_sum += pylint_result.exit_code display('') subheading('Results') # print success messages first if pep8_result and not pep8_result.error: display('Flake8: PASSED') if pylint_result and not pylint_result.error: display('Pylint: PASSED') display('') # print error messages last if pep8_result and pep8_result.error: logger.error(pep8_result.error.output.decode('utf-8')) logger.error('Flake8: FAILED\n') if pylint_result and pylint_result.error: logger.error(pylint_result.error.output.decode('utf-8')) logger.error('Pylint: FAILED\n') sys.exit(exit_code_sum)
def check_history(modules=None): # TODO: Does not work with extensions path_table = get_path_table(include_only=modules) selected_modules = list(path_table['core'].items()) + list(path_table['mod'].items()) heading('Verify History') module_names = sorted([name for name, _ in selected_modules]) display('Verifying README and HISTORY files for modules: {}'.format(' '.join(module_names))) failed_mods = [] for name, path in selected_modules: errors = _check_readme_render(path) if errors: failed_mods.append(name) subheading('{} errors'.format(name)) for error in errors: logger.error('%s\n', error) subheading('Results') if failed_mods: display('The following modules have invalid README/HISTORYs:') logger.error('\n'.join(failed_mods)) logger.warning('See above for the full warning/errors') logger.warning('note: Line numbers in the errors map to the long_description of your setup.py.') sys.exit(1) display('OK')
def _help_files_not_in_map(cli_repo, help_files_in_map): not_in_map = [] for _, path in get_path_table()['mod'].items(): help_path = os.path.join(path, HELP_FILE_NAME) help_path = help_path.replace(cli_repo.lower() + os.sep, '') if help_path in help_files_in_map or not os.path.isfile(help_path): continue not_in_map.append(help_path) return not_in_map
def verify_versions(): import tempfile import shutil require_azure_cli() heading('Verify CLI Versions') path_table = get_path_table() modules = list(path_table['core'].items()) modules = [x for x in modules if x[0] not in EXCLUDED_MODULES] if not modules: raise CLIError('No modules selected to test.') display('MODULES: {}'.format(', '.join([x[0] for x in modules]))) results = {} original_cwd = os.getcwd() temp_dir = tempfile.mkdtemp() for mod, mod_path in modules: if not mod.startswith(COMMAND_MODULE_PREFIX) and mod != 'azure-cli': mod = '{}{}'.format(COMMAND_MODULE_PREFIX, mod) results[mod] = {} results.update( _compare_module_against_pypi(results, temp_dir, mod, mod_path)) shutil.rmtree(temp_dir) os.chdir(original_cwd) logger.info('Module'.ljust(40) + 'Local Version'.rjust(20) + 'Public Version'.rjust(20)) # pylint: disable=logging-not-lazy for mod, data in results.items(): logger.info( mod.ljust(40) + data['local_version'].rjust(20) + data['public_version'].rjust(20)) bump_mods = {k: v for k, v in results.items() if v['status'] == 'BUMP'} subheading('RESULTS') if bump_mods: logger.error( 'The following modules need their versions bumped. ' 'Scroll up for details: %s', ', '.join(bump_mods.keys())) logger.warning( '\nNote that before changing versions, you should consider ' 'running `git clean` to remove untracked files from your repo. ' 'Files that were once tracked but removed from the source may ' 'still be on your machine, resuling in false positives.') sys.exit(1) else: display('OK!')
def _help_files_not_in_map(cli_repo, help_files_in_map): found_files = [] not_in_map = [] for name, path in get_path_table()['mod'].items(): name.replace(COMMAND_MODULE_PREFIX, '') help_file = os.path.join(path, 'azure', 'cli', 'command_modules', name, HELP_FILE_NAME) if os.path.isfile(help_file): found_files.append(help_file) for f in found_files: f_path = f.replace(cli_repo + '/', '') if f_path not in help_files_in_map: not_in_map.append(f_path) return not_in_map
def filter(self, test_index): """ Strategy on Azure CLI pull request verification stage. :return: a list of modified packages """ modified_packages = git_util.summarize_changed_mods(self.modified_files) if any(core_package in modified_packages for core_package in ['core', 'testsdk', 'telemetry']): path_table = get_path_table() # tests under all packages return list(path_table['mod'].keys()) + list(path_table['core'].keys()) + list(path_table['ext'].keys()) return modified_packages
def update_setup_py(pin=False): require_azure_cli() heading('Update azure-cli setup.py') path_table = get_path_table() azure_cli_path = path_table['core']['azure-cli'] azure_cli_setup_path = find_files(azure_cli_path, SETUP_PY_NAME)[0] modules = list(path_table['core'].items()) + list(path_table['mod'].items()) modules = [x for x in modules if x[0] not in EXCLUDED_MODULES] results = {mod[0]: {} for mod in modules} results = _get_module_versions(results, modules) _update_setup_py(results, azure_cli_setup_path, pin) display('OK!')
def _check_setup_py(results, update, pin): # only audit or update setup.py when all modules are being considered # otherwise, edge cases could arise if update is None: return results # retrieve current versions from azure-cli's setup.py file azure_cli_path = get_path_table(include_only='azure-cli')['core']['azure-cli'] azure_cli_setup_path = find_files(azure_cli_path, SETUP_PY_NAME)[0] with open(azure_cli_setup_path, 'r') as f: setup_py_version_regex = re.compile(r"(?P<quote>[\"'])(?P<mod>[^'=]*)(==(?P<ver>[\d.]*))?(?P=quote)") for line in f.readlines(): if line.strip().startswith("'azure-cli-"): match = setup_py_version_regex.match(line.strip()) mod = match.group('mod') if mod == 'azure-cli-command-modules-nspkg': mod = 'azure-cli-command_modules-nspkg' try: results[mod]['setup_version'] = match.group('ver') except KeyError: # something that is in setup.py but isn't module is # inherently a mismatch results[mod] = { 'local_version': 'Unavailable', 'public_version': 'Unknown', 'setup_version': match.group('ver'), 'status': 'MISMATCH' } if update: _update_setup_py(results, azure_cli_setup_path, pin) else: display('\nAuditing azure-cli setup.py against local module versions...') for mod, data in results.items(): if mod == 'azure-cli': continue setup_version = data['setup_version'] if not setup_version: logger.warning('The azure-cli setup.py file is not using pinned versions. Aborting audit.') break elif setup_version != data['local_version']: data['status'] = 'MISMATCH' return results
def _install_modules(): all_modules = list(get_path_table()['mod'].items()) failures = [] mod_num = 1 total_mods = len(all_modules) for name, path in all_modules: try: pip_cmd( "install -q -e {}".format(path), "Installing module `{}` ({}/{})...".format( name, mod_num, total_mods)) mod_num += 1 except CalledProcessError as err: # exit code is not zero failures.append("Failed to install {}. Error message: {}".format( name, err.output)) for f in failures: display(f) return not any(failures)
def run_tests(tests, xml_path=None, discover=False, in_series=False, run_live=False, profile=None, last_failed=False, pytest_args=None, no_exit_first=False, git_source=None, git_target=None, git_repo=None, cli_ci=False): require_virtual_env() DEFAULT_RESULT_FILE = 'test_results.xml' DEFAULT_RESULT_PATH = os.path.join(get_azdev_config_dir(), DEFAULT_RESULT_FILE) heading('Run Tests') path_table = get_path_table() test_index = _get_test_index(profile or current_profile(), discover) if not tests: tests = list(path_table['mod'].keys()) + list( path_table['core'].keys()) + list(path_table['ext'].keys()) if tests == ['CLI']: tests = list(path_table['mod'].keys()) + list( path_table['core'].keys()) elif tests == ['EXT']: tests = list(path_table['ext'].keys()) # filter out tests whose modules haven't changed modified_mods = _filter_by_git_diff(tests, test_index, git_source, git_target, git_repo) if modified_mods: display('\nTest on modules: {}\n'.format(', '.join(modified_mods))) if cli_ci is True: ctx = CLIAzureDevOpsContext(git_repo, git_source, git_target) modified_mods = ctx.filter(test_index) # resolve the path at which to dump the XML results xml_path = xml_path or DEFAULT_RESULT_PATH if not xml_path.endswith('.xml'): xml_path = os.path.join(xml_path, DEFAULT_RESULT_FILE) # process environment variables if run_live: logger.warning('RUNNING TESTS LIVE') os.environ[ENV_VAR_TEST_LIVE] = 'True' def _find_test(index, name): name_comps = name.split('.') num_comps = len(name_comps) key_error = KeyError() for i in range(num_comps): check_name = '.'.join(name_comps[(-1 - i):]) try: match = index[check_name] if check_name != name: logger.info( "Test found using just '%s'. The rest of the name was ignored.\n", check_name) return match except KeyError as ex: key_error = ex continue raise key_error # lookup test paths from index test_paths = [] for t in modified_mods: try: test_path = os.path.normpath(_find_test(test_index, t)) test_paths.append(test_path) except KeyError: logger.warning( "'%s' not found. If newly added, re-run with --discover", t) continue exit_code = 0 # Tests have been collected. Now run them. if not test_paths: logger.warning('No tests selected to run.') sys.exit(exit_code) exit_code = 0 with ProfileContext(profile): runner = get_test_runner(parallel=not in_series, log_path=xml_path, last_failed=last_failed, no_exit_first=no_exit_first) exit_code = runner(test_paths=test_paths, pytest_args=pytest_args) sys.exit(0 if not exit_code else 1)
def check_style(modules=None, pylint=False, pep8=False, git_source=None, git_target=None, git_repo=None): heading('Style Check') # allow user to run only on CLI or extensions cli_only = modules == ['CLI'] ext_only = modules == ['EXT'] if cli_only or ext_only: modules = None selected_modules = get_path_table(include_only=modules) # remove these two non-modules selected_modules['core'].pop('azure-cli-nspkg', None) selected_modules['core'].pop('azure-cli-command_modules-nspkg', None) pep8_result = None pylint_result = None if pylint: try: require_azure_cli() except CLIError: raise CLIError('usage error: --pylint requires Azure CLI to be installed.') if cli_only: ext_names = None selected_modules['ext'] = {} if ext_only: mod_names = None selected_modules['mod'] = {} selected_modules['core'] = {} # filter down to only modules that have changed based on git diff selected_modules = filter_by_git_diff(selected_modules, git_source, git_target, git_repo) if not any((selected_modules[x] for x in selected_modules)): raise CLIError('No modules selected.') mod_names = list(selected_modules['mod'].keys()) + list(selected_modules['core'].keys()) ext_names = list(selected_modules['ext'].keys()) if mod_names: display('Modules: {}\n'.format(', '.join(mod_names))) if ext_names: display('Extensions: {}\n'.format(', '.join(ext_names))) # if neither flag provided, same as if both were provided if not any([pylint, pep8]): pep8 = True pylint = True exit_code_sum = 0 if pep8: pep8_result = _run_pep8(selected_modules) exit_code_sum += pep8_result.exit_code if pylint: pylint_result = _run_pylint(selected_modules) exit_code_sum += pylint_result.exit_code display('') subheading('Results') # print success messages first if pep8_result and not pep8_result.error: display('Flake8: PASSED') if pylint_result and not pylint_result.error: display('Pylint: PASSED') display('') # print error messages last if pep8_result and pep8_result.error: logger.error(pep8_result.error.output.decode('utf-8')) logger.error('Flake8: FAILED\n') if pylint_result and pylint_result.error: logger.error(pylint_result.error.output.decode('utf-8')) logger.error('Pylint: FAILED\n') sys.exit(exit_code_sum)
def _check_setup_py(results, update): # only audit or update setup.py when all modules are being considered # otherwise, edge cases could arise if update is None: return results # retrieve current versions from azure-cli's setup.py file azure_cli_path = get_path_table( include_only='azure-cli')['core']['azure-cli'] azure_cli_setup_path = find_files(azure_cli_path, SETUP_PY_NAME)[0] with open(azure_cli_setup_path, 'r') as f: setup_py_version_regex = re.compile( r"(?P<quote>[\"'])(?P<mod>[^'=]*)==(?P<ver>[\d.]*)(?P=quote)") for line in f.readlines(): if line.strip().startswith("'azure-cli-"): match = setup_py_version_regex.match(line.strip()) mod = match.group('mod') if mod == 'azure-cli-command-modules-nspkg': mod = 'azure-cli-command_modules-nspkg' try: results[mod]['setup_version'] = match.group('ver') except KeyError: # something that is in setup.py but isn't module is # inherently a mismatch results[mod] = { 'local_version': 'Unavailable', 'public_version': 'Unknown', 'setup_version': match.group('ver'), 'status': 'MISMATCH' } if not update: display( '\nAuditing azure-cli setup.py against local module versions...') for mod, data in results.items(): if mod == 'azure-cli': continue if data['local_version'] != data['setup_version']: data['status'] = 'MISMATCH' return results # update azure-cli's setup.py file with the collected versions logger.warning( '\nUpdating azure-cli setup.py with collected module versions...') old_lines = [] with open(azure_cli_setup_path, 'r') as f: old_lines = f.readlines() with open(azure_cli_setup_path, 'w') as f: start_line = 'DEPENDENCIES = [' end_line = ']' write_versions = False for line in old_lines: if line.strip() == start_line: write_versions = True f.write(line) for mod, data in results.items(): if mod == 'azure-cli' or data[ 'local_version'] == 'Unavailable': continue f.write(" '{}=={}'\n".format(mod, data['local_version'])) continue elif line.strip() == end_line: write_versions = False elif write_versions: # stop writing lines until the end bracket is found continue f.write(line) return results
def _discover_tests(profile): """ Builds an index of tests so that the user can simply supply the name they wish to test instead of the full path. """ profile_split = profile.split('-') profile_namespace = '_'.join([profile_split[-1]] + profile_split[:-1]) heading('Discovering Tests') path_table = get_path_table() core_modules = path_table['core'].items() command_modules = path_table['mod'].items() extensions = path_table['ext'].items() inverse_name_table = get_name_index(invert=True) module_data = {} logger.info('\nCore Modules: %s', ', '.join([name for name, _ in core_modules])) for mod_name, mod_path in core_modules: file_path = mod_path for comp in mod_name.split('-'): file_path = os.path.join(file_path, comp) mod_data = { 'alt_name': 'main' if mod_name == 'azure-cli' else mod_name.replace(COMMAND_MODULE_PREFIX, ''), 'filepath': os.path.join(file_path, 'tests'), 'base_path': '{}.tests'.format(mod_name).replace('-', '.'), 'files': {} } tests = _discover_module_tests(mod_name, mod_data) if tests: module_data[mod_name] = tests logger.info('\nCommand Modules: %s', ', '.join([name for name, _ in command_modules])) for mod_name, mod_path in command_modules: mod_data = { # Modules don't technically have azure-cli-foo moniker anymore, but preserving # for consistency. 'alt_name': '{}{}'.format(COMMAND_MODULE_PREFIX, mod_name), 'filepath': os.path.join( mod_path, 'tests', profile_namespace), 'base_path': 'azure.cli.command_modules.{}.tests.{}'.format(mod_name, profile_namespace), 'files': {} } tests = _discover_module_tests(mod_name, mod_data) if tests: module_data[mod_name] = tests logger.info('\nExtensions: %s', ', '.join([name for name, _ in extensions])) for mod_name, mod_path in extensions: glob_pattern = os.path.normcase(os.path.join('{}*'.format(EXTENSION_PREFIX))) try: file_path = glob.glob(os.path.join(mod_path, glob_pattern))[0] except IndexError: logger.debug("No extension found at: %s", os.path.join(mod_path, glob_pattern)) continue import_name = os.path.basename(file_path) mod_data = { 'alt_name': inverse_name_table[mod_name], 'filepath': os.path.join(file_path, 'tests', profile_namespace), 'base_path': '{}.tests.{}'.format(import_name, profile_namespace), 'files': {} } tests = _discover_module_tests(import_name, mod_data) if tests: module_data[mod_name] = tests test_index = {} conflicted_keys = [] def add_to_index(key, path): from azdev.utilities import extract_module_name key = key or mod_name if key in test_index: if key not in conflicted_keys: conflicted_keys.append(key) mod1 = extract_module_name(path) mod2 = extract_module_name(test_index[key]) if mod1 != mod2: # resolve conflicted keys by prefixing with the module name and a dot (.) logger.warning("'%s' exists in both '%s' and '%s'. Resolve using `%s.%s` or `%s.%s`", key, mod1, mod2, mod1, key, mod2, key) test_index['{}.{}'.format(mod1, key)] = path test_index['{}.{}'.format(mod2, key)] = test_index[key] else: logger.error("'%s' exists twice in the '%s' module. " "Please rename one or both and re-run --discover.", key, mod1) else: test_index[key] = path # build the index for mod_name, mod_data in module_data.items(): # don't add empty mods to the index if not mod_data: continue mod_path = mod_data['filepath'] for file_name, file_data in mod_data['files'].items(): file_path = os.path.join(mod_path, file_name) + '.py' for class_name, test_list in file_data.items(): for test_name in test_list: test_path = '{}::{}::{}'.format(file_path, class_name, test_name) add_to_index(test_name, test_path) class_path = '{}::{}'.format(file_path, class_name) add_to_index(class_name, class_path) add_to_index(file_name, file_path) add_to_index(mod_name, mod_path) add_to_index(mod_data['alt_name'], mod_path) # remove the conflicted keys since they would arbitrarily point to a random implementation for key in conflicted_keys: del test_index[key] return test_index
def verify_versions(modules=None, update=False, pin=False): import tempfile import shutil require_azure_cli() heading('Verify CLI Module Versions') usage_err = CLIError('usage error: <MODULES> | --update [--pin]') if modules and (update or pin): raise usage_err if not modules and pin and not update: raise usage_err if modules: update = None pin = None path_table = get_path_table(include_only=modules) modules = list(path_table['core'].items()) + list(path_table['mod'].items()) modules = [x for x in modules if x[0] not in EXCLUDED_MODULES] if not modules: raise CLIError('No modules selected to test.') display('MODULES: {}'.format(', '.join([x[0] for x in modules]))) results = {mod[0]: {} for mod in modules} original_cwd = os.getcwd() temp_dir = tempfile.mkdtemp() for mod, mod_path in modules: if not mod.startswith(COMMAND_MODULE_PREFIX) and mod != 'azure-cli': mod = '{}{}'.format(COMMAND_MODULE_PREFIX, mod) results.update(_compare_module_against_pypi(results, temp_dir, mod, mod_path)) shutil.rmtree(temp_dir) os.chdir(original_cwd) results = _check_setup_py(results, update, pin) logger.info('Module'.ljust(40) + 'Local Version'.rjust(20) + 'Public Version'.rjust(20)) # pylint: disable=logging-not-lazy for mod, data in results.items(): logger.info(mod.ljust(40) + data['local_version'].rjust(20) + data['public_version'].rjust(20)) bump_mods = {k: v for k, v in results.items() if v['status'] == 'BUMP'} mismatch_mods = {k: v for k, v in results.items() if v['status'] == 'MISMATCH'} subheading('RESULTS') if bump_mods: logger.error('The following modules need their versions bumped. ' 'Scroll up for details: %s', ', '.join(bump_mods.keys())) logger.warning('\nNote that before changing versions, you should consider ' 'running `git clean` to remove untracked files from your repo. ' 'Files that were once tracked but removed from the source may ' 'still be on your machine, resuling in false positives.') sys.exit(1) elif mismatch_mods and not update: logger.error('The following modules have a mismatch between the module version ' 'and the version in azure-cli\'s setup.py file. ' 'Scroll up for details: %s', ', '.join(mismatch_mods.keys())) sys.exit(1) else: display('OK!')
def run_linter(modules=None, rule_types=None, rules=None, ci_exclusions=None, git_source=None, git_target=None, git_repo=None, include_whl_extensions=False, min_severity=None, save_global_exclusion=False): require_azure_cli() from azure.cli.core import get_default_cli # pylint: disable=import-error from azure.cli.core.file_util import ( # pylint: disable=import-error get_all_help, create_invoker_and_load_cmds_and_args) heading('CLI Linter') # allow user to run only on CLI or extensions cli_only = modules == ['CLI'] ext_only = modules == ['EXT'] if cli_only or ext_only: modules = None # process severity option if min_severity: try: min_severity = LinterSeverity.get_linter_severity(min_severity) except ValueError: valid_choices = linter_severity_choices() raise CLIError( "Please specify a valid linter severity. It should be one of: {}" .format(", ".join(valid_choices))) # needed to remove helps from azdev azdev_helps = helps.copy() exclusions = {} selected_modules = get_path_table( include_only=modules, include_whl_extensions=include_whl_extensions) if cli_only: selected_modules['ext'] = {} if ext_only: selected_modules['mod'] = {} selected_modules['core'] = {} # used to upsert global exclusion update_global_exclusion = None if save_global_exclusion and (cli_only or ext_only): if cli_only: update_global_exclusion = 'CLI' if os.path.exists( os.path.join(get_cli_repo_path(), 'linter_exclusions.yml')): os.remove( os.path.join(get_cli_repo_path(), 'linter_exclusions.yml')) elif ext_only: update_global_exclusion = 'EXT' for ext_path in get_ext_repo_paths(): if os.path.exists( os.path.join(ext_path, 'linter_exclusions.yml')): os.remove(os.path.join(ext_path, 'linter_exclusions.yml')) # filter down to only modules that have changed based on git diff selected_modules = filter_by_git_diff(selected_modules, git_source, git_target, git_repo) if not any((selected_modules[x] for x in selected_modules)): raise CLIError('No modules selected.') selected_mod_names = list(selected_modules['mod'].keys()) + list(selected_modules['core'].keys()) + \ list(selected_modules['ext'].keys()) selected_mod_paths = list(selected_modules['mod'].values()) + list(selected_modules['core'].values()) + \ list(selected_modules['ext'].values()) if selected_mod_names: display('Modules: {}\n'.format(', '.join(selected_mod_names))) # collect all rule exclusions for path in selected_mod_paths: exclusion_path = os.path.join(path, 'linter_exclusions.yml') if os.path.isfile(exclusion_path): mod_exclusions = yaml.safe_load(open(exclusion_path)) merge_exclusion(exclusions, mod_exclusions or {}) global_exclusion_paths = [ os.path.join(get_cli_repo_path(), 'linter_exclusions.yml') ] try: global_exclusion_paths.extend([ os.path.join(path, 'linter_exclusions.yml') for path in (get_ext_repo_paths() or []) ]) except CLIError: pass for path in global_exclusion_paths: if os.path.isfile(path): mod_exclusions = yaml.safe_load(open(path)) merge_exclusion(exclusions, mod_exclusions or {}) start = time.time() display('Initializing linter with command table and help files...') az_cli = get_default_cli() # load commands, args, and help create_invoker_and_load_cmds_and_args(az_cli) loaded_help = get_all_help(az_cli) stop = time.time() logger.info('Commands and help loaded in %i sec', stop - start) command_loader = az_cli.invocation.commands_loader # format loaded help loaded_help = {data.command: data for data in loaded_help if data.command} # load yaml help help_file_entries = {} for entry_name, help_yaml in helps.items(): # ignore help entries from azdev itself, unless it also coincides # with a CLI or extension command name. if entry_name in azdev_helps and entry_name not in command_loader.command_table: continue help_entry = yaml.safe_load(help_yaml) help_file_entries[entry_name] = help_entry # trim command table and help to just selected_modules command_loader, help_file_entries = filter_modules( command_loader, help_file_entries, modules=selected_mod_names, include_whl_extensions=include_whl_extensions) if not command_loader.command_table: raise CLIError('No commands selected to check.') # Instantiate and run Linter linter_manager = LinterManager( command_loader=command_loader, help_file_entries=help_file_entries, loaded_help=loaded_help, exclusions=exclusions, rule_inclusions=rules, use_ci_exclusions=ci_exclusions, min_severity=min_severity, update_global_exclusion=update_global_exclusion) subheading('Results') logger.info('Running linter: %i commands, %i help entries', len(command_loader.command_table), len(help_file_entries)) exit_code = linter_manager.run(run_params=not rule_types or 'params' in rule_types, run_commands=not rule_types or 'commands' in rule_types, run_command_groups=not rule_types or 'command_groups' in rule_types, run_help_files_entries=not rule_types or 'help_entries' in rule_types) sys.exit(exit_code)
def get_all_modules(self): result = get_path_table() # only get modules and core, ignore extensions self.modules = {**result['mod'], **result['core']}
def run_linter(modules=None, rule_types=None, rules=None): require_azure_cli() from azure.cli.core import get_default_cli # pylint: disable=import-error from azure.cli.core.file_util import ( # pylint: disable=import-error get_all_help, create_invoker_and_load_cmds_and_args) heading('CLI Linter') # needed to remove helps from azdev azdev_helps = helps.copy() exclusions = {} selected_modules = get_path_table(include_only=modules) if not selected_modules: raise CLIError('No modules selected.') selected_mod_names = list(selected_modules['mod'].keys()) + list(selected_modules['core'].keys()) + \ list(selected_modules['ext'].keys()) selected_mod_paths = list(selected_modules['mod'].values()) + list(selected_modules['core'].values()) + \ list(selected_modules['ext'].values()) if selected_mod_names: display('Modules: {}\n'.format(', '.join(selected_mod_names))) # collect all rule exclusions for path in selected_mod_paths: exclusion_path = os.path.join(path, 'linter_exclusions.yml') if os.path.isfile(exclusion_path): mod_exclusions = yaml.load(open(exclusion_path)) exclusions.update(mod_exclusions) start = time.time() display('Initializing linter with command table and help files...') az_cli = get_default_cli() # load commands, args, and help create_invoker_and_load_cmds_and_args(az_cli) loaded_help = get_all_help(az_cli) stop = time.time() logger.info('Commands and help loaded in %i sec', stop - start) command_loader = az_cli.invocation.commands_loader # format loaded help loaded_help = {data.command: data for data in loaded_help if data.command} # load yaml help help_file_entries = {} for entry_name, help_yaml in helps.items(): # ignore help entries from azdev itself, unless it also coincides # with a CLI or extension command name. if entry_name in azdev_helps and entry_name not in command_loader.command_table: continue help_entry = yaml.load(help_yaml) help_file_entries[entry_name] = help_entry # trim command table and help to just selected_modules command_loader, help_file_entries = filter_modules( command_loader, help_file_entries, modules=selected_mod_names) if not command_loader.command_table: raise CLIError('No commands selected to check.') # Instantiate and run Linter linter_manager = LinterManager(command_loader=command_loader, help_file_entries=help_file_entries, loaded_help=loaded_help, exclusions=exclusions, rule_inclusions=rules) subheading('Results') logger.info('Running linter: %i commands, %i help entries', len(command_loader.command_table), len(help_file_entries)) exit_code = linter_manager.run( run_params=not rule_types or 'params' in rule_types, run_commands=not rule_types or 'commands' in rule_types, run_command_groups=not rule_types or 'command_groups'in rule_types, run_help_files_entries=not rule_types or 'help_entries' in rule_types) sys.exit(exit_code)
def run_tests(tests, xml_path=None, discover=False, in_series=False, run_live=False, profile=None, last_failed=False, pytest_args=None, git_source=None, git_target=None, git_repo=None): require_virtual_env() DEFAULT_RESULT_FILE = 'test_results.xml' DEFAULT_RESULT_PATH = os.path.join(get_azdev_config_dir(), DEFAULT_RESULT_FILE) from .pytest_runner import get_test_runner heading('Run Tests') original_profile = _get_profile(profile) if not profile: profile = original_profile path_table = get_path_table() test_index = _get_test_index(profile, discover) if not tests: tests = list(path_table['mod'].keys()) + list(path_table['core'].keys()) + list(path_table['ext'].keys()) if tests == ['CLI']: tests = list(path_table['mod'].keys()) + list(path_table['core'].keys()) elif tests == ['EXT']: tests = list(path_table['ext'].keys()) # filter out tests whose modules haven't changed tests = _filter_by_git_diff(tests, test_index, git_source, git_target, git_repo) if tests: display('\nTESTS: {}\n'.format(', '.join(tests))) # resolve the path at which to dump the XML results xml_path = xml_path or DEFAULT_RESULT_PATH if not xml_path.endswith('.xml'): xml_path = os.path.join(xml_path, DEFAULT_RESULT_FILE) # process environment variables if run_live: logger.warning('RUNNING TESTS LIVE') os.environ[ENV_VAR_TEST_LIVE] = 'True' def _find_test(index, name): name_comps = name.split('.') num_comps = len(name_comps) key_error = KeyError() for i in range(num_comps): check_name = '.'.join(name_comps[(-1 - i):]) try: match = index[check_name] if check_name != name: logger.info("Test found using just '%s'. The rest of the name was ignored.\n", check_name) return match except KeyError as ex: key_error = ex continue raise key_error # lookup test paths from index test_paths = [] for t in tests: try: test_path = os.path.normpath(_find_test(test_index, t)) test_paths.append(test_path) except KeyError: logger.warning("'%s' not found. If newly added, re-run with --discover", t) continue # Tests have been collected. Now run them. if not test_paths: raise CLIError('No tests selected to run.') runner = get_test_runner(parallel=not in_series, log_path=xml_path, last_failed=last_failed) exit_code = runner(test_paths=test_paths, pytest_args=pytest_args) _summarize_test_results(xml_path) # attempt to restore the original profile if profile != original_profile: result = raw_cmd('az cloud update --profile {}'.format(original_profile), "Restoring profile '{}'.".format(original_profile)) if result.exit_code != 0: logger.warning("Failed to restore profile '%s'.", original_profile) sys.exit(0 if not exit_code else 1)
def setUp(self): self.path_table = get_path_table()