mirror of
https://github.com/servo/servo.git
synced 2025-06-02 15:25:31 +00:00
Use ruff
to enforce python code formatting (#37117)
Requires servo/servo#37045 for deps and config. Testing: No need for tests to test tests. Fixes: servo/servo#37041 --------- Signed-off-by: zefr0x <zer0-x.7ty50@aleeas.com>
This commit is contained in:
parent
41ecfb53a1
commit
c96de69e80
67 changed files with 3021 additions and 3085 deletions
|
@ -17,27 +17,31 @@ import os
|
|||
def size(args):
|
||||
size = os.path.getsize(args.binary)
|
||||
print(size)
|
||||
with open(args.bmf_output, 'w', encoding='utf-8') as f:
|
||||
json.dump({
|
||||
args.variant: {
|
||||
'file-size': {
|
||||
'value': float(size),
|
||||
with open(args.bmf_output, "w", encoding="utf-8") as f:
|
||||
json.dump(
|
||||
{
|
||||
args.variant: {
|
||||
"file-size": {
|
||||
"value": float(size),
|
||||
}
|
||||
}
|
||||
}
|
||||
}, f, indent=4)
|
||||
},
|
||||
f,
|
||||
indent=4,
|
||||
)
|
||||
|
||||
|
||||
def merge(args):
|
||||
output: dict[str, object] = dict()
|
||||
for input_file in args.inputs:
|
||||
with open(input_file, 'r', encoding='utf-8') as f:
|
||||
with open(input_file, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
diff = set(data) & set(output)
|
||||
if diff:
|
||||
print("Duplicated keys:", diff)
|
||||
output = data | output
|
||||
|
||||
with open(args.bmf_output, 'w', encoding='utf-8') as f:
|
||||
with open(args.bmf_output, "w", encoding="utf-8") as f:
|
||||
json.dump(output, f, indent=4)
|
||||
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ TEST_CMD = [
|
|||
"--log-raw=-",
|
||||
# We run the content-security-policy test because it creates
|
||||
# cross-origin iframes, which are good for stress-testing pipelines
|
||||
"content-security-policy"
|
||||
"content-security-policy",
|
||||
]
|
||||
|
||||
# Note that there will probably be test failures caused
|
||||
|
@ -35,7 +35,7 @@ test_results = Popen(TEST_CMD, stdout=PIPE)
|
|||
any_crashes = False
|
||||
|
||||
for line in test_results.stdout:
|
||||
report = json.loads(line.decode('utf-8'))
|
||||
report = json.loads(line.decode("utf-8"))
|
||||
if report.get("action") == "process_output":
|
||||
print("{} - {}".format(report.get("thread"), report.get("data")))
|
||||
status = report.get("status")
|
||||
|
|
|
@ -12,35 +12,46 @@ import re
|
|||
import subprocess
|
||||
import sys
|
||||
|
||||
symbol_regex = re.compile(br"D \*UND\*\t(.*) (.*)$")
|
||||
allowed_symbols = frozenset([
|
||||
b'unshare',
|
||||
b'malloc_usable_size',
|
||||
b'__cxa_type_match',
|
||||
b'signal',
|
||||
b'tcgetattr',
|
||||
b'tcsetattr',
|
||||
b'__strncpy_chk2',
|
||||
b'rand',
|
||||
b'__read_chk',
|
||||
b'fesetenv',
|
||||
b'srand',
|
||||
b'abs',
|
||||
b'fegetenv',
|
||||
b'sigemptyset',
|
||||
b'AHardwareBuffer_allocate',
|
||||
b'AHardwareBuffer_release',
|
||||
b'getentropy',
|
||||
])
|
||||
symbol_regex = re.compile(rb"D \*UND\*\t(.*) (.*)$")
|
||||
allowed_symbols = frozenset(
|
||||
[
|
||||
b"unshare",
|
||||
b"malloc_usable_size",
|
||||
b"__cxa_type_match",
|
||||
b"signal",
|
||||
b"tcgetattr",
|
||||
b"tcsetattr",
|
||||
b"__strncpy_chk2",
|
||||
b"rand",
|
||||
b"__read_chk",
|
||||
b"fesetenv",
|
||||
b"srand",
|
||||
b"abs",
|
||||
b"fegetenv",
|
||||
b"sigemptyset",
|
||||
b"AHardwareBuffer_allocate",
|
||||
b"AHardwareBuffer_release",
|
||||
b"getentropy",
|
||||
]
|
||||
)
|
||||
actual_symbols = set()
|
||||
|
||||
objdump_output = subprocess.check_output([
|
||||
os.path.join(
|
||||
'android-toolchains', 'ndk', 'toolchains', 'arm-linux-androideabi-4.9',
|
||||
'prebuilt', 'linux-x86_64', 'bin', 'arm-linux-androideabi-objdump'),
|
||||
'-T',
|
||||
'target/android/armv7-linux-androideabi/debug/libservoshell.so']
|
||||
).split(b'\n')
|
||||
objdump_output = subprocess.check_output(
|
||||
[
|
||||
os.path.join(
|
||||
"android-toolchains",
|
||||
"ndk",
|
||||
"toolchains",
|
||||
"arm-linux-androideabi-4.9",
|
||||
"prebuilt",
|
||||
"linux-x86_64",
|
||||
"bin",
|
||||
"arm-linux-androideabi-objdump",
|
||||
),
|
||||
"-T",
|
||||
"target/android/armv7-linux-androideabi/debug/libservoshell.so",
|
||||
]
|
||||
).split(b"\n")
|
||||
|
||||
for line in objdump_output:
|
||||
m = symbol_regex.search(line)
|
||||
|
|
|
@ -16,43 +16,47 @@ SCRIPT_PATH = os.path.split(__file__)[0]
|
|||
|
||||
|
||||
def main():
|
||||
default_output_dir = os.path.join(SCRIPT_PATH, 'output')
|
||||
default_cache_dir = os.path.join(SCRIPT_PATH, '.cache')
|
||||
default_output_dir = os.path.join(SCRIPT_PATH, "output")
|
||||
default_cache_dir = os.path.join(SCRIPT_PATH, ".cache")
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Download buildbot metadata"
|
||||
parser = argparse.ArgumentParser(description="Download buildbot metadata")
|
||||
parser.add_argument(
|
||||
"--index-url",
|
||||
type=str,
|
||||
default="https://build.servo.org/json",
|
||||
help="the URL to get the JSON index data index from. Default: https://build.servo.org/json",
|
||||
)
|
||||
parser.add_argument("--index-url",
|
||||
type=str,
|
||||
default='https://build.servo.org/json',
|
||||
help="the URL to get the JSON index data index from. "
|
||||
"Default: https://build.servo.org/json")
|
||||
parser.add_argument("--build-url",
|
||||
type=str,
|
||||
default='https://build.servo.org/json/builders/{}/builds/{}',
|
||||
help="the URL to get the JSON build data from. "
|
||||
"Default: https://build.servo.org/json/builders/{}/builds/{}")
|
||||
parser.add_argument("--cache-dir",
|
||||
type=str,
|
||||
default=default_cache_dir,
|
||||
help="the directory to cache JSON files in. Default: " + default_cache_dir)
|
||||
parser.add_argument("--cache-name",
|
||||
type=str,
|
||||
default='build-{}-{}.json',
|
||||
help="the filename to cache JSON data in. "
|
||||
"Default: build-{}-{}.json")
|
||||
parser.add_argument("--output-dir",
|
||||
type=str,
|
||||
default=default_output_dir,
|
||||
help="the directory to save the CSV data to. Default: " + default_output_dir)
|
||||
parser.add_argument("--output-name",
|
||||
type=str,
|
||||
default='builds-{}-{}.csv',
|
||||
help="the filename to save the CSV data to. "
|
||||
"Default: builds-{}-{}.csv")
|
||||
parser.add_argument("--verbose", "-v",
|
||||
action='store_true',
|
||||
help="print every HTTP request")
|
||||
parser.add_argument(
|
||||
"--build-url",
|
||||
type=str,
|
||||
default="https://build.servo.org/json/builders/{}/builds/{}",
|
||||
help="the URL to get the JSON build data from. Default: https://build.servo.org/json/builders/{}/builds/{}",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cache-dir",
|
||||
type=str,
|
||||
default=default_cache_dir,
|
||||
help="the directory to cache JSON files in. Default: " + default_cache_dir,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cache-name",
|
||||
type=str,
|
||||
default="build-{}-{}.json",
|
||||
help="the filename to cache JSON data in. Default: build-{}-{}.json",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-dir",
|
||||
type=str,
|
||||
default=default_output_dir,
|
||||
help="the directory to save the CSV data to. Default: " + default_output_dir,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-name",
|
||||
type=str,
|
||||
default="builds-{}-{}.csv",
|
||||
help="the filename to save the CSV data to. Default: builds-{}-{}.csv",
|
||||
)
|
||||
parser.add_argument("--verbose", "-v", action="store_true", help="print every HTTP request")
|
||||
args = parser.parse_args()
|
||||
|
||||
os.makedirs(args.cache_dir, exist_ok=True)
|
||||
|
@ -63,7 +67,7 @@ def main():
|
|||
if args.verbose:
|
||||
print("Downloading index {}.".format(args.index_url))
|
||||
with urlopen(args.index_url) as response:
|
||||
index = json.loads(response.read().decode('utf-8'))
|
||||
index = json.loads(response.read().decode("utf-8"))
|
||||
|
||||
builds = []
|
||||
|
||||
|
@ -75,12 +79,11 @@ def main():
|
|||
if args.verbose:
|
||||
print("Downloading recent build {}.".format(recent_build_url))
|
||||
with urlopen(recent_build_url) as response:
|
||||
recent_build = json.loads(response.read().decode('utf-8'))
|
||||
recent_build = json.loads(response.read().decode("utf-8"))
|
||||
recent_build_number = recent_build["number"]
|
||||
|
||||
# Download each build, and convert to CSV
|
||||
for build_number in range(0, recent_build_number):
|
||||
|
||||
# Rather annoyingly, we can't just use the Python http cache,
|
||||
# because it doesn't cache 404 responses. So we roll our own.
|
||||
cache_json_name = args.cache_name.format(builder, build_number)
|
||||
|
@ -96,7 +99,7 @@ def main():
|
|||
print("Downloading build {}.".format(build_url))
|
||||
try:
|
||||
with urlopen(build_url) as response:
|
||||
build = json.loads(response.read().decode('utf-8'))
|
||||
build = json.loads(response.read().decode("utf-8"))
|
||||
except HTTPError as e:
|
||||
if e.code == 404:
|
||||
build = {}
|
||||
|
@ -104,46 +107,46 @@ def main():
|
|||
raise
|
||||
|
||||
# Don't cache current builds.
|
||||
if build.get('currentStep'):
|
||||
if build.get("currentStep"):
|
||||
continue
|
||||
|
||||
with open(cache_json, 'w+') as f:
|
||||
with open(cache_json, "w+") as f:
|
||||
json.dump(build, f)
|
||||
|
||||
if 'times' in build:
|
||||
if "times" in build:
|
||||
builds.append(build)
|
||||
|
||||
years = {}
|
||||
for build in builds:
|
||||
build_date = date.fromtimestamp(build['times'][0])
|
||||
build_date = date.fromtimestamp(build["times"][0])
|
||||
years.setdefault(build_date.year, {}).setdefault(build_date.month, []).append(build)
|
||||
|
||||
for year, months in years.items():
|
||||
for month, builds in months.items():
|
||||
|
||||
output_name = args.output_name.format(year, month)
|
||||
output = os.path.join(args.output_dir, output_name)
|
||||
|
||||
# Create the CSV file.
|
||||
if args.verbose:
|
||||
print('Creating file {}.'.format(output))
|
||||
with open(output, 'w+') as output_file:
|
||||
print("Creating file {}.".format(output))
|
||||
with open(output, "w+") as output_file:
|
||||
output_csv = csv.writer(output_file)
|
||||
|
||||
# The CSV column names
|
||||
output_csv.writerow([
|
||||
'builder',
|
||||
'buildNumber',
|
||||
'buildTimestamp',
|
||||
'stepName',
|
||||
'stepText',
|
||||
'stepNumber',
|
||||
'stepStart',
|
||||
'stepFinish'
|
||||
])
|
||||
output_csv.writerow(
|
||||
[
|
||||
"builder",
|
||||
"buildNumber",
|
||||
"buildTimestamp",
|
||||
"stepName",
|
||||
"stepText",
|
||||
"stepNumber",
|
||||
"stepStart",
|
||||
"stepFinish",
|
||||
]
|
||||
)
|
||||
|
||||
for build in builds:
|
||||
|
||||
builder = build["builderName"]
|
||||
build_number = build["number"]
|
||||
build_timestamp = datetime.fromtimestamp(build["times"][0]).replace(microsecond=0)
|
||||
|
@ -152,20 +155,22 @@ def main():
|
|||
for step in build["steps"]:
|
||||
if step["isFinished"]:
|
||||
step_name = step["name"]
|
||||
step_text = ' '.join(step["text"])
|
||||
step_text = " ".join(step["text"])
|
||||
step_number = step["step_number"]
|
||||
step_start = floor(step["times"][0])
|
||||
step_finish = floor(step["times"][1])
|
||||
output_csv.writerow([
|
||||
builder,
|
||||
build_number,
|
||||
build_timestamp,
|
||||
step_name,
|
||||
step_text,
|
||||
step_number,
|
||||
step_start,
|
||||
step_finish
|
||||
])
|
||||
output_csv.writerow(
|
||||
[
|
||||
builder,
|
||||
build_number,
|
||||
build_timestamp,
|
||||
step_name,
|
||||
step_text,
|
||||
step_number,
|
||||
step_start,
|
||||
step_finish,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -15,7 +15,7 @@ import sys
|
|||
@contextmanager
|
||||
def create_gecko_session():
|
||||
try:
|
||||
firefox_binary = os.environ['FIREFOX_BIN']
|
||||
firefox_binary = os.environ["FIREFOX_BIN"]
|
||||
except KeyError:
|
||||
print("+=============================================================+")
|
||||
print("| You must set the path to your firefox binary to FIREFOX_BIN |")
|
||||
|
@ -36,10 +36,7 @@ def generate_placeholder(testcase):
|
|||
# use a placeholder with values = -1 to make Treeherder happy, and still be
|
||||
# able to identify failed tests (successful tests have time >=0).
|
||||
|
||||
timings = {
|
||||
"testcase": testcase,
|
||||
"title": ""
|
||||
}
|
||||
timings = {"testcase": testcase, "title": ""}
|
||||
|
||||
timing_names = [
|
||||
"navigationStart",
|
||||
|
@ -81,16 +78,9 @@ def run_gecko_test(testcase, url, date, timeout, is_async):
|
|||
return generate_placeholder(testcase)
|
||||
|
||||
try:
|
||||
timings = {
|
||||
"testcase": testcase,
|
||||
"title": driver.title.replace(",", ",")
|
||||
}
|
||||
timings = {"testcase": testcase, "title": driver.title.replace(",", ",")}
|
||||
|
||||
timings.update(json.loads(
|
||||
driver.execute_script(
|
||||
"return JSON.stringify(performance.timing)"
|
||||
)
|
||||
))
|
||||
timings.update(json.loads(driver.execute_script("return JSON.stringify(performance.timing)")))
|
||||
except Exception:
|
||||
# We need to return a timing object no matter what happened.
|
||||
# See the comment in generate_placeholder() for explanation
|
||||
|
@ -101,17 +91,14 @@ def run_gecko_test(testcase, url, date, timeout, is_async):
|
|||
# TODO: the timeout is hardcoded
|
||||
driver.implicitly_wait(5) # sec
|
||||
driver.find_element_by_id("GECKO_TEST_DONE")
|
||||
timings.update(json.loads(
|
||||
driver.execute_script(
|
||||
"return JSON.stringify(window.customTimers)"
|
||||
)
|
||||
))
|
||||
timings.update(json.loads(driver.execute_script("return JSON.stringify(window.customTimers)")))
|
||||
|
||||
return [timings]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
# Just for manual testing
|
||||
from pprint import pprint
|
||||
|
||||
url = "http://localhost:8000/page_load_test/tp5n/dailymail.co.uk/www.dailymail.co.uk/ushome/index.html"
|
||||
pprint(run_gecko_test(url, 15))
|
||||
|
|
|
@ -23,14 +23,13 @@ SYSTEM = platform.system()
|
|||
|
||||
|
||||
def load_manifest(filename):
|
||||
with open(filename, 'r') as f:
|
||||
with open(filename, "r") as f:
|
||||
text = f.read()
|
||||
return list(parse_manifest(text))
|
||||
|
||||
|
||||
def parse_manifest(text):
|
||||
lines = filter(lambda x: x != "" and not x.startswith("#"),
|
||||
map(lambda x: x.strip(), text.splitlines()))
|
||||
lines = filter(lambda x: x != "" and not x.startswith("#"), map(lambda x: x.strip(), text.splitlines()))
|
||||
output = []
|
||||
for line in lines:
|
||||
if line.split(" ")[0] == "async":
|
||||
|
@ -46,21 +45,18 @@ def testcase_url(base, testcase):
|
|||
# the server on port 80. To allow non-root users to run the test
|
||||
# case, we take the URL to be relative to a base URL.
|
||||
(scheme, netloc, path, query, fragment) = urlsplit(testcase)
|
||||
relative_url = urlunsplit(('', '', '.' + path, query, fragment))
|
||||
relative_url = urlunsplit(("", "", "." + path, query, fragment))
|
||||
absolute_url = urljoin(base, relative_url)
|
||||
return absolute_url
|
||||
|
||||
|
||||
def execute_test(url, command, timeout):
|
||||
try:
|
||||
return subprocess.check_output(
|
||||
command, stderr=subprocess.STDOUT, timeout=timeout
|
||||
)
|
||||
return subprocess.check_output(command, stderr=subprocess.STDOUT, timeout=timeout)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Unexpected Fail:")
|
||||
print(e)
|
||||
print("You may want to re-run the test manually:\n{}"
|
||||
.format(' '.join(command)))
|
||||
print("You may want to re-run the test manually:\n{}".format(" ".join(command)))
|
||||
except subprocess.TimeoutExpired:
|
||||
print("Test FAILED due to timeout: {}".format(url))
|
||||
return ""
|
||||
|
@ -74,22 +70,21 @@ def run_servo_test(testcase, url, date, timeout, is_async):
|
|||
|
||||
ua_script_path = "{}/user-agent-js".format(os.getcwd())
|
||||
command = [
|
||||
"../../../target/release/servo", url,
|
||||
"../../../target/release/servo",
|
||||
url,
|
||||
"--userscripts=" + ua_script_path,
|
||||
"--headless",
|
||||
"-x", "-o", "output.png"
|
||||
"-x",
|
||||
"-o",
|
||||
"output.png",
|
||||
]
|
||||
log = ""
|
||||
try:
|
||||
log = subprocess.check_output(
|
||||
command, stderr=subprocess.STDOUT, timeout=timeout
|
||||
)
|
||||
log = subprocess.check_output(command, stderr=subprocess.STDOUT, timeout=timeout)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Unexpected Fail:")
|
||||
print(e)
|
||||
print("You may want to re-run the test manually:\n{}".format(
|
||||
' '.join(command)
|
||||
))
|
||||
print("You may want to re-run the test manually:\n{}".format(" ".join(command)))
|
||||
except subprocess.TimeoutExpired:
|
||||
print("Test FAILED due to timeout: {}".format(testcase))
|
||||
return parse_log(log, testcase, url, date)
|
||||
|
@ -100,7 +95,7 @@ def parse_log(log, testcase, url, date):
|
|||
block = []
|
||||
copy = False
|
||||
for line_bytes in log.splitlines():
|
||||
line = line_bytes.decode('utf-8')
|
||||
line = line_bytes.decode("utf-8")
|
||||
|
||||
if line.strip() == ("[PERF] perf block start"):
|
||||
copy = True
|
||||
|
@ -119,10 +114,10 @@ def parse_log(log, testcase, url, date):
|
|||
except ValueError:
|
||||
print("[DEBUG] failed to parse the following line:")
|
||||
print(line)
|
||||
print('[DEBUG] log:')
|
||||
print('-----')
|
||||
print("[DEBUG] log:")
|
||||
print("-----")
|
||||
print(log)
|
||||
print('-----')
|
||||
print("-----")
|
||||
return None
|
||||
|
||||
if key == "testcase" or key == "title":
|
||||
|
@ -133,10 +128,12 @@ def parse_log(log, testcase, url, date):
|
|||
return timing
|
||||
|
||||
def valid_timing(timing, url=None):
|
||||
if (timing is None
|
||||
or testcase is None
|
||||
or timing.get('title') == 'Error loading page'
|
||||
or timing.get('testcase') != url):
|
||||
if (
|
||||
timing is None
|
||||
or testcase is None
|
||||
or timing.get("title") == "Error loading page"
|
||||
or timing.get("testcase") != url
|
||||
):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
@ -178,10 +175,10 @@ def parse_log(log, testcase, url, date):
|
|||
# Set the testcase field to contain the original testcase name,
|
||||
# rather than the url.
|
||||
def set_testcase(timing, testcase=None, date=None):
|
||||
timing['testcase'] = testcase
|
||||
timing['system'] = SYSTEM
|
||||
timing['machine'] = MACHINE
|
||||
timing['date'] = date
|
||||
timing["testcase"] = testcase
|
||||
timing["system"] = SYSTEM
|
||||
timing["machine"] = MACHINE
|
||||
timing["date"] = date
|
||||
return timing
|
||||
|
||||
valid_timing_for_case = partial(valid_timing, url=url)
|
||||
|
@ -190,10 +187,10 @@ def parse_log(log, testcase, url, date):
|
|||
|
||||
if len(timings) == 0:
|
||||
print("Didn't find any perf data in the log, test timeout?")
|
||||
print('[DEBUG] log:')
|
||||
print('-----')
|
||||
print("[DEBUG] log:")
|
||||
print("-----")
|
||||
print(log)
|
||||
print('-----')
|
||||
print("-----")
|
||||
|
||||
return [create_placeholder(testcase)]
|
||||
else:
|
||||
|
@ -204,22 +201,25 @@ def filter_result_by_manifest(result_json, manifest, base):
|
|||
filtered = []
|
||||
for name, is_async in manifest:
|
||||
url = testcase_url(base, name)
|
||||
match = [tc for tc in result_json if tc['testcase'] == url]
|
||||
match = [tc for tc in result_json if tc["testcase"] == url]
|
||||
if len(match) == 0:
|
||||
raise Exception(("Missing test result: {}. This will cause a "
|
||||
"discontinuity in the treeherder graph, "
|
||||
"so we won't submit this data.").format(name))
|
||||
raise Exception(
|
||||
(
|
||||
"Missing test result: {}. This will cause a "
|
||||
"discontinuity in the treeherder graph, "
|
||||
"so we won't submit this data."
|
||||
).format(name)
|
||||
)
|
||||
filtered += match
|
||||
return filtered
|
||||
|
||||
|
||||
def take_result_median(result_json, expected_runs):
|
||||
median_results = []
|
||||
for k, g in itertools.groupby(result_json, lambda x: x['testcase']):
|
||||
for k, g in itertools.groupby(result_json, lambda x: x["testcase"]):
|
||||
group = list(g)
|
||||
if len(group) != expected_runs:
|
||||
print(("Warning: Not enough test data for {},"
|
||||
" maybe some runs failed?").format(k))
|
||||
print(("Warning: Not enough test data for {}, maybe some runs failed?").format(k))
|
||||
|
||||
median_result = {}
|
||||
for k, _ in group[0].items():
|
||||
|
@ -227,8 +227,7 @@ def take_result_median(result_json, expected_runs):
|
|||
median_result[k] = group[0][k]
|
||||
else:
|
||||
try:
|
||||
median_result[k] = median([x[k] for x in group
|
||||
if x[k] is not None])
|
||||
median_result[k] = median([x[k] for x in group if x[k] is not None])
|
||||
except StatisticsError:
|
||||
median_result[k] = -1
|
||||
median_results.append(median_result)
|
||||
|
@ -236,72 +235,65 @@ def take_result_median(result_json, expected_runs):
|
|||
|
||||
|
||||
def save_result_json(results, filename, manifest, expected_runs, base):
|
||||
|
||||
results = filter_result_by_manifest(results, manifest, base)
|
||||
results = take_result_median(results, expected_runs)
|
||||
|
||||
if len(results) == 0:
|
||||
with open(filename, 'w') as f:
|
||||
json.dump("No test result found in the log. All tests timeout?",
|
||||
f, indent=2)
|
||||
with open(filename, "w") as f:
|
||||
json.dump("No test result found in the log. All tests timeout?", f, indent=2)
|
||||
else:
|
||||
with open(filename, 'w') as f:
|
||||
with open(filename, "w") as f:
|
||||
json.dump(results, f, indent=2)
|
||||
print("Result saved to {}".format(filename))
|
||||
|
||||
|
||||
def save_result_csv(results, filename, manifest, expected_runs, base):
|
||||
|
||||
fieldnames = [
|
||||
'system',
|
||||
'machine',
|
||||
'date',
|
||||
'testcase',
|
||||
'title',
|
||||
'connectEnd',
|
||||
'connectStart',
|
||||
'domComplete',
|
||||
'domContentLoadedEventEnd',
|
||||
'domContentLoadedEventStart',
|
||||
'domInteractive',
|
||||
'domLoading',
|
||||
'domainLookupEnd',
|
||||
'domainLookupStart',
|
||||
'fetchStart',
|
||||
'loadEventEnd',
|
||||
'loadEventStart',
|
||||
'navigationStart',
|
||||
'redirectEnd',
|
||||
'redirectStart',
|
||||
'requestStart',
|
||||
'responseEnd',
|
||||
'responseStart',
|
||||
'secureConnectionStart',
|
||||
'unloadEventEnd',
|
||||
'unloadEventStart',
|
||||
"system",
|
||||
"machine",
|
||||
"date",
|
||||
"testcase",
|
||||
"title",
|
||||
"connectEnd",
|
||||
"connectStart",
|
||||
"domComplete",
|
||||
"domContentLoadedEventEnd",
|
||||
"domContentLoadedEventStart",
|
||||
"domInteractive",
|
||||
"domLoading",
|
||||
"domainLookupEnd",
|
||||
"domainLookupStart",
|
||||
"fetchStart",
|
||||
"loadEventEnd",
|
||||
"loadEventStart",
|
||||
"navigationStart",
|
||||
"redirectEnd",
|
||||
"redirectStart",
|
||||
"requestStart",
|
||||
"responseEnd",
|
||||
"responseStart",
|
||||
"secureConnectionStart",
|
||||
"unloadEventEnd",
|
||||
"unloadEventStart",
|
||||
]
|
||||
|
||||
successes = [r for r in results if r['domComplete'] != -1]
|
||||
successes = [r for r in results if r["domComplete"] != -1]
|
||||
|
||||
with open(filename, 'w', encoding='utf-8') as csvfile:
|
||||
with open(filename, "w", encoding="utf-8") as csvfile:
|
||||
writer = csv.DictWriter(csvfile, fieldnames)
|
||||
writer.writeheader()
|
||||
writer.writerows(successes)
|
||||
|
||||
|
||||
def format_result_summary(results):
|
||||
failures = list(filter(lambda x: x['domComplete'] == -1, results))
|
||||
failures = list(filter(lambda x: x["domComplete"] == -1, results))
|
||||
result_log = """
|
||||
========================================
|
||||
Total {total} tests; {suc} succeeded, {fail} failed.
|
||||
|
||||
Failure summary:
|
||||
""".format(
|
||||
total=len(results),
|
||||
suc=len(list(filter(lambda x: x['domComplete'] != -1, results))),
|
||||
fail=len(failures)
|
||||
)
|
||||
uniq_failures = list(set(map(lambda x: x['testcase'], failures)))
|
||||
""".format(total=len(results), suc=len(list(filter(lambda x: x["domComplete"] != -1, results))), fail=len(failures))
|
||||
uniq_failures = list(set(map(lambda x: x["testcase"], failures)))
|
||||
for failure in uniq_failures:
|
||||
result_log += " - {}\n".format(failure)
|
||||
|
||||
|
@ -311,40 +303,40 @@ Failure summary:
|
|||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run page load test on servo"
|
||||
parser = argparse.ArgumentParser(description="Run page load test on servo")
|
||||
parser.add_argument("tp5_manifest", help="the test manifest in tp5 format")
|
||||
parser.add_argument("output_file", help="filename for the output json")
|
||||
parser.add_argument(
|
||||
"--base",
|
||||
type=str,
|
||||
default="http://localhost:8000/",
|
||||
help="the base URL for tests. Default: http://localhost:8000/",
|
||||
)
|
||||
parser.add_argument("--runs", type=int, default=20, help="number of runs for each test case. Defult: 20")
|
||||
parser.add_argument(
|
||||
"--timeout",
|
||||
type=int,
|
||||
default=300, # 5 min
|
||||
help=("kill the test if not finished in time (sec). Default: 5 min"),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--date",
|
||||
type=str,
|
||||
default=None, # 5 min
|
||||
help=("the date to use in the CSV file."),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--engine",
|
||||
type=str,
|
||||
default="servo",
|
||||
help=("The engine to run the tests on. Currently only servo and gecko are supported."),
|
||||
)
|
||||
parser.add_argument("tp5_manifest",
|
||||
help="the test manifest in tp5 format")
|
||||
parser.add_argument("output_file",
|
||||
help="filename for the output json")
|
||||
parser.add_argument("--base",
|
||||
type=str,
|
||||
default='http://localhost:8000/',
|
||||
help="the base URL for tests. Default: http://localhost:8000/")
|
||||
parser.add_argument("--runs",
|
||||
type=int,
|
||||
default=20,
|
||||
help="number of runs for each test case. Defult: 20")
|
||||
parser.add_argument("--timeout",
|
||||
type=int,
|
||||
default=300, # 5 min
|
||||
help=("kill the test if not finished in time (sec)."
|
||||
" Default: 5 min"))
|
||||
parser.add_argument("--date",
|
||||
type=str,
|
||||
default=None, # 5 min
|
||||
help=("the date to use in the CSV file."))
|
||||
parser.add_argument("--engine",
|
||||
type=str,
|
||||
default='servo',
|
||||
help=("The engine to run the tests on. Currently only"
|
||||
" servo and gecko are supported."))
|
||||
args = parser.parse_args()
|
||||
if args.engine == 'servo':
|
||||
if args.engine == "servo":
|
||||
run_test = run_servo_test
|
||||
elif args.engine == 'gecko':
|
||||
elif args.engine == "gecko":
|
||||
import gecko_driver # Load this only when we need gecko test
|
||||
|
||||
run_test = gecko_driver.run_gecko_test
|
||||
date = args.date or DATE
|
||||
try:
|
||||
|
@ -354,9 +346,7 @@ def main():
|
|||
for testcase, is_async in testcases:
|
||||
url = testcase_url(args.base, testcase)
|
||||
for run in range(args.runs):
|
||||
print("Running test {}/{} on {}".format(run + 1,
|
||||
args.runs,
|
||||
url))
|
||||
print("Running test {}/{} on {}".format(run + 1, args.runs, url))
|
||||
# results will be a mixure of timings dict and testcase strings
|
||||
# testcase string indicates a failed test
|
||||
results += run_test(testcase, url, date, args.timeout, is_async)
|
||||
|
@ -364,7 +354,7 @@ def main():
|
|||
# TODO: Record and analyze other performance.timing properties
|
||||
|
||||
print(format_result_summary(results))
|
||||
if args.output_file.endswith('.csv'):
|
||||
if args.output_file.endswith(".csv"):
|
||||
save_result_csv(results, args.output_file, testcases, args.runs, args.base)
|
||||
else:
|
||||
save_result_json(results, args.output_file, testcases, args.runs, args.base)
|
||||
|
|
|
@ -10,13 +10,14 @@ import boto3
|
|||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description=("Set the policy of the servo-perf bucket. "
|
||||
"Remember to set your S3 credentials "
|
||||
"https://github.com/boto/boto3"))
|
||||
description=(
|
||||
"Set the policy of the servo-perf bucket. Remember to set your S3 credentials https://github.com/boto/boto3"
|
||||
)
|
||||
)
|
||||
parser.parse_args()
|
||||
|
||||
s3 = boto3.resource('s3')
|
||||
BUCKET = 'servo-perf'
|
||||
s3 = boto3.resource("s3")
|
||||
BUCKET = "servo-perf"
|
||||
POLICY = """{
|
||||
"Version":"2012-10-17",
|
||||
"Statement":[
|
||||
|
|
|
@ -11,8 +11,7 @@ import operator
|
|||
import os
|
||||
import random
|
||||
import string
|
||||
from thclient import (TreeherderClient, TreeherderResultSetCollection,
|
||||
TreeherderJobCollection)
|
||||
from thclient import TreeherderClient, TreeherderResultSetCollection, TreeherderJobCollection
|
||||
import time
|
||||
|
||||
from runner import format_result_summary
|
||||
|
@ -24,33 +23,28 @@ def geometric_mean(iterable):
|
|||
|
||||
|
||||
def format_testcase_name(name):
|
||||
temp = name.replace('http://localhost:8000/page_load_test/', '')
|
||||
temp = temp.replace('http://localhost:8000/tp6/', '')
|
||||
temp = temp.split('/')[0]
|
||||
temp = name.replace("http://localhost:8000/page_load_test/", "")
|
||||
temp = temp.replace("http://localhost:8000/tp6/", "")
|
||||
temp = temp.split("/")[0]
|
||||
temp = temp[0:80]
|
||||
return temp
|
||||
|
||||
|
||||
def format_perf_data(perf_json, engine='servo'):
|
||||
def format_perf_data(perf_json, engine="servo"):
|
||||
suites = []
|
||||
measurement = "domComplete" # Change this to an array when we have more
|
||||
|
||||
def get_time_from_nav_start(timings, measurement):
|
||||
return timings[measurement] - timings['navigationStart']
|
||||
return timings[measurement] - timings["navigationStart"]
|
||||
|
||||
measurementFromNavStart = partial(get_time_from_nav_start,
|
||||
measurement=measurement)
|
||||
measurementFromNavStart = partial(get_time_from_nav_start, measurement=measurement)
|
||||
|
||||
if (engine == 'gecko'):
|
||||
name = 'gecko.{}'.format(measurement)
|
||||
if engine == "gecko":
|
||||
name = "gecko.{}".format(measurement)
|
||||
else:
|
||||
name = measurement
|
||||
|
||||
suite = {
|
||||
"name": name,
|
||||
"value": geometric_mean(map(measurementFromNavStart, perf_json)),
|
||||
"subtests": []
|
||||
}
|
||||
suite = {"name": name, "value": geometric_mean(map(measurementFromNavStart, perf_json)), "subtests": []}
|
||||
for testcase in perf_json:
|
||||
if measurementFromNavStart(testcase) < 0:
|
||||
value = -1
|
||||
|
@ -58,10 +52,7 @@ def format_perf_data(perf_json, engine='servo'):
|
|||
else:
|
||||
value = measurementFromNavStart(testcase)
|
||||
|
||||
suite["subtests"].append({
|
||||
"name": format_testcase_name(testcase["testcase"]),
|
||||
"value": value
|
||||
})
|
||||
suite["subtests"].append({"name": format_testcase_name(testcase["testcase"]), "value": value})
|
||||
|
||||
suites.append(suite)
|
||||
|
||||
|
@ -69,7 +60,7 @@ def format_perf_data(perf_json, engine='servo'):
|
|||
"performance_data": {
|
||||
# https://bugzilla.mozilla.org/show_bug.cgi?id=1271472
|
||||
"framework": {"name": "servo-perf"},
|
||||
"suites": suites
|
||||
"suites": suites,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,20 +73,20 @@ def create_resultset_collection(dataset):
|
|||
for data in dataset:
|
||||
trs = trsc.get_resultset()
|
||||
|
||||
trs.add_push_timestamp(data['push_timestamp'])
|
||||
trs.add_revision(data['revision'])
|
||||
trs.add_author(data['author'])
|
||||
trs.add_push_timestamp(data["push_timestamp"])
|
||||
trs.add_revision(data["revision"])
|
||||
trs.add_author(data["author"])
|
||||
# TODO: figure out where type is used
|
||||
# trs.add_type(data['type'])
|
||||
|
||||
revisions = []
|
||||
for rev in data['revisions']:
|
||||
for rev in data["revisions"]:
|
||||
tr = trs.get_revision()
|
||||
|
||||
tr.add_revision(rev['revision'])
|
||||
tr.add_author(rev['author'])
|
||||
tr.add_comment(rev['comment'])
|
||||
tr.add_repository(rev['repository'])
|
||||
tr.add_revision(rev["revision"])
|
||||
tr.add_author(rev["author"])
|
||||
tr.add_comment(rev["comment"])
|
||||
tr.add_repository(rev["repository"])
|
||||
|
||||
revisions.append(tr)
|
||||
trs.add_revisions(revisions)
|
||||
|
@ -114,46 +105,42 @@ def create_job_collection(dataset):
|
|||
for data in dataset:
|
||||
tj = tjc.get_job()
|
||||
|
||||
tj.add_revision(data['revision'])
|
||||
tj.add_project(data['project'])
|
||||
tj.add_coalesced_guid(data['job']['coalesced'])
|
||||
tj.add_job_guid(data['job']['job_guid'])
|
||||
tj.add_job_name(data['job']['name'])
|
||||
tj.add_job_symbol(data['job']['job_symbol'])
|
||||
tj.add_group_name(data['job']['group_name'])
|
||||
tj.add_group_symbol(data['job']['group_symbol'])
|
||||
tj.add_description(data['job']['desc'])
|
||||
tj.add_product_name(data['job']['product_name'])
|
||||
tj.add_state(data['job']['state'])
|
||||
tj.add_result(data['job']['result'])
|
||||
tj.add_reason(data['job']['reason'])
|
||||
tj.add_who(data['job']['who'])
|
||||
tj.add_tier(data['job']['tier'])
|
||||
tj.add_submit_timestamp(data['job']['submit_timestamp'])
|
||||
tj.add_start_timestamp(data['job']['start_timestamp'])
|
||||
tj.add_end_timestamp(data['job']['end_timestamp'])
|
||||
tj.add_machine(data['job']['machine'])
|
||||
tj.add_revision(data["revision"])
|
||||
tj.add_project(data["project"])
|
||||
tj.add_coalesced_guid(data["job"]["coalesced"])
|
||||
tj.add_job_guid(data["job"]["job_guid"])
|
||||
tj.add_job_name(data["job"]["name"])
|
||||
tj.add_job_symbol(data["job"]["job_symbol"])
|
||||
tj.add_group_name(data["job"]["group_name"])
|
||||
tj.add_group_symbol(data["job"]["group_symbol"])
|
||||
tj.add_description(data["job"]["desc"])
|
||||
tj.add_product_name(data["job"]["product_name"])
|
||||
tj.add_state(data["job"]["state"])
|
||||
tj.add_result(data["job"]["result"])
|
||||
tj.add_reason(data["job"]["reason"])
|
||||
tj.add_who(data["job"]["who"])
|
||||
tj.add_tier(data["job"]["tier"])
|
||||
tj.add_submit_timestamp(data["job"]["submit_timestamp"])
|
||||
tj.add_start_timestamp(data["job"]["start_timestamp"])
|
||||
tj.add_end_timestamp(data["job"]["end_timestamp"])
|
||||
tj.add_machine(data["job"]["machine"])
|
||||
|
||||
tj.add_build_info(
|
||||
data['job']['build_platform']['os_name'],
|
||||
data['job']['build_platform']['platform'],
|
||||
data['job']['build_platform']['architecture']
|
||||
data["job"]["build_platform"]["os_name"],
|
||||
data["job"]["build_platform"]["platform"],
|
||||
data["job"]["build_platform"]["architecture"],
|
||||
)
|
||||
|
||||
tj.add_machine_info(
|
||||
data['job']['machine_platform']['os_name'],
|
||||
data['job']['machine_platform']['platform'],
|
||||
data['job']['machine_platform']['architecture']
|
||||
data["job"]["machine_platform"]["os_name"],
|
||||
data["job"]["machine_platform"]["platform"],
|
||||
data["job"]["machine_platform"]["architecture"],
|
||||
)
|
||||
|
||||
tj.add_option_collection(data['job']['option_collection'])
|
||||
tj.add_option_collection(data["job"]["option_collection"])
|
||||
|
||||
for artifact_data in data['job']['artifacts']:
|
||||
tj.add_artifact(
|
||||
artifact_data['name'],
|
||||
artifact_data['type'],
|
||||
artifact_data['blob']
|
||||
)
|
||||
for artifact_data in data["job"]["artifacts"]:
|
||||
tj.add_artifact(artifact_data["name"], artifact_data["type"], artifact_data["blob"])
|
||||
tjc.add(tj)
|
||||
|
||||
return tjc
|
||||
|
@ -161,30 +148,28 @@ def create_job_collection(dataset):
|
|||
|
||||
# TODO: refactor this big function to smaller chunks
|
||||
def submit(perf_data, failures, revision, summary, engine):
|
||||
|
||||
print("[DEBUG] failures:")
|
||||
print(list(map(lambda x: x['testcase'], failures)))
|
||||
print(list(map(lambda x: x["testcase"], failures)))
|
||||
|
||||
author = "{} <{}>".format(revision['author']['name'],
|
||||
revision['author']['email'])
|
||||
author = "{} <{}>".format(revision["author"]["name"], revision["author"]["email"])
|
||||
|
||||
dataset = [
|
||||
{
|
||||
# The top-most revision in the list of commits for a push.
|
||||
'revision': revision['commit'],
|
||||
'author': author,
|
||||
'push_timestamp': int(revision['author']['timestamp']),
|
||||
'type': 'push',
|
||||
"revision": revision["commit"],
|
||||
"author": author,
|
||||
"push_timestamp": int(revision["author"]["timestamp"]),
|
||||
"type": "push",
|
||||
# a list of revisions associated with the resultset. There should
|
||||
# be at least one.
|
||||
'revisions': [
|
||||
"revisions": [
|
||||
{
|
||||
'comment': revision['subject'],
|
||||
'revision': revision['commit'],
|
||||
'repository': 'servo',
|
||||
'author': author
|
||||
"comment": revision["subject"],
|
||||
"revision": revision["commit"],
|
||||
"repository": "servo",
|
||||
"author": author,
|
||||
}
|
||||
]
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -195,158 +180,129 @@ def submit(perf_data, failures, revision, summary, engine):
|
|||
# if len(failures) > 0:
|
||||
# result = "testfailed"
|
||||
|
||||
hashlen = len(revision['commit'])
|
||||
job_guid = ''.join(
|
||||
random.choice(string.ascii_letters + string.digits) for i in range(hashlen)
|
||||
)
|
||||
hashlen = len(revision["commit"])
|
||||
job_guid = "".join(random.choice(string.ascii_letters + string.digits) for i in range(hashlen))
|
||||
|
||||
if (engine == "gecko"):
|
||||
if engine == "gecko":
|
||||
project = "servo"
|
||||
job_symbol = 'PLG'
|
||||
group_symbol = 'SPG'
|
||||
group_name = 'Servo Perf on Gecko'
|
||||
job_symbol = "PLG"
|
||||
group_symbol = "SPG"
|
||||
group_name = "Servo Perf on Gecko"
|
||||
else:
|
||||
project = "servo"
|
||||
job_symbol = 'PL'
|
||||
group_symbol = 'SP'
|
||||
group_name = 'Servo Perf'
|
||||
job_symbol = "PL"
|
||||
group_symbol = "SP"
|
||||
group_name = "Servo Perf"
|
||||
|
||||
dataset = [
|
||||
{
|
||||
'project': project,
|
||||
'revision': revision['commit'],
|
||||
'job': {
|
||||
'job_guid': job_guid,
|
||||
'product_name': project,
|
||||
'reason': 'scheduler',
|
||||
"project": project,
|
||||
"revision": revision["commit"],
|
||||
"job": {
|
||||
"job_guid": job_guid,
|
||||
"product_name": project,
|
||||
"reason": "scheduler",
|
||||
# TODO: What is `who` for?
|
||||
'who': 'Servo',
|
||||
'desc': 'Servo Page Load Time Tests',
|
||||
'name': 'Servo Page Load Time',
|
||||
"who": "Servo",
|
||||
"desc": "Servo Page Load Time Tests",
|
||||
"name": "Servo Page Load Time",
|
||||
# The symbol representing the job displayed in
|
||||
# treeherder.allizom.org
|
||||
'job_symbol': job_symbol,
|
||||
|
||||
"job_symbol": job_symbol,
|
||||
# The symbol representing the job group in
|
||||
# treeherder.allizom.org
|
||||
'group_symbol': group_symbol,
|
||||
'group_name': group_name,
|
||||
|
||||
"group_symbol": group_symbol,
|
||||
"group_name": group_name,
|
||||
# TODO: get the real timing from the test runner
|
||||
'submit_timestamp': str(int(time.time())),
|
||||
'start_timestamp': str(int(time.time())),
|
||||
'end_timestamp': str(int(time.time())),
|
||||
|
||||
'state': 'completed',
|
||||
'result': result, # "success" or "testfailed"
|
||||
|
||||
'machine': 'local-machine',
|
||||
"submit_timestamp": str(int(time.time())),
|
||||
"start_timestamp": str(int(time.time())),
|
||||
"end_timestamp": str(int(time.time())),
|
||||
"state": "completed",
|
||||
"result": result, # "success" or "testfailed"
|
||||
"machine": "local-machine",
|
||||
# TODO: read platform from test result
|
||||
'build_platform': {
|
||||
'platform': 'linux64',
|
||||
'os_name': 'linux',
|
||||
'architecture': 'x86_64'
|
||||
},
|
||||
'machine_platform': {
|
||||
'platform': 'linux64',
|
||||
'os_name': 'linux',
|
||||
'architecture': 'x86_64'
|
||||
},
|
||||
|
||||
'option_collection': {'opt': True},
|
||||
|
||||
"build_platform": {"platform": "linux64", "os_name": "linux", "architecture": "x86_64"},
|
||||
"machine_platform": {"platform": "linux64", "os_name": "linux", "architecture": "x86_64"},
|
||||
"option_collection": {"opt": True},
|
||||
# jobs can belong to different tiers
|
||||
# setting the tier here will determine which tier the job
|
||||
# belongs to. However, if a job is set as Tier of 1, but
|
||||
# belongs to the Tier 2 profile on the server, it will still
|
||||
# be saved as Tier 2.
|
||||
'tier': 1,
|
||||
|
||||
"tier": 1,
|
||||
# the ``name`` of the log can be the default of "buildbot_text"
|
||||
# however, you can use a custom name. See below.
|
||||
# TODO: point this to the log when we have them uploaded to S3
|
||||
'log_references': [
|
||||
{
|
||||
'url': 'TBD',
|
||||
'name': 'test log'
|
||||
}
|
||||
],
|
||||
"log_references": [{"url": "TBD", "name": "test log"}],
|
||||
# The artifact can contain any kind of structured data
|
||||
# associated with a test.
|
||||
'artifacts': [
|
||||
"artifacts": [
|
||||
{
|
||||
'type': 'json',
|
||||
'name': 'performance_data',
|
||||
"type": "json",
|
||||
"name": "performance_data",
|
||||
# TODO: include the job_guid when the runner actually
|
||||
# generates one
|
||||
# 'job_guid': job_guid,
|
||||
'blob': perf_data
|
||||
"blob": perf_data,
|
||||
},
|
||||
{
|
||||
'type': 'json',
|
||||
'name': 'Job Info',
|
||||
"type": "json",
|
||||
"name": "Job Info",
|
||||
# 'job_guid': job_guid,
|
||||
"blob": {
|
||||
"job_details": [
|
||||
{
|
||||
"content_type": "raw_html",
|
||||
"title": "Result Summary",
|
||||
"value": summary
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
"job_details": [{"content_type": "raw_html", "title": "Result Summary", "value": summary}]
|
||||
},
|
||||
},
|
||||
],
|
||||
# List of job guids that were coalesced to this job
|
||||
'coalesced': []
|
||||
}
|
||||
"coalesced": [],
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
tjc = create_job_collection(dataset)
|
||||
|
||||
# TODO: extract this read credential code out of this function.
|
||||
cred = {
|
||||
'client_id': os.environ['TREEHERDER_CLIENT_ID'],
|
||||
'secret': os.environ['TREEHERDER_CLIENT_SECRET']
|
||||
}
|
||||
cred = {"client_id": os.environ["TREEHERDER_CLIENT_ID"], "secret": os.environ["TREEHERDER_CLIENT_SECRET"]}
|
||||
|
||||
client = TreeherderClient(server_url='https://treeherder.mozilla.org',
|
||||
client_id=cred['client_id'],
|
||||
secret=cred['secret'])
|
||||
client = TreeherderClient(
|
||||
server_url="https://treeherder.mozilla.org", client_id=cred["client_id"], secret=cred["secret"]
|
||||
)
|
||||
|
||||
# data structure validation is automatically performed here, if validation
|
||||
# fails a TreeherderClientError is raised
|
||||
client.post_collection('servo', trsc)
|
||||
client.post_collection('servo', tjc)
|
||||
client.post_collection("servo", trsc)
|
||||
client.post_collection("servo", tjc)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description=("Submit Servo performance data to Perfherder. "
|
||||
"Remember to set your Treeherder credential as environment"
|
||||
" variable \'TREEHERDER_CLIENT_ID\' and "
|
||||
"\'TREEHERDER_CLIENT_SECRET\'"))
|
||||
parser.add_argument("perf_json",
|
||||
help="the output json from runner")
|
||||
parser.add_argument("revision_json",
|
||||
help="the json containing the servo revision data")
|
||||
parser.add_argument("--engine",
|
||||
type=str,
|
||||
default='servo',
|
||||
help=("The engine to run the tests on. Currently only"
|
||||
" servo and gecko are supported."))
|
||||
description=(
|
||||
"Submit Servo performance data to Perfherder. "
|
||||
"Remember to set your Treeherder credential as environment"
|
||||
" variable 'TREEHERDER_CLIENT_ID' and "
|
||||
"'TREEHERDER_CLIENT_SECRET'"
|
||||
)
|
||||
)
|
||||
parser.add_argument("perf_json", help="the output json from runner")
|
||||
parser.add_argument("revision_json", help="the json containing the servo revision data")
|
||||
parser.add_argument(
|
||||
"--engine",
|
||||
type=str,
|
||||
default="servo",
|
||||
help=("The engine to run the tests on. Currently only servo and gecko are supported."),
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
with open(args.perf_json, 'r') as f:
|
||||
with open(args.perf_json, "r") as f:
|
||||
result_json = json.load(f)
|
||||
|
||||
with open(args.revision_json, 'r') as f:
|
||||
with open(args.revision_json, "r") as f:
|
||||
revision = json.load(f)
|
||||
|
||||
perf_data = format_perf_data(result_json, args.engine)
|
||||
failures = list(filter(lambda x: x['domComplete'] == -1, result_json))
|
||||
summary = format_result_summary(result_json).replace('\n', '<br/>')
|
||||
failures = list(filter(lambda x: x["domComplete"] == -1, result_json))
|
||||
summary = format_result_summary(result_json).replace("\n", "<br/>")
|
||||
|
||||
submit(perf_data, failures, revision, summary, args.engine)
|
||||
print("Done!")
|
||||
|
|
|
@ -10,17 +10,16 @@ import boto3
|
|||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description=("Submit Servo performance data to S3. "
|
||||
"Remember to set your S3 credentials "
|
||||
"https://github.com/boto/boto3"))
|
||||
parser.add_argument("perf_file",
|
||||
help="the output CSV file from runner")
|
||||
parser.add_argument("perf_key",
|
||||
help="the S3 key to upload to")
|
||||
description=(
|
||||
"Submit Servo performance data to S3. Remember to set your S3 credentials https://github.com/boto/boto3"
|
||||
)
|
||||
)
|
||||
parser.add_argument("perf_file", help="the output CSV file from runner")
|
||||
parser.add_argument("perf_key", help="the S3 key to upload to")
|
||||
args = parser.parse_args()
|
||||
|
||||
s3 = boto3.client('s3')
|
||||
BUCKET = 'servo-perf'
|
||||
s3 = boto3.client("s3")
|
||||
BUCKET = "servo-perf"
|
||||
s3.upload_file(args.perf_file, BUCKET, args.perf_key)
|
||||
|
||||
print("Done!")
|
||||
|
|
|
@ -16,16 +16,16 @@ args = parser.parse_args()
|
|||
|
||||
|
||||
def load_data(filename):
|
||||
with open(filename, 'r') as f:
|
||||
with open(filename, "r") as f:
|
||||
results = {}
|
||||
totals = {}
|
||||
counts = {}
|
||||
records = json.load(f)
|
||||
for record in records:
|
||||
key = record.get('testcase')
|
||||
value = record.get('domComplete') - record.get('domLoading')
|
||||
totals[key] = totals.get('key', 0) + value
|
||||
counts[key] = counts.get('key', 0) + 1
|
||||
key = record.get("testcase")
|
||||
value = record.get("domComplete") - record.get("domLoading")
|
||||
totals[key] = totals.get("key", 0) + value
|
||||
counts[key] = counts.get("key", 0) + 1
|
||||
results[key] = round(totals[key] / counts[key])
|
||||
return results
|
||||
|
||||
|
@ -34,10 +34,10 @@ data1 = load_data(args.file1)
|
|||
data2 = load_data(args.file2)
|
||||
keys = set(data1.keys()).union(data2.keys())
|
||||
|
||||
BLUE = '\033[94m'
|
||||
GREEN = '\033[92m'
|
||||
WARNING = '\033[93m'
|
||||
END = '\033[0m'
|
||||
BLUE = "\033[94m"
|
||||
GREEN = "\033[92m"
|
||||
WARNING = "\033[93m"
|
||||
END = "\033[0m"
|
||||
|
||||
|
||||
total1 = 0
|
||||
|
|
|
@ -10,7 +10,7 @@ import pytest
|
|||
|
||||
def test_log_parser():
|
||||
mock_url = "http://localhost:8000/page_load_test/56.com/www.56.com/index.html"
|
||||
mock_log = b'''
|
||||
mock_log = b"""
|
||||
[PERF] perf block start
|
||||
[PERF],testcase,http://localhost:8000/page_load_test/56.com/www.56.com/index.html
|
||||
[PERF],navigationStart,1460358376
|
||||
|
@ -36,38 +36,40 @@ def test_log_parser():
|
|||
[PERF],loadEventEnd,undefined
|
||||
[PERF] perf block end
|
||||
Shutting down the Constellation after generating an output file or exit flag specified
|
||||
'''
|
||||
"""
|
||||
|
||||
expected = [{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"navigationStart": 1460358376,
|
||||
"unloadEventStart": None,
|
||||
"unloadEventEnd": None,
|
||||
"redirectStart": None,
|
||||
"redirectEnd": None,
|
||||
"fetchStart": None,
|
||||
"domainLookupStart": None,
|
||||
"domainLookupEnd": None,
|
||||
"connectStart": None,
|
||||
"connectEnd": None,
|
||||
"secureConnectionStart": None,
|
||||
"requestStart": None,
|
||||
"responseStart": None,
|
||||
"responseEnd": None,
|
||||
"domLoading": 1460358376000,
|
||||
"domInteractive": 1460358388000,
|
||||
"domContentLoadedEventStart": 1460358388000,
|
||||
"domContentLoadedEventEnd": 1460358388000,
|
||||
"domComplete": 1460358389000,
|
||||
"loadEventStart": None,
|
||||
"loadEventEnd": None
|
||||
}]
|
||||
expected = [
|
||||
{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"navigationStart": 1460358376,
|
||||
"unloadEventStart": None,
|
||||
"unloadEventEnd": None,
|
||||
"redirectStart": None,
|
||||
"redirectEnd": None,
|
||||
"fetchStart": None,
|
||||
"domainLookupStart": None,
|
||||
"domainLookupEnd": None,
|
||||
"connectStart": None,
|
||||
"connectEnd": None,
|
||||
"secureConnectionStart": None,
|
||||
"requestStart": None,
|
||||
"responseStart": None,
|
||||
"responseEnd": None,
|
||||
"domLoading": 1460358376000,
|
||||
"domInteractive": 1460358388000,
|
||||
"domContentLoadedEventStart": 1460358388000,
|
||||
"domContentLoadedEventEnd": 1460358388000,
|
||||
"domComplete": 1460358389000,
|
||||
"loadEventStart": None,
|
||||
"loadEventEnd": None,
|
||||
}
|
||||
]
|
||||
result = runner.parse_log(mock_log, mock_url)
|
||||
assert (expected == list(result))
|
||||
assert expected == list(result)
|
||||
|
||||
|
||||
def test_log_parser_complex():
|
||||
mock_log = b'''
|
||||
mock_log = b"""
|
||||
[PERF] perf block start
|
||||
[PERF],testcase,http://localhost:8000/page_load_test/56.com/www.56.com/content.html
|
||||
[PERF],navigationStart,1460358300
|
||||
|
@ -119,38 +121,40 @@ Some other js error logs here
|
|||
[PERF],loadEventEnd,undefined
|
||||
[PERF] perf block end
|
||||
Shutting down the Constellation after generating an output file or exit flag specified
|
||||
'''
|
||||
"""
|
||||
mock_url = "http://localhost:8000/page_load_test/56.com/www.56.com/index.html"
|
||||
expected = [{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"navigationStart": 1460358376,
|
||||
"unloadEventStart": None,
|
||||
"unloadEventEnd": None,
|
||||
"redirectStart": None,
|
||||
"redirectEnd": None,
|
||||
"fetchStart": None,
|
||||
"domainLookupStart": None,
|
||||
"domainLookupEnd": None,
|
||||
"connectStart": None,
|
||||
"connectEnd": None,
|
||||
"secureConnectionStart": None,
|
||||
"requestStart": None,
|
||||
"responseStart": None,
|
||||
"responseEnd": None,
|
||||
"domLoading": 1460358376000,
|
||||
"domInteractive": 1460358388000,
|
||||
"domContentLoadedEventStart": 1460358388000,
|
||||
"domContentLoadedEventEnd": 1460358388000,
|
||||
"domComplete": 1460358389000,
|
||||
"loadEventStart": None,
|
||||
"loadEventEnd": None
|
||||
}]
|
||||
expected = [
|
||||
{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"navigationStart": 1460358376,
|
||||
"unloadEventStart": None,
|
||||
"unloadEventEnd": None,
|
||||
"redirectStart": None,
|
||||
"redirectEnd": None,
|
||||
"fetchStart": None,
|
||||
"domainLookupStart": None,
|
||||
"domainLookupEnd": None,
|
||||
"connectStart": None,
|
||||
"connectEnd": None,
|
||||
"secureConnectionStart": None,
|
||||
"requestStart": None,
|
||||
"responseStart": None,
|
||||
"responseEnd": None,
|
||||
"domLoading": 1460358376000,
|
||||
"domInteractive": 1460358388000,
|
||||
"domContentLoadedEventStart": 1460358388000,
|
||||
"domContentLoadedEventEnd": 1460358388000,
|
||||
"domComplete": 1460358389000,
|
||||
"loadEventStart": None,
|
||||
"loadEventEnd": None,
|
||||
}
|
||||
]
|
||||
result = runner.parse_log(mock_log, mock_url)
|
||||
assert (expected == list(result))
|
||||
assert expected == list(result)
|
||||
|
||||
|
||||
def test_log_parser_empty():
|
||||
mock_log = b'''
|
||||
mock_log = b"""
|
||||
[PERF] perf block start
|
||||
[PERF]BROKEN!!!!!!!!!1
|
||||
[PERF]BROKEN!!!!!!!!!1
|
||||
|
@ -158,75 +162,79 @@ def test_log_parser_empty():
|
|||
[PERF]BROKEN!!!!!!!!!1
|
||||
[PERF]BROKEN!!!!!!!!!1
|
||||
[PERF] perf block end
|
||||
'''
|
||||
"""
|
||||
mock_testcase = "http://localhost:8000/page_load_test/56.com/www.56.com/index.html"
|
||||
|
||||
expected = [{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"title": "",
|
||||
"navigationStart": 0,
|
||||
"unloadEventStart": -1,
|
||||
"unloadEventEnd": -1,
|
||||
"redirectStart": -1,
|
||||
"redirectEnd": -1,
|
||||
"fetchStart": -1,
|
||||
"domainLookupStart": -1,
|
||||
"domainLookupEnd": -1,
|
||||
"connectStart": -1,
|
||||
"connectEnd": -1,
|
||||
"secureConnectionStart": -1,
|
||||
"requestStart": -1,
|
||||
"responseStart": -1,
|
||||
"responseEnd": -1,
|
||||
"domLoading": -1,
|
||||
"domInteractive": -1,
|
||||
"domContentLoadedEventStart": -1,
|
||||
"domContentLoadedEventEnd": -1,
|
||||
"domComplete": -1,
|
||||
"loadEventStart": -1,
|
||||
"loadEventEnd": -1
|
||||
}]
|
||||
expected = [
|
||||
{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"title": "",
|
||||
"navigationStart": 0,
|
||||
"unloadEventStart": -1,
|
||||
"unloadEventEnd": -1,
|
||||
"redirectStart": -1,
|
||||
"redirectEnd": -1,
|
||||
"fetchStart": -1,
|
||||
"domainLookupStart": -1,
|
||||
"domainLookupEnd": -1,
|
||||
"connectStart": -1,
|
||||
"connectEnd": -1,
|
||||
"secureConnectionStart": -1,
|
||||
"requestStart": -1,
|
||||
"responseStart": -1,
|
||||
"responseEnd": -1,
|
||||
"domLoading": -1,
|
||||
"domInteractive": -1,
|
||||
"domContentLoadedEventStart": -1,
|
||||
"domContentLoadedEventEnd": -1,
|
||||
"domComplete": -1,
|
||||
"loadEventStart": -1,
|
||||
"loadEventEnd": -1,
|
||||
}
|
||||
]
|
||||
result = runner.parse_log(mock_log, mock_testcase)
|
||||
assert (expected == list(result))
|
||||
assert expected == list(result)
|
||||
|
||||
|
||||
def test_log_parser_error():
|
||||
mock_log = b'Nothing here! Test failed!'
|
||||
mock_log = b"Nothing here! Test failed!"
|
||||
mock_testcase = "http://localhost:8000/page_load_test/56.com/www.56.com/index.html"
|
||||
|
||||
expected = [{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"title": "",
|
||||
"navigationStart": 0,
|
||||
"unloadEventStart": -1,
|
||||
"unloadEventEnd": -1,
|
||||
"redirectStart": -1,
|
||||
"redirectEnd": -1,
|
||||
"fetchStart": -1,
|
||||
"domainLookupStart": -1,
|
||||
"domainLookupEnd": -1,
|
||||
"connectStart": -1,
|
||||
"connectEnd": -1,
|
||||
"secureConnectionStart": -1,
|
||||
"requestStart": -1,
|
||||
"responseStart": -1,
|
||||
"responseEnd": -1,
|
||||
"domLoading": -1,
|
||||
"domInteractive": -1,
|
||||
"domContentLoadedEventStart": -1,
|
||||
"domContentLoadedEventEnd": -1,
|
||||
"domComplete": -1,
|
||||
"loadEventStart": -1,
|
||||
"loadEventEnd": -1
|
||||
}]
|
||||
expected = [
|
||||
{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"title": "",
|
||||
"navigationStart": 0,
|
||||
"unloadEventStart": -1,
|
||||
"unloadEventEnd": -1,
|
||||
"redirectStart": -1,
|
||||
"redirectEnd": -1,
|
||||
"fetchStart": -1,
|
||||
"domainLookupStart": -1,
|
||||
"domainLookupEnd": -1,
|
||||
"connectStart": -1,
|
||||
"connectEnd": -1,
|
||||
"secureConnectionStart": -1,
|
||||
"requestStart": -1,
|
||||
"responseStart": -1,
|
||||
"responseEnd": -1,
|
||||
"domLoading": -1,
|
||||
"domInteractive": -1,
|
||||
"domContentLoadedEventStart": -1,
|
||||
"domContentLoadedEventEnd": -1,
|
||||
"domComplete": -1,
|
||||
"loadEventStart": -1,
|
||||
"loadEventEnd": -1,
|
||||
}
|
||||
]
|
||||
result = runner.parse_log(mock_log, mock_testcase)
|
||||
assert (expected == list(result))
|
||||
assert expected == list(result)
|
||||
|
||||
|
||||
def test_log_parser_bad_testcase_name():
|
||||
mock_testcase = "http://localhost:8000/page_load_test/56.com/www.56.com/index.html"
|
||||
# Notice the testcase is about:blank, servo crashed
|
||||
mock_log = b'''
|
||||
mock_log = b"""
|
||||
[PERF] perf block start
|
||||
[PERF],testcase,about:blank
|
||||
[PERF],navigationStart,1460358376
|
||||
|
@ -252,182 +260,196 @@ def test_log_parser_bad_testcase_name():
|
|||
[PERF],loadEventEnd,undefined
|
||||
[PERF] perf block end
|
||||
Shutting down the Constellation after generating an output file or exit flag specified
|
||||
'''
|
||||
"""
|
||||
|
||||
expected = [{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"title": "",
|
||||
"navigationStart": 0,
|
||||
"unloadEventStart": -1,
|
||||
"unloadEventEnd": -1,
|
||||
"redirectStart": -1,
|
||||
"redirectEnd": -1,
|
||||
"fetchStart": -1,
|
||||
"domainLookupStart": -1,
|
||||
"domainLookupEnd": -1,
|
||||
"connectStart": -1,
|
||||
"connectEnd": -1,
|
||||
"secureConnectionStart": -1,
|
||||
"requestStart": -1,
|
||||
"responseStart": -1,
|
||||
"responseEnd": -1,
|
||||
"domLoading": -1,
|
||||
"domInteractive": -1,
|
||||
"domContentLoadedEventStart": -1,
|
||||
"domContentLoadedEventEnd": -1,
|
||||
"domComplete": -1,
|
||||
"loadEventStart": -1,
|
||||
"loadEventEnd": -1
|
||||
}]
|
||||
expected = [
|
||||
{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"title": "",
|
||||
"navigationStart": 0,
|
||||
"unloadEventStart": -1,
|
||||
"unloadEventEnd": -1,
|
||||
"redirectStart": -1,
|
||||
"redirectEnd": -1,
|
||||
"fetchStart": -1,
|
||||
"domainLookupStart": -1,
|
||||
"domainLookupEnd": -1,
|
||||
"connectStart": -1,
|
||||
"connectEnd": -1,
|
||||
"secureConnectionStart": -1,
|
||||
"requestStart": -1,
|
||||
"responseStart": -1,
|
||||
"responseEnd": -1,
|
||||
"domLoading": -1,
|
||||
"domInteractive": -1,
|
||||
"domContentLoadedEventStart": -1,
|
||||
"domContentLoadedEventEnd": -1,
|
||||
"domComplete": -1,
|
||||
"loadEventStart": -1,
|
||||
"loadEventEnd": -1,
|
||||
}
|
||||
]
|
||||
result = runner.parse_log(mock_log, mock_testcase)
|
||||
assert (expected == list(result))
|
||||
assert expected == list(result)
|
||||
|
||||
|
||||
def test_manifest_loader():
|
||||
|
||||
text = '''
|
||||
text = """
|
||||
http://localhost/page_load_test/tp5n/163.com/www.163.com/index.html
|
||||
http://localhost/page_load_test/tp5n/56.com/www.56.com/index.html
|
||||
|
||||
http://localhost/page_load_test/tp5n/aljazeera.net/aljazeera.net/portal.html
|
||||
# Disabled! http://localhost/page_load_test/tp5n/aljazeera.net/aljazeera.net/portal.html
|
||||
'''
|
||||
"""
|
||||
expected = [
|
||||
("http://localhost/page_load_test/tp5n/163.com/www.163.com/index.html", False),
|
||||
("http://localhost/page_load_test/tp5n/56.com/www.56.com/index.html", False),
|
||||
("http://localhost/page_load_test/tp5n/aljazeera.net/aljazeera.net/portal.html", False)
|
||||
("http://localhost/page_load_test/tp5n/aljazeera.net/aljazeera.net/portal.html", False),
|
||||
]
|
||||
assert (expected == list(runner.parse_manifest(text)))
|
||||
assert expected == list(runner.parse_manifest(text))
|
||||
|
||||
|
||||
def test_manifest_loader_async():
|
||||
|
||||
text = '''
|
||||
text = """
|
||||
http://localhost/page_load_test/tp5n/163.com/www.163.com/index.html
|
||||
async http://localhost/page_load_test/tp5n/56.com/www.56.com/index.html
|
||||
'''
|
||||
"""
|
||||
expected = [
|
||||
("http://localhost/page_load_test/tp5n/163.com/www.163.com/index.html", False),
|
||||
("http://localhost/page_load_test/tp5n/56.com/www.56.com/index.html", True),
|
||||
]
|
||||
assert (expected == list(runner.parse_manifest(text)))
|
||||
assert expected == list(runner.parse_manifest(text))
|
||||
|
||||
|
||||
def test_filter_result_by_manifest():
|
||||
input_json = [{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/content.html",
|
||||
"domComplete": 1460358389000,
|
||||
}, {
|
||||
"testcase": "non-existing-html",
|
||||
"domComplete": 1460358389000,
|
||||
}, {
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389000,
|
||||
}]
|
||||
|
||||
expected = [{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389000,
|
||||
}]
|
||||
|
||||
manifest = [
|
||||
("http://localhost:8000/page_load_test/56.com/www.56.com/index.html", False)
|
||||
input_json = [
|
||||
{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/content.html",
|
||||
"domComplete": 1460358389000,
|
||||
},
|
||||
{
|
||||
"testcase": "non-existing-html",
|
||||
"domComplete": 1460358389000,
|
||||
},
|
||||
{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389000,
|
||||
},
|
||||
]
|
||||
|
||||
assert (expected == runner.filter_result_by_manifest(input_json, manifest))
|
||||
expected = [
|
||||
{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389000,
|
||||
}
|
||||
]
|
||||
|
||||
manifest = [("http://localhost:8000/page_load_test/56.com/www.56.com/index.html", False)]
|
||||
|
||||
assert expected == runner.filter_result_by_manifest(input_json, manifest)
|
||||
|
||||
|
||||
def test_filter_result_by_manifest_error():
|
||||
input_json = [{
|
||||
"testcase": "1.html",
|
||||
"domComplete": 1460358389000,
|
||||
}]
|
||||
|
||||
manifest = [
|
||||
("1.html", False),
|
||||
("2.html", False)
|
||||
input_json = [
|
||||
{
|
||||
"testcase": "1.html",
|
||||
"domComplete": 1460358389000,
|
||||
}
|
||||
]
|
||||
|
||||
manifest = [("1.html", False), ("2.html", False)]
|
||||
|
||||
with pytest.raises(Exception) as execinfo:
|
||||
runner.filter_result_by_manifest(input_json, manifest)
|
||||
assert "Missing test result" in str(execinfo.value)
|
||||
|
||||
|
||||
def test_take_result_median_odd():
|
||||
input_json = [{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389001,
|
||||
"domLoading": 1460358380002
|
||||
}, {
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389002,
|
||||
"domLoading": 1460358380001
|
||||
}, {
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389003,
|
||||
"domLoading": 1460358380003
|
||||
}]
|
||||
input_json = [
|
||||
{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389001,
|
||||
"domLoading": 1460358380002,
|
||||
},
|
||||
{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389002,
|
||||
"domLoading": 1460358380001,
|
||||
},
|
||||
{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389003,
|
||||
"domLoading": 1460358380003,
|
||||
},
|
||||
]
|
||||
|
||||
expected = [{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389002,
|
||||
"domLoading": 1460358380002
|
||||
}]
|
||||
expected = [
|
||||
{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389002,
|
||||
"domLoading": 1460358380002,
|
||||
}
|
||||
]
|
||||
|
||||
assert (expected == runner.take_result_median(input_json, len(input_json)))
|
||||
assert expected == runner.take_result_median(input_json, len(input_json))
|
||||
|
||||
|
||||
def test_take_result_median_even():
|
||||
input_json = [{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389001,
|
||||
"domLoading": 1460358380002
|
||||
}, {
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389002,
|
||||
"domLoading": 1460358380001
|
||||
}]
|
||||
input_json = [
|
||||
{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389001,
|
||||
"domLoading": 1460358380002,
|
||||
},
|
||||
{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389002,
|
||||
"domLoading": 1460358380001,
|
||||
},
|
||||
]
|
||||
|
||||
expected = [{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389001.5,
|
||||
"domLoading": 1460358380001.5
|
||||
}]
|
||||
expected = [
|
||||
{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389001.5,
|
||||
"domLoading": 1460358380001.5,
|
||||
}
|
||||
]
|
||||
|
||||
assert (expected == runner.take_result_median(input_json, len(input_json)))
|
||||
assert expected == runner.take_result_median(input_json, len(input_json))
|
||||
|
||||
|
||||
def test_take_result_median_error():
|
||||
input_json = [{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": None,
|
||||
"domLoading": 1460358380002
|
||||
}, {
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389002,
|
||||
"domLoading": 1460358380001
|
||||
}]
|
||||
input_json = [
|
||||
{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": None,
|
||||
"domLoading": 1460358380002,
|
||||
},
|
||||
{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389002,
|
||||
"domLoading": 1460358380001,
|
||||
},
|
||||
]
|
||||
|
||||
expected = [{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389002,
|
||||
"domLoading": 1460358380001.5
|
||||
}]
|
||||
expected = [
|
||||
{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": 1460358389002,
|
||||
"domLoading": 1460358380001.5,
|
||||
}
|
||||
]
|
||||
|
||||
assert (expected == runner.take_result_median(input_json, len(input_json)))
|
||||
assert expected == runner.take_result_median(input_json, len(input_json))
|
||||
|
||||
|
||||
def test_log_result():
|
||||
results = [{
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": -1
|
||||
}, {
|
||||
"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html",
|
||||
"domComplete": -1
|
||||
}, {
|
||||
"testcase": "http://localhost:8000/page_load_test/104.com/www.104.com/index.html",
|
||||
"domComplete": 123456789
|
||||
}]
|
||||
results = [
|
||||
{"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html", "domComplete": -1},
|
||||
{"testcase": "http://localhost:8000/page_load_test/56.com/www.56.com/index.html", "domComplete": -1},
|
||||
{"testcase": "http://localhost:8000/page_load_test/104.com/www.104.com/index.html", "domComplete": 123456789},
|
||||
]
|
||||
|
||||
expected = """
|
||||
========================================
|
||||
|
@ -437,4 +459,4 @@ Failure summary:
|
|||
- http://localhost:8000/page_load_test/56.com/www.56.com/index.html
|
||||
========================================
|
||||
"""
|
||||
assert (expected == runner.format_result_summary(results))
|
||||
assert expected == runner.format_result_summary(results)
|
||||
|
|
|
@ -8,18 +8,18 @@ import submit_to_perfherder
|
|||
|
||||
|
||||
def test_format_testcase_name():
|
||||
assert ('about:blank' == submit_to_perfherder.format_testcase_name(
|
||||
'about:blank'))
|
||||
assert ('163.com' == submit_to_perfherder.format_testcase_name((
|
||||
'http://localhost:8000/page_load_test/163.com/p.mail.163.com/'
|
||||
'mailinfo/shownewmsg_www_1222.htm.html')))
|
||||
assert (('1234567890223456789032345678904234567890'
|
||||
'5234567890623456789072345678908234567890')
|
||||
== submit_to_perfherder.format_testcase_name((
|
||||
'1234567890223456789032345678904234567890'
|
||||
'52345678906234567890723456789082345678909234567890')))
|
||||
assert ('news.ycombinator.com' == submit_to_perfherder.format_testcase_name(
|
||||
'http://localhost:8000/tp6/news.ycombinator.com/index.html'))
|
||||
assert "about:blank" == submit_to_perfherder.format_testcase_name("about:blank")
|
||||
assert "163.com" == submit_to_perfherder.format_testcase_name(
|
||||
("http://localhost:8000/page_load_test/163.com/p.mail.163.com/mailinfo/shownewmsg_www_1222.htm.html")
|
||||
)
|
||||
assert (
|
||||
"12345678902234567890323456789042345678905234567890623456789072345678908234567890"
|
||||
) == submit_to_perfherder.format_testcase_name(
|
||||
("123456789022345678903234567890423456789052345678906234567890723456789082345678909234567890")
|
||||
)
|
||||
assert "news.ycombinator.com" == submit_to_perfherder.format_testcase_name(
|
||||
"http://localhost:8000/tp6/news.ycombinator.com/index.html"
|
||||
)
|
||||
|
||||
|
||||
def test_format_perf_data():
|
||||
|
@ -46,7 +46,7 @@ def test_format_perf_data():
|
|||
"unloadEventEnd": None,
|
||||
"responseEnd": None,
|
||||
"testcase": "about:blank",
|
||||
"domComplete": 1460444931000
|
||||
"domComplete": 1460444931000,
|
||||
},
|
||||
{
|
||||
"unloadEventStart": None,
|
||||
|
@ -69,11 +69,11 @@ def test_format_perf_data():
|
|||
"domainLookupEnd": None,
|
||||
"unloadEventEnd": None,
|
||||
"responseEnd": None,
|
||||
"testcase": ("http://localhost:8000/page_load_test/163.com/"
|
||||
"p.mail.163.com/mailinfo/"
|
||||
"shownewmsg_www_1222.htm.html"),
|
||||
"domComplete": 1460444948000
|
||||
}
|
||||
"testcase": (
|
||||
"http://localhost:8000/page_load_test/163.com/p.mail.163.com/mailinfo/shownewmsg_www_1222.htm.html"
|
||||
),
|
||||
"domComplete": 1460444948000,
|
||||
},
|
||||
]
|
||||
|
||||
expected = {
|
||||
|
@ -84,33 +84,27 @@ def test_format_perf_data():
|
|||
"name": "domComplete",
|
||||
"value": 3741.657386773941,
|
||||
"subtests": [
|
||||
{"name": "about:blank",
|
||||
"value": 1000},
|
||||
{"name": "163.com",
|
||||
"value": 14000},
|
||||
]
|
||||
{"name": "about:blank", "value": 1000},
|
||||
{"name": "163.com", "value": 14000},
|
||||
],
|
||||
}
|
||||
]
|
||||
],
|
||||
}
|
||||
}
|
||||
result = submit_to_perfherder.format_perf_data(mock_result)
|
||||
assert (expected == result)
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_format_bad_perf_data():
|
||||
mock_result = [
|
||||
{
|
||||
"navigationStart": 1460444930000,
|
||||
"testcase": "about:blank",
|
||||
"domComplete": 0
|
||||
},
|
||||
{"navigationStart": 1460444930000, "testcase": "about:blank", "domComplete": 0},
|
||||
{
|
||||
"navigationStart": 1460444934000,
|
||||
"testcase": ("http://localhost:8000/page_load_test/163.com/"
|
||||
"p.mail.163.com/mailinfo/"
|
||||
"shownewmsg_www_1222.htm.html"),
|
||||
"domComplete": 1460444948000
|
||||
}
|
||||
"testcase": (
|
||||
"http://localhost:8000/page_load_test/163.com/p.mail.163.com/mailinfo/shownewmsg_www_1222.htm.html"
|
||||
),
|
||||
"domComplete": 1460444948000,
|
||||
},
|
||||
]
|
||||
|
||||
expected = {
|
||||
|
@ -121,14 +115,12 @@ def test_format_bad_perf_data():
|
|||
"name": "domComplete",
|
||||
"value": 14000.0,
|
||||
"subtests": [
|
||||
{"name": "about:blank",
|
||||
"value": -1}, # Timeout
|
||||
{"name": "163.com",
|
||||
"value": 14000},
|
||||
]
|
||||
{"name": "about:blank", "value": -1}, # Timeout
|
||||
{"name": "163.com", "value": 14000},
|
||||
],
|
||||
}
|
||||
]
|
||||
],
|
||||
}
|
||||
}
|
||||
result = submit_to_perfherder.format_perf_data(mock_result)
|
||||
assert (expected == result)
|
||||
assert expected == result
|
||||
|
|
|
@ -37,7 +37,7 @@ class Item:
|
|||
def from_result(cls, result: dict, title: Optional[str] = None, print_stack=True):
|
||||
expected = result["expected"]
|
||||
actual = result["actual"]
|
||||
title = title if title else f'`{result["path"]}`'
|
||||
title = title if title else f"`{result['path']}`"
|
||||
if expected != actual:
|
||||
title = f"{actual} [expected {expected}] {title}"
|
||||
else:
|
||||
|
@ -45,8 +45,7 @@ class Item:
|
|||
|
||||
issue_url = "http://github.com/servo/servo/issues/"
|
||||
if "issues" in result and result["issues"]:
|
||||
issues = ", ".join([f"[#{issue}]({issue_url}{issue})"
|
||||
for issue in result["issues"]])
|
||||
issues = ", ".join([f"[#{issue}]({issue_url}{issue})" for issue in result["issues"]])
|
||||
title += f" ({issues})"
|
||||
|
||||
stack = result["stack"] if result["stack"] and print_stack else ""
|
||||
|
@ -59,8 +58,9 @@ class Item:
|
|||
cls.from_result(
|
||||
subtest_result,
|
||||
f"subtest: `{subtest_result['subtest']}`"
|
||||
+ (f" \n```\n{subtest_result['message']}\n```\n" if subtest_result['message'] else ""),
|
||||
False)
|
||||
+ (f" \n```\n{subtest_result['message']}\n```\n" if subtest_result["message"] else ""),
|
||||
False,
|
||||
)
|
||||
for subtest_result in subtest_results
|
||||
]
|
||||
return cls(title, body, children)
|
||||
|
@ -68,10 +68,8 @@ class Item:
|
|||
def to_string(self, bullet: str = "", indent: str = ""):
|
||||
output = f"{indent}{bullet}{self.title}\n"
|
||||
if self.body:
|
||||
output += textwrap.indent(f"{self.body}\n",
|
||||
" " * len(indent + bullet))
|
||||
output += "\n".join([child.to_string("• ", indent + " ")
|
||||
for child in self.children])
|
||||
output += textwrap.indent(f"{self.body}\n", " " * len(indent + bullet))
|
||||
output += "\n".join([child.to_string("• ", indent + " ") for child in self.children])
|
||||
return output.rstrip().replace("`", "")
|
||||
|
||||
def to_html(self, level: int = 0) -> ElementTree.Element:
|
||||
|
@ -88,17 +86,13 @@ class Item:
|
|||
if self.children:
|
||||
# Some tests have dozens of failing tests, which overwhelm the
|
||||
# output. Limit the output for subtests in GitHub comment output.
|
||||
max_children = len(
|
||||
self.children) if level < 2 else SUBTEST_RESULT_TRUNCATION
|
||||
max_children = len(self.children) if level < 2 else SUBTEST_RESULT_TRUNCATION
|
||||
if len(self.children) > max_children:
|
||||
children = self.children[:max_children]
|
||||
children.append(Item(
|
||||
f"And {len(self.children) - max_children} more unexpected results...",
|
||||
"", []))
|
||||
children.append(Item(f"And {len(self.children) - max_children} more unexpected results...", "", []))
|
||||
else:
|
||||
children = self.children
|
||||
container = ElementTree.SubElement(
|
||||
result, "div" if not level else "ul")
|
||||
container = ElementTree.SubElement(result, "div" if not level else "ul")
|
||||
for child in children:
|
||||
container.append(child.to_html(level + 1))
|
||||
|
||||
|
@ -125,17 +119,16 @@ def get_results(filenames: list[str], tag: str = "") -> Optional[Item]:
|
|||
return not is_flaky(result) and not result["issues"]
|
||||
|
||||
def add_children(children: List[Item], results: List[dict], filter_func, text):
|
||||
filtered = [Item.from_result(result) for result in
|
||||
filter(filter_func, results)]
|
||||
filtered = [Item.from_result(result) for result in filter(filter_func, results)]
|
||||
if filtered:
|
||||
children.append(Item(f"{text} ({len(filtered)})", "", filtered))
|
||||
|
||||
children: List[Item] = []
|
||||
add_children(children, unexpected, is_flaky, "Flaky unexpected result")
|
||||
add_children(children, unexpected, is_stable_and_known,
|
||||
"Stable unexpected results that are known to be intermittent")
|
||||
add_children(children, unexpected, is_stable_and_unexpected,
|
||||
"Stable unexpected results")
|
||||
add_children(
|
||||
children, unexpected, is_stable_and_known, "Stable unexpected results that are known to be intermittent"
|
||||
)
|
||||
add_children(children, unexpected, is_stable_and_unexpected, "Stable unexpected results")
|
||||
|
||||
run_url = get_github_run_url()
|
||||
text = "Test results"
|
||||
|
@ -154,8 +147,8 @@ def get_github_run_url() -> Optional[str]:
|
|||
return None
|
||||
if "run_id" not in github_context:
|
||||
return None
|
||||
repository = github_context['repository']
|
||||
run_id = github_context['run_id']
|
||||
repository = github_context["repository"]
|
||||
run_id = github_context["run_id"]
|
||||
return f"[#{run_id}](https://github.com/{repository}/actions/runs/{run_id})"
|
||||
|
||||
|
||||
|
@ -197,14 +190,14 @@ def create_github_reports(body: str, tag: str = ""):
|
|||
# This process is based on the documentation here:
|
||||
# https://docs.github.com/en/rest/checks/runs?apiVersion=2022-11-28#create-a-check-runs
|
||||
results = json.loads(os.environ.get("RESULTS", "{}"))
|
||||
if all(r == 'success' for r in results):
|
||||
conclusion = 'success'
|
||||
if all(r == "success" for r in results):
|
||||
conclusion = "success"
|
||||
elif "failure" in results:
|
||||
conclusion = 'failure'
|
||||
conclusion = "failure"
|
||||
elif "cancelled" in results:
|
||||
conclusion = 'cancelled'
|
||||
conclusion = "cancelled"
|
||||
else:
|
||||
conclusion = 'neutral'
|
||||
conclusion = "neutral"
|
||||
|
||||
github_token = os.environ.get("GITHUB_TOKEN")
|
||||
github_context = json.loads(os.environ.get("GITHUB_CONTEXT", "{}"))
|
||||
|
@ -214,34 +207,42 @@ def create_github_reports(body: str, tag: str = ""):
|
|||
return None
|
||||
repo = github_context["repository"]
|
||||
data = {
|
||||
'name': tag,
|
||||
'head_sha': github_context["sha"],
|
||||
'status': 'completed',
|
||||
'started_at': datetime.utcnow().replace(microsecond=0).isoformat() + "Z",
|
||||
'conclusion': conclusion,
|
||||
'completed_at': datetime.utcnow().replace(microsecond=0).isoformat() + "Z",
|
||||
'output': {
|
||||
'title': f'Aggregated {tag} report',
|
||||
'summary': body,
|
||||
'images': [{'alt': 'WPT logo', 'image_url': 'https://avatars.githubusercontent.com/u/37226233'}]
|
||||
"name": tag,
|
||||
"head_sha": github_context["sha"],
|
||||
"status": "completed",
|
||||
"started_at": datetime.utcnow().replace(microsecond=0).isoformat() + "Z",
|
||||
"conclusion": conclusion,
|
||||
"completed_at": datetime.utcnow().replace(microsecond=0).isoformat() + "Z",
|
||||
"output": {
|
||||
"title": f"Aggregated {tag} report",
|
||||
"summary": body,
|
||||
"images": [{"alt": "WPT logo", "image_url": "https://avatars.githubusercontent.com/u/37226233"}],
|
||||
},
|
||||
'actions': [
|
||||
]
|
||||
"actions": [],
|
||||
}
|
||||
|
||||
subprocess.Popen(["curl", "-L",
|
||||
"-X", "POST",
|
||||
"-H", "Accept: application/vnd.github+json",
|
||||
"-H", f"Authorization: Bearer {github_token}",
|
||||
"-H", "X-GitHub-Api-Version: 2022-11-28",
|
||||
f"https://api.github.com/repos/{repo}/check-runs",
|
||||
"-d", json.dumps(data)]).wait()
|
||||
subprocess.Popen(
|
||||
[
|
||||
"curl",
|
||||
"-L",
|
||||
"-X",
|
||||
"POST",
|
||||
"-H",
|
||||
"Accept: application/vnd.github+json",
|
||||
"-H",
|
||||
f"Authorization: Bearer {github_token}",
|
||||
"-H",
|
||||
"X-GitHub-Api-Version: 2022-11-28",
|
||||
f"https://api.github.com/repos/{repo}/check-runs",
|
||||
"-d",
|
||||
json.dumps(data),
|
||||
]
|
||||
).wait()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--tag", default="wpt", action="store",
|
||||
help="A string tag used to distinguish the results.")
|
||||
parser.add_argument("--tag", default="wpt", action="store", help="A string tag used to distinguish the results.")
|
||||
args, filenames = parser.parse_known_args()
|
||||
results = get_results(filenames, args.tag)
|
||||
if not results:
|
||||
|
@ -251,14 +252,12 @@ def main():
|
|||
|
||||
print(results.to_string())
|
||||
|
||||
html_string = ElementTree.tostring(
|
||||
results.to_html(), encoding="unicode")
|
||||
html_string = ElementTree.tostring(results.to_html(), encoding="unicode")
|
||||
create_github_reports(html_string, args.tag)
|
||||
|
||||
pr_number = get_pr_number()
|
||||
if pr_number:
|
||||
process = subprocess.Popen(
|
||||
['gh', 'pr', 'comment', pr_number, '-F', '-'], stdin=subprocess.PIPE)
|
||||
process = subprocess.Popen(["gh", "pr", "comment", pr_number, "-F", "-"], stdin=subprocess.PIPE)
|
||||
print(process.communicate(input=html_string.encode("utf-8"))[0])
|
||||
else:
|
||||
print("Could not find PR number in environment. Not making GitHub comment.")
|
||||
|
|
|
@ -35,6 +35,7 @@ def main(crate=None):
|
|||
for dependency in graph.get(name, []):
|
||||
filtered.setdefault(name, []).append(dependency)
|
||||
traverse(dependency)
|
||||
|
||||
traverse(crate)
|
||||
else:
|
||||
filtered = graph
|
||||
|
|
|
@ -42,12 +42,15 @@ import signal
|
|||
import sys
|
||||
from argparse import ArgumentParser
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
try:
|
||||
from termcolor import colored
|
||||
except ImportError:
|
||||
|
||||
def colored(text, *args, **kwargs):
|
||||
return text
|
||||
|
||||
|
||||
fields = ["frame.time", "tcp.srcport", "tcp.payload"]
|
||||
|
||||
|
||||
|
@ -57,10 +60,14 @@ def record_data(file, port):
|
|||
# Create tshark command
|
||||
cmd = [
|
||||
"tshark",
|
||||
"-T", "fields",
|
||||
"-i", "lo",
|
||||
"-d", f"tcp.port=={port},http",
|
||||
"-w", file,
|
||||
"-T",
|
||||
"fields",
|
||||
"-i",
|
||||
"lo",
|
||||
"-d",
|
||||
f"tcp.port=={port},http",
|
||||
"-w",
|
||||
file,
|
||||
] + [e for f in fields for e in ("-e", f)]
|
||||
process = Popen(cmd, stdout=PIPE)
|
||||
|
||||
|
@ -84,8 +91,10 @@ def read_data(file):
|
|||
# Create tshark command
|
||||
cmd = [
|
||||
"tshark",
|
||||
"-T", "fields",
|
||||
"-r", file,
|
||||
"-T",
|
||||
"fields",
|
||||
"-r",
|
||||
file,
|
||||
] + [e for f in fields for e in ("-e", f)]
|
||||
process = Popen(cmd, stdout=PIPE)
|
||||
|
||||
|
@ -182,7 +191,7 @@ def parse_message(msg, *, json_output=False):
|
|||
time, sender, i, data = msg
|
||||
from_servo = sender == "Servo"
|
||||
|
||||
colored_sender = colored(sender, 'black', 'on_yellow' if from_servo else 'on_magenta', attrs=['bold'])
|
||||
colored_sender = colored(sender, "black", "on_yellow" if from_servo else "on_magenta", attrs=["bold"])
|
||||
if not json_output:
|
||||
print(f"\n{colored_sender} - {colored(i, 'blue')} - {colored(time, 'dark_grey')}")
|
||||
|
||||
|
@ -199,7 +208,7 @@ def parse_message(msg, *, json_output=False):
|
|||
assert False, "Message is neither a request nor a response"
|
||||
else:
|
||||
if from_servo and "from" in content:
|
||||
print(colored(f"Actor: {content['from']}", 'yellow'))
|
||||
print(colored(f"Actor: {content['from']}", "yellow"))
|
||||
print(json.dumps(content, sort_keys=True, indent=4))
|
||||
except json.JSONDecodeError:
|
||||
print(f"Warning: Couldn't decode json\n{data}")
|
||||
|
@ -236,7 +245,7 @@ if __name__ == "__main__":
|
|||
if args.range and len(args.range.split(":")) == 2:
|
||||
min, max = args.range.split(":")
|
||||
|
||||
for msg in data[int(min):int(max) + 1]:
|
||||
for msg in data[int(min) : int(max) + 1]:
|
||||
# Filter the messages if specified
|
||||
if not args.filter or args.filter.lower() in msg[3].lower():
|
||||
parse_message(msg, json_output=args.json)
|
||||
|
|
|
@ -21,14 +21,14 @@ def extract_memory_reports(lines):
|
|||
report_lines = []
|
||||
times = []
|
||||
for line in lines:
|
||||
if line.startswith('Begin memory reports'):
|
||||
if line.startswith("Begin memory reports"):
|
||||
in_report = True
|
||||
report_lines += [[]]
|
||||
times += [line.strip().split()[-1]]
|
||||
elif line == 'End memory reports\n':
|
||||
elif line == "End memory reports\n":
|
||||
in_report = False
|
||||
elif in_report:
|
||||
if line.startswith('|'):
|
||||
if line.startswith("|"):
|
||||
report_lines[-1].append(line.strip())
|
||||
return (report_lines, times)
|
||||
|
||||
|
@ -38,11 +38,11 @@ def parse_memory_report(lines):
|
|||
parents = []
|
||||
last_separator_index = None
|
||||
for line in lines:
|
||||
assert (line[0] == '|')
|
||||
assert line[0] == "|"
|
||||
line = line[1:]
|
||||
if not line:
|
||||
continue
|
||||
separator_index = line.index('--')
|
||||
separator_index = line.index("--")
|
||||
if last_separator_index and separator_index <= last_separator_index:
|
||||
while parents and parents[-1][1] >= separator_index:
|
||||
parents.pop()
|
||||
|
@ -50,13 +50,9 @@ def parse_memory_report(lines):
|
|||
amount, unit, _, name = line.split()
|
||||
|
||||
dest_report = reports
|
||||
for (parent, index) in parents:
|
||||
dest_report = dest_report[parent]['children']
|
||||
dest_report[name] = {
|
||||
'amount': amount,
|
||||
'unit': unit,
|
||||
'children': {}
|
||||
}
|
||||
for parent, index in parents:
|
||||
dest_report = dest_report[parent]["children"]
|
||||
dest_report[name] = {"amount": amount, "unit": unit, "children": {}}
|
||||
|
||||
parents += [(name, separator_index)]
|
||||
last_separator_index = separator_index
|
||||
|
@ -68,24 +64,26 @@ def transform_report_for_test(report):
|
|||
remaining = list(report.items())
|
||||
while remaining:
|
||||
(name, value) = remaining.pop()
|
||||
transformed[name] = '%s %s' % (value['amount'], value['unit'])
|
||||
remaining += map(lambda k_v: (name + '/' + k_v[0], k_v[1]), list(value['children'].items()))
|
||||
transformed[name] = "%s %s" % (value["amount"], value["unit"])
|
||||
remaining += map(lambda k_v: (name + "/" + k_v[0], k_v[1]), list(value["children"].items()))
|
||||
return transformed
|
||||
|
||||
|
||||
def test_extract_memory_reports():
|
||||
input = ["Begin memory reports",
|
||||
"|",
|
||||
" 154.56 MiB -- explicit\n",
|
||||
"| 107.88 MiB -- system-heap-unclassified\n",
|
||||
"End memory reports\n"]
|
||||
expected = ([['|', '| 107.88 MiB -- system-heap-unclassified']], ['reports'])
|
||||
assert (extract_memory_reports(input) == expected)
|
||||
input = [
|
||||
"Begin memory reports",
|
||||
"|",
|
||||
" 154.56 MiB -- explicit\n",
|
||||
"| 107.88 MiB -- system-heap-unclassified\n",
|
||||
"End memory reports\n",
|
||||
]
|
||||
expected = ([["|", "| 107.88 MiB -- system-heap-unclassified"]], ["reports"])
|
||||
assert extract_memory_reports(input) == expected
|
||||
return 0
|
||||
|
||||
|
||||
def test():
|
||||
input = '''|
|
||||
input = """|
|
||||
| 23.89 MiB -- explicit
|
||||
| 21.35 MiB -- jemalloc-heap-unclassified
|
||||
| 2.54 MiB -- url(https://servo.org/)
|
||||
|
@ -97,33 +95,33 @@ def test():
|
|||
| 0.27 MiB -- stylist
|
||||
| 0.12 MiB -- dom-tree
|
||||
|
|
||||
| 25.18 MiB -- jemalloc-heap-active'''
|
||||
| 25.18 MiB -- jemalloc-heap-active"""
|
||||
|
||||
expected = {
|
||||
'explicit': '23.89 MiB',
|
||||
'explicit/jemalloc-heap-unclassified': '21.35 MiB',
|
||||
'explicit/url(https://servo.org/)': '2.54 MiB',
|
||||
'explicit/url(https://servo.org/)/js': '2.16 MiB',
|
||||
'explicit/url(https://servo.org/)/js/gc-heap': '1.00 MiB',
|
||||
'explicit/url(https://servo.org/)/js/gc-heap/decommitted': '0.77 MiB',
|
||||
'explicit/url(https://servo.org/)/js/non-heap': '1.00 MiB',
|
||||
'explicit/url(https://servo.org/)/layout-thread': '0.27 MiB',
|
||||
'explicit/url(https://servo.org/)/layout-thread/stylist': '0.27 MiB',
|
||||
'explicit/url(https://servo.org/)/dom-tree': '0.12 MiB',
|
||||
'jemalloc-heap-active': '25.18 MiB',
|
||||
"explicit": "23.89 MiB",
|
||||
"explicit/jemalloc-heap-unclassified": "21.35 MiB",
|
||||
"explicit/url(https://servo.org/)": "2.54 MiB",
|
||||
"explicit/url(https://servo.org/)/js": "2.16 MiB",
|
||||
"explicit/url(https://servo.org/)/js/gc-heap": "1.00 MiB",
|
||||
"explicit/url(https://servo.org/)/js/gc-heap/decommitted": "0.77 MiB",
|
||||
"explicit/url(https://servo.org/)/js/non-heap": "1.00 MiB",
|
||||
"explicit/url(https://servo.org/)/layout-thread": "0.27 MiB",
|
||||
"explicit/url(https://servo.org/)/layout-thread/stylist": "0.27 MiB",
|
||||
"explicit/url(https://servo.org/)/dom-tree": "0.12 MiB",
|
||||
"jemalloc-heap-active": "25.18 MiB",
|
||||
}
|
||||
report = parse_memory_report(input.split('\n'))
|
||||
report = parse_memory_report(input.split("\n"))
|
||||
transformed = transform_report_for_test(report)
|
||||
assert (sorted(transformed.keys()) == sorted(expected.keys()))
|
||||
assert sorted(transformed.keys()) == sorted(expected.keys())
|
||||
for k, v in transformed.items():
|
||||
assert (v == expected[k])
|
||||
assert v == expected[k]
|
||||
test_extract_memory_reports()
|
||||
return 0
|
||||
|
||||
|
||||
def usage():
|
||||
print('%s --test - run automated tests' % sys.argv[0])
|
||||
print('%s file - extract all memory reports that are present in file' % sys.argv[0])
|
||||
print("%s --test - run automated tests" % sys.argv[0])
|
||||
print("%s file - extract all memory reports that are present in file" % sys.argv[0])
|
||||
return 1
|
||||
|
||||
|
||||
|
@ -131,19 +129,19 @@ if __name__ == "__main__":
|
|||
if len(sys.argv) == 1:
|
||||
sys.exit(usage())
|
||||
|
||||
if sys.argv[1] == '--test':
|
||||
if sys.argv[1] == "--test":
|
||||
sys.exit(test())
|
||||
|
||||
with open(sys.argv[1]) as f:
|
||||
lines = f.readlines()
|
||||
(reports, times) = extract_memory_reports(lines)
|
||||
json_reports = []
|
||||
for (report_lines, seconds) in zip(reports, times):
|
||||
for report_lines, seconds in zip(reports, times):
|
||||
report = parse_memory_report(report_lines)
|
||||
json_reports += [{'seconds': seconds, 'report': report}]
|
||||
json_reports += [{"seconds": seconds, "report": report}]
|
||||
with tempfile.NamedTemporaryFile(delete=False) as output:
|
||||
thisdir = os.path.dirname(os.path.abspath(__file__))
|
||||
with open(os.path.join(thisdir, 'memory_chart.html')) as template:
|
||||
with open(os.path.join(thisdir, "memory_chart.html")) as template:
|
||||
content = template.read()
|
||||
output.write(content.replace('[/* json data */]', json.dumps(json_reports)))
|
||||
webbrowser.open_new_tab('file://' + output.name)
|
||||
output.write(content.replace("[/* json data */]", json.dumps(json_reports)))
|
||||
webbrowser.open_new_tab("file://" + output.name)
|
||||
|
|
|
@ -31,7 +31,7 @@ Example:
|
|||
""")
|
||||
sys.exit(0)
|
||||
|
||||
rust_source = open(sys.argv[1], 'r')
|
||||
rust_source = open(sys.argv[1], "r")
|
||||
lines = iter(rust_source)
|
||||
for line in lines:
|
||||
if line.lstrip().startswith("pub enum ProfilerCategory"):
|
||||
|
@ -53,21 +53,21 @@ plist = ElementTree.ElementTree(ElementTree.fromstring(xml))
|
|||
|
||||
elems = iter(plist.findall("./dict/*"))
|
||||
for elem in elems:
|
||||
if elem.tag != 'key' or elem.text != '$objects':
|
||||
if elem.tag != "key" or elem.text != "$objects":
|
||||
continue
|
||||
array = elems.next()
|
||||
break
|
||||
|
||||
elems = iter(array.findall("./*"))
|
||||
for elem in elems:
|
||||
if elem.tag != 'string' or elem.text != 'kdebugIntervalRule':
|
||||
if elem.tag != "string" or elem.text != "kdebugIntervalRule":
|
||||
continue
|
||||
dictionary = elems.next()
|
||||
break
|
||||
|
||||
elems = iter(dictionary.findall("./*"))
|
||||
for elem in elems:
|
||||
if elem.tag != 'key' or elem.text != 'NS.objects':
|
||||
if elem.tag != "key" or elem.text != "NS.objects":
|
||||
continue
|
||||
objects_array = elems.next()
|
||||
break
|
||||
|
@ -76,33 +76,33 @@ child_count = sum(1 for _ in iter(array.findall("./*")))
|
|||
|
||||
for code_pair in code_pairs:
|
||||
number_index = child_count
|
||||
integer = Element('integer')
|
||||
integer = Element("integer")
|
||||
integer.text = str(int(code_pair[0], 0))
|
||||
array.append(integer)
|
||||
child_count += 1
|
||||
|
||||
string_index = child_count
|
||||
string = Element('string')
|
||||
string = Element("string")
|
||||
string.text = code_pair[1]
|
||||
array.append(string)
|
||||
child_count += 1
|
||||
|
||||
dictionary = Element('dict')
|
||||
key = Element('key')
|
||||
dictionary = Element("dict")
|
||||
key = Element("key")
|
||||
key.text = "CF$UID"
|
||||
dictionary.append(key)
|
||||
integer = Element('integer')
|
||||
integer = Element("integer")
|
||||
integer.text = str(number_index)
|
||||
dictionary.append(integer)
|
||||
objects_array.append(dictionary)
|
||||
|
||||
dictionary = Element('dict')
|
||||
key = Element('key')
|
||||
dictionary = Element("dict")
|
||||
key = Element("key")
|
||||
key.text = "CF$UID"
|
||||
dictionary.append(key)
|
||||
integer = Element('integer')
|
||||
integer = Element("integer")
|
||||
integer.text = str(string_index)
|
||||
dictionary.append(integer)
|
||||
objects_array.append(dictionary)
|
||||
|
||||
plist.write(sys.stdout, encoding='utf-8', xml_declaration=True)
|
||||
plist.write(sys.stdout, encoding="utf-8", xml_declaration=True)
|
||||
|
|
|
@ -53,17 +53,17 @@ stacks = {}
|
|||
thread_data = defaultdict(list)
|
||||
thread_order = {}
|
||||
for sample in samples:
|
||||
if sample['name']:
|
||||
name = sample['name']
|
||||
if sample["name"]:
|
||||
name = sample["name"]
|
||||
else:
|
||||
name = "%s %d %d" % (sample['type'], sample['namespace'], sample['index'])
|
||||
thread_data[name].append((sample['time'], sample['frames']))
|
||||
name = "%s %d %d" % (sample["type"], sample["namespace"], sample["index"])
|
||||
thread_data[name].append((sample["time"], sample["frames"]))
|
||||
if name not in thread_order:
|
||||
thread_order[name] = (sample['namespace'], sample['index'])
|
||||
thread_order[name] = (sample["namespace"], sample["index"])
|
||||
|
||||
tid = 0
|
||||
threads = []
|
||||
for (name, raw_samples) in sorted(iter(thread_data.items()), key=lambda x: thread_order[x[0]]):
|
||||
for name, raw_samples in sorted(iter(thread_data.items()), key=lambda x: thread_order[x[0]]):
|
||||
string_table = StringTable()
|
||||
tid += 1
|
||||
|
||||
|
@ -77,13 +77,13 @@ for (name, raw_samples) in sorted(iter(thread_data.items()), key=lambda x: threa
|
|||
for sample in raw_samples:
|
||||
prefix = None
|
||||
for frame in sample[1]:
|
||||
if not frame['name']:
|
||||
if not frame["name"]:
|
||||
continue
|
||||
if frame['name'] not in frameMap:
|
||||
frameMap[frame['name']] = len(frames)
|
||||
frame_index = string_table.get(frame['name'])
|
||||
if frame["name"] not in frameMap:
|
||||
frameMap[frame["name"]] = len(frames)
|
||||
frame_index = string_table.get(frame["name"])
|
||||
frames.append([frame_index])
|
||||
frame = frameMap[frame['name']]
|
||||
frame = frameMap[frame["name"]]
|
||||
|
||||
stack_key = "%d,%d" % (frame, prefix) if prefix else str(frame)
|
||||
if stack_key not in stackMap:
|
||||
|
@ -93,61 +93,63 @@ for (name, raw_samples) in sorted(iter(thread_data.items()), key=lambda x: threa
|
|||
prefix = stack
|
||||
samples.append([stack, sample[0]])
|
||||
|
||||
threads.append({
|
||||
'tid': tid,
|
||||
'name': name,
|
||||
'markers': {
|
||||
'schema': {
|
||||
'name': 0,
|
||||
'time': 1,
|
||||
'data': 2,
|
||||
threads.append(
|
||||
{
|
||||
"tid": tid,
|
||||
"name": name,
|
||||
"markers": {
|
||||
"schema": {
|
||||
"name": 0,
|
||||
"time": 1,
|
||||
"data": 2,
|
||||
},
|
||||
"data": [],
|
||||
},
|
||||
'data': [],
|
||||
},
|
||||
'samples': {
|
||||
'schema': {
|
||||
'stack': 0,
|
||||
'time': 1,
|
||||
'responsiveness': 2,
|
||||
'rss': 2,
|
||||
'uss': 4,
|
||||
'frameNumber': 5,
|
||||
"samples": {
|
||||
"schema": {
|
||||
"stack": 0,
|
||||
"time": 1,
|
||||
"responsiveness": 2,
|
||||
"rss": 2,
|
||||
"uss": 4,
|
||||
"frameNumber": 5,
|
||||
},
|
||||
"data": samples,
|
||||
},
|
||||
'data': samples,
|
||||
},
|
||||
'frameTable': {
|
||||
'schema': {
|
||||
'location': 0,
|
||||
'implementation': 1,
|
||||
'optimizations': 2,
|
||||
'line': 3,
|
||||
'category': 4,
|
||||
"frameTable": {
|
||||
"schema": {
|
||||
"location": 0,
|
||||
"implementation": 1,
|
||||
"optimizations": 2,
|
||||
"line": 3,
|
||||
"category": 4,
|
||||
},
|
||||
"data": frames,
|
||||
},
|
||||
'data': frames,
|
||||
},
|
||||
'stackTable': {
|
||||
'schema': {
|
||||
'frame': 0,
|
||||
'prefix': 1,
|
||||
"stackTable": {
|
||||
"schema": {
|
||||
"frame": 0,
|
||||
"prefix": 1,
|
||||
},
|
||||
"data": stacks,
|
||||
},
|
||||
'data': stacks,
|
||||
},
|
||||
'stringTable': string_table.contents(),
|
||||
})
|
||||
"stringTable": string_table.contents(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
output = {
|
||||
'meta': {
|
||||
'interval': rate,
|
||||
'processType': 0,
|
||||
'product': 'Servo',
|
||||
'stackwalk': 1,
|
||||
'startTime': startTime,
|
||||
'version': 4,
|
||||
'presymbolicated': True,
|
||||
"meta": {
|
||||
"interval": rate,
|
||||
"processType": 0,
|
||||
"product": "Servo",
|
||||
"stackwalk": 1,
|
||||
"startTime": startTime,
|
||||
"version": 4,
|
||||
"presymbolicated": True,
|
||||
},
|
||||
'libs': [],
|
||||
'threads': threads,
|
||||
"libs": [],
|
||||
"threads": threads,
|
||||
}
|
||||
|
||||
print(json.dumps(output))
|
||||
|
|
|
@ -27,8 +27,10 @@ def main(avd_name, apk_path, *args):
|
|||
"-no-window",
|
||||
"-no-snapshot",
|
||||
"-no-snapstorage",
|
||||
"-gpu", "guest",
|
||||
"-port", emulator_port,
|
||||
"-gpu",
|
||||
"guest",
|
||||
"-port",
|
||||
emulator_port,
|
||||
]
|
||||
with terminate_on_exit(emulator_args, stdout=sys.stderr) as emulator_process:
|
||||
# This is hopefully enough time for the emulator to exit
|
||||
|
@ -70,7 +72,6 @@ def main(avd_name, apk_path, *args):
|
|||
"*:S", # Hide everything else
|
||||
]
|
||||
with terminate_on_exit(adb + ["logcat"] + logcat_args) as logcat:
|
||||
|
||||
# This step needs to happen after application start
|
||||
forward_webdriver(adb, args)
|
||||
|
||||
|
@ -84,8 +85,7 @@ def tool_path(directory, bin_name):
|
|||
if os.path.exists(path):
|
||||
return path
|
||||
|
||||
path = os.path.join(os.path.dirname(__file__), "..", "android-toolchains", "sdk",
|
||||
directory, bin_name)
|
||||
path = os.path.join(os.path.dirname(__file__), "..", "android-toolchains", "sdk", directory, bin_name)
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
|
||||
|
@ -207,8 +207,7 @@ def interrupt(_signum, _frame):
|
|||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: %s avd_name apk_path [servo args...]" % sys.argv[0])
|
||||
print("Example: %s servo-x86 target/i686-linux-android/release/servo.apk https://servo.org"
|
||||
% sys.argv[0])
|
||||
print("Example: %s servo-x86 target/i686-linux-android/release/servo.apk https://servo.org" % sys.argv[0])
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
|
|
|
@ -29,16 +29,16 @@ import getopt
|
|||
|
||||
|
||||
def print_help():
|
||||
|
||||
print('\nPlease enter the command as shown below: \n')
|
||||
print('python3 ./etc/servo_automation_screenshot.py -p <port>'
|
||||
+ ' -i /path/to/folder/containing/files -r <resolution>'
|
||||
+ ' -n num_of_files\n')
|
||||
print("\nPlease enter the command as shown below: \n")
|
||||
print(
|
||||
"python3 ./etc/servo_automation_screenshot.py -p <port>"
|
||||
+ " -i /path/to/folder/containing/files -r <resolution>"
|
||||
+ " -n num_of_files\n"
|
||||
)
|
||||
|
||||
|
||||
def servo_ready_to_accept(url, payload, headers):
|
||||
while (True):
|
||||
|
||||
while True:
|
||||
try:
|
||||
# Before sending an additional request, we wait for one second each time
|
||||
time.sleep(1)
|
||||
|
@ -48,45 +48,46 @@ def servo_ready_to_accept(url, payload, headers):
|
|||
break
|
||||
except Exception as e:
|
||||
time.sleep(5)
|
||||
print('Exception: ', e)
|
||||
print("Exception: ", e)
|
||||
return json_string
|
||||
|
||||
|
||||
def ensure_screenshots_directory_exists():
|
||||
if not os.path.exists('screenshots'):
|
||||
os.makedirs('screenshots')
|
||||
if not os.path.exists("screenshots"):
|
||||
os.makedirs("screenshots")
|
||||
|
||||
|
||||
def render_html_files(num_of_files, url, file_url, json_string, headers, cwd):
|
||||
for x in range(num_of_files):
|
||||
|
||||
json_data = {}
|
||||
json_data['url'] = 'file://{0}file{1}.html'.format(file_url, str(x))
|
||||
print(json_data['url'])
|
||||
json_data["url"] = "file://{0}file{1}.html".format(file_url, str(x))
|
||||
print(json_data["url"])
|
||||
json_data = json.dumps(json_data)
|
||||
requests.post('{0}/{1}/url'.format(url, json_string['value']['sessionId']), data=json_data, headers=headers)
|
||||
screenshot_request = requests.get('{0}/{1}/screenshot'.format(url, json_string['value']['sessionId']))
|
||||
image_data_encoded = screenshot_request.json()['value']
|
||||
requests.post("{0}/{1}/url".format(url, json_string["value"]["sessionId"]), data=json_data, headers=headers)
|
||||
screenshot_request = requests.get("{0}/{1}/screenshot".format(url, json_string["value"]["sessionId"]))
|
||||
image_data_encoded = screenshot_request.json()["value"]
|
||||
with open("screenshots/output_image_{0}.png".format(str(x)), "wb") as image_file:
|
||||
image_file.write(base64.decodebytes(image_data_encoded.encode('utf-8')))
|
||||
image_file.write(base64.decodebytes(image_data_encoded.encode("utf-8")))
|
||||
print("################################")
|
||||
print("The screenshot is stored in the location: {0}/screenshots/"
|
||||
" with filename: output_image_{1}.png".format(cwd, str(x)))
|
||||
print(
|
||||
"The screenshot is stored in the location: {0}/screenshots/ with filename: output_image_{1}.png".format(
|
||||
cwd, str(x)
|
||||
)
|
||||
)
|
||||
print("################################")
|
||||
|
||||
|
||||
def main(argv): # take inputs from command line by considering the options parameter i.e -h, -p etc.
|
||||
|
||||
# Local Variables
|
||||
port = ''
|
||||
resolution = ''
|
||||
file_url = ''
|
||||
num_of_files = ''
|
||||
port = ""
|
||||
resolution = ""
|
||||
file_url = ""
|
||||
num_of_files = ""
|
||||
cwd = os.getcwd()
|
||||
url = ''
|
||||
url = ""
|
||||
payload = "{}"
|
||||
headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'}
|
||||
json_string = ''
|
||||
headers = {"content-type": "application/json", "Accept-Charset": "UTF-8"}
|
||||
json_string = ""
|
||||
try:
|
||||
# input options defined here.
|
||||
opts, args = getopt.getopt(argv, "p:i:r:n:", ["port=", "ifile=", "resolution=", "num-files="])
|
||||
|
@ -96,7 +97,7 @@ def main(argv): # take inputs from command line by considering the options para
|
|||
print_help()
|
||||
sys.exit(2)
|
||||
for opt, arg in opts:
|
||||
if opt == '-h': # -h means help. Displays how to input command line arguments
|
||||
if opt == "-h": # -h means help. Displays how to input command line arguments
|
||||
print_help()
|
||||
sys.exit()
|
||||
elif opt in ("-p", "--port"): # store the value provided with the option -p in port variable.
|
||||
|
@ -108,7 +109,7 @@ def main(argv): # take inputs from command line by considering the options para
|
|||
elif opt in ("-n", "--num-files"): # store the value provided with the option -n in num_of_files variable.
|
||||
num_of_files = arg
|
||||
|
||||
url = 'http://localhost:{0}/session'.format(port)
|
||||
url = "http://localhost:{0}/session".format(port)
|
||||
num_of_files = int(num_of_files)
|
||||
|
||||
# Starting servo on specified port
|
||||
|
|
|
@ -68,7 +68,7 @@ class TrustedNodeAddressPrinter:
|
|||
def children(self):
|
||||
node_type = gdb.lookup_type("struct script::dom::node::Node").pointer()
|
||||
value = self.val.cast(node_type)
|
||||
return [('Node', value)]
|
||||
return [("Node", value)]
|
||||
|
||||
def to_string(self):
|
||||
return self.val.address
|
||||
|
@ -83,7 +83,7 @@ class NodeTypeIdPrinter:
|
|||
u8_ptr_type = gdb.lookup_type("u8").pointer()
|
||||
enum_0 = self.val.address.cast(u8_ptr_type).dereference()
|
||||
enum_type = self.val.type.fields()[int(enum_0)].type
|
||||
return str(enum_type).lstrip('struct ')
|
||||
return str(enum_type).lstrip("struct ")
|
||||
|
||||
|
||||
# Printer for std::Option<>
|
||||
|
@ -113,8 +113,8 @@ class OptionPrinter:
|
|||
value_type = option_type.fields()[1].type.fields()[1].type
|
||||
v_size = value_type.sizeof
|
||||
data_ptr = (ptr + t_size - v_size).cast(value_type.pointer()).dereference()
|
||||
return [('Some', data_ptr)]
|
||||
return [('None', None)]
|
||||
return [("Some", data_ptr)]
|
||||
return [("None", None)]
|
||||
|
||||
def to_string(self):
|
||||
return None
|
||||
|
@ -130,19 +130,19 @@ class TestPrinter:
|
|||
|
||||
|
||||
type_map = [
|
||||
('struct Au', AuPrinter),
|
||||
('FlowFlags', BitFieldU8Printer),
|
||||
('IntrinsicWidths', ChildPrinter),
|
||||
('PlacementInfo', ChildPrinter),
|
||||
('TrustedNodeAddress', TrustedNodeAddressPrinter),
|
||||
('NodeTypeId', NodeTypeIdPrinter),
|
||||
('Option', OptionPrinter),
|
||||
("struct Au", AuPrinter),
|
||||
("FlowFlags", BitFieldU8Printer),
|
||||
("IntrinsicWidths", ChildPrinter),
|
||||
("PlacementInfo", ChildPrinter),
|
||||
("TrustedNodeAddress", TrustedNodeAddressPrinter),
|
||||
("NodeTypeId", NodeTypeIdPrinter),
|
||||
("Option", OptionPrinter),
|
||||
]
|
||||
|
||||
|
||||
def lookup_servo_type(val):
|
||||
val_type = str(val.type)
|
||||
for (type_name, printer) in type_map:
|
||||
for type_name, printer in type_map:
|
||||
if val_type == type_name or val_type.endswith("::" + type_name):
|
||||
return printer(val)
|
||||
return None
|
||||
|
|
|
@ -12,13 +12,13 @@ Created on Mon Mar 26 20:08:25 2018
|
|||
@author: Pranshu Sinha, Abhay Soni, Aayushi Agrawal
|
||||
The script is intended to start servo on localhost:7002
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
|
||||
|
||||
def start_servo(port, resolution):
|
||||
|
||||
# Use the below command if you are running this script on windows
|
||||
# cmds = 'mach.bat run --webdriver ' + port + ' --window-size ' + resolution
|
||||
cmds = './mach run --webdriver=' + port + ' --window-size ' + resolution
|
||||
cmds = "./mach run --webdriver=" + port + " --window-size " + resolution
|
||||
process = subprocess.Popen(cmds, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
return process
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
import sys
|
||||
import json
|
||||
|
||||
full_search = len(sys.argv) > 3 and sys.argv[3] == '--full'
|
||||
full_search = len(sys.argv) > 3 and sys.argv[3] == "--full"
|
||||
|
||||
with open(sys.argv[1]) as f:
|
||||
data = f.readlines()
|
||||
|
@ -34,13 +34,9 @@ with open(sys.argv[1]) as f:
|
|||
if "action" in entry and entry["action"] == "test_end":
|
||||
thread = None
|
||||
else:
|
||||
if ("action" in entry
|
||||
and entry["action"] == "test_start"
|
||||
and entry["test"] == sys.argv[2]):
|
||||
if "action" in entry and entry["action"] == "test_start" and entry["test"] == sys.argv[2]:
|
||||
thread = entry["thread"]
|
||||
print(json.dumps(entry))
|
||||
elif (full_search
|
||||
and "command" in entry
|
||||
and sys.argv[2] in entry["command"]):
|
||||
elif full_search and "command" in entry and sys.argv[2] in entry["command"]:
|
||||
thread = entry["thread"]
|
||||
print(json.dumps(entry))
|
||||
|
|
|
@ -45,9 +45,7 @@ def process_log(data):
|
|||
elif entry["action"] == "test_end":
|
||||
test = tests[entry["test"]]
|
||||
test["end"] = int(entry["time"])
|
||||
test_results[entry["status"]] += [
|
||||
(entry["test"], test["end"] - test["start"])
|
||||
]
|
||||
test_results[entry["status"]] += [(entry["test"], test["end"] - test["start"])]
|
||||
|
||||
return test_results
|
||||
|
||||
|
@ -73,24 +71,18 @@ print("%d tests timed out." % len(test_results["TIMEOUT"]))
|
|||
longest_crash = sorted(test_results["CRASH"], key=lambda x: x[1], reverse=True)
|
||||
print("Longest CRASH test took %dms (%s)" % (longest_crash[0][1], longest_crash[0][0]))
|
||||
|
||||
longest_ok = sorted(
|
||||
test_results["PASS"] + test_results["OK"],
|
||||
key=lambda x: x[1], reverse=True
|
||||
)
|
||||
csv_data = [['Test path', 'Milliseconds']]
|
||||
with open('longest_ok.csv', 'w') as csv_file:
|
||||
longest_ok = sorted(test_results["PASS"] + test_results["OK"], key=lambda x: x[1], reverse=True)
|
||||
csv_data = [["Test path", "Milliseconds"]]
|
||||
with open("longest_ok.csv", "w") as csv_file:
|
||||
writer = csv.writer(csv_file)
|
||||
writer.writerows(csv_data + longest_ok)
|
||||
|
||||
longest_fail = sorted(
|
||||
test_results["ERROR"] + test_results["FAIL"],
|
||||
key=lambda x: x[1], reverse=True
|
||||
)
|
||||
with open('longest_err.csv', 'w') as csv_file:
|
||||
longest_fail = sorted(test_results["ERROR"] + test_results["FAIL"], key=lambda x: x[1], reverse=True)
|
||||
with open("longest_err.csv", "w") as csv_file:
|
||||
writer = csv.writer(csv_file)
|
||||
writer.writerows(csv_data + longest_fail)
|
||||
|
||||
longest_timeout = sorted(test_results["TIMEOUT"], key=lambda x: x[1], reverse=True)
|
||||
with open('timeouts.csv', 'w') as csv_file:
|
||||
with open("timeouts.csv", "w") as csv_file:
|
||||
writer = csv.writer(csv_file)
|
||||
writer.writerows(csv_data + longest_timeout)
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
|
||||
import os
|
||||
|
||||
test_root = os.path.join('tests', 'wpt', 'tests')
|
||||
meta_root = os.path.join('tests', 'wpt', 'meta')
|
||||
test_root = os.path.join("tests", "wpt", "tests")
|
||||
meta_root = os.path.join("tests", "wpt", "meta")
|
||||
|
||||
test_counts = {}
|
||||
meta_counts = {}
|
||||
|
@ -35,7 +35,7 @@ for base_dir, dir_names, files in os.walk(test_root):
|
|||
continue
|
||||
|
||||
test_files = []
|
||||
exts = ['.html', '.htm', '.xht', '.xhtml', '.window.js', '.worker.js', '.any.js']
|
||||
exts = [".html", ".htm", ".xht", ".xhtml", ".window.js", ".worker.js", ".any.js"]
|
||||
for f in files:
|
||||
for ext in exts:
|
||||
if f.endswith(ext):
|
||||
|
@ -48,21 +48,21 @@ for base_dir, dir_names, files in os.walk(meta_root):
|
|||
|
||||
rel_base = os.path.relpath(base_dir, meta_root)
|
||||
num_files = len(files)
|
||||
if '__dir__.ini' in files:
|
||||
if "__dir__.ini" in files:
|
||||
num_files -= 1
|
||||
meta_counts[rel_base] = num_files
|
||||
|
||||
final_counts = []
|
||||
for (test_dir, test_count) in test_counts.items():
|
||||
for test_dir, test_count in test_counts.items():
|
||||
if not test_count:
|
||||
continue
|
||||
meta_count = meta_counts.get(test_dir, 0)
|
||||
final_counts += [(test_dir, test_count, meta_count)]
|
||||
|
||||
print('Test counts')
|
||||
print('dir: %% failed (num tests / num failures)')
|
||||
print("Test counts")
|
||||
print("dir: %% failed (num tests / num failures)")
|
||||
s = sorted(final_counts, key=lambda x: x[2] / x[1])
|
||||
for (test_dir, test_count, meta_count) in reversed(sorted(s, key=lambda x: x[2])):
|
||||
for test_dir, test_count, meta_count in reversed(sorted(s, key=lambda x: x[2])):
|
||||
if not meta_count:
|
||||
continue
|
||||
print('%s: %.2f%% (%d / %d)' % (test_dir, meta_count / test_count * 100, test_count, meta_count))
|
||||
print("%s: %.2f%% (%d / %d)" % (test_dir, meta_count / test_count * 100, test_count, meta_count))
|
||||
|
|
|
@ -22,61 +22,61 @@ SEARCH_PATHS = [
|
|||
|
||||
# Individual files providing mach commands.
|
||||
MACH_MODULES = [
|
||||
os.path.join('python', 'servo', 'bootstrap_commands.py'),
|
||||
os.path.join('python', 'servo', 'build_commands.py'),
|
||||
os.path.join('python', 'servo', 'testing_commands.py'),
|
||||
os.path.join('python', 'servo', 'post_build_commands.py'),
|
||||
os.path.join('python', 'servo', 'package_commands.py'),
|
||||
os.path.join('python', 'servo', 'devenv_commands.py'),
|
||||
os.path.join("python", "servo", "bootstrap_commands.py"),
|
||||
os.path.join("python", "servo", "build_commands.py"),
|
||||
os.path.join("python", "servo", "testing_commands.py"),
|
||||
os.path.join("python", "servo", "post_build_commands.py"),
|
||||
os.path.join("python", "servo", "package_commands.py"),
|
||||
os.path.join("python", "servo", "devenv_commands.py"),
|
||||
]
|
||||
|
||||
CATEGORIES = {
|
||||
'bootstrap': {
|
||||
'short': 'Bootstrap Commands',
|
||||
'long': 'Bootstrap the build system',
|
||||
'priority': 90,
|
||||
"bootstrap": {
|
||||
"short": "Bootstrap Commands",
|
||||
"long": "Bootstrap the build system",
|
||||
"priority": 90,
|
||||
},
|
||||
'build': {
|
||||
'short': 'Build Commands',
|
||||
'long': 'Interact with the build system',
|
||||
'priority': 80,
|
||||
"build": {
|
||||
"short": "Build Commands",
|
||||
"long": "Interact with the build system",
|
||||
"priority": 80,
|
||||
},
|
||||
'post-build': {
|
||||
'short': 'Post-build Commands',
|
||||
'long': 'Common actions performed after completing a build.',
|
||||
'priority': 70,
|
||||
"post-build": {
|
||||
"short": "Post-build Commands",
|
||||
"long": "Common actions performed after completing a build.",
|
||||
"priority": 70,
|
||||
},
|
||||
'testing': {
|
||||
'short': 'Testing',
|
||||
'long': 'Run tests.',
|
||||
'priority': 60,
|
||||
"testing": {
|
||||
"short": "Testing",
|
||||
"long": "Run tests.",
|
||||
"priority": 60,
|
||||
},
|
||||
'devenv': {
|
||||
'short': 'Development Environment',
|
||||
'long': 'Set up and configure your development environment.',
|
||||
'priority': 50,
|
||||
"devenv": {
|
||||
"short": "Development Environment",
|
||||
"long": "Set up and configure your development environment.",
|
||||
"priority": 50,
|
||||
},
|
||||
'build-dev': {
|
||||
'short': 'Low-level Build System Interaction',
|
||||
'long': 'Interact with specific parts of the build system.',
|
||||
'priority': 20,
|
||||
"build-dev": {
|
||||
"short": "Low-level Build System Interaction",
|
||||
"long": "Interact with specific parts of the build system.",
|
||||
"priority": 20,
|
||||
},
|
||||
'package': {
|
||||
'short': 'Package',
|
||||
'long': 'Create objects to distribute',
|
||||
'priority': 15,
|
||||
"package": {
|
||||
"short": "Package",
|
||||
"long": "Create objects to distribute",
|
||||
"priority": 15,
|
||||
},
|
||||
'misc': {
|
||||
'short': 'Potpourri',
|
||||
'long': 'Potent potables and assorted snacks.',
|
||||
'priority': 10,
|
||||
"misc": {
|
||||
"short": "Potpourri",
|
||||
"long": "Potent potables and assorted snacks.",
|
||||
"priority": 10,
|
||||
},
|
||||
"disabled": {
|
||||
"short": "Disabled",
|
||||
"long": "The disabled commands are hidden by default. Use -v to display them. These commands are unavailable "
|
||||
'for your current context, run "mach <command>" to see why.',
|
||||
"priority": 0,
|
||||
},
|
||||
'disabled': {
|
||||
'short': 'Disabled',
|
||||
'long': 'The disabled commands are hidden by default. Use -v to display them. These commands are unavailable '
|
||||
'for your current context, run "mach <command>" to see why.',
|
||||
'priority': 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -92,17 +92,25 @@ def _process_exec(args, cwd):
|
|||
def install_virtual_env_requirements(project_path: str, marker_path: str):
|
||||
requirements_paths = [
|
||||
os.path.join(project_path, "python", "requirements.txt"),
|
||||
os.path.join(project_path, WPT_TOOLS_PATH, "requirements_tests.txt",),
|
||||
os.path.join(project_path, WPT_RUNNER_PATH, "requirements.txt",),
|
||||
os.path.join(
|
||||
project_path,
|
||||
WPT_TOOLS_PATH,
|
||||
"requirements_tests.txt",
|
||||
),
|
||||
os.path.join(
|
||||
project_path,
|
||||
WPT_RUNNER_PATH,
|
||||
"requirements.txt",
|
||||
),
|
||||
]
|
||||
|
||||
requirements_hasher = hashlib.sha256()
|
||||
for path in requirements_paths:
|
||||
with open(path, 'rb') as file:
|
||||
with open(path, "rb") as file:
|
||||
requirements_hasher.update(file.read())
|
||||
|
||||
try:
|
||||
with open(marker_path, 'r') as marker_file:
|
||||
with open(marker_path, "r") as marker_file:
|
||||
marker_hash = marker_file.read()
|
||||
except FileNotFoundError:
|
||||
marker_hash = None
|
||||
|
@ -132,27 +140,28 @@ def _activate_virtualenv(topdir):
|
|||
_process_exec(["uv", "venv"], cwd=topdir)
|
||||
|
||||
script_dir = "Scripts" if _is_windows() else "bin"
|
||||
runpy.run_path(os.path.join(virtualenv_path, script_dir, 'activate_this.py'))
|
||||
runpy.run_path(os.path.join(virtualenv_path, script_dir, "activate_this.py"))
|
||||
|
||||
install_virtual_env_requirements(topdir, marker_path)
|
||||
|
||||
# Turn off warnings about deprecated syntax in our indirect dependencies.
|
||||
# TODO: Find a better approach for doing this.
|
||||
import warnings
|
||||
warnings.filterwarnings('ignore', category=SyntaxWarning, module=r'.*.venv')
|
||||
|
||||
warnings.filterwarnings("ignore", category=SyntaxWarning, module=r".*.venv")
|
||||
|
||||
|
||||
def _ensure_case_insensitive_if_windows():
|
||||
# The folder is called 'python'. By deliberately checking for it with the wrong case, we determine if the file
|
||||
# system is case sensitive or not.
|
||||
if _is_windows() and not os.path.exists('Python'):
|
||||
print('Cannot run mach in a path on a case-sensitive file system on Windows.')
|
||||
print('For more details, see https://github.com/pypa/virtualenv/issues/935')
|
||||
if _is_windows() and not os.path.exists("Python"):
|
||||
print("Cannot run mach in a path on a case-sensitive file system on Windows.")
|
||||
print("For more details, see https://github.com/pypa/virtualenv/issues/935")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _is_windows():
|
||||
return sys.platform == 'win32'
|
||||
return sys.platform == "win32"
|
||||
|
||||
|
||||
def bootstrap_command_only(topdir):
|
||||
|
@ -168,9 +177,9 @@ def bootstrap_command_only(topdir):
|
|||
import servo.util
|
||||
|
||||
try:
|
||||
force = '-f' in sys.argv or '--force' in sys.argv
|
||||
skip_platform = '--skip-platform' in sys.argv
|
||||
skip_lints = '--skip-lints' in sys.argv
|
||||
force = "-f" in sys.argv or "--force" in sys.argv
|
||||
skip_platform = "--skip-platform" in sys.argv
|
||||
skip_lints = "--skip-lints" in sys.argv
|
||||
servo.platform.get().bootstrap(force, skip_platform, skip_lints)
|
||||
except NotImplementedError as exception:
|
||||
print(exception)
|
||||
|
@ -186,9 +195,9 @@ def bootstrap(topdir):
|
|||
|
||||
# We don't support paths with spaces for now
|
||||
# https://github.com/servo/servo/issues/9616
|
||||
if ' ' in topdir and (not _is_windows()):
|
||||
print('Cannot run mach in a path with spaces.')
|
||||
print('Current path:', topdir)
|
||||
if " " in topdir and (not _is_windows()):
|
||||
print("Cannot run mach in a path with spaces.")
|
||||
print("Current path:", topdir)
|
||||
sys.exit(1)
|
||||
|
||||
_activate_virtualenv(topdir)
|
||||
|
@ -196,7 +205,7 @@ def bootstrap(topdir):
|
|||
def populate_context(context, key=None):
|
||||
if key is None:
|
||||
return
|
||||
if key == 'topdir':
|
||||
if key == "topdir":
|
||||
return topdir
|
||||
raise AttributeError(key)
|
||||
|
||||
|
@ -204,11 +213,12 @@ def bootstrap(topdir):
|
|||
sys.path[0:0] = [WPT_PATH, WPT_RUNNER_PATH, WPT_SERVE_PATH]
|
||||
|
||||
import mach.main
|
||||
|
||||
mach = mach.main.Mach(os.getcwd())
|
||||
mach.populate_context_handler = populate_context
|
||||
|
||||
for category, meta in CATEGORIES.items():
|
||||
mach.define_category(category, meta['short'], meta['long'], meta['priority'])
|
||||
mach.define_category(category, meta["short"], meta["long"], meta["priority"])
|
||||
|
||||
for path in MACH_MODULES:
|
||||
# explicitly provide a module name
|
||||
|
|
|
@ -36,18 +36,10 @@ from servo.util import delete, download_bytes
|
|||
|
||||
@CommandProvider
|
||||
class MachCommands(CommandBase):
|
||||
@Command('bootstrap',
|
||||
description='Install required packages for building.',
|
||||
category='bootstrap')
|
||||
@CommandArgument('--force', '-f',
|
||||
action='store_true',
|
||||
help='Boostrap without confirmation')
|
||||
@CommandArgument('--skip-platform',
|
||||
action='store_true',
|
||||
help='Skip platform bootstrapping.')
|
||||
@CommandArgument('--skip-lints',
|
||||
action='store_true',
|
||||
help='Skip tool necessary for linting.')
|
||||
@Command("bootstrap", description="Install required packages for building.", category="bootstrap")
|
||||
@CommandArgument("--force", "-f", action="store_true", help="Boostrap without confirmation")
|
||||
@CommandArgument("--skip-platform", action="store_true", help="Skip platform bootstrapping.")
|
||||
@CommandArgument("--skip-lints", action="store_true", help="Skip tool necessary for linting.")
|
||||
def bootstrap(self, force=False, skip_platform=False, skip_lints=False):
|
||||
# Note: This entry point isn't actually invoked by ./mach bootstrap.
|
||||
# ./mach bootstrap calls mach_bootstrap.bootstrap_command_only so that
|
||||
|
@ -59,12 +51,12 @@ class MachCommands(CommandBase):
|
|||
return 1
|
||||
return 0
|
||||
|
||||
@Command('bootstrap-gstreamer',
|
||||
description='Set up a local copy of the gstreamer libraries (linux only).',
|
||||
category='bootstrap')
|
||||
@CommandArgument('--force', '-f',
|
||||
action='store_true',
|
||||
help='Boostrap without confirmation')
|
||||
@Command(
|
||||
"bootstrap-gstreamer",
|
||||
description="Set up a local copy of the gstreamer libraries (linux only).",
|
||||
category="bootstrap",
|
||||
)
|
||||
@CommandArgument("--force", "-f", action="store_true", help="Boostrap without confirmation")
|
||||
def bootstrap_gstreamer(self, force=False):
|
||||
try:
|
||||
servo.platform.get().bootstrap_gstreamer(force)
|
||||
|
@ -73,15 +65,15 @@ class MachCommands(CommandBase):
|
|||
return 1
|
||||
return 0
|
||||
|
||||
@Command('update-hsts-preload',
|
||||
description='Download the HSTS preload list',
|
||||
category='bootstrap')
|
||||
@Command("update-hsts-preload", description="Download the HSTS preload list", category="bootstrap")
|
||||
def bootstrap_hsts_preload(self, force=False):
|
||||
preload_filename = "hsts_preload.fstmap"
|
||||
preload_path = path.join(self.context.topdir, "resources")
|
||||
|
||||
chromium_hsts_url = "https://chromium.googlesource.com/chromium/src" + \
|
||||
"/+/main/net/http/transport_security_state_static.json?format=TEXT"
|
||||
chromium_hsts_url = (
|
||||
"https://chromium.googlesource.com/chromium/src"
|
||||
+ "/+/main/net/http/transport_security_state_static.json?format=TEXT"
|
||||
)
|
||||
|
||||
try:
|
||||
content_base64 = download_bytes("Chromium HSTS preload list", chromium_hsts_url)
|
||||
|
@ -93,7 +85,7 @@ class MachCommands(CommandBase):
|
|||
|
||||
# The chromium "json" has single line comments in it which, of course,
|
||||
# are non-standard/non-valid json. Simply strip them out before parsing
|
||||
content_json = re.sub(r'(^|\s+)//.*$', '', content_decoded, flags=re.MULTILINE)
|
||||
content_json = re.sub(r"(^|\s+)//.*$", "", content_decoded, flags=re.MULTILINE)
|
||||
try:
|
||||
pins_and_static_preloads = json.loads(content_json)
|
||||
with tempfile.NamedTemporaryFile(mode="w") as csv_file:
|
||||
|
@ -107,13 +99,15 @@ class MachCommands(CommandBase):
|
|||
print(f"Unable to parse chromium HSTS preload list, has the format changed? \n{e}")
|
||||
sys.exit(1)
|
||||
|
||||
@Command('update-pub-domains',
|
||||
description='Download the public domains list and update resources/public_domains.txt',
|
||||
category='bootstrap')
|
||||
@Command(
|
||||
"update-pub-domains",
|
||||
description="Download the public domains list and update resources/public_domains.txt",
|
||||
category="bootstrap",
|
||||
)
|
||||
def bootstrap_pub_suffix(self, force=False):
|
||||
list_url = "https://publicsuffix.org/list/public_suffix_list.dat"
|
||||
dst_filename = path.join(self.context.topdir, "resources", "public_domains.txt")
|
||||
not_implemented_case = re.compile(r'^[^*]+\*')
|
||||
not_implemented_case = re.compile(r"^[^*]+\*")
|
||||
|
||||
try:
|
||||
content = download_bytes("Public suffix list", list_url)
|
||||
|
@ -130,29 +124,22 @@ class MachCommands(CommandBase):
|
|||
print("Warning: the new list contains a case that servo can't handle: %s" % suffix)
|
||||
fo.write(suffix.encode("idna") + "\n")
|
||||
|
||||
@Command('clean-nightlies',
|
||||
description='Clean unused nightly builds of Rust and Cargo',
|
||||
category='bootstrap')
|
||||
@CommandArgument('--force', '-f',
|
||||
action='store_true',
|
||||
help='Actually remove stuff')
|
||||
@CommandArgument('--keep',
|
||||
default='1',
|
||||
help='Keep up to this many most recent nightlies')
|
||||
@Command("clean-nightlies", description="Clean unused nightly builds of Rust and Cargo", category="bootstrap")
|
||||
@CommandArgument("--force", "-f", action="store_true", help="Actually remove stuff")
|
||||
@CommandArgument("--keep", default="1", help="Keep up to this many most recent nightlies")
|
||||
def clean_nightlies(self, force=False, keep=None):
|
||||
print(f"Current Rust version for Servo: {self.rust_toolchain()}")
|
||||
old_toolchains = []
|
||||
keep = int(keep)
|
||||
stdout = subprocess.check_output(['git', 'log', '--format=%H', 'rust-toolchain.toml'])
|
||||
stdout = subprocess.check_output(["git", "log", "--format=%H", "rust-toolchain.toml"])
|
||||
for i, commit_hash in enumerate(stdout.split(), 1):
|
||||
if i > keep:
|
||||
toolchain_config_text = subprocess.check_output(
|
||||
['git', 'show', f'{commit_hash}:rust-toolchain.toml'])
|
||||
toolchain = toml.loads(toolchain_config_text)['toolchain']['channel']
|
||||
toolchain_config_text = subprocess.check_output(["git", "show", f"{commit_hash}:rust-toolchain.toml"])
|
||||
toolchain = toml.loads(toolchain_config_text)["toolchain"]["channel"]
|
||||
old_toolchains.append(toolchain)
|
||||
|
||||
removing_anything = False
|
||||
stdout = subprocess.check_output(['rustup', 'toolchain', 'list'])
|
||||
stdout = subprocess.check_output(["rustup", "toolchain", "list"])
|
||||
for toolchain_with_host in stdout.split():
|
||||
for old in old_toolchains:
|
||||
if toolchain_with_host.startswith(old):
|
||||
|
@ -165,21 +152,12 @@ class MachCommands(CommandBase):
|
|||
if not removing_anything:
|
||||
print("Nothing to remove.")
|
||||
elif not force:
|
||||
print("Nothing done. "
|
||||
"Run `./mach clean-nightlies -f` to actually remove.")
|
||||
print("Nothing done. Run `./mach clean-nightlies -f` to actually remove.")
|
||||
|
||||
@Command('clean-cargo-cache',
|
||||
description='Clean unused Cargo packages',
|
||||
category='bootstrap')
|
||||
@CommandArgument('--force', '-f',
|
||||
action='store_true',
|
||||
help='Actually remove stuff')
|
||||
@CommandArgument('--show-size', '-s',
|
||||
action='store_true',
|
||||
help='Show packages size')
|
||||
@CommandArgument('--keep',
|
||||
default='1',
|
||||
help='Keep up to this many most recent dependencies')
|
||||
@Command("clean-cargo-cache", description="Clean unused Cargo packages", category="bootstrap")
|
||||
@CommandArgument("--force", "-f", action="store_true", help="Actually remove stuff")
|
||||
@CommandArgument("--show-size", "-s", action="store_true", help="Show packages size")
|
||||
@CommandArgument("--keep", default="1", help="Keep up to this many most recent dependencies")
|
||||
def clean_cargo_cache(self, force=False, show_size=False, keep=None):
|
||||
def get_size(path):
|
||||
if os.path.isfile(path):
|
||||
|
@ -193,10 +171,11 @@ class MachCommands(CommandBase):
|
|||
|
||||
removing_anything = False
|
||||
packages = {
|
||||
'crates': {},
|
||||
'git': {},
|
||||
"crates": {},
|
||||
"git": {},
|
||||
}
|
||||
import toml
|
||||
|
||||
if os.environ.get("CARGO_HOME", ""):
|
||||
cargo_dir = os.environ.get("CARGO_HOME")
|
||||
else:
|
||||
|
@ -210,7 +189,7 @@ class MachCommands(CommandBase):
|
|||
for package in content.get("package", []):
|
||||
source = package.get("source", "")
|
||||
version = package["version"]
|
||||
if source == u"registry+https://github.com/rust-lang/crates.io-index":
|
||||
if source == "registry+https://github.com/rust-lang/crates.io-index":
|
||||
crate_name = "{}-{}".format(package["name"], version)
|
||||
if not packages["crates"].get(crate_name, False):
|
||||
packages["crates"][package["name"]] = {
|
||||
|
@ -248,7 +227,7 @@ class MachCommands(CommandBase):
|
|||
git_db_dir = path.join(git_dir, "db")
|
||||
git_checkout_dir = path.join(git_dir, "checkouts")
|
||||
if os.path.isdir(git_db_dir):
|
||||
git_db_list = list(filter(lambda f: not f.startswith('.'), os.listdir(git_db_dir)))
|
||||
git_db_list = list(filter(lambda f: not f.startswith("."), os.listdir(git_db_dir)))
|
||||
else:
|
||||
git_db_list = []
|
||||
if os.path.isdir(git_checkout_dir):
|
||||
|
@ -265,7 +244,7 @@ class MachCommands(CommandBase):
|
|||
}
|
||||
if os.path.isdir(path.join(git_checkout_dir, d)):
|
||||
with cd(path.join(git_checkout_dir, d)):
|
||||
git_crate_hash = glob.glob('*')
|
||||
git_crate_hash = glob.glob("*")
|
||||
if not git_crate_hash or not os.path.isdir(path.join(git_db_dir, d)):
|
||||
packages["git"][crate_name]["exist"].append(("del", d, ""))
|
||||
continue
|
||||
|
@ -299,8 +278,12 @@ class MachCommands(CommandBase):
|
|||
exist_item = exist[2] if packages_type == "git" else exist
|
||||
if exist_item not in current_crate:
|
||||
crate_count += 1
|
||||
if int(crate_count) >= int(keep) or not current_crate or \
|
||||
exist[0] == "del" or exist[2] == "master":
|
||||
if (
|
||||
int(crate_count) >= int(keep)
|
||||
or not current_crate
|
||||
or exist[0] == "del"
|
||||
or exist[2] == "master"
|
||||
):
|
||||
removing_anything = True
|
||||
crate_paths = []
|
||||
if packages_type == "git":
|
||||
|
@ -317,7 +300,7 @@ class MachCommands(CommandBase):
|
|||
else:
|
||||
crate_paths.append(exist_path)
|
||||
|
||||
exist_checkout_list = glob.glob(path.join(exist_checkout_path, '*'))
|
||||
exist_checkout_list = glob.glob(path.join(exist_checkout_path, "*"))
|
||||
if len(exist_checkout_list) <= 1:
|
||||
crate_paths.append(exist_checkout_path)
|
||||
if os.path.isdir(exist_db_path):
|
||||
|
@ -347,5 +330,4 @@ class MachCommands(CommandBase):
|
|||
if not removing_anything:
|
||||
print("Nothing to remove.")
|
||||
elif not force:
|
||||
print("\nNothing done. "
|
||||
"Run `./mach clean-cargo-cache -f` to actually remove.")
|
||||
print("\nNothing done. Run `./mach clean-cargo-cache -f` to actually remove.")
|
||||
|
|
|
@ -37,8 +37,12 @@ from servo.command_base import BuildType, CommandBase, call, check_call
|
|||
from servo.gstreamer import windows_dlls, windows_plugins, package_gstreamer_dylibs
|
||||
from servo.platform.build_target import BuildTarget
|
||||
|
||||
SUPPORTED_ASAN_TARGETS = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu",
|
||||
"x86_64-apple-darwin", "x86_64-unknown-linux-gnu"]
|
||||
SUPPORTED_ASAN_TARGETS = [
|
||||
"aarch64-apple-darwin",
|
||||
"aarch64-unknown-linux-gnu",
|
||||
"x86_64-apple-darwin",
|
||||
"x86_64-unknown-linux-gnu",
|
||||
]
|
||||
|
||||
|
||||
def get_rustc_llvm_version() -> Optional[List[int]]:
|
||||
|
@ -50,14 +54,14 @@ def get_rustc_llvm_version() -> Optional[List[int]]:
|
|||
be valid in both rustup managed environment and on nix.
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(['rustc', '--version', '--verbose'], encoding='utf-8', capture_output=True)
|
||||
result = subprocess.run(["rustc", "--version", "--verbose"], encoding="utf-8", capture_output=True)
|
||||
result.check_returncode()
|
||||
for line in result.stdout.splitlines():
|
||||
line_lowercase = line.lower()
|
||||
if line_lowercase.startswith("llvm version:"):
|
||||
llvm_version = line_lowercase.strip("llvm version:")
|
||||
llvm_version = llvm_version.strip()
|
||||
version = llvm_version.split('.')
|
||||
version = llvm_version.split(".")
|
||||
print(f"Info: rustc is using LLVM version {'.'.join(version)}")
|
||||
return version
|
||||
else:
|
||||
|
@ -69,24 +73,27 @@ def get_rustc_llvm_version() -> Optional[List[int]]:
|
|||
|
||||
@CommandProvider
|
||||
class MachCommands(CommandBase):
|
||||
@Command('build', description='Build Servo', category='build')
|
||||
@CommandArgument('--jobs', '-j',
|
||||
default=None,
|
||||
help='Number of jobs to run in parallel')
|
||||
@CommandArgument('--no-package',
|
||||
action='store_true',
|
||||
help='For Android, disable packaging into a .apk after building')
|
||||
@CommandArgument('--verbose', '-v',
|
||||
action='store_true',
|
||||
help='Print verbose output')
|
||||
@CommandArgument('--very-verbose', '-vv',
|
||||
action='store_true',
|
||||
help='Print very verbose output')
|
||||
@CommandArgument('params', nargs='...',
|
||||
help="Command-line arguments to be passed through to Cargo")
|
||||
@Command("build", description="Build Servo", category="build")
|
||||
@CommandArgument("--jobs", "-j", default=None, help="Number of jobs to run in parallel")
|
||||
@CommandArgument(
|
||||
"--no-package", action="store_true", help="For Android, disable packaging into a .apk after building"
|
||||
)
|
||||
@CommandArgument("--verbose", "-v", action="store_true", help="Print verbose output")
|
||||
@CommandArgument("--very-verbose", "-vv", action="store_true", help="Print very verbose output")
|
||||
@CommandArgument("params", nargs="...", help="Command-line arguments to be passed through to Cargo")
|
||||
@CommandBase.common_command_arguments(build_configuration=True, build_type=True, package_configuration=True)
|
||||
def build(self, build_type: BuildType, jobs=None, params=None, no_package=False,
|
||||
verbose=False, very_verbose=False, with_asan=False, flavor=None, **kwargs):
|
||||
def build(
|
||||
self,
|
||||
build_type: BuildType,
|
||||
jobs=None,
|
||||
params=None,
|
||||
no_package=False,
|
||||
verbose=False,
|
||||
very_verbose=False,
|
||||
with_asan=False,
|
||||
flavor=None,
|
||||
**kwargs,
|
||||
):
|
||||
opts = params or []
|
||||
|
||||
if build_type.is_release():
|
||||
|
@ -112,8 +119,10 @@ class MachCommands(CommandBase):
|
|||
|
||||
if with_asan:
|
||||
if target_triple not in SUPPORTED_ASAN_TARGETS:
|
||||
print("AddressSanitizer is currently not supported on this platform\n",
|
||||
"See https://doc.rust-lang.org/beta/unstable-book/compiler-flags/sanitizer.html")
|
||||
print(
|
||||
"AddressSanitizer is currently not supported on this platform\n",
|
||||
"See https://doc.rust-lang.org/beta/unstable-book/compiler-flags/sanitizer.html",
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# do not use crown (clashes with different rust version)
|
||||
|
@ -157,12 +166,14 @@ class MachCommands(CommandBase):
|
|||
|
||||
build_start = time()
|
||||
|
||||
if host != target_triple and 'windows' in target_triple:
|
||||
if os.environ.get('VisualStudioVersion') or os.environ.get('VCINSTALLDIR'):
|
||||
print("Can't cross-compile for Windows inside of a Visual Studio shell.\n"
|
||||
"Please run `python mach build [arguments]` to bypass automatic "
|
||||
"Visual Studio shell, and make sure the VisualStudioVersion and "
|
||||
"VCINSTALLDIR environment variables are not set.")
|
||||
if host != target_triple and "windows" in target_triple:
|
||||
if os.environ.get("VisualStudioVersion") or os.environ.get("VCINSTALLDIR"):
|
||||
print(
|
||||
"Can't cross-compile for Windows inside of a Visual Studio shell.\n"
|
||||
"Please run `python mach build [arguments]` to bypass automatic "
|
||||
"Visual Studio shell, and make sure the VisualStudioVersion and "
|
||||
"VCINSTALLDIR environment variables are not set."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Gather Cargo build timings (https://doc.rust-lang.org/cargo/reference/timings.html).
|
||||
|
@ -173,8 +184,7 @@ class MachCommands(CommandBase):
|
|||
for key in env:
|
||||
print((key, env[key]))
|
||||
|
||||
status = self.run_cargo_build_like_command(
|
||||
"rustc", opts, env=env, verbose=verbose, **kwargs)
|
||||
status = self.run_cargo_build_like_command("rustc", opts, env=env, verbose=verbose, **kwargs)
|
||||
|
||||
if status == 0:
|
||||
built_binary = self.get_binary_path(build_type, asan=with_asan)
|
||||
|
@ -201,12 +211,11 @@ class MachCommands(CommandBase):
|
|||
# like Instruments.app.
|
||||
try:
|
||||
import Cocoa
|
||||
|
||||
icon_path = path.join(self.get_top_dir(), "resources", "servo_1024.png")
|
||||
icon = Cocoa.NSImage.alloc().initWithContentsOfFile_(icon_path)
|
||||
if icon is not None:
|
||||
Cocoa.NSWorkspace.sharedWorkspace().setIcon_forFile_options_(icon,
|
||||
built_binary,
|
||||
0)
|
||||
Cocoa.NSWorkspace.sharedWorkspace().setIcon_forFile_options_(icon, built_binary, 0)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
@ -220,23 +229,16 @@ class MachCommands(CommandBase):
|
|||
|
||||
return status
|
||||
|
||||
@Command('clean',
|
||||
description='Clean the target/ and Python virtual environment directories',
|
||||
category='build')
|
||||
@CommandArgument('--manifest-path',
|
||||
default=None,
|
||||
help='Path to the manifest to the package to clean')
|
||||
@CommandArgument('--verbose', '-v',
|
||||
action='store_true',
|
||||
help='Print verbose output')
|
||||
@CommandArgument('params', nargs='...',
|
||||
help="Command-line arguments to be passed through to Cargo")
|
||||
@Command("clean", description="Clean the target/ and Python virtual environment directories", category="build")
|
||||
@CommandArgument("--manifest-path", default=None, help="Path to the manifest to the package to clean")
|
||||
@CommandArgument("--verbose", "-v", action="store_true", help="Print verbose output")
|
||||
@CommandArgument("params", nargs="...", help="Command-line arguments to be passed through to Cargo")
|
||||
def clean(self, manifest_path=None, params=[], verbose=False):
|
||||
self.ensure_bootstrapped()
|
||||
|
||||
virtualenv_path = path.join(self.get_top_dir(), '.venv')
|
||||
virtualenv_path = path.join(self.get_top_dir(), ".venv")
|
||||
if path.exists(virtualenv_path):
|
||||
print('Removing virtualenv directory: %s' % virtualenv_path)
|
||||
print("Removing virtualenv directory: %s" % virtualenv_path)
|
||||
shutil.rmtree(virtualenv_path)
|
||||
|
||||
opts = ["--manifest-path", manifest_path or path.join(self.context.topdir, "Cargo.toml")]
|
||||
|
@ -263,6 +265,7 @@ class MachCommands(CommandBase):
|
|||
def send_notification(self, **kwargs):
|
||||
try:
|
||||
import dbus
|
||||
|
||||
bus = dbus.SessionBus()
|
||||
notify_obj = bus.get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
|
||||
method = notify_obj.get_dbus_method("Notify", "org.freedesktop.Notifications")
|
||||
|
@ -274,17 +277,15 @@ class MachCommands(CommandBase):
|
|||
kwargs.get("notification_subtitle"),
|
||||
[], # actions
|
||||
{"transient": True}, # hints
|
||||
-1 # timeout
|
||||
-1, # timeout
|
||||
)
|
||||
except Exception as exception:
|
||||
print(f"[Warning] Could not generate notification: {exception}",
|
||||
file=sys.stderr)
|
||||
print(f"[Warning] Could not generate notification: {exception}", file=sys.stderr)
|
||||
return True
|
||||
|
||||
if notify_command:
|
||||
if call([notify_command, title, message]) != 0:
|
||||
print("[Warning] Could not generate notification: "
|
||||
f"Could not run '{notify_command}'.", file=sys.stderr)
|
||||
print(f"[Warning] Could not generate notification: Could not run '{notify_command}'.", file=sys.stderr)
|
||||
else:
|
||||
try:
|
||||
notifier = LinuxNotifier if sys.platform.startswith("linux") else None
|
||||
|
@ -384,11 +385,12 @@ def package_msvc_dlls(servo_exe_dir: str, target: BuildTarget):
|
|||
"x86_64": "x64",
|
||||
"i686": "x86",
|
||||
"aarch64": "arm64",
|
||||
}[target.triple().split('-')[0]]
|
||||
}[target.triple().split("-")[0]]
|
||||
|
||||
for msvc_redist_dir in servo.visual_studio.find_msvc_redist_dirs(vs_platform):
|
||||
if copy_file(os.path.join(msvc_redist_dir, "msvcp140.dll")) and \
|
||||
copy_file(os.path.join(msvc_redist_dir, "vcruntime140.dll")):
|
||||
if copy_file(os.path.join(msvc_redist_dir, "msvcp140.dll")) and copy_file(
|
||||
os.path.join(msvc_redist_dir, "vcruntime140.dll")
|
||||
):
|
||||
break
|
||||
|
||||
# Different SDKs install the file into different directory structures within the
|
||||
|
|
|
@ -118,7 +118,7 @@ def find_dep_path_newest(package, bin_path):
|
|||
deps_path = path.join(path.split(bin_path)[0], "build")
|
||||
candidates = []
|
||||
with cd(deps_path):
|
||||
for c in glob(package + '-*'):
|
||||
for c in glob(package + "-*"):
|
||||
candidate_path = path.join(deps_path, c)
|
||||
if path.exists(path.join(candidate_path, "output")):
|
||||
candidates.append(candidate_path)
|
||||
|
@ -152,24 +152,24 @@ def archive_deterministically(dir_to_archive, dest_archive, prepend_path=None):
|
|||
file_list.append(os.path.join(root, name))
|
||||
|
||||
# Sort file entries with the fixed locale
|
||||
with setlocale('C'):
|
||||
with setlocale("C"):
|
||||
file_list.sort(key=functools.cmp_to_key(locale.strcoll))
|
||||
|
||||
# Use a temporary file and atomic rename to avoid partially-formed
|
||||
# packaging (in case of exceptional situations like running out of disk space).
|
||||
# TODO do this in a temporary folder after #11983 is fixed
|
||||
temp_file = '{}.temp~'.format(dest_archive)
|
||||
with os.fdopen(os.open(temp_file, os.O_WRONLY | os.O_CREAT, 0o644), 'wb') as out_file:
|
||||
if dest_archive.endswith('.zip'):
|
||||
with zipfile.ZipFile(temp_file, 'w', zipfile.ZIP_DEFLATED) as zip_file:
|
||||
temp_file = "{}.temp~".format(dest_archive)
|
||||
with os.fdopen(os.open(temp_file, os.O_WRONLY | os.O_CREAT, 0o644), "wb") as out_file:
|
||||
if dest_archive.endswith(".zip"):
|
||||
with zipfile.ZipFile(temp_file, "w", zipfile.ZIP_DEFLATED) as zip_file:
|
||||
for entry in file_list:
|
||||
arcname = entry
|
||||
if prepend_path is not None:
|
||||
arcname = os.path.normpath(os.path.join(prepend_path, arcname))
|
||||
zip_file.write(entry, arcname=arcname)
|
||||
else:
|
||||
with gzip.GzipFile(mode='wb', fileobj=out_file, mtime=0) as gzip_file:
|
||||
with tarfile.open(fileobj=gzip_file, mode='w:') as tar_file:
|
||||
with gzip.GzipFile(mode="wb", fileobj=out_file, mtime=0) as gzip_file:
|
||||
with tarfile.open(fileobj=gzip_file, mode="w:") as tar_file:
|
||||
for entry in file_list:
|
||||
arcname = entry
|
||||
if prepend_path is not None:
|
||||
|
@ -180,35 +180,35 @@ def archive_deterministically(dir_to_archive, dest_archive, prepend_path=None):
|
|||
|
||||
def call(*args, **kwargs):
|
||||
"""Wrap `subprocess.call`, printing the command if verbose=True."""
|
||||
verbose = kwargs.pop('verbose', False)
|
||||
verbose = kwargs.pop("verbose", False)
|
||||
if verbose:
|
||||
print(' '.join(args[0]))
|
||||
print(" ".join(args[0]))
|
||||
# we have to use shell=True in order to get PATH handling
|
||||
# when looking for the binary on Windows
|
||||
return subprocess.call(*args, shell=sys.platform == 'win32', **kwargs)
|
||||
return subprocess.call(*args, shell=sys.platform == "win32", **kwargs)
|
||||
|
||||
|
||||
def check_output(*args, **kwargs) -> bytes:
|
||||
"""Wrap `subprocess.call`, printing the command if verbose=True."""
|
||||
verbose = kwargs.pop('verbose', False)
|
||||
verbose = kwargs.pop("verbose", False)
|
||||
if verbose:
|
||||
print(' '.join(args[0]))
|
||||
print(" ".join(args[0]))
|
||||
# we have to use shell=True in order to get PATH handling
|
||||
# when looking for the binary on Windows
|
||||
return subprocess.check_output(*args, shell=sys.platform == 'win32', **kwargs)
|
||||
return subprocess.check_output(*args, shell=sys.platform == "win32", **kwargs)
|
||||
|
||||
|
||||
def check_call(*args, **kwargs):
|
||||
"""Wrap `subprocess.check_call`, printing the command if verbose=True.
|
||||
|
||||
Also fix any unicode-containing `env`, for subprocess """
|
||||
verbose = kwargs.pop('verbose', False)
|
||||
Also fix any unicode-containing `env`, for subprocess"""
|
||||
verbose = kwargs.pop("verbose", False)
|
||||
|
||||
if verbose:
|
||||
print(' '.join(args[0]))
|
||||
print(" ".join(args[0]))
|
||||
# we have to use shell=True in order to get PATH handling
|
||||
# when looking for the binary on Windows
|
||||
proc = subprocess.Popen(*args, shell=sys.platform == 'win32', **kwargs)
|
||||
proc = subprocess.Popen(*args, shell=sys.platform == "win32", **kwargs)
|
||||
status = None
|
||||
# Leave it to the subprocess to handle Ctrl+C. If it terminates as
|
||||
# a result of Ctrl+C, proc.wait() will return a status code, and,
|
||||
|
@ -221,19 +221,19 @@ def check_call(*args, **kwargs):
|
|||
pass
|
||||
|
||||
if status:
|
||||
raise subprocess.CalledProcessError(status, ' '.join(*args))
|
||||
raise subprocess.CalledProcessError(status, " ".join(*args))
|
||||
|
||||
|
||||
def is_windows():
|
||||
return sys.platform == 'win32'
|
||||
return sys.platform == "win32"
|
||||
|
||||
|
||||
def is_macosx():
|
||||
return sys.platform == 'darwin'
|
||||
return sys.platform == "darwin"
|
||||
|
||||
|
||||
def is_linux():
|
||||
return sys.platform.startswith('linux')
|
||||
return sys.platform.startswith("linux")
|
||||
|
||||
|
||||
class BuildNotFound(Exception):
|
||||
|
@ -262,14 +262,13 @@ class CommandBase(object):
|
|||
# Contents of env vars are strings by default. This returns the
|
||||
# boolean value of the specified environment variable, or the
|
||||
# speciried default if the var doesn't contain True or False
|
||||
return {'True': True, 'False': False}.get(os.environ.get(var), default)
|
||||
return {"True": True, "False": False}.get(os.environ.get(var), default)
|
||||
|
||||
def resolverelative(category, key):
|
||||
# Allow ~
|
||||
self.config[category][key] = path.expanduser(self.config[category][key])
|
||||
# Resolve relative paths
|
||||
self.config[category][key] = path.join(context.topdir,
|
||||
self.config[category][key])
|
||||
self.config[category][key] = path.join(context.topdir, self.config[category][key])
|
||||
|
||||
if not hasattr(self.context, "bootstrapped"):
|
||||
self.context.bootstrapped = False
|
||||
|
@ -286,8 +285,7 @@ class CommandBase(object):
|
|||
self.config["tools"].setdefault("cache-dir", get_default_cache_dir(context.topdir))
|
||||
resolverelative("tools", "cache-dir")
|
||||
|
||||
default_cargo_home = os.environ.get("CARGO_HOME",
|
||||
path.join(context.topdir, ".cargo"))
|
||||
default_cargo_home = os.environ.get("CARGO_HOME", path.join(context.topdir, ".cargo"))
|
||||
self.config["tools"].setdefault("cargo-home-dir", default_cargo_home)
|
||||
resolverelative("tools", "cargo-home-dir")
|
||||
|
||||
|
@ -323,7 +321,7 @@ class CommandBase(object):
|
|||
return self._rust_toolchain
|
||||
|
||||
toolchain_file = path.join(self.context.topdir, "rust-toolchain.toml")
|
||||
self._rust_toolchain = toml.load(toolchain_file)['toolchain']['channel']
|
||||
self._rust_toolchain = toml.load(toolchain_file)["toolchain"]["channel"]
|
||||
return self._rust_toolchain
|
||||
|
||||
def get_top_dir(self):
|
||||
|
@ -337,14 +335,14 @@ class CommandBase(object):
|
|||
binary_path = path.join(base_path, build_type.directory_name(), binary_name)
|
||||
|
||||
if not path.exists(binary_path):
|
||||
raise BuildNotFound('No Servo binary found. Perhaps you forgot to run `./mach build`?')
|
||||
raise BuildNotFound("No Servo binary found. Perhaps you forgot to run `./mach build`?")
|
||||
|
||||
return binary_path
|
||||
|
||||
def detach_volume(self, mounted_volume):
|
||||
print("Detaching volume {}".format(mounted_volume))
|
||||
try:
|
||||
subprocess.check_call(['hdiutil', 'detach', mounted_volume])
|
||||
subprocess.check_call(["hdiutil", "detach", mounted_volume])
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Could not detach volume {} : {}".format(mounted_volume, e.returncode))
|
||||
sys.exit(1)
|
||||
|
@ -356,7 +354,7 @@ class CommandBase(object):
|
|||
def mount_dmg(self, dmg_path):
|
||||
print("Mounting dmg {}".format(dmg_path))
|
||||
try:
|
||||
subprocess.check_call(['hdiutil', 'attach', dmg_path])
|
||||
subprocess.check_call(["hdiutil", "attach", dmg_path])
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Could not mount Servo dmg : {}".format(e.returncode))
|
||||
sys.exit(1)
|
||||
|
@ -374,8 +372,9 @@ class CommandBase(object):
|
|||
self.detach_volume(mounted_volume)
|
||||
else:
|
||||
if is_windows():
|
||||
command = 'msiexec /a {} /qn TARGETDIR={}'.format(
|
||||
os.path.join(nightlies_folder, destination_file), destination_folder)
|
||||
command = "msiexec /a {} /qn TARGETDIR={}".format(
|
||||
os.path.join(nightlies_folder, destination_file), destination_folder
|
||||
)
|
||||
if subprocess.call(command, stdout=PIPE, stderr=PIPE) != 0:
|
||||
print("Could not extract the nightly executable from the msi package.")
|
||||
sys.exit(1)
|
||||
|
@ -394,8 +393,7 @@ class CommandBase(object):
|
|||
if nightly_date is None:
|
||||
return
|
||||
if not nightly_date:
|
||||
print(
|
||||
"No nightly date has been provided although the --nightly or -n flag has been passed.")
|
||||
print("No nightly date has been provided although the --nightly or -n flag has been passed.")
|
||||
sys.exit(1)
|
||||
# Will alow us to fetch the relevant builds from the nightly repository
|
||||
os_prefix = "linux"
|
||||
|
@ -406,55 +404,44 @@ class CommandBase(object):
|
|||
nightly_date = nightly_date.strip()
|
||||
# Fetch the filename to download from the build list
|
||||
repository_index = NIGHTLY_REPOSITORY_URL + "?list-type=2&prefix=nightly"
|
||||
req = urllib.request.Request(
|
||||
"{}/{}/{}".format(repository_index, os_prefix, nightly_date))
|
||||
req = urllib.request.Request("{}/{}/{}".format(repository_index, os_prefix, nightly_date))
|
||||
try:
|
||||
response = urllib.request.urlopen(req).read()
|
||||
tree = XML(response)
|
||||
namespaces = {'ns': tree.tag[1:tree.tag.index('}')]}
|
||||
file_to_download = tree.find('ns:Contents', namespaces).find(
|
||||
'ns:Key', namespaces).text
|
||||
namespaces = {"ns": tree.tag[1 : tree.tag.index("}")]}
|
||||
file_to_download = tree.find("ns:Contents", namespaces).find("ns:Key", namespaces).text
|
||||
except urllib.error.URLError as e:
|
||||
print("Could not fetch the available nightly versions from the repository : {}".format(
|
||||
e.reason))
|
||||
print("Could not fetch the available nightly versions from the repository : {}".format(e.reason))
|
||||
sys.exit(1)
|
||||
except AttributeError:
|
||||
print("Could not fetch a nightly version for date {} and platform {}".format(
|
||||
nightly_date, os_prefix))
|
||||
print("Could not fetch a nightly version for date {} and platform {}".format(nightly_date, os_prefix))
|
||||
sys.exit(1)
|
||||
|
||||
nightly_target_directory = path.join(self.context.topdir, "target")
|
||||
# ':' is not an authorized character for a file name on Windows
|
||||
# make sure the OS specific separator is used
|
||||
target_file_path = file_to_download.replace(':', '-').split('/')
|
||||
destination_file = os.path.join(
|
||||
nightly_target_directory, os.path.join(*target_file_path))
|
||||
target_file_path = file_to_download.replace(":", "-").split("/")
|
||||
destination_file = os.path.join(nightly_target_directory, os.path.join(*target_file_path))
|
||||
# Once extracted, the nightly folder name is the tar name without the extension
|
||||
# (eg /foo/bar/baz.tar.gz extracts to /foo/bar/baz)
|
||||
destination_folder = os.path.splitext(destination_file)[0]
|
||||
nightlies_folder = path.join(
|
||||
nightly_target_directory, 'nightly', os_prefix)
|
||||
nightlies_folder = path.join(nightly_target_directory, "nightly", os_prefix)
|
||||
|
||||
# Make sure the target directory exists
|
||||
if not os.path.isdir(nightlies_folder):
|
||||
print("The nightly folder for the target does not exist yet. Creating {}".format(
|
||||
nightlies_folder))
|
||||
print("The nightly folder for the target does not exist yet. Creating {}".format(nightlies_folder))
|
||||
os.makedirs(nightlies_folder)
|
||||
|
||||
# Download the nightly version
|
||||
if os.path.isfile(path.join(nightlies_folder, destination_file)):
|
||||
print("The nightly file {} has already been downloaded.".format(
|
||||
destination_file))
|
||||
print("The nightly file {} has already been downloaded.".format(destination_file))
|
||||
else:
|
||||
print("The nightly {} does not exist yet, downloading it.".format(
|
||||
destination_file))
|
||||
download_file(destination_file, NIGHTLY_REPOSITORY_URL
|
||||
+ file_to_download, destination_file)
|
||||
print("The nightly {} does not exist yet, downloading it.".format(destination_file))
|
||||
download_file(destination_file, NIGHTLY_REPOSITORY_URL + file_to_download, destination_file)
|
||||
|
||||
# Extract the downloaded nightly version
|
||||
if os.path.isdir(destination_folder):
|
||||
print("The nightly folder {} has already been extracted.".format(
|
||||
destination_folder))
|
||||
print("The nightly folder {} has already been extracted.".format(destination_folder))
|
||||
else:
|
||||
self.extract_nightly(nightlies_folder, destination_folder, destination_file)
|
||||
|
||||
|
@ -493,34 +480,34 @@ class CommandBase(object):
|
|||
elif self.config["build"]["incremental"] is not None:
|
||||
env["CARGO_INCREMENTAL"] = "0"
|
||||
|
||||
env['RUSTFLAGS'] = env.get('RUSTFLAGS', "")
|
||||
env["RUSTFLAGS"] = env.get("RUSTFLAGS", "")
|
||||
|
||||
if self.config["build"]["rustflags"]:
|
||||
env['RUSTFLAGS'] += " " + self.config["build"]["rustflags"]
|
||||
env["RUSTFLAGS"] += " " + self.config["build"]["rustflags"]
|
||||
|
||||
if not (self.config["build"]["ccache"] == ""):
|
||||
env['CCACHE'] = self.config["build"]["ccache"]
|
||||
env["CCACHE"] = self.config["build"]["ccache"]
|
||||
|
||||
env["CARGO_TARGET_DIR"] = servo.util.get_target_dir()
|
||||
|
||||
# Work around https://github.com/servo/servo/issues/24446
|
||||
# Argument-less str.split normalizes leading, trailing, and double spaces
|
||||
env['RUSTFLAGS'] = " ".join(env['RUSTFLAGS'].split())
|
||||
env["RUSTFLAGS"] = " ".join(env["RUSTFLAGS"].split())
|
||||
|
||||
# Suppress known false-positives during memory leak sanitizing.
|
||||
env["LSAN_OPTIONS"] = f"{env.get('LSAN_OPTIONS', '')}:suppressions={ASAN_LEAK_SUPPRESSION_FILE}"
|
||||
|
||||
self.target.configure_build_environment(env, self.config, self.context.topdir)
|
||||
|
||||
if sys.platform == 'win32' and 'windows' not in self.target.triple():
|
||||
if sys.platform == "win32" and "windows" not in self.target.triple():
|
||||
# aws-lc-rs only supports the Ninja Generator when cross-compiling on windows hosts to non-windows.
|
||||
env['TARGET_CMAKE_GENERATOR'] = "Ninja"
|
||||
if shutil.which('ninja') is None:
|
||||
env["TARGET_CMAKE_GENERATOR"] = "Ninja"
|
||||
if shutil.which("ninja") is None:
|
||||
print("Error: Cross-compiling servo on windows requires the Ninja tool to be installed and in PATH.")
|
||||
print("Hint: Ninja-build is available on github at: https://github.com/ninja-build/ninja/releases")
|
||||
exit(1)
|
||||
# `tr` is also required by the CMake build rules of `aws-lc-rs`
|
||||
if shutil.which('tr') is None:
|
||||
if shutil.which("tr") is None:
|
||||
print("Error: Cross-compiling servo on windows requires the `tr` tool, which was not found.")
|
||||
print("Hint: Try running ./mach from `git bash` instead of powershell.")
|
||||
exit(1)
|
||||
|
@ -528,132 +515,146 @@ class CommandBase(object):
|
|||
return env
|
||||
|
||||
@staticmethod
|
||||
def common_command_arguments(build_configuration=False,
|
||||
build_type=False,
|
||||
binary_selection=False,
|
||||
package_configuration=False
|
||||
):
|
||||
def common_command_arguments(
|
||||
build_configuration=False, build_type=False, binary_selection=False, package_configuration=False
|
||||
):
|
||||
decorators = []
|
||||
if build_type or binary_selection:
|
||||
decorators += [
|
||||
CommandArgumentGroup('Build Type'),
|
||||
CommandArgument('--release', '-r', group="Build Type",
|
||||
action='store_true',
|
||||
help='Build in release mode'),
|
||||
CommandArgument('--dev', '--debug', '-d', group="Build Type",
|
||||
action='store_true',
|
||||
help='Build in development mode'),
|
||||
CommandArgument('--prod', '--production', group="Build Type",
|
||||
action='store_true',
|
||||
help='Build in release mode without debug assertions'),
|
||||
CommandArgument('--profile', group="Build Type",
|
||||
help='Build with custom Cargo profile'),
|
||||
CommandArgument('--with-asan', action='store_true', help="Build with AddressSanitizer"),
|
||||
CommandArgumentGroup("Build Type"),
|
||||
CommandArgument(
|
||||
"--release", "-r", group="Build Type", action="store_true", help="Build in release mode"
|
||||
),
|
||||
CommandArgument(
|
||||
"--dev", "--debug", "-d", group="Build Type", action="store_true", help="Build in development mode"
|
||||
),
|
||||
CommandArgument(
|
||||
"--prod",
|
||||
"--production",
|
||||
group="Build Type",
|
||||
action="store_true",
|
||||
help="Build in release mode without debug assertions",
|
||||
),
|
||||
CommandArgument("--profile", group="Build Type", help="Build with custom Cargo profile"),
|
||||
CommandArgument("--with-asan", action="store_true", help="Build with AddressSanitizer"),
|
||||
]
|
||||
|
||||
if build_configuration:
|
||||
decorators += [
|
||||
CommandArgumentGroup('Cross Compilation'),
|
||||
CommandArgumentGroup("Cross Compilation"),
|
||||
CommandArgument(
|
||||
'--target', '-t',
|
||||
"--target",
|
||||
"-t",
|
||||
group="Cross Compilation",
|
||||
default=None,
|
||||
help='Cross compile for given target platform',
|
||||
help="Cross compile for given target platform",
|
||||
),
|
||||
CommandArgument(
|
||||
'--android', default=None, action='store_true',
|
||||
help='Build for Android. If --target is not specified, this '
|
||||
f'will choose the default target architecture ({AndroidTarget.DEFAULT_TRIPLE}).',
|
||||
"--android",
|
||||
default=None,
|
||||
action="store_true",
|
||||
help="Build for Android. If --target is not specified, this "
|
||||
f"will choose the default target architecture ({AndroidTarget.DEFAULT_TRIPLE}).",
|
||||
),
|
||||
CommandArgument(
|
||||
'--ohos', default=None, action='store_true',
|
||||
help='Build for OpenHarmony. If --target is not specified, this '
|
||||
f'will choose a default target architecture ({OpenHarmonyTarget.DEFAULT_TRIPLE}).',
|
||||
"--ohos",
|
||||
default=None,
|
||||
action="store_true",
|
||||
help="Build for OpenHarmony. If --target is not specified, this "
|
||||
f"will choose a default target architecture ({OpenHarmonyTarget.DEFAULT_TRIPLE}).",
|
||||
),
|
||||
CommandArgument('--win-arm64', action='store_true', help="Use arm64 Windows target"),
|
||||
CommandArgumentGroup('Feature Selection'),
|
||||
CommandArgument("--win-arm64", action="store_true", help="Use arm64 Windows target"),
|
||||
CommandArgumentGroup("Feature Selection"),
|
||||
CommandArgument(
|
||||
'--features', default=None, group="Feature Selection", nargs='+',
|
||||
help='Space-separated list of features to also build',
|
||||
"--features",
|
||||
default=None,
|
||||
group="Feature Selection",
|
||||
nargs="+",
|
||||
help="Space-separated list of features to also build",
|
||||
),
|
||||
CommandArgument(
|
||||
'--media-stack', default=None, group="Feature Selection",
|
||||
choices=["gstreamer", "dummy"], help='Which media stack to use',
|
||||
"--media-stack",
|
||||
default=None,
|
||||
group="Feature Selection",
|
||||
choices=["gstreamer", "dummy"],
|
||||
help="Which media stack to use",
|
||||
),
|
||||
CommandArgument(
|
||||
'--debug-mozjs',
|
||||
"--debug-mozjs",
|
||||
default=False,
|
||||
group="Feature Selection",
|
||||
action='store_true',
|
||||
help='Enable debug assertions in mozjs',
|
||||
action="store_true",
|
||||
help="Enable debug assertions in mozjs",
|
||||
),
|
||||
CommandArgument(
|
||||
'--with-debug-assertions',
|
||||
"--with-debug-assertions",
|
||||
default=False,
|
||||
group="Feature Selection",
|
||||
action='store_true',
|
||||
help='Enable debug assertions in release',
|
||||
action="store_true",
|
||||
help="Enable debug assertions in release",
|
||||
),
|
||||
CommandArgument(
|
||||
'--with-frame-pointer',
|
||||
default=None, group="Feature Selection",
|
||||
action='store_true',
|
||||
help='Build with frame pointer enabled, used by the background hang monitor.',
|
||||
"--with-frame-pointer",
|
||||
default=None,
|
||||
group="Feature Selection",
|
||||
action="store_true",
|
||||
help="Build with frame pointer enabled, used by the background hang monitor.",
|
||||
),
|
||||
CommandArgument(
|
||||
'--use-crown',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help="Enable Servo's `crown` linter tool"
|
||||
)
|
||||
"--use-crown", default=False, action="store_true", help="Enable Servo's `crown` linter tool"
|
||||
),
|
||||
]
|
||||
if package_configuration:
|
||||
decorators += [
|
||||
CommandArgumentGroup('Packaging options'),
|
||||
CommandArgumentGroup("Packaging options"),
|
||||
CommandArgument(
|
||||
'--flavor', default=None, group="Packaging options",
|
||||
help='Product flavor to be used when packaging with Gradle/Hvigor (android/ohos).'
|
||||
"--flavor",
|
||||
default=None,
|
||||
group="Packaging options",
|
||||
help="Product flavor to be used when packaging with Gradle/Hvigor (android/ohos).",
|
||||
),
|
||||
]
|
||||
|
||||
if binary_selection:
|
||||
decorators += [
|
||||
CommandArgumentGroup('Binary selection'),
|
||||
CommandArgument('--bin', default=None,
|
||||
help='Launch with specific binary'),
|
||||
CommandArgument('--nightly', '-n', default=None,
|
||||
help='Specify a YYYY-MM-DD nightly build to run'),
|
||||
CommandArgumentGroup("Binary selection"),
|
||||
CommandArgument("--bin", default=None, help="Launch with specific binary"),
|
||||
CommandArgument("--nightly", "-n", default=None, help="Specify a YYYY-MM-DD nightly build to run"),
|
||||
]
|
||||
|
||||
def decorator_function(original_function):
|
||||
def configuration_decorator(self, *args, **kwargs):
|
||||
if build_type or binary_selection:
|
||||
# If `build_type` already exists in kwargs we are doing a recursive dispatch.
|
||||
if 'build_type' not in kwargs:
|
||||
kwargs['build_type'] = self.configure_build_type(
|
||||
kwargs['release'], kwargs['dev'], kwargs['prod'], kwargs['profile'],
|
||||
if "build_type" not in kwargs:
|
||||
kwargs["build_type"] = self.configure_build_type(
|
||||
kwargs["release"],
|
||||
kwargs["dev"],
|
||||
kwargs["prod"],
|
||||
kwargs["profile"],
|
||||
)
|
||||
kwargs.pop('release', None)
|
||||
kwargs.pop('dev', None)
|
||||
kwargs.pop('prod', None)
|
||||
kwargs.pop('profile', None)
|
||||
kwargs.pop("release", None)
|
||||
kwargs.pop("dev", None)
|
||||
kwargs.pop("prod", None)
|
||||
kwargs.pop("profile", None)
|
||||
|
||||
if build_configuration:
|
||||
self.configure_build_target(kwargs)
|
||||
self.features = kwargs.get("features", None) or []
|
||||
self.enable_media = self.is_media_enabled(kwargs['media_stack'])
|
||||
self.enable_media = self.is_media_enabled(kwargs["media_stack"])
|
||||
|
||||
if binary_selection:
|
||||
if 'servo_binary' not in kwargs:
|
||||
kwargs['servo_binary'] = (kwargs.get('bin')
|
||||
or self.get_nightly_binary_path(kwargs.get('nightly'))
|
||||
or self.get_binary_path(kwargs.get('build_type'),
|
||||
asan=kwargs.get('with_asan')))
|
||||
kwargs.pop('bin')
|
||||
kwargs.pop('nightly')
|
||||
if "servo_binary" not in kwargs:
|
||||
kwargs["servo_binary"] = (
|
||||
kwargs.get("bin")
|
||||
or self.get_nightly_binary_path(kwargs.get("nightly"))
|
||||
or self.get_binary_path(kwargs.get("build_type"), asan=kwargs.get("with_asan"))
|
||||
)
|
||||
kwargs.pop("bin")
|
||||
kwargs.pop("nightly")
|
||||
if not build_type:
|
||||
kwargs.pop('build_type')
|
||||
kwargs.pop('with_asan')
|
||||
kwargs.pop("build_type")
|
||||
kwargs.pop("with_asan")
|
||||
|
||||
return original_function(self, *args, **kwargs)
|
||||
|
||||
|
@ -669,9 +670,9 @@ class CommandBase(object):
|
|||
def allow_target_configuration(original_function):
|
||||
def target_configuration_decorator(self, *args, **kwargs):
|
||||
self.configure_build_target(kwargs, suppress_log=True)
|
||||
kwargs.pop('target', False)
|
||||
kwargs.pop('android', False)
|
||||
kwargs.pop('ohos', False)
|
||||
kwargs.pop("target", False)
|
||||
kwargs.pop("android", False)
|
||||
kwargs.pop("ohos", False)
|
||||
return original_function(self, *args, **kwargs)
|
||||
|
||||
return target_configuration_decorator
|
||||
|
@ -709,15 +710,15 @@ class CommandBase(object):
|
|||
return BuildType.custom(profile)
|
||||
|
||||
def configure_build_target(self, kwargs: Dict[str, Any], suppress_log: bool = False):
|
||||
if hasattr(self.context, 'target'):
|
||||
if hasattr(self.context, "target"):
|
||||
# This call is for a dispatched command and we've already configured
|
||||
# the target, so just use it.
|
||||
self.target = self.context.target
|
||||
return
|
||||
|
||||
android = kwargs.get('android') or self.config["build"]["android"]
|
||||
ohos = kwargs.get('ohos') or self.config["build"]["ohos"]
|
||||
target_triple = kwargs.get('target')
|
||||
android = kwargs.get("android") or self.config["build"]["android"]
|
||||
ohos = kwargs.get("ohos") or self.config["build"]["ohos"]
|
||||
target_triple = kwargs.get("target")
|
||||
|
||||
if android and ohos:
|
||||
print("Cannot build both android and ohos targets simultaneously.")
|
||||
|
@ -753,8 +754,8 @@ class CommandBase(object):
|
|||
|
||||
def is_media_enabled(self, media_stack: Optional[str]):
|
||||
"""Determine whether media is enabled based on the value of the build target
|
||||
platform and the value of the '--media-stack' command-line argument.
|
||||
Returns true if media is enabled."""
|
||||
platform and the value of the '--media-stack' command-line argument.
|
||||
Returns true if media is enabled."""
|
||||
if not media_stack:
|
||||
if self.config["build"]["media-stack"] != "auto":
|
||||
media_stack = self.config["build"]["media-stack"]
|
||||
|
@ -768,20 +769,23 @@ class CommandBase(object):
|
|||
# Once we drop support for this platform (it's currently needed for wpt.fyi runners),
|
||||
# we can remove this workaround and officially only support Ubuntu 22.04 and up.
|
||||
platform = servo.platform.get()
|
||||
if not self.target.is_cross_build() and platform.is_linux and \
|
||||
not platform.is_gstreamer_installed(self.target):
|
||||
if not self.target.is_cross_build() and platform.is_linux and not platform.is_gstreamer_installed(self.target):
|
||||
return False
|
||||
|
||||
return media_stack != "dummy"
|
||||
|
||||
def run_cargo_build_like_command(
|
||||
self, command: str, cargo_args: List[str],
|
||||
env=None, verbose=False,
|
||||
debug_mozjs=False, with_debug_assertions=False,
|
||||
self,
|
||||
command: str,
|
||||
cargo_args: List[str],
|
||||
env=None,
|
||||
verbose=False,
|
||||
debug_mozjs=False,
|
||||
with_debug_assertions=False,
|
||||
with_frame_pointer=False,
|
||||
use_crown=False,
|
||||
target_override: Optional[str] = None,
|
||||
**_kwargs
|
||||
**_kwargs,
|
||||
):
|
||||
env = env or self.build_env()
|
||||
|
||||
|
@ -790,8 +794,7 @@ class CommandBase(object):
|
|||
platform = servo.platform.get()
|
||||
if self.enable_media and not platform.is_gstreamer_installed(self.target):
|
||||
raise FileNotFoundError(
|
||||
"GStreamer libraries not found (>= version 1.18)."
|
||||
"Please see installation instructions in README.md"
|
||||
"GStreamer libraries not found (>= version 1.18).Please see installation instructions in README.md"
|
||||
)
|
||||
|
||||
args = []
|
||||
|
@ -806,21 +809,23 @@ class CommandBase(object):
|
|||
args += ["--target", self.target.triple()]
|
||||
if type(self.target) in [AndroidTarget, OpenHarmonyTarget]:
|
||||
# Note: in practice `cargo rustc` should just be used unconditionally.
|
||||
assert command != 'build', "For Android / OpenHarmony `cargo rustc` must be used instead of cargo build"
|
||||
if command == 'rustc':
|
||||
assert command != "build", "For Android / OpenHarmony `cargo rustc` must be used instead of cargo build"
|
||||
if command == "rustc":
|
||||
args += ["--lib", "--crate-type=cdylib"]
|
||||
|
||||
features = []
|
||||
|
||||
if use_crown:
|
||||
if 'CARGO_BUILD_RUSTC' in env:
|
||||
current_rustc = env['CARGO_BUILD_RUSTC']
|
||||
if current_rustc != 'crown':
|
||||
print('Error: `mach` was called with `--use-crown` while `CARGO_BUILD_RUSTC` was'
|
||||
f'already set to `{current_rustc}` in the parent environment.\n'
|
||||
'These options conflict, please specify only one of them.')
|
||||
if "CARGO_BUILD_RUSTC" in env:
|
||||
current_rustc = env["CARGO_BUILD_RUSTC"]
|
||||
if current_rustc != "crown":
|
||||
print(
|
||||
"Error: `mach` was called with `--use-crown` while `CARGO_BUILD_RUSTC` was"
|
||||
f"already set to `{current_rustc}` in the parent environment.\n"
|
||||
"These options conflict, please specify only one of them."
|
||||
)
|
||||
sys.exit(1)
|
||||
env['CARGO_BUILD_RUSTC'] = 'crown'
|
||||
env["CARGO_BUILD_RUSTC"] = "crown"
|
||||
# Modyfing `RUSTC` or `CARGO_BUILD_RUSTC` to use a linter does not cause
|
||||
# `cargo check` to rebuild. To work around this bug use a `crown` feature
|
||||
# to invalidate caches and force a rebuild / relint.
|
||||
|
@ -835,7 +840,7 @@ class CommandBase(object):
|
|||
features.append("debugmozjs")
|
||||
|
||||
if with_frame_pointer:
|
||||
env['RUSTFLAGS'] = env.get('RUSTFLAGS', "") + " -C force-frame-pointers=yes"
|
||||
env["RUSTFLAGS"] = env.get("RUSTFLAGS", "") + " -C force-frame-pointers=yes"
|
||||
features.append("profilemozjs")
|
||||
if self.config["build"]["webgl-backtrace"]:
|
||||
features.append("webgl-backtrace")
|
||||
|
@ -844,11 +849,11 @@ class CommandBase(object):
|
|||
args += ["--features", " ".join(features)]
|
||||
|
||||
if with_debug_assertions or self.config["build"]["debug-assertions"]:
|
||||
env['RUSTFLAGS'] = env.get('RUSTFLAGS', "") + " -C debug_assertions"
|
||||
env["RUSTFLAGS"] = env.get("RUSTFLAGS", "") + " -C debug_assertions"
|
||||
|
||||
# mozjs gets its Python from `env['PYTHON3']`, which defaults to `python3`,
|
||||
# but uv venv on Windows only provides a `python`, not `python3`.
|
||||
env['PYTHON3'] = "python"
|
||||
env["PYTHON3"] = "python"
|
||||
|
||||
return call(["cargo", command] + args + cargo_args, env=env, verbose=verbose)
|
||||
|
||||
|
@ -877,13 +882,9 @@ class CommandBase(object):
|
|||
if not self.target.is_cross_build():
|
||||
return
|
||||
|
||||
installed_targets = check_output(
|
||||
["rustup", "target", "list", "--installed"],
|
||||
cwd=self.context.topdir
|
||||
).decode()
|
||||
installed_targets = check_output(["rustup", "target", "list", "--installed"], cwd=self.context.topdir).decode()
|
||||
if self.target.triple() not in installed_targets:
|
||||
check_call(["rustup", "target", "add", self.target.triple()],
|
||||
cwd=self.context.topdir)
|
||||
check_call(["rustup", "target", "add", self.target.triple()], cwd=self.context.topdir)
|
||||
|
||||
def ensure_rustup_version(self):
|
||||
try:
|
||||
|
@ -891,16 +892,18 @@ class CommandBase(object):
|
|||
["rustup" + servo.platform.get().executable_suffix(), "--version"],
|
||||
# Silence "info: This is the version for the rustup toolchain manager,
|
||||
# not the rustc compiler."
|
||||
stderr=open(os.devnull, "wb")
|
||||
stderr=open(os.devnull, "wb"),
|
||||
)
|
||||
except OSError as e:
|
||||
if e.errno == NO_SUCH_FILE_OR_DIRECTORY:
|
||||
print("It looks like rustup is not installed. See instructions at "
|
||||
"https://github.com/servo/servo/#setting-up-your-environment")
|
||||
print(
|
||||
"It looks like rustup is not installed. See instructions at "
|
||||
"https://github.com/servo/servo/#setting-up-your-environment"
|
||||
)
|
||||
print()
|
||||
sys.exit(1)
|
||||
raise
|
||||
version = tuple(map(int, re.match(br"rustup (\d+)\.(\d+)\.(\d+)", version_line).groups()))
|
||||
version = tuple(map(int, re.match(rb"rustup (\d+)\.(\d+)\.(\d+)", version_line).groups()))
|
||||
version_needed = (1, 23, 0)
|
||||
if version < version_needed:
|
||||
print("rustup is at version %s.%s.%s, Servo requires %s.%s.%s or more recent." % (version + version_needed))
|
||||
|
@ -910,25 +913,25 @@ class CommandBase(object):
|
|||
def ensure_clobbered(self, target_dir=None):
|
||||
if target_dir is None:
|
||||
target_dir = util.get_target_dir()
|
||||
auto = True if os.environ.get('AUTOCLOBBER', False) else False
|
||||
src_clobber = os.path.join(self.context.topdir, 'CLOBBER')
|
||||
target_clobber = os.path.join(target_dir, 'CLOBBER')
|
||||
auto = True if os.environ.get("AUTOCLOBBER", False) else False
|
||||
src_clobber = os.path.join(self.context.topdir, "CLOBBER")
|
||||
target_clobber = os.path.join(target_dir, "CLOBBER")
|
||||
|
||||
if not os.path.exists(target_dir):
|
||||
os.makedirs(target_dir)
|
||||
|
||||
if not os.path.exists(target_clobber):
|
||||
# Simply touch the file.
|
||||
with open(target_clobber, 'a'):
|
||||
with open(target_clobber, "a"):
|
||||
pass
|
||||
|
||||
if auto:
|
||||
if os.path.getmtime(src_clobber) > os.path.getmtime(target_clobber):
|
||||
print('Automatically clobbering target directory: {}'.format(target_dir))
|
||||
print("Automatically clobbering target directory: {}".format(target_dir))
|
||||
|
||||
try:
|
||||
Registrar.dispatch("clean", context=self.context, verbose=True)
|
||||
print('Successfully completed auto clobber.')
|
||||
print("Successfully completed auto clobber.")
|
||||
except subprocess.CalledProcessError as error:
|
||||
sys.exit(error)
|
||||
else:
|
||||
|
|
|
@ -25,12 +25,10 @@ from servo.command_base import CommandBase, cd, call
|
|||
|
||||
@CommandProvider
|
||||
class MachCommands(CommandBase):
|
||||
@Command('check',
|
||||
description='Run "cargo check"',
|
||||
category='devenv')
|
||||
@Command("check", description='Run "cargo check"', category="devenv")
|
||||
@CommandArgument(
|
||||
'params', default=None, nargs='...',
|
||||
help="Command-line arguments to be passed through to cargo check")
|
||||
"params", default=None, nargs="...", help="Command-line arguments to be passed through to cargo check"
|
||||
)
|
||||
@CommandBase.common_command_arguments(build_configuration=True, build_type=False)
|
||||
def check(self, params, **kwargs):
|
||||
if not params:
|
||||
|
@ -40,45 +38,34 @@ class MachCommands(CommandBase):
|
|||
self.ensure_clobbered()
|
||||
status = self.run_cargo_build_like_command("check", params, **kwargs)
|
||||
if status == 0:
|
||||
print('Finished checking, binary NOT updated. Consider ./mach build before ./mach run')
|
||||
print("Finished checking, binary NOT updated. Consider ./mach build before ./mach run")
|
||||
|
||||
return status
|
||||
|
||||
@Command('cargo-update',
|
||||
description='Same as update-cargo',
|
||||
category='devenv')
|
||||
@Command("cargo-update", description="Same as update-cargo", category="devenv")
|
||||
@CommandArgument(
|
||||
'params', default=None, nargs='...',
|
||||
help='Command-line arguments to be passed through to cargo update')
|
||||
@CommandArgument(
|
||||
'--package', '-p', default=None,
|
||||
help='Updates selected package')
|
||||
@CommandArgument(
|
||||
'--all-packages', '-a', action='store_true',
|
||||
help='Updates all packages')
|
||||
@CommandArgument(
|
||||
'--dry-run', '-d', action='store_true',
|
||||
help='Show outdated packages.')
|
||||
"params", default=None, nargs="...", help="Command-line arguments to be passed through to cargo update"
|
||||
)
|
||||
@CommandArgument("--package", "-p", default=None, help="Updates selected package")
|
||||
@CommandArgument("--all-packages", "-a", action="store_true", help="Updates all packages")
|
||||
@CommandArgument("--dry-run", "-d", action="store_true", help="Show outdated packages.")
|
||||
def cargo_update(self, params=None, package=None, all_packages=None, dry_run=None):
|
||||
self.update_cargo(params, package, all_packages, dry_run)
|
||||
|
||||
@Command('update-cargo',
|
||||
description='Update Cargo dependencies',
|
||||
category='devenv')
|
||||
@Command("update-cargo", description="Update Cargo dependencies", category="devenv")
|
||||
@CommandArgument(
|
||||
'params', default=None, nargs='...',
|
||||
help='Command-line arguments to be passed through to cargo update')
|
||||
"params", default=None, nargs="...", help="Command-line arguments to be passed through to cargo update"
|
||||
)
|
||||
@CommandArgument("--package", "-p", default=None, help="Updates the selected package")
|
||||
@CommandArgument(
|
||||
'--package', '-p', default=None,
|
||||
help='Updates the selected package')
|
||||
@CommandArgument(
|
||||
'--all-packages', '-a', action='store_true',
|
||||
help='Updates all packages. NOTE! This is very likely to break your '
|
||||
'working copy, making it impossible to build servo. Only do '
|
||||
'this if you really know what you are doing.')
|
||||
@CommandArgument(
|
||||
'--dry-run', '-d', action='store_true',
|
||||
help='Show outdated packages.')
|
||||
"--all-packages",
|
||||
"-a",
|
||||
action="store_true",
|
||||
help="Updates all packages. NOTE! This is very likely to break your "
|
||||
"working copy, making it impossible to build servo. Only do "
|
||||
"this if you really know what you are doing.",
|
||||
)
|
||||
@CommandArgument("--dry-run", "-d", action="store_true", help="Show outdated packages.")
|
||||
def update_cargo(self, params=None, package=None, all_packages=None, dry_run=None):
|
||||
if not params:
|
||||
params = []
|
||||
|
@ -97,12 +84,8 @@ class MachCommands(CommandBase):
|
|||
with cd(self.context.topdir):
|
||||
call(["cargo", "update"] + params, env=self.build_env())
|
||||
|
||||
@Command('rustc',
|
||||
description='Run the Rust compiler',
|
||||
category='devenv')
|
||||
@CommandArgument(
|
||||
'params', default=None, nargs='...',
|
||||
help="Command-line arguments to be passed through to rustc")
|
||||
@Command("rustc", description="Run the Rust compiler", category="devenv")
|
||||
@CommandArgument("params", default=None, nargs="...", help="Command-line arguments to be passed through to rustc")
|
||||
def rustc(self, params):
|
||||
if params is None:
|
||||
params = []
|
||||
|
@ -110,12 +93,10 @@ class MachCommands(CommandBase):
|
|||
self.ensure_bootstrapped()
|
||||
return call(["rustc"] + params, env=self.build_env())
|
||||
|
||||
@Command('cargo-fix',
|
||||
description='Run "cargo fix"',
|
||||
category='devenv')
|
||||
@Command("cargo-fix", description='Run "cargo fix"', category="devenv")
|
||||
@CommandArgument(
|
||||
'params', default=None, nargs='...',
|
||||
help="Command-line arguments to be passed through to cargo-fix")
|
||||
"params", default=None, nargs="...", help="Command-line arguments to be passed through to cargo-fix"
|
||||
)
|
||||
@CommandBase.common_command_arguments(build_configuration=True, build_type=False)
|
||||
def cargo_fix(self, params, **kwargs):
|
||||
if not params:
|
||||
|
@ -125,12 +106,8 @@ class MachCommands(CommandBase):
|
|||
self.ensure_clobbered()
|
||||
return self.run_cargo_build_like_command("fix", params, **kwargs)
|
||||
|
||||
@Command('clippy',
|
||||
description='Run "cargo clippy"',
|
||||
category='devenv')
|
||||
@CommandArgument(
|
||||
'params', default=None, nargs='...',
|
||||
help="Command-line arguments to be passed through to clippy")
|
||||
@Command("clippy", description='Run "cargo clippy"', category="devenv")
|
||||
@CommandArgument("params", default=None, nargs="...", help="Command-line arguments to be passed through to clippy")
|
||||
@CommandBase.common_command_arguments(build_configuration=True, build_type=False)
|
||||
def cargo_clippy(self, params, **kwargs):
|
||||
if not params:
|
||||
|
@ -139,48 +116,42 @@ class MachCommands(CommandBase):
|
|||
self.ensure_bootstrapped()
|
||||
self.ensure_clobbered()
|
||||
env = self.build_env()
|
||||
env['RUSTC'] = 'rustc'
|
||||
env["RUSTC"] = "rustc"
|
||||
return self.run_cargo_build_like_command("clippy", params, env=env, **kwargs)
|
||||
|
||||
@Command('grep',
|
||||
description='`git grep` for selected directories.',
|
||||
category='devenv')
|
||||
@Command("grep", description="`git grep` for selected directories.", category="devenv")
|
||||
@CommandArgument(
|
||||
'params', default=None, nargs='...',
|
||||
help="Command-line arguments to be passed through to `git grep`")
|
||||
"params", default=None, nargs="...", help="Command-line arguments to be passed through to `git grep`"
|
||||
)
|
||||
def grep(self, params):
|
||||
if not params:
|
||||
params = []
|
||||
# get all directories under tests/
|
||||
tests_dirs = listdir('tests')
|
||||
tests_dirs = listdir("tests")
|
||||
# Directories to be excluded under tests/
|
||||
excluded_tests_dirs = ['wpt', 'jquery']
|
||||
excluded_tests_dirs = ["wpt", "jquery"]
|
||||
tests_dirs = filter(lambda dir: dir not in excluded_tests_dirs, tests_dirs)
|
||||
# Set of directories in project root
|
||||
root_dirs = ['components', 'ports', 'python', 'etc', 'resources']
|
||||
root_dirs = ["components", "ports", "python", "etc", "resources"]
|
||||
# Generate absolute paths for directories in tests/ and project-root/
|
||||
tests_dirs_abs = [path.join(self.context.topdir, 'tests', s) for s in tests_dirs]
|
||||
tests_dirs_abs = [path.join(self.context.topdir, "tests", s) for s in tests_dirs]
|
||||
root_dirs_abs = [path.join(self.context.topdir, s) for s in root_dirs]
|
||||
# Absolute paths for all directories to be considered
|
||||
grep_paths = root_dirs_abs + tests_dirs_abs
|
||||
return call(
|
||||
["git"] + ["grep"] + params + ['--'] + grep_paths + [':(exclude)*.min.js', ':(exclude)*.min.css'],
|
||||
env=self.build_env())
|
||||
["git"] + ["grep"] + params + ["--"] + grep_paths + [":(exclude)*.min.js", ":(exclude)*.min.css"],
|
||||
env=self.build_env(),
|
||||
)
|
||||
|
||||
@Command('fetch',
|
||||
description='Fetch Rust, Cargo and Cargo dependencies',
|
||||
category='devenv')
|
||||
@Command("fetch", description="Fetch Rust, Cargo and Cargo dependencies", category="devenv")
|
||||
def fetch(self):
|
||||
self.ensure_bootstrapped()
|
||||
return call(["cargo", "fetch"], env=self.build_env())
|
||||
|
||||
@Command('ndk-stack',
|
||||
description='Invoke the ndk-stack tool with the expected symbol paths',
|
||||
category='devenv')
|
||||
@CommandArgument('--release', action='store_true', help="Use release build symbols")
|
||||
@CommandArgument('--target', action='store', default="armv7-linux-androideabi",
|
||||
help="Build target")
|
||||
@CommandArgument('logfile', action='store', help="Path to logcat output with crash report")
|
||||
@Command("ndk-stack", description="Invoke the ndk-stack tool with the expected symbol paths", category="devenv")
|
||||
@CommandArgument("--release", action="store_true", help="Use release build symbols")
|
||||
@CommandArgument("--target", action="store", default="armv7-linux-androideabi", help="Build target")
|
||||
@CommandArgument("logfile", action="store", help="Path to logcat output with crash report")
|
||||
def stack(self, release, target, logfile):
|
||||
if not path.isfile(logfile):
|
||||
print(logfile + " doesn't exist")
|
||||
|
@ -190,21 +161,13 @@ class MachCommands(CommandBase):
|
|||
ndk_stack = path.join(env["ANDROID_NDK"], "ndk-stack")
|
||||
self.setup_configuration_for_android_target(target)
|
||||
sym_path = path.join(
|
||||
"target",
|
||||
target,
|
||||
"release" if release else "debug",
|
||||
"apk",
|
||||
"obj",
|
||||
"local",
|
||||
self.config["android"]["lib"])
|
||||
"target", target, "release" if release else "debug", "apk", "obj", "local", self.config["android"]["lib"]
|
||||
)
|
||||
print(subprocess.check_output([ndk_stack, "-sym", sym_path, "-dump", logfile]))
|
||||
|
||||
@Command('ndk-gdb',
|
||||
description='Invoke ndk-gdb tool with the expected symbol paths',
|
||||
category='devenv')
|
||||
@CommandArgument('--release', action='store_true', help="Use release build symbols")
|
||||
@CommandArgument('--target', action='store', default="armv7-linux-androideabi",
|
||||
help="Build target")
|
||||
@Command("ndk-gdb", description="Invoke ndk-gdb tool with the expected symbol paths", category="devenv")
|
||||
@CommandArgument("--release", action="store_true", help="Use release build symbols")
|
||||
@CommandArgument("--target", action="store", default="armv7-linux-androideabi", help="Build target")
|
||||
def ndk_gdb(self, release, target):
|
||||
env = self.build_env()
|
||||
ndk_gdb = path.join(env["ANDROID_NDK"], "ndk-gdb")
|
||||
|
@ -218,7 +181,7 @@ class MachCommands(CommandBase):
|
|||
"apk",
|
||||
"obj",
|
||||
"local",
|
||||
self.config["android"]["lib"]
|
||||
self.config["android"]["lib"],
|
||||
),
|
||||
path.join(
|
||||
getcwd(),
|
||||
|
@ -227,27 +190,38 @@ class MachCommands(CommandBase):
|
|||
"release" if release else "debug",
|
||||
"apk",
|
||||
"libs",
|
||||
self.config["android"]["lib"]
|
||||
self.config["android"]["lib"],
|
||||
),
|
||||
]
|
||||
env["NDK_PROJECT_PATH"] = path.join(getcwd(), "support", "android", "apk")
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
f.write('\n'.join([
|
||||
"python",
|
||||
"param = gdb.parameter('solib-search-path')",
|
||||
"param += ':{}'".format(':'.join(sym_paths)),
|
||||
"gdb.execute('set solib-search-path ' + param)",
|
||||
"end",
|
||||
]))
|
||||
f.write(
|
||||
"\n".join(
|
||||
[
|
||||
"python",
|
||||
"param = gdb.parameter('solib-search-path')",
|
||||
"param += ':{}'".format(":".join(sym_paths)),
|
||||
"gdb.execute('set solib-search-path ' + param)",
|
||||
"end",
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
p = subprocess.Popen([
|
||||
ndk_gdb,
|
||||
"--adb", adb_path,
|
||||
"--project", "support/android/apk/servoapp/src/main/",
|
||||
"--launch", "org.servo.servoshell.MainActivity",
|
||||
"-x", f.name,
|
||||
"--verbose",
|
||||
], env=env)
|
||||
p = subprocess.Popen(
|
||||
[
|
||||
ndk_gdb,
|
||||
"--adb",
|
||||
adb_path,
|
||||
"--project",
|
||||
"support/android/apk/servoapp/src/main/",
|
||||
"--launch",
|
||||
"org.servo.servoshell.MainActivity",
|
||||
"-x",
|
||||
f.name,
|
||||
"--verbose",
|
||||
],
|
||||
env=env,
|
||||
)
|
||||
return p.wait()
|
||||
|
|
|
@ -51,10 +51,21 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
|||
def test_sources_list(self):
|
||||
self.start_web_server(test_dir=os.path.join(DevtoolsTests.script_path, "devtools_tests/sources"))
|
||||
self.run_servoshell()
|
||||
self.assert_sources_list(2, set([
|
||||
tuple([f"{self.base_url}/classic.js", f"{self.base_url}/test.html", "https://servo.org/js/load-table.js"]),
|
||||
tuple([f"{self.base_url}/worker.js"]),
|
||||
]))
|
||||
self.assert_sources_list(
|
||||
2,
|
||||
set(
|
||||
[
|
||||
tuple(
|
||||
[
|
||||
f"{self.base_url}/classic.js",
|
||||
f"{self.base_url}/test.html",
|
||||
"https://servo.org/js/load-table.js",
|
||||
]
|
||||
),
|
||||
tuple([f"{self.base_url}/worker.js"]),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
def test_sources_list_with_data_no_scripts(self):
|
||||
self.run_servoshell(url="data:text/html,")
|
||||
|
@ -70,7 +81,7 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
|||
|
||||
def test_sources_list_with_data_external_classic_script(self):
|
||||
self.start_web_server(test_dir=os.path.join(DevtoolsTests.script_path, "devtools_tests/sources"))
|
||||
self.run_servoshell(url=f"data:text/html,<script src=\"{self.base_url}/classic.js\"></script>")
|
||||
self.run_servoshell(url=f'data:text/html,<script src="{self.base_url}/classic.js"></script>')
|
||||
self.assert_sources_list(1, set([tuple([f"{self.base_url}/classic.js"])]))
|
||||
|
||||
def test_sources_list_with_data_empty_inline_module_script(self):
|
||||
|
@ -158,7 +169,9 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
|
|||
done.set_result(e)
|
||||
|
||||
client.add_event_listener(
|
||||
watcher.actor_id, Events.Watcher.TARGET_AVAILABLE_FORM, on_target,
|
||||
watcher.actor_id,
|
||||
Events.Watcher.TARGET_AVAILABLE_FORM,
|
||||
on_target,
|
||||
)
|
||||
watcher.watch_targets(WatcherActor.Targets.FRAME)
|
||||
watcher.watch_targets(WatcherActor.Targets.WORKER)
|
||||
|
|
|
@ -15,7 +15,7 @@ from typing import Set
|
|||
|
||||
# This file is called as a script from components/servo/build.rs, so
|
||||
# we need to explicitly modify the search path here.
|
||||
sys.path[0:0] = [os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))]
|
||||
sys.path[0:0] = [os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))]
|
||||
from servo.platform.build_target import BuildTarget # noqa: E402
|
||||
|
||||
GSTREAMER_BASE_LIBS = [
|
||||
|
@ -158,18 +158,12 @@ def windows_dlls():
|
|||
|
||||
|
||||
def windows_plugins():
|
||||
libs = [
|
||||
*GSTREAMER_PLUGIN_LIBS,
|
||||
*GSTREAMER_WIN_PLUGIN_LIBS
|
||||
]
|
||||
libs = [*GSTREAMER_PLUGIN_LIBS, *GSTREAMER_WIN_PLUGIN_LIBS]
|
||||
return [f"{lib}.dll" for lib in libs]
|
||||
|
||||
|
||||
def macos_plugins():
|
||||
plugins = [
|
||||
*GSTREAMER_PLUGIN_LIBS,
|
||||
*GSTREAMER_MAC_PLUGIN_LIBS
|
||||
]
|
||||
plugins = [*GSTREAMER_PLUGIN_LIBS, *GSTREAMER_MAC_PLUGIN_LIBS]
|
||||
|
||||
return [f"lib{plugin}.dylib" for plugin in plugins]
|
||||
|
||||
|
@ -178,34 +172,35 @@ def write_plugin_list(target):
|
|||
plugins = []
|
||||
if "apple-" in target:
|
||||
plugins = macos_plugins()
|
||||
elif '-windows-' in target:
|
||||
elif "-windows-" in target:
|
||||
plugins = windows_plugins()
|
||||
print('''/* This is a generated file. Do not modify. */
|
||||
print(
|
||||
"""/* This is a generated file. Do not modify. */
|
||||
|
||||
pub(crate) static GSTREAMER_PLUGINS: &[&str] = &[
|
||||
%s
|
||||
];
|
||||
''' % ',\n'.join(map(lambda x: '"' + x + '"', plugins)))
|
||||
"""
|
||||
% ",\n".join(map(lambda x: '"' + x + '"', plugins))
|
||||
)
|
||||
|
||||
|
||||
def is_macos_system_library(library_path: str) -> bool:
|
||||
"""Returns true if if the given dependency line from otool refers to
|
||||
a system library that should not be packaged."""
|
||||
return (library_path.startswith("/System/Library")
|
||||
or library_path.startswith("/usr/lib")
|
||||
or ".asan." in library_path)
|
||||
a system library that should not be packaged."""
|
||||
return library_path.startswith("/System/Library") or library_path.startswith("/usr/lib") or ".asan." in library_path
|
||||
|
||||
|
||||
def rewrite_dependencies_to_be_relative(binary: str, dependency_lines: Set[str], relative_path: str):
|
||||
"""Given a path to a binary (either an executable or a dylib), rewrite the
|
||||
the given dependency lines to be found at the given relative path to
|
||||
the executable in which they are used. In our case, this is typically servoshell."""
|
||||
the given dependency lines to be found at the given relative path to
|
||||
the executable in which they are used. In our case, this is typically servoshell."""
|
||||
for dependency_line in dependency_lines:
|
||||
if is_macos_system_library(dependency_line) or dependency_line.startswith("@rpath/"):
|
||||
continue
|
||||
|
||||
new_path = os.path.join("@executable_path", relative_path, os.path.basename(dependency_line))
|
||||
arguments = ['install_name_tool', '-change', dependency_line, new_path, binary]
|
||||
arguments = ["install_name_tool", "-change", dependency_line, new_path, binary]
|
||||
try:
|
||||
subprocess.check_call(arguments)
|
||||
except subprocess.CalledProcessError as exception:
|
||||
|
@ -214,13 +209,13 @@ def rewrite_dependencies_to_be_relative(binary: str, dependency_lines: Set[str],
|
|||
|
||||
def make_rpath_path_absolute(dylib_path_from_otool: str, rpath: str):
|
||||
"""Given a dylib dependency from otool, resolve the path into a full path if it
|
||||
contains `@rpath`."""
|
||||
contains `@rpath`."""
|
||||
if not dylib_path_from_otool.startswith("@rpath/"):
|
||||
return dylib_path_from_otool
|
||||
|
||||
# Not every dependency is in the same directory as the binary that is references. For
|
||||
# instance, plugins dylibs can be found in "gstreamer-1.0".
|
||||
path_relative_to_rpath = dylib_path_from_otool.replace('@rpath/', '')
|
||||
path_relative_to_rpath = dylib_path_from_otool.replace("@rpath/", "")
|
||||
for relative_directory in ["", "..", "gstreamer-1.0"]:
|
||||
full_path = os.path.join(rpath, relative_directory, path_relative_to_rpath)
|
||||
if os.path.exists(full_path):
|
||||
|
@ -231,14 +226,14 @@ def make_rpath_path_absolute(dylib_path_from_otool: str, rpath: str):
|
|||
|
||||
def find_non_system_dependencies_with_otool(binary_path: str) -> Set[str]:
|
||||
"""Given a binary path, find all dylib dependency lines that do not refer to
|
||||
system libraries."""
|
||||
process = subprocess.Popen(['/usr/bin/otool', '-L', binary_path], stdout=subprocess.PIPE)
|
||||
system libraries."""
|
||||
process = subprocess.Popen(["/usr/bin/otool", "-L", binary_path], stdout=subprocess.PIPE)
|
||||
output = set()
|
||||
|
||||
for line in map(lambda line: line.decode('utf8'), process.stdout):
|
||||
for line in map(lambda line: line.decode("utf8"), process.stdout):
|
||||
if not line.startswith("\t"):
|
||||
continue
|
||||
dependency = line.split(' ', 1)[0][1:]
|
||||
dependency = line.split(" ", 1)[0][1:]
|
||||
|
||||
# No need to do any processing for system libraries. They should be
|
||||
# present on all macOS systems.
|
||||
|
@ -249,8 +244,8 @@ def find_non_system_dependencies_with_otool(binary_path: str) -> Set[str]:
|
|||
|
||||
def package_gstreamer_dylibs(binary_path: str, library_target_directory: str, target: BuildTarget):
|
||||
"""Copy all GStreamer dependencies to the "lib" subdirectory of a built version of
|
||||
Servo. Also update any transitive shared library paths so that they are relative to
|
||||
this subdirectory."""
|
||||
Servo. Also update any transitive shared library paths so that they are relative to
|
||||
this subdirectory."""
|
||||
|
||||
# This import only works when called from `mach`.
|
||||
import servo.platform
|
||||
|
@ -288,8 +283,7 @@ def package_gstreamer_dylibs(binary_path: str, library_target_directory: str, ta
|
|||
# which are loaded dynmically at runtime and don't appear in `otool` output.
|
||||
binary_dependencies = set(find_non_system_dependencies_with_otool(binary_path))
|
||||
binary_dependencies.update(
|
||||
[os.path.join(gstreamer_root_libs, "gstreamer-1.0", plugin)
|
||||
for plugin in macos_plugins()]
|
||||
[os.path.join(gstreamer_root_libs, "gstreamer-1.0", plugin) for plugin in macos_plugins()]
|
||||
)
|
||||
|
||||
rewrite_dependencies_to_be_relative(binary_path, binary_dependencies, relative_path)
|
||||
|
|
|
@ -15,12 +15,7 @@ import test
|
|||
import logging
|
||||
import random
|
||||
|
||||
test_summary = {
|
||||
test.Status.KILLED: 0,
|
||||
test.Status.SURVIVED: 0,
|
||||
test.Status.SKIPPED: 0,
|
||||
test.Status.UNEXPECTED: 0
|
||||
}
|
||||
test_summary = {test.Status.KILLED: 0, test.Status.SURVIVED: 0, test.Status.SKIPPED: 0, test.Status.UNEXPECTED: 0}
|
||||
|
||||
|
||||
def get_folders_list(path):
|
||||
|
@ -33,7 +28,7 @@ def get_folders_list(path):
|
|||
|
||||
|
||||
def mutation_test_for(mutation_path):
|
||||
test_mapping_file = join(mutation_path, 'test_mapping.json')
|
||||
test_mapping_file = join(mutation_path, "test_mapping.json")
|
||||
if isfile(test_mapping_file):
|
||||
json_data = open(test_mapping_file).read()
|
||||
test_mapping = json.loads(json_data)
|
||||
|
@ -41,7 +36,7 @@ def mutation_test_for(mutation_path):
|
|||
source_files = list(test_mapping.keys())
|
||||
random.shuffle(source_files)
|
||||
for src_file in source_files:
|
||||
status = test.mutation_test(join(mutation_path, src_file.encode('utf-8')), test_mapping[src_file])
|
||||
status = test.mutation_test(join(mutation_path, src_file.encode("utf-8")), test_mapping[src_file])
|
||||
test_summary[status] += 1
|
||||
# Run mutation test in all folder in the path.
|
||||
for folder in get_folders_list(mutation_path):
|
||||
|
|
|
@ -39,7 +39,7 @@ class Strategy:
|
|||
def mutate(self, file_name):
|
||||
line_numbers = []
|
||||
for line in fileinput.input(file_name):
|
||||
if not is_comment(line) and re.search(self._replace_strategy['regex'], line):
|
||||
if not is_comment(line) and re.search(self._replace_strategy["regex"], line):
|
||||
line_numbers.append(fileinput.lineno())
|
||||
if len(line_numbers) == 0:
|
||||
return -1
|
||||
|
@ -47,7 +47,7 @@ class Strategy:
|
|||
mutation_line_number = line_numbers[random.randint(0, len(line_numbers) - 1)]
|
||||
for line in fileinput.input(file_name, inplace=True):
|
||||
if fileinput.lineno() == mutation_line_number:
|
||||
line = re.sub(self._replace_strategy['regex'], self._replace_strategy['replaceString'], line)
|
||||
line = re.sub(self._replace_strategy["regex"], self._replace_strategy["replaceString"], line)
|
||||
print(line.rstrip())
|
||||
return mutation_line_number
|
||||
|
||||
|
@ -56,30 +56,21 @@ class AndOr(Strategy):
|
|||
def __init__(self):
|
||||
Strategy.__init__(self)
|
||||
logical_and = r"(?<=\s)&&(?=\s)"
|
||||
self._replace_strategy = {
|
||||
'regex': logical_and,
|
||||
'replaceString': '||'
|
||||
}
|
||||
self._replace_strategy = {"regex": logical_and, "replaceString": "||"}
|
||||
|
||||
|
||||
class IfTrue(Strategy):
|
||||
def __init__(self):
|
||||
Strategy.__init__(self)
|
||||
if_condition = r"(?<=if\s)\s*(?!let\s)(.*)(?=\s\{)"
|
||||
self._replace_strategy = {
|
||||
'regex': if_condition,
|
||||
'replaceString': 'true'
|
||||
}
|
||||
self._replace_strategy = {"regex": if_condition, "replaceString": "true"}
|
||||
|
||||
|
||||
class IfFalse(Strategy):
|
||||
def __init__(self):
|
||||
Strategy.__init__(self)
|
||||
if_condition = r"(?<=if\s)\s*(?!let\s)(.*)(?=\s\{)"
|
||||
self._replace_strategy = {
|
||||
'regex': if_condition,
|
||||
'replaceString': 'false'
|
||||
}
|
||||
self._replace_strategy = {"regex": if_condition, "replaceString": "false"}
|
||||
|
||||
|
||||
class ModifyComparision(Strategy):
|
||||
|
@ -87,10 +78,7 @@ class ModifyComparision(Strategy):
|
|||
Strategy.__init__(self)
|
||||
less_than_equals = r"(?<=\s)(\<)\=(?=\s)"
|
||||
greater_than_equals = r"(?<=\s)(\<)\=(?=\s)"
|
||||
self._replace_strategy = {
|
||||
'regex': (less_than_equals + '|' + greater_than_equals),
|
||||
'replaceString': r"\1"
|
||||
}
|
||||
self._replace_strategy = {"regex": (less_than_equals + "|" + greater_than_equals), "replaceString": r"\1"}
|
||||
|
||||
|
||||
class MinusToPlus(Strategy):
|
||||
|
@ -98,10 +86,7 @@ class MinusToPlus(Strategy):
|
|||
Strategy.__init__(self)
|
||||
arithmetic_minus = r"(?<=\s)\-(?=\s.+)"
|
||||
minus_in_shorthand = r"(?<=\s)\-(?=\=)"
|
||||
self._replace_strategy = {
|
||||
'regex': (arithmetic_minus + '|' + minus_in_shorthand),
|
||||
'replaceString': '+'
|
||||
}
|
||||
self._replace_strategy = {"regex": (arithmetic_minus + "|" + minus_in_shorthand), "replaceString": "+"}
|
||||
|
||||
|
||||
class PlusToMinus(Strategy):
|
||||
|
@ -109,20 +94,14 @@ class PlusToMinus(Strategy):
|
|||
Strategy.__init__(self)
|
||||
arithmetic_plus = r"(?<=[^\"]\s)\+(?=\s[^A-Z\'?\":\{]+)"
|
||||
plus_in_shorthand = r"(?<=\s)\+(?=\=)"
|
||||
self._replace_strategy = {
|
||||
'regex': (arithmetic_plus + '|' + plus_in_shorthand),
|
||||
'replaceString': '-'
|
||||
}
|
||||
self._replace_strategy = {"regex": (arithmetic_plus + "|" + plus_in_shorthand), "replaceString": "-"}
|
||||
|
||||
|
||||
class AtomicString(Strategy):
|
||||
def __init__(self):
|
||||
Strategy.__init__(self)
|
||||
string_literal = r"(?<=\").+(?=\")"
|
||||
self._replace_strategy = {
|
||||
'regex': string_literal,
|
||||
'replaceString': ' '
|
||||
}
|
||||
self._replace_strategy = {"regex": string_literal, "replaceString": " "}
|
||||
|
||||
|
||||
class DuplicateLine(Strategy):
|
||||
|
@ -136,9 +115,20 @@ class DuplicateLine(Strategy):
|
|||
plus_equals_statement = r".+?\s\+\=\s.*"
|
||||
minus_equals_statement = r".+?\s\-\=\s.*"
|
||||
self._replace_strategy = {
|
||||
'regex': (append_statement + '|' + remove_statement + '|' + push_statement
|
||||
+ '|' + pop_statement + '|' + plus_equals_statement + '|' + minus_equals_statement),
|
||||
'replaceString': r"\g<0>\n\g<0>",
|
||||
"regex": (
|
||||
append_statement
|
||||
+ "|"
|
||||
+ remove_statement
|
||||
+ "|"
|
||||
+ push_statement
|
||||
+ "|"
|
||||
+ pop_statement
|
||||
+ "|"
|
||||
+ plus_equals_statement
|
||||
+ "|"
|
||||
+ minus_equals_statement
|
||||
),
|
||||
"replaceString": r"\g<0>\n\g<0>",
|
||||
}
|
||||
|
||||
|
||||
|
@ -161,14 +151,17 @@ class DeleteIfBlock(Strategy):
|
|||
while line_to_mutate <= len(code_lines):
|
||||
current_line = code_lines[line_to_mutate - 1]
|
||||
next_line = code_lines[line_to_mutate]
|
||||
if re.search(self.else_block, current_line) is not None \
|
||||
or re.search(self.else_block, next_line) is not None:
|
||||
if (
|
||||
re.search(self.else_block, current_line) is not None
|
||||
or re.search(self.else_block, next_line) is not None
|
||||
):
|
||||
if_blocks.pop(random_index)
|
||||
if len(if_blocks) == 0:
|
||||
return -1
|
||||
else:
|
||||
random_index, start_counter, end_counter, lines_to_delete, line_to_mutate = \
|
||||
init_variables(if_blocks)
|
||||
random_index, start_counter, end_counter, lines_to_delete, line_to_mutate = init_variables(
|
||||
if_blocks
|
||||
)
|
||||
continue
|
||||
lines_to_delete.append(line_to_mutate)
|
||||
for ch in current_line:
|
||||
|
@ -183,8 +176,17 @@ class DeleteIfBlock(Strategy):
|
|||
|
||||
|
||||
def get_strategies():
|
||||
return AndOr, IfTrue, IfFalse, ModifyComparision, PlusToMinus, MinusToPlus, \
|
||||
AtomicString, DuplicateLine, DeleteIfBlock
|
||||
return (
|
||||
AndOr,
|
||||
IfTrue,
|
||||
IfFalse,
|
||||
ModifyComparision,
|
||||
PlusToMinus,
|
||||
MinusToPlus,
|
||||
AtomicString,
|
||||
DuplicateLine,
|
||||
DeleteIfBlock,
|
||||
)
|
||||
|
||||
|
||||
class Mutator:
|
||||
|
|
|
@ -14,7 +14,8 @@ import logging
|
|||
|
||||
from mutator import Mutator, get_strategies
|
||||
from enum import Enum
|
||||
DEVNULL = open(os.devnull, 'wb')
|
||||
|
||||
DEVNULL = open(os.devnull, "wb")
|
||||
|
||||
logging.basicConfig(format="%(asctime)s %(levelname)s: %(message)s", level=logging.DEBUG)
|
||||
|
||||
|
@ -28,7 +29,7 @@ class Status(Enum):
|
|||
|
||||
def mutation_test(file_name, tests):
|
||||
status = Status.UNEXPECTED
|
||||
local_changes_present = subprocess.call('git diff --quiet {0}'.format(file_name), shell=True)
|
||||
local_changes_present = subprocess.call("git diff --quiet {0}".format(file_name), shell=True)
|
||||
if local_changes_present == 1:
|
||||
status = Status.SKIPPED
|
||||
logging.warning("{0} has local changes, please commit/remove changes before running the test".format(file_name))
|
||||
|
@ -46,24 +47,24 @@ def mutation_test(file_name, tests):
|
|||
if subprocess.call(test_command, shell=True, stdout=DEVNULL):
|
||||
logging.error("Compilation Failed: Unexpected error")
|
||||
logging.error("Failed: while running `{0}`".format(test_command))
|
||||
subprocess.call('git --no-pager diff {0}'.format(file_name), shell=True)
|
||||
subprocess.call("git --no-pager diff {0}".format(file_name), shell=True)
|
||||
status = Status.UNEXPECTED
|
||||
else:
|
||||
for test in tests:
|
||||
test_command = "python mach test-wpt {0} --release".format(test.encode('utf-8'))
|
||||
test_command = "python mach test-wpt {0} --release".format(test.encode("utf-8"))
|
||||
logging.info("running `{0}` test for mutant {1}:{2}".format(test, file_name, mutated_line))
|
||||
test_status = subprocess.call(test_command, shell=True, stdout=DEVNULL)
|
||||
if test_status != 0:
|
||||
logging.error("Failed: while running `{0}`".format(test_command))
|
||||
logging.error("mutated file {0} diff".format(file_name))
|
||||
subprocess.call('git --no-pager diff {0}'.format(file_name), shell=True)
|
||||
subprocess.call("git --no-pager diff {0}".format(file_name), shell=True)
|
||||
status = Status.SURVIVED
|
||||
else:
|
||||
logging.info("Success: Mutation killed by {0}".format(test.encode('utf-8')))
|
||||
logging.info("Success: Mutation killed by {0}".format(test.encode("utf-8")))
|
||||
status = Status.KILLED
|
||||
break
|
||||
logging.info("reverting mutant {0}:{1}\n".format(file_name, mutated_line))
|
||||
subprocess.call('git checkout {0}'.format(file_name), shell=True)
|
||||
subprocess.call("git checkout {0}".format(file_name), shell=True)
|
||||
break
|
||||
elif not len(strategies):
|
||||
# All strategies are tried
|
||||
|
|
|
@ -42,23 +42,25 @@ from servo.command_base import (
|
|||
from servo.util import delete, get_target_dir
|
||||
|
||||
PACKAGES = {
|
||||
'android': [
|
||||
'android/aarch64-linux-android/release/servoapp.apk',
|
||||
'android/aarch64-linux-android/release/servoview.aar',
|
||||
"android": [
|
||||
"android/aarch64-linux-android/release/servoapp.apk",
|
||||
"android/aarch64-linux-android/release/servoview.aar",
|
||||
],
|
||||
'linux': [
|
||||
'production/servo-tech-demo.tar.gz',
|
||||
"linux": [
|
||||
"production/servo-tech-demo.tar.gz",
|
||||
],
|
||||
'mac': [
|
||||
'production/servo-tech-demo.dmg',
|
||||
"mac": [
|
||||
"production/servo-tech-demo.dmg",
|
||||
],
|
||||
'windows-msvc': [
|
||||
r'production\msi\Servo.exe',
|
||||
r'production\msi\Servo.zip',
|
||||
"windows-msvc": [
|
||||
r"production\msi\Servo.exe",
|
||||
r"production\msi\Servo.zip",
|
||||
],
|
||||
'ohos': [
|
||||
('openharmony/aarch64-unknown-linux-ohos/release/entry/build/'
|
||||
'default/outputs/default/servoshell-default-signed.hap')
|
||||
"ohos": [
|
||||
(
|
||||
"openharmony/aarch64-unknown-linux-ohos/release/entry/build/"
|
||||
"default/outputs/default/servoshell-default-signed.hap"
|
||||
)
|
||||
],
|
||||
}
|
||||
|
||||
|
@ -71,8 +73,7 @@ def packages_for_platform(platform):
|
|||
|
||||
|
||||
def listfiles(directory):
|
||||
return [f for f in os.listdir(directory)
|
||||
if path.isfile(path.join(directory, f))]
|
||||
return [f for f in os.listdir(directory) if path.isfile(path.join(directory, f))]
|
||||
|
||||
|
||||
def copy_windows_dependencies(binary_path, destination):
|
||||
|
@ -101,20 +102,10 @@ def check_call_with_randomized_backoff(args: List[str], retries: int) -> int:
|
|||
|
||||
@CommandProvider
|
||||
class PackageCommands(CommandBase):
|
||||
@Command('package',
|
||||
description='Package Servo',
|
||||
category='package')
|
||||
@CommandArgument('--android',
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Package Android')
|
||||
@CommandArgument('--ohos',
|
||||
default=None,
|
||||
action='store_true',
|
||||
help='Package OpenHarmony')
|
||||
@CommandArgument('--target', '-t',
|
||||
default=None,
|
||||
help='Package for given target platform')
|
||||
@Command("package", description="Package Servo", category="package")
|
||||
@CommandArgument("--android", default=None, action="store_true", help="Package Android")
|
||||
@CommandArgument("--ohos", default=None, action="store_true", help="Package OpenHarmony")
|
||||
@CommandArgument("--target", "-t", default=None, help="Package for given target platform")
|
||||
@CommandBase.common_command_arguments(build_configuration=False, build_type=True, package_configuration=True)
|
||||
@CommandBase.allow_target_configuration
|
||||
def package(self, build_type: BuildType, flavor=None, with_asan=False):
|
||||
|
@ -146,11 +137,11 @@ class PackageCommands(CommandBase):
|
|||
if flavor is not None:
|
||||
flavor_name = flavor.title()
|
||||
|
||||
dir_to_resources = path.join(self.get_top_dir(), 'target', 'android', 'resources')
|
||||
dir_to_resources = path.join(self.get_top_dir(), "target", "android", "resources")
|
||||
if path.exists(dir_to_resources):
|
||||
delete(dir_to_resources)
|
||||
|
||||
shutil.copytree(path.join(dir_to_root, 'resources'), dir_to_resources)
|
||||
shutil.copytree(path.join(dir_to_root, "resources"), dir_to_resources)
|
||||
|
||||
variant = ":assemble" + flavor_name + arch_string + build_type_string
|
||||
apk_task_name = ":servoapp" + variant
|
||||
|
@ -167,8 +158,7 @@ class PackageCommands(CommandBase):
|
|||
# so copy the source files into the target/openharmony directory first.
|
||||
ohos_app_dir = path.join(self.get_top_dir(), "support", "openharmony")
|
||||
build_mode = build_type.directory_name()
|
||||
ohos_target_dir = path.join(
|
||||
self.get_top_dir(), "target", "openharmony", self.target.triple(), build_mode)
|
||||
ohos_target_dir = path.join(self.get_top_dir(), "target", "openharmony", self.target.triple(), build_mode)
|
||||
if path.exists(ohos_target_dir):
|
||||
print("Cleaning up from previous packaging")
|
||||
delete(ohos_target_dir)
|
||||
|
@ -186,9 +176,14 @@ class PackageCommands(CommandBase):
|
|||
if flavor is not None:
|
||||
flavor_name = flavor
|
||||
|
||||
hvigor_command = ["--no-daemon", "assembleHap",
|
||||
"-p", f"product={flavor_name}",
|
||||
"-p", f"buildMode={build_mode}"]
|
||||
hvigor_command = [
|
||||
"--no-daemon",
|
||||
"assembleHap",
|
||||
"-p",
|
||||
f"product={flavor_name}",
|
||||
"-p",
|
||||
f"buildMode={build_mode}",
|
||||
]
|
||||
# Detect if PATH already has hvigor, or else fallback to npm installation
|
||||
# provided via HVIGOR_PATH
|
||||
if "HVIGOR_PATH" not in env:
|
||||
|
@ -198,9 +193,11 @@ class PackageCommands(CommandBase):
|
|||
print(f"Found `hvigorw` with version {str(version, 'utf-8').strip()} in system PATH")
|
||||
hvigor_command[0:0] = ["hvigorw"]
|
||||
except FileNotFoundError:
|
||||
print("Unable to find `hvigor` tool. Please either modify PATH to include the"
|
||||
"path to hvigorw or set the HVIGOR_PATH environment variable to the npm"
|
||||
"installation containing `node_modules` directory with hvigor modules.")
|
||||
print(
|
||||
"Unable to find `hvigor` tool. Please either modify PATH to include the"
|
||||
"path to hvigorw or set the HVIGOR_PATH environment variable to the npm"
|
||||
"installation containing `node_modules` directory with hvigor modules."
|
||||
)
|
||||
sys.exit(1)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"hvigor exited with the following error: {e}")
|
||||
|
@ -227,21 +224,21 @@ class PackageCommands(CommandBase):
|
|||
except subprocess.CalledProcessError as e:
|
||||
print("Packaging OpenHarmony exited with return value %d" % e.returncode)
|
||||
return e.returncode
|
||||
elif 'darwin' in self.target.triple():
|
||||
elif "darwin" in self.target.triple():
|
||||
print("Creating Servo.app")
|
||||
dir_to_dmg = path.join(target_dir, 'dmg')
|
||||
dir_to_app = path.join(dir_to_dmg, 'Servo.app')
|
||||
dir_to_resources = path.join(dir_to_app, 'Contents', 'Resources')
|
||||
dir_to_dmg = path.join(target_dir, "dmg")
|
||||
dir_to_app = path.join(dir_to_dmg, "Servo.app")
|
||||
dir_to_resources = path.join(dir_to_app, "Contents", "Resources")
|
||||
if path.exists(dir_to_dmg):
|
||||
print("Cleaning up from previous packaging")
|
||||
delete(dir_to_dmg)
|
||||
|
||||
print("Copying files")
|
||||
shutil.copytree(path.join(dir_to_root, 'resources'), dir_to_resources)
|
||||
shutil.copy2(path.join(dir_to_root, 'Info.plist'), path.join(dir_to_app, 'Contents', 'Info.plist'))
|
||||
shutil.copytree(path.join(dir_to_root, "resources"), dir_to_resources)
|
||||
shutil.copy2(path.join(dir_to_root, "Info.plist"), path.join(dir_to_app, "Contents", "Info.plist"))
|
||||
|
||||
content_dir = path.join(dir_to_app, 'Contents', 'MacOS')
|
||||
lib_dir = path.join(content_dir, 'lib')
|
||||
content_dir = path.join(dir_to_app, "Contents", "MacOS")
|
||||
lib_dir = path.join(content_dir, "lib")
|
||||
os.makedirs(lib_dir)
|
||||
shutil.copy2(binary_path, content_dir)
|
||||
|
||||
|
@ -250,19 +247,19 @@ class PackageCommands(CommandBase):
|
|||
servo.gstreamer.package_gstreamer_dylibs(dmg_binary, lib_dir, self.target)
|
||||
|
||||
print("Adding version to Credits.rtf")
|
||||
version_command = [binary_path, '--version']
|
||||
p = subprocess.Popen(version_command,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
universal_newlines=True)
|
||||
version_command = [binary_path, "--version"]
|
||||
p = subprocess.Popen(
|
||||
version_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True
|
||||
)
|
||||
version, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
raise Exception("Error occurred when getting Servo version: " + stderr)
|
||||
version = "Nightly version: " + version
|
||||
|
||||
import mako.template
|
||||
template_path = path.join(dir_to_resources, 'Credits.rtf.mako')
|
||||
credits_path = path.join(dir_to_resources, 'Credits.rtf')
|
||||
|
||||
template_path = path.join(dir_to_resources, "Credits.rtf.mako")
|
||||
credits_path = path.join(dir_to_resources, "Credits.rtf")
|
||||
with open(template_path) as template_file:
|
||||
template = mako.template.Template(template_file.read())
|
||||
with open(credits_path, "w") as credits_file:
|
||||
|
@ -270,7 +267,7 @@ class PackageCommands(CommandBase):
|
|||
delete(template_path)
|
||||
|
||||
print("Creating dmg")
|
||||
os.symlink('/Applications', path.join(dir_to_dmg, 'Applications'))
|
||||
os.symlink("/Applications", path.join(dir_to_dmg, "Applications"))
|
||||
dmg_path = path.join(target_dir, "servo-tech-demo.dmg")
|
||||
|
||||
if path.exists(dmg_path):
|
||||
|
@ -282,10 +279,9 @@ class PackageCommands(CommandBase):
|
|||
# after a random wait.
|
||||
try:
|
||||
check_call_with_randomized_backoff(
|
||||
['hdiutil', 'create', '-volname', 'Servo',
|
||||
'-megabytes', '900', dmg_path,
|
||||
'-srcfolder', dir_to_dmg],
|
||||
retries=3)
|
||||
["hdiutil", "create", "-volname", "Servo", "-megabytes", "900", dmg_path, "-srcfolder", dir_to_dmg],
|
||||
retries=3,
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Packaging MacOS dmg exited with return value %d" % e.returncode)
|
||||
return e.returncode
|
||||
|
@ -294,42 +290,42 @@ class PackageCommands(CommandBase):
|
|||
delete(dir_to_dmg)
|
||||
print("Packaged Servo into " + dmg_path)
|
||||
|
||||
elif 'windows' in self.target.triple():
|
||||
dir_to_msi = path.join(target_dir, 'msi')
|
||||
elif "windows" in self.target.triple():
|
||||
dir_to_msi = path.join(target_dir, "msi")
|
||||
if path.exists(dir_to_msi):
|
||||
print("Cleaning up from previous packaging")
|
||||
delete(dir_to_msi)
|
||||
os.makedirs(dir_to_msi)
|
||||
|
||||
print("Copying files")
|
||||
dir_to_temp = path.join(dir_to_msi, 'temp')
|
||||
dir_to_resources = path.join(dir_to_temp, 'resources')
|
||||
shutil.copytree(path.join(dir_to_root, 'resources'), dir_to_resources)
|
||||
dir_to_temp = path.join(dir_to_msi, "temp")
|
||||
dir_to_resources = path.join(dir_to_temp, "resources")
|
||||
shutil.copytree(path.join(dir_to_root, "resources"), dir_to_resources)
|
||||
shutil.copy(binary_path, dir_to_temp)
|
||||
copy_windows_dependencies(target_dir, dir_to_temp)
|
||||
|
||||
# generate Servo.wxs
|
||||
import mako.template
|
||||
|
||||
template_path = path.join(dir_to_root, "support", "windows", "Servo.wxs.mako")
|
||||
template = mako.template.Template(open(template_path).read())
|
||||
wxs_path = path.join(dir_to_msi, "Installer.wxs")
|
||||
open(wxs_path, "w").write(template.render(
|
||||
exe_path=target_dir,
|
||||
dir_to_temp=dir_to_temp,
|
||||
resources_path=dir_to_resources))
|
||||
open(wxs_path, "w").write(
|
||||
template.render(exe_path=target_dir, dir_to_temp=dir_to_temp, resources_path=dir_to_resources)
|
||||
)
|
||||
|
||||
# run candle and light
|
||||
print("Creating MSI")
|
||||
try:
|
||||
with cd(dir_to_msi):
|
||||
subprocess.check_call(['candle', wxs_path])
|
||||
subprocess.check_call(["candle", wxs_path])
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("WiX candle exited with return value %d" % e.returncode)
|
||||
return e.returncode
|
||||
try:
|
||||
wxsobj_path = "{}.wixobj".format(path.splitext(wxs_path)[0])
|
||||
with cd(dir_to_msi):
|
||||
subprocess.check_call(['light', wxsobj_path])
|
||||
subprocess.check_call(["light", wxsobj_path])
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("WiX light exited with return value %d" % e.returncode)
|
||||
return e.returncode
|
||||
|
@ -338,18 +334,18 @@ class PackageCommands(CommandBase):
|
|||
|
||||
# Generate bundle with Servo installer.
|
||||
print("Creating bundle")
|
||||
shutil.copy(path.join(dir_to_root, 'support', 'windows', 'Servo.wxs'), dir_to_msi)
|
||||
bundle_wxs_path = path.join(dir_to_msi, 'Servo.wxs')
|
||||
shutil.copy(path.join(dir_to_root, "support", "windows", "Servo.wxs"), dir_to_msi)
|
||||
bundle_wxs_path = path.join(dir_to_msi, "Servo.wxs")
|
||||
try:
|
||||
with cd(dir_to_msi):
|
||||
subprocess.check_call(['candle', bundle_wxs_path, '-ext', 'WixBalExtension'])
|
||||
subprocess.check_call(["candle", bundle_wxs_path, "-ext", "WixBalExtension"])
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("WiX candle exited with return value %d" % e.returncode)
|
||||
return e.returncode
|
||||
try:
|
||||
wxsobj_path = "{}.wixobj".format(path.splitext(bundle_wxs_path)[0])
|
||||
with cd(dir_to_msi):
|
||||
subprocess.check_call(['light', wxsobj_path, '-ext', 'WixBalExtension'])
|
||||
subprocess.check_call(["light", wxsobj_path, "-ext", "WixBalExtension"])
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("WiX light exited with return value %d" % e.returncode)
|
||||
return e.returncode
|
||||
|
@ -357,51 +353,39 @@ class PackageCommands(CommandBase):
|
|||
|
||||
print("Creating ZIP")
|
||||
zip_path = path.join(dir_to_msi, "Servo.zip")
|
||||
archive_deterministically(dir_to_temp, zip_path, prepend_path='servo/')
|
||||
archive_deterministically(dir_to_temp, zip_path, prepend_path="servo/")
|
||||
print("Packaged Servo into " + zip_path)
|
||||
|
||||
print("Cleaning up")
|
||||
delete(dir_to_temp)
|
||||
delete(dir_to_installer)
|
||||
else:
|
||||
dir_to_temp = path.join(target_dir, 'packaging-temp')
|
||||
dir_to_temp = path.join(target_dir, "packaging-temp")
|
||||
if path.exists(dir_to_temp):
|
||||
# TODO(aneeshusa): lock dir_to_temp to prevent simultaneous builds
|
||||
print("Cleaning up from previous packaging")
|
||||
delete(dir_to_temp)
|
||||
|
||||
print("Copying files")
|
||||
dir_to_resources = path.join(dir_to_temp, 'resources')
|
||||
shutil.copytree(path.join(dir_to_root, 'resources'), dir_to_resources)
|
||||
dir_to_resources = path.join(dir_to_temp, "resources")
|
||||
shutil.copytree(path.join(dir_to_root, "resources"), dir_to_resources)
|
||||
shutil.copy(binary_path, dir_to_temp)
|
||||
|
||||
print("Creating tarball")
|
||||
tar_path = path.join(target_dir, 'servo-tech-demo.tar.gz')
|
||||
tar_path = path.join(target_dir, "servo-tech-demo.tar.gz")
|
||||
|
||||
archive_deterministically(dir_to_temp, tar_path, prepend_path='servo/')
|
||||
archive_deterministically(dir_to_temp, tar_path, prepend_path="servo/")
|
||||
|
||||
print("Cleaning up")
|
||||
delete(dir_to_temp)
|
||||
print("Packaged Servo into " + tar_path)
|
||||
|
||||
@Command('install',
|
||||
description='Install Servo (currently, Android and Windows only)',
|
||||
category='package')
|
||||
@CommandArgument('--android',
|
||||
action='store_true',
|
||||
help='Install on Android')
|
||||
@CommandArgument('--ohos',
|
||||
action='store_true',
|
||||
help='Install on OpenHarmony')
|
||||
@CommandArgument('--emulator',
|
||||
action='store_true',
|
||||
help='For Android, install to the only emulated device')
|
||||
@CommandArgument('--usb',
|
||||
action='store_true',
|
||||
help='For Android, install to the only USB device')
|
||||
@CommandArgument('--target', '-t',
|
||||
default=None,
|
||||
help='Install the given target platform')
|
||||
@Command("install", description="Install Servo (currently, Android and Windows only)", category="package")
|
||||
@CommandArgument("--android", action="store_true", help="Install on Android")
|
||||
@CommandArgument("--ohos", action="store_true", help="Install on OpenHarmony")
|
||||
@CommandArgument("--emulator", action="store_true", help="For Android, install to the only emulated device")
|
||||
@CommandArgument("--usb", action="store_true", help="For Android, install to the only USB device")
|
||||
@CommandArgument("--target", "-t", default=None, help="Install the given target platform")
|
||||
@CommandBase.common_command_arguments(build_configuration=False, build_type=True, package_configuration=True)
|
||||
@CommandBase.allow_target_configuration
|
||||
def install(self, build_type: BuildType, emulator=False, usb=False, with_asan=False, flavor=None):
|
||||
|
@ -410,9 +394,7 @@ class PackageCommands(CommandBase):
|
|||
binary_path = self.get_binary_path(build_type, asan=with_asan)
|
||||
except BuildNotFound:
|
||||
print("Servo build not found. Building servo...")
|
||||
result = Registrar.dispatch(
|
||||
"build", context=self.context, build_type=build_type, flavor=flavor
|
||||
)
|
||||
result = Registrar.dispatch("build", context=self.context, build_type=build_type, flavor=flavor)
|
||||
if result:
|
||||
return result
|
||||
try:
|
||||
|
@ -437,33 +419,26 @@ class PackageCommands(CommandBase):
|
|||
hdc_path = path.join(env["OHOS_SDK_NATIVE"], "../", "toolchains", "hdc")
|
||||
exec_command = [hdc_path, "install", "-r", pkg_path]
|
||||
elif is_windows():
|
||||
pkg_path = path.join(path.dirname(binary_path), 'msi', 'Servo.msi')
|
||||
pkg_path = path.join(path.dirname(binary_path), "msi", "Servo.msi")
|
||||
exec_command = ["msiexec", "/i", pkg_path]
|
||||
|
||||
if not path.exists(pkg_path):
|
||||
print("Servo package not found. Packaging servo...")
|
||||
result = Registrar.dispatch(
|
||||
"package", context=self.context, build_type=build_type, flavor=flavor
|
||||
)
|
||||
result = Registrar.dispatch("package", context=self.context, build_type=build_type, flavor=flavor)
|
||||
if result != 0:
|
||||
return result
|
||||
|
||||
print(" ".join(exec_command))
|
||||
return subprocess.call(exec_command, env=env)
|
||||
|
||||
@Command('upload-nightly',
|
||||
description='Upload Servo nightly to S3',
|
||||
category='package')
|
||||
@CommandArgument('platform',
|
||||
choices=PACKAGES.keys(),
|
||||
help='Package platform type to upload')
|
||||
@CommandArgument('--secret-from-environment',
|
||||
action='store_true',
|
||||
help='Retrieve the appropriate secrets from the environment.')
|
||||
@CommandArgument('--github-release-id',
|
||||
default=None,
|
||||
type=int,
|
||||
help='The github release to upload the nightly builds.')
|
||||
@Command("upload-nightly", description="Upload Servo nightly to S3", category="package")
|
||||
@CommandArgument("platform", choices=PACKAGES.keys(), help="Package platform type to upload")
|
||||
@CommandArgument(
|
||||
"--secret-from-environment", action="store_true", help="Retrieve the appropriate secrets from the environment."
|
||||
)
|
||||
@CommandArgument(
|
||||
"--github-release-id", default=None, type=int, help="The github release to upload the nightly builds."
|
||||
)
|
||||
def upload_nightly(self, platform, secret_from_environment, github_release_id):
|
||||
import boto3
|
||||
|
||||
|
@ -471,69 +446,62 @@ class PackageCommands(CommandBase):
|
|||
aws_access_key = None
|
||||
aws_secret_access_key = None
|
||||
if secret_from_environment:
|
||||
secret = json.loads(os.environ['S3_UPLOAD_CREDENTIALS'])
|
||||
secret = json.loads(os.environ["S3_UPLOAD_CREDENTIALS"])
|
||||
aws_access_key = secret["aws_access_key_id"]
|
||||
aws_secret_access_key = secret["aws_secret_access_key"]
|
||||
return (aws_access_key, aws_secret_access_key)
|
||||
|
||||
def nightly_filename(package, timestamp):
|
||||
return '{}-{}'.format(
|
||||
timestamp.isoformat() + 'Z', # The `Z` denotes UTC
|
||||
path.basename(package)
|
||||
return "{}-{}".format(
|
||||
timestamp.isoformat() + "Z", # The `Z` denotes UTC
|
||||
path.basename(package),
|
||||
)
|
||||
|
||||
def upload_to_github_release(platform, package, package_hash):
|
||||
if not github_release_id:
|
||||
return
|
||||
|
||||
extension = path.basename(package).partition('.')[2]
|
||||
g = Github(os.environ['NIGHTLY_REPO_TOKEN'])
|
||||
nightly_repo = g.get_repo(os.environ['NIGHTLY_REPO'])
|
||||
extension = path.basename(package).partition(".")[2]
|
||||
g = Github(os.environ["NIGHTLY_REPO_TOKEN"])
|
||||
nightly_repo = g.get_repo(os.environ["NIGHTLY_REPO"])
|
||||
release = nightly_repo.get_release(github_release_id)
|
||||
package_hash_fileobj = io.BytesIO(package_hash.encode('utf-8'))
|
||||
package_hash_fileobj = io.BytesIO(package_hash.encode("utf-8"))
|
||||
|
||||
asset_name = f'servo-latest.{extension}'
|
||||
asset_name = f"servo-latest.{extension}"
|
||||
release.upload_asset(package, name=asset_name)
|
||||
release.upload_asset_from_memory(
|
||||
package_hash_fileobj,
|
||||
package_hash_fileobj.getbuffer().nbytes,
|
||||
name=f'{asset_name}.sha256')
|
||||
package_hash_fileobj, package_hash_fileobj.getbuffer().nbytes, name=f"{asset_name}.sha256"
|
||||
)
|
||||
|
||||
def upload_to_s3(platform, package, package_hash, timestamp):
|
||||
(aws_access_key, aws_secret_access_key) = get_s3_secret()
|
||||
s3 = boto3.client(
|
||||
's3',
|
||||
aws_access_key_id=aws_access_key,
|
||||
aws_secret_access_key=aws_secret_access_key
|
||||
)
|
||||
s3 = boto3.client("s3", aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_access_key)
|
||||
|
||||
cloudfront = boto3.client(
|
||||
'cloudfront',
|
||||
aws_access_key_id=aws_access_key,
|
||||
aws_secret_access_key=aws_secret_access_key
|
||||
"cloudfront", aws_access_key_id=aws_access_key, aws_secret_access_key=aws_secret_access_key
|
||||
)
|
||||
|
||||
BUCKET = 'servo-builds2'
|
||||
DISTRIBUTION_ID = 'EJ8ZWSJKFCJS2'
|
||||
BUCKET = "servo-builds2"
|
||||
DISTRIBUTION_ID = "EJ8ZWSJKFCJS2"
|
||||
|
||||
nightly_dir = f'nightly/{platform}'
|
||||
nightly_dir = f"nightly/{platform}"
|
||||
filename = nightly_filename(package, timestamp)
|
||||
package_upload_key = '{}/{}'.format(nightly_dir, filename)
|
||||
extension = path.basename(package).partition('.')[2]
|
||||
latest_upload_key = '{}/servo-latest.{}'.format(nightly_dir, extension)
|
||||
package_upload_key = "{}/{}".format(nightly_dir, filename)
|
||||
extension = path.basename(package).partition(".")[2]
|
||||
latest_upload_key = "{}/servo-latest.{}".format(nightly_dir, extension)
|
||||
|
||||
package_hash_fileobj = io.BytesIO(package_hash.encode('utf-8'))
|
||||
latest_hash_upload_key = f'{latest_upload_key}.sha256'
|
||||
package_hash_fileobj = io.BytesIO(package_hash.encode("utf-8"))
|
||||
latest_hash_upload_key = f"{latest_upload_key}.sha256"
|
||||
|
||||
s3.upload_file(package, BUCKET, package_upload_key)
|
||||
|
||||
copy_source = {
|
||||
'Bucket': BUCKET,
|
||||
'Key': package_upload_key,
|
||||
"Bucket": BUCKET,
|
||||
"Key": package_upload_key,
|
||||
}
|
||||
s3.copy(copy_source, BUCKET, latest_upload_key)
|
||||
s3.upload_fileobj(
|
||||
package_hash_fileobj, BUCKET, latest_hash_upload_key, ExtraArgs={'ContentType': 'text/plain'}
|
||||
package_hash_fileobj, BUCKET, latest_hash_upload_key, ExtraArgs={"ContentType": "text/plain"}
|
||||
)
|
||||
|
||||
# Invalidate previous "latest" nightly files from
|
||||
|
@ -541,14 +509,9 @@ class PackageCommands(CommandBase):
|
|||
cloudfront.create_invalidation(
|
||||
DistributionId=DISTRIBUTION_ID,
|
||||
InvalidationBatch={
|
||||
'CallerReference': f'{latest_upload_key}-{timestamp}',
|
||||
'Paths': {
|
||||
'Quantity': 1,
|
||||
'Items': [
|
||||
f'/{latest_upload_key}*'
|
||||
]
|
||||
}
|
||||
}
|
||||
"CallerReference": f"{latest_upload_key}-{timestamp}",
|
||||
"Paths": {"Quantity": 1, "Items": [f"/{latest_upload_key}*"]},
|
||||
},
|
||||
)
|
||||
|
||||
timestamp = datetime.utcnow().replace(microsecond=0)
|
||||
|
@ -556,16 +519,13 @@ class PackageCommands(CommandBase):
|
|||
if path.isdir(package):
|
||||
continue
|
||||
if not path.isfile(package):
|
||||
print("Could not find package for {} at {}".format(
|
||||
platform,
|
||||
package
|
||||
), file=sys.stderr)
|
||||
print("Could not find package for {} at {}".format(platform, package), file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# Compute the hash
|
||||
SHA_BUF_SIZE = 1048576 # read in 1 MiB chunks
|
||||
sha256_digest = hashlib.sha256()
|
||||
with open(package, 'rb') as package_file:
|
||||
with open(package, "rb") as package_file:
|
||||
while True:
|
||||
data = package_file.read(SHA_BUF_SIZE)
|
||||
if not data:
|
||||
|
|
|
@ -64,11 +64,14 @@ def get():
|
|||
__platform__ = Windows(triple)
|
||||
elif "linux-gnu" in triple:
|
||||
from .linux import Linux
|
||||
|
||||
__platform__ = Linux(triple)
|
||||
elif "apple-darwin" in triple:
|
||||
from .macos import MacOS
|
||||
|
||||
__platform__ = MacOS(triple)
|
||||
else:
|
||||
from .base import Base
|
||||
|
||||
__platform__ = Base(triple)
|
||||
return __platform__
|
||||
|
|
|
@ -33,9 +33,7 @@ class Base:
|
|||
raise NotImplementedError("Bootstrap installation detection not yet available.")
|
||||
|
||||
def _platform_bootstrap_gstreamer(self, _target: BuildTarget, _force: bool) -> bool:
|
||||
raise NotImplementedError(
|
||||
"GStreamer bootstrap support is not yet available for your OS."
|
||||
)
|
||||
raise NotImplementedError("GStreamer bootstrap support is not yet available for your OS.")
|
||||
|
||||
def is_gstreamer_installed(self, target: BuildTarget) -> bool:
|
||||
gstreamer_root = self.gstreamer_root(target)
|
||||
|
@ -92,8 +90,7 @@ class Base:
|
|||
if force or not shutil.which("cargo-deny"):
|
||||
return False
|
||||
# Tidy needs at least version 0.18.1 installed.
|
||||
result = subprocess.run(["cargo-deny", "--version"],
|
||||
encoding='utf-8', capture_output=True)
|
||||
result = subprocess.run(["cargo-deny", "--version"], encoding="utf-8", capture_output=True)
|
||||
(major, minor, micro) = result.stdout.strip().split(" ")[1].split(".", 2)
|
||||
return (int(major), int(minor), int(micro)) >= (0, 18, 1)
|
||||
|
||||
|
@ -114,8 +111,8 @@ class Base:
|
|||
|
||||
def passive_bootstrap(self) -> bool:
|
||||
"""A bootstrap method that is called without explicitly invoking `./mach bootstrap`
|
||||
but that is executed in the process of other `./mach` commands. This should be
|
||||
as fast as possible."""
|
||||
but that is executed in the process of other `./mach` commands. This should be
|
||||
as fast as possible."""
|
||||
return False
|
||||
|
||||
def bootstrap_gstreamer(self, force: bool):
|
||||
|
|
|
@ -29,12 +29,12 @@ class BuildTarget(object):
|
|||
self.target_triple = target_triple
|
||||
|
||||
@staticmethod
|
||||
def from_triple(target_triple: Optional[str]) -> 'BuildTarget':
|
||||
def from_triple(target_triple: Optional[str]) -> "BuildTarget":
|
||||
host_triple = servo.platform.host_triple()
|
||||
if target_triple:
|
||||
if 'android' in target_triple:
|
||||
if "android" in target_triple:
|
||||
return AndroidTarget(target_triple)
|
||||
elif 'ohos' in target_triple:
|
||||
elif "ohos" in target_triple:
|
||||
return OpenHarmonyTarget(target_triple)
|
||||
elif target_triple != host_triple:
|
||||
raise Exception(f"Unknown build target {target_triple}")
|
||||
|
@ -129,16 +129,16 @@ class AndroidTarget(CrossBuildTarget):
|
|||
android_toolchain_name = ndk_configuration["toolchain_name"]
|
||||
android_lib = ndk_configuration["lib"]
|
||||
|
||||
android_api = android_platform.replace('android-', '')
|
||||
android_api = android_platform.replace("android-", "")
|
||||
|
||||
# Check if the NDK version is 26
|
||||
if not os.path.isfile(path.join(env["ANDROID_NDK_ROOT"], 'source.properties')):
|
||||
if not os.path.isfile(path.join(env["ANDROID_NDK_ROOT"], "source.properties")):
|
||||
print("ANDROID_NDK should have file `source.properties`.")
|
||||
print("The environment variable ANDROID_NDK_ROOT may be set at a wrong path.")
|
||||
sys.exit(1)
|
||||
with open(path.join(env["ANDROID_NDK_ROOT"], 'source.properties'), encoding="utf8") as ndk_properties:
|
||||
with open(path.join(env["ANDROID_NDK_ROOT"], "source.properties"), encoding="utf8") as ndk_properties:
|
||||
lines = ndk_properties.readlines()
|
||||
if lines[1].split(' = ')[1].split('.')[0] != '26':
|
||||
if lines[1].split(" = ")[1].split(".")[0] != "26":
|
||||
print("Servo currently only supports NDK r26c.")
|
||||
sys.exit(1)
|
||||
|
||||
|
@ -149,7 +149,7 @@ class AndroidTarget(CrossBuildTarget):
|
|||
if os_type not in ["linux", "darwin"]:
|
||||
raise Exception("Android cross builds are only supported on Linux and macOS.")
|
||||
|
||||
llvm_prebuilt = path.join(env['ANDROID_NDK_ROOT'], "toolchains", "llvm", "prebuilt")
|
||||
llvm_prebuilt = path.join(env["ANDROID_NDK_ROOT"], "toolchains", "llvm", "prebuilt")
|
||||
|
||||
cpu_type = platform.machine().lower()
|
||||
host_suffix = "unknown"
|
||||
|
@ -172,11 +172,11 @@ class AndroidTarget(CrossBuildTarget):
|
|||
raise Exception("Can't determine LLVM prebuilt directory.")
|
||||
host = os_type + "-" + host_suffix
|
||||
|
||||
host_cc = env.get('HOST_CC') or shutil.which("clang")
|
||||
host_cxx = env.get('HOST_CXX') or shutil.which("clang++")
|
||||
host_cc = env.get("HOST_CC") or shutil.which("clang")
|
||||
host_cxx = env.get("HOST_CXX") or shutil.which("clang++")
|
||||
|
||||
llvm_toolchain = path.join(llvm_prebuilt, host)
|
||||
env['PATH'] = (env['PATH'] + ':' + path.join(llvm_toolchain, "bin"))
|
||||
env["PATH"] = env["PATH"] + ":" + path.join(llvm_toolchain, "bin")
|
||||
|
||||
def to_ndk_bin(prog):
|
||||
return path.join(llvm_toolchain, "bin", prog)
|
||||
|
@ -189,26 +189,26 @@ class AndroidTarget(CrossBuildTarget):
|
|||
[to_ndk_bin(f"x86_64-linux-android{android_api}-clang"), "--print-libgcc-file-name"],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
encoding="utf8"
|
||||
encoding="utf8",
|
||||
).stdout
|
||||
env['RUSTFLAGS'] = env.get('RUSTFLAGS', "")
|
||||
env["RUSTFLAGS"] = env.get("RUSTFLAGS", "")
|
||||
env["RUSTFLAGS"] += f"-C link-arg={libclangrt_filename}"
|
||||
|
||||
env["RUST_TARGET"] = self.triple()
|
||||
env['HOST_CC'] = host_cc
|
||||
env['HOST_CXX'] = host_cxx
|
||||
env['HOST_CFLAGS'] = ''
|
||||
env['HOST_CXXFLAGS'] = ''
|
||||
env['TARGET_CC'] = to_ndk_bin("clang")
|
||||
env['TARGET_CPP'] = to_ndk_bin("clang") + " -E"
|
||||
env['TARGET_CXX'] = to_ndk_bin("clang++")
|
||||
env["HOST_CC"] = host_cc
|
||||
env["HOST_CXX"] = host_cxx
|
||||
env["HOST_CFLAGS"] = ""
|
||||
env["HOST_CXXFLAGS"] = ""
|
||||
env["TARGET_CC"] = to_ndk_bin("clang")
|
||||
env["TARGET_CPP"] = to_ndk_bin("clang") + " -E"
|
||||
env["TARGET_CXX"] = to_ndk_bin("clang++")
|
||||
|
||||
env['TARGET_AR'] = to_ndk_bin("llvm-ar")
|
||||
env['TARGET_RANLIB'] = to_ndk_bin("llvm-ranlib")
|
||||
env['TARGET_OBJCOPY'] = to_ndk_bin("llvm-objcopy")
|
||||
env['TARGET_YASM'] = to_ndk_bin("yasm")
|
||||
env['TARGET_STRIP'] = to_ndk_bin("llvm-strip")
|
||||
env['RUST_FONTCONFIG_DLOPEN'] = "on"
|
||||
env["TARGET_AR"] = to_ndk_bin("llvm-ar")
|
||||
env["TARGET_RANLIB"] = to_ndk_bin("llvm-ranlib")
|
||||
env["TARGET_OBJCOPY"] = to_ndk_bin("llvm-objcopy")
|
||||
env["TARGET_YASM"] = to_ndk_bin("yasm")
|
||||
env["TARGET_STRIP"] = to_ndk_bin("llvm-strip")
|
||||
env["RUST_FONTCONFIG_DLOPEN"] = "on"
|
||||
|
||||
env["LIBCLANG_PATH"] = path.join(llvm_toolchain, "lib")
|
||||
env["CLANG_PATH"] = to_ndk_bin("clang")
|
||||
|
@ -224,11 +224,11 @@ class AndroidTarget(CrossBuildTarget):
|
|||
#
|
||||
# Also worth remembering: autoconf uses C for its configuration,
|
||||
# even for C++ builds, so the C flags need to line up with the C++ flags.
|
||||
env['TARGET_CFLAGS'] = "--target=" + android_toolchain_name
|
||||
env['TARGET_CXXFLAGS'] = "--target=" + android_toolchain_name
|
||||
env["TARGET_CFLAGS"] = "--target=" + android_toolchain_name
|
||||
env["TARGET_CXXFLAGS"] = "--target=" + android_toolchain_name
|
||||
|
||||
# These two variables are needed for the mozjs compilation.
|
||||
env['ANDROID_API_LEVEL'] = android_api
|
||||
env["ANDROID_API_LEVEL"] = android_api
|
||||
env["ANDROID_NDK_HOME"] = env["ANDROID_NDK_ROOT"]
|
||||
|
||||
# The two variables set below are passed by our custom
|
||||
|
@ -236,15 +236,16 @@ class AndroidTarget(CrossBuildTarget):
|
|||
env["ANDROID_ABI"] = android_lib
|
||||
env["ANDROID_PLATFORM"] = android_platform
|
||||
env["NDK_CMAKE_TOOLCHAIN_FILE"] = path.join(
|
||||
env['ANDROID_NDK_ROOT'], "build", "cmake", "android.toolchain.cmake")
|
||||
env["ANDROID_NDK_ROOT"], "build", "cmake", "android.toolchain.cmake"
|
||||
)
|
||||
env["CMAKE_TOOLCHAIN_FILE"] = path.join(topdir, "support", "android", "toolchain.cmake")
|
||||
|
||||
# Set output dir for gradle aar files
|
||||
env["AAR_OUT_DIR"] = path.join(topdir, "target", "android", "aar")
|
||||
if not os.path.exists(env['AAR_OUT_DIR']):
|
||||
os.makedirs(env['AAR_OUT_DIR'])
|
||||
if not os.path.exists(env["AAR_OUT_DIR"]):
|
||||
os.makedirs(env["AAR_OUT_DIR"])
|
||||
|
||||
env['TARGET_PKG_CONFIG_SYSROOT_DIR'] = path.join(llvm_toolchain, 'sysroot')
|
||||
env["TARGET_PKG_CONFIG_SYSROOT_DIR"] = path.join(llvm_toolchain, "sysroot")
|
||||
|
||||
def binary_name(self) -> str:
|
||||
return "libservoshell.so"
|
||||
|
@ -273,8 +274,10 @@ class OpenHarmonyTarget(CrossBuildTarget):
|
|||
env["OHOS_SDK_NATIVE"] = config["ohos"]["ndk"]
|
||||
|
||||
if "OHOS_SDK_NATIVE" not in env:
|
||||
print("Please set the OHOS_SDK_NATIVE environment variable to the location of the `native` directory "
|
||||
"in the OpenHarmony SDK.")
|
||||
print(
|
||||
"Please set the OHOS_SDK_NATIVE environment variable to the location of the `native` directory "
|
||||
"in the OpenHarmony SDK."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
ndk_root = pathlib.Path(env["OHOS_SDK_NATIVE"])
|
||||
|
@ -288,9 +291,9 @@ class OpenHarmonyTarget(CrossBuildTarget):
|
|||
try:
|
||||
with open(package_info) as meta_file:
|
||||
meta = json.load(meta_file)
|
||||
ohos_api_version = int(meta['apiVersion'])
|
||||
ohos_sdk_version = parse_version(meta['version'])
|
||||
if ohos_sdk_version < parse_version('5.0') or ohos_api_version < 12:
|
||||
ohos_api_version = int(meta["apiVersion"])
|
||||
ohos_sdk_version = parse_version(meta["version"])
|
||||
if ohos_sdk_version < parse_version("5.0") or ohos_api_version < 12:
|
||||
raise RuntimeError("Building servo for OpenHarmony requires SDK version 5.0 (API-12) or newer.")
|
||||
print(f"Info: The OpenHarmony SDK {ohos_sdk_version} is targeting API-level {ohos_api_version}")
|
||||
except (OSError, json.JSONDecodeError) as e:
|
||||
|
@ -318,72 +321,79 @@ class OpenHarmonyTarget(CrossBuildTarget):
|
|||
# Instead, we ensure that all the necessary flags for the c-compiler are set
|
||||
# via environment variables such as `TARGET_CFLAGS`.
|
||||
def to_sdk_llvm_bin(prog: str):
|
||||
if sys.platform == 'win32':
|
||||
prog = prog + '.exe'
|
||||
if sys.platform == "win32":
|
||||
prog = prog + ".exe"
|
||||
llvm_prog = llvm_bin.joinpath(prog)
|
||||
if not llvm_prog.is_file():
|
||||
raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), llvm_prog)
|
||||
return llvm_bin.joinpath(prog).as_posix()
|
||||
|
||||
# CC and CXX should already be set to appropriate host compilers by `build_env()`
|
||||
env['HOST_CC'] = env['CC']
|
||||
env['HOST_CXX'] = env['CXX']
|
||||
env['TARGET_AR'] = to_sdk_llvm_bin("llvm-ar")
|
||||
env['TARGET_RANLIB'] = to_sdk_llvm_bin("llvm-ranlib")
|
||||
env['TARGET_READELF'] = to_sdk_llvm_bin("llvm-readelf")
|
||||
env['TARGET_OBJCOPY'] = to_sdk_llvm_bin("llvm-objcopy")
|
||||
env['TARGET_STRIP'] = to_sdk_llvm_bin("llvm-strip")
|
||||
env["HOST_CC"] = env["CC"]
|
||||
env["HOST_CXX"] = env["CXX"]
|
||||
env["TARGET_AR"] = to_sdk_llvm_bin("llvm-ar")
|
||||
env["TARGET_RANLIB"] = to_sdk_llvm_bin("llvm-ranlib")
|
||||
env["TARGET_READELF"] = to_sdk_llvm_bin("llvm-readelf")
|
||||
env["TARGET_OBJCOPY"] = to_sdk_llvm_bin("llvm-objcopy")
|
||||
env["TARGET_STRIP"] = to_sdk_llvm_bin("llvm-strip")
|
||||
|
||||
target_triple = self.triple()
|
||||
rust_target_triple = str(target_triple).replace('-', '_')
|
||||
rust_target_triple = str(target_triple).replace("-", "_")
|
||||
ndk_clang = to_sdk_llvm_bin("clang")
|
||||
ndk_clangxx = to_sdk_llvm_bin("clang++")
|
||||
env[f'CC_{rust_target_triple}'] = ndk_clang
|
||||
env[f'CXX_{rust_target_triple}'] = ndk_clangxx
|
||||
env[f"CC_{rust_target_triple}"] = ndk_clang
|
||||
env[f"CXX_{rust_target_triple}"] = ndk_clangxx
|
||||
# The clang target name is different from the LLVM target name
|
||||
clang_target_triple = str(target_triple).replace('-unknown-', '-')
|
||||
clang_target_triple_underscore = clang_target_triple.replace('-', '_')
|
||||
env[f'CC_{clang_target_triple_underscore}'] = ndk_clang
|
||||
env[f'CXX_{clang_target_triple_underscore}'] = ndk_clangxx
|
||||
clang_target_triple = str(target_triple).replace("-unknown-", "-")
|
||||
clang_target_triple_underscore = clang_target_triple.replace("-", "_")
|
||||
env[f"CC_{clang_target_triple_underscore}"] = ndk_clang
|
||||
env[f"CXX_{clang_target_triple_underscore}"] = ndk_clangxx
|
||||
# rustc linker
|
||||
env[f'CARGO_TARGET_{rust_target_triple.upper()}_LINKER'] = ndk_clang
|
||||
env[f"CARGO_TARGET_{rust_target_triple.upper()}_LINKER"] = ndk_clang
|
||||
# We could also use a cross-compile wrapper
|
||||
env["RUSTFLAGS"] += f' -Clink-arg=--target={clang_target_triple}'
|
||||
env["RUSTFLAGS"] += f' -Clink-arg=--sysroot={ohos_sysroot_posix}'
|
||||
env["RUSTFLAGS"] += f" -Clink-arg=--target={clang_target_triple}"
|
||||
env["RUSTFLAGS"] += f" -Clink-arg=--sysroot={ohos_sysroot_posix}"
|
||||
|
||||
env['HOST_CFLAGS'] = ''
|
||||
env['HOST_CXXFLAGS'] = ''
|
||||
ohos_cflags = ['-D__MUSL__', f' --target={clang_target_triple}', f' --sysroot={ohos_sysroot_posix}',
|
||||
"-Wno-error=unused-command-line-argument"]
|
||||
if clang_target_triple.startswith('armv7-'):
|
||||
ohos_cflags.extend(['-march=armv7-a', '-mfloat-abi=softfp', '-mtune=generic-armv7-a', '-mthumb'])
|
||||
env["HOST_CFLAGS"] = ""
|
||||
env["HOST_CXXFLAGS"] = ""
|
||||
ohos_cflags = [
|
||||
"-D__MUSL__",
|
||||
f" --target={clang_target_triple}",
|
||||
f" --sysroot={ohos_sysroot_posix}",
|
||||
"-Wno-error=unused-command-line-argument",
|
||||
]
|
||||
if clang_target_triple.startswith("armv7-"):
|
||||
ohos_cflags.extend(["-march=armv7-a", "-mfloat-abi=softfp", "-mtune=generic-armv7-a", "-mthumb"])
|
||||
ohos_cflags_str = " ".join(ohos_cflags)
|
||||
env['TARGET_CFLAGS'] = ohos_cflags_str
|
||||
env['TARGET_CPPFLAGS'] = '-D__MUSL__'
|
||||
env['TARGET_CXXFLAGS'] = ohos_cflags_str
|
||||
env["TARGET_CFLAGS"] = ohos_cflags_str
|
||||
env["TARGET_CPPFLAGS"] = "-D__MUSL__"
|
||||
env["TARGET_CXXFLAGS"] = ohos_cflags_str
|
||||
|
||||
# CMake related flags
|
||||
env['CMAKE'] = ndk_root.joinpath("build-tools", "cmake", "bin", "cmake").as_posix()
|
||||
env["CMAKE"] = ndk_root.joinpath("build-tools", "cmake", "bin", "cmake").as_posix()
|
||||
cmake_toolchain_file = ndk_root.joinpath("build", "cmake", "ohos.toolchain.cmake")
|
||||
if cmake_toolchain_file.is_file():
|
||||
env[f'CMAKE_TOOLCHAIN_FILE_{rust_target_triple}'] = cmake_toolchain_file.as_posix()
|
||||
env[f"CMAKE_TOOLCHAIN_FILE_{rust_target_triple}"] = cmake_toolchain_file.as_posix()
|
||||
else:
|
||||
print(
|
||||
f"Warning: Failed to find the OpenHarmony CMake Toolchain file - Expected it at {cmake_toolchain_file}")
|
||||
env[f'CMAKE_C_COMPILER_{rust_target_triple}'] = ndk_clang
|
||||
env[f'CMAKE_CXX_COMPILER_{rust_target_triple}'] = ndk_clangxx
|
||||
f"Warning: Failed to find the OpenHarmony CMake Toolchain file - Expected it at {cmake_toolchain_file}"
|
||||
)
|
||||
env[f"CMAKE_C_COMPILER_{rust_target_triple}"] = ndk_clang
|
||||
env[f"CMAKE_CXX_COMPILER_{rust_target_triple}"] = ndk_clangxx
|
||||
|
||||
# pkg-config
|
||||
pkg_config_path = '{}:{}'.format(ohos_sysroot.joinpath("usr", "lib", "pkgconfig").as_posix(),
|
||||
ohos_sysroot.joinpath("usr", "share", "pkgconfig").as_posix())
|
||||
env[f'PKG_CONFIG_SYSROOT_DIR_{rust_target_triple}'] = ohos_sysroot_posix
|
||||
env[f'PKG_CONFIG_PATH_{rust_target_triple}'] = pkg_config_path
|
||||
pkg_config_path = "{}:{}".format(
|
||||
ohos_sysroot.joinpath("usr", "lib", "pkgconfig").as_posix(),
|
||||
ohos_sysroot.joinpath("usr", "share", "pkgconfig").as_posix(),
|
||||
)
|
||||
env[f"PKG_CONFIG_SYSROOT_DIR_{rust_target_triple}"] = ohos_sysroot_posix
|
||||
env[f"PKG_CONFIG_PATH_{rust_target_triple}"] = pkg_config_path
|
||||
|
||||
# bindgen / libclang-sys
|
||||
env["LIBCLANG_PATH"] = path.join(llvm_toolchain, "lib")
|
||||
env["CLANG_PATH"] = ndk_clangxx
|
||||
env[f'CXXSTDLIB_{clang_target_triple_underscore}'] = "c++"
|
||||
bindgen_extra_clangs_args_var = f'BINDGEN_EXTRA_CLANG_ARGS_{rust_target_triple}'
|
||||
env[f"CXXSTDLIB_{clang_target_triple_underscore}"] = "c++"
|
||||
bindgen_extra_clangs_args_var = f"BINDGEN_EXTRA_CLANG_ARGS_{rust_target_triple}"
|
||||
bindgen_extra_clangs_args = env.get(bindgen_extra_clangs_args_var, "")
|
||||
bindgen_extra_clangs_args = bindgen_extra_clangs_args + " " + ohos_cflags_str
|
||||
env[bindgen_extra_clangs_args_var] = bindgen_extra_clangs_args
|
||||
|
@ -404,8 +414,5 @@ class OpenHarmonyTarget(CrossBuildTarget):
|
|||
return path.join(base_path, build_type_directory, build_output_path, hap_name)
|
||||
|
||||
def abi_string(self) -> str:
|
||||
abi_map = {
|
||||
"aarch64-unknown-linux-ohos": "arm64-v8a",
|
||||
"x86_64-unknown-linux-ohos": "x86_64"
|
||||
}
|
||||
abi_map = {"aarch64-unknown-linux-ohos": "arm64-v8a", "x86_64-unknown-linux-ohos": "x86_64"}
|
||||
return abi_map[self.triple()]
|
||||
|
|
|
@ -26,22 +26,48 @@ from .build_target import BuildTarget
|
|||
# 3. copy(`sudo apt install ${APT_PKGS.join(" ")}`)
|
||||
# 4. paste into https://github.com/servo/book/edit/main/src/hacking/setting-up-your-environment.md
|
||||
APT_PKGS = [
|
||||
'build-essential', 'ccache', 'clang', 'cmake', 'curl', 'g++', 'git',
|
||||
'gperf', 'libdbus-1-dev', 'libfreetype6-dev', 'libgl1-mesa-dri',
|
||||
'libgles2-mesa-dev', 'libglib2.0-dev',
|
||||
'gstreamer1.0-plugins-good', 'libgstreamer-plugins-good1.0-dev',
|
||||
'gstreamer1.0-plugins-bad', 'libgstreamer-plugins-bad1.0-dev',
|
||||
'gstreamer1.0-plugins-ugly',
|
||||
"gstreamer1.0-plugins-base", 'libgstreamer-plugins-base1.0-dev',
|
||||
'gstreamer1.0-libav',
|
||||
'libgstrtspserver-1.0-dev',
|
||||
'gstreamer1.0-tools',
|
||||
'libges-1.0-dev',
|
||||
'libharfbuzz-dev', 'liblzma-dev', 'libudev-dev', 'libunwind-dev',
|
||||
'libvulkan1', 'libx11-dev', 'libxcb-render0-dev', 'libxcb-shape0-dev',
|
||||
'libxcb-xfixes0-dev', 'libxmu-dev', 'libxmu6', 'libegl1-mesa-dev',
|
||||
'llvm-dev', 'm4', 'xorg-dev', 'libxkbcommon0', "libxkbcommon-x11-0",
|
||||
'tshark',
|
||||
"build-essential",
|
||||
"ccache",
|
||||
"clang",
|
||||
"cmake",
|
||||
"curl",
|
||||
"g++",
|
||||
"git",
|
||||
"gperf",
|
||||
"libdbus-1-dev",
|
||||
"libfreetype6-dev",
|
||||
"libgl1-mesa-dri",
|
||||
"libgles2-mesa-dev",
|
||||
"libglib2.0-dev",
|
||||
"gstreamer1.0-plugins-good",
|
||||
"libgstreamer-plugins-good1.0-dev",
|
||||
"gstreamer1.0-plugins-bad",
|
||||
"libgstreamer-plugins-bad1.0-dev",
|
||||
"gstreamer1.0-plugins-ugly",
|
||||
"gstreamer1.0-plugins-base",
|
||||
"libgstreamer-plugins-base1.0-dev",
|
||||
"gstreamer1.0-libav",
|
||||
"libgstrtspserver-1.0-dev",
|
||||
"gstreamer1.0-tools",
|
||||
"libges-1.0-dev",
|
||||
"libharfbuzz-dev",
|
||||
"liblzma-dev",
|
||||
"libudev-dev",
|
||||
"libunwind-dev",
|
||||
"libvulkan1",
|
||||
"libx11-dev",
|
||||
"libxcb-render0-dev",
|
||||
"libxcb-shape0-dev",
|
||||
"libxcb-xfixes0-dev",
|
||||
"libxmu-dev",
|
||||
"libxmu6",
|
||||
"libegl1-mesa-dev",
|
||||
"llvm-dev",
|
||||
"m4",
|
||||
"xorg-dev",
|
||||
"libxkbcommon0",
|
||||
"libxkbcommon-x11-0",
|
||||
"tshark",
|
||||
]
|
||||
|
||||
# https://packages.fedoraproject.org
|
||||
|
@ -49,37 +75,92 @@ APT_PKGS = [
|
|||
# 2. paste in the whole DNF_PKGS = [...]
|
||||
# 3. copy(`sudo dnf install ${DNF_PKGS.join(" ")}`)
|
||||
# 4. paste into https://github.com/servo/book/edit/main/src/hacking/setting-up-your-environment.md
|
||||
DNF_PKGS = ['libtool', 'gcc-c++', 'libXi-devel', 'freetype-devel',
|
||||
'libunwind-devel', 'mesa-libGL-devel', 'mesa-libEGL-devel',
|
||||
'glib2-devel', 'libX11-devel', 'libXrandr-devel', 'gperf',
|
||||
'fontconfig-devel', 'cabextract', 'ttmkfdir', 'expat-devel',
|
||||
'rpm-build', 'cmake', 'libXcursor-devel', 'libXmu-devel',
|
||||
'dbus-devel', 'ncurses-devel', 'harfbuzz-devel', 'ccache',
|
||||
'clang', 'clang-libs', 'llvm', 'python3-devel',
|
||||
'gstreamer1-devel', 'gstreamer1-plugins-base-devel',
|
||||
'gstreamer1-plugins-good', 'gstreamer1-plugins-bad-free-devel',
|
||||
'gstreamer1-plugins-ugly-free', 'libjpeg-turbo-devel',
|
||||
'zlib-ng', 'libjpeg-turbo', 'vulkan-loader', 'libxkbcommon',
|
||||
'libxkbcommon-x11', 'wireshark-cli']
|
||||
DNF_PKGS = [
|
||||
"libtool",
|
||||
"gcc-c++",
|
||||
"libXi-devel",
|
||||
"freetype-devel",
|
||||
"libunwind-devel",
|
||||
"mesa-libGL-devel",
|
||||
"mesa-libEGL-devel",
|
||||
"glib2-devel",
|
||||
"libX11-devel",
|
||||
"libXrandr-devel",
|
||||
"gperf",
|
||||
"fontconfig-devel",
|
||||
"cabextract",
|
||||
"ttmkfdir",
|
||||
"expat-devel",
|
||||
"rpm-build",
|
||||
"cmake",
|
||||
"libXcursor-devel",
|
||||
"libXmu-devel",
|
||||
"dbus-devel",
|
||||
"ncurses-devel",
|
||||
"harfbuzz-devel",
|
||||
"ccache",
|
||||
"clang",
|
||||
"clang-libs",
|
||||
"llvm",
|
||||
"python3-devel",
|
||||
"gstreamer1-devel",
|
||||
"gstreamer1-plugins-base-devel",
|
||||
"gstreamer1-plugins-good",
|
||||
"gstreamer1-plugins-bad-free-devel",
|
||||
"gstreamer1-plugins-ugly-free",
|
||||
"libjpeg-turbo-devel",
|
||||
"zlib-ng",
|
||||
"libjpeg-turbo",
|
||||
"vulkan-loader",
|
||||
"libxkbcommon",
|
||||
"libxkbcommon-x11",
|
||||
"wireshark-cli",
|
||||
]
|
||||
|
||||
# https://voidlinux.org/packages/
|
||||
# 1. open devtools
|
||||
# 2. paste in the whole XBPS_PKGS = [...]
|
||||
# 3. copy(`sudo xbps-install ${XBPS_PKGS.join(" ")}`)
|
||||
# 4. paste into https://github.com/servo/book/edit/main/src/hacking/setting-up-your-environment.md
|
||||
XBPS_PKGS = ['libtool', 'gcc', 'libXi-devel', 'freetype-devel',
|
||||
'libunwind-devel', 'MesaLib-devel', 'glib-devel', 'pkg-config',
|
||||
'libX11-devel', 'libXrandr-devel', 'gperf', 'bzip2-devel',
|
||||
'fontconfig-devel', 'cabextract', 'expat-devel', 'cmake',
|
||||
'cmake', 'libXcursor-devel', 'libXmu-devel', 'dbus-devel',
|
||||
'ncurses-devel', 'harfbuzz-devel', 'ccache', 'glu-devel',
|
||||
'clang', 'gstreamer1-devel', 'gst-plugins-base1-devel',
|
||||
'gst-plugins-good1', 'gst-plugins-bad1-devel',
|
||||
'gst-plugins-ugly1', 'vulkan-loader', 'libxkbcommon',
|
||||
'libxkbcommon-x11']
|
||||
XBPS_PKGS = [
|
||||
"libtool",
|
||||
"gcc",
|
||||
"libXi-devel",
|
||||
"freetype-devel",
|
||||
"libunwind-devel",
|
||||
"MesaLib-devel",
|
||||
"glib-devel",
|
||||
"pkg-config",
|
||||
"libX11-devel",
|
||||
"libXrandr-devel",
|
||||
"gperf",
|
||||
"bzip2-devel",
|
||||
"fontconfig-devel",
|
||||
"cabextract",
|
||||
"expat-devel",
|
||||
"cmake",
|
||||
"cmake",
|
||||
"libXcursor-devel",
|
||||
"libXmu-devel",
|
||||
"dbus-devel",
|
||||
"ncurses-devel",
|
||||
"harfbuzz-devel",
|
||||
"ccache",
|
||||
"glu-devel",
|
||||
"clang",
|
||||
"gstreamer1-devel",
|
||||
"gst-plugins-base1-devel",
|
||||
"gst-plugins-good1",
|
||||
"gst-plugins-bad1-devel",
|
||||
"gst-plugins-ugly1",
|
||||
"vulkan-loader",
|
||||
"libxkbcommon",
|
||||
"libxkbcommon-x11",
|
||||
]
|
||||
|
||||
GSTREAMER_URL = \
|
||||
GSTREAMER_URL = (
|
||||
"https://github.com/servo/servo-build-deps/releases/download/linux/gstreamer-1.16-x86_64-linux-gnu.20190515.tar.gz"
|
||||
)
|
||||
|
||||
|
||||
class Linux(Base):
|
||||
|
@ -93,67 +174,68 @@ class Linux(Base):
|
|||
distrib = distro.name()
|
||||
version = distro.version()
|
||||
|
||||
if distrib in ['LinuxMint', 'Linux Mint', 'KDE neon', 'Pop!_OS', 'TUXEDO OS']:
|
||||
if '.' in version:
|
||||
major, _ = version.split('.', 1)
|
||||
if distrib in ["LinuxMint", "Linux Mint", "KDE neon", "Pop!_OS", "TUXEDO OS"]:
|
||||
if "." in version:
|
||||
major, _ = version.split(".", 1)
|
||||
else:
|
||||
major = version
|
||||
|
||||
distrib = 'Ubuntu'
|
||||
if major == '22':
|
||||
version = '22.04'
|
||||
elif major == '21':
|
||||
version = '21.04'
|
||||
elif major == '20':
|
||||
version = '20.04'
|
||||
elif major == '19':
|
||||
version = '18.04'
|
||||
elif major == '18':
|
||||
version = '16.04'
|
||||
distrib = "Ubuntu"
|
||||
if major == "22":
|
||||
version = "22.04"
|
||||
elif major == "21":
|
||||
version = "21.04"
|
||||
elif major == "20":
|
||||
version = "20.04"
|
||||
elif major == "19":
|
||||
version = "18.04"
|
||||
elif major == "18":
|
||||
version = "16.04"
|
||||
|
||||
if distrib.lower() == 'elementary':
|
||||
distrib = 'Ubuntu'
|
||||
if version == '5.0':
|
||||
version = '18.04'
|
||||
elif version[0:3] == '0.4':
|
||||
version = '16.04'
|
||||
if distrib.lower() == "elementary":
|
||||
distrib = "Ubuntu"
|
||||
if version == "5.0":
|
||||
version = "18.04"
|
||||
elif version[0:3] == "0.4":
|
||||
version = "16.04"
|
||||
|
||||
return (distrib, version)
|
||||
|
||||
def _platform_bootstrap(self, force: bool) -> bool:
|
||||
if self.distro.lower() == 'nixos':
|
||||
print('NixOS does not need bootstrap, it will automatically enter a nix-shell')
|
||||
print('Just run ./mach build')
|
||||
print('')
|
||||
print('You will need to run a nix-shell if you are trying '
|
||||
'to run any of the built binaries')
|
||||
print('To enter the nix-shell manually use:')
|
||||
print(' $ nix-shell')
|
||||
if self.distro.lower() == "nixos":
|
||||
print("NixOS does not need bootstrap, it will automatically enter a nix-shell")
|
||||
print("Just run ./mach build")
|
||||
print("")
|
||||
print("You will need to run a nix-shell if you are trying to run any of the built binaries")
|
||||
print("To enter the nix-shell manually use:")
|
||||
print(" $ nix-shell")
|
||||
return False
|
||||
|
||||
if self.distro.lower() == 'ubuntu' and self.version > '22.04':
|
||||
if self.distro.lower() == "ubuntu" and self.version > "22.04":
|
||||
print(f"WARNING: unsupported version of {self.distro}: {self.version}")
|
||||
|
||||
# FIXME: Better version checking for these distributions.
|
||||
if self.distro.lower() not in [
|
||||
'arch linux',
|
||||
'arch',
|
||||
'artix',
|
||||
'endeavouros',
|
||||
'centos linux',
|
||||
'centos',
|
||||
'debian gnu/linux',
|
||||
'raspbian gnu/linux',
|
||||
'fedora linux',
|
||||
'fedora',
|
||||
'nixos',
|
||||
'ubuntu',
|
||||
'void',
|
||||
'fedora linux asahi remix'
|
||||
"arch linux",
|
||||
"arch",
|
||||
"artix",
|
||||
"endeavouros",
|
||||
"centos linux",
|
||||
"centos",
|
||||
"debian gnu/linux",
|
||||
"raspbian gnu/linux",
|
||||
"fedora linux",
|
||||
"fedora",
|
||||
"nixos",
|
||||
"ubuntu",
|
||||
"void",
|
||||
"fedora linux asahi remix",
|
||||
]:
|
||||
print(f"mach bootstrap does not support {self.distro}."
|
||||
" You may be able to install dependencies manually."
|
||||
" See https://github.com/servo/servo/wiki/Building.")
|
||||
print(
|
||||
f"mach bootstrap does not support {self.distro}."
|
||||
" You may be able to install dependencies manually."
|
||||
" See https://github.com/servo/servo/wiki/Building."
|
||||
)
|
||||
input("Press Enter to continue...")
|
||||
return False
|
||||
|
||||
|
@ -163,41 +245,39 @@ class Linux(Base):
|
|||
def install_non_gstreamer_dependencies(self, force: bool) -> bool:
|
||||
install = False
|
||||
pkgs = []
|
||||
if self.distro in ['Ubuntu', 'Debian GNU/Linux', 'Raspbian GNU/Linux']:
|
||||
command = ['apt-get', 'install', "-m"]
|
||||
if self.distro in ["Ubuntu", "Debian GNU/Linux", "Raspbian GNU/Linux"]:
|
||||
command = ["apt-get", "install", "-m"]
|
||||
pkgs = APT_PKGS
|
||||
|
||||
# Skip 'clang' if 'clang' binary already exists.
|
||||
result = subprocess.run(['which', 'clang'], capture_output=True)
|
||||
result = subprocess.run(["which", "clang"], capture_output=True)
|
||||
if result and result.returncode == 0:
|
||||
pkgs.remove('clang')
|
||||
pkgs.remove("clang")
|
||||
|
||||
# Try to filter out unknown packages from the list. This is important for Debian
|
||||
# as it does not ship all of the packages we want.
|
||||
installable = subprocess.check_output(['apt-cache', '--generate', 'pkgnames'])
|
||||
installable = subprocess.check_output(["apt-cache", "--generate", "pkgnames"])
|
||||
if installable:
|
||||
installable = installable.decode("ascii").splitlines()
|
||||
pkgs = list(filter(lambda pkg: pkg in installable, pkgs))
|
||||
|
||||
if subprocess.call(['dpkg', '-s'] + pkgs, shell=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE) != 0:
|
||||
if subprocess.call(["dpkg", "-s"] + pkgs, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) != 0:
|
||||
install = True
|
||||
elif self.distro in ['CentOS', 'CentOS Linux', 'Fedora', 'Fedora Linux', 'Fedora Linux Asahi Remix']:
|
||||
command = ['dnf', 'install']
|
||||
installed_pkgs: [str] = (
|
||||
subprocess.check_output(['rpm', '--query', '--all', '--queryformat', '%{NAME}\n'],
|
||||
encoding='utf-8')
|
||||
.split('\n'))
|
||||
elif self.distro in ["CentOS", "CentOS Linux", "Fedora", "Fedora Linux", "Fedora Linux Asahi Remix"]:
|
||||
command = ["dnf", "install"]
|
||||
installed_pkgs: [str] = subprocess.check_output(
|
||||
["rpm", "--query", "--all", "--queryformat", "%{NAME}\n"], encoding="utf-8"
|
||||
).split("\n")
|
||||
pkgs = DNF_PKGS
|
||||
for pkg in pkgs:
|
||||
if pkg not in installed_pkgs:
|
||||
install = True
|
||||
break
|
||||
elif self.distro == 'void':
|
||||
installed_pkgs = str(subprocess.check_output(['xbps-query', '-l']))
|
||||
elif self.distro == "void":
|
||||
installed_pkgs = str(subprocess.check_output(["xbps-query", "-l"]))
|
||||
pkgs = XBPS_PKGS
|
||||
for pkg in pkgs:
|
||||
command = ['xbps-install', '-A']
|
||||
command = ["xbps-install", "-A"]
|
||||
if "ii {}-".format(pkg) not in installed_pkgs:
|
||||
install = force = True
|
||||
break
|
||||
|
@ -207,22 +287,24 @@ class Linux(Base):
|
|||
|
||||
def check_sudo():
|
||||
if os.geteuid() != 0:
|
||||
if shutil.which('sudo') is None:
|
||||
if shutil.which("sudo") is None:
|
||||
return False
|
||||
return True
|
||||
|
||||
def run_as_root(command, force=False):
|
||||
if os.geteuid() != 0:
|
||||
command.insert(0, 'sudo')
|
||||
command.insert(0, "sudo")
|
||||
if force:
|
||||
command.append('-y')
|
||||
command.append("-y")
|
||||
return subprocess.call(command)
|
||||
|
||||
print("Installing missing dependencies...")
|
||||
if not check_sudo():
|
||||
print("'sudo' command not found."
|
||||
" You may be able to install dependencies manually."
|
||||
" See https://github.com/servo/servo/wiki/Building.")
|
||||
print(
|
||||
"'sudo' command not found."
|
||||
" You may be able to install dependencies manually."
|
||||
" See https://github.com/servo/servo/wiki/Building."
|
||||
)
|
||||
input("Press Enter to continue...")
|
||||
return False
|
||||
|
||||
|
@ -236,4 +318,5 @@ class Linux(Base):
|
|||
def _platform_bootstrap_gstreamer(self, _target: BuildTarget, _force: bool) -> bool:
|
||||
raise EnvironmentError(
|
||||
"Bootstrapping GStreamer on Linux is not supported. "
|
||||
+ "Please install it using your distribution package manager.")
|
||||
+ "Please install it using your distribution package manager."
|
||||
)
|
||||
|
|
|
@ -42,9 +42,7 @@ class MacOS(Base):
|
|||
installed_something = False
|
||||
try:
|
||||
brewfile = os.path.join(util.SERVO_ROOT, "support", "macos", "Brewfile")
|
||||
output = subprocess.check_output(
|
||||
['brew', 'bundle', 'install', "--file", brewfile]
|
||||
).decode("utf-8")
|
||||
output = subprocess.check_output(["brew", "bundle", "install", "--file", brewfile]).decode("utf-8")
|
||||
print(output)
|
||||
installed_something = "Installing" in output
|
||||
except subprocess.CalledProcessError as e:
|
||||
|
@ -60,14 +58,10 @@ class MacOS(Base):
|
|||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
libs_pkg = os.path.join(temp_dir, GSTREAMER_URL.rsplit("/", maxsplit=1)[-1])
|
||||
devel_pkg = os.path.join(
|
||||
temp_dir, GSTREAMER_DEVEL_URL.rsplit("/", maxsplit=1)[-1]
|
||||
)
|
||||
devel_pkg = os.path.join(temp_dir, GSTREAMER_DEVEL_URL.rsplit("/", maxsplit=1)[-1])
|
||||
|
||||
util.download_file("GStreamer libraries", GSTREAMER_URL, libs_pkg)
|
||||
util.download_file(
|
||||
"GStreamer development support", GSTREAMER_DEVEL_URL, devel_pkg
|
||||
)
|
||||
util.download_file("GStreamer development support", GSTREAMER_DEVEL_URL, devel_pkg)
|
||||
|
||||
print("Installing GStreamer packages...")
|
||||
subprocess.check_call(
|
||||
|
@ -75,8 +69,7 @@ class MacOS(Base):
|
|||
"sudo",
|
||||
"sh",
|
||||
"-c",
|
||||
f"installer -pkg '{libs_pkg}' -target / &&"
|
||||
f"installer -pkg '{devel_pkg}' -target /",
|
||||
f"installer -pkg '{libs_pkg}' -target / &&installer -pkg '{devel_pkg}' -target /",
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
@ -71,10 +71,18 @@ class Windows(Base):
|
|||
cmd_exe_args += ",'-f'"
|
||||
|
||||
print(cmd_exe_args)
|
||||
subprocess.check_output([
|
||||
"powershell", "Start-Process", "-Wait", "-verb", "runAs",
|
||||
"cmd.exe", "-ArgumentList", f"@({cmd_exe_args})"
|
||||
]).decode("utf-8")
|
||||
subprocess.check_output(
|
||||
[
|
||||
"powershell",
|
||||
"Start-Process",
|
||||
"-Wait",
|
||||
"-verb",
|
||||
"runAs",
|
||||
"cmd.exe",
|
||||
"-ArgumentList",
|
||||
f"@({cmd_exe_args})",
|
||||
]
|
||||
).decode("utf-8")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Could not run chocolatey. Follow manual build setup instructions.")
|
||||
raise e
|
||||
|
@ -85,10 +93,9 @@ class Windows(Base):
|
|||
|
||||
def passive_bootstrap(self) -> bool:
|
||||
"""A bootstrap method that is called without explicitly invoking `./mach bootstrap`
|
||||
but that is executed in the process of other `./mach` commands. This should be
|
||||
as fast as possible."""
|
||||
to_install = [package for package in DEPENDENCIES if
|
||||
not os.path.isdir(get_dependency_dir(package))]
|
||||
but that is executed in the process of other `./mach` commands. This should be
|
||||
as fast as possible."""
|
||||
to_install = [package for package in DEPENDENCIES if not os.path.isdir(get_dependency_dir(package))]
|
||||
if not to_install:
|
||||
return False
|
||||
|
||||
|
@ -116,9 +123,7 @@ class Windows(Base):
|
|||
gst_arch_name = gst_arch_names[build_target_triple.split("-")[0]]
|
||||
|
||||
# The bootstraped version of GStreamer always takes precedance of the installed vesion.
|
||||
prepackaged_root = os.path.join(
|
||||
DEPENDENCIES_DIR, "gstreamer", "1.0", f"msvc_{gst_arch_name}"
|
||||
)
|
||||
prepackaged_root = os.path.join(DEPENDENCIES_DIR, "gstreamer", "1.0", f"msvc_{gst_arch_name}")
|
||||
if os.path.exists(os.path.join(prepackaged_root, "bin", "ffi-7.dll")):
|
||||
return prepackaged_root
|
||||
|
||||
|
@ -143,20 +148,15 @@ class Windows(Base):
|
|||
return False
|
||||
|
||||
if "x86_64" not in self.triple:
|
||||
print("Bootstrapping gstreamer not supported on "
|
||||
"non-x86-64 Windows. Please install manually")
|
||||
print("Bootstrapping gstreamer not supported on non-x86-64 Windows. Please install manually")
|
||||
return False
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
libs_msi = os.path.join(temp_dir, GSTREAMER_URL.rsplit("/", maxsplit=1)[-1])
|
||||
devel_msi = os.path.join(
|
||||
temp_dir, GSTREAMER_DEVEL_URL.rsplit("/", maxsplit=1)[-1]
|
||||
)
|
||||
devel_msi = os.path.join(temp_dir, GSTREAMER_DEVEL_URL.rsplit("/", maxsplit=1)[-1])
|
||||
|
||||
util.download_file("GStreamer libraries", GSTREAMER_URL, libs_msi)
|
||||
util.download_file(
|
||||
"GStreamer development support", GSTREAMER_DEVEL_URL, devel_msi
|
||||
)
|
||||
util.download_file("GStreamer development support", GSTREAMER_DEVEL_URL, devel_msi)
|
||||
|
||||
print(f"Installing GStreamer packages to {DEPENDENCIES_DIR}...")
|
||||
os.makedirs(DEPENDENCIES_DIR, exist_ok=True)
|
||||
|
@ -164,15 +164,24 @@ class Windows(Base):
|
|||
for installer in [libs_msi, devel_msi]:
|
||||
arguments = [
|
||||
"/a",
|
||||
f'"{installer}"'
|
||||
f'TARGETDIR="{DEPENDENCIES_DIR}"', # Install destination
|
||||
f'"{installer}"TARGETDIR="{DEPENDENCIES_DIR}"', # Install destination
|
||||
"/qn", # Quiet mode
|
||||
]
|
||||
quoted_arguments = ",".join((f"'{arg}'" for arg in arguments))
|
||||
subprocess.check_call([
|
||||
"powershell", "exit (Start-Process", "-PassThru", "-Wait", "-verb", "runAs",
|
||||
"msiexec.exe", "-ArgumentList", f"@({quoted_arguments})", ").ExitCode"
|
||||
])
|
||||
subprocess.check_call(
|
||||
[
|
||||
"powershell",
|
||||
"exit (Start-Process",
|
||||
"-PassThru",
|
||||
"-Wait",
|
||||
"-verb",
|
||||
"runAs",
|
||||
"msiexec.exe",
|
||||
"-ArgumentList",
|
||||
f"@({quoted_arguments})",
|
||||
").ExitCode",
|
||||
]
|
||||
)
|
||||
|
||||
assert self.is_gstreamer_installed(target)
|
||||
return True
|
||||
|
|
|
@ -51,39 +51,50 @@ def shell_quote(arg):
|
|||
|
||||
@CommandProvider
|
||||
class PostBuildCommands(CommandBase):
|
||||
@Command('run',
|
||||
description='Run Servo',
|
||||
category='post-build')
|
||||
@CommandArgument('--android', action='store_true', default=None,
|
||||
help='Run on an Android device through `adb shell`')
|
||||
@CommandArgument('--emulator',
|
||||
action='store_true',
|
||||
help='For Android, run in the only emulated device')
|
||||
@CommandArgument('--usb',
|
||||
action='store_true',
|
||||
help='For Android, run in the only USB device')
|
||||
@CommandArgument('--debugger', action='store_true',
|
||||
help='Enable the debugger. Not specifying a '
|
||||
'--debugger-cmd option will result in the default '
|
||||
'debugger being used. The following arguments '
|
||||
'have no effect without this.')
|
||||
@CommandArgument('--debugger-cmd', default=None, type=str,
|
||||
help='Name of debugger to use.')
|
||||
@CommandArgument('--headless', '-z', action='store_true',
|
||||
help='Launch in headless mode')
|
||||
@CommandArgument('--software', '-s', action='store_true',
|
||||
help='Launch with software rendering')
|
||||
@Command("run", description="Run Servo", category="post-build")
|
||||
@CommandArgument(
|
||||
'params', nargs='...',
|
||||
help="Command-line arguments to be passed through to Servo")
|
||||
"--android", action="store_true", default=None, help="Run on an Android device through `adb shell`"
|
||||
)
|
||||
@CommandArgument("--emulator", action="store_true", help="For Android, run in the only emulated device")
|
||||
@CommandArgument("--usb", action="store_true", help="For Android, run in the only USB device")
|
||||
@CommandArgument(
|
||||
"--debugger",
|
||||
action="store_true",
|
||||
help="Enable the debugger. Not specifying a "
|
||||
"--debugger-cmd option will result in the default "
|
||||
"debugger being used. The following arguments "
|
||||
"have no effect without this.",
|
||||
)
|
||||
@CommandArgument("--debugger-cmd", default=None, type=str, help="Name of debugger to use.")
|
||||
@CommandArgument("--headless", "-z", action="store_true", help="Launch in headless mode")
|
||||
@CommandArgument("--software", "-s", action="store_true", help="Launch with software rendering")
|
||||
@CommandArgument("params", nargs="...", help="Command-line arguments to be passed through to Servo")
|
||||
@CommandBase.common_command_arguments(binary_selection=True)
|
||||
@CommandBase.allow_target_configuration
|
||||
def run(self, servo_binary: str, params, debugger=False, debugger_cmd=None,
|
||||
headless=False, software=False, emulator=False, usb=False):
|
||||
def run(
|
||||
self,
|
||||
servo_binary: str,
|
||||
params,
|
||||
debugger=False,
|
||||
debugger_cmd=None,
|
||||
headless=False,
|
||||
software=False,
|
||||
emulator=False,
|
||||
usb=False,
|
||||
):
|
||||
return self._run(servo_binary, params, debugger, debugger_cmd, headless, software, emulator, usb)
|
||||
|
||||
def _run(self, servo_binary: str, params, debugger=False, debugger_cmd=None,
|
||||
headless=False, software=False, emulator=False, usb=False):
|
||||
def _run(
|
||||
self,
|
||||
servo_binary: str,
|
||||
params,
|
||||
debugger=False,
|
||||
debugger_cmd=None,
|
||||
headless=False,
|
||||
software=False,
|
||||
emulator=False,
|
||||
usb=False,
|
||||
):
|
||||
env = self.build_env()
|
||||
env["RUST_BACKTRACE"] = "1"
|
||||
if software:
|
||||
|
@ -91,7 +102,7 @@ class PostBuildCommands(CommandBase):
|
|||
print("Software rendering is only supported on Linux at the moment.")
|
||||
return
|
||||
|
||||
env['LIBGL_ALWAYS_SOFTWARE'] = "1"
|
||||
env["LIBGL_ALWAYS_SOFTWARE"] = "1"
|
||||
os.environ.update(env)
|
||||
|
||||
# Make --debugger-cmd imply --debugger
|
||||
|
@ -119,7 +130,7 @@ class PostBuildCommands(CommandBase):
|
|||
"sleep 0.5",
|
||||
f"echo Servo PID: $(pidof {ANDROID_APP_NAME})",
|
||||
f"logcat --pid=$(pidof {ANDROID_APP_NAME})",
|
||||
"exit"
|
||||
"exit",
|
||||
]
|
||||
args = [self.android_adb_path(env)]
|
||||
if emulator and usb:
|
||||
|
@ -136,7 +147,7 @@ class PostBuildCommands(CommandBase):
|
|||
args = [servo_binary]
|
||||
|
||||
if headless:
|
||||
args.append('-z')
|
||||
args.append("-z")
|
||||
|
||||
# Borrowed and modified from:
|
||||
# http://hg.mozilla.org/mozilla-central/file/c9cfa9b91dea/python/mozbuild/mozbuild/mach_commands.py#l883
|
||||
|
@ -144,8 +155,7 @@ class PostBuildCommands(CommandBase):
|
|||
if not debugger_cmd:
|
||||
# No debugger name was provided. Look for the default ones on
|
||||
# current OS.
|
||||
debugger_cmd = mozdebug.get_default_debugger_name(
|
||||
mozdebug.DebuggerSearch.KeepLooking)
|
||||
debugger_cmd = mozdebug.get_default_debugger_name(mozdebug.DebuggerSearch.KeepLooking)
|
||||
|
||||
debugger_info = mozdebug.get_debugger_info(debugger_cmd)
|
||||
if not debugger_info:
|
||||
|
@ -153,17 +163,17 @@ class PostBuildCommands(CommandBase):
|
|||
return 1
|
||||
|
||||
command = debugger_info.path
|
||||
if debugger_cmd == 'gdb' or debugger_cmd == 'lldb':
|
||||
rust_command = 'rust-' + debugger_cmd
|
||||
if debugger_cmd == "gdb" or debugger_cmd == "lldb":
|
||||
rust_command = "rust-" + debugger_cmd
|
||||
try:
|
||||
subprocess.check_call([rust_command, '--version'], env=env, stdout=open(os.devnull, 'w'))
|
||||
subprocess.check_call([rust_command, "--version"], env=env, stdout=open(os.devnull, "w"))
|
||||
except (OSError, subprocess.CalledProcessError):
|
||||
pass
|
||||
else:
|
||||
command = rust_command
|
||||
|
||||
# Prepend the debugger args.
|
||||
args = ([command] + debugger_info.args + args + params)
|
||||
args = [command] + debugger_info.args + args + params
|
||||
else:
|
||||
args = args + params
|
||||
|
||||
|
@ -177,36 +187,27 @@ class PostBuildCommands(CommandBase):
|
|||
return exception.returncode
|
||||
except OSError as exception:
|
||||
if exception.errno == 2:
|
||||
print("Servo Binary can't be found! Run './mach build'"
|
||||
" and try again!")
|
||||
print("Servo Binary can't be found! Run './mach build' and try again!")
|
||||
else:
|
||||
raise exception
|
||||
|
||||
@Command('android-emulator',
|
||||
description='Run the Android emulator',
|
||||
category='post-build')
|
||||
@CommandArgument(
|
||||
'args', nargs='...',
|
||||
help="Command-line arguments to be passed through to the emulator")
|
||||
@Command("android-emulator", description="Run the Android emulator", category="post-build")
|
||||
@CommandArgument("args", nargs="...", help="Command-line arguments to be passed through to the emulator")
|
||||
def android_emulator(self, args=None):
|
||||
if not args:
|
||||
print("AVDs created by `./mach bootstrap-android` are servo-arm and servo-x86.")
|
||||
emulator = self.android_emulator_path(self.build_env())
|
||||
return subprocess.call([emulator] + args)
|
||||
|
||||
@Command('rr-record',
|
||||
description='Run Servo whilst recording execution with rr',
|
||||
category='post-build')
|
||||
@CommandArgument(
|
||||
'params', nargs='...',
|
||||
help="Command-line arguments to be passed through to Servo")
|
||||
@Command("rr-record", description="Run Servo whilst recording execution with rr", category="post-build")
|
||||
@CommandArgument("params", nargs="...", help="Command-line arguments to be passed through to Servo")
|
||||
@CommandBase.common_command_arguments(binary_selection=True)
|
||||
def rr_record(self, servo_binary: str, params=[]):
|
||||
env = self.build_env()
|
||||
env["RUST_BACKTRACE"] = "1"
|
||||
|
||||
servo_cmd = [servo_binary] + params
|
||||
rr_cmd = ['rr', '--fatal-errors', 'record']
|
||||
rr_cmd = ["rr", "--fatal-errors", "record"]
|
||||
try:
|
||||
check_call(rr_cmd + servo_cmd)
|
||||
except OSError as e:
|
||||
|
@ -215,24 +216,22 @@ class PostBuildCommands(CommandBase):
|
|||
else:
|
||||
raise e
|
||||
|
||||
@Command('rr-replay',
|
||||
description='Replay the most recent execution of Servo that was recorded with rr',
|
||||
category='post-build')
|
||||
@Command(
|
||||
"rr-replay",
|
||||
description="Replay the most recent execution of Servo that was recorded with rr",
|
||||
category="post-build",
|
||||
)
|
||||
def rr_replay(self):
|
||||
try:
|
||||
check_call(['rr', '--fatal-errors', 'replay'])
|
||||
check_call(["rr", "--fatal-errors", "replay"])
|
||||
except OSError as e:
|
||||
if e.errno == 2:
|
||||
print("rr binary can't be found!")
|
||||
else:
|
||||
raise e
|
||||
|
||||
@Command('doc',
|
||||
description='Generate documentation',
|
||||
category='post-build')
|
||||
@CommandArgument(
|
||||
'params', nargs='...',
|
||||
help="Command-line arguments to be passed through to cargo doc")
|
||||
@Command("doc", description="Generate documentation", category="post-build")
|
||||
@CommandArgument("params", nargs="...", help="Command-line arguments to be passed through to cargo doc")
|
||||
@CommandBase.common_command_arguments(build_configuration=True, build_type=False)
|
||||
def doc(self, params: List[str], **kwargs):
|
||||
self.ensure_bootstrapped()
|
||||
|
|
|
@ -46,10 +46,14 @@ SERVO_TESTS_PATH = os.path.join("tests", "wpt", "mozilla", "tests")
|
|||
# Servo depends on several `rustfmt` options that are unstable. These are still
|
||||
# supported by stable `rustfmt` if they are passed as these command-line arguments.
|
||||
UNSTABLE_RUSTFMT_ARGUMENTS = [
|
||||
"--config", "unstable_features=true",
|
||||
"--config", "binop_separator=Back",
|
||||
"--config", "imports_granularity=Module",
|
||||
"--config", "group_imports=StdExternalCrate",
|
||||
"--config",
|
||||
"unstable_features=true",
|
||||
"--config",
|
||||
"binop_separator=Back",
|
||||
"--config",
|
||||
"imports_granularity=Module",
|
||||
"--config",
|
||||
"group_imports=StdExternalCrate",
|
||||
]
|
||||
|
||||
# Listing these globs manually is a work-around for very slow `taplo` invocation
|
||||
|
@ -72,9 +76,21 @@ def format_toml_files_with_taplo(check_only: bool = True) -> int:
|
|||
return 1
|
||||
|
||||
if check_only:
|
||||
return call([taplo, "fmt", "--check", *TOML_GLOBS], env={'RUST_LOG': 'error'})
|
||||
return call([taplo, "fmt", "--check", *TOML_GLOBS], env={"RUST_LOG": "error"})
|
||||
else:
|
||||
return call([taplo, "fmt", *TOML_GLOBS], env={'RUST_LOG': 'error'})
|
||||
return call([taplo, "fmt", *TOML_GLOBS], env={"RUST_LOG": "error"})
|
||||
|
||||
|
||||
def format_python_files_with_ruff(check_only: bool = True) -> int:
|
||||
ruff = shutil.which("ruff")
|
||||
if ruff is None:
|
||||
print("Could not find `ruff`. Run `./mach bootstrap`")
|
||||
return 1
|
||||
|
||||
if check_only:
|
||||
return call([ruff, "format", "--check", "--quiet"])
|
||||
else:
|
||||
return call([ruff, "format", "--quiet"])
|
||||
|
||||
|
||||
def format_with_rustfmt(check_only: bool = True) -> int:
|
||||
|
@ -83,8 +99,17 @@ def format_with_rustfmt(check_only: bool = True) -> int:
|
|||
if result != 0:
|
||||
return result
|
||||
|
||||
return call(["cargo", "fmt", "--manifest-path", "support/crown/Cargo.toml",
|
||||
"--", *UNSTABLE_RUSTFMT_ARGUMENTS, *maybe_check_only])
|
||||
return call(
|
||||
[
|
||||
"cargo",
|
||||
"fmt",
|
||||
"--manifest-path",
|
||||
"support/crown/Cargo.toml",
|
||||
"--",
|
||||
*UNSTABLE_RUSTFMT_ARGUMENTS,
|
||||
*maybe_check_only,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@CommandProvider
|
||||
|
@ -97,15 +122,10 @@ class MachCommands(CommandBase):
|
|||
if not hasattr(self.context, "built_tests"):
|
||||
self.context.built_tests = False
|
||||
|
||||
@Command('test-perf',
|
||||
description='Run the page load performance test',
|
||||
category='testing')
|
||||
@CommandArgument('--base', default=None,
|
||||
help="the base URL for testcases")
|
||||
@CommandArgument('--date', default=None,
|
||||
help="the datestamp for the data")
|
||||
@CommandArgument('--submit', '-a', default=False, action="store_true",
|
||||
help="submit the data to perfherder")
|
||||
@Command("test-perf", description="Run the page load performance test", category="testing")
|
||||
@CommandArgument("--base", default=None, help="the base URL for testcases")
|
||||
@CommandArgument("--date", default=None, help="the datestamp for the data")
|
||||
@CommandArgument("--submit", "-a", default=False, action="store_true", help="submit the data to perfherder")
|
||||
def test_perf(self, base=None, date=None, submit=False):
|
||||
env = self.build_env()
|
||||
cmd = ["bash", "test_perf.sh"]
|
||||
|
@ -115,20 +135,15 @@ class MachCommands(CommandBase):
|
|||
cmd += ["--date", date]
|
||||
if submit:
|
||||
cmd += ["--submit"]
|
||||
return call(cmd,
|
||||
env=env,
|
||||
cwd=path.join("etc", "ci", "performance"))
|
||||
return call(cmd, env=env, cwd=path.join("etc", "ci", "performance"))
|
||||
|
||||
@Command('test-unit',
|
||||
description='Run unit tests',
|
||||
category='testing')
|
||||
@CommandArgument('test_name', nargs=argparse.REMAINDER,
|
||||
help="Only run tests that match this pattern or file path")
|
||||
@CommandArgument('--package', '-p', default=None, help="Specific package to test")
|
||||
@CommandArgument('--bench', default=False, action="store_true",
|
||||
help="Run in bench mode")
|
||||
@CommandArgument('--nocapture', default=False, action="store_true",
|
||||
help="Run tests with nocapture ( show test stdout )")
|
||||
@Command("test-unit", description="Run unit tests", category="testing")
|
||||
@CommandArgument("test_name", nargs=argparse.REMAINDER, help="Only run tests that match this pattern or file path")
|
||||
@CommandArgument("--package", "-p", default=None, help="Specific package to test")
|
||||
@CommandArgument("--bench", default=False, action="store_true", help="Run in bench mode")
|
||||
@CommandArgument(
|
||||
"--nocapture", default=False, action="store_true", help="Run tests with nocapture ( show test stdout )"
|
||||
)
|
||||
@CommandBase.common_command_arguments(build_configuration=True, build_type=True)
|
||||
def test_unit(self, build_type: BuildType, test_name=None, package=None, bench=False, nocapture=False, **kwargs):
|
||||
if test_name is None:
|
||||
|
@ -183,7 +198,7 @@ class MachCommands(CommandBase):
|
|||
"stylo_config",
|
||||
]
|
||||
if not packages:
|
||||
packages = set(os.listdir(path.join(self.context.topdir, "tests", "unit"))) - set(['.DS_Store'])
|
||||
packages = set(os.listdir(path.join(self.context.topdir, "tests", "unit"))) - set([".DS_Store"])
|
||||
packages |= set(self_contained_tests)
|
||||
|
||||
in_crate_packages = []
|
||||
|
@ -194,7 +209,7 @@ class MachCommands(CommandBase):
|
|||
except KeyError:
|
||||
pass
|
||||
|
||||
packages.discard('stylo')
|
||||
packages.discard("stylo")
|
||||
|
||||
# Return if there is nothing to do.
|
||||
if len(packages) == 0 and len(in_crate_packages) == 0:
|
||||
|
@ -223,59 +238,56 @@ class MachCommands(CommandBase):
|
|||
result = call(["cargo", "bench" if bench else "test"], cwd="support/crown")
|
||||
if result != 0:
|
||||
return result
|
||||
return self.run_cargo_build_like_command(
|
||||
"bench" if bench else "test",
|
||||
args,
|
||||
env=env,
|
||||
**kwargs)
|
||||
return self.run_cargo_build_like_command("bench" if bench else "test", args, env=env, **kwargs)
|
||||
|
||||
@Command('test-content',
|
||||
description='Run the content tests',
|
||||
category='testing')
|
||||
@Command("test-content", description="Run the content tests", category="testing")
|
||||
def test_content(self):
|
||||
print("Content tests have been replaced by web-platform-tests under "
|
||||
"tests/wpt/mozilla/.")
|
||||
print("Content tests have been replaced by web-platform-tests under tests/wpt/mozilla/.")
|
||||
return 0
|
||||
|
||||
@Command('test-tidy',
|
||||
description='Run the source code tidiness check',
|
||||
category='testing')
|
||||
@CommandArgument('--all', default=False, action="store_true", dest="all_files",
|
||||
help="Check all files, and run the WPT lint in tidy, "
|
||||
"even if unchanged")
|
||||
@CommandArgument('--no-progress', default=False, action="store_true",
|
||||
help="Don't show progress for tidy")
|
||||
@Command("test-tidy", description="Run the source code tidiness check", category="testing")
|
||||
@CommandArgument(
|
||||
"--all",
|
||||
default=False,
|
||||
action="store_true",
|
||||
dest="all_files",
|
||||
help="Check all files, and run the WPT lint in tidy, even if unchanged",
|
||||
)
|
||||
@CommandArgument("--no-progress", default=False, action="store_true", help="Don't show progress for tidy")
|
||||
def test_tidy(self, all_files, no_progress):
|
||||
tidy_failed = tidy.scan(not all_files, not no_progress)
|
||||
|
||||
print("\r ➤ Checking formatting of Rust files...")
|
||||
rustfmt_failed = format_with_rustfmt(check_only=True)
|
||||
if rustfmt_failed:
|
||||
print("Run `./mach fmt` to fix the formatting")
|
||||
|
||||
print("\r ➤ Checking formatting of python files...")
|
||||
ruff_format_failed = format_python_files_with_ruff()
|
||||
|
||||
print("\r ➤ Checking formatting of toml files...")
|
||||
taplo_failed = format_toml_files_with_taplo()
|
||||
|
||||
tidy_failed = tidy_failed or rustfmt_failed or taplo_failed
|
||||
format_failed = rustfmt_failed or ruff_format_failed or taplo_failed
|
||||
tidy_failed = format_failed or tidy_failed
|
||||
print()
|
||||
if tidy_failed:
|
||||
print("\r ❌ test-tidy reported errors.")
|
||||
else:
|
||||
print("\r ✅ test-tidy reported no errors.")
|
||||
|
||||
if format_failed:
|
||||
print("Run `./mach fmt` to fix the formatting")
|
||||
|
||||
return tidy_failed
|
||||
|
||||
@Command('test-scripts',
|
||||
description='Run tests for all build and support scripts.',
|
||||
category='testing')
|
||||
@CommandArgument('--verbose', '-v', default=False, action="store_true",
|
||||
help="Enable verbose output")
|
||||
@CommandArgument('--very-verbose', '-vv', default=False, action="store_true",
|
||||
help="Enable very verbose output")
|
||||
@CommandArgument('--all', '-a', default=False, action="store_true",
|
||||
help="Run all script tests, even the slow ones.")
|
||||
@CommandArgument('tests', default=None, nargs="...",
|
||||
help="Specific WebIDL tests to run, relative to the tests directory")
|
||||
@Command("test-scripts", description="Run tests for all build and support scripts.", category="testing")
|
||||
@CommandArgument("--verbose", "-v", default=False, action="store_true", help="Enable verbose output")
|
||||
@CommandArgument("--very-verbose", "-vv", default=False, action="store_true", help="Enable very verbose output")
|
||||
@CommandArgument(
|
||||
"--all", "-a", default=False, action="store_true", help="Run all script tests, even the slow ones."
|
||||
)
|
||||
@CommandArgument(
|
||||
"tests", default=None, nargs="...", help="Specific WebIDL tests to run, relative to the tests directory"
|
||||
)
|
||||
def test_scripts(self, verbose, very_verbose, all, tests):
|
||||
if very_verbose:
|
||||
logging.getLogger().level = logging.DEBUG
|
||||
|
@ -290,6 +302,7 @@ class MachCommands(CommandBase):
|
|||
passed = tidy.run_tests() and passed
|
||||
|
||||
import python.servo.try_parser as try_parser
|
||||
|
||||
print("Running try_parser tests...")
|
||||
passed = try_parser.run_tests() and passed
|
||||
|
||||
|
@ -302,7 +315,9 @@ class MachCommands(CommandBase):
|
|||
try:
|
||||
result = subprocess.run(
|
||||
["etc/devtools_parser.py", "--json", "--use", "etc/devtools_parser_test.pcap"],
|
||||
check=True, capture_output=True)
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
expected = open("etc/devtools_parser_test.json", "rb").read()
|
||||
actual = result.stdout
|
||||
assert actual == expected, f"Incorrect output!\nExpected: {repr(expected)}\nActual: {repr(actual)}"
|
||||
|
@ -323,41 +338,42 @@ class MachCommands(CommandBase):
|
|||
sys.path.insert(0, test_file_dir)
|
||||
run_file = path.abspath(path.join(test_file_dir, "runtests.py"))
|
||||
run_globals = {"__file__": run_file}
|
||||
exec(compile(open(run_file).read(), run_file, 'exec'), run_globals)
|
||||
exec(compile(open(run_file).read(), run_file, "exec"), run_globals)
|
||||
passed = run_globals["run_tests"](tests, verbose or very_verbose) and passed
|
||||
|
||||
return 0 if passed else 1
|
||||
|
||||
@Command('test-devtools',
|
||||
description='Run tests for devtools.',
|
||||
category='testing')
|
||||
@Command("test-devtools", description="Run tests for devtools.", category="testing")
|
||||
def test_devtools(self):
|
||||
print("Running devtools tests...")
|
||||
passed = servo.devtools_tests.run_tests(SCRIPT_PATH)
|
||||
return 0 if passed else 1
|
||||
|
||||
@Command('test-wpt-failure',
|
||||
description='Run the tests harness that verifies that the test failures are reported correctly',
|
||||
category='testing',
|
||||
parser=wpt.create_parser)
|
||||
@Command(
|
||||
"test-wpt-failure",
|
||||
description="Run the tests harness that verifies that the test failures are reported correctly",
|
||||
category="testing",
|
||||
parser=wpt.create_parser,
|
||||
)
|
||||
@CommandBase.common_command_arguments(build_configuration=False, build_type=True)
|
||||
def test_wpt_failure(self, build_type: BuildType, **kwargs):
|
||||
kwargs["pause_after_test"] = False
|
||||
kwargs["include"] = ["infrastructure/failing-test.html"]
|
||||
return not self._test_wpt(build_type=build_type, **kwargs)
|
||||
|
||||
@Command('test-wpt',
|
||||
description='Run the regular web platform test suite',
|
||||
category='testing',
|
||||
parser=wpt.create_parser)
|
||||
@Command(
|
||||
"test-wpt", description="Run the regular web platform test suite", category="testing", parser=wpt.create_parser
|
||||
)
|
||||
@CommandBase.common_command_arguments(binary_selection=True)
|
||||
def test_wpt(self, servo_binary: str, **kwargs):
|
||||
return self._test_wpt(servo_binary, **kwargs)
|
||||
|
||||
@Command('test-wpt-android',
|
||||
description='Run the web platform test suite in an Android emulator',
|
||||
category='testing',
|
||||
parser=wpt.create_parser)
|
||||
@Command(
|
||||
"test-wpt-android",
|
||||
description="Run the web platform test suite in an Android emulator",
|
||||
category="testing",
|
||||
parser=wpt.create_parser,
|
||||
)
|
||||
@CommandBase.common_command_arguments(build_configuration=False, build_type=True)
|
||||
def test_wpt_android(self, build_type: BuildType, binary_args=None, **kwargs):
|
||||
kwargs.update(
|
||||
|
@ -374,27 +390,30 @@ class MachCommands(CommandBase):
|
|||
return_value = wpt.run.run_tests(servo_binary, **kwargs)
|
||||
return return_value if not kwargs["always_succeed"] else 0
|
||||
|
||||
@Command('update-manifest',
|
||||
description='Run test-wpt --manifest-update SKIP_TESTS to regenerate MANIFEST.json',
|
||||
category='testing',
|
||||
parser=wpt.manifestupdate.create_parser)
|
||||
@Command(
|
||||
"update-manifest",
|
||||
description="Run test-wpt --manifest-update SKIP_TESTS to regenerate MANIFEST.json",
|
||||
category="testing",
|
||||
parser=wpt.manifestupdate.create_parser,
|
||||
)
|
||||
def update_manifest(self, **kwargs):
|
||||
return wpt.manifestupdate.update(check_clean=False)
|
||||
|
||||
@Command('fmt',
|
||||
description='Format Rust and TOML files',
|
||||
category='testing')
|
||||
@Command("fmt", description="Format Rust, Python, and TOML files", category="testing")
|
||||
def format_code(self):
|
||||
result = format_python_files_with_ruff(check_only=False)
|
||||
if result != 0:
|
||||
return result
|
||||
|
||||
result = format_toml_files_with_taplo(check_only=False)
|
||||
if result != 0:
|
||||
return result
|
||||
|
||||
return format_with_rustfmt(check_only=False)
|
||||
|
||||
@Command('update-wpt',
|
||||
description='Update the web platform tests',
|
||||
category='testing',
|
||||
parser=wpt.update.create_parser)
|
||||
@Command(
|
||||
"update-wpt", description="Update the web platform tests", category="testing", parser=wpt.update.create_parser
|
||||
)
|
||||
def update_wpt(self, **kwargs):
|
||||
patch = kwargs.get("patch", False)
|
||||
if not patch and kwargs["sync"]:
|
||||
|
@ -402,9 +421,7 @@ class MachCommands(CommandBase):
|
|||
return 1
|
||||
return wpt.update.update_tests(**kwargs)
|
||||
|
||||
@Command('test-android-startup',
|
||||
description='Extremely minimal testing of Servo for Android',
|
||||
category='testing')
|
||||
@Command("test-android-startup", description="Extremely minimal testing of Servo for Android", category="testing")
|
||||
@CommandBase.common_command_arguments(build_configuration=False, build_type=True)
|
||||
def test_android_startup(self, build_type: BuildType):
|
||||
html = """
|
||||
|
@ -441,50 +458,49 @@ class MachCommands(CommandBase):
|
|||
py = path.join(self.context.topdir, "etc", "run_in_headless_android_emulator.py")
|
||||
return [py, avd, apk]
|
||||
|
||||
@Command('test-jquery', description='Run the jQuery test suite', category='testing')
|
||||
@Command("test-jquery", description="Run the jQuery test suite", category="testing")
|
||||
@CommandBase.common_command_arguments(binary_selection=True)
|
||||
def test_jquery(self, servo_binary: str):
|
||||
return self.jquery_test_runner("test", servo_binary)
|
||||
|
||||
@Command('test-dromaeo', description='Run the Dromaeo test suite', category='testing')
|
||||
@CommandArgument('tests', default=["recommended"], nargs="...", help="Specific tests to run")
|
||||
@CommandArgument('--bmf-output', default=None, help="Specify BMF JSON output file")
|
||||
@Command("test-dromaeo", description="Run the Dromaeo test suite", category="testing")
|
||||
@CommandArgument("tests", default=["recommended"], nargs="...", help="Specific tests to run")
|
||||
@CommandArgument("--bmf-output", default=None, help="Specify BMF JSON output file")
|
||||
@CommandBase.common_command_arguments(binary_selection=True)
|
||||
def test_dromaeo(self, tests, servo_binary: str, bmf_output: str | None = None):
|
||||
return self.dromaeo_test_runner(tests, servo_binary, bmf_output)
|
||||
|
||||
@Command('test-speedometer', description="Run servo's speedometer", category='testing')
|
||||
@CommandArgument('--bmf-output', default=None, help="Specify BMF JSON output file")
|
||||
@Command("test-speedometer", description="Run servo's speedometer", category="testing")
|
||||
@CommandArgument("--bmf-output", default=None, help="Specify BMF JSON output file")
|
||||
@CommandBase.common_command_arguments(binary_selection=True)
|
||||
def test_speedometer(self, servo_binary: str, bmf_output: str | None = None):
|
||||
return self.speedometer_runner(servo_binary, bmf_output)
|
||||
|
||||
@Command('update-jquery',
|
||||
description='Update the jQuery test suite expected results',
|
||||
category='testing')
|
||||
@Command("update-jquery", description="Update the jQuery test suite expected results", category="testing")
|
||||
@CommandBase.common_command_arguments(binary_selection=True)
|
||||
def update_jquery(self, servo_binary: str):
|
||||
return self.jquery_test_runner("update", servo_binary)
|
||||
|
||||
@Command('compare_dromaeo',
|
||||
description='Compare outputs of two runs of ./mach test-dromaeo command',
|
||||
category='testing')
|
||||
@CommandArgument('params', default=None, nargs="...",
|
||||
help=" filepaths of output files of two runs of dromaeo test ")
|
||||
@Command(
|
||||
"compare_dromaeo", description="Compare outputs of two runs of ./mach test-dromaeo command", category="testing"
|
||||
)
|
||||
@CommandArgument(
|
||||
"params", default=None, nargs="...", help=" filepaths of output files of two runs of dromaeo test "
|
||||
)
|
||||
def compare_dromaeo(self, params):
|
||||
prev_op_filename = params[0]
|
||||
cur_op_filename = params[1]
|
||||
result = {'Test': [], 'Prev_Time': [], 'Cur_Time': [], 'Difference(%)': []}
|
||||
with open(prev_op_filename, 'r') as prev_op, open(cur_op_filename, 'r') as cur_op:
|
||||
result = {"Test": [], "Prev_Time": [], "Cur_Time": [], "Difference(%)": []}
|
||||
with open(prev_op_filename, "r") as prev_op, open(cur_op_filename, "r") as cur_op:
|
||||
l1 = prev_op.readline()
|
||||
l2 = cur_op.readline()
|
||||
|
||||
while ((l1.find('[dromaeo] Saving...') and l2.find('[dromaeo] Saving...'))):
|
||||
while l1.find("[dromaeo] Saving...") and l2.find("[dromaeo] Saving..."):
|
||||
l1 = prev_op.readline()
|
||||
l2 = cur_op.readline()
|
||||
|
||||
reach = 3
|
||||
while (reach > 0):
|
||||
while reach > 0:
|
||||
l1 = prev_op.readline()
|
||||
l2 = cur_op.readline()
|
||||
reach -= 1
|
||||
|
@ -494,33 +510,62 @@ class MachCommands(CommandBase):
|
|||
l2 = cur_op.readline()
|
||||
if not l1:
|
||||
break
|
||||
result['Test'].append(str(l1).split('|')[0].strip())
|
||||
result['Prev_Time'].append(float(str(l1).split('|')[1].strip()))
|
||||
result['Cur_Time'].append(float(str(l2).split('|')[1].strip()))
|
||||
a = float(str(l1).split('|')[1].strip())
|
||||
b = float(str(l2).split('|')[1].strip())
|
||||
result['Difference(%)'].append(((b - a) / a) * 100)
|
||||
result["Test"].append(str(l1).split("|")[0].strip())
|
||||
result["Prev_Time"].append(float(str(l1).split("|")[1].strip()))
|
||||
result["Cur_Time"].append(float(str(l2).split("|")[1].strip()))
|
||||
a = float(str(l1).split("|")[1].strip())
|
||||
b = float(str(l2).split("|")[1].strip())
|
||||
result["Difference(%)"].append(((b - a) / a) * 100)
|
||||
|
||||
width_col1 = max([len(x) for x in result['Test']])
|
||||
width_col2 = max([len(str(x)) for x in result['Prev_Time']])
|
||||
width_col3 = max([len(str(x)) for x in result['Cur_Time']])
|
||||
width_col4 = max([len(str(x)) for x in result['Difference(%)']])
|
||||
width_col1 = max([len(x) for x in result["Test"]])
|
||||
width_col2 = max([len(str(x)) for x in result["Prev_Time"]])
|
||||
width_col3 = max([len(str(x)) for x in result["Cur_Time"]])
|
||||
width_col4 = max([len(str(x)) for x in result["Difference(%)"]])
|
||||
|
||||
for p, q, r, s in zip(['Test'], ['First Run'], ['Second Run'], ['Difference(%)']):
|
||||
print("\033[1m" + "{}|{}|{}|{}".format(p.ljust(width_col1), q.ljust(width_col2), r.ljust(width_col3),
|
||||
s.ljust(width_col4)) + "\033[0m" + "\n" + "--------------------------------------------------"
|
||||
+ "-------------------------------------------------------------------------")
|
||||
for p, q, r, s in zip(["Test"], ["First Run"], ["Second Run"], ["Difference(%)"]):
|
||||
print(
|
||||
"\033[1m"
|
||||
+ "{}|{}|{}|{}".format(
|
||||
p.ljust(width_col1), q.ljust(width_col2), r.ljust(width_col3), s.ljust(width_col4)
|
||||
)
|
||||
+ "\033[0m"
|
||||
+ "\n"
|
||||
+ "--------------------------------------------------"
|
||||
+ "-------------------------------------------------------------------------"
|
||||
)
|
||||
|
||||
for a1, b1, c1, d1 in zip(result['Test'], result['Prev_Time'], result['Cur_Time'], result['Difference(%)']):
|
||||
for a1, b1, c1, d1 in zip(result["Test"], result["Prev_Time"], result["Cur_Time"], result["Difference(%)"]):
|
||||
if d1 > 0:
|
||||
print("\033[91m" + "{}|{}|{}|{}".format(a1.ljust(width_col1),
|
||||
str(b1).ljust(width_col2), str(c1).ljust(width_col3), str(d1).ljust(width_col4)) + "\033[0m")
|
||||
print(
|
||||
"\033[91m"
|
||||
+ "{}|{}|{}|{}".format(
|
||||
a1.ljust(width_col1),
|
||||
str(b1).ljust(width_col2),
|
||||
str(c1).ljust(width_col3),
|
||||
str(d1).ljust(width_col4),
|
||||
)
|
||||
+ "\033[0m"
|
||||
)
|
||||
elif d1 < 0:
|
||||
print("\033[92m" + "{}|{}|{}|{}".format(a1.ljust(width_col1),
|
||||
str(b1).ljust(width_col2), str(c1).ljust(width_col3), str(d1).ljust(width_col4)) + "\033[0m")
|
||||
print(
|
||||
"\033[92m"
|
||||
+ "{}|{}|{}|{}".format(
|
||||
a1.ljust(width_col1),
|
||||
str(b1).ljust(width_col2),
|
||||
str(c1).ljust(width_col3),
|
||||
str(d1).ljust(width_col4),
|
||||
)
|
||||
+ "\033[0m"
|
||||
)
|
||||
else:
|
||||
print("{}|{}|{}|{}".format(a1.ljust(width_col1), str(b1).ljust(width_col2),
|
||||
str(c1).ljust(width_col3), str(d1).ljust(width_col4)))
|
||||
print(
|
||||
"{}|{}|{}|{}".format(
|
||||
a1.ljust(width_col1),
|
||||
str(b1).ljust(width_col2),
|
||||
str(c1).ljust(width_col3),
|
||||
str(d1).ljust(width_col4),
|
||||
)
|
||||
)
|
||||
|
||||
def jquery_test_runner(self, cmd, binary: str):
|
||||
base_dir = path.abspath(path.join("tests", "jquery"))
|
||||
|
@ -529,12 +574,10 @@ class MachCommands(CommandBase):
|
|||
|
||||
# Clone the jQuery repository if it doesn't exist
|
||||
if not os.path.isdir(jquery_dir):
|
||||
check_call(
|
||||
["git", "clone", "-b", "servo", "--depth", "1", "https://github.com/servo/jquery", jquery_dir])
|
||||
check_call(["git", "clone", "-b", "servo", "--depth", "1", "https://github.com/servo/jquery", jquery_dir])
|
||||
|
||||
# Run pull in case the jQuery repo was updated since last test run
|
||||
check_call(
|
||||
["git", "-C", jquery_dir, "pull"])
|
||||
check_call(["git", "-C", jquery_dir, "pull"])
|
||||
|
||||
# Check that a release servo build exists
|
||||
bin_path = path.abspath(binary)
|
||||
|
@ -553,29 +596,34 @@ class MachCommands(CommandBase):
|
|||
# Clone the Dromaeo repository if it doesn't exist
|
||||
if not os.path.isdir(dromaeo_dir):
|
||||
check_call(
|
||||
["git", "clone", "-b", "servo", "--depth", "1", "https://github.com/notriddle/dromaeo", dromaeo_dir])
|
||||
["git", "clone", "-b", "servo", "--depth", "1", "https://github.com/notriddle/dromaeo", dromaeo_dir]
|
||||
)
|
||||
|
||||
# Run pull in case the Dromaeo repo was updated since last test run
|
||||
check_call(
|
||||
["git", "-C", dromaeo_dir, "pull"])
|
||||
check_call(["git", "-C", dromaeo_dir, "pull"])
|
||||
|
||||
# Compile test suite
|
||||
check_call(
|
||||
["make", "-C", dromaeo_dir, "web"])
|
||||
check_call(["make", "-C", dromaeo_dir, "web"])
|
||||
|
||||
# Check that a release servo build exists
|
||||
bin_path = path.abspath(binary)
|
||||
|
||||
return check_call(
|
||||
[run_file, "|".join(tests), bin_path, base_dir, bmf_output])
|
||||
return check_call([run_file, "|".join(tests), bin_path, base_dir, bmf_output])
|
||||
|
||||
def speedometer_runner(self, binary: str, bmf_output: str | None):
|
||||
speedometer = json.loads(subprocess.check_output([
|
||||
binary,
|
||||
"https://servospeedometer.netlify.app?headless=1",
|
||||
"--pref", "dom_allow_scripts_to_close_windows",
|
||||
"--window-size=1100x900",
|
||||
"--headless"], timeout=120).decode())
|
||||
speedometer = json.loads(
|
||||
subprocess.check_output(
|
||||
[
|
||||
binary,
|
||||
"https://servospeedometer.netlify.app?headless=1",
|
||||
"--pref",
|
||||
"dom_allow_scripts_to_close_windows",
|
||||
"--window-size=1100x900",
|
||||
"--headless",
|
||||
],
|
||||
timeout=120,
|
||||
).decode()
|
||||
)
|
||||
|
||||
print(f"Score: {speedometer['Score']['mean']} ± {speedometer['Score']['delta']}")
|
||||
|
||||
|
@ -583,53 +631,53 @@ class MachCommands(CommandBase):
|
|||
output = dict()
|
||||
|
||||
def parse_speedometer_result(result):
|
||||
if result['unit'] == "ms":
|
||||
if result["unit"] == "ms":
|
||||
output[f"Speedometer/{result['name']}"] = {
|
||||
'latency': { # speedometer has ms we need to convert to ns
|
||||
'value': float(result['mean']) * 1000.0,
|
||||
'lower_value': float(result['min']) * 1000.0,
|
||||
'upper_value': float(result['max']) * 1000.0,
|
||||
"latency": { # speedometer has ms we need to convert to ns
|
||||
"value": float(result["mean"]) * 1000.0,
|
||||
"lower_value": float(result["min"]) * 1000.0,
|
||||
"upper_value": float(result["max"]) * 1000.0,
|
||||
}
|
||||
}
|
||||
elif result['unit'] == "score":
|
||||
elif result["unit"] == "score":
|
||||
output[f"Speedometer/{result['name']}"] = {
|
||||
'score': {
|
||||
'value': float(result['mean']),
|
||||
'lower_value': float(result['min']),
|
||||
'upper_value': float(result['max']),
|
||||
"score": {
|
||||
"value": float(result["mean"]),
|
||||
"lower_value": float(result["min"]),
|
||||
"upper_value": float(result["max"]),
|
||||
}
|
||||
}
|
||||
else:
|
||||
raise "Unknown unit!"
|
||||
|
||||
for child in result['children']:
|
||||
for child in result["children"]:
|
||||
parse_speedometer_result(child)
|
||||
|
||||
for v in speedometer.values():
|
||||
parse_speedometer_result(v)
|
||||
with open(bmf_output, 'w', encoding='utf-8') as f:
|
||||
with open(bmf_output, "w", encoding="utf-8") as f:
|
||||
json.dump(output, f, indent=4)
|
||||
|
||||
@Command('update-net-cookies',
|
||||
description='Update the net unit tests with cookie tests from http-state',
|
||||
category='testing')
|
||||
@Command(
|
||||
"update-net-cookies",
|
||||
description="Update the net unit tests with cookie tests from http-state",
|
||||
category="testing",
|
||||
)
|
||||
def update_net_cookies(self):
|
||||
cache_dir = path.join(self.config["tools"]["cache-dir"], "tests")
|
||||
run_file = path.abspath(path.join(PROJECT_TOPLEVEL_PATH,
|
||||
"components", "net", "tests",
|
||||
"cookie_http_state_utils.py"))
|
||||
run_file = path.abspath(
|
||||
path.join(PROJECT_TOPLEVEL_PATH, "components", "net", "tests", "cookie_http_state_utils.py")
|
||||
)
|
||||
run_globals = {"__file__": run_file}
|
||||
exec(compile(open(run_file).read(), run_file, 'exec'), run_globals)
|
||||
exec(compile(open(run_file).read(), run_file, "exec"), run_globals)
|
||||
return run_globals["update_test_file"](cache_dir)
|
||||
|
||||
@Command('update-webgl',
|
||||
description='Update the WebGL conformance suite tests from Khronos repo',
|
||||
category='testing')
|
||||
@CommandArgument('--version', default='2.0.0',
|
||||
help='WebGL conformance suite version')
|
||||
@Command(
|
||||
"update-webgl", description="Update the WebGL conformance suite tests from Khronos repo", category="testing"
|
||||
)
|
||||
@CommandArgument("--version", default="2.0.0", help="WebGL conformance suite version")
|
||||
def update_webgl(self, version=None):
|
||||
base_dir = path.abspath(path.join(PROJECT_TOPLEVEL_PATH,
|
||||
"tests", "wpt", "mozilla", "tests", "webgl"))
|
||||
base_dir = path.abspath(path.join(PROJECT_TOPLEVEL_PATH, "tests", "wpt", "mozilla", "tests", "webgl"))
|
||||
run_file = path.join(base_dir, "tools", "import-conformance-tests.py")
|
||||
dest_folder = path.join(base_dir, "conformance-%s" % version)
|
||||
patches_dir = path.join(base_dir, "tools")
|
||||
|
@ -638,18 +686,12 @@ class MachCommands(CommandBase):
|
|||
shutil.rmtree(dest_folder)
|
||||
|
||||
run_globals = {"__file__": run_file}
|
||||
exec(compile(open(run_file).read(), run_file, 'exec'), run_globals)
|
||||
exec(compile(open(run_file).read(), run_file, "exec"), run_globals)
|
||||
return run_globals["update_conformance"](version, dest_folder, None, patches_dir)
|
||||
|
||||
@Command('update-webgpu',
|
||||
description='Update the WebGPU conformance test suite',
|
||||
category='testing')
|
||||
@CommandArgument(
|
||||
'--repo', '-r', default="https://github.com/gpuweb/cts",
|
||||
help='Repo to vendor cts from')
|
||||
@CommandArgument(
|
||||
'--checkout', '-c', default="main",
|
||||
help='Branch or commit of repo')
|
||||
@Command("update-webgpu", description="Update the WebGPU conformance test suite", category="testing")
|
||||
@CommandArgument("--repo", "-r", default="https://github.com/gpuweb/cts", help="Repo to vendor cts from")
|
||||
@CommandArgument("--checkout", "-c", default="main", help="Branch or commit of repo")
|
||||
def cts(self, repo="https://github.com/gpuweb/cts", checkout="main"):
|
||||
tdir = path.join(self.context.topdir, "tests/wpt/webgpu/tests")
|
||||
clone_dir = path.join(tdir, "cts_clone")
|
||||
|
@ -672,52 +714,52 @@ class MachCommands(CommandBase):
|
|||
delete(path.join(clone_dir, "out-wpt", "cts-chunked2sec.https.html"))
|
||||
cts_html = path.join(clone_dir, "out-wpt", "cts.https.html")
|
||||
# patch
|
||||
with open(cts_html, 'r') as file:
|
||||
with open(cts_html, "r") as file:
|
||||
filedata = file.read()
|
||||
# files are mounted differently
|
||||
filedata = filedata.replace('src=/webgpu/common/runtime/wpt.js', 'src=../webgpu/common/runtime/wpt.js')
|
||||
filedata = filedata.replace("src=/webgpu/common/runtime/wpt.js", "src=../webgpu/common/runtime/wpt.js")
|
||||
# Mark all webgpu tests as long to increase their timeouts. This is needed due to wgpu's slowness.
|
||||
# TODO: replace this with more fine grained solution: https://github.com/servo/servo/issues/30999
|
||||
filedata = filedata.replace('<meta charset=utf-8>',
|
||||
'<meta charset=utf-8>\n<meta name="timeout" content="long">')
|
||||
filedata = filedata.replace(
|
||||
"<meta charset=utf-8>", '<meta charset=utf-8>\n<meta name="timeout" content="long">'
|
||||
)
|
||||
# Write the file out again
|
||||
with open(cts_html, 'w') as file:
|
||||
with open(cts_html, "w") as file:
|
||||
file.write(filedata)
|
||||
logger = path.join(clone_dir, "out-wpt", "common/internal/logging/test_case_recorder.js")
|
||||
with open(logger, 'r') as file:
|
||||
with open(logger, "r") as file:
|
||||
filedata = file.read()
|
||||
filedata.replace("info(ex) {", "info(ex) {return;")
|
||||
with open(logger, 'w') as file:
|
||||
with open(logger, "w") as file:
|
||||
file.write(filedata)
|
||||
# copy
|
||||
delete(path.join(tdir, "webgpu"))
|
||||
shutil.copytree(path.join(clone_dir, "out-wpt"), path.join(tdir, "webgpu"))
|
||||
# update commit
|
||||
commit = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=clone_dir).decode()
|
||||
with open(path.join(tdir, "checkout_commit.txt"), 'w') as file:
|
||||
with open(path.join(tdir, "checkout_commit.txt"), "w") as file:
|
||||
file.write(commit)
|
||||
# clean up
|
||||
delete(clone_dir)
|
||||
print("Updating manifest.")
|
||||
return self.context.commands.dispatch("update-manifest", self.context)
|
||||
|
||||
@Command('smoketest',
|
||||
description='Load a simple page in Servo and ensure that it closes properly',
|
||||
category='testing')
|
||||
@CommandArgument('params', nargs='...',
|
||||
help="Command-line arguments to be passed through to Servo")
|
||||
@Command(
|
||||
"smoketest", description="Load a simple page in Servo and ensure that it closes properly", category="testing"
|
||||
)
|
||||
@CommandArgument("params", nargs="...", help="Command-line arguments to be passed through to Servo")
|
||||
@CommandBase.common_command_arguments(binary_selection=True)
|
||||
def smoketest(self, servo_binary: str, params, **kwargs):
|
||||
# We pass `-f` here so that any thread panic will cause Servo to exit,
|
||||
# preventing a panic from hanging execution. This means that these kind
|
||||
# of panics won't cause timeouts on CI.
|
||||
return PostBuildCommands(self.context)._run(servo_binary,
|
||||
params + ['-f', 'tests/html/close-on-load.html'])
|
||||
return PostBuildCommands(self.context)._run(servo_binary, params + ["-f", "tests/html/close-on-load.html"])
|
||||
|
||||
@Command('try', description='Runs try jobs by force pushing to try branch', category='testing')
|
||||
@CommandArgument('--remote', '-r', default="origin", help='A git remote to run the try job on')
|
||||
@CommandArgument('try_strings', default=["full"], nargs='...',
|
||||
help="A list of try strings specifying what kind of job to run.")
|
||||
@Command("try", description="Runs try jobs by force pushing to try branch", category="testing")
|
||||
@CommandArgument("--remote", "-r", default="origin", help="A git remote to run the try job on")
|
||||
@CommandArgument(
|
||||
"try_strings", default=["full"], nargs="...", help="A list of try strings specifying what kind of job to run."
|
||||
)
|
||||
def try_command(self, remote: str, try_strings: list[str]):
|
||||
if subprocess.check_output(["git", "diff", "--cached", "--name-only"]).strip():
|
||||
print("Cannot run `try` with staged and uncommited changes. ")
|
||||
|
@ -755,7 +797,7 @@ class MachCommands(CommandBase):
|
|||
# tool and get the real URL.
|
||||
actions_url = remote_url.replace(".git", "/actions")
|
||||
if not actions_url.startswith("https"):
|
||||
actions_url = actions_url.replace(':', '/')
|
||||
actions_url = actions_url.replace(":", "/")
|
||||
actions_url = actions_url.replace("git@", "")
|
||||
actions_url = f"https://{actions_url}"
|
||||
print(f"Actions available at: {actions_url}")
|
||||
|
@ -770,25 +812,27 @@ class MachCommands(CommandBase):
|
|||
|
||||
def create_parser_create():
|
||||
import argparse
|
||||
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument("--no-editor", action="store_true",
|
||||
help="Don't try to open the test in an editor")
|
||||
p.add_argument("--no-editor", action="store_true", help="Don't try to open the test in an editor")
|
||||
p.add_argument("-e", "--editor", action="store", help="Editor to use")
|
||||
p.add_argument("--no-run", action="store_true",
|
||||
help="Don't try to update the wpt manifest or open the test in a browser")
|
||||
p.add_argument('--release', action="store_true",
|
||||
help="Run with a release build of servo")
|
||||
p.add_argument("--long-timeout", action="store_true",
|
||||
help="Test should be given a long timeout (typically 60s rather than 10s,"
|
||||
"but varies depending on environment)")
|
||||
p.add_argument("--overwrite", action="store_true",
|
||||
help="Allow overwriting an existing test file")
|
||||
p.add_argument("-r", "--reftest", action="store_true",
|
||||
help="Create a reftest rather than a testharness (js) test"),
|
||||
p.add_argument(
|
||||
"--no-run", action="store_true", help="Don't try to update the wpt manifest or open the test in a browser"
|
||||
)
|
||||
p.add_argument("--release", action="store_true", help="Run with a release build of servo")
|
||||
p.add_argument(
|
||||
"--long-timeout",
|
||||
action="store_true",
|
||||
help="Test should be given a long timeout (typically 60s rather than 10s,but varies depending on environment)",
|
||||
)
|
||||
p.add_argument("--overwrite", action="store_true", help="Allow overwriting an existing test file")
|
||||
(
|
||||
p.add_argument(
|
||||
"-r", "--reftest", action="store_true", help="Create a reftest rather than a testharness (js) test"
|
||||
),
|
||||
)
|
||||
p.add_argument("-ref", "--reference", dest="ref", help="Path to the reference file")
|
||||
p.add_argument("--mismatch", action="store_true",
|
||||
help="Create a mismatch reftest")
|
||||
p.add_argument("--wait", action="store_true",
|
||||
help="Create a reftest that waits until takeScreenshot() is called")
|
||||
p.add_argument("--mismatch", action="store_true", help="Create a mismatch reftest")
|
||||
p.add_argument("--wait", action="store_true", help="Create a reftest that waits until takeScreenshot() is called")
|
||||
p.add_argument("path", action="store", help="Path to the test file")
|
||||
return p
|
||||
|
|
|
@ -44,11 +44,11 @@ class JobConfig(object):
|
|||
number_of_wpt_chunks: int = 20
|
||||
# These are the fields that must match in between two JobConfigs for them to be able to be
|
||||
# merged. If you modify any of the fields above, make sure to update this line as well.
|
||||
merge_compatibility_fields: ClassVar[List[str]] = ['workflow', 'profile', 'wpt_args', 'build_args']
|
||||
merge_compatibility_fields: ClassVar[List[str]] = ["workflow", "profile", "wpt_args", "build_args"]
|
||||
|
||||
def merge(self, other: JobConfig) -> bool:
|
||||
"""Try to merge another job with this job. Returns True if merging is successful
|
||||
or False if not. If merging is successful this job will be modified."""
|
||||
or False if not. If merging is successful this job will be modified."""
|
||||
for field in self.merge_compatibility_fields:
|
||||
if getattr(self, field) != getattr(other, field):
|
||||
return False
|
||||
|
@ -101,11 +101,14 @@ def handle_preset(s: str) -> Optional[JobConfig]:
|
|||
elif any(word in s for word in ["ohos", "openharmony"]):
|
||||
return JobConfig("OpenHarmony", Workflow.OHOS)
|
||||
elif any(word in s for word in ["webgpu"]):
|
||||
return JobConfig("WebGPU CTS", Workflow.LINUX,
|
||||
wpt=True, # reftests are mode for new layout
|
||||
wpt_args="_webgpu", # run only webgpu cts
|
||||
profile="production", # WebGPU works to slow with debug assert
|
||||
unit_tests=False) # production profile does not work with unit-tests
|
||||
return JobConfig(
|
||||
"WebGPU CTS",
|
||||
Workflow.LINUX,
|
||||
wpt=True, # reftests are mode for new layout
|
||||
wpt_args="_webgpu", # run only webgpu cts
|
||||
profile="production", # WebGPU works to slow with debug assert
|
||||
unit_tests=False,
|
||||
) # production profile does not work with unit-tests
|
||||
elif any(word in s for word in ["lint", "tidy"]):
|
||||
return JobConfig("Lint", Workflow.LINT)
|
||||
else:
|
||||
|
@ -199,115 +202,130 @@ if __name__ == "__main__":
|
|||
|
||||
class TestParser(unittest.TestCase):
|
||||
def test_string(self):
|
||||
self.assertDictEqual(json.loads(Config("linux-unit-tests fail-fast").to_json()),
|
||||
{'fail_fast': True,
|
||||
'matrix': [{
|
||||
'bencher': False,
|
||||
'name': 'Linux (Unit Tests)',
|
||||
'number_of_wpt_chunks': 20,
|
||||
'profile': 'release',
|
||||
'unit_tests': True,
|
||||
'build_libservo': False,
|
||||
'workflow': 'linux',
|
||||
'wpt': False,
|
||||
'wpt_args': '',
|
||||
'build_args': ''
|
||||
}]
|
||||
})
|
||||
self.assertDictEqual(
|
||||
json.loads(Config("linux-unit-tests fail-fast").to_json()),
|
||||
{
|
||||
"fail_fast": True,
|
||||
"matrix": [
|
||||
{
|
||||
"bencher": False,
|
||||
"name": "Linux (Unit Tests)",
|
||||
"number_of_wpt_chunks": 20,
|
||||
"profile": "release",
|
||||
"unit_tests": True,
|
||||
"build_libservo": False,
|
||||
"workflow": "linux",
|
||||
"wpt": False,
|
||||
"wpt_args": "",
|
||||
"build_args": "",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
def test_empty(self):
|
||||
self.assertDictEqual(json.loads(Config("").to_json()),
|
||||
{"fail_fast": False, "matrix": [
|
||||
{
|
||||
"name": "Linux (Unit Tests, WPT, Bencher)",
|
||||
'number_of_wpt_chunks': 20,
|
||||
"workflow": "linux",
|
||||
"wpt": True,
|
||||
"profile": "release",
|
||||
"unit_tests": True,
|
||||
'build_libservo': False,
|
||||
'bencher': True,
|
||||
"wpt_args": "",
|
||||
'build_args': ''
|
||||
},
|
||||
{
|
||||
"name": "MacOS (Unit Tests)",
|
||||
'number_of_wpt_chunks': 20,
|
||||
"workflow": "macos",
|
||||
"wpt": False,
|
||||
"profile": "release",
|
||||
"unit_tests": True,
|
||||
'build_libservo': False,
|
||||
'bencher': False,
|
||||
"wpt_args": "",
|
||||
'build_args': ''
|
||||
},
|
||||
{
|
||||
"name": "Windows (Unit Tests)",
|
||||
'number_of_wpt_chunks': 20,
|
||||
"workflow": "windows",
|
||||
"wpt": False,
|
||||
"profile": "release",
|
||||
"unit_tests": True,
|
||||
'build_libservo': False,
|
||||
'bencher': False,
|
||||
"wpt_args": "",
|
||||
'build_args': ''
|
||||
},
|
||||
{
|
||||
"name": "Android",
|
||||
'number_of_wpt_chunks': 20,
|
||||
"workflow": "android",
|
||||
"wpt": False,
|
||||
"profile": "release",
|
||||
"unit_tests": False,
|
||||
'build_libservo': False,
|
||||
'bencher': False,
|
||||
"wpt_args": "",
|
||||
'build_args': ''
|
||||
},
|
||||
{
|
||||
"name": "OpenHarmony",
|
||||
'number_of_wpt_chunks': 20,
|
||||
"workflow": "ohos",
|
||||
"wpt": False,
|
||||
"profile": "release",
|
||||
"unit_tests": False,
|
||||
'build_libservo': False,
|
||||
'bencher': False,
|
||||
"wpt_args": "",
|
||||
'build_args': ''
|
||||
},
|
||||
{
|
||||
"name": "Lint",
|
||||
'number_of_wpt_chunks': 20,
|
||||
"workflow": "lint",
|
||||
"wpt": False,
|
||||
"profile": "release",
|
||||
"unit_tests": False,
|
||||
'build_libservo': False,
|
||||
'bencher': False,
|
||||
"wpt_args": "",
|
||||
'build_args': ''
|
||||
}
|
||||
]})
|
||||
self.assertDictEqual(
|
||||
json.loads(Config("").to_json()),
|
||||
{
|
||||
"fail_fast": False,
|
||||
"matrix": [
|
||||
{
|
||||
"name": "Linux (Unit Tests, WPT, Bencher)",
|
||||
"number_of_wpt_chunks": 20,
|
||||
"workflow": "linux",
|
||||
"wpt": True,
|
||||
"profile": "release",
|
||||
"unit_tests": True,
|
||||
"build_libservo": False,
|
||||
"bencher": True,
|
||||
"wpt_args": "",
|
||||
"build_args": "",
|
||||
},
|
||||
{
|
||||
"name": "MacOS (Unit Tests)",
|
||||
"number_of_wpt_chunks": 20,
|
||||
"workflow": "macos",
|
||||
"wpt": False,
|
||||
"profile": "release",
|
||||
"unit_tests": True,
|
||||
"build_libservo": False,
|
||||
"bencher": False,
|
||||
"wpt_args": "",
|
||||
"build_args": "",
|
||||
},
|
||||
{
|
||||
"name": "Windows (Unit Tests)",
|
||||
"number_of_wpt_chunks": 20,
|
||||
"workflow": "windows",
|
||||
"wpt": False,
|
||||
"profile": "release",
|
||||
"unit_tests": True,
|
||||
"build_libservo": False,
|
||||
"bencher": False,
|
||||
"wpt_args": "",
|
||||
"build_args": "",
|
||||
},
|
||||
{
|
||||
"name": "Android",
|
||||
"number_of_wpt_chunks": 20,
|
||||
"workflow": "android",
|
||||
"wpt": False,
|
||||
"profile": "release",
|
||||
"unit_tests": False,
|
||||
"build_libservo": False,
|
||||
"bencher": False,
|
||||
"wpt_args": "",
|
||||
"build_args": "",
|
||||
},
|
||||
{
|
||||
"name": "OpenHarmony",
|
||||
"number_of_wpt_chunks": 20,
|
||||
"workflow": "ohos",
|
||||
"wpt": False,
|
||||
"profile": "release",
|
||||
"unit_tests": False,
|
||||
"build_libservo": False,
|
||||
"bencher": False,
|
||||
"wpt_args": "",
|
||||
"build_args": "",
|
||||
},
|
||||
{
|
||||
"name": "Lint",
|
||||
"number_of_wpt_chunks": 20,
|
||||
"workflow": "lint",
|
||||
"wpt": False,
|
||||
"profile": "release",
|
||||
"unit_tests": False,
|
||||
"build_libservo": False,
|
||||
"bencher": False,
|
||||
"wpt_args": "",
|
||||
"build_args": "",
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
def test_job_merging(self):
|
||||
self.assertDictEqual(json.loads(Config("linux-wpt").to_json()),
|
||||
{'fail_fast': False,
|
||||
'matrix': [{
|
||||
'bencher': False,
|
||||
'name': 'Linux (WPT)',
|
||||
'number_of_wpt_chunks': 20,
|
||||
'profile': 'release',
|
||||
'unit_tests': False,
|
||||
'build_libservo': False,
|
||||
'workflow': 'linux',
|
||||
'wpt': True,
|
||||
'wpt_args': '',
|
||||
'build_args': ''
|
||||
}]
|
||||
})
|
||||
self.assertDictEqual(
|
||||
json.loads(Config("linux-wpt").to_json()),
|
||||
{
|
||||
"fail_fast": False,
|
||||
"matrix": [
|
||||
{
|
||||
"bencher": False,
|
||||
"name": "Linux (WPT)",
|
||||
"number_of_wpt_chunks": 20,
|
||||
"profile": "release",
|
||||
"unit_tests": False,
|
||||
"build_libservo": False,
|
||||
"workflow": "linux",
|
||||
"wpt": True,
|
||||
"wpt_args": "",
|
||||
"build_args": "",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
a = JobConfig("Linux (Unit Tests)", Workflow.LINUX, unit_tests=True)
|
||||
b = JobConfig("Linux", Workflow.LINUX, unit_tests=False)
|
||||
|
@ -319,8 +337,7 @@ class TestParser(unittest.TestCase):
|
|||
b = handle_preset("linux-wpt")
|
||||
b = handle_modifier(b, "linux-wpt")
|
||||
self.assertTrue(a.merge(b), "Should merge jobs that have different unit test configurations.")
|
||||
self.assertEqual(a, JobConfig("Linux (Unit Tests, WPT)", Workflow.LINUX,
|
||||
unit_tests=True, wpt=True))
|
||||
self.assertEqual(a, JobConfig("Linux (Unit Tests, WPT)", Workflow.LINUX, unit_tests=True, wpt=True))
|
||||
|
||||
a = JobConfig("Linux (Unit Tests)", Workflow.LINUX, unit_tests=True)
|
||||
b = JobConfig("Mac", Workflow.MACOS, unit_tests=True)
|
||||
|
@ -343,12 +360,10 @@ class TestParser(unittest.TestCase):
|
|||
self.assertEqual(a, JobConfig("Linux (Unit Tests)", Workflow.LINUX, unit_tests=True))
|
||||
|
||||
def test_full(self):
|
||||
self.assertDictEqual(json.loads(Config("full").to_json()),
|
||||
json.loads(Config("").to_json()))
|
||||
self.assertDictEqual(json.loads(Config("full").to_json()), json.loads(Config("").to_json()))
|
||||
|
||||
def test_wpt_alias(self):
|
||||
self.assertDictEqual(json.loads(Config("wpt").to_json()),
|
||||
json.loads(Config("linux-wpt").to_json()))
|
||||
self.assertDictEqual(json.loads(Config("wpt").to_json()), json.loads(Config("linux-wpt").to_json()))
|
||||
|
||||
|
||||
def run_tests():
|
||||
|
|
|
@ -49,12 +49,12 @@ def download(description: str, url: str, writer: BufferedIOBase, start_byte: int
|
|||
try:
|
||||
req = urllib.request.Request(url)
|
||||
if start_byte:
|
||||
req = urllib.request.Request(url, headers={'Range': 'bytes={}-'.format(start_byte)})
|
||||
req = urllib.request.Request(url, headers={"Range": "bytes={}-".format(start_byte)})
|
||||
resp = urllib.request.urlopen(req)
|
||||
|
||||
fsize = None
|
||||
if resp.info().get('Content-Length'):
|
||||
fsize = int(resp.info().get('Content-Length').strip()) + start_byte
|
||||
if resp.info().get("Content-Length"):
|
||||
fsize = int(resp.info().get("Content-Length").strip()) + start_byte
|
||||
|
||||
recved = start_byte
|
||||
chunk_size = 64 * 1024
|
||||
|
@ -72,7 +72,7 @@ def download(description: str, url: str, writer: BufferedIOBase, start_byte: int
|
|||
progress_line = "\rDownloading %s: %5.1f%%" % (description, pct)
|
||||
now = time.time()
|
||||
duration = now - previous_progress_line_time
|
||||
if progress_line != previous_progress_line and duration > .1:
|
||||
if progress_line != previous_progress_line and duration > 0.1:
|
||||
print(progress_line, end="")
|
||||
previous_progress_line = progress_line
|
||||
previous_progress_line_time = now
|
||||
|
@ -85,8 +85,10 @@ def download(description: str, url: str, writer: BufferedIOBase, start_byte: int
|
|||
except urllib.error.HTTPError as e:
|
||||
print("Download failed ({}): {} - {}".format(e.code, e.reason, url))
|
||||
if e.code == 403:
|
||||
print("No Rust compiler binary available for this platform. "
|
||||
"Please see https://github.com/servo/servo/#prerequisites")
|
||||
print(
|
||||
"No Rust compiler binary available for this platform. "
|
||||
"Please see https://github.com/servo/servo/#prerequisites"
|
||||
)
|
||||
sys.exit(1)
|
||||
except urllib.error.URLError as e:
|
||||
print("Error downloading {}: {}. The failing URL was: {}".format(description, e.reason, url))
|
||||
|
@ -109,10 +111,10 @@ def download_file(description: str, url: str, destination_path: str):
|
|||
tmp_path = destination_path + ".part"
|
||||
try:
|
||||
start_byte = os.path.getsize(tmp_path)
|
||||
with open(tmp_path, 'ab') as fd:
|
||||
with open(tmp_path, "ab") as fd:
|
||||
download(description, url, fd, start_byte=start_byte)
|
||||
except os.error:
|
||||
with open(tmp_path, 'wb') as fd:
|
||||
with open(tmp_path, "wb") as fd:
|
||||
download(description, url, fd)
|
||||
os.rename(tmp_path, destination_path)
|
||||
|
||||
|
@ -129,7 +131,7 @@ class ZipFileWithUnixPermissions(zipfile.ZipFile):
|
|||
|
||||
extracted = self._extract_member(member, path, pwd)
|
||||
mode = os.stat(extracted).st_mode
|
||||
mode |= (member.external_attr >> 16)
|
||||
mode |= member.external_attr >> 16
|
||||
os.chmod(extracted, mode)
|
||||
return extracted
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ def find_vswhere():
|
|||
for path in [PROGRAM_FILES, PROGRAM_FILES_X86]:
|
||||
if not path:
|
||||
continue
|
||||
vswhere = os.path.join(path, 'Microsoft Visual Studio', 'Installer', 'vswhere.exe')
|
||||
vswhere = os.path.join(path, "Microsoft Visual Studio", "Installer", "vswhere.exe")
|
||||
if os.path.exists(vswhere):
|
||||
return vswhere
|
||||
return None
|
||||
|
@ -52,24 +52,30 @@ def find_compatible_msvc_with_vswhere() -> Generator[VisualStudioInstallation, N
|
|||
if not vswhere:
|
||||
return
|
||||
|
||||
output = subprocess.check_output([
|
||||
vswhere,
|
||||
'-format', 'json',
|
||||
'-products', '*',
|
||||
'-requires', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
|
||||
'-requires', 'Microsoft.VisualStudio.Component.Windows10SDK',
|
||||
'-utf8'
|
||||
]).decode(errors='ignore')
|
||||
output = subprocess.check_output(
|
||||
[
|
||||
vswhere,
|
||||
"-format",
|
||||
"json",
|
||||
"-products",
|
||||
"*",
|
||||
"-requires",
|
||||
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
||||
"-requires",
|
||||
"Microsoft.VisualStudio.Component.Windows10SDK",
|
||||
"-utf8",
|
||||
]
|
||||
).decode(errors="ignore")
|
||||
|
||||
for install in json.loads(output):
|
||||
installed_version = f"{install['installationVersion'].split('.')[0]}.0"
|
||||
if installed_version not in COMPATIBLE_MSVC_VERSIONS.values():
|
||||
continue
|
||||
installation_path = install['installationPath']
|
||||
installation_path = install["installationPath"]
|
||||
yield VisualStudioInstallation(
|
||||
version_number=installed_version,
|
||||
installation_path=installation_path,
|
||||
vc_install_path=os.path.join(installation_path, "VC")
|
||||
vc_install_path=os.path.join(installation_path, "VC"),
|
||||
)
|
||||
|
||||
|
||||
|
@ -77,20 +83,20 @@ def find_compatible_msvc_with_path() -> Generator[VisualStudioInstallation, None
|
|||
for program_files in [PROGRAM_FILES, PROGRAM_FILES_X86]:
|
||||
if not program_files:
|
||||
continue
|
||||
for (version, version_number) in COMPATIBLE_MSVC_VERSIONS.items():
|
||||
for version, version_number in COMPATIBLE_MSVC_VERSIONS.items():
|
||||
for edition in ["Enterprise", "Professional", "Community", "BuildTools"]:
|
||||
installation_path = os.path.join(program_files, "Microsoft Visual Studio", version, edition)
|
||||
if os.path.exists(installation_path):
|
||||
yield VisualStudioInstallation(
|
||||
version_number=version_number,
|
||||
installation_path=installation_path,
|
||||
vc_install_path=os.path.join(installation_path, "VC")
|
||||
vc_install_path=os.path.join(installation_path, "VC"),
|
||||
)
|
||||
|
||||
|
||||
def find_compatible_msvc_with_environment_variables() -> Optional[VisualStudioInstallation]:
|
||||
installation_path = os.environ.get('VSINSTALLDIR')
|
||||
version_number = os.environ.get('VisualStudioVersion')
|
||||
installation_path = os.environ.get("VSINSTALLDIR")
|
||||
version_number = os.environ.get("VisualStudioVersion")
|
||||
if not installation_path or not version_number:
|
||||
return None
|
||||
vc_install_path = os.environ.get("VCINSTALLDIR", os.path.join(installation_path, "VC"))
|
||||
|
@ -116,8 +122,10 @@ def find_msvc_installations() -> List[VisualStudioInstallation]:
|
|||
if installation:
|
||||
return [installation]
|
||||
|
||||
raise Exception("Can't find a Visual Studio installation. "
|
||||
"Please set the VSINSTALLDIR and VisualStudioVersion environment variables")
|
||||
raise Exception(
|
||||
"Can't find a Visual Studio installation. "
|
||||
"Please set the VSINSTALLDIR and VisualStudioVersion environment variables"
|
||||
)
|
||||
|
||||
|
||||
def find_msvc_redist_dirs(vs_platform: str) -> Generator[str, None, None]:
|
||||
|
@ -160,7 +168,7 @@ def find_windows_sdk_installation_path() -> str:
|
|||
|
||||
# This is based on the advice from
|
||||
# https://stackoverflow.com/questions/35119223/how-to-programmatically-detect-and-locate-the-windows-10-sdk
|
||||
key_path = r'SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0'
|
||||
key_path = r"SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0"
|
||||
try:
|
||||
with winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, key_path) as key:
|
||||
return str(winreg.QueryValueEx(key, "InstallationFolder")[0])
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
# option. This file may not be copied, modified, or distributed
|
||||
# except according to those terms.
|
||||
|
||||
from .tidy import scan # noqa
|
||||
from .tidy import scan # noqa
|
||||
from .test import run_tests # noqa
|
||||
|
|
|
@ -15,7 +15,7 @@ import unittest
|
|||
from . import tidy
|
||||
|
||||
|
||||
BASE_PATH = 'python/tidy/tests/'
|
||||
BASE_PATH = "python/tidy/tests/"
|
||||
|
||||
|
||||
def test_file_path(name):
|
||||
|
@ -32,179 +32,170 @@ class CheckTidiness(unittest.TestCase):
|
|||
next(errors)
|
||||
|
||||
def test_tidy_config(self):
|
||||
errors = tidy.check_config_file(os.path.join(BASE_PATH, 'servo-tidy.toml'), print_text=False)
|
||||
errors = tidy.check_config_file(os.path.join(BASE_PATH, "servo-tidy.toml"), print_text=False)
|
||||
self.assertEqual("invalid config key 'key-outside'", next(errors)[2])
|
||||
self.assertEqual("invalid config key 'wrong-key'", next(errors)[2])
|
||||
self.assertEqual('invalid config table [wrong]', next(errors)[2])
|
||||
self.assertEqual("invalid config table [wrong]", next(errors)[2])
|
||||
self.assertEqual("ignored file './fake/file.html' doesn't exist", next(errors)[2])
|
||||
self.assertEqual("ignored directory './fake/dir' doesn't exist", next(errors)[2])
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
def test_directory_checks(self):
|
||||
dirs = {
|
||||
os.path.join(BASE_PATH, "dir_check/webidl_plus"): ['webidl', 'test'],
|
||||
os.path.join(BASE_PATH, "dir_check/only_webidl"): ['webidl']
|
||||
os.path.join(BASE_PATH, "dir_check/webidl_plus"): ["webidl", "test"],
|
||||
os.path.join(BASE_PATH, "dir_check/only_webidl"): ["webidl"],
|
||||
}
|
||||
errors = tidy.check_directory_files(dirs, print_text=False)
|
||||
error_dir = os.path.join(BASE_PATH, "dir_check/webidl_plus")
|
||||
self.assertEqual("Unexpected extension found for test.rs. We only expect files with webidl, "
|
||||
+ f"test extensions in {error_dir}", next(errors)[2])
|
||||
self.assertEqual("Unexpected extension found for test2.rs. We only expect files with webidl, "
|
||||
+ f"test extensions in {error_dir}", next(errors)[2])
|
||||
self.assertEqual(
|
||||
"Unexpected extension found for test.rs. We only expect files with webidl, "
|
||||
+ f"test extensions in {error_dir}",
|
||||
next(errors)[2],
|
||||
)
|
||||
self.assertEqual(
|
||||
"Unexpected extension found for test2.rs. We only expect files with webidl, "
|
||||
+ f"test extensions in {error_dir}",
|
||||
next(errors)[2],
|
||||
)
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
def test_spaces_correctnes(self):
|
||||
errors = tidy.collect_errors_for_files(iterFile('wrong_space.rs'), [], [tidy.check_by_line], print_text=False)
|
||||
self.assertEqual('trailing whitespace', next(errors)[2])
|
||||
self.assertEqual('no newline at EOF', next(errors)[2])
|
||||
self.assertEqual('tab on line', next(errors)[2])
|
||||
self.assertEqual('CR on line', next(errors)[2])
|
||||
self.assertEqual('no newline at EOF', next(errors)[2])
|
||||
errors = tidy.collect_errors_for_files(iterFile("wrong_space.rs"), [], [tidy.check_by_line], print_text=False)
|
||||
self.assertEqual("trailing whitespace", next(errors)[2])
|
||||
self.assertEqual("no newline at EOF", next(errors)[2])
|
||||
self.assertEqual("tab on line", next(errors)[2])
|
||||
self.assertEqual("CR on line", next(errors)[2])
|
||||
self.assertEqual("no newline at EOF", next(errors)[2])
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
def test_empty_file(self):
|
||||
errors = tidy.collect_errors_for_files(iterFile('empty_file.rs'), [], [tidy.check_by_line], print_text=False)
|
||||
self.assertEqual('file is empty', next(errors)[2])
|
||||
errors = tidy.collect_errors_for_files(iterFile("empty_file.rs"), [], [tidy.check_by_line], print_text=False)
|
||||
self.assertEqual("file is empty", next(errors)[2])
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
def test_long_line(self):
|
||||
errors = tidy.collect_errors_for_files(iterFile('long_line.rs'), [], [tidy.check_by_line], print_text=False)
|
||||
self.assertEqual('Line is longer than 120 characters', next(errors)[2])
|
||||
errors = tidy.collect_errors_for_files(iterFile("long_line.rs"), [], [tidy.check_by_line], print_text=False)
|
||||
self.assertEqual("Line is longer than 120 characters", next(errors)[2])
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
def test_whatwg_link(self):
|
||||
errors = tidy.collect_errors_for_files(iterFile('whatwg_link.rs'), [], [tidy.check_by_line], print_text=False)
|
||||
self.assertEqual('link to WHATWG may break in the future, use this format instead: https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata', next(errors)[2])
|
||||
self.assertEqual('links to WHATWG single-page url, change to multi page: https://html.spec.whatwg.org/multipage/#typographic-conventions', next(errors)[2])
|
||||
errors = tidy.collect_errors_for_files(iterFile("whatwg_link.rs"), [], [tidy.check_by_line], print_text=False)
|
||||
self.assertEqual(
|
||||
"link to WHATWG may break in the future, use this format instead: https://html.spec.whatwg.org/multipage/#dom-context-2d-putimagedata",
|
||||
next(errors)[2],
|
||||
)
|
||||
self.assertEqual(
|
||||
"links to WHATWG single-page url, change to multi page: https://html.spec.whatwg.org/multipage/#typographic-conventions",
|
||||
next(errors)[2],
|
||||
)
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
def test_license(self):
|
||||
errors = tidy.collect_errors_for_files(
|
||||
iterFile('incorrect_license.rs'),
|
||||
[],
|
||||
[tidy.check_license],
|
||||
print_text=False
|
||||
iterFile("incorrect_license.rs"), [], [tidy.check_license], print_text=False
|
||||
)
|
||||
self.assertEqual('incorrect license', next(errors)[2])
|
||||
self.assertEqual("incorrect license", next(errors)[2])
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
def test_shebang_license(self):
|
||||
errors = tidy.collect_errors_for_files(
|
||||
iterFile('shebang_license.py'),
|
||||
[],
|
||||
[tidy.check_license],
|
||||
print_text=False
|
||||
iterFile("shebang_license.py"), [], [tidy.check_license], print_text=False
|
||||
)
|
||||
self.assertEqual('missing blank line after shebang', next(errors)[2])
|
||||
self.assertEqual("missing blank line after shebang", next(errors)[2])
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
def test_shell(self):
|
||||
errors = tidy.collect_errors_for_files(iterFile('shell_tidy.sh'), [], [tidy.check_shell], print_text=False)
|
||||
errors = tidy.collect_errors_for_files(iterFile("shell_tidy.sh"), [], [tidy.check_shell], print_text=False)
|
||||
self.assertEqual('script does not have shebang "#!/usr/bin/env bash"', next(errors)[2])
|
||||
self.assertEqual('script is missing options "set -o errexit", "set -o pipefail"', next(errors)[2])
|
||||
self.assertEqual('script should not use backticks for command substitution', next(errors)[2])
|
||||
self.assertEqual('variable substitutions should use the full \"${VAR}\" form', next(errors)[2])
|
||||
self.assertEqual('script should use `[[` instead of `[` for conditional testing', next(errors)[2])
|
||||
self.assertEqual('script should use `[[` instead of `[` for conditional testing', next(errors)[2])
|
||||
self.assertEqual("script should not use backticks for command substitution", next(errors)[2])
|
||||
self.assertEqual('variable substitutions should use the full "${VAR}" form', next(errors)[2])
|
||||
self.assertEqual("script should use `[[` instead of `[` for conditional testing", next(errors)[2])
|
||||
self.assertEqual("script should use `[[` instead of `[` for conditional testing", next(errors)[2])
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
def test_apache2_incomplete(self):
|
||||
errors = tidy.collect_errors_for_files(
|
||||
iterFile('apache2_license.rs'),
|
||||
[],
|
||||
[tidy.check_license],
|
||||
print_text=False
|
||||
iterFile("apache2_license.rs"), [], [tidy.check_license], print_text=False
|
||||
)
|
||||
self.assertEqual('incorrect license', next(errors)[2])
|
||||
self.assertEqual("incorrect license", next(errors)[2])
|
||||
|
||||
def test_rust(self):
|
||||
errors = tidy.collect_errors_for_files(
|
||||
iterFile('rust_tidy.rs'),
|
||||
[],
|
||||
[tidy.check_rust],
|
||||
print_text=False
|
||||
)
|
||||
self.assertTrue('mod declaration is not in alphabetical order' in next(errors)[2])
|
||||
self.assertEqual('mod declaration spans multiple lines', next(errors)[2])
|
||||
self.assertTrue('derivable traits list is not in alphabetical order' in next(errors)[2])
|
||||
self.assertEqual('found an empty line following a {', next(errors)[2])
|
||||
self.assertEqual('use &[T] instead of &Vec<T>', next(errors)[2])
|
||||
self.assertEqual('use &str instead of &String', next(errors)[2])
|
||||
self.assertEqual('use &T instead of &Root<T>', next(errors)[2])
|
||||
self.assertEqual('use &T instead of &DomRoot<T>', next(errors)[2])
|
||||
self.assertEqual('encountered function signature with -> ()', next(errors)[2])
|
||||
self.assertEqual('operators should go at the end of the first line', next(errors)[2])
|
||||
self.assertEqual('unwrap() or panic!() found in code which should not panic.', next(errors)[2])
|
||||
self.assertEqual('unwrap() or panic!() found in code which should not panic.', next(errors)[2])
|
||||
errors = tidy.collect_errors_for_files(iterFile("rust_tidy.rs"), [], [tidy.check_rust], print_text=False)
|
||||
self.assertTrue("mod declaration is not in alphabetical order" in next(errors)[2])
|
||||
self.assertEqual("mod declaration spans multiple lines", next(errors)[2])
|
||||
self.assertTrue("derivable traits list is not in alphabetical order" in next(errors)[2])
|
||||
self.assertEqual("found an empty line following a {", next(errors)[2])
|
||||
self.assertEqual("use &[T] instead of &Vec<T>", next(errors)[2])
|
||||
self.assertEqual("use &str instead of &String", next(errors)[2])
|
||||
self.assertEqual("use &T instead of &Root<T>", next(errors)[2])
|
||||
self.assertEqual("use &T instead of &DomRoot<T>", next(errors)[2])
|
||||
self.assertEqual("encountered function signature with -> ()", next(errors)[2])
|
||||
self.assertEqual("operators should go at the end of the first line", next(errors)[2])
|
||||
self.assertEqual("unwrap() or panic!() found in code which should not panic.", next(errors)[2])
|
||||
self.assertEqual("unwrap() or panic!() found in code which should not panic.", next(errors)[2])
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
feature_errors = tidy.collect_errors_for_files(iterFile('lib.rs'), [], [tidy.check_rust], print_text=False)
|
||||
feature_errors = tidy.collect_errors_for_files(iterFile("lib.rs"), [], [tidy.check_rust], print_text=False)
|
||||
|
||||
self.assertTrue('feature attribute is not in alphabetical order' in next(feature_errors)[2])
|
||||
self.assertTrue('feature attribute is not in alphabetical order' in next(feature_errors)[2])
|
||||
self.assertTrue('feature attribute is not in alphabetical order' in next(feature_errors)[2])
|
||||
self.assertTrue('feature attribute is not in alphabetical order' in next(feature_errors)[2])
|
||||
self.assertTrue("feature attribute is not in alphabetical order" in next(feature_errors)[2])
|
||||
self.assertTrue("feature attribute is not in alphabetical order" in next(feature_errors)[2])
|
||||
self.assertTrue("feature attribute is not in alphabetical order" in next(feature_errors)[2])
|
||||
self.assertTrue("feature attribute is not in alphabetical order" in next(feature_errors)[2])
|
||||
self.assertNoMoreErrors(feature_errors)
|
||||
|
||||
ban_errors = tidy.collect_errors_for_files(iterFile('ban.rs'), [], [tidy.check_rust], print_text=False)
|
||||
self.assertEqual('Banned type Cell<JSVal> detected. Use MutDom<JSVal> instead', next(ban_errors)[2])
|
||||
ban_errors = tidy.collect_errors_for_files(iterFile("ban.rs"), [], [tidy.check_rust], print_text=False)
|
||||
self.assertEqual("Banned type Cell<JSVal> detected. Use MutDom<JSVal> instead", next(ban_errors)[2])
|
||||
self.assertNoMoreErrors(ban_errors)
|
||||
|
||||
ban_errors = tidy.collect_errors_for_files(iterFile(
|
||||
'ban-domrefcell.rs'),
|
||||
[],
|
||||
[tidy.check_rust],
|
||||
print_text=False
|
||||
ban_errors = tidy.collect_errors_for_files(
|
||||
iterFile("ban-domrefcell.rs"), [], [tidy.check_rust], print_text=False
|
||||
)
|
||||
self.assertEqual('Banned type DomRefCell<Dom<T>> detected. Use MutDom<T> instead', next(ban_errors)[2])
|
||||
self.assertEqual("Banned type DomRefCell<Dom<T>> detected. Use MutDom<T> instead", next(ban_errors)[2])
|
||||
self.assertNoMoreErrors(ban_errors)
|
||||
|
||||
def test_spec_link(self):
|
||||
tidy.SPEC_BASE_PATH = BASE_PATH
|
||||
errors = tidy.collect_errors_for_files(iterFile('speclink.rs'), [], [tidy.check_spec], print_text=False)
|
||||
self.assertEqual('method declared in webidl is missing a comment with a specification link', next(errors)[2])
|
||||
self.assertEqual('method declared in webidl is missing a comment with a specification link', next(errors)[2])
|
||||
errors = tidy.collect_errors_for_files(iterFile("speclink.rs"), [], [tidy.check_spec], print_text=False)
|
||||
self.assertEqual("method declared in webidl is missing a comment with a specification link", next(errors)[2])
|
||||
self.assertEqual("method declared in webidl is missing a comment with a specification link", next(errors)[2])
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
def test_webidl(self):
|
||||
errors = tidy.collect_errors_for_files(iterFile('spec.webidl'), [tidy.check_webidl_spec], [], print_text=False)
|
||||
self.assertEqual('No specification link found.', next(errors)[2])
|
||||
errors = tidy.collect_errors_for_files(iterFile("spec.webidl"), [tidy.check_webidl_spec], [], print_text=False)
|
||||
self.assertEqual("No specification link found.", next(errors)[2])
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
def test_toml(self):
|
||||
errors = tidy.collect_errors_for_files(iterFile('Cargo.toml'), [], [tidy.check_toml], print_text=False)
|
||||
self.assertEqual('found asterisk instead of minimum version number', next(errors)[2])
|
||||
self.assertEqual('.toml file should contain a valid license.', next(errors)[2])
|
||||
errors = tidy.collect_errors_for_files(iterFile("Cargo.toml"), [], [tidy.check_toml], print_text=False)
|
||||
self.assertEqual("found asterisk instead of minimum version number", next(errors)[2])
|
||||
self.assertEqual(".toml file should contain a valid license.", next(errors)[2])
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
def test_modeline(self):
|
||||
errors = tidy.collect_errors_for_files(iterFile('modeline.txt'), [], [tidy.check_modeline], print_text=False)
|
||||
self.assertEqual('vi modeline present', next(errors)[2])
|
||||
self.assertEqual('vi modeline present', next(errors)[2])
|
||||
self.assertEqual('vi modeline present', next(errors)[2])
|
||||
self.assertEqual('emacs file variables present', next(errors)[2])
|
||||
self.assertEqual('emacs file variables present', next(errors)[2])
|
||||
errors = tidy.collect_errors_for_files(iterFile("modeline.txt"), [], [tidy.check_modeline], print_text=False)
|
||||
self.assertEqual("vi modeline present", next(errors)[2])
|
||||
self.assertEqual("vi modeline present", next(errors)[2])
|
||||
self.assertEqual("vi modeline present", next(errors)[2])
|
||||
self.assertEqual("emacs file variables present", next(errors)[2])
|
||||
self.assertEqual("emacs file variables present", next(errors)[2])
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
def test_file_list(self):
|
||||
file_path = os.path.join(BASE_PATH, 'test_ignored')
|
||||
file_path = os.path.join(BASE_PATH, "test_ignored")
|
||||
file_list = tidy.FileList(file_path, only_changed_files=False, exclude_dirs=[], progress=False)
|
||||
lst = list(file_list)
|
||||
self.assertEqual(
|
||||
[
|
||||
os.path.join(file_path, 'whee', 'test.rs'),
|
||||
os.path.join(file_path, 'whee', 'foo', 'bar.rs')
|
||||
],
|
||||
lst
|
||||
[os.path.join(file_path, "whee", "test.rs"), os.path.join(file_path, "whee", "foo", "bar.rs")], lst
|
||||
)
|
||||
file_list = tidy.FileList(
|
||||
file_path, only_changed_files=False, exclude_dirs=[os.path.join(file_path, "whee", "foo")], progress=False
|
||||
)
|
||||
file_list = tidy.FileList(file_path, only_changed_files=False,
|
||||
exclude_dirs=[os.path.join(file_path, 'whee', 'foo')],
|
||||
progress=False)
|
||||
lst = list(file_list)
|
||||
self.assertEqual([os.path.join(file_path, 'whee', 'test.rs')], lst)
|
||||
self.assertEqual([os.path.join(file_path, "whee", "test.rs")], lst)
|
||||
|
||||
def test_multiline_string(self):
|
||||
errors = tidy.collect_errors_for_files(iterFile('multiline_string.rs'), [], [tidy.check_rust], print_text=False)
|
||||
errors = tidy.collect_errors_for_files(iterFile("multiline_string.rs"), [], [tidy.check_rust], print_text=False)
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
def test_raw_url_in_rustdoc(self):
|
||||
|
@ -212,34 +203,19 @@ class CheckTidiness(unittest.TestCase):
|
|||
self.assertEqual(tidy.ERROR_RAW_URL_IN_RUSTDOC, next(errors)[1])
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
errors = tidy.check_for_raw_urls_in_rustdoc(
|
||||
"file.rs", 3,
|
||||
b"/// https://google.com"
|
||||
)
|
||||
errors = tidy.check_for_raw_urls_in_rustdoc("file.rs", 3, b"/// https://google.com")
|
||||
assert_has_a_single_rustdoc_error(errors)
|
||||
|
||||
errors = tidy.check_for_raw_urls_in_rustdoc(
|
||||
"file.rs", 3,
|
||||
b"//! (https://google.com)"
|
||||
)
|
||||
errors = tidy.check_for_raw_urls_in_rustdoc("file.rs", 3, b"//! (https://google.com)")
|
||||
assert_has_a_single_rustdoc_error(errors)
|
||||
|
||||
errors = tidy.check_for_raw_urls_in_rustdoc(
|
||||
"file.rs", 3,
|
||||
b"/// <https://google.com>"
|
||||
)
|
||||
errors = tidy.check_for_raw_urls_in_rustdoc("file.rs", 3, b"/// <https://google.com>")
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
errors = tidy.check_for_raw_urls_in_rustdoc(
|
||||
"file.rs", 3,
|
||||
b"/// [hi]: https://google.com"
|
||||
)
|
||||
errors = tidy.check_for_raw_urls_in_rustdoc("file.rs", 3, b"/// [hi]: https://google.com")
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
errors = tidy.check_for_raw_urls_in_rustdoc(
|
||||
"file.rs", 3,
|
||||
b"/// [hi](https://google.com)"
|
||||
)
|
||||
errors = tidy.check_for_raw_urls_in_rustdoc("file.rs", 3, b"/// [hi](https://google.com)")
|
||||
self.assertNoMoreErrors(errors)
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from servo_tidy.tidy import LintRunner
|
||||
|
||||
|
||||
class Lint(LintRunner):
|
||||
def run(self):
|
||||
yield None
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from servo_tidy.tidy import LintRunner
|
||||
|
||||
|
||||
class Linter(LintRunner):
|
||||
def run(self):
|
||||
pass
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from servo_tidy.tidy import LintRunner
|
||||
|
||||
|
||||
class Lint(LintRunner):
|
||||
def some_method(self):
|
||||
pass
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from servo_tidy.tidy import LintRunner
|
||||
|
||||
|
||||
class Lint(LintRunner):
|
||||
def run(self):
|
||||
for _ in [None]:
|
||||
yield ('path', 0, 'foobar')
|
||||
yield ("path", 0, "foobar")
|
||||
|
|
|
@ -31,8 +31,8 @@ WPT_PATH = os.path.join(".", "tests", "wpt")
|
|||
CONFIG_FILE_PATH = os.path.join(".", "servo-tidy.toml")
|
||||
WPT_CONFIG_INI_PATH = os.path.join(WPT_PATH, "config.ini")
|
||||
# regex source https://stackoverflow.com/questions/6883049/
|
||||
URL_REGEX = re.compile(br'https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+')
|
||||
UTF8_URL_REGEX = re.compile(r'https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+')
|
||||
URL_REGEX = re.compile(rb"https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+")
|
||||
UTF8_URL_REGEX = re.compile(r"https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+")
|
||||
CARGO_LOCK_FILE = os.path.join(TOPDIR, "Cargo.lock")
|
||||
CARGO_DENY_CONFIG_FILE = os.path.join(TOPDIR, "deny.toml")
|
||||
|
||||
|
@ -50,22 +50,32 @@ config = {
|
|||
"blocked-packages": {},
|
||||
"ignore": {
|
||||
"files": [
|
||||
os.path.join(".", "."), # ignore hidden files
|
||||
os.path.join(".", "."), # ignore hidden files
|
||||
],
|
||||
"directories": [
|
||||
os.path.join(".", "."), # ignore hidden directories
|
||||
os.path.join(".", "."), # ignore hidden directories
|
||||
],
|
||||
"packages": [],
|
||||
},
|
||||
"check_ext": {}
|
||||
"check_ext": {},
|
||||
}
|
||||
|
||||
COMMENTS = [b"// ", b"# ", b" *", b"/* "]
|
||||
|
||||
# File patterns to include in the non-WPT tidy check.
|
||||
FILE_PATTERNS_TO_CHECK = ["*.rs", "*.rc", "*.cpp", "*.c",
|
||||
"*.h", "*.py", "*.sh",
|
||||
"*.toml", "*.webidl", "*.json", "*.html"]
|
||||
FILE_PATTERNS_TO_CHECK = [
|
||||
"*.rs",
|
||||
"*.rc",
|
||||
"*.cpp",
|
||||
"*.c",
|
||||
"*.h",
|
||||
"*.py",
|
||||
"*.sh",
|
||||
"*.toml",
|
||||
"*.webidl",
|
||||
"*.json",
|
||||
"*.html",
|
||||
]
|
||||
|
||||
# File patterns that are ignored for all tidy and lint checks.
|
||||
FILE_PATTERNS_TO_IGNORE = ["*.#*", "*.pyc", "fake-ld.sh", "*.ogv", "*.webm"]
|
||||
|
@ -106,8 +116,7 @@ WEBIDL_STANDARDS = [
|
|||
b"//notifications.spec.whatwg.org",
|
||||
b"//testutils.spec.whatwg.org/",
|
||||
# Not a URL
|
||||
b"// This interface is entirely internal to Servo, and should not be"
|
||||
+ b" accessible to\n// web pages."
|
||||
b"// This interface is entirely internal to Servo, and should not be" + b" accessible to\n// web pages.",
|
||||
]
|
||||
|
||||
|
||||
|
@ -121,9 +130,9 @@ def is_iter_empty(iterator):
|
|||
|
||||
def normilize_paths(paths):
|
||||
if isinstance(paths, str):
|
||||
return os.path.join(*paths.split('/'))
|
||||
return os.path.join(*paths.split("/"))
|
||||
else:
|
||||
return [os.path.join(*path.split('/')) for path in paths]
|
||||
return [os.path.join(*path.split("/")) for path in paths]
|
||||
|
||||
|
||||
# A simple wrapper for iterators to show progress
|
||||
|
@ -133,7 +142,7 @@ def progress_wrapper(iterator):
|
|||
total_files, progress = len(list_of_stuff), 0
|
||||
for idx, thing in enumerate(list_of_stuff):
|
||||
progress = int(float(idx + 1) / total_files * 100)
|
||||
sys.stdout.write('\r Progress: %s%% (%d/%d)' % (progress, idx + 1, total_files))
|
||||
sys.stdout.write("\r Progress: %s%% (%d/%d)" % (progress, idx + 1, total_files))
|
||||
sys.stdout.flush()
|
||||
yield thing
|
||||
|
||||
|
@ -170,8 +179,8 @@ class FileList(object):
|
|||
if not file_list:
|
||||
return
|
||||
for f in file_list:
|
||||
if not any(os.path.join('.', os.path.dirname(f)).startswith(path) for path in self.excluded):
|
||||
yield os.path.join('.', f)
|
||||
if not any(os.path.join(".", os.path.dirname(f)).startswith(path) for path in self.excluded):
|
||||
yield os.path.join(".", f)
|
||||
|
||||
def _filter_excluded(self):
|
||||
for root, dirs, files in os.walk(self.directory, topdown=True):
|
||||
|
@ -197,8 +206,12 @@ def filter_file(file_name):
|
|||
|
||||
|
||||
def filter_files(start_dir, only_changed_files, progress):
|
||||
file_iter = FileList(start_dir, only_changed_files=only_changed_files,
|
||||
exclude_dirs=config["ignore"]["directories"], progress=progress)
|
||||
file_iter = FileList(
|
||||
start_dir,
|
||||
only_changed_files=only_changed_files,
|
||||
exclude_dirs=config["ignore"]["directories"],
|
||||
progress=progress,
|
||||
)
|
||||
|
||||
for file_name in iter(file_iter):
|
||||
base_name = os.path.basename(file_name)
|
||||
|
@ -213,8 +226,8 @@ def uncomment(line):
|
|||
for c in COMMENTS:
|
||||
if line.startswith(c):
|
||||
if line.endswith(b"*/"):
|
||||
return line[len(c):(len(line) - 3)].strip()
|
||||
return line[len(c):].strip()
|
||||
return line[len(c) : (len(line) - 3)].strip()
|
||||
return line[len(c) :].strip()
|
||||
|
||||
|
||||
def is_apache_licensed(header):
|
||||
|
@ -226,8 +239,7 @@ def is_apache_licensed(header):
|
|||
|
||||
|
||||
def check_license(file_name, lines):
|
||||
if any(file_name.endswith(ext) for ext in (".toml", ".lock", ".json", ".html")) or \
|
||||
config["skip-check-licenses"]:
|
||||
if any(file_name.endswith(ext) for ext in (".toml", ".lock", ".json", ".html")) or config["skip-check-licenses"]:
|
||||
return
|
||||
|
||||
if lines[0].startswith(b"#!") and lines[1].strip():
|
||||
|
@ -238,7 +250,7 @@ def check_license(file_name, lines):
|
|||
license_block = []
|
||||
|
||||
for line in lines:
|
||||
line = line.rstrip(b'\n')
|
||||
line = line.rstrip(b"\n")
|
||||
if not line.strip():
|
||||
blank_lines += 1
|
||||
if blank_lines >= max_blank_lines:
|
||||
|
@ -257,20 +269,19 @@ def check_license(file_name, lines):
|
|||
|
||||
def check_modeline(file_name, lines):
|
||||
for idx, line in enumerate(lines[:5]):
|
||||
if re.search(b'^.*[ \t](vi:|vim:|ex:)[ \t]', line):
|
||||
if re.search(b"^.*[ \t](vi:|vim:|ex:)[ \t]", line):
|
||||
yield (idx + 1, "vi modeline present")
|
||||
elif re.search(br'-\*-.*-\*-', line, re.IGNORECASE):
|
||||
elif re.search(rb"-\*-.*-\*-", line, re.IGNORECASE):
|
||||
yield (idx + 1, "emacs file variables present")
|
||||
|
||||
|
||||
def check_length(file_name, idx, line):
|
||||
if any(file_name.endswith(ext) for ext in (".lock", ".json", ".html", ".toml")) or \
|
||||
config["skip-check-length"]:
|
||||
if any(file_name.endswith(ext) for ext in (".lock", ".json", ".html", ".toml")) or config["skip-check-length"]:
|
||||
return
|
||||
|
||||
# Prefer shorter lines when shell scripting.
|
||||
max_length = 80 if file_name.endswith(".sh") else 120
|
||||
if len(line.rstrip(b'\n')) > max_length and not is_unsplittable(file_name, line):
|
||||
if len(line.rstrip(b"\n")) > max_length and not is_unsplittable(file_name, line):
|
||||
yield (idx + 1, "Line is longer than %d characters" % max_length)
|
||||
|
||||
|
||||
|
@ -279,23 +290,18 @@ def contains_url(line):
|
|||
|
||||
|
||||
def is_unsplittable(file_name, line):
|
||||
return (
|
||||
contains_url(line)
|
||||
or file_name.endswith(".rs")
|
||||
and line.startswith(b"use ")
|
||||
and b"{" not in line
|
||||
)
|
||||
return contains_url(line) or file_name.endswith(".rs") and line.startswith(b"use ") and b"{" not in line
|
||||
|
||||
|
||||
def check_whatwg_specific_url(idx, line):
|
||||
match = re.search(br"https://html\.spec\.whatwg\.org/multipage/[\w-]+\.html#([\w\'\:-]+)", line)
|
||||
match = re.search(rb"https://html\.spec\.whatwg\.org/multipage/[\w-]+\.html#([\w\'\:-]+)", line)
|
||||
if match is not None:
|
||||
preferred_link = "https://html.spec.whatwg.org/multipage/#{}".format(match.group(1).decode("utf-8"))
|
||||
yield (idx + 1, "link to WHATWG may break in the future, use this format instead: {}".format(preferred_link))
|
||||
|
||||
|
||||
def check_whatwg_single_page_url(idx, line):
|
||||
match = re.search(br"https://html\.spec\.whatwg\.org/#([\w\'\:-]+)", line)
|
||||
match = re.search(rb"https://html\.spec\.whatwg\.org/#([\w\'\:-]+)", line)
|
||||
if match is not None:
|
||||
preferred_link = "https://html.spec.whatwg.org/multipage/#{}".format(match.group(1).decode("utf-8"))
|
||||
yield (idx + 1, "links to WHATWG single-page url, change to multi page: {}".format(preferred_link))
|
||||
|
@ -335,10 +341,11 @@ def check_for_raw_urls_in_rustdoc(file_name: str, idx: int, line: bytes):
|
|||
# [link text]: https://example.com
|
||||
match = URL_REGEX.search(line)
|
||||
if match and (
|
||||
not line[match.start() - 1:].startswith(b"<")
|
||||
and not line[match.start() - 1:].startswith(b"[")
|
||||
and not line[match.start() - 2:].startswith(b"](")
|
||||
and not line[match.start() - 3:].startswith(b"]: ")):
|
||||
not line[match.start() - 1 :].startswith(b"<")
|
||||
and not line[match.start() - 1 :].startswith(b"[")
|
||||
and not line[match.start() - 2 :].startswith(b"](")
|
||||
and not line[match.start() - 3 :].startswith(b"]: ")
|
||||
):
|
||||
yield (idx + 1, ERROR_RAW_URL_IN_RUSTDOC)
|
||||
|
||||
|
||||
|
@ -369,12 +376,11 @@ def check_ruff_lints():
|
|||
)
|
||||
|
||||
|
||||
|
||||
def run_cargo_deny_lints():
|
||||
print("\r ➤ Running `cargo-deny` checks...")
|
||||
result = subprocess.run(["cargo-deny", "--format=json", "--all-features", "check"],
|
||||
encoding='utf-8',
|
||||
capture_output=True)
|
||||
result = subprocess.run(
|
||||
["cargo-deny", "--format=json", "--all-features", "check"], encoding="utf-8", capture_output=True
|
||||
)
|
||||
assert result.stderr is not None, "cargo deny should return error information via stderr when failing"
|
||||
|
||||
errors = []
|
||||
|
@ -397,11 +403,7 @@ def run_cargo_deny_lints():
|
|||
if error_code == "rejected":
|
||||
crate = CargoDenyKrate(error_fields["graphs"][0])
|
||||
license_name = error_fields["notes"][0]
|
||||
errors.append((
|
||||
CARGO_LOCK_FILE,
|
||||
1,
|
||||
f"Rust dependency {crate}: Rejected license \"{license_name}\""
|
||||
))
|
||||
errors.append((CARGO_LOCK_FILE, 1, f'Rust dependency {crate}: Rejected license "{license_name}"'))
|
||||
# This detects if a crate has been marked as banned in the configuration file.
|
||||
elif error_code == "banned":
|
||||
crate = CargoDenyKrate(error_fields["graphs"][0])
|
||||
|
@ -431,7 +433,7 @@ def check_toml(file_name, lines):
|
|||
if line_without_comment.find("*") != -1:
|
||||
yield (idx + 1, "found asterisk instead of minimum version number")
|
||||
for license_line in licenses_toml:
|
||||
ok_licensed |= (license_line in line)
|
||||
ok_licensed |= license_line in line
|
||||
if "license.workspace" in line:
|
||||
ok_licensed = True
|
||||
if not ok_licensed:
|
||||
|
@ -448,7 +450,7 @@ def check_shell(file_name, lines):
|
|||
did_shebang_check = False
|
||||
|
||||
if not lines:
|
||||
yield (0, 'script is an empty file')
|
||||
yield (0, "script is an empty file")
|
||||
return
|
||||
|
||||
if lines[0].rstrip() != shebang.encode("utf-8"):
|
||||
|
@ -477,23 +479,25 @@ def check_shell(file_name, lines):
|
|||
if " [ " in stripped or stripped.startswith("[ "):
|
||||
yield (idx + 1, "script should use `[[` instead of `[` for conditional testing")
|
||||
|
||||
for dollar in re.finditer(r'\$', stripped):
|
||||
for dollar in re.finditer(r"\$", stripped):
|
||||
next_idx = dollar.end()
|
||||
if next_idx < len(stripped):
|
||||
next_char = stripped[next_idx]
|
||||
if not (next_char == '{' or next_char == '('):
|
||||
yield (idx + 1, "variable substitutions should use the full \"${VAR}\" form")
|
||||
if not (next_char == "{" or next_char == "("):
|
||||
yield (idx + 1, 'variable substitutions should use the full "${VAR}" form')
|
||||
|
||||
|
||||
def check_rust(file_name, lines):
|
||||
if not file_name.endswith(".rs") or \
|
||||
file_name.endswith(".mako.rs") or \
|
||||
file_name.endswith(os.path.join("style", "build.rs")) or \
|
||||
file_name.endswith(os.path.join("unit", "style", "stylesheets.rs")):
|
||||
if (
|
||||
not file_name.endswith(".rs")
|
||||
or file_name.endswith(".mako.rs")
|
||||
or file_name.endswith(os.path.join("style", "build.rs"))
|
||||
or file_name.endswith(os.path.join("unit", "style", "stylesheets.rs"))
|
||||
):
|
||||
return
|
||||
|
||||
comment_depth = 0
|
||||
merged_lines = ''
|
||||
merged_lines = ""
|
||||
import_block = False
|
||||
whitespace = False
|
||||
|
||||
|
@ -507,8 +511,7 @@ def check_rust(file_name, lines):
|
|||
os.path.join("*", "ports", "servoshell", "embedder.rs"),
|
||||
os.path.join("*", "rust_tidy.rs"), # This is for the tests.
|
||||
]
|
||||
is_panic_not_allowed_rs_file = any([
|
||||
glob.fnmatch.fnmatch(file_name, path) for path in PANIC_NOT_ALLOWED_PATHS])
|
||||
is_panic_not_allowed_rs_file = any([glob.fnmatch.fnmatch(file_name, path) for path in PANIC_NOT_ALLOWED_PATHS])
|
||||
|
||||
prev_open_brace = False
|
||||
multi_line_string = False
|
||||
|
@ -531,11 +534,11 @@ def check_rust(file_name, lines):
|
|||
is_comment = re.search(r"^//|^/\*|^\*", line)
|
||||
|
||||
# Simple heuristic to avoid common case of no comments.
|
||||
if '/' in line:
|
||||
comment_depth += line.count('/*')
|
||||
comment_depth -= line.count('*/')
|
||||
if "/" in line:
|
||||
comment_depth += line.count("/*")
|
||||
comment_depth -= line.count("*/")
|
||||
|
||||
if line.endswith('\\'):
|
||||
if line.endswith("\\"):
|
||||
merged_lines += line[:-1]
|
||||
continue
|
||||
if comment_depth:
|
||||
|
@ -543,11 +546,10 @@ def check_rust(file_name, lines):
|
|||
continue
|
||||
if merged_lines:
|
||||
line = merged_lines + line
|
||||
merged_lines = ''
|
||||
merged_lines = ""
|
||||
|
||||
if multi_line_string:
|
||||
line, count = re.subn(
|
||||
r'^(\\.|[^"\\])*?"', '', line, count=1)
|
||||
line, count = re.subn(r'^(\\.|[^"\\])*?"', "", line, count=1)
|
||||
if count == 1:
|
||||
multi_line_string = False
|
||||
else:
|
||||
|
@ -565,9 +567,7 @@ def check_rust(file_name, lines):
|
|||
# get rid of strings and chars because cases like regex expression, keep attributes
|
||||
if not is_attribute and not is_comment:
|
||||
line = re.sub(r'"(\\.|[^\\"])*?"', '""', line)
|
||||
line = re.sub(
|
||||
r"'(\\.|[^\\']|(\\x[0-9a-fA-F]{2})|(\\u{[0-9a-fA-F]{1,6}}))'",
|
||||
"''", line)
|
||||
line = re.sub(r"'(\\.|[^\\']|(\\x[0-9a-fA-F]{2})|(\\u{[0-9a-fA-F]{1,6}}))'", "''", line)
|
||||
# If, after parsing all single-line strings, we still have
|
||||
# an odd number of double quotes, this line starts a
|
||||
# multiline string
|
||||
|
@ -576,15 +576,16 @@ def check_rust(file_name, lines):
|
|||
multi_line_string = True
|
||||
|
||||
# get rid of comments
|
||||
line = re.sub(r'//.*?$|/\*.*?$|^\*.*?$', '//', line)
|
||||
line = re.sub(r"//.*?$|/\*.*?$|^\*.*?$", "//", line)
|
||||
|
||||
# get rid of attributes that do not contain =
|
||||
line = re.sub(r'^#[A-Za-z0-9\(\)\[\]_]*?$', '#[]', line)
|
||||
line = re.sub(r"^#[A-Za-z0-9\(\)\[\]_]*?$", "#[]", line)
|
||||
|
||||
# flag this line if it matches one of the following regular expressions
|
||||
# tuple format: (pattern, format_message, filter_function(match, line))
|
||||
def no_filter(match, line):
|
||||
return True
|
||||
|
||||
regex_rules = [
|
||||
# There should not be any extra pointer dereferencing
|
||||
(r": &Vec<", "use &[T] instead of &Vec<T>", no_filter),
|
||||
|
@ -618,17 +619,23 @@ def check_rust(file_name, lines):
|
|||
match = re.search(r"#!\[feature\((.*)\)\]", line)
|
||||
|
||||
if match:
|
||||
features = list(map(lambda w: w.strip(), match.group(1).split(',')))
|
||||
features = list(map(lambda w: w.strip(), match.group(1).split(",")))
|
||||
sorted_features = sorted(features)
|
||||
if sorted_features != features and check_alphabetical_order:
|
||||
yield (idx + 1, decl_message.format("feature attribute")
|
||||
+ decl_expected.format(tuple(sorted_features))
|
||||
+ decl_found.format(tuple(features)))
|
||||
yield (
|
||||
idx + 1,
|
||||
decl_message.format("feature attribute")
|
||||
+ decl_expected.format(tuple(sorted_features))
|
||||
+ decl_found.format(tuple(features)),
|
||||
)
|
||||
|
||||
if prev_feature_name > sorted_features[0] and check_alphabetical_order:
|
||||
yield (idx + 1, decl_message.format("feature attribute")
|
||||
+ decl_expected.format(prev_feature_name + " after " + sorted_features[0])
|
||||
+ decl_found.format(prev_feature_name + " before " + sorted_features[0]))
|
||||
yield (
|
||||
idx + 1,
|
||||
decl_message.format("feature attribute")
|
||||
+ decl_expected.format(prev_feature_name + " after " + sorted_features[0])
|
||||
+ decl_found.format(prev_feature_name + " before " + sorted_features[0]),
|
||||
)
|
||||
|
||||
prev_feature_name = sorted_features[0]
|
||||
else:
|
||||
|
@ -652,9 +659,12 @@ def check_rust(file_name, lines):
|
|||
if match == -1 and not line.endswith(";"):
|
||||
yield (idx + 1, "mod declaration spans multiple lines")
|
||||
if prev_mod[indent] and mod < prev_mod[indent] and check_alphabetical_order:
|
||||
yield (idx + 1, decl_message.format("mod declaration")
|
||||
+ decl_expected.format(prev_mod[indent])
|
||||
+ decl_found.format(mod))
|
||||
yield (
|
||||
idx + 1,
|
||||
decl_message.format("mod declaration")
|
||||
+ decl_expected.format(prev_mod[indent])
|
||||
+ decl_found.format(mod),
|
||||
)
|
||||
prev_mod[indent] = mod
|
||||
else:
|
||||
# we now erase previous entries
|
||||
|
@ -665,21 +675,24 @@ def check_rust(file_name, lines):
|
|||
# match the derivable traits filtering out macro expansions
|
||||
match = re.search(r"#\[derive\(([a-zA-Z, ]*)", line)
|
||||
if match:
|
||||
derives = list(map(lambda w: w.strip(), match.group(1).split(',')))
|
||||
derives = list(map(lambda w: w.strip(), match.group(1).split(",")))
|
||||
# sort, compare and report
|
||||
sorted_derives = sorted(derives)
|
||||
if sorted_derives != derives and check_alphabetical_order:
|
||||
yield (idx + 1, decl_message.format("derivable traits list")
|
||||
+ decl_expected.format(", ".join(sorted_derives))
|
||||
+ decl_found.format(", ".join(derives)))
|
||||
yield (
|
||||
idx + 1,
|
||||
decl_message.format("derivable traits list")
|
||||
+ decl_expected.format(", ".join(sorted_derives))
|
||||
+ decl_found.format(", ".join(derives)),
|
||||
)
|
||||
|
||||
|
||||
# Avoid flagging <Item=Foo> constructs
|
||||
def is_associated_type(match, line):
|
||||
if match.group(1) != '=':
|
||||
if match.group(1) != "=":
|
||||
return False
|
||||
open_angle = line[0:match.end()].rfind('<')
|
||||
close_angle = line[open_angle:].find('>') if open_angle != -1 else -1
|
||||
open_angle = line[0 : match.end()].rfind("<")
|
||||
close_angle = line[open_angle:].find(">") if open_angle != -1 else -1
|
||||
generic_open = open_angle != -1 and open_angle < match.start()
|
||||
generic_close = close_angle != -1 and close_angle + open_angle >= match.end()
|
||||
return generic_open and generic_close
|
||||
|
@ -731,6 +744,7 @@ def check_that_manifests_exist():
|
|||
|
||||
def check_that_manifests_are_clean():
|
||||
from wptrunner import wptlogging
|
||||
|
||||
print("\r ➤ Checking WPT manifests for cleanliness...")
|
||||
output_stream = io.StringIO("")
|
||||
logger = wptlogging.setup({}, {"mach": output_stream})
|
||||
|
@ -822,8 +836,8 @@ def check_spec(file_name, lines):
|
|||
yield (idx + 1, "method declared in webidl is missing a comment with a specification link")
|
||||
break
|
||||
if in_impl:
|
||||
brace_count += line.count('{')
|
||||
brace_count -= line.count('}')
|
||||
brace_count += line.count("{")
|
||||
brace_count -= line.count("}")
|
||||
if brace_count < 1:
|
||||
break
|
||||
|
||||
|
@ -870,7 +884,7 @@ def check_config_file(config_file, print_text=True):
|
|||
# Print invalid listed ignored directories
|
||||
if current_table == "ignore" and invalid_dirs:
|
||||
for d in invalid_dirs:
|
||||
if line.strip().strip('\'",') == d:
|
||||
if line.strip().strip("'\",") == d:
|
||||
yield config_file, idx + 1, "ignored directory '%s' doesn't exist" % d
|
||||
invalid_dirs.remove(d)
|
||||
break
|
||||
|
@ -878,7 +892,7 @@ def check_config_file(config_file, print_text=True):
|
|||
# Print invalid listed ignored files
|
||||
if current_table == "ignore" and invalid_files:
|
||||
for f in invalid_files:
|
||||
if line.strip().strip('\'",') == f:
|
||||
if line.strip().strip("'\",") == f:
|
||||
yield config_file, idx + 1, "ignored file '%s' doesn't exist" % f
|
||||
invalid_files.remove(f)
|
||||
break
|
||||
|
@ -890,10 +904,14 @@ def check_config_file(config_file, print_text=True):
|
|||
key = line.split("=")[0].strip()
|
||||
|
||||
# Check for invalid keys inside [configs] and [ignore] table
|
||||
if (current_table == "configs" and key not in config
|
||||
or current_table == "ignore" and key not in config["ignore"]
|
||||
# Any key outside of tables
|
||||
or current_table == ""):
|
||||
if (
|
||||
current_table == "configs"
|
||||
and key not in config
|
||||
or current_table == "ignore"
|
||||
and key not in config["ignore"]
|
||||
# Any key outside of tables
|
||||
or current_table == ""
|
||||
):
|
||||
yield config_file, idx + 1, "invalid config key '%s'" % key
|
||||
|
||||
# Parse config file
|
||||
|
@ -914,7 +932,7 @@ def parse_config(config_file):
|
|||
dirs_to_check = config_file.get("check_ext", {})
|
||||
# Fix the paths (OS-dependent)
|
||||
for path, exts in dirs_to_check.items():
|
||||
config['check_ext'][normilize_paths(path)] = exts
|
||||
config["check_ext"][normilize_paths(path)] = exts
|
||||
|
||||
# Add list of blocked packages
|
||||
config["blocked-packages"] = config_file.get("blocked-packages", {})
|
||||
|
@ -933,13 +951,9 @@ def check_directory_files(directories, print_text=True):
|
|||
files = sorted(os.listdir(directory))
|
||||
for filename in files:
|
||||
if not any(filename.endswith(ext) for ext in file_extensions):
|
||||
details = {
|
||||
"name": os.path.basename(filename),
|
||||
"ext": ", ".join(file_extensions),
|
||||
"dir_name": directory
|
||||
}
|
||||
message = '''Unexpected extension found for {name}. \
|
||||
We only expect files with {ext} extensions in {dir_name}'''.format(**details)
|
||||
details = {"name": os.path.basename(filename), "ext": ", ".join(file_extensions), "dir_name": directory}
|
||||
message = """Unexpected extension found for {name}. \
|
||||
We only expect files with {ext} extensions in {dir_name}""".format(**details)
|
||||
yield (filename, 1, message)
|
||||
|
||||
|
||||
|
@ -972,12 +986,19 @@ def scan(only_changed_files=False, progress=False):
|
|||
# check config file for errors
|
||||
config_errors = check_config_file(CONFIG_FILE_PATH)
|
||||
# check directories contain expected files
|
||||
directory_errors = check_directory_files(config['check_ext'])
|
||||
directory_errors = check_directory_files(config["check_ext"])
|
||||
# standard checks
|
||||
files_to_check = filter_files('.', only_changed_files, progress)
|
||||
files_to_check = filter_files(".", only_changed_files, progress)
|
||||
checking_functions = (check_webidl_spec,)
|
||||
line_checking_functions = (check_license, check_by_line, check_toml, check_shell,
|
||||
check_rust, check_spec, check_modeline)
|
||||
line_checking_functions = (
|
||||
check_license,
|
||||
check_by_line,
|
||||
check_toml,
|
||||
check_shell,
|
||||
check_rust,
|
||||
check_spec,
|
||||
check_modeline,
|
||||
)
|
||||
file_errors = collect_errors_for_files(files_to_check, checking_functions, line_checking_functions)
|
||||
|
||||
python_errors = check_ruff_lints()
|
||||
|
@ -985,26 +1006,27 @@ def scan(only_changed_files=False, progress=False):
|
|||
wpt_errors = run_wpt_lints(only_changed_files)
|
||||
|
||||
# chain all the iterators
|
||||
errors = itertools.chain(config_errors, directory_errors, file_errors,
|
||||
python_errors, wpt_errors, cargo_lock_errors)
|
||||
errors = itertools.chain(config_errors, directory_errors, file_errors, python_errors, wpt_errors, cargo_lock_errors)
|
||||
|
||||
colorama.init()
|
||||
error = None
|
||||
for error in errors:
|
||||
print("\r | "
|
||||
+ f"{colorama.Fore.BLUE}{error[0]}{colorama.Style.RESET_ALL}:"
|
||||
+ f"{colorama.Fore.YELLOW}{error[1]}{colorama.Style.RESET_ALL}: "
|
||||
+ f"{colorama.Fore.RED}{error[2]}{colorama.Style.RESET_ALL}")
|
||||
print(
|
||||
"\r | "
|
||||
+ f"{colorama.Fore.BLUE}{error[0]}{colorama.Style.RESET_ALL}:"
|
||||
+ f"{colorama.Fore.YELLOW}{error[1]}{colorama.Style.RESET_ALL}: "
|
||||
+ f"{colorama.Fore.RED}{error[2]}{colorama.Style.RESET_ALL}"
|
||||
)
|
||||
|
||||
return int(error is not None)
|
||||
|
||||
|
||||
class CargoDenyKrate:
|
||||
def __init__(self, data: Dict[Any, Any]):
|
||||
crate = data['Krate']
|
||||
self.name = crate['name']
|
||||
self.version = crate['version']
|
||||
self.parents = [CargoDenyKrate(parent) for parent in data.get('parents', [])]
|
||||
crate = data["Krate"]
|
||||
self.name = crate["name"]
|
||||
self.version = crate["version"]
|
||||
self.parents = [CargoDenyKrate(parent) for parent in data.get("parents", [])]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}@{self.version}"
|
||||
|
|
|
@ -27,22 +27,36 @@ import wptrunner.wptcommandline # noqa: E402
|
|||
|
||||
def create_parser():
|
||||
parser = wptrunner.wptcommandline.create_parser()
|
||||
parser.add_argument('--rr-chaos', default=False, action="store_true",
|
||||
help="Run under chaos mode in rr until a failure is captured")
|
||||
parser.add_argument('--pref', default=[], action="append", dest="prefs",
|
||||
help="Pass preferences to servo")
|
||||
parser.add_argument('--log-servojson', action="append", type=mozlog.commandline.log_file,
|
||||
help="Servo's JSON logger of unexpected results")
|
||||
parser.add_argument('--always-succeed', default=False, action="store_true",
|
||||
help="Always yield exit code of zero")
|
||||
parser.add_argument('--no-default-test-types', default=False, action="store_true",
|
||||
help="Run all of the test types provided by wptrunner or specified explicitly by --test-types")
|
||||
parser.add_argument('--filter-intermittents', default=None, action="store",
|
||||
help="Filter intermittents against known intermittents "
|
||||
"and save the filtered output to the given file.")
|
||||
parser.add_argument('--log-raw-unexpected', default=None, action="store",
|
||||
help="Raw structured log messages for unexpected results."
|
||||
" '--log-raw' Must also be passed in order to use this.")
|
||||
parser.add_argument(
|
||||
"--rr-chaos", default=False, action="store_true", help="Run under chaos mode in rr until a failure is captured"
|
||||
)
|
||||
parser.add_argument("--pref", default=[], action="append", dest="prefs", help="Pass preferences to servo")
|
||||
parser.add_argument(
|
||||
"--log-servojson",
|
||||
action="append",
|
||||
type=mozlog.commandline.log_file,
|
||||
help="Servo's JSON logger of unexpected results",
|
||||
)
|
||||
parser.add_argument("--always-succeed", default=False, action="store_true", help="Always yield exit code of zero")
|
||||
parser.add_argument(
|
||||
"--no-default-test-types",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Run all of the test types provided by wptrunner or specified explicitly by --test-types",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--filter-intermittents",
|
||||
default=None,
|
||||
action="store",
|
||||
help="Filter intermittents against known intermittents and save the filtered output to the given file.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--log-raw-unexpected",
|
||||
default=None,
|
||||
action="store",
|
||||
help="Raw structured log messages for unexpected results."
|
||||
" '--log-raw' Must also be passed in order to use this.",
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
|
|
|
@ -21,20 +21,20 @@ from exporter import WPTSync
|
|||
|
||||
|
||||
def main() -> int:
|
||||
context = json.loads(os.environ['GITHUB_CONTEXT'])
|
||||
context = json.loads(os.environ["GITHUB_CONTEXT"])
|
||||
logging.getLogger().level = logging.INFO
|
||||
|
||||
success = WPTSync(
|
||||
servo_repo='servo/servo',
|
||||
wpt_repo='web-platform-tests/wpt',
|
||||
downstream_wpt_repo='servo/wpt',
|
||||
servo_path='./servo',
|
||||
wpt_path='./wpt',
|
||||
github_api_token=os.environ['WPT_SYNC_TOKEN'],
|
||||
github_api_url='https://api.github.com/',
|
||||
github_username='servo-wpt-sync',
|
||||
github_email='ghbot+wpt-sync@servo.org',
|
||||
github_name='Servo WPT Sync',
|
||||
servo_repo="servo/servo",
|
||||
wpt_repo="web-platform-tests/wpt",
|
||||
downstream_wpt_repo="servo/wpt",
|
||||
servo_path="./servo",
|
||||
wpt_path="./wpt",
|
||||
github_api_token=os.environ["WPT_SYNC_TOKEN"],
|
||||
github_api_url="https://api.github.com/",
|
||||
github_username="servo-wpt-sync",
|
||||
github_email="ghbot+wpt-sync@servo.org",
|
||||
github_name="Servo WPT Sync",
|
||||
).run(context["event"])
|
||||
return 0 if success else 1
|
||||
|
||||
|
|
|
@ -24,26 +24,28 @@ import subprocess
|
|||
|
||||
from typing import Callable, Optional
|
||||
|
||||
from .common import \
|
||||
CLOSING_EXISTING_UPSTREAM_PR, \
|
||||
NO_SYNC_SIGNAL, \
|
||||
NO_UPSTREAMBLE_CHANGES_COMMENT, \
|
||||
OPENED_NEW_UPSTREAM_PR, \
|
||||
UPDATED_EXISTING_UPSTREAM_PR, \
|
||||
UPDATED_TITLE_IN_EXISTING_UPSTREAM_PR, \
|
||||
UPSTREAMABLE_PATH, \
|
||||
wpt_branch_name_from_servo_pr_number
|
||||
from .common import (
|
||||
CLOSING_EXISTING_UPSTREAM_PR,
|
||||
NO_SYNC_SIGNAL,
|
||||
NO_UPSTREAMBLE_CHANGES_COMMENT,
|
||||
OPENED_NEW_UPSTREAM_PR,
|
||||
UPDATED_EXISTING_UPSTREAM_PR,
|
||||
UPDATED_TITLE_IN_EXISTING_UPSTREAM_PR,
|
||||
UPSTREAMABLE_PATH,
|
||||
wpt_branch_name_from_servo_pr_number,
|
||||
)
|
||||
|
||||
from .github import GithubRepository, PullRequest
|
||||
from .step import \
|
||||
AsyncValue, \
|
||||
ChangePRStep, \
|
||||
CommentStep, \
|
||||
CreateOrUpdateBranchForPRStep, \
|
||||
MergePRStep, \
|
||||
OpenPRStep, \
|
||||
RemoveBranchForPRStep, \
|
||||
Step
|
||||
from .step import (
|
||||
AsyncValue,
|
||||
ChangePRStep,
|
||||
CommentStep,
|
||||
CreateOrUpdateBranchForPRStep,
|
||||
MergePRStep,
|
||||
OpenPRStep,
|
||||
RemoveBranchForPRStep,
|
||||
Step,
|
||||
)
|
||||
|
||||
|
||||
class LocalGitRepo:
|
||||
|
@ -57,8 +59,7 @@ class LocalGitRepo:
|
|||
|
||||
def run_without_encoding(self, *args, env: dict = {}):
|
||||
command_line = [self.git_path] + list(args)
|
||||
logging.info(" → Execution (cwd='%s'): %s",
|
||||
self.path, " ".join(command_line))
|
||||
logging.info(" → Execution (cwd='%s'): %s", self.path, " ".join(command_line))
|
||||
|
||||
env.setdefault("GIT_AUTHOR_EMAIL", self.sync.github_email)
|
||||
env.setdefault("GIT_COMMITTER_EMAIL", self.sync.github_email)
|
||||
|
@ -66,20 +67,15 @@ class LocalGitRepo:
|
|||
env.setdefault("GIT_COMMITTER_NAME", self.sync.github_name)
|
||||
|
||||
try:
|
||||
return subprocess.check_output(
|
||||
command_line, cwd=self.path, env=env, stderr=subprocess.STDOUT
|
||||
)
|
||||
return subprocess.check_output(command_line, cwd=self.path, env=env, stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as exception:
|
||||
logging.warning("Process execution failed with output:\n%s",
|
||||
exception.output.decode("utf-8", errors="surrogateescape"))
|
||||
logging.warning(
|
||||
"Process execution failed with output:\n%s", exception.output.decode("utf-8", errors="surrogateescape")
|
||||
)
|
||||
raise exception
|
||||
|
||||
def run(self, *args, env: dict = {}):
|
||||
return (
|
||||
self
|
||||
.run_without_encoding(*args, env=env)
|
||||
.decode("utf-8", errors="surrogateescape")
|
||||
)
|
||||
return self.run_without_encoding(*args, env=env).decode("utf-8", errors="surrogateescape")
|
||||
|
||||
|
||||
@dataclasses.dataclass()
|
||||
|
@ -167,11 +163,7 @@ class WPTSync:
|
|||
if action not in ["opened", "synchronize", "reopened", "edited", "closed"]:
|
||||
return True
|
||||
|
||||
if (
|
||||
action == "edited"
|
||||
and "title" not in payload["changes"]
|
||||
and "body" not in payload["changes"]
|
||||
):
|
||||
if action == "edited" and "title" not in payload["changes"] and "body" not in payload["changes"]:
|
||||
return True
|
||||
|
||||
try:
|
||||
|
@ -179,15 +171,11 @@ class WPTSync:
|
|||
downstream_wpt_branch = self.downstream_wpt.get_branch(
|
||||
wpt_branch_name_from_servo_pr_number(servo_pr.number)
|
||||
)
|
||||
upstream_pr = self.wpt.get_open_pull_request_for_branch(
|
||||
self.github_username, downstream_wpt_branch
|
||||
)
|
||||
upstream_pr = self.wpt.get_open_pull_request_for_branch(self.github_username, downstream_wpt_branch)
|
||||
if upstream_pr:
|
||||
logging.info(
|
||||
" → Detected existing upstream PR %s", upstream_pr)
|
||||
logging.info(" → Detected existing upstream PR %s", upstream_pr)
|
||||
|
||||
run = SyncRun(self, servo_pr, AsyncValue(
|
||||
upstream_pr), step_callback)
|
||||
run = SyncRun(self, servo_pr, AsyncValue(upstream_pr), step_callback)
|
||||
|
||||
pull_data = payload["pull_request"]
|
||||
if payload["action"] in ["opened", "synchronize", "reopened"]:
|
||||
|
@ -210,50 +198,44 @@ class WPTSync:
|
|||
num_commits = pull_data["commits"]
|
||||
head_sha = pull_data["head"]["sha"]
|
||||
is_upstreamable = (
|
||||
len(
|
||||
self.local_servo_repo.run(
|
||||
"diff", head_sha, f"{head_sha}~{num_commits}", "--", UPSTREAMABLE_PATH
|
||||
)
|
||||
)
|
||||
> 0
|
||||
len(self.local_servo_repo.run("diff", head_sha, f"{head_sha}~{num_commits}", "--", UPSTREAMABLE_PATH)) > 0
|
||||
)
|
||||
logging.info(" → PR is upstreamable: '%s'", is_upstreamable)
|
||||
|
||||
title = pull_data['title']
|
||||
body = pull_data['body']
|
||||
title = pull_data["title"]
|
||||
body = pull_data["body"]
|
||||
if run.upstream_pr.has_value():
|
||||
if is_upstreamable:
|
||||
# In case this is adding new upstreamable changes to a PR that was closed
|
||||
# due to a lack of upstreamable changes, force it to be reopened.
|
||||
# Github refuses to reopen a PR that had a branch force pushed, so be sure
|
||||
# to do this first.
|
||||
run.add_step(ChangePRStep(
|
||||
run.upstream_pr.value(), "opened", title, body))
|
||||
run.add_step(ChangePRStep(run.upstream_pr.value(), "opened", title, body))
|
||||
# Push the relevant changes to the upstream branch.
|
||||
run.add_step(CreateOrUpdateBranchForPRStep(
|
||||
pull_data, run.servo_pr))
|
||||
run.add_step(CommentStep(
|
||||
run.servo_pr, UPDATED_EXISTING_UPSTREAM_PR))
|
||||
run.add_step(CreateOrUpdateBranchForPRStep(pull_data, run.servo_pr))
|
||||
run.add_step(CommentStep(run.servo_pr, UPDATED_EXISTING_UPSTREAM_PR))
|
||||
else:
|
||||
# Close the upstream PR, since would contain no changes otherwise.
|
||||
run.add_step(CommentStep(run.upstream_pr.value(),
|
||||
NO_UPSTREAMBLE_CHANGES_COMMENT))
|
||||
run.add_step(CommentStep(run.upstream_pr.value(), NO_UPSTREAMBLE_CHANGES_COMMENT))
|
||||
run.add_step(ChangePRStep(run.upstream_pr.value(), "closed"))
|
||||
run.add_step(RemoveBranchForPRStep(pull_data))
|
||||
run.add_step(CommentStep(
|
||||
run.servo_pr, CLOSING_EXISTING_UPSTREAM_PR))
|
||||
run.add_step(CommentStep(run.servo_pr, CLOSING_EXISTING_UPSTREAM_PR))
|
||||
|
||||
elif is_upstreamable:
|
||||
# Push the relevant changes to a new upstream branch.
|
||||
branch = run.add_step(
|
||||
CreateOrUpdateBranchForPRStep(pull_data, run.servo_pr))
|
||||
branch = run.add_step(CreateOrUpdateBranchForPRStep(pull_data, run.servo_pr))
|
||||
|
||||
# Create a pull request against the upstream repository for the new branch.
|
||||
assert branch
|
||||
upstream_pr = run.add_step(OpenPRStep(
|
||||
branch, self.wpt, title, body,
|
||||
["servo-export", "do not merge yet"],
|
||||
))
|
||||
upstream_pr = run.add_step(
|
||||
OpenPRStep(
|
||||
branch,
|
||||
self.wpt,
|
||||
title,
|
||||
body,
|
||||
["servo-export", "do not merge yet"],
|
||||
)
|
||||
)
|
||||
|
||||
assert upstream_pr
|
||||
run.upstream_pr = upstream_pr
|
||||
|
@ -264,12 +246,8 @@ class WPTSync:
|
|||
def handle_edited_pull_request(self, run: SyncRun, pull_data: dict):
|
||||
logging.info("Changing upstream PR title")
|
||||
if run.upstream_pr.has_value():
|
||||
run.add_step(ChangePRStep(
|
||||
run.upstream_pr.value(
|
||||
), "open", pull_data["title"], pull_data["body"]
|
||||
))
|
||||
run.add_step(CommentStep(
|
||||
run.servo_pr, UPDATED_TITLE_IN_EXISTING_UPSTREAM_PR))
|
||||
run.add_step(ChangePRStep(run.upstream_pr.value(), "open", pull_data["title"], pull_data["body"]))
|
||||
run.add_step(CommentStep(run.servo_pr, UPDATED_TITLE_IN_EXISTING_UPSTREAM_PR))
|
||||
|
||||
def handle_closed_pull_request(self, run: SyncRun, pull_data: dict):
|
||||
logging.info("Processing closed PR")
|
||||
|
@ -279,8 +257,7 @@ class WPTSync:
|
|||
if pull_data["merged"]:
|
||||
# Since the upstreamable changes have now been merged locally, merge the
|
||||
# corresponding upstream PR.
|
||||
run.add_step(MergePRStep(
|
||||
run.upstream_pr.value(), ["do not merge yet"]))
|
||||
run.add_step(MergePRStep(run.upstream_pr.value(), ["do not merge yet"]))
|
||||
else:
|
||||
# If a PR with upstreamable changes is closed without being merged, we
|
||||
# don't want to merge the changes upstream either.
|
||||
|
|
|
@ -12,17 +12,11 @@
|
|||
UPSTREAMABLE_PATH = "tests/wpt/tests/"
|
||||
NO_SYNC_SIGNAL = "[no-wpt-sync]"
|
||||
|
||||
OPENED_NEW_UPSTREAM_PR = (
|
||||
"🤖 Opened new upstream WPT pull request ({upstream_pr}) "
|
||||
"with upstreamable changes."
|
||||
)
|
||||
OPENED_NEW_UPSTREAM_PR = "🤖 Opened new upstream WPT pull request ({upstream_pr}) with upstreamable changes."
|
||||
UPDATED_EXISTING_UPSTREAM_PR = (
|
||||
"📝 Transplanted new upstreamable changes to existing "
|
||||
"upstream WPT pull request ({upstream_pr})."
|
||||
)
|
||||
UPDATED_TITLE_IN_EXISTING_UPSTREAM_PR = (
|
||||
"✍ Updated existing upstream WPT pull request ({upstream_pr}) title and body."
|
||||
"📝 Transplanted new upstreamable changes to existing upstream WPT pull request ({upstream_pr})."
|
||||
)
|
||||
UPDATED_TITLE_IN_EXISTING_UPSTREAM_PR = "✍ Updated existing upstream WPT pull request ({upstream_pr}) title and body."
|
||||
CLOSING_EXISTING_UPSTREAM_PR = (
|
||||
"🤖 This change no longer contains upstreamable changes to WPT; closed existing "
|
||||
"upstream pull request ({upstream_pr})."
|
||||
|
|
|
@ -40,13 +40,9 @@ def authenticated(sync: WPTSync, method, url, json=None) -> requests.Response:
|
|||
}
|
||||
|
||||
url = urllib.parse.urljoin(sync.github_api_url, url)
|
||||
response = requests.request(
|
||||
method, url, headers=headers, json=json, timeout=TIMEOUT
|
||||
)
|
||||
response = requests.request(method, url, headers=headers, json=json, timeout=TIMEOUT)
|
||||
if int(response.status_code / 100) != 2:
|
||||
raise ValueError(
|
||||
f"Got unexpected {response.status_code} response: {response.text}"
|
||||
)
|
||||
raise ValueError(f"Got unexpected {response.status_code} response: {response.text}")
|
||||
return response
|
||||
|
||||
|
||||
|
@ -71,33 +67,27 @@ class GithubRepository:
|
|||
def get_branch(self, name: str) -> GithubBranch:
|
||||
return GithubBranch(self, name)
|
||||
|
||||
def get_open_pull_request_for_branch(
|
||||
self,
|
||||
github_username: str,
|
||||
branch: GithubBranch
|
||||
) -> Optional[PullRequest]:
|
||||
def get_open_pull_request_for_branch(self, github_username: str, branch: GithubBranch) -> Optional[PullRequest]:
|
||||
"""If this repository has an open pull request with the
|
||||
given source head reference targeting the main branch,
|
||||
return the first matching pull request, otherwise return None."""
|
||||
|
||||
params = "+".join([
|
||||
"is:pr",
|
||||
"state:open",
|
||||
f"repo:{self.repo}",
|
||||
f"author:{github_username}",
|
||||
f"head:{branch.name}",
|
||||
])
|
||||
params = "+".join(
|
||||
[
|
||||
"is:pr",
|
||||
"state:open",
|
||||
f"repo:{self.repo}",
|
||||
f"author:{github_username}",
|
||||
f"head:{branch.name}",
|
||||
]
|
||||
)
|
||||
response = authenticated(self.sync, "GET", f"search/issues?q={params}")
|
||||
if int(response.status_code / 100) != 2:
|
||||
return None
|
||||
|
||||
json = response.json()
|
||||
if not isinstance(json, dict) or \
|
||||
"total_count" not in json or \
|
||||
"items" not in json:
|
||||
raise ValueError(
|
||||
f"Got unexpected response from GitHub search: {response.text}"
|
||||
)
|
||||
if not isinstance(json, dict) or "total_count" not in json or "items" not in json:
|
||||
raise ValueError(f"Got unexpected response from GitHub search: {response.text}")
|
||||
|
||||
if json["total_count"] < 1:
|
||||
return None
|
||||
|
@ -152,9 +142,7 @@ class PullRequest:
|
|||
return authenticated(self.context, *args, **kwargs)
|
||||
|
||||
def leave_comment(self, comment: str):
|
||||
return self.api(
|
||||
"POST", f"{self.base_issues_url}/comments", json={"body": comment}
|
||||
)
|
||||
return self.api("POST", f"{self.base_issues_url}/comments", json={"body": comment})
|
||||
|
||||
def change(
|
||||
self,
|
||||
|
|
|
@ -46,7 +46,7 @@ class Step:
|
|||
return
|
||||
|
||||
|
||||
T = TypeVar('T')
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class AsyncValue(Generic[T]):
|
||||
|
@ -76,8 +76,7 @@ class CreateOrUpdateBranchForPRStep(Step):
|
|||
|
||||
def run(self, run: SyncRun):
|
||||
try:
|
||||
commits = self._get_upstreamable_commits_from_local_servo_repo(
|
||||
run.sync)
|
||||
commits = self._get_upstreamable_commits_from_local_servo_repo(run.sync)
|
||||
branch_name = self._create_or_update_branch_for_pr(run, commits)
|
||||
branch = run.sync.downstream_wpt.get_branch(branch_name)
|
||||
|
||||
|
@ -88,21 +87,15 @@ class CreateOrUpdateBranchForPRStep(Step):
|
|||
logging.info(exception, exc_info=True)
|
||||
|
||||
run.steps = []
|
||||
run.add_step(CommentStep(
|
||||
self.pull_request, COULD_NOT_APPLY_CHANGES_DOWNSTREAM_COMMENT
|
||||
))
|
||||
run.add_step(CommentStep(self.pull_request, COULD_NOT_APPLY_CHANGES_DOWNSTREAM_COMMENT))
|
||||
if run.upstream_pr.has_value():
|
||||
run.add_step(CommentStep(
|
||||
run.upstream_pr.value(), COULD_NOT_APPLY_CHANGES_UPSTREAM_COMMENT
|
||||
))
|
||||
run.add_step(CommentStep(run.upstream_pr.value(), COULD_NOT_APPLY_CHANGES_UPSTREAM_COMMENT))
|
||||
|
||||
def _get_upstreamable_commits_from_local_servo_repo(self, sync: WPTSync):
|
||||
local_servo_repo = sync.local_servo_repo
|
||||
number_of_commits = self.pull_data["commits"]
|
||||
pr_head = self.pull_data["head"]["sha"]
|
||||
commit_shas = local_servo_repo.run(
|
||||
"log", "--pretty=%H", pr_head, f"-{number_of_commits}"
|
||||
).splitlines()
|
||||
commit_shas = local_servo_repo.run("log", "--pretty=%H", pr_head, f"-{number_of_commits}").splitlines()
|
||||
|
||||
filtered_commits = []
|
||||
# We must iterate the commits in reverse to ensure we apply older changes first,
|
||||
|
@ -128,12 +121,8 @@ class CreateOrUpdateBranchForPRStep(Step):
|
|||
# commit to another repository.
|
||||
filtered_commits += [
|
||||
{
|
||||
"author": local_servo_repo.run(
|
||||
"show", "-s", "--pretty=%an <%ae>", sha
|
||||
),
|
||||
"message": local_servo_repo.run(
|
||||
"show", "-s", "--pretty=%B", sha
|
||||
),
|
||||
"author": local_servo_repo.run("show", "-s", "--pretty=%an <%ae>", sha),
|
||||
"message": local_servo_repo.run("show", "-s", "--pretty=%B", sha),
|
||||
"diff": diff,
|
||||
}
|
||||
]
|
||||
|
@ -146,23 +135,16 @@ class CreateOrUpdateBranchForPRStep(Step):
|
|||
try:
|
||||
with open(patch_path, "wb") as file:
|
||||
file.write(commit["diff"])
|
||||
run.sync.local_wpt_repo.run(
|
||||
"apply", PATCH_FILE_NAME, "-p", str(strip_count)
|
||||
)
|
||||
run.sync.local_wpt_repo.run("apply", PATCH_FILE_NAME, "-p", str(strip_count))
|
||||
finally:
|
||||
# Ensure the patch file is not added with the other changes.
|
||||
os.remove(patch_path)
|
||||
|
||||
run.sync.local_wpt_repo.run("add", "--all")
|
||||
run.sync.local_wpt_repo.run(
|
||||
"commit", "--message", commit["message"], "--author", commit["author"]
|
||||
)
|
||||
run.sync.local_wpt_repo.run("commit", "--message", commit["message"], "--author", commit["author"])
|
||||
|
||||
def _create_or_update_branch_for_pr(
|
||||
self, run: SyncRun, commits: list[dict], pre_commit_callback=None
|
||||
):
|
||||
branch_name = wpt_branch_name_from_servo_pr_number(
|
||||
self.pull_data["number"])
|
||||
def _create_or_update_branch_for_pr(self, run: SyncRun, commits: list[dict], pre_commit_callback=None):
|
||||
branch_name = wpt_branch_name_from_servo_pr_number(self.pull_data["number"])
|
||||
try:
|
||||
# Create a new branch with a unique name that is consistent between
|
||||
# updates of the same PR.
|
||||
|
@ -176,7 +158,6 @@ class CreateOrUpdateBranchForPRStep(Step):
|
|||
|
||||
# Push the branch upstream (forcing to overwrite any existing changes).
|
||||
if not run.sync.suppress_force_push:
|
||||
|
||||
# In order to push to our downstream branch we need to ensure that
|
||||
# the local repository isn't a shallow clone. Shallow clones are
|
||||
# commonly created by GitHub actions.
|
||||
|
@ -186,8 +167,7 @@ class CreateOrUpdateBranchForPRStep(Step):
|
|||
token = run.sync.github_api_token
|
||||
repo = run.sync.downstream_wpt_repo
|
||||
remote_url = f"https://{user}:{token}@github.com/{repo}.git"
|
||||
run.sync.local_wpt_repo.run(
|
||||
"push", "-f", remote_url, branch_name)
|
||||
run.sync.local_wpt_repo.run("push", "-f", remote_url, branch_name)
|
||||
|
||||
return branch_name
|
||||
finally:
|
||||
|
@ -201,8 +181,7 @@ class CreateOrUpdateBranchForPRStep(Step):
|
|||
class RemoveBranchForPRStep(Step):
|
||||
def __init__(self, pull_request):
|
||||
Step.__init__(self, "RemoveBranchForPRStep")
|
||||
self.branch_name = wpt_branch_name_from_servo_pr_number(
|
||||
pull_request["number"])
|
||||
self.branch_name = wpt_branch_name_from_servo_pr_number(pull_request["number"])
|
||||
|
||||
def run(self, run: SyncRun):
|
||||
self.name += f":{run.sync.downstream_wpt.get_branch(self.branch_name)}"
|
||||
|
@ -212,8 +191,7 @@ class RemoveBranchForPRStep(Step):
|
|||
token = run.sync.github_api_token
|
||||
repo = run.sync.downstream_wpt_repo
|
||||
remote_url = f"https://{user}:{token}@github.com/{repo}.git"
|
||||
run.sync.local_wpt_repo.run("push", remote_url, "--delete",
|
||||
self.branch_name)
|
||||
run.sync.local_wpt_repo.run("push", remote_url, "--delete", self.branch_name)
|
||||
|
||||
|
||||
class ChangePRStep(Step):
|
||||
|
@ -238,9 +216,7 @@ class ChangePRStep(Step):
|
|||
body = self.body
|
||||
if body:
|
||||
body = run.prepare_body_text(body)
|
||||
self.name += (
|
||||
f':{textwrap.shorten(body, width=20, placeholder="...")}[{len(body)}]'
|
||||
)
|
||||
self.name += f":{textwrap.shorten(body, width=20, placeholder='...')}[{len(body)}]"
|
||||
|
||||
self.pull_request.change(state=self.state, title=self.title, body=body)
|
||||
|
||||
|
@ -261,12 +237,8 @@ class MergePRStep(Step):
|
|||
logging.warning(exception, exc_info=True)
|
||||
|
||||
run.steps = []
|
||||
run.add_step(CommentStep(
|
||||
self.pull_request, COULD_NOT_MERGE_CHANGES_UPSTREAM_COMMENT
|
||||
))
|
||||
run.add_step(CommentStep(
|
||||
run.servo_pr, COULD_NOT_MERGE_CHANGES_DOWNSTREAM_COMMENT
|
||||
))
|
||||
run.add_step(CommentStep(self.pull_request, COULD_NOT_MERGE_CHANGES_UPSTREAM_COMMENT))
|
||||
run.add_step(CommentStep(run.servo_pr, COULD_NOT_MERGE_CHANGES_DOWNSTREAM_COMMENT))
|
||||
self.pull_request.add_labels(["stale-servo-export"])
|
||||
|
||||
|
||||
|
|
|
@ -16,12 +16,12 @@ from dataclasses import dataclass, field
|
|||
from typing import Dict, List, Optional, Any
|
||||
from six import itervalues
|
||||
|
||||
DEFAULT_MOVE_UP_CODE = u"\x1b[A"
|
||||
DEFAULT_CLEAR_EOL_CODE = u"\x1b[K"
|
||||
DEFAULT_MOVE_UP_CODE = "\x1b[A"
|
||||
DEFAULT_CLEAR_EOL_CODE = "\x1b[K"
|
||||
|
||||
|
||||
@dataclass
|
||||
class UnexpectedSubtestResult():
|
||||
class UnexpectedSubtestResult:
|
||||
path: str
|
||||
subtest: str
|
||||
actual: str
|
||||
|
@ -32,15 +32,14 @@ class UnexpectedSubtestResult():
|
|||
|
||||
|
||||
@dataclass
|
||||
class UnexpectedResult():
|
||||
class UnexpectedResult:
|
||||
path: str
|
||||
actual: str
|
||||
expected: str
|
||||
message: str
|
||||
time: int
|
||||
stack: Optional[str]
|
||||
unexpected_subtest_results: list[UnexpectedSubtestResult] = field(
|
||||
default_factory=list)
|
||||
unexpected_subtest_results: list[UnexpectedSubtestResult] = field(default_factory=list)
|
||||
issues: list[str] = field(default_factory=list)
|
||||
flaky: bool = False
|
||||
|
||||
|
@ -48,13 +47,13 @@ class UnexpectedResult():
|
|||
output = UnexpectedResult.to_lines(self)
|
||||
|
||||
if self.unexpected_subtest_results:
|
||||
|
||||
def make_subtests_failure(subtest_results):
|
||||
# Test names sometimes contain control characters, which we want
|
||||
# to be printed in their raw form, and not their interpreted form.
|
||||
lines = []
|
||||
for subtest in subtest_results[:-1]:
|
||||
lines += UnexpectedResult.to_lines(
|
||||
subtest, print_stack=False)
|
||||
lines += UnexpectedResult.to_lines(subtest, print_stack=False)
|
||||
lines += UnexpectedResult.to_lines(subtest_results[-1])
|
||||
return self.wrap_and_indent_lines(lines, " ").splitlines()
|
||||
|
||||
|
@ -78,11 +77,11 @@ class UnexpectedResult():
|
|||
if not lines:
|
||||
return ""
|
||||
|
||||
output = indent + u"\u25B6 %s\n" % lines[0]
|
||||
output = indent + "\u25b6 %s\n" % lines[0]
|
||||
for line in lines[1:-1]:
|
||||
output += indent + u"\u2502 %s\n" % line
|
||||
output += indent + "\u2502 %s\n" % line
|
||||
if len(lines) > 1:
|
||||
output += indent + u"\u2514 %s\n" % lines[-1]
|
||||
output += indent + "\u2514 %s\n" % lines[-1]
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
|
@ -111,7 +110,8 @@ class UnexpectedResult():
|
|||
|
||||
class ServoHandler(mozlog.reader.LogHandler):
|
||||
"""LogHandler designed to collect unexpected results for use by
|
||||
script or by the ServoFormatter output formatter."""
|
||||
script or by the ServoFormatter output formatter."""
|
||||
|
||||
def __init__(self):
|
||||
self.reset_state()
|
||||
|
||||
|
@ -126,24 +126,24 @@ class ServoHandler(mozlog.reader.LogHandler):
|
|||
self.unexpected_results: List[UnexpectedResult] = []
|
||||
|
||||
self.expected = {
|
||||
'OK': 0,
|
||||
'PASS': 0,
|
||||
'FAIL': 0,
|
||||
'ERROR': 0,
|
||||
'TIMEOUT': 0,
|
||||
'SKIP': 0,
|
||||
'CRASH': 0,
|
||||
'PRECONDITION_FAILED': 0,
|
||||
"OK": 0,
|
||||
"PASS": 0,
|
||||
"FAIL": 0,
|
||||
"ERROR": 0,
|
||||
"TIMEOUT": 0,
|
||||
"SKIP": 0,
|
||||
"CRASH": 0,
|
||||
"PRECONDITION_FAILED": 0,
|
||||
}
|
||||
|
||||
self.unexpected_tests = {
|
||||
'OK': [],
|
||||
'PASS': [],
|
||||
'FAIL': [],
|
||||
'ERROR': [],
|
||||
'TIMEOUT': [],
|
||||
'CRASH': [],
|
||||
'PRECONDITION_FAILED': [],
|
||||
"OK": [],
|
||||
"PASS": [],
|
||||
"FAIL": [],
|
||||
"ERROR": [],
|
||||
"TIMEOUT": [],
|
||||
"CRASH": [],
|
||||
"PRECONDITION_FAILED": [],
|
||||
}
|
||||
|
||||
def suite_start(self, data):
|
||||
|
@ -155,20 +155,19 @@ class ServoHandler(mozlog.reader.LogHandler):
|
|||
pass
|
||||
|
||||
def test_start(self, data):
|
||||
self.running_tests[data['thread']] = data['test']
|
||||
self.running_tests[data["thread"]] = data["test"]
|
||||
|
||||
@staticmethod
|
||||
def data_was_for_expected_result(data):
|
||||
if "expected" not in data:
|
||||
return True
|
||||
return "known_intermittent" in data \
|
||||
and data["status"] in data["known_intermittent"]
|
||||
return "known_intermittent" in data and data["status"] in data["known_intermittent"]
|
||||
|
||||
def test_end(self, data: dict) -> Optional[UnexpectedResult]:
|
||||
self.completed_tests += 1
|
||||
test_status = data["status"]
|
||||
test_path = data["test"]
|
||||
del self.running_tests[data['thread']]
|
||||
del self.running_tests[data["thread"]]
|
||||
|
||||
had_expected_test_result = self.data_was_for_expected_result(data)
|
||||
subtest_failures = self.subtest_failures.pop(test_path, [])
|
||||
|
@ -191,7 +190,7 @@ class ServoHandler(mozlog.reader.LogHandler):
|
|||
data.get("message", ""),
|
||||
data["time"],
|
||||
stack,
|
||||
subtest_failures
|
||||
subtest_failures,
|
||||
)
|
||||
|
||||
if not had_expected_test_result:
|
||||
|
@ -205,19 +204,21 @@ class ServoHandler(mozlog.reader.LogHandler):
|
|||
def test_status(self, data: dict):
|
||||
if self.data_was_for_expected_result(data):
|
||||
return
|
||||
self.subtest_failures[data["test"]].append(UnexpectedSubtestResult(
|
||||
data["test"],
|
||||
data["subtest"],
|
||||
data["status"],
|
||||
data["expected"],
|
||||
data.get("message", ""),
|
||||
data["time"],
|
||||
data.get('stack', None),
|
||||
))
|
||||
self.subtest_failures[data["test"]].append(
|
||||
UnexpectedSubtestResult(
|
||||
data["test"],
|
||||
data["subtest"],
|
||||
data["status"],
|
||||
data["expected"],
|
||||
data.get("message", ""),
|
||||
data["time"],
|
||||
data.get("stack", None),
|
||||
)
|
||||
)
|
||||
|
||||
def process_output(self, data):
|
||||
if 'test' in data:
|
||||
self.test_output[data['test']] += data['data'] + "\n"
|
||||
if "test" in data:
|
||||
self.test_output[data["test"]] += data["data"] + "\n"
|
||||
|
||||
def log(self, _):
|
||||
pass
|
||||
|
@ -225,7 +226,8 @@ class ServoHandler(mozlog.reader.LogHandler):
|
|||
|
||||
class ServoFormatter(mozlog.formatters.base.BaseFormatter, ServoHandler):
|
||||
"""Formatter designed to produce unexpected test results grouped
|
||||
together in a readable format."""
|
||||
together in a readable format."""
|
||||
|
||||
def __init__(self):
|
||||
ServoHandler.__init__(self)
|
||||
self.current_display = ""
|
||||
|
@ -239,18 +241,17 @@ class ServoFormatter(mozlog.formatters.base.BaseFormatter, ServoHandler):
|
|||
|
||||
try:
|
||||
import blessed
|
||||
|
||||
self.terminal = blessed.Terminal()
|
||||
self.move_up = self.terminal.move_up
|
||||
self.clear_eol = self.terminal.clear_eol
|
||||
except Exception as exception:
|
||||
sys.stderr.write("GroupingFormatter: Could not get terminal "
|
||||
"control characters: %s\n" % exception)
|
||||
sys.stderr.write("GroupingFormatter: Could not get terminal control characters: %s\n" % exception)
|
||||
|
||||
def text_to_erase_display(self):
|
||||
if not self.interactive or not self.current_display:
|
||||
return ""
|
||||
return ((self.move_up + self.clear_eol)
|
||||
* self.current_display.count('\n'))
|
||||
return (self.move_up + self.clear_eol) * self.current_display.count("\n")
|
||||
|
||||
def generate_output(self, text=None, new_display=None):
|
||||
if not self.interactive:
|
||||
|
@ -278,17 +279,16 @@ class ServoFormatter(mozlog.formatters.base.BaseFormatter, ServoHandler):
|
|||
max_width = self.line_width - len(new_display)
|
||||
else:
|
||||
max_width = sys.maxsize
|
||||
return new_display + ("\n%s" % indent).join(
|
||||
val[:max_width] for val in self.running_tests.values()) + "\n"
|
||||
return new_display + ("\n%s" % indent).join(val[:max_width] for val in self.running_tests.values()) + "\n"
|
||||
else:
|
||||
return new_display + "No tests running.\n"
|
||||
|
||||
def suite_start(self, data):
|
||||
ServoHandler.suite_start(self, data)
|
||||
if self.number_of_tests == 0:
|
||||
return "Running tests in %s\n\n" % data[u'source']
|
||||
return "Running tests in %s\n\n" % data["source"]
|
||||
else:
|
||||
return "Running %i tests in %s\n\n" % (self.number_of_tests, data[u'source'])
|
||||
return "Running %i tests in %s\n\n" % (self.number_of_tests, data["source"])
|
||||
|
||||
def test_start(self, data):
|
||||
ServoHandler.test_start(self, data)
|
||||
|
@ -300,8 +300,7 @@ class ServoFormatter(mozlog.formatters.base.BaseFormatter, ServoHandler):
|
|||
if unexpected_result:
|
||||
# Surround test output by newlines so that it is easier to read.
|
||||
output_for_unexpected_test = f"{unexpected_result}\n"
|
||||
return self.generate_output(text=output_for_unexpected_test,
|
||||
new_display=self.build_status_line())
|
||||
return self.generate_output(text=output_for_unexpected_test, new_display=self.build_status_line())
|
||||
|
||||
# Print reason that tests are skipped.
|
||||
if data["status"] == "SKIP":
|
||||
|
@ -321,12 +320,14 @@ class ServoFormatter(mozlog.formatters.base.BaseFormatter, ServoHandler):
|
|||
def suite_end(self, data):
|
||||
ServoHandler.suite_end(self, data)
|
||||
if not self.interactive:
|
||||
output = u"\n"
|
||||
output = "\n"
|
||||
else:
|
||||
output = ""
|
||||
|
||||
output += u"Ran %i tests finished in %.1f seconds.\n" % (
|
||||
self.completed_tests, (data["time"] - self.suite_start_time) / 1000)
|
||||
output += "Ran %i tests finished in %.1f seconds.\n" % (
|
||||
self.completed_tests,
|
||||
(data["time"] - self.suite_start_time) / 1000,
|
||||
)
|
||||
|
||||
# Sum the number of expected test results from each category
|
||||
expected_test_results = sum(self.expected.values())
|
||||
|
@ -337,29 +338,27 @@ class ServoFormatter(mozlog.formatters.base.BaseFormatter, ServoHandler):
|
|||
def text_for_unexpected_list(text, section):
|
||||
tests = self.unexpected_tests[section]
|
||||
if not tests:
|
||||
return u""
|
||||
return u" \u2022 %i tests %s\n" % (len(tests), text)
|
||||
return ""
|
||||
return " \u2022 %i tests %s\n" % (len(tests), text)
|
||||
|
||||
output += text_for_unexpected_list(u"crashed unexpectedly", 'CRASH')
|
||||
output += text_for_unexpected_list(u"had errors unexpectedly", 'ERROR')
|
||||
output += text_for_unexpected_list(u"failed unexpectedly", 'FAIL')
|
||||
output += text_for_unexpected_list(u"precondition failed unexpectedly", 'PRECONDITION_FAILED')
|
||||
output += text_for_unexpected_list(u"timed out unexpectedly", 'TIMEOUT')
|
||||
output += text_for_unexpected_list(u"passed unexpectedly", 'PASS')
|
||||
output += text_for_unexpected_list(u"unexpectedly okay", 'OK')
|
||||
output += text_for_unexpected_list("crashed unexpectedly", "CRASH")
|
||||
output += text_for_unexpected_list("had errors unexpectedly", "ERROR")
|
||||
output += text_for_unexpected_list("failed unexpectedly", "FAIL")
|
||||
output += text_for_unexpected_list("precondition failed unexpectedly", "PRECONDITION_FAILED")
|
||||
output += text_for_unexpected_list("timed out unexpectedly", "TIMEOUT")
|
||||
output += text_for_unexpected_list("passed unexpectedly", "PASS")
|
||||
output += text_for_unexpected_list("unexpectedly okay", "OK")
|
||||
|
||||
num_with_failing_subtests = len(self.tests_with_failing_subtests)
|
||||
if num_with_failing_subtests:
|
||||
output += (u" \u2022 %i tests had unexpected subtest results\n"
|
||||
% num_with_failing_subtests)
|
||||
output += " \u2022 %i tests had unexpected subtest results\n" % num_with_failing_subtests
|
||||
output += "\n"
|
||||
|
||||
# Repeat failing test output, so that it is easier to find, since the
|
||||
# non-interactive version prints all the test names.
|
||||
if not self.interactive and self.unexpected_results:
|
||||
output += u"Tests with unexpected results:\n"
|
||||
output += "".join([str(result)
|
||||
for result in self.unexpected_results])
|
||||
output += "Tests with unexpected results:\n"
|
||||
output += "".join([str(result) for result in self.unexpected_results])
|
||||
|
||||
return self.generate_output(text=output, new_display="")
|
||||
|
||||
|
@ -371,8 +370,8 @@ class ServoFormatter(mozlog.formatters.base.BaseFormatter, ServoHandler):
|
|||
|
||||
# We are logging messages that begin with STDERR, because that is how exceptions
|
||||
# in this formatter are indicated.
|
||||
if data['message'].startswith('STDERR'):
|
||||
return self.generate_output(text=data['message'] + "\n")
|
||||
if data["message"].startswith("STDERR"):
|
||||
return self.generate_output(text=data["message"] + "\n")
|
||||
|
||||
if data['level'] in ('CRITICAL', 'ERROR'):
|
||||
return self.generate_output(text=data['message'] + "\n")
|
||||
if data["level"] in ("CRITICAL", "ERROR"):
|
||||
return self.generate_output(text=data["message"] + "\n")
|
||||
|
|
|
@ -22,10 +22,10 @@ from wptrunner import wptlogging
|
|||
|
||||
def create_parser():
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument("--check-clean", action="store_true",
|
||||
help="Check that updating the manifest doesn't lead to any changes")
|
||||
p.add_argument("--rebuild", action="store_true",
|
||||
help="Rebuild the manifest from scratch")
|
||||
p.add_argument(
|
||||
"--check-clean", action="store_true", help="Check that updating the manifest doesn't lead to any changes"
|
||||
)
|
||||
p.add_argument("--rebuild", action="store_true", help="Rebuild the manifest from scratch")
|
||||
commandline.add_logging_group(p)
|
||||
|
||||
return p
|
||||
|
@ -34,11 +34,13 @@ def create_parser():
|
|||
def update(check_clean=True, rebuild=False, logger=None, **kwargs):
|
||||
if not logger:
|
||||
logger = wptlogging.setup(kwargs, {"mach": sys.stdout})
|
||||
kwargs = {"config": os.path.join(WPT_PATH, "config.ini"),
|
||||
"product": "servo",
|
||||
"manifest_path": os.path.join(WPT_PATH, "meta"),
|
||||
"tests_root": None,
|
||||
"metadata_root": None}
|
||||
kwargs = {
|
||||
"config": os.path.join(WPT_PATH, "config.ini"),
|
||||
"product": "servo",
|
||||
"manifest_path": os.path.join(WPT_PATH, "meta"),
|
||||
"tests_root": None,
|
||||
"metadata_root": None,
|
||||
}
|
||||
|
||||
set_from_config(kwargs)
|
||||
config = kwargs["config"]
|
||||
|
@ -53,15 +55,15 @@ def update(check_clean=True, rebuild=False, logger=None, **kwargs):
|
|||
def _update(logger, test_paths, rebuild):
|
||||
for url_base, paths in iteritems(test_paths):
|
||||
manifest_path = os.path.join(paths.metadata_path, "MANIFEST.json")
|
||||
cache_subdir = os.path.relpath(os.path.dirname(manifest_path),
|
||||
os.path.dirname(__file__))
|
||||
wptmanifest.manifest.load_and_update(paths.tests_path,
|
||||
manifest_path,
|
||||
url_base,
|
||||
working_copy=True,
|
||||
rebuild=rebuild,
|
||||
cache_root=os.path.join(SERVO_ROOT, ".wpt",
|
||||
cache_subdir))
|
||||
cache_subdir = os.path.relpath(os.path.dirname(manifest_path), os.path.dirname(__file__))
|
||||
wptmanifest.manifest.load_and_update(
|
||||
paths.tests_path,
|
||||
manifest_path,
|
||||
url_base,
|
||||
working_copy=True,
|
||||
rebuild=rebuild,
|
||||
cache_root=os.path.join(SERVO_ROOT, ".wpt", cache_subdir),
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
|
@ -72,26 +74,25 @@ def _check_clean(logger, test_paths):
|
|||
tests_path = paths.tests_path
|
||||
manifest_path = os.path.join(paths.metadata_path, "MANIFEST.json")
|
||||
|
||||
old_manifest = wptmanifest.manifest.load_and_update(tests_path,
|
||||
manifest_path,
|
||||
url_base,
|
||||
working_copy=False,
|
||||
update=False,
|
||||
write_manifest=False)
|
||||
old_manifest = wptmanifest.manifest.load_and_update(
|
||||
tests_path, manifest_path, url_base, working_copy=False, update=False, write_manifest=False
|
||||
)
|
||||
|
||||
# Even if no cache is specified, one will be used automatically by the
|
||||
# VCS integration. Create a brand new cache every time to ensure that
|
||||
# the VCS integration always thinks that any file modifications in the
|
||||
# working directory are new and interesting.
|
||||
cache_root = tempfile.mkdtemp()
|
||||
new_manifest = wptmanifest.manifest.load_and_update(tests_path,
|
||||
manifest_path,
|
||||
url_base,
|
||||
working_copy=True,
|
||||
update=True,
|
||||
cache_root=cache_root,
|
||||
write_manifest=False,
|
||||
allow_cached=False)
|
||||
new_manifest = wptmanifest.manifest.load_and_update(
|
||||
tests_path,
|
||||
manifest_path,
|
||||
url_base,
|
||||
working_copy=True,
|
||||
update=True,
|
||||
cache_root=cache_root,
|
||||
write_manifest=False,
|
||||
allow_cached=False,
|
||||
)
|
||||
|
||||
manifests_by_path[manifest_path] = (old_manifest, new_manifest)
|
||||
|
||||
|
@ -116,8 +117,7 @@ def diff_manifests(logger, manifest_path, old_manifest, new_manifest):
|
|||
"""
|
||||
logger.info("Diffing old and new manifests %s" % manifest_path)
|
||||
old_items, new_items = defaultdict(set), defaultdict(set)
|
||||
for manifest, items in [(old_manifest, old_items),
|
||||
(new_manifest, new_items)]:
|
||||
for manifest, items in [(old_manifest, old_items), (new_manifest, new_items)]:
|
||||
for test_type, path, tests in manifest:
|
||||
for test in tests:
|
||||
test_id = [test.id]
|
||||
|
@ -158,8 +158,8 @@ def diff_manifests(logger, manifest_path, old_manifest, new_manifest):
|
|||
if clean:
|
||||
# Manifest currently has some list vs tuple inconsistencies that break
|
||||
# a simple equality comparison.
|
||||
old_paths = old_manifest.to_json()['items']
|
||||
new_paths = new_manifest.to_json()['items']
|
||||
old_paths = old_manifest.to_json()["items"]
|
||||
new_paths = new_manifest.to_json()["items"]
|
||||
if old_paths != new_paths:
|
||||
logger.warning("Manifest %s contains correct tests but file hashes changed." % manifest_path) # noqa
|
||||
clean = False
|
||||
|
@ -168,8 +168,4 @@ def diff_manifests(logger, manifest_path, old_manifest, new_manifest):
|
|||
|
||||
|
||||
def log_error(logger, manifest_path, msg):
|
||||
logger.lint_error(path=manifest_path,
|
||||
message=msg,
|
||||
lineno=0,
|
||||
source="",
|
||||
linter="wpt-manifest")
|
||||
logger.lint_error(path=manifest_path, message=msg, lineno=0, source="", linter="wpt-manifest")
|
||||
|
|
|
@ -19,10 +19,7 @@ import mozlog
|
|||
import mozlog.formatters
|
||||
|
||||
from . import SERVO_ROOT, WPT_PATH, WPT_TOOLS_PATH
|
||||
from .grouping_formatter import (
|
||||
ServoFormatter, ServoHandler,
|
||||
UnexpectedResult, UnexpectedSubtestResult
|
||||
)
|
||||
from .grouping_formatter import ServoFormatter, ServoHandler, UnexpectedResult, UnexpectedSubtestResult
|
||||
from wptrunner import wptcommandline
|
||||
from wptrunner import wptrunner
|
||||
|
||||
|
@ -63,12 +60,8 @@ def run_tests(default_binary_path: str, **kwargs):
|
|||
set_if_none(kwargs, "processes", multiprocessing.cpu_count())
|
||||
|
||||
set_if_none(kwargs, "ca_cert_path", os.path.join(CERTS_PATH, "cacert.pem"))
|
||||
set_if_none(
|
||||
kwargs, "host_key_path", os.path.join(CERTS_PATH, "web-platform.test.key")
|
||||
)
|
||||
set_if_none(
|
||||
kwargs, "host_cert_path", os.path.join(CERTS_PATH, "web-platform.test.pem")
|
||||
)
|
||||
set_if_none(kwargs, "host_key_path", os.path.join(CERTS_PATH, "web-platform.test.key"))
|
||||
set_if_none(kwargs, "host_cert_path", os.path.join(CERTS_PATH, "web-platform.test.pem"))
|
||||
# Set `id_hash` as the default chunk, as this better distributes testing across different
|
||||
# chunks and leads to more consistent timing on GitHub Actions.
|
||||
set_if_none(kwargs, "chunk_type", "id_hash")
|
||||
|
@ -139,8 +132,7 @@ def run_tests(default_binary_path: str, **kwargs):
|
|||
handler.reset_state()
|
||||
|
||||
print(80 * "=")
|
||||
print(f"Rerunning {len(unexpected_results)} tests "
|
||||
"with unexpected results to detect flaky tests.")
|
||||
print(f"Rerunning {len(unexpected_results)} tests with unexpected results to detect flaky tests.")
|
||||
unexpected_results_tests = [result.path for result in unexpected_results]
|
||||
kwargs["test_list"] = unexpected_results_tests
|
||||
kwargs["include"] = unexpected_results_tests
|
||||
|
@ -158,8 +150,7 @@ def run_tests(default_binary_path: str, **kwargs):
|
|||
for result in unexpected_results:
|
||||
result.flaky = result.path not in stable_tests
|
||||
|
||||
all_filtered = filter_intermittents(unexpected_results,
|
||||
filter_intermittents_output)
|
||||
all_filtered = filter_intermittents(unexpected_results, filter_intermittents_output)
|
||||
return_value = 0 if all_filtered else 1
|
||||
|
||||
# Write the unexpected-only raw log if that was specified on the command-line.
|
||||
|
@ -168,9 +159,7 @@ def run_tests(default_binary_path: str, **kwargs):
|
|||
print("'--log-raw-unexpected' not written without '--log-raw'.")
|
||||
else:
|
||||
write_unexpected_only_raw_log(
|
||||
handler.unexpected_results,
|
||||
raw_log_outputs[0].name,
|
||||
unexpected_raw_log_output_file
|
||||
handler.unexpected_results, raw_log_outputs[0].name, unexpected_raw_log_output_file
|
||||
)
|
||||
|
||||
return return_value
|
||||
|
@ -182,12 +171,10 @@ class GithubContextInformation(NamedTuple):
|
|||
branch_name: Optional[str]
|
||||
|
||||
|
||||
class TrackerDashboardFilter():
|
||||
class TrackerDashboardFilter:
|
||||
def __init__(self):
|
||||
base_url = os.environ.get(TRACKER_API_ENV_VAR, TRACKER_API)
|
||||
self.headers = {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
self.headers = {"Content-Type": "application/json"}
|
||||
if TRACKER_DASHBOARD_SECRET_ENV_VAR in os.environ and os.environ[TRACKER_DASHBOARD_SECRET_ENV_VAR]:
|
||||
self.url = f"{base_url}/dashboard/attempts"
|
||||
secret = os.environ[TRACKER_DASHBOARD_SECRET_ENV_VAR]
|
||||
|
@ -201,10 +188,10 @@ class TrackerDashboardFilter():
|
|||
if not github_context:
|
||||
return GithubContextInformation(None, None, None)
|
||||
|
||||
repository = github_context['repository']
|
||||
repository = github_context["repository"]
|
||||
repo_url = f"https://github.com/{repository}"
|
||||
|
||||
run_id = github_context['run_id']
|
||||
run_id = github_context["run_id"]
|
||||
build_url = f"{repo_url}/actions/runs/{run_id}"
|
||||
|
||||
commit_title = "<no title>"
|
||||
|
@ -214,32 +201,27 @@ class TrackerDashboardFilter():
|
|||
commit_title = github_context["event"]["head_commit"]["message"]
|
||||
|
||||
pr_url = None
|
||||
match = re.match(r"^Auto merge of #(\d+)", commit_title) or \
|
||||
re.match(r"\(#(\d+)\)", commit_title)
|
||||
match = re.match(r"^Auto merge of #(\d+)", commit_title) or re.match(r"\(#(\d+)\)", commit_title)
|
||||
if match:
|
||||
pr_url = f"{repo_url}/pull/{match.group(1)}" if match else None
|
||||
|
||||
return GithubContextInformation(
|
||||
build_url,
|
||||
pr_url,
|
||||
github_context["ref_name"]
|
||||
)
|
||||
return GithubContextInformation(build_url, pr_url, github_context["ref_name"])
|
||||
|
||||
def make_data_from_result(
|
||||
self,
|
||||
result: Union[UnexpectedResult, UnexpectedSubtestResult],
|
||||
) -> dict:
|
||||
data = {
|
||||
'path': result.path,
|
||||
'subtest': None,
|
||||
'expected': result.expected,
|
||||
'actual': result.actual,
|
||||
'time': result.time // 1000,
|
||||
"path": result.path,
|
||||
"subtest": None,
|
||||
"expected": result.expected,
|
||||
"actual": result.actual,
|
||||
"time": result.time // 1000,
|
||||
# Truncate the message, to avoid issues with lots of output causing "HTTP
|
||||
# Error 413: Request Entity Too Large."
|
||||
# See https://github.com/servo/servo/issues/31845.
|
||||
'message': result.message[0:TRACKER_DASHBOARD_MAXIMUM_OUTPUT_LENGTH],
|
||||
'stack': result.stack,
|
||||
"message": result.message[0:TRACKER_DASHBOARD_MAXIMUM_OUTPUT_LENGTH],
|
||||
"stack": result.stack,
|
||||
}
|
||||
if isinstance(result, UnexpectedSubtestResult):
|
||||
data["subtest"] = result.subtest
|
||||
|
@ -256,20 +238,22 @@ class TrackerDashboardFilter():
|
|||
try:
|
||||
request = urllib.request.Request(
|
||||
url=self.url,
|
||||
method='POST',
|
||||
data=json.dumps({
|
||||
'branch': context.branch_name,
|
||||
'build_url': context.build_url,
|
||||
'pull_url': context.pull_url,
|
||||
'attempts': attempts
|
||||
}).encode('utf-8'),
|
||||
headers=self.headers)
|
||||
method="POST",
|
||||
data=json.dumps(
|
||||
{
|
||||
"branch": context.branch_name,
|
||||
"build_url": context.build_url,
|
||||
"pull_url": context.pull_url,
|
||||
"attempts": attempts,
|
||||
}
|
||||
).encode("utf-8"),
|
||||
headers=self.headers,
|
||||
)
|
||||
|
||||
known_intermittents = dict()
|
||||
with urllib.request.urlopen(request) as response:
|
||||
for test in json.load(response)["known"]:
|
||||
known_intermittents[test["path"]] = \
|
||||
[issue["number"] for issue in test["issues"]]
|
||||
known_intermittents[test["path"]] = [issue["number"] for issue in test["issues"]]
|
||||
|
||||
except urllib.error.HTTPError as e:
|
||||
print(e)
|
||||
|
@ -280,13 +264,9 @@ class TrackerDashboardFilter():
|
|||
result.issues = known_intermittents.get(result.path, [])
|
||||
|
||||
|
||||
def filter_intermittents(
|
||||
unexpected_results: List[UnexpectedResult],
|
||||
output_path: str
|
||||
) -> bool:
|
||||
def filter_intermittents(unexpected_results: List[UnexpectedResult], output_path: str) -> bool:
|
||||
dashboard = TrackerDashboardFilter()
|
||||
print(f"Filtering {len(unexpected_results)} "
|
||||
f"unexpected results for known intermittents via <{dashboard.url}>")
|
||||
print(f"Filtering {len(unexpected_results)} unexpected results for known intermittents via <{dashboard.url}>")
|
||||
dashboard.report_failures(unexpected_results)
|
||||
|
||||
def add_result(output, text, results: List[UnexpectedResult], filter_func) -> None:
|
||||
|
@ -298,12 +278,14 @@ def filter_intermittents(
|
|||
return not result.flaky and not result.issues
|
||||
|
||||
output: List[str] = []
|
||||
add_result(output, "Flaky unexpected results", unexpected_results,
|
||||
lambda result: result.flaky)
|
||||
add_result(output, "Stable unexpected results that are known-intermittent",
|
||||
unexpected_results, lambda result: not result.flaky and result.issues)
|
||||
add_result(output, "Stable unexpected results",
|
||||
unexpected_results, is_stable_and_unexpected)
|
||||
add_result(output, "Flaky unexpected results", unexpected_results, lambda result: result.flaky)
|
||||
add_result(
|
||||
output,
|
||||
"Stable unexpected results that are known-intermittent",
|
||||
unexpected_results,
|
||||
lambda result: not result.flaky and result.issues,
|
||||
)
|
||||
add_result(output, "Stable unexpected results", unexpected_results, is_stable_and_unexpected)
|
||||
print("\n".join(output))
|
||||
|
||||
with open(output_path, "w", encoding="utf-8") as file:
|
||||
|
@ -313,9 +295,7 @@ def filter_intermittents(
|
|||
|
||||
|
||||
def write_unexpected_only_raw_log(
|
||||
unexpected_results: List[UnexpectedResult],
|
||||
raw_log_file: str,
|
||||
filtered_raw_log_file: str
|
||||
unexpected_results: List[UnexpectedResult], raw_log_file: str, filtered_raw_log_file: str
|
||||
):
|
||||
tests = [result.path for result in unexpected_results]
|
||||
print(f"Writing unexpected-only raw log to {filtered_raw_log_file}")
|
||||
|
@ -324,6 +304,5 @@ def write_unexpected_only_raw_log(
|
|||
with open(raw_log_file) as input:
|
||||
for line in input.readlines():
|
||||
data = json.loads(line)
|
||||
if data["action"] in ["suite_start", "suite_end"] or \
|
||||
("test" in data and data["test"] in tests):
|
||||
if data["action"] in ["suite_start", "suite_end"] or ("test" in data and data["test"] in tests):
|
||||
output.write(line)
|
||||
|
|
|
@ -49,13 +49,13 @@ PORT = 9000
|
|||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class MockPullRequest():
|
||||
class MockPullRequest:
|
||||
head: str
|
||||
number: int
|
||||
state: str = "open"
|
||||
|
||||
|
||||
class MockGitHubAPIServer():
|
||||
class MockGitHubAPIServer:
|
||||
def __init__(self, port: int):
|
||||
self.port = port
|
||||
self.disable_logging()
|
||||
|
@ -65,18 +65,19 @@ class MockGitHubAPIServer():
|
|||
class NoLoggingHandler(WSGIRequestHandler):
|
||||
def log_message(self, *args):
|
||||
pass
|
||||
|
||||
if logging.getLogger().level == logging.DEBUG:
|
||||
handler = WSGIRequestHandler
|
||||
else:
|
||||
handler = NoLoggingHandler
|
||||
|
||||
self.server = make_server('localhost', self.port, self.app, handler_class=handler)
|
||||
self.server = make_server("localhost", self.port, self.app, handler_class=handler)
|
||||
self.start_server_thread()
|
||||
|
||||
def disable_logging(self):
|
||||
flask.cli.show_server_banner = lambda *args: None
|
||||
logging.getLogger("werkzeug").disabled = True
|
||||
logging.getLogger('werkzeug').setLevel(logging.CRITICAL)
|
||||
logging.getLogger("werkzeug").setLevel(logging.CRITICAL)
|
||||
|
||||
def start(self):
|
||||
self.thread.start()
|
||||
|
@ -84,21 +85,21 @@ class MockGitHubAPIServer():
|
|||
# Wait for the server to be started.
|
||||
while True:
|
||||
try:
|
||||
response = requests.get(f'http://localhost:{self.port}/ping', timeout=1)
|
||||
response = requests.get(f"http://localhost:{self.port}/ping", timeout=1)
|
||||
assert response.status_code == 200
|
||||
assert response.text == 'pong'
|
||||
assert response.text == "pong"
|
||||
break
|
||||
except Exception:
|
||||
time.sleep(0.1)
|
||||
|
||||
def reset_server_state_with_pull_requests(self, pulls: list[MockPullRequest]):
|
||||
response = requests.get(
|
||||
f'http://localhost:{self.port}/reset-mock-github',
|
||||
f"http://localhost:{self.port}/reset-mock-github",
|
||||
json=[dataclasses.asdict(pull_request) for pull_request in pulls],
|
||||
timeout=1
|
||||
timeout=1,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.text == '👍'
|
||||
assert response.text == "👍"
|
||||
|
||||
def shutdown(self):
|
||||
self.server.shutdown()
|
||||
|
@ -111,26 +112,25 @@ class MockGitHubAPIServer():
|
|||
|
||||
@self.app.route("/ping")
|
||||
def ping():
|
||||
return ('pong', 200)
|
||||
return ("pong", 200)
|
||||
|
||||
@self.app.route("/reset-mock-github")
|
||||
def reset_server():
|
||||
self.pulls = [
|
||||
MockPullRequest(pull_request['head'],
|
||||
pull_request['number'],
|
||||
pull_request['state'])
|
||||
for pull_request in flask.request.json]
|
||||
return ('👍', 200)
|
||||
MockPullRequest(pull_request["head"], pull_request["number"], pull_request["state"])
|
||||
for pull_request in flask.request.json
|
||||
]
|
||||
return ("👍", 200)
|
||||
|
||||
@self.app.route("/repos/<org>/<repo>/pulls/<int:number>/merge", methods=['PUT'])
|
||||
@self.app.route("/repos/<org>/<repo>/pulls/<int:number>/merge", methods=["PUT"])
|
||||
def merge_pull_request(org, repo, number):
|
||||
for pull_request in self.pulls:
|
||||
if pull_request.number == number:
|
||||
pull_request.state = 'closed'
|
||||
return ('', 204)
|
||||
return ('', 404)
|
||||
pull_request.state = "closed"
|
||||
return ("", 204)
|
||||
return ("", 404)
|
||||
|
||||
@self.app.route("/search/issues", methods=['GET'])
|
||||
@self.app.route("/search/issues", methods=["GET"])
|
||||
def search():
|
||||
params = {}
|
||||
param_strings = flask.request.args.get("q", "").split(" ")
|
||||
|
@ -145,38 +145,29 @@ class MockGitHubAPIServer():
|
|||
|
||||
for pull_request in self.pulls:
|
||||
if pull_request.head.endswith(head_ref):
|
||||
return json.dumps({
|
||||
"total_count": 1,
|
||||
"items": [{
|
||||
"number": pull_request.number
|
||||
}]
|
||||
})
|
||||
return json.dumps({"total_count": 1, "items": [{"number": pull_request.number}]})
|
||||
return json.dumps({"total_count": 0, "items": []})
|
||||
|
||||
@self.app.route("/repos/<org>/<repo>/pulls", methods=['POST'])
|
||||
@self.app.route("/repos/<org>/<repo>/pulls", methods=["POST"])
|
||||
def create_pull_request(org, repo):
|
||||
new_pr_number = len(self.pulls) + 1
|
||||
self.pulls.append(MockPullRequest(
|
||||
flask.request.json["head"],
|
||||
new_pr_number,
|
||||
"open"
|
||||
))
|
||||
self.pulls.append(MockPullRequest(flask.request.json["head"], new_pr_number, "open"))
|
||||
return {"number": new_pr_number}
|
||||
|
||||
@self.app.route("/repos/<org>/<repo>/pulls/<int:number>", methods=['PATCH'])
|
||||
@self.app.route("/repos/<org>/<repo>/pulls/<int:number>", methods=["PATCH"])
|
||||
def update_pull_request(org, repo, number):
|
||||
for pull_request in self.pulls:
|
||||
if pull_request.number == number:
|
||||
if 'state' in flask.request.json:
|
||||
pull_request.state = flask.request.json['state']
|
||||
return ('', 204)
|
||||
return ('', 404)
|
||||
if "state" in flask.request.json:
|
||||
pull_request.state = flask.request.json["state"]
|
||||
return ("", 204)
|
||||
return ("", 404)
|
||||
|
||||
@self.app.route("/repos/<org>/<repo>/issues/<number>/labels", methods=['GET', 'POST'])
|
||||
@self.app.route("/repos/<org>/<repo>/issues/<number>/labels/<label>", methods=['DELETE'])
|
||||
@self.app.route("/repos/<org>/<repo>/issues/<issue>/comments", methods=['GET', 'POST'])
|
||||
@self.app.route("/repos/<org>/<repo>/issues/<number>/labels", methods=["GET", "POST"])
|
||||
@self.app.route("/repos/<org>/<repo>/issues/<number>/labels/<label>", methods=["DELETE"])
|
||||
@self.app.route("/repos/<org>/<repo>/issues/<issue>/comments", methods=["GET", "POST"])
|
||||
def other_requests(*args, **kwargs):
|
||||
return ('', 204)
|
||||
return ("", 204)
|
||||
|
||||
|
||||
class TestCleanUpBodyText(unittest.TestCase):
|
||||
|
@ -196,28 +187,22 @@ class TestCleanUpBodyText(unittest.TestCase):
|
|||
)
|
||||
self.assertEqual(
|
||||
"Subject\n\nBody text #<!-- nolink -->1",
|
||||
SyncRun.clean_up_body_text(
|
||||
"Subject\n\nBody text #1\n---<!-- Thank you for contributing"
|
||||
),
|
||||
SyncRun.clean_up_body_text("Subject\n\nBody text #1\n---<!-- Thank you for contributing"),
|
||||
)
|
||||
self.assertEqual(
|
||||
"Subject\n\nNo dashes",
|
||||
SyncRun.clean_up_body_text(
|
||||
"Subject\n\nNo dashes<!-- Thank you for contributing"
|
||||
),
|
||||
SyncRun.clean_up_body_text("Subject\n\nNo dashes<!-- Thank you for contributing"),
|
||||
)
|
||||
self.assertEqual(
|
||||
"Subject\n\nNo --- comment",
|
||||
SyncRun.clean_up_body_text(
|
||||
"Subject\n\nNo --- comment\n---Other stuff that"
|
||||
),
|
||||
SyncRun.clean_up_body_text("Subject\n\nNo --- comment\n---Other stuff that"),
|
||||
)
|
||||
self.assertEqual(
|
||||
"Subject\n\n#<!-- nolink -->3 servo#<!-- nolink -->3 servo/servo#3",
|
||||
SyncRun.clean_up_body_text(
|
||||
"Subject\n\n#3 servo#3 servo/servo#3",
|
||||
),
|
||||
"Only relative and bare issue reference links should be escaped."
|
||||
"Only relative and bare issue reference links should be escaped.",
|
||||
)
|
||||
|
||||
|
||||
|
@ -236,9 +221,7 @@ class TestApplyCommitsToWPT(unittest.TestCase):
|
|||
pull_request = SYNC.servo.get_pull_request(pr_number)
|
||||
step = CreateOrUpdateBranchForPRStep({"number": pr_number}, pull_request)
|
||||
|
||||
def get_applied_commits(
|
||||
num_commits: int, applied_commits: list[Tuple[str, str]]
|
||||
):
|
||||
def get_applied_commits(num_commits: int, applied_commits: list[Tuple[str, str]]):
|
||||
assert SYNC is not None
|
||||
repo = SYNC.local_wpt_repo
|
||||
log = ["log", "--oneline", f"-{num_commits}"]
|
||||
|
@ -252,17 +235,13 @@ class TestApplyCommitsToWPT(unittest.TestCase):
|
|||
|
||||
applied_commits: list[Any] = []
|
||||
callback = partial(get_applied_commits, len(commits), applied_commits)
|
||||
step._create_or_update_branch_for_pr(
|
||||
SyncRun(SYNC, pull_request, None, None), commits, callback
|
||||
)
|
||||
step._create_or_update_branch_for_pr(SyncRun(SYNC, pull_request, None, None), commits, callback)
|
||||
|
||||
expected_commits = [(commit["author"], commit["message"]) for commit in commits]
|
||||
self.assertListEqual(applied_commits, expected_commits)
|
||||
|
||||
def test_simple_commit(self):
|
||||
self.run_test(
|
||||
45, [["test author <test@author>", "test commit message", "18746.diff"]]
|
||||
)
|
||||
self.run_test(45, [["test author <test@author>", "test commit message", "18746.diff"]])
|
||||
|
||||
def test_two_commits(self):
|
||||
self.run_test(
|
||||
|
@ -299,9 +278,7 @@ class TestFullSyncRun(unittest.TestCase):
|
|||
assert SYNC is not None
|
||||
|
||||
# Clean up any old files.
|
||||
first_commit_hash = SYNC.local_servo_repo.run("rev-list", "HEAD").splitlines()[
|
||||
-1
|
||||
]
|
||||
first_commit_hash = SYNC.local_servo_repo.run("rev-list", "HEAD").splitlines()[-1]
|
||||
SYNC.local_servo_repo.run("reset", "--hard", first_commit_hash)
|
||||
SYNC.local_servo_repo.run("clean", "-fxd")
|
||||
|
||||
|
@ -339,9 +316,7 @@ class TestFullSyncRun(unittest.TestCase):
|
|||
SYNC.local_servo_repo.run("reset", "--hard", orig_sha)
|
||||
return last_commit_sha
|
||||
|
||||
def run_test(
|
||||
self, payload_file: str, diffs: list, existing_prs: list[MockPullRequest] = []
|
||||
):
|
||||
def run_test(self, payload_file: str, diffs: list, existing_prs: list[MockPullRequest] = []):
|
||||
with open(os.path.join(TESTS_DIR, payload_file), encoding="utf-8") as file:
|
||||
payload = json.loads(file.read())
|
||||
|
||||
|
@ -413,12 +388,8 @@ class TestFullSyncRun(unittest.TestCase):
|
|||
)
|
||||
|
||||
def test_opened_new_mr_with_no_sync_signal(self):
|
||||
self.assertListEqual(
|
||||
self.run_test("opened-with-no-sync-signal.json", ["18746.diff"]), []
|
||||
)
|
||||
self.assertListEqual(
|
||||
self.run_test("opened-with-no-sync-signal.json", ["non-wpt.diff"]), []
|
||||
)
|
||||
self.assertListEqual(self.run_test("opened-with-no-sync-signal.json", ["18746.diff"]), [])
|
||||
self.assertListEqual(self.run_test("opened-with-no-sync-signal.json", ["non-wpt.diff"]), [])
|
||||
|
||||
def test_opened_upstreamable_pr_not_applying_cleanly_to_upstream(self):
|
||||
self.assertListEqual(
|
||||
|
@ -459,7 +430,7 @@ class TestFullSyncRun(unittest.TestCase):
|
|||
"RemoveBranchForPRStep:servo/wpt/servo_export_18746",
|
||||
"CommentStep:servo/servo#18746:🤖 This change no longer contains upstreamable changes "
|
||||
"to WPT; closed existing upstream pull request (wpt/wpt#1).",
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
def test_opened_upstreamable_pr_with_non_utf8_file_contents(self):
|
||||
|
@ -502,10 +473,7 @@ class TestFullSyncRun(unittest.TestCase):
|
|||
["18746.diff"],
|
||||
[MockPullRequest("servo:servo_export_18746", 10)],
|
||||
),
|
||||
[
|
||||
"ChangePRStep:wpt/wpt#10:closed",
|
||||
"RemoveBranchForPRStep:servo/wpt/servo_export_18746"
|
||||
]
|
||||
["ChangePRStep:wpt/wpt#10:closed", "RemoveBranchForPRStep:servo/wpt/servo_export_18746"],
|
||||
)
|
||||
|
||||
def test_synchronize_move_new_changes_to_preexisting_upstream_pr(self):
|
||||
|
@ -520,7 +488,7 @@ class TestFullSyncRun(unittest.TestCase):
|
|||
"CreateOrUpdateBranchForPRStep:1:servo/wpt/servo_export_19612",
|
||||
"CommentStep:servo/servo#19612:📝 Transplanted new upstreamable changes to existing "
|
||||
"upstream WPT pull request (wpt/wpt#10).",
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
def test_synchronize_close_upstream_pr_after_new_changes_do_not_include_wpt(self):
|
||||
|
@ -537,7 +505,7 @@ class TestFullSyncRun(unittest.TestCase):
|
|||
"RemoveBranchForPRStep:servo/wpt/servo_export_19612",
|
||||
"CommentStep:servo/servo#19612:🤖 This change no longer contains upstreamable changes to WPT; "
|
||||
"closed existing upstream pull request (wpt/wpt#11).",
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
def test_synchronize_open_upstream_pr_after_new_changes_include_wpt(self):
|
||||
|
@ -548,7 +516,7 @@ class TestFullSyncRun(unittest.TestCase):
|
|||
"OpenPRStep:servo/wpt/servo_export_19612→wpt/wpt#1",
|
||||
"CommentStep:servo/servo#19612:🤖 Opened new upstream WPT pull request "
|
||||
"(wpt/wpt#1) with upstreamable changes.",
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
def test_synchronize_fail_to_update_preexisting_pr_after_new_changes_do_not_apply(
|
||||
|
@ -567,20 +535,17 @@ class TestFullSyncRun(unittest.TestCase):
|
|||
"latest upstream WPT. Servo's copy of the Web Platform Tests may be out of sync.",
|
||||
"CommentStep:wpt/wpt#11:🛠 Changes from the source pull request (servo/servo#19612) can "
|
||||
"no longer be cleanly applied. Waiting for a new version of these changes downstream.",
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
def test_edited_with_upstream_pr(self):
|
||||
self.assertListEqual(
|
||||
self.run_test(
|
||||
"edited.json", ["wpt.diff"],
|
||||
[MockPullRequest("servo:servo_export_19620", 10)]
|
||||
),
|
||||
self.run_test("edited.json", ["wpt.diff"], [MockPullRequest("servo:servo_export_19620", 10)]),
|
||||
[
|
||||
"ChangePRStep:wpt/wpt#10:open:A cool new title:Reference #<!--...[136]",
|
||||
"CommentStep:servo/servo#19620:✍ Updated existing upstream WPT pull "
|
||||
"request (wpt/wpt#10) title and body."
|
||||
]
|
||||
"request (wpt/wpt#10) title and body.",
|
||||
],
|
||||
)
|
||||
|
||||
def test_edited_with_no_upstream_pr(self):
|
||||
|
@ -590,15 +555,13 @@ class TestFullSyncRun(unittest.TestCase):
|
|||
self,
|
||||
):
|
||||
self.assertListEqual(
|
||||
self.run_test(
|
||||
"synchronize-multiple.json", ["18746.diff", "non-wpt.diff", "wpt.diff"]
|
||||
),
|
||||
self.run_test("synchronize-multiple.json", ["18746.diff", "non-wpt.diff", "wpt.diff"]),
|
||||
[
|
||||
"CreateOrUpdateBranchForPRStep:2:servo/wpt/servo_export_19612",
|
||||
"OpenPRStep:servo/wpt/servo_export_19612→wpt/wpt#1",
|
||||
"CommentStep:servo/servo#19612:"
|
||||
"🤖 Opened new upstream WPT pull request (wpt/wpt#1) with upstreamable changes.",
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
def test_synchronize_with_non_upstreamable_changes(self):
|
||||
|
@ -606,15 +569,8 @@ class TestFullSyncRun(unittest.TestCase):
|
|||
|
||||
def test_merge_upstream_pr_after_merge(self):
|
||||
self.assertListEqual(
|
||||
self.run_test(
|
||||
"merged.json",
|
||||
["18746.diff"],
|
||||
[MockPullRequest("servo:servo_export_19620", 100)]
|
||||
),
|
||||
[
|
||||
"MergePRStep:wpt/wpt#100",
|
||||
"RemoveBranchForPRStep:servo/wpt/servo_export_19620"
|
||||
]
|
||||
self.run_test("merged.json", ["18746.diff"], [MockPullRequest("servo:servo_export_19620", 100)]),
|
||||
["MergePRStep:wpt/wpt#100", "RemoveBranchForPRStep:servo/wpt/servo_export_19620"],
|
||||
)
|
||||
|
||||
def test_pr_merged_no_upstream_pr(self):
|
||||
|
@ -644,8 +600,7 @@ def setUpModule():
|
|||
)
|
||||
|
||||
def setup_mock_repo(repo_name, local_repo, default_branch: str):
|
||||
subprocess.check_output(
|
||||
["cp", "-R", "-p", os.path.join(TESTS_DIR, repo_name), local_repo.path])
|
||||
subprocess.check_output(["cp", "-R", "-p", os.path.join(TESTS_DIR, repo_name), local_repo.path])
|
||||
local_repo.run("init", "-b", default_branch)
|
||||
local_repo.run("add", ".")
|
||||
local_repo.run("commit", "-a", "-m", "Initial commit")
|
||||
|
@ -666,12 +621,16 @@ def run_tests():
|
|||
verbosity = 1 if logging.getLogger().level >= logging.WARN else 2
|
||||
|
||||
def run_suite(test_case: Type[unittest.TestCase]):
|
||||
return unittest.TextTestRunner(verbosity=verbosity).run(
|
||||
unittest.TestLoader().loadTestsFromTestCase(test_case)
|
||||
).wasSuccessful()
|
||||
return (
|
||||
unittest.TextTestRunner(verbosity=verbosity)
|
||||
.run(unittest.TestLoader().loadTestsFromTestCase(test_case))
|
||||
.wasSuccessful()
|
||||
)
|
||||
|
||||
return all([
|
||||
run_suite(TestApplyCommitsToWPT),
|
||||
run_suite(TestCleanUpBodyText),
|
||||
run_suite(TestFullSyncRun),
|
||||
])
|
||||
return all(
|
||||
[
|
||||
run_suite(TestApplyCommitsToWPT),
|
||||
run_suite(TestCleanUpBodyText),
|
||||
run_suite(TestFullSyncRun),
|
||||
]
|
||||
)
|
||||
|
|
|
@ -5,6 +5,6 @@ index 10d52a0..92fb89d 100644
|
|||
@@ -8,3 +8,4 @@
|
||||
# except according to those terms.
|
||||
|
||||
print('this is a python file')
|
||||
+print('this is a change')
|
||||
print("this is a python file")
|
||||
+print("this is a change")
|
||||
|
||||
|
|
|
@ -7,4 +7,4 @@
|
|||
# option. This file may not be copied, modified, or distributed
|
||||
# except according to those terms.
|
||||
|
||||
print('this is a python file')
|
||||
print("this is a python file")
|
||||
|
|
|
@ -15,11 +15,8 @@ from wptrunner import wptcommandline # noqa: F401
|
|||
from . import WPT_PATH
|
||||
from . import manifestupdate
|
||||
|
||||
TEST_ROOT = os.path.join(WPT_PATH, 'tests')
|
||||
META_ROOTS = [
|
||||
os.path.join(WPT_PATH, 'meta'),
|
||||
os.path.join(WPT_PATH, 'meta-legacy')
|
||||
]
|
||||
TEST_ROOT = os.path.join(WPT_PATH, "tests")
|
||||
META_ROOTS = [os.path.join(WPT_PATH, "meta"), os.path.join(WPT_PATH, "meta-legacy")]
|
||||
|
||||
|
||||
def do_sync(**kwargs) -> int:
|
||||
|
@ -28,8 +25,8 @@ def do_sync(**kwargs) -> int:
|
|||
# Commits should always be authored by the GitHub Actions bot.
|
||||
os.environ["GIT_AUTHOR_NAME"] = "Servo WPT Sync"
|
||||
os.environ["GIT_AUTHOR_EMAIL"] = "ghbot+wpt-sync@servo.org"
|
||||
os.environ["GIT_COMMITTER_NAME"] = os.environ['GIT_AUTHOR_NAME']
|
||||
os.environ["GIT_COMMITTER_EMAIL"] = os.environ['GIT_AUTHOR_EMAIL']
|
||||
os.environ["GIT_COMMITTER_NAME"] = os.environ["GIT_AUTHOR_NAME"]
|
||||
os.environ["GIT_COMMITTER_EMAIL"] = os.environ["GIT_AUTHOR_EMAIL"]
|
||||
|
||||
print("Updating WPT from upstream...")
|
||||
run_update(**kwargs)
|
||||
|
@ -67,7 +64,7 @@ def remove_unused_metadata():
|
|||
dir_path = os.path.join(base_dir, dir_name)
|
||||
|
||||
# Skip any known directories that are meta-metadata.
|
||||
if dir_name == '.cache':
|
||||
if dir_name == ".cache":
|
||||
unused_dirs.append(dir_path)
|
||||
continue
|
||||
|
||||
|
@ -78,12 +75,11 @@ def remove_unused_metadata():
|
|||
|
||||
for fname in files:
|
||||
# Skip any known files that are meta-metadata.
|
||||
if not fname.endswith(".ini") or fname == '__dir__.ini':
|
||||
if not fname.endswith(".ini") or fname == "__dir__.ini":
|
||||
continue
|
||||
|
||||
# Turn tests/wpt/meta/foo/bar.html.ini into tests/wpt/tests/foo/bar.html.
|
||||
test_file = os.path.join(
|
||||
TEST_ROOT, os.path.relpath(base_dir, meta_root), fname[:-4])
|
||||
test_file = os.path.join(TEST_ROOT, os.path.relpath(base_dir, meta_root), fname[:-4])
|
||||
|
||||
if not os.path.exists(test_file):
|
||||
unused_files.append(os.path.join(base_dir, fname))
|
||||
|
@ -106,10 +102,10 @@ def update_tests(**kwargs) -> int:
|
|||
kwargs["store_state"] = False
|
||||
|
||||
wptcommandline.set_from_config(kwargs)
|
||||
if hasattr(wptcommandline, 'check_paths'):
|
||||
if hasattr(wptcommandline, "check_paths"):
|
||||
wptcommandline.check_paths(kwargs["test_paths"])
|
||||
|
||||
if kwargs.get('sync', False):
|
||||
if kwargs.get("sync", False):
|
||||
return do_sync(**kwargs)
|
||||
|
||||
return 0 if run_update(**kwargs) else 1
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue