зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
73f81b1c85
Коммит
cb92d64d4d
|
@ -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()
|
Загрузка…
Ссылка в новой задаче