summaryrefslogtreecommitdiffstats
path: root/testing/condprofile/condprof/runner.py
blob: 07fb6d3e73e79a81f5bf8d0153407f5bf116d3f3 (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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
""" Script that launches profiles creation.
"""
import os
import shutil
import asyncio

import mozversion

from condprof.creator import ProfileCreator
from condprof.desktop import DesktopEnv
from condprof.android import AndroidEnv
from condprof.changelog import Changelog
from condprof.scenarii import scenarii
from condprof.util import logger, get_current_platform, extract_from_dmg
from condprof.customization import get_customizations, find_customization
from condprof.client import read_changelog, ProfileNotFoundError


class Runner:
    def __init__(
        self,
        profile,
        firefox,
        geckodriver,
        archive,
        device_name,
        strict,
        force_new,
        visible,
        skip_logs=False,
        remote_test_root="/sdcard/test_root/",
    ):
        self.force_new = force_new
        self.profile = profile
        self.geckodriver = geckodriver
        self.archive = archive
        self.device_name = device_name
        self.strict = strict
        self.visible = visible
        self.skip_logs = skip_logs
        self.remote_test_root = remote_test_root
        self.env = {}
        # unpacking a dmg
        # XXX do something similar if we get an apk (but later)
        # XXX we want to do
        #   adb install -r target.apk
        #   and get the installed app name
        if firefox is not None and firefox.endswith("dmg"):
            target = os.path.join(os.path.dirname(firefox), "firefox.app")
            extract_from_dmg(firefox, target)
            firefox = os.path.join(target, "Contents", "MacOS", "firefox")
        self.firefox = firefox
        self.android = self.firefox is not None and self.firefox.startswith(
            "org.mozilla"
        )

    def prepare(self, scenario, customization):
        self.scenario = scenario
        self.customization = customization

        # early checks to avoid extra work
        if self.customization != "all":
            if find_customization(self.customization) is None:
                raise IOError("Cannot find customization %r" % self.customization)

        if self.scenario != "all" and self.scenario not in scenarii:
            raise IOError("Cannot find scenario %r" % self.scenario)

        if not self.android and self.firefox is not None:
            logger.info("Verifying Desktop Firefox binary")
            # we want to verify we do have a firefox binary
            # XXX so lame
            if not os.path.exists(self.firefox):
                if "MOZ_FETCHES_DIR" in os.environ:
                    target = os.path.join(os.environ["MOZ_FETCHES_DIR"], self.firefox)
                    if os.path.exists(target):
                        self.firefox = target

            if not os.path.exists(self.firefox):
                raise IOError("Cannot find %s" % self.firefox)

            mozversion.get_version(self.firefox)

        logger.info(os.environ)
        if self.archive:
            self.archive = os.path.abspath(self.archive)
            logger.info("Archives directory is %s" % self.archive)
            if not os.path.exists(self.archive):
                os.makedirs(self.archive, exist_ok=True)

        if shutil.which(self.geckodriver) is None and not os.path.exists(
            self.geckodriver
        ):
            raise IOError("Cannot find %s" % self.geckodriver)

        if not self.skip_logs:
            try:
                if self.android:
                    plat = "%s-%s" % (
                        self.device_name,
                        self.firefox.split("org.mozilla.")[-1],
                    )
                else:
                    plat = get_current_platform()
                self.changelog = read_changelog(plat)
                logger.info("Got the changelog from TaskCluster")
            except ProfileNotFoundError:
                logger.info("changelog not found on TaskCluster, creating a local one.")
                self.changelog = Changelog(self.archive)
        else:
            self.changelog = []

    def _create_env(self):
        if self.android:
            klass = AndroidEnv
        else:
            klass = DesktopEnv

        return klass(
            self.profile, self.firefox, self.geckodriver, self.archive, self.device_name
        )

    def display_error(self, scenario, customization):
        logger.error("%s x %s failed." % (scenario, customization), exc_info=True)
        # TODO: this might avoid the exceptions that slip through in automation
        # if self.strict:
        #     raise

    async def one_run(self, scenario, customization):
        """Runs one single conditioned profile.

        Create an instance of the environment and run the ProfileCreator.
        """
        self.env = self._create_env()
        return await ProfileCreator(
            scenario,
            customization,
            self.archive,
            self.changelog,
            self.force_new,
            self.env,
            skip_logs=self.skip_logs,
            remote_test_root=self.remote_test_root,
        ).run(not self.visible)

    async def run_all(self):
        """Runs the conditioned profile builders"""
        if self.scenario != "all":
            selected_scenario = [self.scenario]
        else:
            selected_scenario = scenarii.keys()

        # this is the loop that generates all combinations of profile
        # for the current platform when "all" is selected
        res = []
        failures = 0
        for scenario in selected_scenario:
            if self.customization != "all":
                try:
                    res.append(await self.one_run(scenario, self.customization))
                except Exception:
                    failures += 1
                    self.display_error(scenario, self.customization)
            else:
                for customization in get_customizations():
                    logger.info("Customization %s" % customization)
                    try:
                        res.append(await self.one_run(scenario, customization))
                    except Exception:
                        failures += 1
                        self.display_error(scenario, customization)

        return failures, [one_res for one_res in res if one_res]

    def save(self):
        self.changelog.save(self.archive)


def run(
    archive,
    firefox=None,
    scenario="all",
    profile=None,
    customization="all",
    visible=False,
    archives_dir="/tmp/archives",
    force_new=False,
    strict=True,
    geckodriver="geckodriver",
    device_name=None,
):
    runner = Runner(
        profile, firefox, geckodriver, archive, device_name, strict, force_new, visible
    )

    runner.prepare(scenario, customization)
    loop = asyncio.get_event_loop()

    try:
        failures, results = loop.run_until_complete(runner.run_all())
        logger.info("Saving changelog in %s" % archive)
        runner.save()
        if failures > 0:
            raise Exception("At least one scenario failed")
    except Exception as e:
        raise e
    finally:
        loop.close()