Beispiel #1
0
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
Beispiel #4
0
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
Beispiel #9
0
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)
Beispiel #11
0
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)
Beispiel #12
0
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
Beispiel #13
0
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!')
Beispiel #15
0
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)
Beispiel #16
0
 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)
Beispiel #18
0
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)
Beispiel #19
0
 def setUp(self):
     self.path_table = get_path_table()