forked from Pissandshittium/pissandshittium
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPRESUBMIT.py
548 lines (477 loc) · 23.2 KB
/
PRESUBMIT.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
# Copyright 2013 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""web_tests/ presubmit script for Blink.
See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
for more details about the presubmit API built into gcl.
"""
import filecmp
import inspect
import os
import sys
import tempfile
import re
from html.parser import HTMLParser
from typing import List
def _CheckTestharnessWdspecResults(input_api, output_api):
"""Checks for all-PASS generic baselines for testharness/wdspec tests.
These files are unnecessary because for testharness/wdspec tests, if there is no
baseline file then the test is considered to pass when the output is all
PASS. Note that only generic baselines are checked because platform specific
and virtual baselines might be needed to prevent fallback.
"""
baseline_files = _TxtGenericBaselinesToCheck(input_api)
if not baseline_files:
return []
checker_path = input_api.os_path.join(input_api.PresubmitLocalPath(), '..',
'tools', 'check_expected_pass.py')
# When running git cl presubmit --all this presubmit may be asked to check
# ~19,000 files. Passing these on the command line would far exceed Windows
# limits, so we use --path-files instead.
# We have to set delete=False and then let the object go out of scope so
# that the file can be opened by name on Windows.
with tempfile.NamedTemporaryFile('w+', newline='', delete=False) as f:
for path in baseline_files:
f.write('%s\n' % path)
paths_name = f.name
args = [
input_api.python3_executable, checker_path, '--path-files', paths_name
]
_, errs = input_api.subprocess.Popen(
args,
stdout=input_api.subprocess.PIPE,
stderr=input_api.subprocess.PIPE,
universal_newlines=True).communicate()
os.remove(paths_name)
if errs:
return [output_api.PresubmitError(errs)]
return []
def _TxtGenericBaselinesToCheck(input_api):
"""Returns a list of paths of generic baselines for testharness/wdspec tests."""
baseline_files = []
this_dir = input_api.PresubmitLocalPath()
for f in input_api.AffectedFiles():
if f.Action() == 'D':
continue
path = f.AbsoluteLocalPath()
if not path.endswith('-expected.txt'):
continue
if (input_api.os_path.join(this_dir, 'platform') in path
or input_api.os_path.join(this_dir, 'virtual') in path
or input_api.os_path.join(this_dir, 'flag-specific') in path):
continue
baseline_files.append(path)
return baseline_files
def _CheckFilesUsingEventSender(input_api, output_api):
"""Check if any new layout tests still use eventSender. If they do, we encourage replacing them with
chrome.gpuBenchmarking.pointerActionSequence.
"""
results = []
actions = ["eventSender.touch", "eventSender.mouse", "eventSender.gesture"]
for f in input_api.AffectedFiles():
if f.Action() == 'A':
for line_num, line in f.ChangedContents():
if line.find("eventSender.beginDragWithFiles") != -1:
break
if any(action in line for action in actions):
results.append(output_api.PresubmitPromptWarning(
'eventSender is deprecated, please use chrome.gpuBenchmarking.pointerActionSequence instead ' +
'(see https://crbug.com/711340 and http://goo.gl/BND75q).\n' +
'Files: %s:%d %s ' % (f.LocalPath(), line_num, line)))
return results
def _CheckTestExpectations(input_api, output_api):
results = []
os_path = input_api.os_path
sys.path.append(
os_path.join(
os_path.dirname(
os_path.abspath(inspect.getfile(_CheckTestExpectations))),
'..', 'tools'))
from blinkpy.presubmit.lint_test_expectations import (
PresubmitCheckTestExpectations)
results.extend(PresubmitCheckTestExpectations(input_api, output_api))
return results
def _CheckForRedundantBaselines(input_api, output_api, max_tests: int = 1000):
tests = _TestsCorrespondingToAffectedBaselines(input_api, max_tests)
if not tests:
return []
elif len(tests) > max_tests:
return [
output_api.PresubmitNotifyResult(
'Too many tests to check for redundant baselines; skipping.',
items=tests),
]
path_to_blink_tool = input_api.os_path.join(input_api.PresubmitLocalPath(),
input_api.os_path.pardir,
'tools', 'blink_tool.py')
with input_api.CreateTemporaryFile(mode='w+') as test_name_file:
for test in tests:
test_name_file.write(f'{test}\n')
test_name_file.flush()
command_args = [
input_api.python3_executable,
path_to_blink_tool,
'optimize-baselines',
'--no-manifest-update',
'--check',
f'--test-name-file={test_name_file.name}',
]
command = input_api.Command(
name='Checking for redundant affected baselines ...',
cmd=command_args,
kwargs={},
message=output_api.PresubmitPromptWarning,
python3=True)
return input_api.RunTests([command])
def _TestsCorrespondingToAffectedBaselines(input_api,
max_tests: int = 1000) -> List[str]:
sep = input_api.re.escape(input_api.os_path.sep)
baseline_pattern = input_api.re.compile(
r'((platform|flag-specific)%s[^%s]+%s)?(virtual%s[^%s]+%s)?'
r'(?P<test_prefix>.*)-expected\.(txt|png|wav)' % ((sep, ) * 6))
test_paths = set()
for affected_file in input_api.AffectedFiles():
if len(test_paths) > max_tests:
# Exit early; no need to glob for more tests.
break
baseline_path_from_web_tests = input_api.os_path.relpath(
affected_file.AbsoluteLocalPath(), input_api.PresubmitLocalPath())
baseline_match = baseline_pattern.fullmatch(
baseline_path_from_web_tests)
if not baseline_match:
continue
# Baselines for WPT-style variants have sanitized filenames with '?' and
# '&' in the query parameter section converted to '_', like:
# a/b.html?c&d -> a/b_c_d-expected.txt
#
# Regrettably, this is a lossy coercion that cannot be distinguished
# from tests with '_' in the original test ID, like:
# a/b_c_d.html -> a/b_c_d-expected.txt
#
# Here, we err on the side of not attempting to check such tests, which
# could be unrelated.
test_prefix = baseline_match['test_prefix']
# Getting the test name from the baseline path is not as easy as the
# other direction. Try all extensions as a heuristic instead.
for extension in [
'html', 'xml', 'xhtml', 'xht', 'pl', 'htm', 'php', 'svg',
'mht', 'pdf', 'js'
]:
abs_prefix = input_api.os_path.join(input_api.PresubmitLocalPath(),
test_prefix)
test_paths.update(input_api.glob(f'{abs_prefix}*.{extension}'))
return [
input_api.os_path.relpath(test_path, input_api.PresubmitLocalPath())
for test_path in sorted(test_paths)
]
def _CheckForJSTest(input_api, output_api):
"""'js-test.js' is the past, 'testharness.js' is our glorious future"""
jstest_re = input_api.re.compile(r'resources/js-test.js')
def source_file_filter(path):
return input_api.FilterSourceFile(path, files_to_check=[r'\.(html|js|php|pl|svg)$'])
errors = input_api.canned_checks._FindNewViolationsOfRule(
lambda _, x: not jstest_re.search(x), input_api, source_file_filter)
errors = [' * %s' % violation for violation in errors]
if errors:
return [output_api.PresubmitPromptOrNotify(
'"resources/js-test.js" is deprecated; please write new layout '
'tests using the assertions in "resources/testharness.js" '
'instead, as these can be more easily upstreamed to Web Platform '
'Tests for cross-vendor compatibility testing. If you\'re not '
'already familiar with this framework, a tutorial is available at '
'https://darobin.github.io/test-harness-tutorial/docs/using-testharness.html'
'\n\n%s' % '\n'.join(errors))]
return []
def _CheckForInvalidPreferenceError(input_api, output_api):
pattern = input_api.re.compile('Invalid name for preference: (.+)')
results = []
for f in input_api.AffectedFiles():
if not f.LocalPath().endswith('-expected.txt'):
continue
for line_num, line in f.ChangedContents():
error = pattern.search(line)
if error:
results.append(output_api.PresubmitError('Found an invalid preference %s in expected result %s:%s' % (error.group(1), f, line_num)))
return results
def _CheckRunAfterLayoutAndPaintJS(input_api, output_api):
"""Checks if resources/run-after-layout-and-paint.js and
http/tests/resources/run-after-layout-and-paint.js are the same."""
js_file = input_api.os_path.join(input_api.PresubmitLocalPath(),
'resources', 'run-after-layout-and-paint.js')
http_tests_js_file = input_api.os_path.join(input_api.PresubmitLocalPath(),
'http', 'tests', 'resources', 'run-after-layout-and-paint.js')
for f in input_api.AffectedFiles():
path = f.AbsoluteLocalPath()
if path == js_file or path == http_tests_js_file:
if not filecmp.cmp(js_file, http_tests_js_file):
return [output_api.PresubmitError(
'%s and %s must be kept exactly the same' %
(js_file, http_tests_js_file))]
break
return []
def _CheckForUnlistedTestFolder(input_api, output_api):
"""Checks all the test folders under web_tests are listed in BUILD.gn.
"""
this_dir = input_api.PresubmitLocalPath()
possible_new_dirs = set()
for f in input_api.AffectedFiles():
if f.Action() == 'A':
# We only check added folders. For deleted folders, if BUILD.gn is
# not updated, the build will fail at upload step. The reason is that
# we can not know if the folder is deleted as there can be local
# unchecked in files.
path = f.AbsoluteLocalPath()
fns = path[len(this_dir)+1:].split('/')
if len(fns) > 1:
possible_new_dirs.add(fns[0])
if possible_new_dirs:
path_build_gn = input_api.os_path.join(input_api.change.RepositoryRoot(), 'BUILD.gn')
dirs_from_build_gn = []
start_line = '# === List Test Cases folders here ==='
end_line = '# === Test Case Folders Ends ==='
end_line_count = 0
find_start_line = False
for line in input_api.ReadFile(path_build_gn).splitlines():
line = line.strip()
if line.startswith(start_line):
find_start_line = True
continue
if find_start_line:
if line.startswith(end_line):
find_start_line = False
end_line_count += 1
if end_line_count == 2:
break
continue
if len(line.split('/')) > 1:
dirs_from_build_gn.append(line.split('/')[-2])
dirs_from_build_gn.extend(
['platform', 'FlagExpectations', 'flag-specific', 'TestLists'])
new_dirs = [x for x in possible_new_dirs if x not in dirs_from_build_gn]
if new_dirs:
dir_plural = "directories" if len(new_dirs) > 1 else "directory"
error_message = (
'This CL adds new %s(%s) under //third_party/blink/web_tests/, but //BUILD.gn '
'is not updated. Please add the %s to BUILD.gn.' % (dir_plural, ', '.join(new_dirs), dir_plural))
if input_api.is_committing:
return [output_api.PresubmitError(error_message)]
else:
return [output_api.PresubmitPromptWarning(error_message)]
return []
def _CheckForExtraVirtualBaselines(input_api, output_api):
"""Checks that expectations in virtual test suites are for virtual test suites that exist
"""
os_path = input_api.os_path
local_dir = os_path.relpath(
os_path.normpath('{0}/'.format(input_api.PresubmitLocalPath().replace(
os_path.sep, '/'))), input_api.change.RepositoryRoot())
check_all = False
check_files = []
for f in input_api.AffectedFiles(include_deletes=False):
local_path = f.LocalPath()
assert local_path.startswith(local_dir)
local_path = os_path.relpath(local_path, local_dir)
path_components = local_path.split(os_path.sep)
if f.Action() == 'A':
if len(path_components) > 2 and path_components[0] == 'virtual':
check_files.append((local_path, path_components[1]))
elif (len(path_components) > 4 and path_components[2] == 'virtual'
and (path_components[0] == 'platform'
or path_components[0] == 'flag-specific')):
check_files.append((local_path, path_components[3]))
elif local_path == 'VirtualTestSuites':
check_all = True
if not check_all and len(check_files) == 0:
return []
# The rest of this test fails on Windows because win32pipe is not available
# and other errors.
if os.name == 'nt':
return []
from blinkpy.common.host import Host
port_factory = Host().port_factory
known_virtual_suites = [
suite.full_prefix[8:-1] for suite in port_factory.get(
port_factory.all_port_names()[0]).virtual_test_suites()
]
results = []
if check_all:
for f in input_api.change.AllFiles(
os_path.join(input_api.PresubmitLocalPath(), "virtual")):
suite = f.split('/')[0]
if not suite in known_virtual_suites:
path = os_path.relpath(
os_path.join(input_api.PresubmitLocalPath(), "virtual", f),
input_api.change.RepositoryRoot())
results.append(
output_api.PresubmitError(
"Baseline %s exists, but %s is not a known virtual test suite."
% (path, suite)))
for subdir in ["platform", "flag-specific"]:
for f in input_api.change.AllFiles(
os_path.join(input_api.PresubmitLocalPath(), subdir)):
path_components = f.split('/')
if len(path_components) < 3 or path_components[1] != 'virtual':
continue
suite = path_components[2]
if not suite in known_virtual_suites:
path = os_path.relpath(
os_path.join(input_api.PresubmitLocalPath(), subdir,
f), input_api.change.RepositoryRoot())
results.append(
output_api.PresubmitError(
"Baseline %s exists, but %s is not a known virtual test suite."
% (path, suite)))
else:
for (f, suite) in check_files:
if not suite in known_virtual_suites:
path = os_path.relpath(
os_path.join(input_api.PresubmitLocalPath(), f),
input_api.change.RepositoryRoot())
results.append(
output_api.PresubmitError(
"This CL adds a new baseline %s, but %s is not a known virtual test suite."
% (path, suite)))
return results
def _CheckWebViewExpectations(input_api, output_api):
src_dir = os.path.join(input_api.PresubmitLocalPath(), os.pardir,
os.pardir, os.pardir)
webview_data_dir = input_api.os_path.join(src_dir, 'android_webview',
'tools', 'system_webview_shell',
'test', 'data', 'webexposed')
if webview_data_dir not in sys.path:
sys.path.append(webview_data_dir)
# pylint: disable=import-outside-toplevel
from exposed_webview_interfaces_presubmit import (
CheckNotWebViewExposedInterfaces)
return CheckNotWebViewExposedInterfaces(input_api, output_api)
class _DoctypeParser(HTMLParser):
"""Parses HTML to check if there exists a DOCTYPE declaration before all other tags.
"""
def __init__(self):
super().__init__()
self.encountered_tag = False
self.doctype = ""
def handle_starttag(self, *_):
self.encountered_tag = True
def handle_startendtag(self, *_):
self.encountered_tag = True
def handle_decl(self, decl):
if not self.encountered_tag:
self.doctype = decl
self.encountered_tag = True
def _IsDoctypeHTMLSet(lines):
"""Returns true if the given HTML file starts with <!DOCTYPE html>.
"""
parser = _DoctypeParser()
for l in lines:
parser.feed(l)
return re.match("DOCTYPE\s*html\s*$", parser.doctype, re.IGNORECASE)
def _CheckForDoctypeHTML(input_api, output_api):
"""Checks that all changed HTML files start with the correct <!DOCTYPE html> tag.
"""
results = []
if input_api.no_diffs:
return results
wpt_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
"external", "wpt")
for f in input_api.AffectedFiles(include_deletes=False):
path = f.LocalPath()
fname = input_api.os_path.basename(path)
if not fname.endswith(".html") or "quirk" in fname:
continue
if not _IsDoctypeHTMLSet(f.NewContents()):
error = "HTML file \"%s\" does not start with <!DOCTYPE html>. " \
"If you really intend to test in quirks mode, add \"quirk\" " \
"to the name of your test." % path
if f.Action() == "A" or _IsDoctypeHTMLSet(f.OldContents()):
# These tests are being imported from WPT, so <!DOCTYPE html> is
# not required yet.
no_errors = f.AbsoluteLocalPath().startswith(wpt_path)
if no_errors:
results.append(output_api.PresubmitPromptWarning(error))
else:
results.append(output_api.PresubmitError(error))
return results
def _CheckNewVirtualSuites(input_api, output_api, max_suite_length: int = 48):
"""Validate new virtual test suites."""
# TODO(crbug.com/1380165): Once all virtual suites adopt "owners", consider
# making the field mandatory. In that case, we don't need to access the
# change contents and can promote this check to `lint_test_expectations.py`.
vts_path = input_api.os_path.join(input_api.PresubmitLocalPath(),
'VirtualTestSuites')
results = []
for affected_file in input_api.AffectedFiles():
if affected_file.AbsoluteLocalPath() != vts_path:
continue
old_contents = ''.join(affected_file.OldContents())
new_contents = ''.join(affected_file.NewContents())
try:
old_suites = _FilterForSuites(input_api.json.loads(old_contents))
new_suites = _FilterForSuites(input_api.json.loads(new_contents))
old_suite_names = {suite['prefix'] for suite in old_suites}
new_ownerless_suites, new_long_suites = [], []
for suite in new_suites:
prefix, owners = suite['prefix'], suite.get('owners', [])
if prefix in old_suite_names:
continue
if not owners:
new_ownerless_suites.append(prefix)
if len(prefix) > max_suite_length:
new_long_suites.append(prefix)
if new_ownerless_suites:
results.append(
output_api.PresubmitPromptWarning(
'Consider specifying "owners" (a list of emails) '
'for the virtual suites added by this patch:',
new_ownerless_suites))
if new_long_suites:
results.append(
output_api.PresubmitPromptWarning(
'Consider shorter virtual suite names so that the '
"global filename length presubmit doesn't reject "
'future `*-expected.txt` under their directories. You '
'can add comments about these suites to '
'VirtualTestSuites.', new_long_suites))
except (ValueError, KeyError):
# Invalid JSON or missing required fields will be detected by
# `lint_test_expectations.py`.
pass
break
return results
def _FilterForSuites(suites):
return [suite for suite in suites if not isinstance(suite, str)]
def CheckChangeOnUpload(input_api, output_api):
results = []
results.extend(_CheckTestharnessWdspecResults(input_api, output_api))
results.extend(_CheckFilesUsingEventSender(input_api, output_api))
results.extend(_CheckTestExpectations(input_api, output_api))
# `_CheckTestExpectations()` updates the WPT manifests for
# `_CheckForRedundantBaselines()`, so they must run in order. (Updating the
# manifest is needed to correctly detect tests but takes 10-15s, so try
# to only do so once; see crbug.com/1492238.)
results.extend(_CheckForRedundantBaselines(input_api, output_api))
results.extend(_CheckForJSTest(input_api, output_api))
results.extend(_CheckForInvalidPreferenceError(input_api, output_api))
results.extend(_CheckRunAfterLayoutAndPaintJS(input_api, output_api))
results.extend(_CheckForUnlistedTestFolder(input_api, output_api))
results.extend(_CheckForExtraVirtualBaselines(input_api, output_api))
results.extend(_CheckWebViewExpectations(input_api, output_api))
results.extend(_CheckForDoctypeHTML(input_api, output_api))
results.extend(_CheckNewVirtualSuites(input_api, output_api))
return results
def CheckChangeOnCommit(input_api, output_api):
results = []
results.extend(_CheckTestharnessWdspecResults(input_api, output_api))
results.extend(_CheckFilesUsingEventSender(input_api, output_api))
results.extend(_CheckTestExpectations(input_api, output_api))
# `_CheckTestExpectations()` updates the WPT manifests for
# `_CheckForRedundantBaselines()`, so they must run in order. (Updating the
# manifest is needed to correctly detect tests but takes 10-15s, so try
# to only do so once; see crbug.com/1492238.)
results.extend(_CheckForRedundantBaselines(input_api, output_api))
results.extend(_CheckForUnlistedTestFolder(input_api, output_api))
results.extend(_CheckForExtraVirtualBaselines(input_api, output_api))
results.extend(_CheckWebViewExpectations(input_api, output_api))
results.extend(_CheckForDoctypeHTML(input_api, output_api))
results.extend(_CheckNewVirtualSuites(input_api, output_api))
return results