blob: 13ffa965fc2ead628dd13dbf627e8593b36d238c [file] [log] [blame]
// Copyright (c) 2023, 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.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'command.dart';
// Runs tests on a fuchsia emulator with chromium maintained test-scripts and
// CFv2 targets.
class FuchsiaEmulator {
static const String testScriptRoot =
"./third_party/fuchsia/test_scripts/test/";
static const String withEnv = "./build/fuchsia/with_envs.py";
static const String tmpRoot = "/tmp/dart_ffi_test/";
// TODO(#38752): Once we have vm/cc and AOT tests running, this will sometimes
// need to be the component for run_vm_tests or dart_precompiled_runtime.
static const String cmName = "dart_test_component.cm";
final Map<String, String> envs = <String, String>{};
Process? daemonProc;
Process? emuProc;
String? emuName;
Process? repoProc;
// Publishes the packages to the Fuchsia environment.
Future<void> publishPackage(String buildDir, String mode, String arch) async {
try {
await Directory(tmpRoot).delete(recursive: true);
} catch (_) {}
// The /tmp/ should always be present, recursive creation is not expected.
await Directory(tmpRoot).create();
assert(daemonProc == null);
daemonProc = await _run("isolate_daemon.py", []);
var isolateDir = await _captureStdout(daemonProc!);
print("+ ffx daemon running on $isolateDir should be ready now.");
envs["FFX_ISOLATE_DIR"] = isolateDir;
assert(emuProc == null);
emuProc = await _run("start_emulator.py", [
"--disable-graphics",
"--target-id-only",
"--product",
arch == "arm64" ? "terminal.qemu-arm64" : "terminal.x64",
"--device-spec",
"virtual_device_large"
]);
emuName = await _captureStdout(emuProc!);
print("+ Targeting emu name $emuName");
await _assertRun("test_connection.py", [emuName!]);
await _assertRun("publish_package.py", [
"--packages",
_testPackagePath(buildDir, mode),
"--purge-repo",
"--repo",
_tempDirectoryOf("repo")
]);
repoProc = await _run("serve_repo.py", [
"run",
"--serve-repo",
_tempDirectoryOf("repo"),
"--repo-name",
"dart-ffi-test-repo",
"--target-id",
emuName!
]);
print("+ Fuchsia repo ${await _captureStdout(repoProc!)} is running "
"at ${_tempDirectoryOf('repo')}");
await _assertRun("pkg_resolve.py", [emuName!, _testPackageName(mode)]);
}
// Tears down the Fuchsia environment.
Future<void> stop() async {
assert(repoProc != null);
repoProc!.kill();
await repoProc!.exitCode;
assert(emuProc != null);
emuProc!.kill();
await emuProc!.exitCode;
assert(daemonProc != null);
daemonProc!.kill();
await daemonProc!.exitCode;
}
// Returns a command to execute a set of tests against the running Fuchsia
// environment.
VMCommand getTestCommand(
String buildDir, String mode, String arch, List<String> arguments) {
return VMCommand(
withEnv,
_runArgs("run_executable_test.py", [
"--target-id",
emuName!,
"--out-dir",
_tempDirectoryOf("out"),
"--test-name",
"fuchsia-pkg://fuchsia.com/${_testPackageName(mode)}#meta/$cmName",
"--test-realm",
// VmexResource not available in default hermetic realm
// TODO(38752): Setup a Dart test realm.
"/core/testing:system-tests",
"--logs-dir",
_tempDirectoryOf("logs"),
"--package-deps",
_testPackagePath(buildDir, mode),
...arguments
]),
envs);
}
static String _testPackageName(String mode) {
return "dart_test_$mode";
}
static String _testPackagePath(String buildDir, String mode) {
var farName = _testPackageName(mode);
return "$buildDir/gen/$farName/$farName.far";
}
static String _tempDirectoryOf(String name) {
return tmpRoot + name;
}
List<String> _runArgs(String script, List<String> args) {
return [testScriptRoot + script, ...args];
}
/// Executes a test script inside of third_party/fuchsia/test_scripts/test/
/// with the required environment setup and the arguments.
Future<Process> _run(String script, List<String> args) async {
var newArgs = _runArgs(script, args);
print("+ Start $withEnv with $newArgs with environment $envs.");
return Process.start(withEnv, newArgs, environment: envs);
}
/// Executes a test script and asserts its return code is 0; see _run and
/// _assert.
Future<void> _assertRun(String script, List<String> args) async {
_assert((await (await _run(script, args)).exitCode) == 0);
}
/// Captures the first line of output in utf8.
Future<String> _captureStdout(Process proc) async {
// The stderr needs to be fully consumed as well.
proc.stderr.transform(utf8.decoder).forEach((x) => stderr.write(x));
return (await proc.stdout.transform(utf8.decoder).first).trim();
}
/// Unlike assert keyword, always evaluates the input function and throws
/// exception when the evaluated result is false.
void _assert(bool condition) {
if (!condition) {
throw AssertionError();
}
}
static final FuchsiaEmulator _instance = _create();
static FuchsiaEmulator _create() {
return FuchsiaEmulator();
}
static FuchsiaEmulator instance() {
return _instance;
}
}