зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1868144 - Introduce a new condprof-addons linter and condprof-addons-verify taskcluster task. r=sparky,jmaher,linter-reviewers,ahal
Differential Revision: https://phabricator.services.mozilla.com/D195434
This commit is contained in:
Родитель
4d4fc711b1
Коммит
95d082d369
|
@ -0,0 +1,85 @@
|
|||
CondProf Addons
|
||||
===============
|
||||
|
||||
CondProf Addons is a linter for condprof customization JSON files (see :searchfox:`testing/condprofile/condprof/customization`),
|
||||
it reports linting errors if:
|
||||
|
||||
- any of the addons required by the customization files (e.g. see :searchfox:`testing/condprofile/condprof/customization/webext.json`)
|
||||
is not found in the tar file fetched through the `firefox-addons` fetch task (see :searchfox:`taskcluster/ci/fetch/browsertime.yml`)
|
||||
- or the expected `firefox-addons` fetch task has not been found
|
||||
|
||||
Run Locally
|
||||
-----------
|
||||
|
||||
The mozlint integration of condprof-addons can be run using mach:
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
$ mach lint --linter condprof-addons <file paths>
|
||||
|
||||
Alternatively, if the ``--linter condprof-addons`` is omitted, the ``condprof-addons`` will still be selected automatically if
|
||||
any of the files paths passed explicitly is detected to be part of the condprof customization directory.
|
||||
|
||||
The ``condprof-addons`` will also be running automatically on ``mach lint --outgoing`` if there are customization files changes
|
||||
detected in the outgoing patches.
|
||||
|
||||
Run on Taskcluster
|
||||
------------------
|
||||
|
||||
The condprof-addons job shows up as ``misc(condprof-addons)`` in the linting job. It should run automatically if changes are made
|
||||
to condprof customization JSON files.
|
||||
|
||||
Fix reported errors
|
||||
-------------------
|
||||
|
||||
XPI file is missing from the firefox-addons.tar archive
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This linting errors is expected to be reported if the linter detected that a confprof customization file
|
||||
requires an addon but the related xpi filename is not included in the firefox-addons.tar file fetched
|
||||
through the `firefox-addons` fetch task (see :searchfox:`taskcluster/ci/fetch/browsertime.yml`).
|
||||
|
||||
If the patch or phabricator revision is not meant to be landed, but only used as a temporary patch
|
||||
pushed on try or only applied locally (e.g. to run the tp6/tp6m webextensions perftests with a given
|
||||
third party extension installed to gather some metrics and/or GeckoProfiler data), then it can be
|
||||
safely ignored.
|
||||
|
||||
On the contrary, if the patch or phabricator revision is meant to be landed on mozilla-central,
|
||||
the linting error have to be fixed before or along landing the change, either by:
|
||||
|
||||
- removing the addition to the customization file if it wasn't intended to include that addon to all runs
|
||||
of the tp6/tp6m webextensions perftests
|
||||
|
||||
- updating the `firefox-addons` fetch task as defined in :searchfox:`taskcluster/ci/fetch/browsertime.yml`
|
||||
by creating a pull request in the github repository where the asset is stored, and ask a review from
|
||||
a peer of the `#webextensions-reviewer` and `#perftests-reviewers` review groups.
|
||||
|
||||
firefox-addons taskcluster config 'add-prefix' attribute should be set to 'firefox-addons/'
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If this linting error is hit, then the `firefox-addons` task defined in :searchfox:`taskcluster/ci/fetch/browsertime.yml`
|
||||
is missing the `add-prefix` attribute or its value is not set to the expected 'firefox-addons/' subdir name.
|
||||
|
||||
This is enforced as a linting rule because when the condprof utility is going to build a conditioned profile
|
||||
for which some add-ons xpi files are expected to be sideloaded (see :searchfox:`testing/condprofile/condprof/customization.webext.json`),
|
||||
to avoid re-downloading the same xpi from a remote urls every time the conditioned profile is built on the build infrastructure
|
||||
(which for tp6/tp6m perftests will happen once per job being executed) condprof is going to look first if the expected xpi file
|
||||
names are already available in `$MOZ_FETCHES_DIR/firefox-addons`.
|
||||
|
||||
firefox-addons taskcluser fetch config section not found
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This linting error is hit if the linter does not find the expected `firefox-addons` task defined in :searchfox:`taskcluster/ci/fetch/browsertime.yml`
|
||||
or it is missing the expected `fetch` attribute.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
ConfProf Addons does not currently provide any configuration files.
|
||||
|
||||
Sources
|
||||
-------
|
||||
|
||||
* :searchfox:`Configuration (YAML) <tools/lint/condprof-addons/yml>`
|
||||
* :searchfox:`Source <tools/lint/condprof-addons/__init__.py>`
|
||||
* :searchfox:`Test <tools/lint/test/test_condprof_addons.py>`
|
|
@ -174,6 +174,7 @@ firefox-addons:
|
|||
fetch:
|
||||
type: static-url
|
||||
artifact-name: firefox-addons.tar.zst
|
||||
add-prefix: firefox-addons/
|
||||
url: https://github.com/mozilla/perf-automation/raw/c9e497eaa86f45538e3509120947215c6f97c95b/test_files/firefox-addons.tar
|
||||
sha256: 20372ff1d58fc33d1568f8922fe66e2e2e01c77663820344d2a364a8ddd68281
|
||||
size: 3584000
|
||||
|
|
|
@ -561,6 +561,22 @@ perfdocs-verify:
|
|||
- 'testing/perfdocs/**'
|
||||
- 'tools/lint/perfdocs.yml'
|
||||
|
||||
condprof-addons-verify:
|
||||
description: |
|
||||
Verifies all xpi files needed by condprof customization sideloading addons are
|
||||
included in the firefox-addons fetch task.
|
||||
treeherder:
|
||||
symbol: misc(condprof-addons)
|
||||
run:
|
||||
mach: |
|
||||
lint -v -l condprof-addons -f treeherder -f json:/builds/worker/mozlint.json
|
||||
fetches:
|
||||
fetch:
|
||||
- firefox-addons
|
||||
when:
|
||||
files-changed:
|
||||
- 'testing/condprofile/condprof/customization/*.json'
|
||||
- 'taskcluster/ci/fetch/browsertime.yml'
|
||||
|
||||
fluent-lint:
|
||||
description: Check for problems with Fluent files.
|
||||
|
|
|
@ -21,7 +21,7 @@ from requests.packages.urllib3.util.retry import Retry
|
|||
|
||||
from condprof import progress
|
||||
|
||||
TASK_CLUSTER = "TASK_ID" in os.environ.keys()
|
||||
TASK_CLUSTER = "MOZ_AUTOMATION" in os.environ.keys()
|
||||
DOWNLOAD_TIMEOUT = 30
|
||||
|
||||
|
||||
|
@ -122,7 +122,12 @@ def fresh_profile(profile, customization_data):
|
|||
extensions = []
|
||||
for name, url in customization_data["addons"].items():
|
||||
logger.info("Downloading addon %s" % name)
|
||||
extension = download_file(url, check_mozfetches=True)
|
||||
# When running on the CI, we expect the xpi files to have been
|
||||
# fetched by the firefox-addons fetch task dependency (see
|
||||
# taskcluster/ci/fetch/browsertime.yml) and the condprof-addons
|
||||
# linter enforces the content of the archive to be unpacked into
|
||||
# a subdirectory named "firefox-addons".
|
||||
extension = download_file(url, mozfetches_subdir="firefox-addons")
|
||||
extensions.append(extension)
|
||||
logger.info("Installing addons")
|
||||
new_profile.addons.install(extensions)
|
||||
|
@ -184,25 +189,26 @@ def check_exists(archive, server=None, all_types=False):
|
|||
return exists, resp.headers
|
||||
|
||||
|
||||
def check_mozfetches_dir(target):
|
||||
def check_mozfetches_dir(target, mozfetches_subdir):
|
||||
logger.info("Checking for existence of: %s in MOZ_FETCHES_DIR" % target)
|
||||
fetches = os.environ.get("MOZ_FETCHES_DIR")
|
||||
if fetches is None:
|
||||
return None
|
||||
fetches_target = os.path.join(fetches, target)
|
||||
fetches_target = os.path.join(fetches, mozfetches_subdir, target)
|
||||
if not os.path.exists(fetches_target):
|
||||
return None
|
||||
logger.info("Already fetched and available in MOZ_FETCHES_DIR: %s" % fetches_target)
|
||||
return fetches_target
|
||||
|
||||
|
||||
def download_file(url, target=None, check_mozfetches=False):
|
||||
def download_file(url, target=None, mozfetches_subdir=None):
|
||||
if target is None:
|
||||
target = url.split("/")[-1]
|
||||
|
||||
# check if the assets has been fetched through a taskgraph fetch task dependency.
|
||||
if check_mozfetches:
|
||||
filepath = check_mozfetches_dir(target)
|
||||
# check if the assets has been fetched through a taskgraph fetch task dependency
|
||||
# and already available in the MOZ_FETCHES_DIR passed as an additional parameter.
|
||||
if mozfetches_subdir is not None:
|
||||
filepath = check_mozfetches_dir(target, mozfetches_subdir)
|
||||
if filepath is not None:
|
||||
return filepath
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
condprof-addons:
|
||||
description: Lint condprof customizations json files sideloading addons
|
||||
include:
|
||||
- 'testing/condprofile/condprof/customization'
|
||||
exclude: []
|
||||
extensions: ['json']
|
||||
support-files: ['taskcluster/ci/fetch/browsertime.yml']
|
||||
type: structured_log
|
||||
payload: condprof-addons:lint
|
|
@ -0,0 +1,217 @@
|
|||
# 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/.
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import tarfile
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
import yaml
|
||||
from mozlint.pathutils import expand_exclusions
|
||||
|
||||
BROWSERTIME_FETCHES_PATH = Path("taskcluster/ci/fetch/browsertime.yml")
|
||||
CUSTOMIZATIONS_PATH = Path("testing/condprofile/condprof/customization/")
|
||||
DOWNLOAD_TIMEOUT = 30
|
||||
ERR_FETCH_TASK_MISSING = "firefox-addons taskcluster fetch config section not found"
|
||||
ERR_FETCH_TASK_ADDPREFIX = "firefox-addons taskcluster config 'add-prefix' attribute should be set to 'firefox-addons/'"
|
||||
ERR_FETCH_TASK_ARCHIVE = (
|
||||
"Error downloading or opening archive from firefox-addons taskcluster fetch url"
|
||||
)
|
||||
LINTER_NAME = "condprof-addons"
|
||||
MOZ_FETCHES_DIR = os.environ.get("MOZ_FETCHES_DIR")
|
||||
RULE_DESC = "condprof addons all listed in firefox-addons.tar fetched archive"
|
||||
MOZ_AUTOMATION = "MOZ_AUTOMATION" in os.environ
|
||||
|
||||
tempdir = tempfile.gettempdir()
|
||||
|
||||
|
||||
def lint(paths, config, logger, fix=None, **lintargs):
|
||||
filepaths = [Path(p) for p in expand_exclusions(paths, config, lintargs["root"])]
|
||||
|
||||
if len(filepaths) == 0:
|
||||
return
|
||||
|
||||
linter = CondprofAddonsLinter(topsrcdir=lintargs["root"], logger=logger)
|
||||
|
||||
for filepath in filepaths:
|
||||
linter.lint(filepath)
|
||||
|
||||
|
||||
class CondprofAddonsLinter:
|
||||
def __init__(self, topsrcdir, logger):
|
||||
self.topsrcdir = topsrcdir
|
||||
self.logger = logger
|
||||
self.BROWSERTIME_FETCHES_FULLPATH = Path(
|
||||
self.topsrcdir, BROWSERTIME_FETCHES_PATH
|
||||
)
|
||||
self.CUSTOMIZATIONS_FULLPATH = Path(self.topsrcdir, CUSTOMIZATIONS_PATH)
|
||||
self.tar_xpi_filenames = self.get_firefox_addons_tar_names()
|
||||
|
||||
def lint(self, filepath):
|
||||
data = self.read_json(filepath)
|
||||
|
||||
if "addons" not in data:
|
||||
return
|
||||
|
||||
for addon_key in data["addons"]:
|
||||
xpi_url = data["addons"][addon_key]
|
||||
xpi_filename = xpi_url.split("/")[-1]
|
||||
self.logger.info(f"Found addon {xpi_filename}")
|
||||
if xpi_filename not in self.tar_xpi_filenames:
|
||||
self.logger.lint_error(
|
||||
self.get_missing_xpi_msg(xpi_filename),
|
||||
lineno=0,
|
||||
column=None,
|
||||
path=str(filepath),
|
||||
linter=LINTER_NAME,
|
||||
rule=RULE_DESC,
|
||||
)
|
||||
|
||||
def get_missing_xpi_msg(self, xpi_filename):
|
||||
return f"{xpi_filename} is missing from the firefox-addons.tar archive"
|
||||
|
||||
def read_json(self, filepath):
|
||||
with filepath.open("r") as f:
|
||||
return json.load(f)
|
||||
|
||||
def read_yaml(self, filepath):
|
||||
with filepath.open("r") as f:
|
||||
return yaml.safe_load(f)
|
||||
|
||||
def download_firefox_addons_tar(self, firefox_addons_tar_url, tar_tmp_path):
|
||||
self.logger.info(f"Downloading {firefox_addons_tar_url} to {tar_tmp_path}")
|
||||
res = requests.get(
|
||||
firefox_addons_tar_url, stream=True, timeout=DOWNLOAD_TIMEOUT
|
||||
)
|
||||
res.raise_for_status()
|
||||
with tar_tmp_path.open("wb") as f:
|
||||
for chunk in res.iter_content(chunk_size=1024):
|
||||
if chunk is not None:
|
||||
f.write(chunk)
|
||||
f.flush()
|
||||
|
||||
def get_firefox_addons_tar_names(self):
|
||||
# Get firefox-addons fetch task config.
|
||||
browsertime_fetches = self.read_yaml(self.BROWSERTIME_FETCHES_FULLPATH)
|
||||
|
||||
if not (
|
||||
"firefox-addons" in browsertime_fetches
|
||||
and "fetch" in browsertime_fetches["firefox-addons"]
|
||||
):
|
||||
self.logger.lint_error(
|
||||
ERR_FETCH_TASK_MISSING,
|
||||
lineno=0,
|
||||
column=None,
|
||||
path=BROWSERTIME_FETCHES_PATH,
|
||||
linter=LINTER_NAME,
|
||||
rule=RULE_DESC,
|
||||
)
|
||||
return []
|
||||
|
||||
fetch_config = browsertime_fetches["firefox-addons"]["fetch"]
|
||||
|
||||
if not (
|
||||
"add-prefix" in fetch_config
|
||||
and fetch_config["add-prefix"] == "firefox-addons/"
|
||||
):
|
||||
self.logger.lint_error(
|
||||
ERR_FETCH_TASK_ADDPREFIX,
|
||||
lineno=0,
|
||||
column=None,
|
||||
path=BROWSERTIME_FETCHES_PATH,
|
||||
linter=LINTER_NAME,
|
||||
rule=RULE_DESC,
|
||||
)
|
||||
return []
|
||||
|
||||
firefox_addons_tar_url = fetch_config["url"]
|
||||
firefox_addons_tar_sha256 = fetch_config["sha256"]
|
||||
|
||||
tar_xpi_files = list()
|
||||
|
||||
# When running on the CI, try to retrieve the list of xpi files from the target MOZ_FETCHES_DIR
|
||||
# subdirectory instead of downloading the archive from the fetch url.
|
||||
if MOZ_AUTOMATION:
|
||||
fetches_path = (
|
||||
Path(MOZ_FETCHES_DIR) if MOZ_FETCHES_DIR is not None else None
|
||||
)
|
||||
if fetches_path is not None and fetches_path.exists():
|
||||
self.logger.info(
|
||||
"Detected MOZ_FETCHES_DIR, look for pre-downloaded firefox-addons fetch results"
|
||||
)
|
||||
# add-prefix presence and value has been enforced at the start of this method.
|
||||
fetches_addons = Path(fetches_path, "firefox-addons/")
|
||||
if fetches_addons.exists():
|
||||
self.logger.info(
|
||||
f"Retrieve list of xpi files from firefox-addons fetch result at {str(fetches_addons)}"
|
||||
)
|
||||
for xpi_path in fetches_addons.iterdir():
|
||||
if xpi_path.suffix == ".xpi":
|
||||
tar_xpi_files.append(xpi_path.name)
|
||||
return tar_xpi_files
|
||||
else:
|
||||
self.logger.warning(
|
||||
"No 'firefox-addons/' subdir found in MOZ_FETCHES_DIR"
|
||||
)
|
||||
|
||||
# Fallback to download the tar archive and retrieve the list of xpi file from it
|
||||
# (e.g. when linting the local changes on the developers environment).
|
||||
tar_tmp_path = Path(tempdir, "firefox-addons.tar")
|
||||
tar_tmp_ready = False
|
||||
|
||||
# If the firefox-addons.tar file is found in the tempdir, check if the
|
||||
# file hash matches, if it does then don't download it again.
|
||||
if tar_tmp_path.exists():
|
||||
tar_tmp_hash = hashlib.sha256()
|
||||
with tar_tmp_path.open("rb") as f:
|
||||
while chunk := f.read(1024):
|
||||
tar_tmp_hash.update(chunk)
|
||||
if tar_tmp_hash.hexdigest() == firefox_addons_tar_sha256:
|
||||
self.logger.info(
|
||||
f"Pre-downloaded file for {tar_tmp_path} found and sha256 matching"
|
||||
)
|
||||
tar_tmp_ready = True
|
||||
else:
|
||||
self.logger.info(
|
||||
f"{tar_tmp_path} sha256 does not match the fetch config"
|
||||
)
|
||||
|
||||
# If the file is not found or the hash doesn't match, download it from the fetch task url.
|
||||
if not tar_tmp_ready:
|
||||
try:
|
||||
self.download_firefox_addons_tar(firefox_addons_tar_url, tar_tmp_path)
|
||||
except requests.exceptions.HTTPError as http_err:
|
||||
self.logger.lint_error(
|
||||
f"{ERR_FETCH_TASK_ARCHIVE}, {str(http_err)}",
|
||||
lineno=0,
|
||||
column=None,
|
||||
path=BROWSERTIME_FETCHES_PATH,
|
||||
linter=LINTER_NAME,
|
||||
rule=RULE_DESC,
|
||||
)
|
||||
return []
|
||||
|
||||
# Retrieve and return the list of xpi file names.
|
||||
try:
|
||||
with tarfile.open(tar_tmp_path, "r") as tf:
|
||||
names = tf.getnames()
|
||||
for name in names:
|
||||
file_path = Path(name)
|
||||
if file_path.suffix == ".xpi":
|
||||
tar_xpi_files.append(file_path.name)
|
||||
except tarfile.ReadError as read_err:
|
||||
self.logger.lint_error(
|
||||
f"{ERR_FETCH_TASK_ARCHIVE}, {str(read_err)}",
|
||||
lineno=0,
|
||||
column=None,
|
||||
path=BROWSERTIME_FETCHES_PATH,
|
||||
linter=LINTER_NAME,
|
||||
rule=RULE_DESC,
|
||||
)
|
||||
return []
|
||||
|
||||
return tar_xpi_files
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
firefox-addons:
|
||||
description: "fixture for the expected firefox-addons.tar ci fetch config"
|
||||
fetch:
|
||||
type: static-url
|
||||
artifact-name: firefox-addons.tar.zst
|
||||
add-prefix: firefox-addons/
|
||||
url: https://localhost/fake-firefox-addons.tar
|
||||
sha256: 20372ff1d58fc33d1568f8922fe66e2e2e01c77663820344d2a364a8ddd68281
|
||||
size: 3584000
|
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -0,0 +1 @@
|
|||
{}
|
Двоичный файл не отображается.
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"addons": {
|
||||
"non-existing": "http://localhost/non-existing.xpi"
|
||||
}
|
||||
}
|
|
@ -10,6 +10,8 @@ requirements = "tools/lint/python/black_requirements.txt"
|
|||
|
||||
["test_codespell.py"]
|
||||
|
||||
["test_condprof_addons.py"]
|
||||
|
||||
["test_eslint.py"]
|
||||
skip-if = ["os == 'win'"] # busts the tree for subsequent tasks on the same worker (bug 1708591)
|
||||
# Setup conflicts with stylelint setup so this should run sequentially.
|
||||
|
|
|
@ -0,0 +1,285 @@
|
|||
import importlib
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
import mozunit
|
||||
import requests
|
||||
|
||||
LINTER = "condprof-addons"
|
||||
|
||||
|
||||
def linter_module_mocks(
|
||||
customizations_path=".", browsertime_fetches_path="browsertime.yml", **othermocks
|
||||
):
|
||||
return mock.patch.multiple(
|
||||
LINTER,
|
||||
CUSTOMIZATIONS_PATH=Path(customizations_path),
|
||||
BROWSERTIME_FETCHES_PATH=Path(browsertime_fetches_path),
|
||||
**othermocks,
|
||||
)
|
||||
|
||||
|
||||
def linter_class_mocks(**mocks):
|
||||
return mock.patch.multiple(
|
||||
f"{LINTER}.CondprofAddonsLinter",
|
||||
**mocks,
|
||||
)
|
||||
|
||||
|
||||
# Sanity check (make sure linter message includes the xpi filename).
|
||||
def test_get_missing_xpi_msg(lint, paths):
|
||||
condprof_addons = importlib.import_module("condprof-addons")
|
||||
with linter_class_mocks(
|
||||
get_firefox_addons_tar_names=mock.Mock(return_value=list()),
|
||||
):
|
||||
instance = condprof_addons.CondprofAddonsLinter(
|
||||
topsrcdir=paths()[0], logger=mock.Mock()
|
||||
)
|
||||
assert instance.get_missing_xpi_msg("test.xpi").startswith(
|
||||
"test.xpi is missing"
|
||||
)
|
||||
|
||||
|
||||
def test_xpi_missing_from_firefox_addons_tar(lint, paths):
|
||||
fixture_customizations = paths("with-missing-xpi.json")
|
||||
with linter_module_mocks(), linter_class_mocks(
|
||||
get_firefox_addons_tar_names=mock.Mock(return_value=list()),
|
||||
):
|
||||
logger_mock = mock.Mock()
|
||||
lint(fixture_customizations, logger=logger_mock)
|
||||
assert logger_mock.lint_error.call_count == 1
|
||||
assert Path(fixture_customizations[0]).samefile(
|
||||
logger_mock.lint_error.call_args.kwargs["path"]
|
||||
)
|
||||
importlib.import_module("condprof-addons")
|
||||
assert "non-existing.xpi" in logger_mock.lint_error.call_args.args[0]
|
||||
|
||||
|
||||
def test_xpi_all_found_in_firefox_addons_tar(lint, paths):
|
||||
get_tarnames_mock = mock.Mock(
|
||||
return_value=["an-extension.xpi", "another-extension.xpi"]
|
||||
)
|
||||
read_json_mock = mock.Mock(
|
||||
return_value={
|
||||
"addons": {
|
||||
"an-extension": "http://localhost/ext/an-extension.xpi",
|
||||
"another-extension": "http://localhost/ext/another-extension.xpi",
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
with linter_module_mocks(), linter_class_mocks(
|
||||
get_firefox_addons_tar_names=get_tarnames_mock, read_json=read_json_mock
|
||||
):
|
||||
logger_mock = mock.Mock()
|
||||
# Compute a fake condprof customization path, the content is
|
||||
# going to be the read_json_mock.return_value and so the
|
||||
# fixture file does not actually exists.
|
||||
fixture_customizations = paths("fake-condprof-config.json")
|
||||
lint(
|
||||
fixture_customizations,
|
||||
logger=logger_mock,
|
||||
config={"include": paths(), "extensions": ["json", "yml"]},
|
||||
)
|
||||
assert read_json_mock.call_count == 1
|
||||
assert get_tarnames_mock.call_count == 1
|
||||
assert logger_mock.lint_error.call_count == 0
|
||||
|
||||
|
||||
def test_lint_error_on_missing_or_invalid_firefoxaddons_fetch_task(
|
||||
lint,
|
||||
paths,
|
||||
):
|
||||
read_json_mock = mock.Mock(return_value=dict())
|
||||
read_yaml_mock = mock.Mock(return_value=dict())
|
||||
# Verify that an explicit linter error is reported if the fetch task is not found.
|
||||
with linter_module_mocks(), linter_class_mocks(
|
||||
read_json=read_json_mock, read_yaml=read_yaml_mock
|
||||
):
|
||||
logger_mock = mock.Mock()
|
||||
fixture_customizations = paths("fake-condprof-config.json")
|
||||
condprof_addons = importlib.import_module("condprof-addons")
|
||||
|
||||
def assert_linter_error(yaml_mock_value, expected_msg):
|
||||
logger_mock.reset_mock()
|
||||
read_yaml_mock.return_value = yaml_mock_value
|
||||
lint(fixture_customizations, logger=logger_mock)
|
||||
assert logger_mock.lint_error.call_count == 1
|
||||
expected_path = condprof_addons.BROWSERTIME_FETCHES_PATH
|
||||
assert logger_mock.lint_error.call_args.kwargs["path"] == expected_path
|
||||
assert logger_mock.lint_error.call_args.args[0] == expected_msg
|
||||
|
||||
# Mock a yaml file that is not including the expected firefox-addons fetch task.
|
||||
assert_linter_error(
|
||||
yaml_mock_value=dict(), expected_msg=condprof_addons.ERR_FETCH_TASK_MISSING
|
||||
)
|
||||
# Mock a yaml file where firefox-addons is missing the fetch attribute.
|
||||
assert_linter_error(
|
||||
yaml_mock_value={"firefox-addons": {}},
|
||||
expected_msg=condprof_addons.ERR_FETCH_TASK_MISSING,
|
||||
)
|
||||
# Mock a yaml file where firefox-addons add-prefix is missing.
|
||||
assert_linter_error(
|
||||
yaml_mock_value={"firefox-addons": {"fetch": {}}},
|
||||
expected_msg=condprof_addons.ERR_FETCH_TASK_ADDPREFIX,
|
||||
)
|
||||
# Mock a yaml file where firefox-addons add-prefix is invalid.
|
||||
assert_linter_error(
|
||||
yaml_mock_value={
|
||||
"firefox-addons": {"fetch": {"add-prefix": "invalid-subdir-name/"}}
|
||||
},
|
||||
expected_msg=condprof_addons.ERR_FETCH_TASK_ADDPREFIX,
|
||||
)
|
||||
|
||||
|
||||
def test_get_xpi_list_from_fetch_dir(lint, paths):
|
||||
# Verify that when executed on the CI, the helper method looks for the xpi files
|
||||
# in the MOZ_FETCHES_DIR subdir where they are expected to be unpacked by the
|
||||
# fetch task.
|
||||
with linter_module_mocks(
|
||||
MOZ_AUTOMATION=1, MOZ_FETCHES_DIR=paths("fake-fetches-dir")[0]
|
||||
):
|
||||
condprof_addons = importlib.import_module("condprof-addons")
|
||||
logger_mock = mock.Mock()
|
||||
Path(paths("browsertime.yml")[0])
|
||||
|
||||
linter = condprof_addons.CondprofAddonsLinter(
|
||||
topsrcdir=paths()[0], logger=logger_mock
|
||||
)
|
||||
results = linter.tar_xpi_filenames
|
||||
|
||||
results.sort()
|
||||
assert results == ["fake-ext-01.xpi", "fake-ext-02.xpi"]
|
||||
|
||||
|
||||
def test_get_xpi_list_from_downloaded_tar(lint, paths):
|
||||
def mocked_download_tar(firefox_addons_tar_url, tar_tmp_path):
|
||||
tar_tmp_path.write_bytes(Path(paths("firefox-addons-fake.tar")[0]).read_bytes())
|
||||
|
||||
download_firefox_addons_tar_mock = mock.Mock()
|
||||
download_firefox_addons_tar_mock.side_effect = mocked_download_tar
|
||||
|
||||
# Verify that when executed locally on a developer machine, the tar archive is downloaded
|
||||
# and the list of xpi files included in it returned by the helper method.
|
||||
with tempfile.TemporaryDirectory() as tempdir, linter_module_mocks(
|
||||
MOZ_AUTOMATION=0,
|
||||
tempdir=tempdir,
|
||||
), linter_class_mocks(
|
||||
download_firefox_addons_tar=download_firefox_addons_tar_mock,
|
||||
):
|
||||
condprof_addons = importlib.import_module("condprof-addons")
|
||||
logger_mock = mock.Mock()
|
||||
Path(paths("browsertime.yml")[0])
|
||||
|
||||
linter = condprof_addons.CondprofAddonsLinter(
|
||||
topsrcdir=paths()[0], logger=logger_mock
|
||||
)
|
||||
results = linter.tar_xpi_filenames
|
||||
assert len(results) > 0
|
||||
print("List of addons found in the downloaded file archive:", results)
|
||||
assert all(filename.endswith(".xpi") for filename in results)
|
||||
assert download_firefox_addons_tar_mock.call_count == 1
|
||||
|
||||
|
||||
@mock.patch("requests.get")
|
||||
def test_error_on_downloading_tar(requests_get_mock, lint, paths):
|
||||
# Verify that when executed locally and the tar archive fails to download
|
||||
# the linter does report an explicit linting error with the http error included.
|
||||
with tempfile.TemporaryDirectory() as tempdir, linter_module_mocks(
|
||||
MOZ_AUTOMATION=0, tempdir=tempdir
|
||||
):
|
||||
condprof_addons = importlib.import_module("condprof-addons")
|
||||
logger_mock = mock.Mock()
|
||||
response_mock = mock.Mock()
|
||||
response_mock.raise_for_status.side_effect = requests.exceptions.HTTPError(
|
||||
"MOCK_ERROR"
|
||||
)
|
||||
requests_get_mock.return_value = response_mock
|
||||
Path(paths("browsertime.yml")[0])
|
||||
|
||||
linter = condprof_addons.CondprofAddonsLinter(
|
||||
topsrcdir=paths()[0], logger=logger_mock
|
||||
)
|
||||
|
||||
assert (
|
||||
logger_mock.lint_error.call_args.kwargs["path"]
|
||||
== condprof_addons.BROWSERTIME_FETCHES_PATH
|
||||
)
|
||||
assert (
|
||||
logger_mock.lint_error.call_args.args[0]
|
||||
== f"{condprof_addons.ERR_FETCH_TASK_ARCHIVE}, MOCK_ERROR"
|
||||
)
|
||||
assert requests_get_mock.call_count == 1
|
||||
assert len(linter.tar_xpi_filenames) == 0
|
||||
|
||||
|
||||
@mock.patch("requests.get")
|
||||
def test_error_on_opening_tar(requests_get_mock, lint, paths):
|
||||
# Verify that when executed locally and the tar archive fails to open
|
||||
# the linter does report an explicit linting error with the tarfile error included.
|
||||
with tempfile.TemporaryDirectory() as tempdir, linter_module_mocks(
|
||||
MOZ_AUTOMATION=0, tempdir=tempdir
|
||||
):
|
||||
condprof_addons = importlib.import_module("condprof-addons")
|
||||
logger_mock = mock.Mock()
|
||||
response_mock = mock.Mock()
|
||||
response_mock.raise_for_status.return_value = None
|
||||
|
||||
def mock_iter_content(chunk_size):
|
||||
yield b"fake tar content"
|
||||
yield b"expected to trigger tarfile.ReadError"
|
||||
|
||||
response_mock.iter_content.side_effect = mock_iter_content
|
||||
requests_get_mock.return_value = response_mock
|
||||
Path(paths("browsertime.yml")[0])
|
||||
|
||||
linter = condprof_addons.CondprofAddonsLinter(
|
||||
topsrcdir=paths()[0], logger=logger_mock
|
||||
)
|
||||
|
||||
assert (
|
||||
logger_mock.lint_error.call_args.kwargs["path"]
|
||||
== condprof_addons.BROWSERTIME_FETCHES_PATH
|
||||
)
|
||||
actual_msg = logger_mock.lint_error.call_args.args[0]
|
||||
print("Got linter error message:", actual_msg)
|
||||
assert actual_msg.startswith(
|
||||
f"{condprof_addons.ERR_FETCH_TASK_ARCHIVE}, file could not be opened successfully"
|
||||
)
|
||||
assert requests_get_mock.call_count == 1
|
||||
assert len(linter.tar_xpi_filenames) == 0
|
||||
|
||||
|
||||
def test_lint_all_customization_files_when_linting_browsertime_yml(
|
||||
lint,
|
||||
paths,
|
||||
):
|
||||
get_tarnames_mock = mock.Mock(return_value=["an-extension.xpi"])
|
||||
read_json_mock = mock.Mock(
|
||||
return_value={
|
||||
"addons": {"an-extension": "http://localhost/ext/an-extension.xpi"}
|
||||
}
|
||||
)
|
||||
with linter_module_mocks(
|
||||
customizations_path="fake-customizations-dir",
|
||||
), linter_class_mocks(
|
||||
get_firefox_addons_tar_names=get_tarnames_mock,
|
||||
read_json=read_json_mock,
|
||||
):
|
||||
logger_mock = mock.Mock()
|
||||
importlib.import_module("condprof-addons")
|
||||
# When mozlint detects a change to the ci fetch browser.yml support file,
|
||||
# condprof-addons linter is called for the entire customizations dir path
|
||||
# and we expect that to be expanded to the list of the json customizations
|
||||
# files from that directory path.
|
||||
lint(paths("fake-customizations-dir"), logger=logger_mock)
|
||||
# Expect read_json_mock to be called once per each of the json files
|
||||
# found in the fixture dir.
|
||||
assert read_json_mock.call_count == 3
|
||||
assert get_tarnames_mock.call_count == 1
|
||||
assert logger_mock.lint_error.call_count == 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mozunit.main()
|
Загрузка…
Ссылка в новой задаче