mirror of https://github.com/ansible/ansible.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
137 lines
6.0 KiB
Python
137 lines
6.0 KiB
Python
"""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)
|
|
|
|
errors: list[str] = []
|
|
|
|
if not is_valid_identifier(collection_namespace):
|
|
errors.append(f'The namespace "{collection_namespace}" is an invalid identifier or a reserved keyword.')
|
|
|
|
if not is_valid_identifier(collection_name):
|
|
errors.append(f'The name "{collection_name}" is an invalid identifier or a reserved keyword.')
|
|
|
|
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=errors,
|
|
)
|
|
|
|
@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)
|