[tools] Add build_test_fast.py for a faster edit-compile-test loop

This change introduces `tools/build_test_fast.py`, a developer-focused
wrapper script that automatically infers and builds the *minimal* required
GN targets needed to run a specific test, avoiding the expensive overhead
of building the full SDK (e.g. `create_sdk`) during local iteration.

Features:
- Infers the correct `test.py` compiler (`-c`) from the provided test
  paths (e.g., `tests/web/wasm` -> `dart2wasm`).
- Infers the correct minimal GN targets for the selected compiler.
  For example, DDC tests now only build `ddc_stable_test_local` and
  `create_common_sdk`, taking ~4 seconds instead of 5 minutes.
- Automatically defaults web compilers (`dart2js`, `dart2wasm`, `ddc`)
  to use the `d8` runtime to prevent noisy browser popups during testing,
  unless explicitly overridden.
- Synchronizes the default `mode` between `build.py` (debug) and
  `test.py` (release) to prevent silent snapshot resolution failures.
- Prints clear, copy-pasteable execution commands for both the build
  and test phases.
- Displays a prominent warning if a test fails, reminding the developer
  to verify the minimal target mapping against `tools/bots/test_matrix.json`.
- Adds documentation for the script to `docs/Testing.md`.

Change-Id: I75473cdacca13f78dd271d9a31d5dcb15df57f6f
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/499043
Commit-Queue: Kevin Moore <kevmoo@google.com>
Reviewed-by: Jake Macdonald <jakemac@google.com>
This commit is contained in:
kevmoo
2026-04-28 15:19:09 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 2e413148cb
commit a5d8c88952
2 changed files with 226 additions and 0 deletions
+15
View File
@@ -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":
+211
View File
@@ -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()