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/.
4from __future__ import absolute_import
5
6import json
7import tempfile
8import time
9import pathlib
10
11from mozperftest.test.browsertime import add_options
12from mozperftest.system.android import _ROOT_URL
13from mozperftest.utils import (
14    download_file,
15    get_multi_tasks_url,
16    get_revision_namespace_url,
17    install_package,
18)
19
20# We specifically select this URL because:
21# - we access Mozilla URLs in tests so any connections to Mozilla URLs may re-use
22# existing connections and have different perf characteristics. We can mostly
23# avoid this problem by using a non-Mozilla URL
24# - we authored the site so we can guarantee it doesn't change or change it if
25# needed
26# - we're not directing traffic to a site we don't own
27URL = "'https://mozilla-mobile.github.io/perf-tools/mozperftest-test-page.html'"
28
29COMMON_OPTIONS = [
30    ("processStartTime", "true"),
31    ("firefox.disableBrowsertimeExtension", "true"),
32    ("firefox.android.intentArgument", "'-a'"),
33    ("firefox.android.intentArgument", "'android.intent.action.VIEW'"),
34    ("firefox.android.intentArgument", "'-d'"),
35    ("firefox.android.intentArgument", URL),
36]
37
38NIGHTLY_SIM_ROUTE = "mobile.v2.fenix.nightly-simulation"
39ROUTE_SUFFIX = "artifacts/public/build/{architecture}/target.apk"
40
41build_generator = None
42
43
44def before_iterations(kw):
45    global build_generator
46
47    install_list = kw.get("android_install_apk")
48    if len(install_list) == 0 or all(
49        ["fenix_nightlysim_multicommit" not in apk for apk in install_list]
50    ):
51        return
52
53    # Install gitpython
54    install_package(kw["virtualenv"], "gitpython==3.1.0")
55    import git
56
57    class _GitProgress(git.RemoteProgress):
58        def update(self, op_code, cur_count, max_count=None, message=""):
59            if message:
60                print(message)
61
62    # Setup the local fenix github repo
63    print("Cloning fenix repo...")
64    fenix_repo = git.Repo.clone_from(
65        "https://github.com/mozilla-mobile/fenix",
66        tempfile.mkdtemp(),
67        branch="master",
68        progress=_GitProgress(),
69    )
70
71    # Get the builds to test
72    architecture = (
73        "arm64-v8a" if "arm64_v8a" in kw.get("android_install_apk") else "armeabi-v7a"
74    )
75    json_ = _fetch_json(
76        get_revision_namespace_url, NIGHTLY_SIM_ROUTE, day=kw["test_date"]
77    )
78    namespaces = json_["namespaces"]
79    revisions = [namespace["name"] for namespace in namespaces]
80
81    tasks = []
82    for revision in revisions:
83        try:
84            commit = fenix_repo.commit(revision)
85            name_rev = str(commit.name_rev)
86            if (
87                "remotes/origin" not in name_rev
88                or "release" in name_rev
89                or "tag" in name_rev
90            ):
91                print(
92                    "Commit %s is a release-branch commit, it won't be tested."
93                    % revision
94                )
95                continue
96
97            commitdate = commit.committed_date
98        except ValueError:
99            print("Commit %s is not from the Fenix master branch" % revision)
100            continue
101
102        json_ = _fetch_json(
103            get_multi_tasks_url, NIGHTLY_SIM_ROUTE, revision, day=kw["test_date"]
104        )
105        for task in json_["tasks"]:
106            route = task["namespace"]
107            task_architecture = route.split(".")[-1]
108            if task_architecture == architecture:
109                tasks.append(
110                    {
111                        "timestamp": commitdate,
112                        "revision": revision,
113                        "route": route,
114                        "route_suffix": ROUTE_SUFFIX.format(
115                            architecture=task_architecture
116                        ),
117                    }
118                )
119
120    # Set the number of test-iterations to the number of builds
121    kw["test_iterations"] = len(tasks)
122
123    def _build_iterator():
124        for task in tasks:
125            revision = task["revision"]
126            timestamp = task["timestamp"]
127
128            humandate = time.ctime(int(timestamp))
129            print(f"Testing revision {revision} from {humandate}")
130
131            download_url = f'{_ROOT_URL}{task["route"]}/{task["route_suffix"]}'
132            yield revision, timestamp, [download_url]
133
134    build_generator = _build_iterator()
135
136    return kw
137
138
139def _fetch_json(get_url_function, *args, **kwargs):
140    build_url = get_url_function(*args, **kwargs)
141    tmpfile = pathlib.Path(tempfile.mkdtemp(), "temp.json")
142    download_file(build_url, tmpfile)
143
144    with tmpfile.open() as f:
145        return json.load(f)
146
147
148def before_runs(env, **kw):
149    global build_generator
150
151    add_options(env, COMMON_OPTIONS)
152    if build_generator:
153        revision, timestamp, build = next(build_generator)
154        env.set_arg("android-install-apk", build)
155        env.set_arg("perfherder-prefix", revision)
156        env.set_arg("perfherder-timestamp", timestamp)
157