[stable-2.10] Use AZP config for integration-aliases test.

No changelog entry since this test is limited to the ansible/ansible repo.

Backport of https://github.com/ansible/ansible/pull/72842
This commit is contained in:
Matt Clay 2020-12-03 14:00:18 -08:00
parent 7813b1248b
commit dbdacbd48c
3 changed files with 99 additions and 44 deletions

View file

@ -0,0 +1,15 @@
"""Read YAML from stdin and write JSON to stdout."""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
import sys
from yaml import load
try:
from yaml import CSafeLoader as SafeLoader
except ImportError:
from yaml import SafeLoader
json.dump(load(sys.stdin, Loader=SafeLoader), sys.stdout)

View file

@ -2,8 +2,8 @@
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import json
import textwrap import textwrap
import re
import os import os
from .. import types as t from .. import types as t
@ -14,6 +14,7 @@ from ..sanity import (
SanityFailure, SanityFailure,
SanitySuccess, SanitySuccess,
SanityTargets, SanityTargets,
SANITY_ROOT,
) )
from ..config import ( from ..config import (
@ -38,6 +39,8 @@ from ..io import (
from ..util import ( from ..util import (
display, display,
find_python,
raw_command,
) )
from ..util_common import ( from ..util_common import (
@ -48,7 +51,8 @@ from ..util_common import (
class IntegrationAliasesTest(SanityVersionNeutral): class IntegrationAliasesTest(SanityVersionNeutral):
"""Sanity test to evaluate integration test aliases.""" """Sanity test to evaluate integration test aliases."""
SHIPPABLE_YML = 'shippable.yml' CI_YML = '.azure-pipelines/azure-pipelines.yml'
TEST_ALIAS_PREFIX = 'shippable' # this will be changed at some point in the future
DISABLED = 'disabled/' DISABLED = 'disabled/'
UNSTABLE = 'unstable/' UNSTABLE = 'unstable/'
@ -93,8 +97,8 @@ class IntegrationAliasesTest(SanityVersionNeutral):
def __init__(self): def __init__(self):
super(IntegrationAliasesTest, self).__init__() super(IntegrationAliasesTest, self).__init__()
self._shippable_yml_lines = [] # type: t.List[str] self._ci_config = {} # type: t.Dict[str, t.Any]
self._shippable_test_groups = {} # type: t.Dict[str, t.Set[int]] self._ci_test_groups = {} # type: t.Dict[str, t.List[int]]
@property @property
def can_ignore(self): # type: () -> bool def can_ignore(self): # type: () -> bool
@ -106,61 +110,95 @@ class IntegrationAliasesTest(SanityVersionNeutral):
"""True if the test does not use test targets. Mutually exclusive with all_targets.""" """True if the test does not use test targets. Mutually exclusive with all_targets."""
return True return True
@property def load_ci_config(self, args): # type: (SanityConfig) -> t.Dict[str, t.Any]
def shippable_yml_lines(self): """Load and return the CI YAML configuration."""
""" if not self._ci_config:
:rtype: list[str] self._ci_config = self.load_yaml(args, self.CI_YML)
"""
if not self._shippable_yml_lines:
self._shippable_yml_lines = read_text_file(self.SHIPPABLE_YML).splitlines()
return self._shippable_yml_lines return self._ci_config
@property @property
def shippable_test_groups(self): def ci_test_groups(self): # type: () -> t.Dict[str, t.List[int]]
""" """Return a dictionary of CI test names and their group(s)."""
:rtype: dict[str, set[int]] if not self._ci_test_groups:
""" test_groups = {}
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: for stage in self._ci_config['stages']:
if key not in self._shippable_test_groups: for job in stage['jobs']:
self._shippable_test_groups[key] = set() if job.get('template') != 'templates/matrix.yml':
continue
self._shippable_test_groups[key].add(value) parameters = job['parameters']
return self._shippable_test_groups groups = parameters.get('groups', [])
test_format = parameters.get('testFormat', '{0}')
test_group_format = parameters.get('groupFormat', '{0}/{{1}}')
def format_shippable_group_alias(self, name, fallback=''): for target in parameters['targets']:
test = target.get('test') or target.get('name')
if groups:
tests_formatted = [test_group_format.format(test_format).format(test, group) for group in groups]
else:
tests_formatted = [test_format.format(test)]
for test_formatted in tests_formatted:
parts = test_formatted.split('/')
key = parts[0]
if key in ('sanity', 'units'):
continue
try:
group = int(parts[-1])
except ValueError:
continue
if group < 1 or group > 99:
continue
group_set = test_groups.setdefault(key, set())
group_set.add(group)
self._ci_test_groups = dict((key, sorted(value)) for key, value in test_groups.items())
return self._ci_test_groups
def format_test_group_alias(self, name, fallback=''):
""" """
:type name: str :type name: str
:type fallback: str :type fallback: str
:rtype: str :rtype: str
""" """
group_numbers = self.shippable_test_groups.get(name, None) group_numbers = self.ci_test_groups.get(name, None)
if group_numbers: if group_numbers:
if min(group_numbers) != 1: 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) display.warning('Min test group "%s" in %s is %d instead of 1.' % (name, self.CI_YML, min(group_numbers)), unique=True)
if max(group_numbers) != len(group_numbers): 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) display.warning('Max test group "%s" in %s is %d instead of %d.' % (name, self.CI_YML, max(group_numbers), len(group_numbers)), unique=True)
if max(group_numbers) > 9: 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))) alias = '%s/%s/group(%s)/' % (self.TEST_ALIAS_PREFIX, name, '|'.join(str(i) for i in range(min(group_numbers), max(group_numbers) + 1)))
elif len(group_numbers) > 1: elif len(group_numbers) > 1:
alias = 'shippable/%s/group[%d-%d]/' % (name, min(group_numbers), max(group_numbers)) alias = '%s/%s/group[%d-%d]/' % (self.TEST_ALIAS_PREFIX, name, min(group_numbers), max(group_numbers))
else: else:
alias = 'shippable/%s/group%d/' % (name, min(group_numbers)) alias = '%s/%s/group%d/' % (self.TEST_ALIAS_PREFIX, name, min(group_numbers))
elif fallback: elif fallback:
alias = 'shippable/%s/group%d/' % (fallback, 1) alias = '%s/%s/group%d/' % (self.TEST_ALIAS_PREFIX, fallback, 1)
else: else:
raise Exception('cannot find test group "%s" in shippable.yml' % name) raise Exception('cannot find test group "%s" in %s' % (name, self.CI_YML))
return alias return alias
def load_yaml(self, args, path): # type: (SanityConfig, str) -> t.Dict[str, t.Any]
"""Load the specified YAML file and return the contents."""
yaml_to_json_path = os.path.join(SANITY_ROOT, self.name, 'yaml_to_json.py')
python = find_python(args.python_version)
return json.loads(raw_command([python, yaml_to_json_path], data=read_text_file(path), capture=True)[0])
def test(self, args, targets): def test(self, args, targets):
""" """
:type args: SanityConfig :type args: SanityConfig
@ -170,10 +208,10 @@ class IntegrationAliasesTest(SanityVersionNeutral):
if args.explain: if args.explain:
return SanitySuccess(self.name) return SanitySuccess(self.name)
if not os.path.isfile(self.SHIPPABLE_YML): if not os.path.isfile(self.CI_YML):
return SanityFailure(self.name, messages=[SanityMessage( return SanityFailure(self.name, messages=[SanityMessage(
message='file missing', message='file missing',
path=self.SHIPPABLE_YML, path=self.CI_YML,
)]) )])
results = dict( results = dict(
@ -181,6 +219,7 @@ class IntegrationAliasesTest(SanityVersionNeutral):
labels={}, labels={},
) )
self.load_ci_config(args)
self.check_changes(args, results) self.check_changes(args, results)
write_json_test_results(ResultType.BOT, 'data-sanity-ci.json', results) write_json_test_results(ResultType.BOT, 'data-sanity-ci.json', results)
@ -219,23 +258,23 @@ class IntegrationAliasesTest(SanityVersionNeutral):
messages.append(SanityMessage('invalid alias `%s`' % alias, '%s/aliases' % target.path)) messages.append(SanityMessage('invalid alias `%s`' % alias, '%s/aliases' % target.path))
messages += self.check_ci_group( messages += self.check_ci_group(
targets=tuple(filter_targets(posix_targets, ['cloud/', 'shippable/generic/'], include=False, targets=tuple(filter_targets(posix_targets, ['cloud/', '%s/generic/' % self.TEST_ALIAS_PREFIX], include=False,
directories=False, errors=False)), directories=False, errors=False)),
find=self.format_shippable_group_alias('linux').replace('linux', 'posix'), find=self.format_test_group_alias('linux').replace('linux', 'posix'),
find_incidental=['shippable/posix/incidental/'], find_incidental=['%s/posix/incidental/' % self.TEST_ALIAS_PREFIX],
) )
messages += self.check_ci_group( messages += self.check_ci_group(
targets=tuple(filter_targets(posix_targets, ['shippable/generic/'], include=True, directories=False, targets=tuple(filter_targets(posix_targets, ['%s/generic/' % self.TEST_ALIAS_PREFIX], include=True, directories=False,
errors=False)), errors=False)),
find=self.format_shippable_group_alias('generic'), find=self.format_test_group_alias('generic'),
) )
for cloud in clouds: for cloud in clouds:
messages += self.check_ci_group( messages += self.check_ci_group(
targets=tuple(filter_targets(posix_targets, ['cloud/%s/' % cloud], include=True, directories=False, errors=False)), targets=tuple(filter_targets(posix_targets, ['cloud/%s/' % cloud], include=True, directories=False, errors=False)),
find=self.format_shippable_group_alias(cloud, 'cloud'), find=self.format_test_group_alias(cloud, 'cloud'),
find_incidental=['shippable/%s/incidental/' % cloud, 'shippable/cloud/incidental/'], find_incidental=['%s/%s/incidental/' % (self.TEST_ALIAS_PREFIX, cloud), '%s/cloud/incidental/' % self.TEST_ALIAS_PREFIX],
) )
return messages return messages
@ -250,8 +289,8 @@ class IntegrationAliasesTest(SanityVersionNeutral):
messages += self.check_ci_group( messages += self.check_ci_group(
targets=windows_targets, targets=windows_targets,
find=self.format_shippable_group_alias('windows'), find=self.format_test_group_alias('windows'),
find_incidental=['shippable/windows/incidental/'], find_incidental=['%s/windows/incidental/' % self.TEST_ALIAS_PREFIX],
) )
return messages return messages