summaryrefslogtreecommitdiff
path: root/buildscripts/resmokelib/suitesconfig.py
blob: ecfef2659178ce097bb6c7a0ccb008f42546c64a (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
129
130
131
"""Module for retrieving the configuration of resmoke.py test suites."""

import collections
import optparse
import os

from . import config as _config
from . import errors
from . import utils
from .testing import suite as _suite


def get_named_suites():
    """Return a sorted list of the suites names."""
    # Skip "with_*server" and "no_server" because they do not define any test files to run.
    executor_only = {"with_server", "with_external_server", "no_server"}
    names = [name for name in _config.NAMED_SUITES.keys() if name not in executor_only]
    names.sort()
    return names


def get_named_suites_with_root_level_key(root_level_key):
    """Return the suites that contain the given root_level_key and their values."""
    all_suite_names = get_named_suites()
    suites_to_return = []

    for suite in all_suite_names:
        suite_config = _get_suite_config(suite)
        if root_level_key in suite_config.keys() and suite_config[root_level_key]:
            suites_to_return.append(
                {"origin": suite, "multiversion_name": suite_config[root_level_key]})
    return suites_to_return


def create_test_membership_map(fail_on_missing_selector=False, test_kind=None):
    """Return a dict keyed by test name containing all of the suites that will run that test.

    If 'test_kind' is specified, then only the mappings for that kind of test are returned. Multiple
    kinds of tests can be specified as an iterable (e.g. a tuple or list). This function parses the
    definition of every available test suite, which is an expensive operation. It is therefore
    desirable for it to only ever be called once.
    """
    if test_kind is not None:
        if isinstance(test_kind, str):
            test_kind = [test_kind]

        test_kind = frozenset(test_kind)

    test_membership = collections.defaultdict(list)
    suite_names = get_named_suites()
    for suite_name in suite_names:
        try:
            suite_config = _get_suite_config(suite_name)
            if test_kind and suite_config.get("test_kind") not in test_kind:
                continue
            suite = _suite.Suite(suite_name, suite_config)
        except IOError as err:
            # We ignore errors from missing files referenced in the test suite's "selector"
            # section. Certain test suites (e.g. unittests.yml) have a dedicated text file to
            # capture the list of tests they run; the text file may not be available if the
            # associated SCons target hasn't been built yet.
            if err.filename in _config.EXTERNAL_SUITE_SELECTORS:
                if not fail_on_missing_selector:
                    continue
            raise

        for testfile in suite.tests:
            if isinstance(testfile, (dict, list)):
                continue
            test_membership[testfile].append(suite_name)
    return test_membership


def get_suites(suite_files, test_files):
    """Retrieve the Suite instances based on suite configuration files and override parameters.

    Args:
        suite_files: A list of file paths pointing to suite YAML configuration files. For the suites
            defined in 'buildscripts/resmokeconfig/suites/', a shorthand name consisting of the
            filename without the extension can be used.
        test_files: A list of file paths pointing to test files overriding the roots for the suites.
    """
    suite_roots = None
    if test_files:
        # Do not change the execution order of the tests passed as args, unless a tag option is
        # specified. If an option is specified, then sort the tests for consistent execution order.
        _config.ORDER_TESTS_BY_NAME = any(
            tag_filter is not None
            for tag_filter in (_config.EXCLUDE_WITH_ANY_TAGS, _config.INCLUDE_WITH_ANY_TAGS))
        # Build configuration for list of files to run.
        suite_roots = _make_suite_roots(test_files)

    suites = []
    for suite_filename in suite_files:
        suite_config = _get_suite_config(suite_filename)
        if suite_roots:
            # Override the suite's default test files with those passed in from the command line.
            suite_config.update(suite_roots)
        suite = _suite.Suite(suite_filename, suite_config)
        suites.append(suite)
    return suites


def get_suite(suite_file):
    """Retrieve the Suite instance corresponding to a suite configuration file."""
    suite_config = _get_suite_config(suite_file)
    return _suite.Suite(suite_file, suite_config)


def _make_suite_roots(files):
    return {"selector": {"roots": files}}


def _get_suite_config(pathname):
    """Attempt to read YAML configuration from 'pathname' for the suite."""
    return _get_yaml_config("suite", pathname)


def _get_yaml_config(kind, pathname):
    # Named executors or suites are specified as the basename of the file, without the .yml
    # extension.
    if not utils.is_yaml_file(pathname) and not os.path.dirname(pathname):
        if pathname not in _config.NAMED_SUITES:  # pylint: disable=unsupported-membership-test
            raise errors.SuiteNotFound("Unknown %s '%s'" % (kind, pathname))
        # Expand 'pathname' to full path.
        pathname = _config.NAMED_SUITES[pathname]  # pylint: disable=unsubscriptable-object

    if not utils.is_yaml_file(pathname) or not os.path.isfile(pathname):
        raise optparse.OptionValueError(
            "Expected a %s YAML config, but got '%s'" % (kind, pathname))
    return utils.load_yaml_file(pathname)