diff --git a/docs/Testing.md b/docs/Testing.md index a84cccc70c5..81ed2963e7c 100644 --- a/docs/Testing.md +++ b/docs/Testing.md @@ -346,6 +346,21 @@ is out of date even the tool is doing what it should. Eventually, this way of running tests should be removed, along with the status files. +### Using build_test_fast.py (Developer Fast Path) + +For local development, building the entire SDK for a single test can be time-consuming. You can use the `build_test_fast.py` wrapper, which automatically infers the correct compiler and builds only the *minimal* required targets needed to run your tests: + +```sh +# Infers dart2wasm and builds only its minimal targets (takes seconds instead of minutes) +./tools/build_test_fast.py tests/web/wasm/simd/simd_test.dart +``` + +This script will: +1. Infer the compiler from your test path (e.g., `tests/web/wasm` -> `dart2wasm`). +2. Build only the minimal GN targets required for that compiler (e.g., `dart2wasm_platform.dill`, `dartaotruntime`). +3. Automatically default to the headless `d8` runtime for web compilers to prevent noisy browser popups. +4. Execute `tools/test.py` with the correct arguments. + ### Using test.dart The new entrypoint is "test.dart": diff --git a/tools/build_test_fast.py b/tools/build_test_fast.py new file mode 100755 index 00000000000..374f47407ad --- /dev/null +++ b/tools/build_test_fast.py @@ -0,0 +1,211 @@ +#!/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' + ]) + 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"๐Ÿš€ Running: 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" + "โš ๏ธ " * 40) + 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("โš ๏ธ " * 40 + "\n") + + sys.exit(test_result.returncode) + + +if __name__ == '__main__': + main()