Files
Jonathan Schwender 1632e61ed6 servoshell: Rename executable to servoshell. (#42958)
This should help clarify the difference between servo the library /
engine and servoshell the browser (demo).

Other changes: 

- Removed etc/servo.sb ( [apple sandbox profile
format](https://angelica.gitbook.io/hacktricks/macos-hardening/macos-security-and-privilege-escalation/macos-security-protections/macos-sandbox#sandbox-profiles))
since it is not needed anymore. See [this
comment](https://github.com/servo/servo/pull/42958#discussion_r2876253489)
for more details.

Testing: This is a very invasive change, and there are bound to be
scripts / places I have overlooked. Searching for usages of `servo` is
very hard, since it's also the name of the library.
Try run: https://github.com/servo/servo/actions/runs/22637676818

---------

Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>
Signed-off-by: Jonathan Schwender <55576758+jschwe@users.noreply.github.com>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
2026-03-07 08:08:38 +00:00

218 lines
7.0 KiB
Python
Executable File

#!/usr/bin/env -S uv run --script
# Copyright 2025 The Servo Project Developers. See the COPYRIGHT
# file at the top-level directory of this distribution.
#
# Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
# http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
# <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
# option. This file may not be copied, modified, or distributed
# except according to those terms.
# /// script
# requires-python = ">=3.12"
# dependencies = ["selenium"]
# ///
import argparse
import json
import os
from pathlib import PurePath
import subprocess
import sys
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.options import ArgOptions
from urllib3.exceptions import ProtocolError, MaxRetryError
from selenium.common.exceptions import NoSuchElementException
from dataclasses import dataclass
from enum import Enum
import re
class AbortReason(Enum):
NotFound = 1
Panic = 2
# the time in seconds we wait at most
MAX_WAIT_TIME = 60
# tests that currently do not return results
IGNORE_TESTS = [
"tall-content-short-columns.html",
"tall-content-short-columns-realistic.html",
]
def create_driver(webdriver_port: int, timeout: int = 3) -> webdriver.Remote | None:
"""Create the webdriver connection."""
print("Trying to create driver")
options = ArgOptions()
options.set_capability("browserName", "servo")
driver = None
start_time = time.time()
while driver is None and time.time() - start_time < timeout:
try:
driver = webdriver.Remote(command_executor=f"http://127.0.0.1:{webdriver_port}", options=options)
except (ConnectionError, ProtocolError, MaxRetryError):
time.sleep(0.2)
except Exception as e:
print(f"Unexpected exception when creating webdriver: {e}, {type(e)}")
time.sleep(1)
print(
"Established Webdriver connection",
)
return driver
def start_servo(webdriver_port: int, servo_path: str) -> webdriver.Remote | None:
"""Start servo and create webdriver"""
try:
subprocess.Popen(
[
servo_path,
f"--webdriver={webdriver_port}",
]
)
except FileNotFoundError:
print("The servo binary does not exist")
return sys.exit(1)
return create_driver(webdriver_port)
def kill_servo():
subprocess.Popen(["killall", "servoshell"])
REGEX_RESULTS_LOG_ELEMENT = re.compile(
r"""
Time:\s+
values\s+[0-9.,\s]+(?P<unit>ms|runs\/s)\s+
avg\s+(?P<avg>[0-9.]+)\s+(?P=unit)\s+
median\s+[0-9.]+\s+(?P=unit)\s+
stdev\s+[0-9.]+\s+(?P=unit)\s+
min\s+(?P<min>[0-9.]+)\s+(?P=unit)\s+
max\s+(?P<max>[0-9.]+)\s+(?P=unit)
""",
re.VERBOSE,
)
@dataclass(frozen=True)
class TestResult:
value: float
lower_value: float
upper_value: float
unit: str
def test(s: str, driver: webdriver.Remote) -> TestResult | AbortReason:
"""Run a test by loading a website, and returning (avg, min, max).
This will run for MAX_WAIT_TIME seconds and return as soon as the avg line exists in the log element"""
print("Running: " + canonical_test_path(s, None))
try:
driver.get(s)
avg_line, min_line, max_line = None, None, None
for i in range(MAX_WAIT_TIME):
element = driver.find_element(By.ID, "log")
text = element.text
results_log_groups = REGEX_RESULTS_LOG_ELEMENT.search(text)
if results_log_groups:
avg_line = float(results_log_groups.group("avg"))
min_line = float(results_log_groups.group("min"))
max_line = float(results_log_groups.group("max"))
unit = results_log_groups.group("unit")
if avg_line is not None and min_line is not None and max_line is not None and unit is not None:
return TestResult(value=avg_line, lower_value=min_line, upper_value=max_line, unit=unit)
time.sleep(1)
except NoSuchElementException:
print("Could not find log?")
return AbortReason.NotFound
except Exception as e:
print(f"Some other exception for this test case: {e}")
return AbortReason.Panic
print("Wait for valid log element timed out. Could not find valid log.")
return AbortReason.NotFound
def canonical_test_path(filePath: str, prepend: str | None) -> str:
"""Make the filepath into just the directory and name"""
p = PurePath(filePath)
parts = p.parts
index = parts.index("perf_tests")
if prepend:
return prepend + "/" + "/".join(parts[index:])
else:
return "/".join(parts[index:])
def test_file(file_name: str) -> bool:
return ".html" in file_name and (file_name not in IGNORE_TESTS)
def write_file(results):
with open("results.json", "w") as f:
json.dump(results, f)
def oswalk_error(error: OSError):
print(error)
sys.exit(1)
def main():
parser = argparse.ArgumentParser(description="Run Blink Perf Tests on Servo Instance.")
parser.add_argument("servo_path", type=str, help="the servo binary")
parser.add_argument(
"-w", "--webdriver", default=7123, type=int, action="store", help="The webdriver port servo will listen on."
)
parser.add_argument(
"-p",
"--prepend",
action="store",
help="A value prepended to all results. Useful to distinguish between profiles.",
)
args = parser.parse_args()
webdriver = start_servo(args.webdriver, args.servo_path)
final_result = {}
time.sleep(2)
if webdriver:
webdriver.implicitly_wait(30)
for root, _, files in os.walk("tests/blink_perf_tests/perf_tests/layout", onerror=oswalk_error):
for file in files:
if test_file(file):
filePath = os.path.join(os.path.abspath(root), file)
result = test("file://" + filePath, webdriver)
if result == AbortReason.Panic:
print("Restarting servo")
start_servo(args.webdriver, args.servo_path)
elif result == AbortReason.NotFound:
pass
else:
combined_result = {}
combined_result["value"] = result.value
combined_result["lower_value"] = result.lower_value
combined_result["upper_value"] = result.upper_value
bencher_unit = None
if result.unit == "ms":
bencher_unit = "ms"
elif result.unit == "runs/s":
bencher_unit = "throughput"
else:
bencher_unit = "other"
final_result[canonical_test_path(filePath, args.prepend)] = {bencher_unit: combined_result}
print(final_result)
write_file(final_result)
kill_servo()
if __name__ == "__main__":
main()