<commit_before>"""Sanity test to check integration test aliases."""
from __future__ import absolute_import, print_function

import json
import textwrap
import re
import os

from lib.sanity import (
    SanitySingleVersion,
    SanityMessage,
    SanityFailure,
    SanitySuccess,
    SanityTargets,
)

from lib.config import (
    SanityConfig,
)

from lib.target import (
    filter_targets,
    walk_posix_integration_targets,
    walk_windows_integration_targets,
    walk_integration_targets,
    walk_module_targets,
)

from lib.cloud import (
    get_cloud_platforms,
)

from lib.util import (
    display,
)


class IntegrationAliasesTest(SanitySingleVersion):
    """Sanity test to evaluate integration test aliases."""
    SHIPPABLE_YML = 'shippable.yml'

    DISABLED = 'disabled/'
    UNSTABLE = 'unstable/'
    UNSUPPORTED = 'unsupported/'

    EXPLAIN_URL = 'https://docs.ansible.com/ansible/devel/dev_guide/testing/sanity/integration-aliases.html'

    TEMPLATE_DISABLED = """
    The following integration tests are **disabled** [[explain]({explain_url}#disabled)]:

    {tests}

    Consider fixing the integration tests before or alongside changes.
    """

    TEMPLATE_UNSTABLE = """
    The following integration tests are **unstable** [[explain]({explain_url}#unstable)]:

    {tests}

    Tests may need to be restarted due to failures unrelated to changes.
    """

    TEMPLATE_UNSUPPORTED = """
    The following integration tests are **unsupported** [[explain]({explain_url}#unsupported)]:

    {tests}

    Consider running the tests manually or extending test infrastructure to add support.
    """

    TEMPLATE_UNTESTED = """
    The following modules have **no integration tests** [[explain]({explain_url}#untested)]:

    {tests}

    Consider adding integration tests before or alongside changes.
    """

    def __init__(self):
        super(IntegrationAliasesTest, self).__init__()

        self._shippable_yml_lines = []  # type: list[str]
        self._shippable_test_groups = {}  # type: dict[str, set[int]]

    @property
    def shippable_yml_lines(self):
        """
        :rtype: list[str]
        """
        if not self._shippable_yml_lines:
            with open(self.SHIPPABLE_YML, 'r') as shippable_yml_fd:
                self._shippable_yml_lines = shippable_yml_fd.read().splitlines()

        return self._shippable_yml_lines

    @property
    def shippable_test_groups(self):
        """
        :rtype: dict[str, set[int]]
        """
        if not self._shippable_test_groups:
            matches = [re.search(r'^[ #]+- env: T=(?P<group>[^/]+)/(?P<params>.+)/(?P<number>[1-9])$', line) for line in self.shippable_yml_lines]
            entries = [(match.group('group'), int(match.group('number'))) for match in matches if match]

            for key, value in entries:
                if key not in self._shippable_test_groups:
                    self._shippable_test_groups[key] = set()

                self._shippable_test_groups[key].add(value)

        return self._shippable_test_groups

    def format_shippable_group_alias(self, name, fallback=''):
        """
        :type name: str
        :type fallback: str
        :rtype: str
        """
        group_numbers = self.shippable_test_groups.get(name, None)

        if group_numbers:
            if min(group_numbers) != 1:
                display.warning('Min test group "%s" in shippable.yml is %d instead of 1.' % (name, min(group_numbers)), unique=True)

            if max(group_numbers) != len(group_numbers):
                display.warning('Max test group "%s" in shippable.yml is %d instead of %d.' % (name, max(group_numbers), len(group_numbers)), unique=True)

            if len(group_numbers) > 1:
                alias = 'shippable/%s/group[%d-%d]/' % (name, min(group_numbers), max(group_numbers))
            else:
                alias = 'shippable/%s/group%d/' % (name, min(group_numbers))
        elif fallback:<commit_msg>Allow for test groups with double digits (#58532)<commit_after>"""Sanity test to check integration test aliases."""
from __future__ import absolute_import, print_function

import json
import textwrap
import re
import os

from lib.sanity import (
    SanitySingleVersion,
    SanityMessage,
    SanityFailure,
    SanitySuccess,
    SanityTargets,
)

from lib.config import (
    SanityConfig,
)

from lib.target import (
    filter_targets,
    walk_posix_integration_targets,
    walk_windows_integration_targets,
    walk_integration_targets,
    walk_module_targets,
)

from lib.cloud import (
    get_cloud_platforms,
)

from lib.util import (
    display,
)


class IntegrationAliasesTest(SanitySingleVersion):
    """Sanity test to evaluate integration test aliases."""
    SHIPPABLE_YML = 'shippable.yml'

    DISABLED = 'disabled/'
    UNSTABLE = 'unstable/'
    UNSUPPORTED = 'unsupported/'

    EXPLAIN_URL = 'https://docs.ansible.com/ansible/devel/dev_guide/testing/sanity/integration-aliases.html'

    TEMPLATE_DISABLED = """
    The following integration tests are **disabled** [[explain]({explain_url}#disabled)]:

    {tests}

    Consider fixing the integration tests before or alongside changes.
    """

    TEMPLATE_UNSTABLE = """
    The following integration tests are **unstable** [[explain]({explain_url}#unstable)]:

    {tests}

    Tests may need to be restarted due to failures unrelated to changes.
    """

    TEMPLATE_UNSUPPORTED = """
    The following integration tests are **unsupported** [[explain]({explain_url}#unsupported)]:

    {tests}

    Consider running the tests manually or extending test infrastructure to add support.
    """

    TEMPLATE_UNTESTED = """
    The following modules have **no integration tests** [[explain]({explain_url}#untested)]:

    {tests}

    Consider adding integration tests before or alongside changes.
    """

    def __init__(self):
        super(IntegrationAliasesTest, self).__init__()

        self._shippable_yml_lines = []  # type: list[str]
        self._shippable_test_groups = {}  # type: dict[str, set[int]]

    @property
    def shippable_yml_lines(self):
        """
        :rtype: list[str]
        """
        if not self._shippable_yml_lines:
            with open(self.SHIPPABLE_YML, 'r') as shippable_yml_fd:
                self._shippable_yml_lines = shippable_yml_fd.read().splitlines()

        return self._shippable_yml_lines

    @property
    def shippable_test_groups(self):
        """
        :rtype: dict[str, set[int]]
        """
        if not self._shippable_test_groups:
            matches = [re.search(r'^[ #]+- env: T=(?P<group>[^/]+)/(?P<params>.+)/(?P<number>[1-9][0-9]?)$', line) for line in self.shippable_yml_lines]
            entries = [(match.group('group'), int(match.group('number'))) for match in matches if match]

            for key, value in entries:
                if key not in self._shippable_test_groups:
                    self._shippable_test_groups[key] = set()

                self._shippable_test_groups[key].add(value)

        return self._shippable_test_groups

    def format_shippable_group_alias(self, name, fallback=''):
        """
        :type name: str
        :type fallback: str
        :rtype: str
        """
        group_numbers = self.shippable_test_groups.get(name, None)

        if group_numbers:
            if min(group_numbers) != 1:
                display.warning('Min test group "%s" in shippable.yml is %d instead of 1.' % (name, min(group_numbers)), unique=True)

            if max(group_numbers) != len(group_numbers):
                display.warning('Max test group "%s" in shippable.yml is %d instead of %d.' % (name, max(group_numbers), len(group_numbers)), unique=True)

            if max(group_numbers) > 9:
                alias = 'shippable/%s/group(%s)/' % (name, '|'.join(str(i) for i in range(min(group_numbers), max(group_numbers) + 1)))
            elif len(group_numbers) > 1:
                alias = 'shippable/%s/group[%d-%d]/' % (name, min(group_numbers), max(group_numbers))
            else:
                alias = 'shippable/%s/group%d/' % (name, min(group_numbers))
        elif fallback:
{'fraction_non_alphanumeric': 0.11768315130690882, 'fraction_numerical': 0.002331574426309977, 'mean_word_length': 2.906999041227229, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 12, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2758089', 'n_tokens_mistral': 2658, 'n_tokens_neox': 2467, 'n_words': 583}