summaryrefslogtreecommitdiff
path: root/test/lib/ansible_test/_internal/provider/layout/collection.py
blob: d747f31f319c4b77f1c494ff90ad39cfe9ccd8b7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
"""Layout provider for Ansible collections."""
from __future__ import annotations

import os

from . import (
    ContentLayout,
    LayoutProvider,
    CollectionDetail,
    LayoutMessages,
)

from ...util import (
    is_valid_identifier,
)


class CollectionLayout(LayoutProvider):
    """Layout provider for Ansible collections."""

    @staticmethod
    def is_content_root(path: str) -> bool:
        """Return True if the given path is a content root for this provider."""
        if os.path.basename(os.path.dirname(os.path.dirname(path))) == 'ansible_collections':
            return True

        return False

    def create(self, root: str, paths: list[str]) -> ContentLayout:
        """Create a Layout using the given root and paths."""
        plugin_paths = dict((p, os.path.join('plugins', p)) for p in self.PLUGIN_TYPES)

        collection_root = os.path.dirname(os.path.dirname(root))
        collection_dir = os.path.relpath(root, collection_root)

        collection_namespace: str
        collection_name: str

        collection_namespace, collection_name = collection_dir.split(os.path.sep)

        collection_root = os.path.dirname(collection_root)

        sanity_messages = LayoutMessages()
        integration_messages = LayoutMessages()
        unit_messages = LayoutMessages()

        # these apply to all test commands
        self.__check_test_path(paths, sanity_messages)
        self.__check_test_path(paths, integration_messages)
        self.__check_test_path(paths, unit_messages)

        # these apply to specific test commands
        integration_targets_path = self.__check_integration_path(paths, integration_messages)
        self.__check_unit_path(paths, unit_messages)

        return ContentLayout(
            root,
            paths,
            plugin_paths=plugin_paths,
            collection=CollectionDetail(
                name=collection_name,
                namespace=collection_namespace,
                root=collection_root,
            ),
            test_path='tests',
            results_path='tests/output',
            sanity_path='tests/sanity',
            sanity_messages=sanity_messages,
            integration_path='tests/integration',
            integration_targets_path=integration_targets_path.rstrip(os.path.sep),
            integration_vars_path='tests/integration/integration_config.yml',
            integration_messages=integration_messages,
            unit_path='tests/unit',
            unit_module_path='tests/unit/plugins/modules',
            unit_module_utils_path='tests/unit/plugins/module_utils',
            unit_messages=unit_messages,
            unsupported=not (is_valid_identifier(collection_namespace) and is_valid_identifier(collection_name)),
        )

    @staticmethod
    def __check_test_path(paths: list[str], messages: LayoutMessages) -> None:
        modern_test_path = 'tests/'
        modern_test_path_found = any(path.startswith(modern_test_path) for path in paths)
        legacy_test_path = 'test/'
        legacy_test_path_found = any(path.startswith(legacy_test_path) for path in paths)

        if modern_test_path_found and legacy_test_path_found:
            messages.warning.append('Ignoring tests in "%s" in favor of "%s".' % (legacy_test_path, modern_test_path))
        elif legacy_test_path_found:
            messages.warning.append('Ignoring tests in "%s" that should be in "%s".' % (legacy_test_path, modern_test_path))

    @staticmethod
    def __check_integration_path(paths: list[str], messages: LayoutMessages) -> str:
        modern_integration_path = 'roles/test/'
        modern_integration_path_found = any(path.startswith(modern_integration_path) for path in paths)
        legacy_integration_path = 'tests/integration/targets/'
        legacy_integration_path_found = any(path.startswith(legacy_integration_path) for path in paths)

        if modern_integration_path_found and legacy_integration_path_found:
            messages.warning.append('Ignoring tests in "%s" in favor of "%s".' % (legacy_integration_path, modern_integration_path))
            integration_targets_path = modern_integration_path
        elif legacy_integration_path_found:
            messages.info.append('Falling back to tests in "%s" because "%s" was not found.' % (legacy_integration_path, modern_integration_path))
            integration_targets_path = legacy_integration_path
        elif modern_integration_path_found:
            messages.info.append('Loading tests from "%s".' % modern_integration_path)
            integration_targets_path = modern_integration_path
        else:
            messages.error.append('Cannot run integration tests without "%s" or "%s".' % (modern_integration_path, legacy_integration_path))
            integration_targets_path = modern_integration_path

        return integration_targets_path

    @staticmethod
    def __check_unit_path(paths: list[str], messages: LayoutMessages) -> None:
        modern_unit_path = 'tests/unit/'
        modern_unit_path_found = any(path.startswith(modern_unit_path) for path in paths)
        legacy_unit_path = 'tests/units/'  # test/units/ will be covered by the warnings for test/ vs tests/
        legacy_unit_path_found = any(path.startswith(legacy_unit_path) for path in paths)

        if modern_unit_path_found and legacy_unit_path_found:
            messages.warning.append('Ignoring tests in "%s" in favor of "%s".' % (legacy_unit_path, modern_unit_path))
        elif legacy_unit_path_found:
            messages.warning.append('Rename "%s" to "%s" to run unit tests.' % (legacy_unit_path, modern_unit_path))
        elif modern_unit_path_found:
            pass  # unit tests only run from one directory so no message is needed
        else:
            messages.error.append('Cannot run unit tests without "%s".' % modern_unit_path)