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
5"""Utility functions for Talos"""
6from __future__ import absolute_import
7
8import os
9import platform
10import re
11from sys import stdout
12import urllib.parse
13import string
14import time
15
16try:
17    from mozlog import get_proxy_logger
18
19    LOG = get_proxy_logger()
20except ModuleNotFoundError:
21    LOG = stdout
22
23# directory of this file for use with interpolatePath()
24here = os.path.dirname(os.path.realpath(__file__))
25
26
27class Timer(object):
28    def __init__(self):
29        self._start_time = 0
30        self.start()
31
32    def start(self):
33        self._start_time = time.time()
34
35    def elapsed(self):
36        seconds = time.time() - self._start_time
37        return time.strftime("%H:%M:%S", time.gmtime(seconds))
38
39
40class TalosError(Exception):
41    "Errors found while running the talos harness."
42
43
44class TalosRegression(Exception):
45    """When a regression is detected at runtime, report it properly
46    Currently this is a simple definition so we can detect the class type
47    """
48
49
50class TalosCrash(Exception):
51    """Exception type where we want to report a crash and stay
52    compatible with tbpl while allowing us to continue on.
53
54    https://bugzilla.mozilla.org/show_bug.cgi?id=829734
55    """
56
57
58def interpolate(template, **kwargs):
59    """
60    Use string.Template to substitute variables in a string.
61
62    The placeholder ${talos} is always defined and will be replaced by the
63    folder containing this file (global variable 'here').
64
65    You can add placeholders using kwargs.
66    """
67    kwargs.setdefault("talos", here)
68    return string.Template(template).safe_substitute(**kwargs)
69
70
71def findall(string, token):
72    """find all occurences in a string"""
73    return [m.start() for m in re.finditer(re.escape(token), string)]
74
75
76def tokenize(string, start, end):
77    """
78    tokenize a string by start + end tokens,
79    returns parts and position of last token
80    """
81    assert end not in start, "End token '%s' is contained in start token '%s'" % (
82        end,
83        start,
84    )
85    assert start not in end, "Start token '%s' is contained in end token '%s'" % (
86        start,
87        end,
88    )
89    _start = findall(string, start)
90    _end = findall(string, end)
91    if not _start and not _end:
92        return [], -1
93    assert len(_start), "Could not find start token: '%s'" % start
94    assert len(_end), "Could not find end token: '%s'" % end
95    assert len(_start) == len(
96        _end
97    ), "Unmatched number of tokens found: '%s' (%d) vs '%s' (%d)" % (
98        start,
99        len(_start),
100        end,
101        len(_end),
102    )
103    for i in range(len(_start)):
104        assert _end[i] > _start[i], "End token '%s' occurs before start token '%s'" % (
105            end,
106            start,
107        )
108    parts = []
109    for i in range(len(_start)):
110        parts.append(string[_start[i] + len(start) : _end[i]])
111    return parts, _end[-1]
112
113
114def urlsplit(url, default_scheme="file"):
115    """front-end to urlparse.urlsplit"""
116
117    if "://" not in url:
118        url = "%s://%s" % (default_scheme, url)
119
120    if url.startswith("file://"):
121        # file:// URLs do not play nice with windows
122        # https://bugzilla.mozilla.org/show_bug.cgi?id=793875
123        return ["file", "", url[len("file://") :], "", ""]
124
125    # split the URL and return a list
126    return [i for i in urllib.parse.urlsplit(url)]
127
128
129def parse_pref(value):
130    """parse a preference value from a string"""
131    from mozprofile.prefs import Preferences
132
133    return Preferences.cast(value)
134
135
136def GenerateBrowserCommandLine(
137    browser_path, extra_args, profile_dir, url, profiling_info=None
138):
139    # TODO: allow for spaces in file names on Windows
140    command_args = [browser_path.strip()]
141
142    if platform.system() == "Darwin":
143        command_args.extend(["-foreground"])
144
145    if isinstance(extra_args, list):
146        command_args.extend(extra_args)
147
148    elif extra_args.strip():
149        command_args.extend([extra_args])
150
151    command_args.extend(["-profile", profile_dir])
152
153    if profiling_info:
154        # pageloader tests use a tpmanifest browser pref instead of passing in a manifest url
155        # profiling info is handled differently for pageloader vs non-pageloader (startup) tests
156        # for pageloader the profiling info was mirrored already in an env var; so here just
157        # need to setup profiling info for startup / non-pageloader tests
158        if url is not None:
159            # for non-pageloader/non-manifest tests the profiling info is added to the test url
160            if url.find("?") != -1:
161                url += "&" + urllib.parse.urlencode(profiling_info)
162            else:
163                url += "?" + urllib.parse.urlencode(profiling_info)
164            command_args.extend(url.split(" "))
165
166    # if there's a url i.e. startup test / non-manifest test, add it to the cmd line args
167    if url is not None:
168        command_args.extend(url.split(" "))
169
170    return command_args
171
172
173def run_in_debug_mode(browser_config):
174    if (
175        browser_config.get("debug")
176        or browser_config.get("debugger")
177        or browser_config.get("debugg_args")
178    ):
179        return True
180    return False
181