Ejemplo n.º 1
0
    def get_errors(self, paths):  # type: (t.List[str]) -> t.List[SanityMessage]
        """Return error messages related to issues with the file."""
        messages = []

        # unused errors

        unused = []  # type: t.List[t.Tuple[int, str, str]]

        if self.test.no_targets or self.test.all_targets:
            # tests which do not accept a target list, or which use all targets, always return all possible errors, so all ignores can be checked
            paths = [target.path for target in SanityTargets.get_targets()]

            if self.test.include_directories:
                paths.extend(paths_to_dirs(paths))

        for path in paths:
            path_entry = self.ignore_entries.get(path)

            if not path_entry:
                continue

            unused.extend((line_no, path, code) for code, line_no in path_entry.items() if line_no not in self.used_line_numbers)

        messages.extend(SanityMessage(
            code=self.code,
            message="Ignoring '%s' on '%s' is unnecessary" % (code, path) if self.code else "Ignoring '%s' is unnecessary" % path,
            path=self.parser.relative_path,
            line=line,
            column=1,
            confidence=calculate_best_confidence(((self.parser.path, line), (path, 0)), self.args.metadata) if self.args.metadata.changes else None,
        ) for line, path, code in unused)

        return messages
Ejemplo n.º 2
0
def command_sanity(args):
    """
    :type args: SanityConfig
    """
    changes = get_changes_filter(args)
    require = args.require + changes
    targets = SanityTargets.create(args.include, args.exclude, require)

    if not targets.include:
        raise AllTargetsSkipped()

    if args.delegate:
        raise Delegate(require=changes, exclude=args.exclude)

    install_command_requirements(args)

    tests = sanity_get_tests()

    if args.test:
        tests = [target for target in tests if target.name in args.test]
    else:
        disabled = [
            target.name for target in tests
            if not target.enabled and not args.allow_disabled
        ]
        tests = [
            target for target in tests if target.enabled or args.allow_disabled
        ]

        if disabled:
            display.warning(
                'Skipping tests disabled by default without --allow-disabled: %s'
                % ', '.join(sorted(disabled)))

    if args.skip_test:
        tests = [
            target for target in tests if target.name not in args.skip_test
        ]

    total = 0
    failed = []

    for test in tests:
        if args.list_tests:
            display.info(test.name)
            continue

        available_versions = get_available_python_versions(
            SUPPORTED_PYTHON_VERSIONS)

        if args.python:
            # specific version selected
            versions = (args.python, )
        elif isinstance(test, SanityMultipleVersion):
            # try all supported versions for multi-version tests when a specific version has not been selected
            versions = test.supported_python_versions
        elif not test.supported_python_versions or args.python_version in test.supported_python_versions:
            # the test works with any version or the version we're already running
            versions = (args.python_version, )
        else:
            # available versions supported by the test
            versions = tuple(
                sorted(
                    set(available_versions)
                    & set(test.supported_python_versions)))
            # use the lowest available version supported by the test or the current version as a fallback (which will be skipped)
            versions = versions[:1] or (args.python_version, )

        for version in versions:
            if isinstance(test, SanityMultipleVersion):
                skip_version = version
            else:
                skip_version = None

            options = ''

            if test.supported_python_versions and version not in test.supported_python_versions:
                display.warning(
                    "Skipping sanity test '%s' on unsupported Python %s." %
                    (test.name, version))
                result = SanitySkipped(test.name, skip_version)
            elif not args.python and version not in available_versions:
                display.warning(
                    "Skipping sanity test '%s' on Python %s due to missing interpreter."
                    % (test.name, version))
                result = SanitySkipped(test.name, skip_version)
            else:
                check_pyyaml(args, version)

                if test.supported_python_versions:
                    display.info("Running sanity test '%s' with Python %s" %
                                 (test.name, version))
                else:
                    display.info("Running sanity test '%s'" % test.name)

                if isinstance(test, SanityCodeSmellTest):
                    settings = test.load_processor(args)
                elif isinstance(test, SanityMultipleVersion):
                    settings = test.load_processor(args, version)
                elif isinstance(test, SanitySingleVersion):
                    settings = test.load_processor(args)
                elif isinstance(test, SanityVersionNeutral):
                    settings = test.load_processor(args)
                else:
                    raise Exception('Unsupported test type: %s' % type(test))

                if test.all_targets:
                    usable_targets = targets.targets
                elif test.no_targets:
                    usable_targets = tuple()
                else:
                    usable_targets = targets.include

                if test.include_directories:
                    usable_targets += tuple(
                        TestTarget(path, None, None, '')
                        for path in paths_to_dirs(
                            [target.path for target in usable_targets]))

                usable_targets = sorted(
                    test.filter_targets(list(usable_targets)))
                usable_targets = settings.filter_skipped_targets(
                    usable_targets)
                sanity_targets = SanityTargets(targets.targets,
                                               tuple(usable_targets))

                if usable_targets or test.no_targets:
                    if isinstance(test, SanityCodeSmellTest):
                        result = test.test(args, sanity_targets, version)
                    elif isinstance(test, SanityMultipleVersion):
                        result = test.test(args, sanity_targets, version)
                        options = ' --python %s' % version
                    elif isinstance(test, SanitySingleVersion):
                        result = test.test(args, sanity_targets, version)
                    elif isinstance(test, SanityVersionNeutral):
                        result = test.test(args, sanity_targets)
                    else:
                        raise Exception('Unsupported test type: %s' %
                                        type(test))
                else:
                    result = SanitySkipped(test.name, skip_version)

            result.write(args)

            total += 1

            if isinstance(result, SanityFailure):
                failed.append(result.test + options)

    if failed:
        message = 'The %d sanity test(s) listed below (out of %d) failed. See error output above for details.\n%s' % (
            len(failed), total, '\n'.join(failed))

        if args.failure_ok:
            display.error(message)
        else:
            raise ApplicationError(message)
Ejemplo n.º 3
0
    def __init__(self, args):  # type: (SanityConfig) -> None
        if data_context().content.collection:
            ansible_version = '%s.%s' % tuple(
                get_ansible_version(args).split('.')[:2])

            ansible_label = 'Ansible %s' % ansible_version
            file_name = 'ignore-%s.txt' % ansible_version
        else:
            ansible_label = 'Ansible'
            file_name = 'ignore.txt'

        self.args = args
        self.relative_path = os.path.join('test/sanity', file_name)
        self.path = os.path.join(data_context().content.root,
                                 self.relative_path)
        self.ignores = collections.defaultdict(lambda: collections.defaultdict(
            dict))  # type: t.Dict[str, t.Dict[str, t.Dict[str, int]]]
        self.skips = collections.defaultdict(lambda: collections.defaultdict(
            int))  # type: t.Dict[str, t.Dict[str, int]]
        self.parse_errors = []  # type: t.List[t.Tuple[int, int, str]]
        self.file_not_found_errors = []  # type: t.List[t.Tuple[int, str]]

        lines = read_lines_without_comments(self.path, optional=True)
        targets = SanityTargets.get_targets()
        paths = set(target.path for target in targets)
        tests_by_name = {}  # type: t.Dict[str, SanityTest]
        versioned_test_names = set()  # type: t.Set[str]
        unversioned_test_names = {}  # type: t.Dict[str, str]
        directories = paths_to_dirs(list(paths))
        paths_by_test = {}  # type: t.Dict[str, t.Set[str]]

        display.info('Read %d sanity test ignore line(s) for %s from: %s' %
                     (len(lines), ansible_label, self.relative_path),
                     verbosity=1)

        for test in sanity_get_tests():
            test_targets = list(targets)

            if test.include_directories:
                test_targets += tuple(
                    TestTarget(path, None, None, '') for path in paths_to_dirs(
                        [target.path for target in test_targets]))

            paths_by_test[test.name] = set(
                target.path for target in test.filter_targets(test_targets))

            if isinstance(test, SanityMultipleVersion):
                versioned_test_names.add(test.name)
                tests_by_name.update(
                    dict(('%s-%s' % (test.name, python_version), test)
                         for python_version in test.supported_python_versions))
            else:
                unversioned_test_names.update(
                    dict(('%s-%s' % (test.name, python_version), test.name)
                         for python_version in SUPPORTED_PYTHON_VERSIONS))
                tests_by_name[test.name] = test

        for line_no, line in enumerate(lines, start=1):
            if not line:
                self.parse_errors.append(
                    (line_no, 1,
                     "Line cannot be empty or contain only a comment"))
                continue

            parts = line.split(' ')
            path = parts[0]
            codes = parts[1:]

            if not path:
                self.parse_errors.append(
                    (line_no, 1, "Line cannot start with a space"))
                continue

            if path.endswith(os.path.sep):
                if path not in directories:
                    self.file_not_found_errors.append((line_no, path))
                    continue
            else:
                if path not in paths:
                    self.file_not_found_errors.append((line_no, path))
                    continue

            if not codes:
                self.parse_errors.append(
                    (line_no, len(path), "Error code required after path"))
                continue

            code = codes[0]

            if not code:
                self.parse_errors.append(
                    (line_no, len(path) + 1,
                     "Error code after path cannot be empty"))
                continue

            if len(codes) > 1:
                self.parse_errors.append((line_no, len(path) + len(code) + 2,
                                          "Error code cannot contain spaces"))
                continue

            parts = code.split('!')
            code = parts[0]
            commands = parts[1:]

            parts = code.split(':')
            test_name = parts[0]
            error_codes = parts[1:]

            test = tests_by_name.get(test_name)

            if not test:
                unversioned_name = unversioned_test_names.get(test_name)

                if unversioned_name:
                    self.parse_errors.append((
                        line_no, len(path) + len(unversioned_name) + 2,
                        "Sanity test '%s' cannot use a Python version like '%s'"
                        % (unversioned_name, test_name)))
                elif test_name in versioned_test_names:
                    self.parse_errors.append((
                        line_no, len(path) + len(test_name) + 1,
                        "Sanity test '%s' requires a Python version like '%s-%s'"
                        % (test_name, test_name, args.python_version)))
                else:
                    self.parse_errors.append(
                        (line_no, len(path) + 2,
                         "Sanity test '%s' does not exist" % test_name))

                continue

            if path.endswith(os.path.sep) and not test.include_directories:
                self.parse_errors.append(
                    (line_no, 1,
                     "Sanity test '%s' does not support directory paths" %
                     test_name))
                continue

            if path not in paths_by_test[test.name] and not test.no_targets:
                self.parse_errors.append(
                    (line_no, 1, "Sanity test '%s' does not test path '%s'" %
                     (test_name, path)))
                continue

            if commands and error_codes:
                self.parse_errors.append(
                    (line_no, len(path) + len(test_name) + 2,
                     "Error code cannot contain both '!' and ':' characters"))
                continue

            if commands:
                command = commands[0]

                if len(commands) > 1:
                    self.parse_errors.append(
                        (line_no,
                         len(path) + len(test_name) + len(command) + 3,
                         "Error code cannot contain multiple '!' characters"))
                    continue

                if command == 'skip':
                    if not test.can_skip:
                        self.parse_errors.append(
                            (line_no, len(path) + len(test_name) + 2,
                             "Sanity test '%s' cannot be skipped" % test_name))
                        continue

                    existing_line_no = self.skips.get(test_name, {}).get(path)

                    if existing_line_no:
                        self.parse_errors.append((
                            line_no, 1,
                            "Duplicate '%s' skip for path '%s' first found on line %d"
                            % (test_name, path, existing_line_no)))
                        continue

                    self.skips[test_name][path] = line_no
                    continue

                self.parse_errors.append(
                    (line_no, len(path) + len(test_name) + 2,
                     "Command '!%s' not recognized" % command))
                continue

            if not test.can_ignore:
                self.parse_errors.append(
                    (line_no, len(path) + 1,
                     "Sanity test '%s' cannot be ignored" % test_name))
                continue

            if test.error_code:
                if not error_codes:
                    self.parse_errors.append(
                        (line_no, len(path) + len(test_name) + 1,
                         "Sanity test '%s' requires an error code" %
                         test_name))
                    continue

                error_code = error_codes[0]

                if len(error_codes) > 1:
                    self.parse_errors.append(
                        (line_no,
                         len(path) + len(test_name) + len(error_code) + 3,
                         "Error code cannot contain multiple ':' characters"))
                    continue
            else:
                if error_codes:
                    self.parse_errors.append(
                        (line_no, len(path) + len(test_name) + 2,
                         "Sanity test '%s' does not support error codes" %
                         test_name))
                    continue

                error_code = self.NO_CODE

            existing = self.ignores.get(test_name, {}).get(path,
                                                           {}).get(error_code)

            if existing:
                if test.error_code:
                    self.parse_errors.append((
                        line_no, 1,
                        "Duplicate '%s' ignore for error code '%s' for path '%s' first found on line %d"
                        % (test_name, error_code, path, existing)))
                else:
                    self.parse_errors.append((
                        line_no, 1,
                        "Duplicate '%s' ignore for path '%s' first found on line %d"
                        % (test_name, path, existing)))

                continue

            self.ignores[test_name][path][error_code] = line_no