1# This Source Code Form is subject to the terms of the Mozilla Public
2# License, v. 2.0. If a copy of the MPL was not distributed with this
3# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5from __future__ import absolute_import, print_function, unicode_literals
6
7import copy
8import logging
9
10from . import transform
11from ..util.yaml import load_yaml
12
13logger = logging.getLogger(__name__)
14
15
16class TestTask(transform.TransformTask):
17    """
18    A task implementing a Gecko test.
19    """
20
21    @classmethod
22    def get_inputs(cls, kind, path, config, params, loaded_tasks):
23
24        # the kind on which this one depends
25        if len(config.get('kind-dependencies', [])) != 1:
26            raise Exception("TestTask kinds must have exactly one item in kind-dependencies")
27        dep_kind = config['kind-dependencies'][0]
28
29        # get build tasks, keyed by build platform
30        builds_by_platform = cls.get_builds_by_platform(dep_kind, loaded_tasks)
31
32        # get the test platforms for those build tasks
33        test_platforms_cfg = load_yaml(path, 'test-platforms.yml')
34        test_platforms = cls.get_test_platforms(test_platforms_cfg, builds_by_platform)
35
36        # expand the test sets for each of those platforms
37        test_sets_cfg = load_yaml(path, 'test-sets.yml')
38        test_platforms = cls.expand_tests(test_sets_cfg, test_platforms)
39
40        # load the test descriptions
41        test_descriptions = load_yaml(path, 'tests.yml')
42
43        # generate all tests for all test platforms
44        for test_platform_name, test_platform in test_platforms.iteritems():
45            for test_name in test_platform['test-names']:
46                test = copy.deepcopy(test_descriptions[test_name])
47                test['build-platform'] = test_platform['build-platform']
48                test['test-platform'] = test_platform_name
49                test['build-label'] = test_platform['build-label']
50                test['test-name'] = test_name
51
52                logger.debug("Generating tasks for {} test {} on platform {}".format(
53                    kind, test_name, test['test-platform']))
54                yield test
55
56    @classmethod
57    def get_builds_by_platform(cls, dep_kind, loaded_tasks):
58        """Find the build tasks on which tests will depend, keyed by
59        platform/type.  Returns a dictionary mapping build platform to task
60        label."""
61        builds_by_platform = {}
62        for task in loaded_tasks:
63            if task.kind != dep_kind:
64                continue
65
66            build_platform = task.attributes.get('build_platform')
67            build_type = task.attributes.get('build_type')
68            if not build_platform or not build_type:
69                continue
70            platform = "{}/{}".format(build_platform, build_type)
71            if platform in builds_by_platform:
72                raise Exception("multiple build jobs for " + platform)
73            builds_by_platform[platform] = task.label
74        return builds_by_platform
75
76    @classmethod
77    def get_test_platforms(cls, test_platforms_cfg, builds_by_platform):
78        """Get the test platforms for which test tasks should be generated,
79        based on the available build platforms.  Returns a dictionary mapping
80        test platform to {test-set, build-platform, build-label}."""
81        test_platforms = {}
82        for test_platform, cfg in test_platforms_cfg.iteritems():
83            build_platform = cfg['build-platform']
84            if build_platform not in builds_by_platform:
85                logger.warning(
86                    "No build task with platform {}; ignoring test platform {}".format(
87                        build_platform, test_platform))
88                continue
89            test_platforms[test_platform] = {
90                'test-set': cfg['test-set'],
91                'build-platform': build_platform,
92                'build-label': builds_by_platform[build_platform],
93            }
94        return test_platforms
95
96    @classmethod
97    def expand_tests(cls, test_sets_cfg, test_platforms):
98        """Expand the test sets in `test_platforms` out to sets of test names.
99        Returns a dictionary like `get_test_platforms`, with an additional
100        `test-names` key for each test platform, containing a set of test
101        names."""
102        rv = {}
103        for test_platform, cfg in test_platforms.iteritems():
104            test_set = cfg['test-set']
105            if test_set not in test_sets_cfg:
106                raise Exception(
107                    "Test set '{}' for test platform {} is not defined".format(
108                        test_set, test_platform))
109            test_names = test_sets_cfg[test_set]
110            rv[test_platform] = cfg.copy()
111            rv[test_platform]['test-names'] = test_names
112        return rv
113