blob: 926bc07c9a7685d963e237054a0931a922426eeb [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2020 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import collections
import json
import os
import subprocess
import sys
import tempfile
import unittest
def analyze(gn_path, tests_json, build_dir, changed_files):
"""
gn_path: Local path to gn binary (e.g. /work/fuchsia/prebuilt/linux64/gn)
tests_json: Local path to build's tests.json (e.g. out/default/tests.json)
build_dir: Fuchsia build dir (e.g. out/default)
changed_files: List of files to check against, relative to source root.
Returns: List of test URLs and host test paths that should be run.
"""
with open(tests_json) as f:
tests_obj = json.load(f)
label_to_test_packages = collections.defaultdict(list)
label_to_host_tests = collections.defaultdict(list)
test_labels = []
for t in tests_obj:
full_label = t['test']['label']
label_no_toolchain, _, toolchain = full_label.partition('(')
toolchain = toolchain[:-1]
# TODO: ARM, ASAN, etc.
if toolchain == '//build/toolchain/fuchsia:x64':
test_labels.append(full_label)
# This is the default toolchain, so when emitting these, GN drops
# the (//toolchain), so we need store these by toolchain-less-name,
# even though the tests.json name includes fully-qualified names.
label_to_test_packages[label_no_toolchain].append(
t['test']['package_url'])
elif toolchain == '//build/toolchain:host_x64':
test_labels.append(full_label)
label_to_host_tests[full_label].append(t['test']['path'])
# TODO: Handle other toolchain's tests, or otherwise signal failure
# cleanly and indicate that "everything" should be tested.
with tempfile.NamedTemporaryFile(suffix='.json', mode='wt') as tmp:
analyze_input = {
'files': ['//' + x for x in changed_files],
'test_targets': sorted(set(test_labels)),
'additional_compile_targets': ["all"],
}
tmp.write(json.dumps(analyze_input))
result = json.loads(
subprocess.check_output(
[gn_path, 'analyze', build_dir, tmp.name, '-']))
if 'error' in result:
print(result['error'], file=sys.stderr)
print(result, file=sys.stderr)
return None
if result['status'] == 'No dependency':
return []
else:
ret = []
for tl in result['test_targets']:
ret.extend(label_to_test_packages[tl])
ret.extend(label_to_host_tests[tl])
return ret
def main(args):
tests = analyze(
args.gn_path, args.tests_json, args.build_dir,
args.changed_files.splitlines())
if tests is None:
return 1
for t in tests:
print(t)
return 0
def tests_for(sources):
global BUILD_DIR_FOR_TESTS
host_platform = (
'linux-x64' if sys.platform.startswith('linux') else 'mac-x64')
gn_path = os.path.join(
BUILD_DIR_FOR_TESTS, os.pardir, os.pardir, 'prebuilt', 'third_party',
'gn', host_platform, 'gn')
tests_json = os.path.join(BUILD_DIR_FOR_TESTS, 'tests.json')
return analyze(gn_path, tests_json, BUILD_DIR_FOR_TESTS, sources)
class Test(unittest.TestCase):
def test_plain_cpp(self):
self.assertIn(
'fuchsia-pkg://fuchsia.com/fbl#meta/fbl-test.cmx',
tests_for(['zircon/system/ulib/fbl/test/slab_allocator_tests.cc']))
def test_cpp_header_change(self):
self.assertIn(
'fuchsia-pkg://fuchsia.com/fostr_unittests#meta/fostr_unittests.cmx',
tests_for(['garnet/public/lib/fostr/hex_dump.h']))
def test_multiple_packages_single_label(self):
tests = tests_for(['sdk/lib/fidl/cpp/array_unittest.cc'])
self.assertIn(
'fuchsia-pkg://fuchsia.com/fidl_tests#meta/conformance_test.cmx',
tests)
self.assertIn(
'fuchsia-pkg://fuchsia.com/fidl_tests#meta/fidl_cpp_unittests.cmx',
tests)
def test_host_tests(self):
tests = tests_for(['sdk/lib/fidl/cpp/array_unittest.cc'])
self.assertIn(
'fuchsia-pkg://fuchsia.com/fidl_tests#meta/conformance_test.cmx',
tests)
self.assertIn(
'fuchsia-pkg://fuchsia.com/fidl_tests#meta/fidl_cpp_unittests.cmx',
tests)
self.assertIn('host_x64/fidl_cpp_host_unittests', tests)
@unittest.skip('.rs files are not listed in sources yet.')
def test_plain_rust(self):
self.assertEqual(
[
'fuchsia-pkg://fuchsia.com/log_listener_tests#meta/log_listener_bin_test.cmx',
'fuchsia-pkg://fuchsia.com/log_listener_tests#meta/log_listener_return_code_test.cmx',
], tests_for(['garnet/bin/log_listener/src/main.rs']))
def test(fake_argv, build_dir):
global BUILD_DIR_FOR_TESTS
BUILD_DIR_FOR_TESTS = build_dir
unittest.main(argv=fake_argv, verbosity=2)
def parse_args_and_dispatch():
parser = argparse.ArgumentParser(
description='Determine affected tests, given a list of changed sources.'
)
parser.add_argument('--gn-path', help='path to gn binary')
parser.add_argument(
'--tests-json', help='path to tests.json (e.g. out/default/tests.json)')
parser.add_argument(
'--build-dir',
help='path to build dir (e.g. out/default)',
required=True)
parser.add_argument(
'--changed-files',
help='sources files to be checked, separated by newline')
parser.add_argument('action', help='either selftest or analyze')
args = parser.parse_args()
if args.action == 'selftest':
sys.exit(test([sys.argv[0]], args.build_dir))
elif args.action == 'analyze':
sys.exit(main(args))
else:
sys.exit(1)
if __name__ == '__main__':
sys.exit(parse_args_and_dispatch())