Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add script to generate module tests. #16617

Merged
merged 1 commit into from
Jul 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
259 changes: 259 additions & 0 deletions test/utils/shippable/modules/generate-tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
#!/usr/bin/env python

# (c) 2016 Matt Clay <matt@mystile.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.

from __future__ import print_function

import yaml
import os
import subprocess
import sys

from os import path
from argparse import ArgumentParser

import ansible.constants as C

from ansible.playbook import Playbook
from ansible.vars import VariableManager
from ansible.parsing.dataloader import DataLoader


def main():
"""Generate an integration test script for changed modules."""

C.DEPRECATION_WARNINGS = False

targets = [
'non_destructive',
'destructive',
]

parser = ArgumentParser(description='Generate an integration test script for changed modules.')
parser.add_argument('module_group', choices=['core', 'extras'], help='module group to test')
parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', help='write verbose output to stderr')

args = parser.parse_args()

generate_test_commands(args.module_group, targets, verbose=args.verbose)


def generate_test_commands(module_group, targets, verbose=False):
"""Generate test commands for the given module group and test targets.

Args:
module_group: The module group (core, extras) to examine.
targets: The test targets to examine.
verbose: True to write detailed output to stderr.
"""

base_dir = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', '..', '..'))

job_config_path = path.join(base_dir, 'shippable.yml')
module_dir = os.path.join(base_dir, 'lib', 'ansible', 'modules', module_group)

if verbose:
print_stderr(' config: %s' % job_config_path)
print_stderr('modules: %s' % module_dir)
print_stderr('targets: %s' % ' '.join(targets))
print_stderr()

paths_changed = get_changed_paths(module_dir)

if len(paths_changed) == 0:
print_stderr('No changes to files detected.')
exit()

if verbose:
dump_stderr('paths_changed', paths_changed)

modules_changed = get_modules(paths_changed)

if len(modules_changed) == 0:
print_stderr('No changes to modules detected.')
exit()

if verbose:
dump_stderr('modules_changed', modules_changed)

module_tags = get_module_test_tags(modules_changed)

if verbose:
dump_stderr('module_tags', module_tags)

available_tags = get_target_tags(base_dir, targets)

if verbose:
dump_stderr('available_tags', available_tags)

use_tags = module_tags & available_tags

if len(use_tags) == 0:
print_stderr('No tagged test roles found for changed modules.')
exit()

if verbose:
dump_stderr('use_tags', use_tags)

jobs = get_test_jobs(job_config_path)

target = ' '.join(targets)
tags = ','.join(use_tags)
script_path = 'test/utils/shippable/integration.sh'

commands = ['TARGET="%s" TEST_FLAGS="-t %s" %s %s' % (target, tags, j, script_path) for j in jobs]

for command in commands:
print(command)


def print_stderr(*args, **kwargs):
"""Print to stderr."""

print(*args, file=sys.stderr, **kwargs)


def dump_stderr(label, l):
"""Write a label and list contents to stderr.

Args:
label: The label to print for this list.
l: The list to dump to stderr.
"""

print_stderr('[%s:%s]\n%s\n' % (label, len(l), '\n'.join(l)))


def get_target_tags(base_dir, targets):
"""Get role tags from the integration tests for the given test targets.

Args:
base_dir: The root of the ansible source code.
targets: The test targets to scan for tags.

Returns: Set of role tags.
"""

playbook_dir = os.path.join(base_dir, 'test', 'integration')

tags = set()

for target in targets:
playbook_path = os.path.join(playbook_dir, target + '.yml')
tags |= get_role_tags(playbook_path)

return tags


def get_role_tags(playbook_path):
"""Get role tags from the given playbook.

Args:
playbook_path: Path to the playbook to get role tags from.

Returns: Set of role tags.
"""

variable_manager = VariableManager()
loader = DataLoader()
playbook = Playbook.load(playbook_path, variable_manager=variable_manager, loader=loader)
tags = set()

for play in playbook.get_plays():
for role in play.get_roles():
for tag in role.tags:
tags.add(tag)

return tags


def get_test_jobs(config_path):
"""Get list of test jobs to execute based on the given shippable.yml config file.

Args:
config_path: Path to the shippable.yml config file to extract jobs from.

Returns: List of test jobs to execute.
"""

with open(config_path, 'r') as shippable:
config = yaml.load(shippable.read())

includes = [dict([e.split('=') for e in i['env'].split(' ')]) for i in config['matrix']['include']]
tests = [i for i in includes if i['TEST'] == 'integration']
images = list(set([t['IMAGE'] for t in tests]))
privileged = set([t['IMAGE'] for t in tests if 'PRIVILEGED' in t and t['PRIVILEGED'] == 'true'])
images.sort()
jobs = ['IMAGE=%s%s' % (i, ' PRIVILEGED=true' if i in privileged else '') for i in images]

return jobs


def get_changed_paths(git_root, branch='devel'):
"""Get file paths changed in current branch vs given branch.

Args:
git_root: The root of the git clone.
branch: The branch to compare against (default: devel)

Returns: List of file paths changed.
"""

paths = subprocess.check_output(['git', 'diff', '--name-only', branch], cwd=git_root).strip().split('\n')

return paths


def get_modules(paths):
"""Get module names from file paths.

Args:
paths: List of paths to extract module names from.

Returns: List of module names.
"""

module_extensions = [
'.py',
'.ps1',
]

modules = [path.splitext(path.basename(c))[0].strip('_') for c in paths if
path.splitext(c)[1] in module_extensions and
'/' in c and
not c.startswith('test/') and
not path.basename(c)[0] == '__init__.py']

return modules


def get_module_test_tags(modules):
"""Get test tags from module names.

Args:
modules: List of module names to get test tags for.

Returns: Set of test tags.
"""

tags = set(['test_' + m for m in modules])
return tags


if __name__ == '__main__':
main()
2 changes: 2 additions & 0 deletions test/utils/shippable/modules/generate-tests-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
jinja2
pyyaml