mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
303 lines
9.7 KiB
Python
Executable File
303 lines
9.7 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# Copyright 2015 The Chromium Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""Toolbox to manage all the json files in this directory.
|
|
|
|
It can reformat them in their canonical format or ensures they are well
|
|
formatted.
|
|
"""
|
|
|
|
import argparse
|
|
import ast
|
|
import collections
|
|
import glob
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
SRC_DIR = os.path.dirname(os.path.dirname(THIS_DIR))
|
|
sys.path.insert(0, os.path.join(SRC_DIR, 'third_party', 'colorama', 'src'))
|
|
|
|
import colorama
|
|
|
|
|
|
SKIP = {
|
|
# These are not 'builders'.
|
|
'compile_targets', 'gtest_tests', 'filter_compile_builders',
|
|
'non_filter_builders', 'non_filter_tests_builders',
|
|
|
|
# These are not supported on Swarming yet.
|
|
# http://crbug.com/472205
|
|
'Chromium Mac 10.10',
|
|
# http://crbug.com/441429
|
|
'Linux Trusty (32)', 'Linux Trusty (dbg)(32)',
|
|
|
|
# http://crbug.com/480053
|
|
'Linux GN',
|
|
'Linux GN (dbg)',
|
|
'linux_chromium_gn_rel',
|
|
|
|
# Unmaintained builders on chromium.fyi
|
|
'ClangToTMac',
|
|
'ClangToTMacASan',
|
|
|
|
# This builder is fine, but win8_chromium_ng uses GN and this configuration,
|
|
# which breaks everything.
|
|
'Win8 Aura',
|
|
|
|
# One off builders. Note that Swarming does support ARM.
|
|
'Linux ARM Cross-Compile',
|
|
'Site Isolation Linux',
|
|
'Site Isolation Win',
|
|
}
|
|
|
|
|
|
# TODO(GYP): These targets have not been ported to GN yet.
|
|
SKIP_NINJA_TO_GN_TARGETS = {
|
|
'cast_media_unittests',
|
|
'cast_shell_browser_test',
|
|
'chromevox_tests',
|
|
'nacl_helper_nonsfi_unittests',
|
|
}
|
|
|
|
|
|
class Error(Exception):
|
|
"""Processing error."""
|
|
|
|
|
|
def get_isolates():
|
|
"""Returns the list of all isolate files."""
|
|
files = subprocess.check_output(['git', 'ls-files'], cwd=SRC_DIR).splitlines()
|
|
return [os.path.basename(f) for f in files if f.endswith('.isolate')]
|
|
|
|
|
|
def process_builder_convert(data, test_name):
|
|
"""Converts 'test_name' to run on Swarming in 'data'.
|
|
|
|
Returns True if 'test_name' was found.
|
|
"""
|
|
result = False
|
|
for test in data['gtest_tests']:
|
|
if test['test'] != test_name:
|
|
continue
|
|
test.setdefault('swarming', {})
|
|
if not test['swarming'].get('can_use_on_swarming_builders'):
|
|
test['swarming']['can_use_on_swarming_builders'] = True
|
|
result = True
|
|
return result
|
|
|
|
|
|
def process_builder_remaining(data, filename, builder, tests_location):
|
|
"""Calculates tests_location when mode is --remaining."""
|
|
for test in data['gtest_tests']:
|
|
name = test['test']
|
|
if test.get('swarming', {}).get('can_use_on_swarming_builders'):
|
|
tests_location[name]['count_run_on_swarming'] += 1
|
|
else:
|
|
tests_location[name]['count_run_local'] += 1
|
|
tests_location[name]['local_configs'].setdefault(
|
|
filename, []).append(builder)
|
|
|
|
|
|
def process_file(mode, test_name, tests_location, filepath, ninja_targets,
|
|
ninja_targets_seen):
|
|
"""Processes a json file describing what tests should be run for each recipe.
|
|
|
|
The action depends on mode. Updates tests_location.
|
|
|
|
Return False if the process exit code should be 1.
|
|
"""
|
|
filename = os.path.basename(filepath)
|
|
with open(filepath) as f:
|
|
content = f.read()
|
|
try:
|
|
config = json.loads(content)
|
|
except ValueError as e:
|
|
raise Error('Exception raised while checking %s: %s' % (filepath, e))
|
|
|
|
for builder, data in sorted(config.iteritems()):
|
|
if builder in SKIP:
|
|
# Oddities.
|
|
continue
|
|
if not isinstance(data, dict):
|
|
raise Error('%s: %s is broken: %s' % (filename, builder, data))
|
|
if 'gtest_tests' not in data:
|
|
continue
|
|
if not isinstance(data['gtest_tests'], list):
|
|
raise Error(
|
|
'%s: %s is broken: %s' % (filename, builder, data['gtest_tests']))
|
|
if not all(isinstance(g, dict) for g in data['gtest_tests']):
|
|
raise Error(
|
|
'%s: %s is broken: %s' % (filename, builder, data['gtest_tests']))
|
|
|
|
for d in data['gtest_tests']:
|
|
if (d['test'] not in ninja_targets and
|
|
d['test'] not in SKIP_NINJA_TO_GN_TARGETS):
|
|
raise Error('%s: %s / %s is not listed in ninja_to_gn.pyl.' %
|
|
(filename, builder, d['test']))
|
|
elif d['test'] in ninja_targets:
|
|
ninja_targets_seen.add(d['test'])
|
|
|
|
config[builder]['gtest_tests'] = sorted(
|
|
data['gtest_tests'], key=lambda x: x['test'])
|
|
|
|
# The trick here is that process_builder_remaining() is called before
|
|
# process_builder_convert() so tests_location can be used to know how many
|
|
# tests were converted.
|
|
if mode in ('convert', 'remaining'):
|
|
process_builder_remaining(data, filename, builder, tests_location)
|
|
if mode == 'convert':
|
|
process_builder_convert(data, test_name)
|
|
|
|
expected = json.dumps(
|
|
config, sort_keys=True, indent=2, separators=(',', ': ')) + '\n'
|
|
if content != expected:
|
|
if mode in ('convert', 'write'):
|
|
with open(filepath, 'wb') as f:
|
|
f.write(expected)
|
|
if mode == 'write':
|
|
print('Updated %s' % filename)
|
|
else:
|
|
print('%s is not in canonical format' % filename)
|
|
print('run `testing/buildbot/manage.py -w` to fix')
|
|
return mode != 'check'
|
|
return True
|
|
|
|
|
|
def print_convert(test_name, tests_location):
|
|
"""Prints statistics for a test being converted for use in a CL description.
|
|
"""
|
|
data = tests_location[test_name]
|
|
print('Convert %s to run exclusively on Swarming' % test_name)
|
|
print('')
|
|
print('%d configs already ran on Swarming' % data['count_run_on_swarming'])
|
|
print('%d used to run locally and were converted:' % data['count_run_local'])
|
|
for master, builders in sorted(data['local_configs'].iteritems()):
|
|
for builder in builders:
|
|
print('- %s: %s' % (master, builder))
|
|
print('')
|
|
print('Ran:')
|
|
print(' ./manage.py --convert %s' % test_name)
|
|
print('')
|
|
print('R=')
|
|
print('BUG=98637')
|
|
|
|
|
|
def print_remaining(test_name, tests_location):
|
|
"""Prints a visual summary of what tests are yet to be converted to run on
|
|
Swarming.
|
|
"""
|
|
if test_name:
|
|
if test_name not in tests_location:
|
|
raise Error('Unknown test %s' % test_name)
|
|
for config, builders in sorted(
|
|
tests_location[test_name]['local_configs'].iteritems()):
|
|
print('%s:' % config)
|
|
for builder in sorted(builders):
|
|
print(' %s' % builder)
|
|
return
|
|
|
|
isolates = get_isolates()
|
|
l = max(map(len, tests_location))
|
|
print('%-*s%sLocal %sSwarming %sMissing isolate' %
|
|
(l, 'Test', colorama.Fore.RED, colorama.Fore.GREEN,
|
|
colorama.Fore.MAGENTA))
|
|
total_local = 0
|
|
total_swarming = 0
|
|
for name, location in sorted(tests_location.iteritems()):
|
|
if not location['count_run_on_swarming']:
|
|
c = colorama.Fore.RED
|
|
elif location['count_run_local']:
|
|
c = colorama.Fore.YELLOW
|
|
else:
|
|
c = colorama.Fore.GREEN
|
|
total_local += location['count_run_local']
|
|
total_swarming += location['count_run_on_swarming']
|
|
missing_isolate = ''
|
|
if name + '.isolate' not in isolates:
|
|
missing_isolate = colorama.Fore.MAGENTA + '*'
|
|
print('%s%-*s %4d %4d %s' %
|
|
(c, l, name, location['count_run_local'],
|
|
location['count_run_on_swarming'], missing_isolate))
|
|
|
|
total = total_local + total_swarming
|
|
p_local = 100. * total_local / total
|
|
p_swarming = 100. * total_swarming / total
|
|
print('%s%-*s %4d (%4.1f%%) %4d (%4.1f%%)' %
|
|
(colorama.Fore.WHITE, l, 'Total:', total_local, p_local,
|
|
total_swarming, p_swarming))
|
|
print('%-*s %4d' % (l, 'Total executions:', total))
|
|
|
|
|
|
def main():
|
|
colorama.init()
|
|
parser = argparse.ArgumentParser(description=sys.modules[__name__].__doc__)
|
|
group = parser.add_mutually_exclusive_group(required=True)
|
|
group.add_argument(
|
|
'-c', '--check', dest='mode', action='store_const', const='check',
|
|
default='check', help='Only check the files')
|
|
group.add_argument(
|
|
'--convert', dest='mode', action='store_const', const='convert',
|
|
help='Convert a test to run on Swarming everywhere')
|
|
group.add_argument(
|
|
'--remaining', dest='mode', action='store_const', const='remaining',
|
|
help='Count the number of tests not yet running on Swarming')
|
|
group.add_argument(
|
|
'-w', '--write', dest='mode', action='store_const', const='write',
|
|
help='Rewrite the files')
|
|
parser.add_argument(
|
|
'test_name', nargs='?',
|
|
help='The test name to print which configs to update; only to be used '
|
|
'with --remaining')
|
|
args = parser.parse_args()
|
|
|
|
if args.mode == 'convert':
|
|
if not args.test_name:
|
|
parser.error('A test name is required with --convert')
|
|
if args.test_name + '.isolate' not in get_isolates():
|
|
parser.error('Create %s.isolate first' % args.test_name)
|
|
|
|
# Stats when running in --remaining mode;
|
|
tests_location = collections.defaultdict(
|
|
lambda: {
|
|
'count_run_local': 0, 'count_run_on_swarming': 0, 'local_configs': {}
|
|
})
|
|
|
|
with open(os.path.join(THIS_DIR, "ninja_to_gn.pyl")) as fp:
|
|
ninja_targets = ast.literal_eval(fp.read())
|
|
|
|
try:
|
|
result = 0
|
|
ninja_targets_seen = set()
|
|
for filepath in glob.glob(os.path.join(THIS_DIR, '*.json')):
|
|
if not process_file(args.mode, args.test_name, tests_location, filepath,
|
|
ninja_targets, ninja_targets_seen):
|
|
result = 1
|
|
|
|
extra_targets = set(ninja_targets) - ninja_targets_seen
|
|
if extra_targets:
|
|
if len(extra_targets) > 1:
|
|
extra_targets_str = ', '.join(extra_targets) + ' are'
|
|
else:
|
|
extra_targets_str = list(extra_targets)[0] + ' is'
|
|
raise Error('%s listed in ninja_to_gn.pyl but not in any .json files' %
|
|
extra_targets_str)
|
|
|
|
if args.mode == 'convert':
|
|
print_convert(args.test_name, tests_location)
|
|
elif args.mode == 'remaining':
|
|
print_remaining(args.test_name, tests_location)
|
|
return result
|
|
except Error as e:
|
|
sys.stderr.write('%s\n' % e)
|
|
return 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|