|  | #!/usr/bin/env python3 | 
|  | # | 
|  | # Copyright 2013 The Flutter Authors. All rights reserved. | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  |  | 
|  | """ | 
|  | A top level harness to run all unit-tests in a specific engine build. | 
|  | """ | 
|  |  | 
|  | import argparse | 
|  | import glob | 
|  | import errno | 
|  | import multiprocessing | 
|  | import os | 
|  | import re | 
|  | import subprocess | 
|  | import sys | 
|  | import time | 
|  |  | 
|  | buildroot_dir = os.path.abspath( | 
|  | os.path.join(os.path.realpath(__file__), '..', '..', '..') | 
|  | ) | 
|  | out_dir = os.path.join(buildroot_dir, 'out') | 
|  | golden_dir = os.path.join(buildroot_dir, 'flutter', 'testing', 'resources') | 
|  | fonts_dir = os.path.join( | 
|  | buildroot_dir, 'flutter', 'third_party', 'txt', 'third_party', 'fonts' | 
|  | ) | 
|  | roboto_font_path = os.path.join(fonts_dir, 'Roboto-Regular.ttf') | 
|  | font_subset_dir = os.path.join(buildroot_dir, 'flutter', 'tools', 'font-subset') | 
|  |  | 
|  | fml_unittests_filter = '--gtest_filter=-*TimeSensitiveTest*' | 
|  |  | 
|  |  | 
|  | def PrintDivider(char='='): | 
|  | print('\n') | 
|  | for _ in range(4): | 
|  | print(''.join([char for _ in range(80)])) | 
|  | print('\n') | 
|  |  | 
|  |  | 
|  | def IsAsan(build_dir): | 
|  | with open(os.path.join(build_dir, 'args.gn')) as args: | 
|  | if 'is_asan = true' in args.read(): | 
|  | return True | 
|  |  | 
|  | return False | 
|  |  | 
|  |  | 
|  | def RunCmd(cmd, forbidden_output=[], expect_failure=False, env=None, **kwargs): | 
|  | command_string = ' '.join(cmd) | 
|  |  | 
|  | PrintDivider('>') | 
|  | print('Running command "%s"' % command_string) | 
|  |  | 
|  | start_time = time.time() | 
|  | stdout_pipe = sys.stdout if not forbidden_output else subprocess.PIPE | 
|  | stderr_pipe = sys.stderr if not forbidden_output else subprocess.PIPE | 
|  | process = subprocess.Popen( | 
|  | cmd, | 
|  | stdout=stdout_pipe, | 
|  | stderr=stderr_pipe, | 
|  | env=env, | 
|  | universal_newlines=True, | 
|  | **kwargs | 
|  | ) | 
|  | stdout, stderr = process.communicate() | 
|  | end_time = time.time() | 
|  |  | 
|  | if process.returncode != 0 and not expect_failure: | 
|  | PrintDivider('!') | 
|  |  | 
|  | print( | 
|  | 'Failed Command:\n\n%s\n\nExit Code: %d\n' % | 
|  | (command_string, process.returncode) | 
|  | ) | 
|  |  | 
|  | if stdout: | 
|  | print('STDOUT: \n%s' % stdout) | 
|  |  | 
|  | if stderr: | 
|  | print('STDERR: \n%s' % stderr) | 
|  |  | 
|  | PrintDivider('!') | 
|  |  | 
|  | raise Exception( | 
|  | 'Command "%s" exited with code %d.' % | 
|  | (command_string, process.returncode) | 
|  | ) | 
|  |  | 
|  | if stdout or stderr: | 
|  | print(stdout) | 
|  | print(stderr) | 
|  |  | 
|  | for forbidden_string in forbidden_output: | 
|  | if (stdout and forbidden_string in stdout) or (stderr and | 
|  | forbidden_string in stderr): | 
|  | raise Exception( | 
|  | 'command "%s" contained forbidden string %s' % | 
|  | (command_string, forbidden_string) | 
|  | ) | 
|  |  | 
|  | PrintDivider('<') | 
|  | print( | 
|  | 'Command run successfully in %.2f seconds: %s' % | 
|  | (end_time - start_time, command_string) | 
|  | ) | 
|  |  | 
|  |  | 
|  | def IsMac(): | 
|  | return sys.platform == 'darwin' | 
|  |  | 
|  |  | 
|  | def IsLinux(): | 
|  | return sys.platform.startswith('linux') | 
|  |  | 
|  |  | 
|  | def IsWindows(): | 
|  | return sys.platform.startswith(('cygwin', 'win')) | 
|  |  | 
|  |  | 
|  | def ExecutableSuffix(): | 
|  | return '.exe' if IsWindows() else '' | 
|  |  | 
|  |  | 
|  | def FindExecutablePath(path): | 
|  | if os.path.exists(path): | 
|  | return path | 
|  |  | 
|  | if IsWindows(): | 
|  | exe_path = path + '.exe' | 
|  | if os.path.exists(exe_path): | 
|  | return exe_path | 
|  |  | 
|  | bat_path = path + '.bat' | 
|  | if os.path.exists(bat_path): | 
|  | return bat_path | 
|  |  | 
|  | raise Exception('Executable %s does not exist!' % path) | 
|  |  | 
|  |  | 
|  | def BuildEngineExecutableCommand( | 
|  | build_dir, executable_name, flags=[], coverage=False, gtest=False | 
|  | ): | 
|  | unstripped_exe = os.path.join(build_dir, 'exe.unstripped', executable_name) | 
|  | # We cannot run the unstripped binaries directly when coverage is enabled. | 
|  | if IsLinux() and os.path.exists(unstripped_exe) and not coverage: | 
|  | # Use unstripped executables in order to get better symbolized crash | 
|  | # stack traces on Linux. | 
|  | executable = unstripped_exe | 
|  | else: | 
|  | executable = FindExecutablePath(os.path.join(build_dir, executable_name)) | 
|  |  | 
|  | coverage_script = os.path.join( | 
|  | buildroot_dir, 'flutter', 'build', 'generate_coverage.py' | 
|  | ) | 
|  |  | 
|  | if coverage: | 
|  | coverage_flags = [ | 
|  | '-t', executable, '-o', | 
|  | os.path.join(build_dir, 'coverage', executable_name), '-f', 'html' | 
|  | ] | 
|  | updated_flags = ['--args=%s' % ' '.join(flags)] | 
|  | test_command = [coverage_script] + coverage_flags + updated_flags | 
|  | else: | 
|  | test_command = [executable] + flags | 
|  | if gtest: | 
|  | gtest_parallel = os.path.join( | 
|  | buildroot_dir, 'third_party', 'gtest-parallel', 'gtest-parallel' | 
|  | ) | 
|  | test_command = ['python3', gtest_parallel] + test_command | 
|  |  | 
|  | return test_command | 
|  |  | 
|  |  | 
|  | def RunEngineExecutable( | 
|  | build_dir, | 
|  | executable_name, | 
|  | filter, | 
|  | flags=[], | 
|  | cwd=buildroot_dir, | 
|  | forbidden_output=[], | 
|  | expect_failure=False, | 
|  | coverage=False, | 
|  | extra_env={}, | 
|  | gtest=False | 
|  | ): | 
|  | if filter is not None and executable_name not in filter: | 
|  | print('Skipping %s due to filter.' % executable_name) | 
|  | return | 
|  |  | 
|  | unstripped_exe = os.path.join(build_dir, 'exe.unstripped', executable_name) | 
|  | env = os.environ.copy() | 
|  | if IsLinux(): | 
|  | env['LD_LIBRARY_PATH'] = build_dir | 
|  | env['VK_DRIVER_FILES'] = os.path.join(build_dir, 'vk_swiftshader_icd.json') | 
|  | if os.path.exists(unstripped_exe): | 
|  | try: | 
|  | os.symlink( | 
|  | os.path.join(build_dir, 'lib.unstripped', 'libvulkan.so.1'), | 
|  | os.path.join(build_dir, 'exe.unstripped', 'libvulkan.so.1') | 
|  | ) | 
|  | except OSError as e: | 
|  | if e.errno == errno.EEXIST: | 
|  | pass | 
|  | else: | 
|  | raise | 
|  | elif IsMac(): | 
|  | env['DYLD_LIBRARY_PATH'] = build_dir | 
|  | else: | 
|  | env['PATH'] = build_dir + ":" + env['PATH'] | 
|  |  | 
|  | print('Running %s in %s' % (executable_name, cwd)) | 
|  |  | 
|  | test_command = BuildEngineExecutableCommand( | 
|  | build_dir, | 
|  | executable_name, | 
|  | flags=flags, | 
|  | coverage=coverage, | 
|  | gtest=gtest, | 
|  | ) | 
|  |  | 
|  | env['FLUTTER_BUILD_DIRECTORY'] = build_dir | 
|  | for key, value in extra_env.items(): | 
|  | env[key] = value | 
|  |  | 
|  | try: | 
|  | RunCmd( | 
|  | test_command, | 
|  | cwd=cwd, | 
|  | forbidden_output=forbidden_output, | 
|  | expect_failure=expect_failure, | 
|  | env=env | 
|  | ) | 
|  | except: | 
|  | # The LUCI environment may provide a variable containing a directory path | 
|  | # for additional output files that will be uploaded to cloud storage. | 
|  | # If the command generated a core dump, then run a script to analyze | 
|  | # the dump and output a report that will be uploaded. | 
|  | luci_test_outputs_path = os.environ.get('FLUTTER_TEST_OUTPUTS_DIR') | 
|  | core_path = os.path.join(cwd, 'core') | 
|  | if luci_test_outputs_path and os.path.exists(core_path) and os.path.exists( | 
|  | unstripped_exe): | 
|  | dump_path = os.path.join( | 
|  | luci_test_outputs_path, '%s_%s.txt' % (executable_name, sys.platform) | 
|  | ) | 
|  | print('Writing core dump analysis to %s' % dump_path) | 
|  | subprocess.call([ | 
|  | os.path.join( | 
|  | buildroot_dir, 'flutter', 'testing', 'analyze_core_dump.sh' | 
|  | ), | 
|  | buildroot_dir, | 
|  | unstripped_exe, | 
|  | core_path, | 
|  | dump_path, | 
|  | ]) | 
|  | os.unlink(core_path) | 
|  | raise | 
|  |  | 
|  |  | 
|  | class EngineExecutableTask(object): | 
|  |  | 
|  | def __init__( | 
|  | self, | 
|  | build_dir, | 
|  | executable_name, | 
|  | filter, | 
|  | flags=[], | 
|  | cwd=buildroot_dir, | 
|  | forbidden_output=[], | 
|  | expect_failure=False, | 
|  | coverage=False, | 
|  | extra_env={} | 
|  | ): | 
|  | self.build_dir = build_dir | 
|  | self.executable_name = executable_name | 
|  | self.filter = filter | 
|  | self.flags = flags | 
|  | self.cwd = cwd | 
|  | self.forbidden_output = forbidden_output | 
|  | self.expect_failure = expect_failure | 
|  | self.coverage = coverage | 
|  | self.extra_env = extra_env | 
|  |  | 
|  | def __call__(self, *args): | 
|  | RunEngineExecutable( | 
|  | self.build_dir, | 
|  | self.executable_name, | 
|  | self.filter, | 
|  | flags=self.flags, | 
|  | cwd=self.cwd, | 
|  | forbidden_output=self.forbidden_output, | 
|  | expect_failure=self.expect_failure, | 
|  | coverage=self.coverage, | 
|  | extra_env=self.extra_env, | 
|  | ) | 
|  |  | 
|  | def __str__(self): | 
|  | command = BuildEngineExecutableCommand( | 
|  | self.build_dir, | 
|  | self.executable_name, | 
|  | flags=self.flags, | 
|  | coverage=self.coverage | 
|  | ) | 
|  | return " ".join(command) | 
|  |  | 
|  |  | 
|  | def RunCCTests(build_dir, filter, coverage, capture_core_dump): | 
|  | print("Running Engine Unit-tests.") | 
|  |  | 
|  | if capture_core_dump and IsLinux(): | 
|  | import resource | 
|  | resource.setrlimit( | 
|  | resource.RLIMIT_CORE, (resource.RLIM_INFINITY, resource.RLIM_INFINITY) | 
|  | ) | 
|  |  | 
|  | shuffle_flags = [ | 
|  | "--gtest_repeat=2", | 
|  | "--gtest_shuffle", | 
|  | ] | 
|  |  | 
|  | repeat_flags = [ | 
|  | "--repeat=2", | 
|  | ] | 
|  |  | 
|  | def make_test(name, flags=repeat_flags, extra_env={}): | 
|  | return (name, flags, extra_env) | 
|  |  | 
|  | unittests = [ | 
|  | make_test('client_wrapper_glfw_unittests'), | 
|  | make_test('client_wrapper_unittests'), | 
|  | make_test('common_cpp_core_unittests'), | 
|  | make_test('common_cpp_unittests'), | 
|  | make_test('dart_plugin_registrant_unittests'), | 
|  | make_test('display_list_rendertests'), | 
|  | make_test('display_list_unittests'), | 
|  | make_test('embedder_a11y_unittests'), | 
|  | make_test('embedder_proctable_unittests'), | 
|  | make_test('embedder_unittests'), | 
|  | make_test('fml_unittests', flags=[fml_unittests_filter] + repeat_flags), | 
|  | make_test('no_dart_plugin_registrant_unittests'), | 
|  | make_test('runtime_unittests'), | 
|  | make_test('testing_unittests'), | 
|  | make_test('tonic_unittests'), | 
|  | # The image release unit test can take a while on slow machines. | 
|  | make_test('ui_unittests', flags=repeat_flags + ['--timeout=90']), | 
|  | ] | 
|  |  | 
|  | if not IsWindows(): | 
|  | unittests += [ | 
|  | # https://github.com/google/googletest/issues/2490 | 
|  | make_test('android_external_view_embedder_unittests'), | 
|  | make_test('jni_unittests'), | 
|  | make_test('platform_view_android_delegate_unittests'), | 
|  | # https://github.com/flutter/flutter/issues/36295 | 
|  | make_test('shell_unittests'), | 
|  | ] | 
|  |  | 
|  | if IsWindows(): | 
|  | unittests += [ | 
|  | # The accessibility library only supports Mac and Windows. | 
|  | make_test('accessibility_unittests'), | 
|  | make_test('client_wrapper_windows_unittests'), | 
|  | make_test('flutter_windows_unittests'), | 
|  | ] | 
|  |  | 
|  | # These unit-tests are Objective-C and can only run on Darwin. | 
|  | if IsMac(): | 
|  | unittests += [ | 
|  | # The accessibility library only supports Mac and Windows. | 
|  | make_test('accessibility_unittests'), | 
|  | make_test('flutter_channels_unittests'), | 
|  | ] | 
|  |  | 
|  | if IsLinux(): | 
|  | flow_flags = [ | 
|  | '--golden-dir=%s' % golden_dir, | 
|  | '--font-file=%s' % roboto_font_path, | 
|  | ] | 
|  | icu_flags = [ | 
|  | '--icu-data-file-path=%s' % os.path.join(build_dir, 'icudtl.dat') | 
|  | ] | 
|  | unittests += [ | 
|  | make_test('flow_unittests', flags=repeat_flags + ['--'] + flow_flags), | 
|  | make_test('flutter_glfw_unittests'), | 
|  | make_test( | 
|  | 'flutter_linux_unittests', extra_env={'G_DEBUG': 'fatal-criticals'} | 
|  | ), | 
|  | # https://github.com/flutter/flutter/issues/36296 | 
|  | make_test('txt_unittests', flags=repeat_flags + ['--'] + icu_flags), | 
|  | ] | 
|  | else: | 
|  | flow_flags = ['--gtest_filter=-PerformanceOverlayLayer.Gold'] | 
|  | unittests += [ | 
|  | make_test('flow_unittests', flags=repeat_flags + flow_flags), | 
|  | ] | 
|  |  | 
|  | for test, flags, extra_env in unittests: | 
|  | RunEngineExecutable( | 
|  | build_dir, | 
|  | test, | 
|  | filter, | 
|  | flags, | 
|  | coverage=coverage, | 
|  | extra_env=extra_env, | 
|  | gtest=True | 
|  | ) | 
|  |  | 
|  | if IsMac(): | 
|  | # flutter_desktop_darwin_unittests uses global state that isn't handled | 
|  | # correctly by gtest-parallel. | 
|  | # https://github.com/flutter/flutter/issues/104789 | 
|  | RunEngineExecutable( | 
|  | build_dir, | 
|  | 'flutter_desktop_darwin_unittests', | 
|  | filter, | 
|  | shuffle_flags, | 
|  | coverage=coverage | 
|  | ) | 
|  | # Impeller tests are only supported on macOS for now. | 
|  | RunEngineExecutable( | 
|  | build_dir, | 
|  | 'impeller_unittests', | 
|  | filter, | 
|  | shuffle_flags, | 
|  | coverage=coverage | 
|  | ) | 
|  |  | 
|  |  | 
|  | def RunEngineBenchmarks(build_dir, filter): | 
|  | print("Running Engine Benchmarks.") | 
|  |  | 
|  | icu_flags = [ | 
|  | '--icu-data-file-path=%s' % os.path.join(build_dir, 'icudtl.dat') | 
|  | ] | 
|  |  | 
|  | RunEngineExecutable(build_dir, 'shell_benchmarks', filter, icu_flags) | 
|  |  | 
|  | RunEngineExecutable(build_dir, 'fml_benchmarks', filter, icu_flags) | 
|  |  | 
|  | RunEngineExecutable(build_dir, 'ui_benchmarks', filter, icu_flags) | 
|  |  | 
|  | if IsLinux(): | 
|  | RunEngineExecutable(build_dir, 'txt_benchmarks', filter, icu_flags) | 
|  |  | 
|  |  | 
|  | def GatherDartTest( | 
|  | build_dir, | 
|  | test_packages, | 
|  | dart_file, | 
|  | verbose_dart_snapshot, | 
|  | multithreaded, | 
|  | enable_observatory=False, | 
|  | expect_failure=False, | 
|  | alternative_tester=False | 
|  | ): | 
|  | kernel_file_name = os.path.basename(dart_file) + '.dill' | 
|  | kernel_file_output = os.path.join(build_dir, 'gen', kernel_file_name) | 
|  | error_message = "%s doesn't exist. Please run the build that populates %s" % ( | 
|  | kernel_file_output, build_dir | 
|  | ) | 
|  | assert os.path.isfile(kernel_file_output), error_message | 
|  |  | 
|  | command_args = [] | 
|  | if not enable_observatory: | 
|  | command_args.append('--disable-observatory') | 
|  |  | 
|  | dart_file_contents = open(dart_file, 'r') | 
|  | custom_options = re.findall( | 
|  | "// FlutterTesterOptions=(.*)", dart_file_contents.read() | 
|  | ) | 
|  | dart_file_contents.close() | 
|  | command_args.extend(custom_options) | 
|  |  | 
|  | command_args += [ | 
|  | '--use-test-fonts', | 
|  | '--icu-data-file-path=%s' % os.path.join(build_dir, 'icudtl.dat'), | 
|  | '--flutter-assets-dir=%s' % | 
|  | os.path.join(build_dir, 'gen', 'flutter', 'lib', 'ui', 'assets'), | 
|  | '--disable-asset-fonts', | 
|  | kernel_file_output, | 
|  | ] | 
|  |  | 
|  | if multithreaded: | 
|  | threading = 'multithreaded' | 
|  | command_args.insert(0, '--force-multithreading') | 
|  | else: | 
|  | threading = 'single-threaded' | 
|  |  | 
|  | tester_name = 'flutter_tester' | 
|  | print( | 
|  | "Running test '%s' using '%s' (%s)" % | 
|  | (kernel_file_name, tester_name, threading) | 
|  | ) | 
|  | forbidden_output = [] if 'unopt' in build_dir or expect_failure else [ | 
|  | '[ERROR' | 
|  | ] | 
|  | return EngineExecutableTask( | 
|  | build_dir, | 
|  | tester_name, | 
|  | None, | 
|  | command_args, | 
|  | forbidden_output=forbidden_output, | 
|  | expect_failure=expect_failure, | 
|  | ) | 
|  |  | 
|  |  | 
|  | def EnsureDebugUnoptSkyPackagesAreBuilt(): | 
|  | variant_out_dir = os.path.join(out_dir, 'host_debug_unopt') | 
|  | message = [] | 
|  | message.append('gn --runtime-mode debug --unopt --no-lto') | 
|  | message.append('ninja -C %s flutter/sky/packages' % variant_out_dir) | 
|  | final_message = '%s doesn\'t exist. Please run the following commands: \n%s' % ( | 
|  | variant_out_dir, '\n'.join(message) | 
|  | ) | 
|  | assert os.path.exists(variant_out_dir), final_message | 
|  |  | 
|  |  | 
|  | def EnsureIosTestsAreBuilt(ios_out_dir): | 
|  | """Builds the engine variant and the test dylib containing the XCTests""" | 
|  | tmp_out_dir = os.path.join(out_dir, ios_out_dir) | 
|  | ios_test_lib = os.path.join(tmp_out_dir, 'libios_test_flutter.dylib') | 
|  | message = [] | 
|  | message.append( | 
|  | 'gn --ios --unoptimized --runtime-mode=debug --no-lto --simulator' | 
|  | ) | 
|  | message.append('autoninja -C %s ios_test_flutter' % ios_out_dir) | 
|  | final_message = '%s or %s doesn\'t exist. Please run the following commands: \n%s' % ( | 
|  | ios_out_dir, ios_test_lib, '\n'.join(message) | 
|  | ) | 
|  | assert os.path.exists(tmp_out_dir | 
|  | ) and os.path.exists(ios_test_lib), final_message | 
|  |  | 
|  |  | 
|  | def AssertExpectedXcodeVersion(): | 
|  | """Checks that the user has a version of Xcode installed""" | 
|  | version_output = subprocess.check_output(['xcodebuild', '-version']) | 
|  | match = re.match(b"Xcode (\d+)", version_output) | 
|  | message = "Xcode must be installed to run the iOS embedding unit tests" | 
|  | assert match, message | 
|  |  | 
|  |  | 
|  | def JavaHome(): | 
|  | script_path = os.path.dirname(os.path.realpath(__file__)) | 
|  | if IsMac(): | 
|  | return os.path.join( | 
|  | script_path, '..', '..', 'third_party', 'java', 'openjdk', 'Contents', | 
|  | 'Home' | 
|  | ) | 
|  | else: | 
|  | return os.path.join( | 
|  | script_path, '..', '..', 'third_party', 'java', 'openjdk' | 
|  | ) | 
|  |  | 
|  |  | 
|  | def JavaBin(): | 
|  | return os.path.join(JavaHome(), 'bin', 'java.exe' if IsWindows() else 'java') | 
|  |  | 
|  |  | 
|  | def RunJavaTests(filter, android_variant='android_debug_unopt'): | 
|  | """Runs the Java JUnit unit tests for the Android embedding""" | 
|  | test_runner_dir = os.path.join( | 
|  | buildroot_dir, 'flutter', 'shell', 'platform', 'android', 'test_runner' | 
|  | ) | 
|  | gradle_bin = os.path.join( | 
|  | buildroot_dir, 'third_party', 'gradle', 'bin', | 
|  | 'gradle.bat' if IsWindows() else 'gradle' | 
|  | ) | 
|  | flutter_jar = os.path.join(out_dir, android_variant, 'flutter.jar') | 
|  | android_home = os.path.join( | 
|  | buildroot_dir, 'third_party', 'android_tools', 'sdk' | 
|  | ) | 
|  | build_dir = os.path.join( | 
|  | out_dir, android_variant, 'robolectric_tests', 'build' | 
|  | ) | 
|  | gradle_cache_dir = os.path.join( | 
|  | out_dir, android_variant, 'robolectric_tests', '.gradle' | 
|  | ) | 
|  |  | 
|  | test_class = filter if filter else '*' | 
|  | command = [ | 
|  | gradle_bin, | 
|  | '-Pflutter_jar=%s' % flutter_jar, | 
|  | '-Pbuild_dir=%s' % build_dir, | 
|  | 'testDebugUnitTest', | 
|  | '--tests=%s' % test_class, | 
|  | '--rerun-tasks', | 
|  | '--no-daemon', | 
|  | '--project-cache-dir=%s' % gradle_cache_dir, | 
|  | '--gradle-user-home=%s' % gradle_cache_dir, | 
|  | ] | 
|  |  | 
|  | env = dict(os.environ, ANDROID_HOME=android_home, JAVA_HOME=JavaHome()) | 
|  | RunCmd(command, cwd=test_runner_dir, env=env) | 
|  |  | 
|  |  | 
|  | def RunAndroidTests(android_variant='android_debug_unopt', adb_path=None): | 
|  | test_runner_name = 'flutter_shell_native_unittests' | 
|  | tests_path = os.path.join(out_dir, android_variant, test_runner_name) | 
|  | remote_path = '/data/local/tmp' | 
|  | remote_tests_path = os.path.join(remote_path, test_runner_name) | 
|  | if adb_path == None: | 
|  | adb_path = 'adb' | 
|  | RunCmd([adb_path, 'push', tests_path, remote_path], cwd=buildroot_dir) | 
|  | RunCmd([adb_path, 'shell', remote_tests_path]) | 
|  |  | 
|  | systrace_test = os.path.join( | 
|  | buildroot_dir, 'flutter', 'testing', 'android_systrace_test.py' | 
|  | ) | 
|  | scenario_apk = os.path.join( | 
|  | out_dir, android_variant, 'firebase_apks', 'scenario_app.apk' | 
|  | ) | 
|  | RunCmd([ | 
|  | systrace_test, '--adb-path', adb_path, '--apk-path', scenario_apk, | 
|  | '--package-name', 'dev.flutter.scenarios', '--activity-name', | 
|  | '.PlatformViewsActivity' | 
|  | ]) | 
|  |  | 
|  |  | 
|  | def RunObjcTests(ios_variant='ios_debug_sim_unopt', test_filter=None): | 
|  | """Runs Objective-C XCTest unit tests for the iOS embedding""" | 
|  | AssertExpectedXcodeVersion() | 
|  | ios_out_dir = os.path.join(out_dir, ios_variant) | 
|  | EnsureIosTestsAreBuilt(ios_out_dir) | 
|  |  | 
|  | new_simulator_name = 'IosUnitTestsSimulator' | 
|  |  | 
|  | # Delete simulators with this name in case any were leaked | 
|  | # from another test run. | 
|  | DeleteSimulator(new_simulator_name) | 
|  |  | 
|  | create_simulator = [ | 
|  | 'xcrun ' | 
|  | 'simctl ' | 
|  | 'create ' | 
|  | '%s com.apple.CoreSimulator.SimDeviceType.iPhone-11' % new_simulator_name | 
|  | ] | 
|  | RunCmd(create_simulator, shell=True) | 
|  |  | 
|  | try: | 
|  | ios_unit_test_dir = os.path.join( | 
|  | buildroot_dir, 'flutter', 'testing', 'ios', 'IosUnitTests' | 
|  | ) | 
|  | # Avoid using xcpretty unless the following can be addressed: | 
|  | # - Make sure all relevant failure output is printed on a failure. | 
|  | # - Make sure that a failing exit code is set for CI. | 
|  | # See https://github.com/flutter/flutter/issues/63742 | 
|  | test_command = [ | 
|  | 'xcodebuild ' | 
|  | '-sdk iphonesimulator ' | 
|  | '-scheme IosUnitTests ' | 
|  | "-destination name='" + new_simulator_name + "' " | 
|  | 'test ' | 
|  | 'FLUTTER_ENGINE=' + ios_variant | 
|  | ] | 
|  | if test_filter != None: | 
|  | test_command[0] = test_command[0] + " -only-testing:%s" % test_filter | 
|  | RunCmd(test_command, cwd=ios_unit_test_dir, shell=True) | 
|  | finally: | 
|  | DeleteSimulator(new_simulator_name) | 
|  |  | 
|  |  | 
|  | def DeleteSimulator(simulator_name): | 
|  | # Will delete all simulators with this name. | 
|  | delete_simulator = [ | 
|  | 'xcrun', | 
|  | 'simctl', | 
|  | 'delete', | 
|  | simulator_name, | 
|  | ] | 
|  | # Let this fail if the simulator was never created. | 
|  | RunCmd(delete_simulator, expect_failure=True) | 
|  |  | 
|  |  | 
|  | def GatherDartTests(build_dir, filter, verbose_dart_snapshot): | 
|  | dart_tests_dir = os.path.join( | 
|  | buildroot_dir, | 
|  | 'flutter', | 
|  | 'testing', | 
|  | 'dart', | 
|  | ) | 
|  |  | 
|  | # This one is a bit messy. The pubspec.yaml at flutter/testing/dart/pubspec.yaml | 
|  | # has dependencies that are hardcoded to point to the sky packages at host_debug_unopt/ | 
|  | # Before running Dart tests, make sure to run just that target (NOT the whole engine) | 
|  | EnsureDebugUnoptSkyPackagesAreBuilt() | 
|  |  | 
|  | # Now that we have the Sky packages at the hardcoded location, run `dart pub get`. | 
|  | RunEngineExecutable( | 
|  | build_dir, | 
|  | os.path.join('dart-sdk', 'bin', 'dart'), | 
|  | None, | 
|  | flags=['pub', 'get', '--offline'], | 
|  | cwd=dart_tests_dir, | 
|  | ) | 
|  |  | 
|  | dart_observatory_tests = glob.glob( | 
|  | '%s/observatory/*_test.dart' % dart_tests_dir | 
|  | ) | 
|  | dart_tests = glob.glob('%s/*_test.dart' % dart_tests_dir) | 
|  | test_packages = os.path.join( | 
|  | dart_tests_dir, '.dart_tool', 'package_config.json' | 
|  | ) | 
|  |  | 
|  | if 'release' not in build_dir: | 
|  | for dart_test_file in dart_observatory_tests: | 
|  | if filter is not None and os.path.basename(dart_test_file) not in filter: | 
|  | print("Skipping '%s' due to filter." % dart_test_file) | 
|  | else: | 
|  | print( | 
|  | "Gathering dart test '%s' with observatory enabled" % dart_test_file | 
|  | ) | 
|  | yield GatherDartTest( | 
|  | build_dir, test_packages, dart_test_file, verbose_dart_snapshot, | 
|  | True, True | 
|  | ) | 
|  | yield GatherDartTest( | 
|  | build_dir, test_packages, dart_test_file, verbose_dart_snapshot, | 
|  | False, True | 
|  | ) | 
|  |  | 
|  | for dart_test_file in dart_tests: | 
|  | if filter is not None and os.path.basename(dart_test_file) not in filter: | 
|  | print("Skipping '%s' due to filter." % dart_test_file) | 
|  | else: | 
|  | print("Gathering dart test '%s'" % dart_test_file) | 
|  | yield GatherDartTest( | 
|  | build_dir, test_packages, dart_test_file, verbose_dart_snapshot, True | 
|  | ) | 
|  | yield GatherDartTest( | 
|  | build_dir, test_packages, dart_test_file, verbose_dart_snapshot, False | 
|  | ) | 
|  |  | 
|  |  | 
|  | def GatherDartSmokeTest(build_dir, verbose_dart_snapshot): | 
|  | smoke_test = os.path.join( | 
|  | buildroot_dir, "flutter", "testing", "smoke_test_failure", | 
|  | "fail_test.dart" | 
|  | ) | 
|  | test_packages = os.path.join( | 
|  | buildroot_dir, "flutter", "testing", "smoke_test_failure", ".dart_tool", | 
|  | "package_config.json" | 
|  | ) | 
|  | yield GatherDartTest( | 
|  | build_dir, | 
|  | test_packages, | 
|  | smoke_test, | 
|  | verbose_dart_snapshot, | 
|  | True, | 
|  | expect_failure=True | 
|  | ) | 
|  | yield GatherDartTest( | 
|  | build_dir, | 
|  | test_packages, | 
|  | smoke_test, | 
|  | verbose_dart_snapshot, | 
|  | False, | 
|  | expect_failure=True | 
|  | ) | 
|  |  | 
|  |  | 
|  | def GatherFrontEndServerTests(build_dir): | 
|  | test_dir = os.path.join(buildroot_dir, 'flutter', 'flutter_frontend_server') | 
|  | dart_tests = glob.glob('%s/test/*_test.dart' % test_dir) | 
|  | for dart_test_file in dart_tests: | 
|  | opts = [ | 
|  | '--disable-dart-dev', dart_test_file, build_dir, | 
|  | os.path.join(build_dir, 'gen', 'frontend_server.dart.snapshot'), | 
|  | os.path.join(build_dir, 'flutter_patched_sdk') | 
|  | ] | 
|  | yield EngineExecutableTask( | 
|  | build_dir, | 
|  | os.path.join('dart-sdk', 'bin', 'dart'), | 
|  | None, | 
|  | flags=opts, | 
|  | cwd=test_dir | 
|  | ) | 
|  |  | 
|  |  | 
|  | def GatherPathOpsTests(build_dir): | 
|  | # TODO(dnfield): https://github.com/flutter/flutter/issues/107321 | 
|  | if IsAsan(build_dir): | 
|  | return | 
|  |  | 
|  | test_dir = os.path.join( | 
|  | buildroot_dir, 'flutter', 'tools', 'path_ops', 'dart', 'test' | 
|  | ) | 
|  | opts = ['--disable-dart-dev', os.path.join(test_dir, 'path_ops_test.dart')] | 
|  | yield EngineExecutableTask( | 
|  | build_dir, | 
|  | os.path.join('dart-sdk', 'bin', 'dart'), | 
|  | None, | 
|  | flags=opts, | 
|  | cwd=test_dir | 
|  | ) | 
|  |  | 
|  |  | 
|  | def GatherConstFinderTests(build_dir): | 
|  | test_dir = os.path.join( | 
|  | buildroot_dir, 'flutter', 'tools', 'const_finder', 'test' | 
|  | ) | 
|  | opts = [ | 
|  | '--disable-dart-dev', | 
|  | os.path.join(test_dir, 'const_finder_test.dart'), | 
|  | os.path.join(build_dir, 'gen', 'frontend_server.dart.snapshot'), | 
|  | os.path.join(build_dir, 'flutter_patched_sdk') | 
|  | ] | 
|  | yield EngineExecutableTask( | 
|  | build_dir, | 
|  | os.path.join('dart-sdk', 'bin', 'dart'), | 
|  | None, | 
|  | flags=opts, | 
|  | cwd=test_dir | 
|  | ) | 
|  |  | 
|  |  | 
|  | def GatherLitetestTests(build_dir): | 
|  | test_dir = os.path.join(buildroot_dir, 'flutter', 'testing', 'litetest') | 
|  | dart_tests = glob.glob('%s/test/*_test.dart' % test_dir) | 
|  | for dart_test_file in dart_tests: | 
|  | opts = ['--disable-dart-dev', dart_test_file] | 
|  | yield EngineExecutableTask( | 
|  | build_dir, | 
|  | os.path.join('dart-sdk', 'bin', 'dart'), | 
|  | None, | 
|  | flags=opts, | 
|  | cwd=test_dir | 
|  | ) | 
|  |  | 
|  |  | 
|  | def RunBenchmarkTests(build_dir): | 
|  | test_dir = os.path.join(buildroot_dir, 'flutter', 'testing', 'benchmark') | 
|  | dart_tests = glob.glob('%s/test/*_test.dart' % test_dir) | 
|  | for dart_test_file in dart_tests: | 
|  | opts = ['--disable-dart-dev', dart_test_file] | 
|  | RunEngineExecutable( | 
|  | build_dir, | 
|  | os.path.join('dart-sdk', 'bin', 'dart'), | 
|  | None, | 
|  | flags=opts, | 
|  | cwd=test_dir | 
|  | ) | 
|  |  | 
|  |  | 
|  | def GatherGithooksTests(build_dir): | 
|  | test_dir = os.path.join(buildroot_dir, 'flutter', 'tools', 'githooks') | 
|  | dart_tests = glob.glob('%s/test/*_test.dart' % test_dir) | 
|  | for dart_test_file in dart_tests: | 
|  | opts = ['--disable-dart-dev', dart_test_file] | 
|  | yield EngineExecutableTask( | 
|  | build_dir, | 
|  | os.path.join('dart-sdk', 'bin', 'dart'), | 
|  | None, | 
|  | flags=opts, | 
|  | cwd=test_dir | 
|  | ) | 
|  |  | 
|  |  | 
|  | def GatherClangTidyTests(build_dir): | 
|  | test_dir = os.path.join(buildroot_dir, 'flutter', 'tools', 'clang_tidy') | 
|  | dart_tests = glob.glob('%s/test/*_test.dart' % test_dir) | 
|  | for dart_test_file in dart_tests: | 
|  | opts = [ | 
|  | '--disable-dart-dev', dart_test_file, | 
|  | os.path.join(build_dir, 'compile_commands.json'), | 
|  | os.path.join(buildroot_dir, 'flutter') | 
|  | ] | 
|  | yield EngineExecutableTask( | 
|  | build_dir, | 
|  | os.path.join('dart-sdk', 'bin', 'dart'), | 
|  | None, | 
|  | flags=opts, | 
|  | cwd=test_dir | 
|  | ) | 
|  |  | 
|  |  | 
|  | def GatherApiConsistencyTests(build_dir): | 
|  | test_dir = os.path.join(buildroot_dir, 'flutter', 'tools', 'api_check') | 
|  | dart_tests = glob.glob('%s/test/*_test.dart' % test_dir) | 
|  | for dart_test_file in dart_tests: | 
|  | opts = [ | 
|  | '--disable-dart-dev', dart_test_file, | 
|  | os.path.join(buildroot_dir, 'flutter') | 
|  | ] | 
|  | yield EngineExecutableTask( | 
|  | build_dir, | 
|  | os.path.join('dart-sdk', 'bin', 'dart'), | 
|  | None, | 
|  | flags=opts, | 
|  | cwd=test_dir | 
|  | ) | 
|  |  | 
|  |  | 
|  | def RunEngineTasksInParallel(tasks): | 
|  | # Work around a bug in Python. | 
|  | # | 
|  | # The multiprocessing package relies on the win32 WaitForMultipleObjects() | 
|  | # call, which supports waiting on a maximum of MAXIMUM_WAIT_OBJECTS (defined | 
|  | # by Windows to be 64) handles, processes in this case. To avoid hitting | 
|  | # this, we limit ourselves to 60 handles (since there are a couple extra | 
|  | # processes launched for the queue reader and thread wakeup reader). | 
|  | # | 
|  | # See: https://bugs.python.org/issue26903 | 
|  | max_processes = multiprocessing.cpu_count() | 
|  | if sys.platform.startswith(('cygwin', 'win')) and max_processes > 60: | 
|  | max_processes = 60 | 
|  |  | 
|  | pool = multiprocessing.Pool(processes=max_processes) | 
|  | async_results = [(t, pool.apply_async(t, ())) for t in tasks] | 
|  | failures = [] | 
|  | for task, async_result in async_results: | 
|  | try: | 
|  | async_result.get() | 
|  | except Exception as exn: | 
|  | failures += [(task, exn)] | 
|  |  | 
|  | if len(failures) > 0: | 
|  | print("The following commands failed:") | 
|  | for task, exn in failures: | 
|  | print("%s\n%s\n" % (str(task), str(exn))) | 
|  | raise Exception() | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | parser = argparse.ArgumentParser() | 
|  | all_types = [ | 
|  | 'engine', 'dart', 'benchmarks', 'java', 'android', 'objc', 'font-subset' | 
|  | ] | 
|  |  | 
|  | parser.add_argument( | 
|  | '--variant', | 
|  | dest='variant', | 
|  | action='store', | 
|  | default='host_debug_unopt', | 
|  | help='The engine build variant to run the tests for.' | 
|  | ) | 
|  | parser.add_argument( | 
|  | '--type', | 
|  | type=str, | 
|  | default='all', | 
|  | help='A list of test types, default is "all" (equivalent to "%s")' % | 
|  | (','.join(all_types)) | 
|  | ) | 
|  | parser.add_argument( | 
|  | '--engine-filter', | 
|  | type=str, | 
|  | default='', | 
|  | help='A list of engine test executables to run.' | 
|  | ) | 
|  | parser.add_argument( | 
|  | '--dart-filter', | 
|  | type=str, | 
|  | default='', | 
|  | help='A list of Dart test scripts to run.' | 
|  | ) | 
|  | parser.add_argument( | 
|  | '--java-filter', | 
|  | type=str, | 
|  | default='', | 
|  | help='A single Java test class to run (example: "io.flutter.SmokeTest")' | 
|  | ) | 
|  | parser.add_argument( | 
|  | '--android-variant', | 
|  | dest='android_variant', | 
|  | action='store', | 
|  | default='android_debug_unopt', | 
|  | help='The engine build variant to run java or android tests for' | 
|  | ) | 
|  | parser.add_argument( | 
|  | '--ios-variant', | 
|  | dest='ios_variant', | 
|  | action='store', | 
|  | default='ios_debug_sim_unopt', | 
|  | help='The engine build variant to run objective-c tests for' | 
|  | ) | 
|  | parser.add_argument( | 
|  | '--verbose-dart-snapshot', | 
|  | dest='verbose_dart_snapshot', | 
|  | action='store_true', | 
|  | default=False, | 
|  | help='Show extra dart snapshot logging.' | 
|  | ) | 
|  | parser.add_argument( | 
|  | '--objc-filter', | 
|  | type=str, | 
|  | default=None, | 
|  | help='Filter parameter for which objc tests to run (example: "IosUnitTestsTests/SemanticsObjectTest/testShouldTriggerAnnouncement")' | 
|  | ) | 
|  | parser.add_argument( | 
|  | '--coverage', | 
|  | action='store_true', | 
|  | default=None, | 
|  | help='Generate coverage reports for each unit test framework run.' | 
|  | ) | 
|  | parser.add_argument( | 
|  | '--engine-capture-core-dump', | 
|  | dest='engine_capture_core_dump', | 
|  | action='store_true', | 
|  | default=False, | 
|  | help='Capture core dumps from crashes of engine tests.' | 
|  | ) | 
|  | parser.add_argument( | 
|  | '--use-sanitizer-suppressions', | 
|  | dest='sanitizer_suppressions', | 
|  | action='store_true', | 
|  | default=False, | 
|  | help='Provide the sanitizer suppressions lists to the via environment to the tests.' | 
|  | ) | 
|  | parser.add_argument( | 
|  | '--adb-path', | 
|  | dest='adb_path', | 
|  | action='store', | 
|  | default=None, | 
|  | help='Provide the path of adb used for android tests. By default it looks on $PATH.' | 
|  | ) | 
|  |  | 
|  | args = parser.parse_args() | 
|  |  | 
|  | if args.type == 'all': | 
|  | types = all_types | 
|  | else: | 
|  | types = args.type.split(',') | 
|  |  | 
|  | build_dir = os.path.join(out_dir, args.variant) | 
|  | if args.type != 'java' and args.type != 'android': | 
|  | assert os.path.exists( | 
|  | build_dir | 
|  | ), 'Build variant directory %s does not exist!' % build_dir | 
|  |  | 
|  | if args.sanitizer_suppressions: | 
|  | assert IsLinux() or IsMac( | 
|  | ), "The sanitizer suppressions flag is only supported on Linux and Mac." | 
|  | file_dir = os.path.dirname(os.path.abspath(__file__)) | 
|  | command = [ | 
|  | "env", "-i", "bash", "-c", | 
|  | "source {}/sanitizer_suppressions.sh >/dev/null && env" | 
|  | .format(file_dir) | 
|  | ] | 
|  | process = subprocess.Popen(command, stdout=subprocess.PIPE) | 
|  | for line in process.stdout: | 
|  | key, _, value = line.decode('utf8').strip().partition("=") | 
|  | os.environ[key] = value | 
|  | process.communicate()  # Avoid pipe deadlock while waiting for termination. | 
|  |  | 
|  | engine_filter = args.engine_filter.split(',') if args.engine_filter else None | 
|  | if 'engine' in types: | 
|  | RunCCTests( | 
|  | build_dir, engine_filter, args.coverage, args.engine_capture_core_dump | 
|  | ) | 
|  |  | 
|  | if 'dart' in types: | 
|  | dart_filter = args.dart_filter.split(',') if args.dart_filter else None | 
|  | tasks = list(GatherDartSmokeTest(build_dir, args.verbose_dart_snapshot)) | 
|  | tasks += list(GatherLitetestTests(build_dir)) | 
|  | tasks += list(GatherGithooksTests(build_dir)) | 
|  | tasks += list(GatherClangTidyTests(build_dir)) | 
|  | tasks += list(GatherApiConsistencyTests(build_dir)) | 
|  | tasks += list(GatherPathOpsTests(build_dir)) | 
|  | tasks += list(GatherConstFinderTests(build_dir)) | 
|  | tasks += list(GatherFrontEndServerTests(build_dir)) | 
|  | tasks += list( | 
|  | GatherDartTests(build_dir, dart_filter, args.verbose_dart_snapshot) | 
|  | ) | 
|  | RunEngineTasksInParallel(tasks) | 
|  |  | 
|  | if 'java' in types: | 
|  | assert not IsWindows(), "Android engine files can't be compiled on Windows." | 
|  | java_filter = args.java_filter | 
|  | if ',' in java_filter or '*' in java_filter: | 
|  | print( | 
|  | 'Can only filter JUnit4 tests by single entire class name, eg "io.flutter.SmokeTest". Ignoring filter=' | 
|  | + java_filter | 
|  | ) | 
|  | java_filter = None | 
|  | RunJavaTests(java_filter, args.android_variant) | 
|  |  | 
|  | if 'android' in types: | 
|  | assert not IsWindows(), "Android engine files can't be compiled on Windows." | 
|  | RunAndroidTests(args.android_variant, args.adb_path) | 
|  |  | 
|  | if 'objc' in types: | 
|  | assert IsMac(), "iOS embedding tests can only be run on macOS." | 
|  | RunObjcTests(args.ios_variant, args.objc_filter) | 
|  |  | 
|  | # https://github.com/flutter/flutter/issues/36300 | 
|  | if 'benchmarks' in types and not IsWindows(): | 
|  | RunBenchmarkTests(build_dir) | 
|  | RunEngineBenchmarks(build_dir, engine_filter) | 
|  |  | 
|  | variants_to_skip = ['host_release', 'host_profile'] | 
|  | if ('engine' in types or | 
|  | 'font-subset' in types) and args.variant not in variants_to_skip: | 
|  | RunCmd(['python3', 'test.py'], cwd=font_subset_dir) | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | sys.exit(main()) |