зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1651624 - Add a macOS layer r=sparky
Adds a macOS layer that provides the ability to mount DMGs on the fly. For instance Firefox's distribution. Differential Revision: https://phabricator.services.mozilla.com/D84162
This commit is contained in:
Родитель
6094408d95
Коммит
cb88926dd6
|
@ -82,21 +82,25 @@ class PerftestTests(MachCommandBase):
|
|||
reimporting modules and produce wrong coverage info.
|
||||
"""
|
||||
display = kw.pop("display", False)
|
||||
verbose = kw.pop("verbose", False)
|
||||
args = [self.virtualenv_manager.python_path, "-m", module] + list(args)
|
||||
sys.stdout.write("=> %s " % kw.pop("label", module))
|
||||
sys.stdout.flush()
|
||||
try:
|
||||
if verbose:
|
||||
sys.stdout.write("\nRunning %s\n" % " ".join(args))
|
||||
sys.stdout.flush()
|
||||
output = subprocess.check_output(args, stderr=subprocess.STDOUT)
|
||||
if display:
|
||||
print()
|
||||
sys.stdout.write("\n")
|
||||
for line in output.split(b"\n"):
|
||||
print(line.decode("utf8"))
|
||||
sys.stdout.write(line.decode("utf8") + "\n")
|
||||
sys.stdout.write("[OK]\n")
|
||||
sys.stdout.flush()
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
for line in e.output.split(b"\n"):
|
||||
print(line.decode("utf8"))
|
||||
sys.stdout.write(line.decode("utf8") + "\n")
|
||||
sys.stdout.write("[FAILED]\n")
|
||||
sys.stdout.flush()
|
||||
return False
|
||||
|
@ -125,6 +129,7 @@ class PerftestTests(MachCommandBase):
|
|||
from mozperftest.utils import install_package, temporary_env
|
||||
|
||||
skip_linters = kwargs.get("skip_linters", False)
|
||||
verbose = kwargs.get("verbose", False)
|
||||
|
||||
# include in sys.path all deps
|
||||
_setup_path()
|
||||
|
@ -198,7 +203,9 @@ class PerftestTests(MachCommandBase):
|
|||
options,
|
||||
tests,
|
||||
]
|
||||
assert self._run_python_script("coverage", *args, label="running tests")
|
||||
assert self._run_python_script(
|
||||
"coverage", *args, label="running tests", verbose=verbose
|
||||
)
|
||||
if run_coverage_check and not self._run_python_script(
|
||||
"coverage", "report", display=True
|
||||
):
|
||||
|
|
|
@ -5,15 +5,16 @@ from mozperftest.layers import Layers
|
|||
from mozperftest.system.proxy import ProxyRunner
|
||||
from mozperftest.system.android import AndroidDevice
|
||||
from mozperftest.system.profile import Profile
|
||||
from mozperftest.system.macos import MacosDevice
|
||||
|
||||
|
||||
def get_layers():
|
||||
return Profile, ProxyRunner, AndroidDevice
|
||||
return Profile, ProxyRunner, AndroidDevice, MacosDevice
|
||||
|
||||
|
||||
def pick_system(env, flavor, mach_cmd):
|
||||
if flavor in ("desktop-browser", "xpcshell"):
|
||||
return Layers(env, mach_cmd, (Profile, ProxyRunner,))
|
||||
return Layers(env, mach_cmd, (MacosDevice, Profile, ProxyRunner,))
|
||||
if flavor == "mobile-browser":
|
||||
return Layers(env, mach_cmd, (Profile, ProxyRunner, AndroidDevice))
|
||||
raise NotImplementedError(flavor)
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
# 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 platform
|
||||
import tempfile
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import os
|
||||
|
||||
from mozperftest.layers import Layer
|
||||
|
||||
|
||||
# Add here any option that might point to a DMG file we want to extract. The key
|
||||
# is name of the option and the value, the file in the DMG we want to use for
|
||||
# the option.
|
||||
POTENTIAL_DMGS = {"browsertime-binary": "Contents/MacOS/firefox"}
|
||||
|
||||
|
||||
class MacosDevice(Layer):
|
||||
"""Runs on macOS to mount DMGs if we see one.
|
||||
"""
|
||||
|
||||
name = "macos"
|
||||
activated = platform.system() == "Darwin"
|
||||
|
||||
def __init__(self, env, mach_cmd):
|
||||
super(MacosDevice, self).__init__(env, mach_cmd)
|
||||
self._tmp_dirs = []
|
||||
|
||||
def _run_process(self, args):
|
||||
p = subprocess.Popen(
|
||||
args,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True,
|
||||
)
|
||||
|
||||
stdout, stderr = p.communicate(timeout=15)
|
||||
if p.returncode != 0:
|
||||
raise subprocess.CalledProcessError(
|
||||
stdout=stdout, stderr=stderr, returncode=p.returncode
|
||||
)
|
||||
|
||||
return stdout
|
||||
|
||||
def extract_app(self, dmg, target):
|
||||
mount = Path(tempfile.mkdtemp())
|
||||
|
||||
# mounting the DMG with hdiutil
|
||||
cmd = f"hdiutil attach -nobrowse -mountpoint {str(mount)} {dmg}"
|
||||
try:
|
||||
self._run_process(cmd.split())
|
||||
except subprocess.CalledProcessError:
|
||||
self.error(f"Can't mount {dmg}")
|
||||
if mount.exists():
|
||||
shutil.rmtree(str(mount))
|
||||
raise
|
||||
|
||||
# browse the mounted volume, to look for the app.
|
||||
found = False
|
||||
try:
|
||||
for f in os.listdir(str(mount)):
|
||||
if not f.endswith(".app"):
|
||||
continue
|
||||
app = mount / f
|
||||
shutil.copytree(str(app), str(target))
|
||||
found = True
|
||||
break
|
||||
finally:
|
||||
try:
|
||||
self._run_process(f"hdiutil detach {str(mount)}".split())
|
||||
except subprocess.CalledProcessError as e: # noqa
|
||||
self.warning("Detach failed {e.stdout}")
|
||||
finally:
|
||||
if mount.exists():
|
||||
shutil.rmtree(str(mount))
|
||||
if not found:
|
||||
self.error(f"No app file found in {dmg}")
|
||||
raise IOError(dmg)
|
||||
|
||||
def run(self, metadata):
|
||||
# Each DMG is mounted, then we look for the .app
|
||||
# directory in it, which is copied in a directory
|
||||
# alongside the .dmg file. That directory
|
||||
# is removed during teardown.
|
||||
for option, path_in_dmg in POTENTIAL_DMGS.items():
|
||||
value = self.get_arg(option)
|
||||
|
||||
if value is None or not value.endswith(".dmg"):
|
||||
continue
|
||||
|
||||
self.info(f"Mounting {value}")
|
||||
dmg_file = Path(value)
|
||||
if not dmg_file.exists():
|
||||
raise FileNotFoundError(str(dmg_file))
|
||||
|
||||
# let's unpack the DMG in place...
|
||||
target = dmg_file.parent / dmg_file.name.split(".")[0]
|
||||
self._tmp_dirs.append(target)
|
||||
self.extract_app(dmg_file, target)
|
||||
|
||||
# ... find a specific file if needed ...
|
||||
path = target / path_in_dmg
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(str(path))
|
||||
|
||||
# ... and swap the browsertime argument
|
||||
self.info(f"Using {path} for {option}")
|
||||
self.env.set_arg(option, str(path))
|
||||
return metadata
|
||||
|
||||
def teardown(self):
|
||||
for dir in self._tmp_dirs:
|
||||
if dir.exists():
|
||||
shutil.rmtree(str(dir))
|
Двоичный файл не отображается.
|
@ -15,6 +15,7 @@ EXAMPLE_TESTS_DIR = os.path.join(HERE, "data", "samples")
|
|||
EXAMPLE_TEST = os.path.join(EXAMPLE_TESTS_DIR, "perftest_example.js")
|
||||
EXAMPLE_XPCSHELL_TEST = Path(EXAMPLE_TESTS_DIR, "test_xpcshell.js")
|
||||
BT_DATA = Path(HERE, "data", "browsertime-results", "browsertime.json")
|
||||
DMG = Path(HERE, "data", "firefox.dmg")
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
#!/usr/bin/env python
|
||||
import mozunit
|
||||
from unittest import mock
|
||||
import platform
|
||||
import subprocess
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
from mozperftest.tests.support import get_running_env, DMG
|
||||
from mozperftest.system.macos import MacosDevice
|
||||
|
||||
|
||||
def run_proc(*args, **kw):
|
||||
if args[0][1] == "attach":
|
||||
where = args[0][4]
|
||||
bindir = Path(where, "firefox.app", "Contents", "MacOS")
|
||||
os.makedirs(str(bindir))
|
||||
firefox_bin = bindir / "firefox"
|
||||
with firefox_bin.open("w") as f:
|
||||
f.write("OK")
|
||||
|
||||
|
||||
def mock_calls(test):
|
||||
# on macOS we don't mock the system calls
|
||||
# so we're mounting for real using hdiutil
|
||||
if platform.system() == "Darwin":
|
||||
return test
|
||||
|
||||
# on other platforms, we're unsing run_proc
|
||||
@mock.patch("mozperftest.system.macos.MacosDevice._run_process", new=run_proc)
|
||||
def wrapped(*args, **kw):
|
||||
return test(*args, **kw)
|
||||
|
||||
|
||||
@mock_calls
|
||||
def test_mount_dmg():
|
||||
mach_cmd, metadata, env = get_running_env(browsertime_binary=str(DMG))
|
||||
device = MacosDevice(env, mach_cmd)
|
||||
try:
|
||||
device.run(metadata)
|
||||
finally:
|
||||
device.teardown()
|
||||
|
||||
target = Path(DMG.parent, "firefox", "Contents", "MacOS", "firefox")
|
||||
assert env.get_arg("browsertime-binary") == str(target)
|
||||
|
||||
|
||||
def run_fail(cmd):
|
||||
def _run_fail(self, args):
|
||||
run_cmd = " ".join(args)
|
||||
if cmd not in run_cmd:
|
||||
run_proc(args)
|
||||
return
|
||||
raise subprocess.CalledProcessError(returncode=2, cmd=" ".join(args))
|
||||
|
||||
return _run_fail
|
||||
|
||||
|
||||
@mock.patch("mozperftest.system.macos.MacosDevice._run_process", new=run_fail("attach"))
|
||||
def test_attach_fails():
|
||||
mach_cmd, metadata, env = get_running_env(browsertime_binary=str(DMG))
|
||||
device = MacosDevice(env, mach_cmd)
|
||||
|
||||
with pytest.raises(subprocess.CalledProcessError):
|
||||
try:
|
||||
device.run(metadata)
|
||||
finally:
|
||||
device.teardown()
|
||||
|
||||
|
||||
@mock.patch("mozperftest.system.macos.MacosDevice._run_process", new=run_fail("detach"))
|
||||
def test_detach_fails():
|
||||
mach_cmd, metadata, env = get_running_env(browsertime_binary=str(DMG))
|
||||
device = MacosDevice(env, mach_cmd)
|
||||
# detaching will be swallowed
|
||||
try:
|
||||
device.run(metadata)
|
||||
finally:
|
||||
device.teardown()
|
||||
|
||||
target = Path(DMG.parent, "firefox", "Contents", "MacOS", "firefox")
|
||||
assert env.get_arg("browsertime-binary") == str(target)
|
||||
|
||||
|
||||
def test_no_op():
|
||||
mach_cmd, metadata, env = get_running_env(browsertime_binary="notadmg")
|
||||
device = MacosDevice(env, mach_cmd)
|
||||
device.run(metadata)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mozunit.main()
|
|
@ -25,18 +25,15 @@ domcount:
|
|||
cron: true
|
||||
run:
|
||||
command: >-
|
||||
mkdir -p ${MOZ_FETCHES_DIR}/firefox &&
|
||||
hdiutil attach -nobrowse -noautoopen -mountpoint ${MOZ_FETCHES_DIR}/firefox ${MOZ_FETCHES_DIR}/target.dmg &&
|
||||
mkdir -p $MOZ_FETCHES_DIR/../artifacts &&
|
||||
cd $MOZ_FETCHES_DIR &&
|
||||
python3 -m venv . &&
|
||||
bin/python3 python/mozperftest/mozperftest/runner.py
|
||||
browser/base/content/test/perftest_browser_xhtml_dom.js
|
||||
--browsertime-binary ${MOZ_FETCHES_DIR}/firefox/Firefox\ Nightly.app/Contents/MacOS/firefox
|
||||
--browsertime-binary ${MOZ_FETCHES_DIR}/target.dmg
|
||||
--browsertime-node ${MOZ_FETCHES_DIR}/node/bin/node
|
||||
--flavor desktop-browser
|
||||
--perfherder
|
||||
--perfherder-metrics name:totalDOMCount,unit:count name:panelMenuCount,unit:count name:lightDOMCount,unit:count name:lightDOMDetails,unit:count
|
||||
--browsertime-geckodriver ${MOZ_FETCHES_DIR}/geckodriver
|
||||
--output $MOZ_FETCHES_DIR/../artifacts &&
|
||||
hdiutil detach ${MOZ_FETCHES_DIR}/firefox -force
|
||||
--output $MOZ_FETCHES_DIR/../artifacts
|
||||
|
|
Загрузка…
Ссылка в новой задаче