#!/usr/bin/env python3 # Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file # for details. All rights reserved. Use of this source code is governed by a # BSD-style license that can be found in the LICENSE file. # build_test_fast.py # # A smart wrapper around tools/build.py and tools/test.py that uses a # minimal, hardcoded mapping to determine exactly what needs to be built. # This avoids building the entire SDK (like CI does) just to run a single test. import sys import os import subprocess import platform def get_host_os(): if sys.platform == 'win32': return 'win' elif sys.platform == 'darwin': return 'mac' return 'linux' def get_host_arch(): machine = platform.machine().lower() if machine in ['arm64', 'aarch64']: return 'arm64' return 'x64' def get_minimal_build_targets(compiler, test_paths): targets = set() # 1. Base compiler targets (Minimal!) if compiler == 'dart2wasm': targets.update([ 'dartaotruntime', 'dart2wasm_platform.dill', 'dart2wasm', 'create_common_sdk', 'wasm-opt' ]) elif compiler == 'dart2js': # Often dart2js_bot or dart2js_platform.dill is enough, rather than create_sdk targets.update( ['dartaotruntime', 'dart2js_platform.dill', 'create_common_sdk']) elif compiler == 'ddc': targets.update(['ddc_stable_test_local', 'create_common_sdk']) elif compiler in ['analyzer', 'dart2analyzer']: pass # Often no build needed, or just dartanalyzer elif compiler == 'dartkp': targets.update(['runtime', 'runtime_precompiled']) elif compiler in ['vm', 'dartk', 'app_jitk', 'none']: targets.update(['runtime']) else: targets.update(['runtime']) # 2. Path-specific add-ons for path in test_paths: if 'tests/ffi' in path: targets.update(['ffi_test_functions', 'ffi_test_dynamic_library']) return list(targets) def guess_compiler_from_paths(test_paths): # Try to guess the best compiler based on the test paths for path in test_paths: # Wasm tests if 'tests/web/wasm' in path or 'pkg/dart2wasm' in path: return 'dart2wasm' # DDC specific tests elif 'tests/dartdevc' in path or 'pkg/dev_compiler' in path: return 'ddc' # General web / dart2js tests elif 'tests/web' in path or 'pkg/compiler' in path: return 'dart2js' # Analyzer / language server tests elif 'pkg/analyzer' in path or 'pkg/analysis_server' in path: return 'dart2analyzer' # Default to the VM's JIT compiler return 'dartk' def main(): args = sys.argv[1:] compiler = None runtime = None test_paths = [] # test.py defaults to release mode, while build.py defaults to debug. # We MUST pass the mode and arch explicitly to build.py to match. mode = 'release' arch = get_host_arch() system = get_host_os() # Parse out compiler, mode, arch and test paths skip_next = False for i, arg in enumerate(args): if skip_next: skip_next = False continue if arg == '-c' or arg == '--compiler': compiler = args[i + 1] skip_next = True elif arg.startswith('--compiler='): compiler = arg.split('=', 1)[1] elif arg == '-m' or arg == '--mode': mode = args[i + 1] skip_next = True elif arg.startswith('--mode='): mode = arg.split('=', 1)[1] elif arg == '-a' or arg == '--arch': arch = args[i + 1] skip_next = True elif arg.startswith('--arch='): arch = arg.split('=', 1)[1] elif arg == '-r' or arg == '--runtime': runtime = args[i + 1] skip_next = True elif arg.startswith('--runtime='): runtime = arg.split('=', 1)[1] elif not arg.startswith('-'): test_paths.append(arg) # Infer the compiler if not explicitly provided inferred = False if compiler is None: compiler = guess_compiler_from_paths(test_paths) inferred = True if inferred: print( f"🔮 Inferred compiler '\033[1m{compiler}\033[0m' from test paths.") # Default to d8 for web tests to reduce noise, if no runtime specified inferred_runtime = False if runtime is None and compiler in ['dart2js', 'dart2wasm', 'ddc']: runtime = 'd8' inferred_runtime = True print( f"🔮 Inferred runtime '\033[1m{runtime}\033[0m' for web compiler to reduce noise." ) build_args = get_minimal_build_targets(compiler, test_paths) targets_str = ' '.join(build_args) if build_args else '(none)' print( f"🎯 Determined minimal build targets for compiler '\033[1m{compiler}\033[0m': \033[1m{targets_str}\033[0m" ) stars_line = "🔹 " * 35 if build_args: # 3. Build Dart using the minimal targets, matching test.py's mode and arch defaults build_script = os.path.join(os.path.dirname(__file__), 'build.py') build_cmd = [sys.executable, build_script, '-m', mode, '-a', arch ] + build_args print(f"🚀 Building: python3 tools/build.py {' '.join(build_cmd[2:])}") print(stars_line) build_result = subprocess.run(build_cmd) print(stars_line) if build_result.returncode != 0: print("❌ Build failed! Aborting test run.") sys.exit(build_result.returncode) print("⭐⭐⭐ Build succeeded! ⭐⭐⭐\n") else: print("❓ No build targets required!\n") # 4. Run the tests test_script = os.path.join(os.path.dirname(__file__), 'test.py') if inferred: args = ['-c', compiler] + args if inferred_runtime: args = ['-r', runtime] + args test_cmd = [sys.executable, test_script] + args print(f"🧪 Running tests: python3 tools/test.py {' '.join(args)}") print(stars_line) test_result = subprocess.run(test_cmd) print(stars_line) if test_result.returncode != 0: print("\n" + "⚠️ " * 35) print(" TEST FAILED!") print( "If this failure looks like a missing file, missing snapshot, or compilation error," ) print( "it's possible that `build_test_fast.py` didn't build all the required dependencies." ) print( "💡 You may need to update the `get_minimal_build_targets()` mapping in this script." ) print( " (Check tools/bots/test_matrix.json to see what CI builds for this test suite!)" ) print("⚠️ " * 35 + "\n") else: print("⭐⭐⭐ Tests succeeded! ⭐⭐⭐") sys.exit(test_result.returncode) if __name__ == '__main__': main()