Bug 1507272: Enable build telemetry for Mozilla devs by default r=firefox-build-system-reviewers,glandium

Check if a user is a Mozilla employee by checking their
Bugzilla groups, or checking if their VCS email ends
with "@mozilla.com".

When a user is setting up a new build environment, telemetry
will be automatically enabled for them if they are an
employee. If they're not an employee, they'll be asked
if they want to opt in.

Differential Revision: https://phabricator.services.mozilla.com/D106315
This commit is contained in:
Mitchell Hentges 2021-04-20 13:58:51 +00:00
Родитель 73f81b1c85
Коммит cb92d64d4d
13 изменённых файлов: 502 добавлений и 294 удалений

Просмотреть файл

@ -9,9 +9,8 @@ up to ``./mach``) has been configured to collect metrics data
points and errors for various build system actions. This data
helps drive team planning for the build team and ensure that
resources are applied to build processes that need them most.
You can opt-in to send telemetry to Mozilla during
``./mach bootstrap`` or by editing your ``.mozbuild/machrc``
file.
You can adjust your telemetry settings by editing your
``~/.mozbuild/machrc`` file.
Glean Telemetry
===============

Просмотреть файл

@ -4,27 +4,19 @@
from __future__ import absolute_import, unicode_literals
from mach.telemetry import NoopTelemetry
class CommandContext(object):
"""Holds run-time state so it can easily be passed to command providers."""
def __init__(
self,
cwd=None,
settings=None,
log_manager=None,
commands=None,
telemetry=NoopTelemetry(False),
**kwargs
self, cwd=None, settings=None, log_manager=None, commands=None, **kwargs
):
self.cwd = cwd
self.settings = settings
self.log_manager = log_manager
self.commands = commands
self.is_interactive = None # Filled in after args are parsed
self.telemetry = telemetry
self.telemetry = None # Filled in after args are parsed
self.command_attrs = {}
for k, v in kwargs.items():

Просмотреть файл

@ -34,7 +34,10 @@ from .dispatcher import CommandAction
from .logging import LoggingManager
from .registrar import Registrar
from .sentry import register_sentry, NoopErrorReporter
from .telemetry import report_invocation_metrics, create_telemetry_from_environment
from .telemetry import (
report_invocation_metrics,
create_telemetry_from_environment,
)
from .util import setenv, UserError
SUGGEST_MACH_BUSTED_TEMPLATE = r"""
@ -348,12 +351,6 @@ To see more help for a specific command, run:
self.settings.register_provider(provider)
self.load_settings(self.settings_paths)
if self.populate_context_handler:
topsrcdir = self.populate_context_handler("topdir")
sentry = register_sentry(argv, self.settings, topsrcdir)
else:
sentry = NoopErrorReporter()
if sys.version_info < (3, 0):
if stdin.encoding is None:
sys.stdin = codecs.getreader("utf-8")(stdin)
@ -371,7 +368,7 @@ To see more help for a specific command, run:
if os.isatty(orig_stdout.fileno()):
setenv("MACH_STDOUT_ISATTY", "1")
return self._run(argv, sentry)
return self._run(argv)
except KeyboardInterrupt:
print("mach interrupted by signal or user action. Stopping.")
return 1
@ -403,14 +400,19 @@ To see more help for a specific command, run:
sys.stdout = orig_stdout
sys.stderr = orig_stderr
def _run(self, argv, sentry):
telemetry = create_telemetry_from_environment(self.settings)
def _run(self, argv):
topsrcdir = None
if self.populate_context_handler:
topsrcdir = self.populate_context_handler("topdir")
sentry = register_sentry(argv, self.settings, topsrcdir)
else:
sentry = NoopErrorReporter()
context = CommandContext(
cwd=self.cwd,
settings=self.settings,
log_manager=self.log_manager,
commands=Registrar,
telemetry=telemetry,
)
if self.populate_context_handler:
@ -453,6 +455,8 @@ To see more help for a specific command, run:
and sys.__stderr__.isatty()
and not os.environ.get("MOZ_AUTOMATION", None)
)
context.telemetry = create_telemetry_from_environment(self.settings)
handler = getattr(args, "mach_handler")
report_invocation_metrics(context.telemetry, handler.name)

Просмотреть файл

@ -4,68 +4,29 @@
from __future__ import print_function, absolute_import
import json
import os
import sys
from mock import Mock
from six.moves import input, configparser
from textwrap import dedent
import requests
import six.moves.urllib.parse as urllib_parse
from mach.config import ConfigSettings
from mach.telemetry_interface import NoopTelemetry, GleanTelemetry
from mozboot.util import get_state_dir, get_mach_virtualenv_binary
from mozbuild.base import MozbuildObject, BuildEnvironmentNotFoundException
from mozbuild.settings import TelemetrySettings
from mozbuild.telemetry import filter_args
import mozpack.path
from mozversioncontrol import get_repository_object, InvalidRepoPath
MACH_METRICS_PATH = os.path.abspath(os.path.join(__file__, "..", "..", "metrics.yaml"))
class NoopTelemetry(object):
def __init__(self, failed_glean_import):
self._failed_glean_import = failed_glean_import
def metrics(self, metrics_path):
return Mock()
def submit(self, is_bootstrap):
if self._failed_glean_import and not is_bootstrap:
print(
"Glean could not be found, so telemetry will not be reported. "
"You may need to run |mach bootstrap|.",
file=sys.stderr,
)
class GleanTelemetry(object):
"""Records and sends Telemetry using Glean.
Metrics are defined in python/mozbuild/metrics.yaml.
Pings are defined in python/mozbuild/pings.yaml.
The "metrics" and "pings" properties may be replaced with no-op implementations if
Glean isn't available. This allows consumers to report telemetry without having
to guard against incompatible environments.
"""
def __init__(
self,
):
self._metrics_cache = {}
def metrics(self, metrics_path):
if metrics_path not in self._metrics_cache:
from glean import load_metrics
metrics = load_metrics(metrics_path)
self._metrics_cache[metrics_path] = metrics
return self._metrics_cache[metrics_path]
def submit(self, _):
from pathlib import Path
from glean import load_pings
pings = load_pings(Path(__file__).parent.parent / "pings.yaml")
pings.usage.submit()
def create_telemetry_from_environment(settings):
"""Creates and a Telemetry instance based on system details.
@ -142,7 +103,190 @@ def is_telemetry_enabled(settings):
if os.environ.get("DISABLE_TELEMETRY") == "1":
return False
return settings.mach_telemetry.is_enabled
def arcrc_path():
if sys.platform.startswith("win32") or sys.platform.startswith("msys"):
return os.path.join(os.environ.get("APPDATA", ""), ".arcrc")
else:
return os.path.expanduser("~/.arcrc")
def resolve_setting_from_arcconfig(topsrcdir, setting):
for arcconfig_path in [
os.path.join(topsrcdir, ".hg", ".arcconfig"),
os.path.join(topsrcdir, ".git", ".arcconfig"),
os.path.join(topsrcdir, ".arcconfig"),
]:
try:
with open(arcconfig_path, "r") as arcconfig_file:
arcconfig = json.load(arcconfig_file)
except (json.JSONDecodeError, FileNotFoundError):
continue
value = arcconfig.get(setting)
if value:
return value
def resolve_is_employee_by_credentials(topsrcdir):
phabricator_uri = resolve_setting_from_arcconfig(topsrcdir, "phabricator.uri")
if not phabricator_uri:
return None
try:
return settings.build.telemetry
except (AttributeError, KeyError):
return False
with open(arcrc_path(), "r") as arcrc_file:
arcrc = json.load(arcrc_file)
except (json.JSONDecodeError, FileNotFoundError):
return None
phabricator_token = (
arcrc.get("hosts", {})
.get(urllib_parse.urljoin(phabricator_uri, "api/"), {})
.get("token")
)
if not phabricator_token:
return None
bmo_uri = (
resolve_setting_from_arcconfig(topsrcdir, "bmo_url")
or "https://bugzilla.mozilla.org"
)
bmo_api_url = urllib_parse.urljoin(bmo_uri, "rest/whoami")
bmo_result = requests.get(
bmo_api_url, headers={"X-PHABRICATOR-TOKEN": phabricator_token}
)
return "mozilla-employee-confidential" in bmo_result.json().get("groups", [])
def resolve_is_employee_by_vcs(topsrcdir):
try:
vcs = get_repository_object(topsrcdir)
except InvalidRepoPath:
return None
email = vcs.get_user_email()
if not email:
return None
return "@mozilla.com" in email
def resolve_is_employee(topsrcdir):
"""Detect whether or not the current user is a Mozilla employee.
Checks using Bugzilla authentication, if possible. Otherwise falls back to checking
if email configured in VCS is "@mozilla.com".
Returns True if the user could be identified as an employee, False if the user
is confirmed as not being an employee, or None if the user couldn't be
identified.
"""
is_employee = resolve_is_employee_by_credentials(topsrcdir)
if is_employee is not None:
return is_employee
return resolve_is_employee_by_vcs(topsrcdir) or False
def record_telemetry_settings(
main_settings,
state_dir,
is_enabled,
):
# We want to update the user's machrc file. However, the main settings object
# contains config from "$topsrcdir/machrc" (if it exists) which we don't want
# to accidentally include. So, we have to create a brand new mozbuild-specific
# settings, update it, then write to it.
settings_path = os.path.join(state_dir, "machrc")
file_settings = ConfigSettings()
file_settings.register_provider(TelemetrySettings)
try:
file_settings.load_file(settings_path)
except configparser.Error as e:
print(
"Your mach configuration file at `{path}` cannot be parsed:\n{error}".format(
path=settings_path, error=e
)
)
return
file_settings.mach_telemetry.is_enabled = is_enabled
file_settings.mach_telemetry.is_set_up = True
with open(settings_path, "w") as f:
file_settings.write(f)
# Telemetry will want this elsewhere in the mach process, so we'll slap the
# new values on the main settings object.
main_settings.mach_telemetry.is_enabled = is_enabled
main_settings.mach_telemetry.is_set_up = True
TELEMETRY_DESCRIPTION_PREAMBLE = """
Mozilla collects data to improve the developer experience.
To learn more about the data we intend to collect, read here:
https://firefox-source-docs.mozilla.org/build/buildsystem/telemetry.html
If you have questions, please ask in #build on Matrix:
https://chat.mozilla.org/#/room/#build:mozilla.org
""".strip()
def print_telemetry_message_employee():
message_template = dedent(
"""
%s
As a Mozilla employee, telemetry has been automatically enabled.
"""
).strip()
print(message_template % TELEMETRY_DESCRIPTION_PREAMBLE)
return True
def prompt_telemetry_message_contributor():
while True:
prompt = (
dedent(
"""
%s
If you'd like to opt out of data collection, select (N) at the prompt.
Would you like to enable build system telemetry? (Yn): """
)
% TELEMETRY_DESCRIPTION_PREAMBLE
).strip()
choice = input(prompt)
choice = choice.strip().lower()
if choice == "":
return True
if choice not in ("y", "n"):
print("ERROR! Please enter y or n!")
else:
return choice == "y"
def initialize_telemetry_setting(settings, topsrcdir, state_dir):
"""Enables telemetry for employees or prompts the user."""
# If the user doesn't care about telemetry for this invocation, then
# don't make requests to Bugzilla and/or prompt for whether the
# user wants to opt-in.
if os.environ.get("DISABLE_TELEMETRY") == "1":
return
try:
is_employee = resolve_is_employee(topsrcdir)
except requests.exceptions.RequestException:
return
if is_employee:
is_enabled = True
print_telemetry_message_employee()
else:
is_enabled = prompt_telemetry_message_contributor()
record_telemetry_settings(settings, state_dir, is_enabled)

Просмотреть файл

@ -0,0 +1,60 @@
# 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/.
from __future__ import print_function, absolute_import
import sys
from mock import Mock
class NoopTelemetry(object):
def __init__(self, failed_glean_import):
self._failed_glean_import = failed_glean_import
def metrics(self, metrics_path):
return Mock()
def submit(self, is_bootstrap):
if self._failed_glean_import and not is_bootstrap:
print(
"Glean could not be found, so telemetry will not be reported. "
"You may need to run |mach bootstrap|.",
file=sys.stderr,
)
class GleanTelemetry(object):
"""Records and sends Telemetry using Glean.
Metrics are defined in python/mozbuild/metrics.yaml.
Pings are defined in python/mozbuild/pings.yaml.
The "metrics" and "pings" properties may be replaced with no-op implementations if
Glean isn't available. This allows consumers to report telemetry without having
to guard against incompatible environments.
Also tracks whether an employee was just automatically opted into telemetry
during this mach invocation.
"""
def __init__(
self,
):
self._metrics_cache = {}
def metrics(self, metrics_path):
if metrics_path not in self._metrics_cache:
from glean import load_metrics
metrics = load_metrics(metrics_path)
self._metrics_cache[metrics_path] = metrics
return self._metrics_cache[metrics_path]
def submit(self, _):
from pathlib import Path
from glean import load_pings
pings = load_pings(Path(__file__).parent.parent / "pings.yaml")
pings.usage.submit()

Просмотреть файл

@ -13,26 +13,9 @@ import sys
import subprocess
import time
from distutils.version import LooseVersion
from mozboot.util import get_mach_virtualenv_binary
from mozfile import which
# NOTE: This script is intended to be run with a vanilla Python install. We
# have to rely on the standard library instead of Python 2+3 helpers like
# the six module.
if sys.version_info < (3,):
from ConfigParser import (
Error as ConfigParserError,
RawConfigParser,
)
input = raw_input # noqa
else:
from configparser import (
Error as ConfigParserError,
RawConfigParser,
)
from mach.util import UserError
from mach.telemetry import initialize_telemetry_setting
from mozboot.base import MODERN_RUST_VERSION
from mozboot.centosfedora import CentOSFedoraBootstrapper
@ -136,23 +119,6 @@ lines:
Then restart your shell.
"""
TELEMETRY_OPT_IN_PROMPT = """
Build system telemetry
Mozilla collects data about local builds in order to make builds faster and
improve developer tooling. To learn more about the data we intend to collect
read here:
https://firefox-source-docs.mozilla.org/build/buildsystem/telemetry.html
If you have questions, please ask in #build on Matrix:
https://chat.mozilla.org/#/room/#build:mozilla.org
If you would like to opt out of data collection, select (N) at the prompt.
Would you like to enable build system telemetry?"""
OLD_REVISION_WARNING = """
WARNING! You appear to be running `mach bootstrap` from an old revision.
@ -172,32 +138,6 @@ performance.
""".strip()
def update_or_create_build_telemetry_config(path):
"""Write a mach config file enabling build telemetry to `path`. If the file does not exist,
create it. If it exists, add the new setting to the existing data.
This is standalone from mach's `ConfigSettings` so we can use it during bootstrap
without a source checkout.
"""
config = RawConfigParser()
if os.path.exists(path):
try:
config.read([path])
except ConfigParserError as e:
print(
"Your mach configuration file at `{path}` is not parseable:\n{error}".format(
path=path, error=e
)
)
return False
if not config.has_section("build"):
config.add_section("build")
config.set("build", "telemetry", "true")
with open(path, "w") as f:
config.write(f)
return True
class Bootstrapper(object):
"""Main class that performs system bootstrap."""
@ -330,36 +270,6 @@ class Bootstrapper(object):
self.instance.ensure_wasi_sysroot_packages(state_dir, checkout_root)
self.instance.ensure_dump_syms_packages(state_dir, checkout_root)
def check_telemetry_opt_in(self, state_dir):
# Don't prompt if the user already has a setting for this value.
if (
self.mach_context is not None
and "telemetry" in self.mach_context.settings.build
):
return self.mach_context.settings.build.telemetry
# We can't prompt the user.
if self.instance.no_interactive:
return False
mach_python = get_mach_virtualenv_binary()
proc = subprocess.run(
[mach_python, "-c", "import glean"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
# If we couldn't install glean in the mach environment, we can't
# enable telemetry.
if proc.returncode != 0:
return False
choice = self.instance.prompt_yesno(prompt=TELEMETRY_OPT_IN_PROMPT)
if choice:
cfg_file = os.environ.get("MACHRC", os.path.join(state_dir, "machrc"))
if update_or_create_build_telemetry_config(cfg_file):
print(
"\nThanks for enabling build telemetry! You can change this setting at "
+ "any time by editing the config file `{}`\n".format(cfg_file)
)
return choice
def check_code_submission(self, checkout_root):
if self.instance.no_interactive or which("moz-phab"):
return
@ -375,7 +285,7 @@ class Bootstrapper(object):
mach_binary = os.path.join(checkout_root, "mach")
subprocess.check_call((sys.executable, mach_binary, "install-moz-phab"))
def bootstrap(self):
def bootstrap(self, settings):
if sys.version_info[0] < 3:
print(
"This script must be run with Python 3. \n"
@ -432,7 +342,6 @@ class Bootstrapper(object):
if self.instance.no_system_changes:
self.instance.ensure_mach_environment(checkout_root)
self.check_telemetry_opt_in(state_dir)
self.maybe_install_private_packages_or_exit(state_dir, checkout_root)
self._output_mozconfig(application, mozconfig_builder)
sys.exit(0)
@ -475,9 +384,12 @@ class Bootstrapper(object):
which("git"), which("git-cinnabar"), state_dir, checkout_root
)
self.check_telemetry_opt_in(state_dir)
self.maybe_install_private_packages_or_exit(state_dir, checkout_root)
self.check_code_submission(checkout_root)
# Wait until after moz-phab setup to check telemetry so that employees
# will be automatically opted-in.
if not self.instance.no_interactive and not settings.mach_telemetry.is_set_up:
initialize_telemetry_setting(settings, checkout_root, state_dir)
print(FINISHED % name)
if not (

Просмотреть файл

@ -47,7 +47,7 @@ class Bootstrap(MachCommandBase):
no_system_changes=no_system_changes,
mach_context=self._mach_context,
)
bootstrapper.bootstrap()
bootstrapper.bootstrap(self.settings)
@CommandProvider

Просмотреть файл

@ -2,4 +2,3 @@
subsuite = mozbuild
[test_mozconfig.py]
[test_write_config.py]

Просмотреть файл

@ -1,107 +0,0 @@
# 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/.
from __future__ import absolute_import, print_function, unicode_literals
import mozunit
import pytest
from mach.config import ConfigSettings
from mach.decorators import SettingsProvider
from mozboot.bootstrap import update_or_create_build_telemetry_config
# Duplicated from python/mozbuild/mozbuild/mach_commands.py because we can't
# actually import that module here.
@SettingsProvider
class TelemetrySettings:
config_settings = [
(
"build.telemetry",
"boolean",
"""
Enable submission of build system telemetry.
""".strip(),
False,
),
]
@SettingsProvider
class OtherSettings:
config_settings = [
("foo.bar", "int", "", 1),
("build.abc", "string", "", ""),
]
def read(path):
s = ConfigSettings()
s.register_provider(TelemetrySettings)
s.register_provider(OtherSettings)
s.load_file(path)
return s
@pytest.fixture
def config_path(tmpdir):
return str(tmpdir.join("machrc"))
@pytest.fixture
def write_config(config_path):
def _config(contents):
with open(config_path, "w") as f:
f.write(contents)
return _config
def test_nonexistent(config_path):
update_or_create_build_telemetry_config(config_path)
s = read(config_path)
assert s.build.telemetry
def test_file_exists_no_build_section(config_path, write_config):
write_config(
"""[foo]
bar = 2
"""
)
update_or_create_build_telemetry_config(config_path)
s = read(config_path)
assert s.build.telemetry
assert s.foo.bar == 2
def test_existing_build_section(config_path, write_config):
write_config(
"""[foo]
bar = 2
[build]
abc = xyz
"""
)
update_or_create_build_telemetry_config(config_path)
s = read(config_path)
assert s.build.telemetry
assert s.build.abc == "xyz"
assert s.foo.bar == 2
def test_malformed_file(config_path, write_config):
"""Ensure that a malformed config file doesn't cause breakage."""
write_config(
"""[foo
bar = 1
"""
)
assert not update_or_create_build_telemetry_config(config_path)
# Can't read config, it will not have been written!
if __name__ == "__main__":
mozunit.main()

Просмотреть файл

@ -18,6 +18,7 @@ import sys
import tempfile
import time
import mozbuild.settings # noqa need @SettingsProvider hook to execute
import mozpack.path as mozpath
from mach.decorators import (
@ -2101,20 +2102,6 @@ class Repackage(MachCommandBase):
)
@SettingsProvider
class TelemetrySettings:
config_settings = [
(
"build.telemetry",
"boolean",
"""
Enable submission of build system telemetry.
""".strip(),
False,
),
]
@CommandProvider
class L10NCommands(MachCommandBase):
@Command(

Просмотреть файл

@ -0,0 +1,31 @@
# 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/.
from __future__ import absolute_import
from mach.decorators import SettingsProvider
@SettingsProvider
class TelemetrySettings:
config_settings = [
(
"build.telemetry",
"boolean",
"Enable submission of build system telemetry "
'(Deprecated, replaced by "telemetry.is_enabled")',
),
(
"mach_telemetry.is_enabled",
"boolean",
"Build system telemetry is allowed",
False,
),
(
"mach_telemetry.is_set_up",
"boolean",
"The telemetry setup workflow has been completed "
"(e.g.: user has been prompted to opt-in)",
False,
),
]

Просмотреть файл

@ -60,5 +60,7 @@ skip-if = python == 2
skip-if = os == "win" # normalizing pathnames doesn't work here yet
[test_telemetry.py]
skip-if = python == 2 && os == "mac"
[test_telemetry_settings.py]
skip-if = python == 2
[test_util.py]
[test_util_fileavoidwrite.py]

Просмотреть файл

@ -0,0 +1,185 @@
# 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/.
from __future__ import absolute_import, print_function, unicode_literals
import os
from unittest.mock import Mock
import pytest
import mock
import mozunit
import requests
from mach.config import ConfigSettings
from mach.decorators import SettingsProvider
from mach.telemetry import (
initialize_telemetry_setting,
record_telemetry_settings,
resolve_is_employee,
)
from mozbuild.settings import TelemetrySettings
@SettingsProvider
class OtherSettings:
config_settings = [
("foo.bar", "int", "", 1),
("build.abc", "string", "", ""),
]
def record_enabled_telemetry(mozbuild_path, settings):
record_telemetry_settings(
settings,
mozbuild_path,
True,
)
@pytest.fixture
def settings():
s = ConfigSettings()
s.register_provider(TelemetrySettings)
s.register_provider(OtherSettings)
return s
def load_settings_file(mozbuild_path, settings):
settings.load_file(os.path.join(mozbuild_path, "machrc"))
def write_config(mozbuild_path, contents):
with open(os.path.join(mozbuild_path, "machrc"), "w") as f:
f.write(contents)
def test_nonexistent(tmpdir, settings):
record_enabled_telemetry(tmpdir, settings)
load_settings_file(tmpdir, settings)
assert settings.mach_telemetry.is_enabled
def test_file_exists_no_build_section(tmpdir, settings):
write_config(
tmpdir,
"""[foo]
bar = 2
""",
)
record_enabled_telemetry(tmpdir, settings)
load_settings_file(tmpdir, settings)
assert settings.mach_telemetry.is_enabled
assert settings.foo.bar == 2
def test_existing_build_section(tmpdir, settings):
write_config(
tmpdir,
"""[foo]
bar = 2
[build]
abc = xyz
""",
)
record_enabled_telemetry(tmpdir, settings)
load_settings_file(tmpdir, settings)
assert settings.mach_telemetry.is_enabled
assert settings.build.abc == "xyz"
assert settings.foo.bar == 2
def test_malformed_file(tmpdir, settings):
"""Ensure that a malformed config file doesn't cause breakage."""
write_config(
tmpdir,
"""[foo
bar = 1
""",
)
record_enabled_telemetry(tmpdir, settings)
# Can't load_settings config, it will not have been written!
def _initialize_telemetry(settings, is_employee, contributor_prompt_response=None):
with mock.patch(
"mach.telemetry.resolve_is_employee", return_value=is_employee
), mock.patch(
"mach.telemetry.prompt_telemetry_message_contributor",
return_value=contributor_prompt_response,
) as prompt_mock, mock.patch(
"subprocess.run", return_value=Mock(returncode=0)
), mock.patch(
"mach.config.ConfigSettings"
):
initialize_telemetry_setting(settings, "", "")
return prompt_mock.call_count == 1
def test_initialize_new_contributor_deny_telemetry(settings):
did_prompt = _initialize_telemetry(settings, False, False)
assert did_prompt
assert not settings.mach_telemetry.is_enabled
assert settings.mach_telemetry.is_set_up
assert settings.mach_telemetry.is_done_first_time_setup
def test_initialize_new_contributor_allow_telemetry(settings):
did_prompt = _initialize_telemetry(settings, False, True)
assert did_prompt
assert settings.mach_telemetry.is_enabled
assert settings.mach_telemetry.is_set_up
assert settings.mach_telemetry.is_done_first_time_setup
def test_initialize_new_employee(settings):
did_prompt = _initialize_telemetry(settings, True)
assert not did_prompt
assert settings.mach_telemetry.is_enabled
assert settings.mach_telemetry.is_set_up
assert settings.mach_telemetry.is_done_first_time_setup
def test_initialize_noop_when_telemetry_disabled_env(monkeypatch):
monkeypatch.setenv("DISABLE_TELEMETRY", "1")
with mock.patch("mach.telemetry.record_telemetry_settings") as record_mock:
did_prompt = _initialize_telemetry(None, False)
assert record_mock.call_count == 0
assert not did_prompt
def test_initialize_noop_when_request_error(settings):
with mock.patch(
"mach.telemetry.resolve_is_employee",
side_effect=requests.exceptions.RequestException("Unlucky"),
), mock.patch("mach.telemetry.record_telemetry_settings") as record_mock:
initialize_telemetry_setting(None, None, None)
assert record_mock.call_count == 0
def test_resolve_is_employee():
def mock_and_run(is_employee_bugzilla, is_employee_vcs):
with mock.patch(
"mach.telemetry.resolve_is_employee_by_credentials",
return_value=is_employee_bugzilla,
), mock.patch(
"mach.telemetry.resolve_is_employee_by_vcs",
return_value=is_employee_vcs,
):
return resolve_is_employee(None)
assert not mock_and_run(False, False)
assert not mock_and_run(False, True)
assert not mock_and_run(False, None)
assert mock_and_run(True, False)
assert mock_and_run(True, True)
assert mock_and_run(True, None)
assert not mock_and_run(None, False)
assert mock_and_run(None, True)
if __name__ == "__main__":
mozunit.main()