azdev/operations/cmdcov/__init__.py (119 lines of code) (raw):
# -----------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# -----------------------------------------------------------------------------
import os
import time
import requests
import yaml
from knack.log import get_logger
from knack.util import CLIError
from azdev.utilities import (
heading, display, get_path_table, require_azure_cli, filter_by_git_diff)
from azdev.utilities.path import get_cli_repo_path, get_ext_repo_paths
from .cmdcov import CmdcovManager
logger = get_logger(__name__)
try:
with open(os.path.join(get_cli_repo_path(), 'scripts', 'ci', 'cmdcov.yml'), 'r') as file:
config = yaml.safe_load(file)
# pylint: disable=broad-exception-caught
except Exception:
url = "https://raw.githubusercontent.com/Azure/azure-cli/dev/scripts/ci/cmdcov.yml"
response = requests.get(url)
config = yaml.safe_load(response.text)
EXCLUDE_MODULES = config['EXCLUDE_MODULES']
# pylint:disable=too-many-locals, too-many-statements, too-many-branches, duplicate-code
def run_cmdcov(modules=None, git_source=None, git_target=None, git_repo=None, level='command'):
"""
:param modules:
:param git_source:
:param git_target:
:param git_target:
:return: 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 Command Test Coverage')
# allow user to run only on CLI or extensions
cli_only = modules == ['CLI']
ext_only = modules == ['EXT']
both_cli_ext = False
if modules == ['ALL']:
both_cli_ext = True
if cli_only or ext_only or both_cli_ext:
modules = None
enable_cli_own = bool(modules is None)
selected_modules = get_path_table(include_only=modules)
# 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.values()):
logger.warning('No commands selected to check.')
# mapping special extension name
def _map_extension_module(selected_modules):
# azext_applicationinsights -> azext_application_insights
# azext_firewall -> azext_azure_firewall
# azext_dms -> azext_dms_preview
# azext_dnsresolver -> azext_dns_resolver
# azext_expressroutecrossconnection -> azext_express_route_cross_connection
# azext_imagecopy -> azext_image_copy
# azext_loganalytics -> azext_log_analytics
# azext_amcs -> azext_monitor_control_service
# azext_resourcegraph -> azext_resource_graph
# azext_sentinel -> azext_securityinsight
# azext_serialconsole -> azext_serial_console
# azext_vnettap -> azext_virtual_network_tap
# azext_vwan -> azext_virtual_wan
# azext_connection_monitor_preview -> azure cli 2.0.81
# azext_spring_cloud -> deprecate
# azext_interactive -> no command
special_extensions_name = {
'azext_applicationinsights': 'azext_application_insights',
'azext_firewall': 'azext_azure_firewall',
'azext_dms': 'azext_dms_preview',
'azext_dnsresolver': 'azext_dns_resolver',
'azext_expressroutecrossconnection': 'azext_express_route_cross_connection',
'azext_imagecopy': 'azext_image_copy',
'azext_loganalytics': 'azext_log_analytics',
'azext_amcs': 'azext_monitor_control_service',
'azext_resourcegraph': 'azext_resource_graph',
'azext_sentinel': 'azext_securityinsight',
'azext_serialconsole': 'azext_serial_console',
'azext_vnettap': 'azext_virtual_network_tap',
'azext_vwan': 'azext_virtual_wan',
}
import copy
copied_selected_modules = copy.deepcopy(selected_modules)
unsupported_extensions = ['azext_connection_monitor_preview', 'azext_spring_cloud', 'azext_interactive']
for k, v in copied_selected_modules['ext'].items():
if k in special_extensions_name:
selected_modules['ext'][special_extensions_name[k]] = v
del selected_modules['ext'][k]
if k in unsupported_extensions:
del selected_modules['ext'][k]
_map_extension_module(selected_modules)
if EXCLUDE_MODULES:
selected_modules['mod'] = {k: v for k, v in selected_modules['mod'].items() if k not in EXCLUDE_MODULES}
selected_modules['ext'] = {k: v for k, v in selected_modules['ext'].items() if k not in EXCLUDE_MODULES}
if cli_only and not both_cli_ext:
selected_mod_names = list(selected_modules['mod'].keys())
selected_mod_paths = list(selected_modules['mod'].values())
elif ext_only and not both_cli_ext:
selected_mod_names = list(selected_modules['ext'].keys())
selected_mod_paths = list(selected_modules['ext'].values())
else:
selected_mod_names = list(selected_modules['mod'].keys()) + list(selected_modules['ext'].keys())
selected_mod_paths = list(selected_modules['mod'].values()) + list(selected_modules['ext'].values())
if selected_mod_names:
display('Modules: {}\n'.format(', '.join(selected_mod_names)))
start = time.time()
display('Initializing cmdcov 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)
# format loaded help
loaded_help = {data.command: data for data in loaded_help if data.command}
linter_exclusions = {}
# collect rule exclusions from selected mod paths
for path in selected_mod_paths:
mod_exclusion_path = os.path.join(path, 'linter_exclusions.yml')
if os.path.isfile(mod_exclusion_path):
with open(mod_exclusion_path) as f:
mod_exclusions = yaml.safe_load(f)
merge_exclusions(linter_exclusions, mod_exclusions or {})
# collect rule exclusions from global exclusion paths
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):
with open(path) as f:
mod_exclusions = yaml.safe_load(f)
merge_exclusions(linter_exclusions, mod_exclusions or {})
cmdcov_manager = CmdcovManager(selected_mod_names=selected_mod_names,
selected_mod_paths=selected_mod_paths,
loaded_help=loaded_help,
level=level,
enable_cli_own=enable_cli_own,
exclusions=linter_exclusions)
cmdcov_manager.run()
# pylint: disable=line-too-long
def merge_exclusions(left_exclusion, right_exclusion):
for command_name, value in right_exclusion.items():
for rule_name in value.get('rule_exclusions', []):
left_exclusion.setdefault(command_name, {}).setdefault('rule_exclusions', []).append(rule_name)
for param_name in value.get('parameters', {}):
for rule_name in value.get('parameters', {}).get(param_name, {}).get('rule_exclusions', []):
left_exclusion.setdefault(command_name, {}).setdefault('parameters', {}).setdefault(param_name, {}).setdefault('rule_exclusions', []).append(rule_name)
if __name__ == '__main__':
pass
# _get_all_tested_commands(['a'], ['b'])
# regex2()
# regex3()