[test] Infra for running tests on Fuchsia emulator
The IO tests aren't working yet, but basic tests work:
tools/test.py -n dartk-fuchsia-debug-x64 language_2/list/literal3_test
You may need to run this first:
sudo chmod 666 /dev/kvm
Change-Id: I04915ce11f671f1d493f9eeb6bc832089ba9bfa4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/154828
Commit-Queue: Liam Appelbe <liama@google.com>
Reviewed-by: Zach Anderson <zra@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Reviewed-by: William Hesse <whesse@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 41563a1..fe0376d 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -157,14 +157,18 @@
import("third_party/fuchsia/sdk/linux/build/component.gni")
import("third_party/fuchsia/sdk/linux/build/package.gni")
- fuchsia_component("dart_sdk_fuchsia_test_component") {
+ fuchsia_component("fuchsia_test_component") {
testonly = true
data_deps = [ "runtime/bin:dart" ]
manifest = "build/fuchsia/dart.cmx"
- resource_files = [ ".packages" ]
+ resource_files = [
+ ".packages",
+ "pkg/testing/test/hello_test.dart",
+ ]
resource_dirs = [
"tests/standalone",
+ "tests/language_2",
"pkg/async_helper",
"pkg/expect",
"pkg/meta",
@@ -190,8 +194,16 @@
exec_script("tools/fuchsia/find_resources.py", resource_dirs, "json")
}
- fuchsia_package("dart_sdk_fuchsia_test_package") {
+ fuchsia_package("fuchsia_test_package") {
+ package_name = "dart_test_"
+ if (is_debug) {
+ package_name += "debug"
+ } else if (is_release) {
+ package_name += "release"
+ } else if (is_product) {
+ package_name += "product"
+ }
testonly = true
- deps = [ ":dart_sdk_fuchsia_test_component" ]
+ deps = [ ":fuchsia_test_component" ]
}
}
diff --git a/pkg/test_runner/lib/src/configuration.dart b/pkg/test_runner/lib/src/configuration.dart
index dfc966e..c5d61b0 100644
--- a/pkg/test_runner/lib/src/configuration.dart
+++ b/pkg/test_runner/lib/src/configuration.dart
@@ -459,6 +459,7 @@
mode.name.substring(0, 1).toUpperCase() + mode.name.substring(1);
if (system == System.android) result += "Android";
+ if (system == System.fuchsia) result += "Fuchsia";
if (sanitizer != Sanitizer.none) {
result += sanitizer.name.toUpperCase();
diff --git a/pkg/test_runner/lib/src/fuchsia.dart b/pkg/test_runner/lib/src/fuchsia.dart
new file mode 100644
index 0000000..874ded5
--- /dev/null
+++ b/pkg/test_runner/lib/src/fuchsia.dart
@@ -0,0 +1,225 @@
+// Copyright (c) 2020, 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 'repository.dart';
+import 'utils.dart';
+
+class FuchsiaEmulator {
+ static final Uri toolsDir =
+ Repository.uri.resolve('third_party/fuchsia/sdk/linux/bin/');
+ static final String femuTool = toolsDir.resolve('femu.sh').toFilePath();
+ static final String fserveTool = toolsDir.resolve('fserve.sh').toFilePath();
+ static final String fpubTool = toolsDir.resolve('fpublish.sh').toFilePath();
+ static final String fsshTool = toolsDir.resolve('fssh.sh').toFilePath();
+ static final RegExp emulatorReadyPattern =
+ RegExp(r'Using unique host name (.+)\.local\.');
+ static final RegExp emulatorPidPattern =
+ RegExp(r'([0-9]+) .* qemu-system-x86');
+ static final String serverReadyPattern = '[pm serve] serving';
+
+ static FuchsiaEmulator _inst;
+
+ Process _emu;
+ Process _server;
+ String _deviceName;
+
+ static Future<void> publishPackage(
+ int emuCpus, String buildDir, String mode) async {
+ if (_inst == null) {
+ _inst = FuchsiaEmulator();
+ await _inst._start(emuCpus);
+ }
+ await _inst._publishPackage(buildDir, mode);
+ }
+
+ static void stop() {
+ _inst?._stop();
+ }
+
+ static List<String> getTestArgs(String mode, List<String> arguments) {
+ return _inst._getSshArgs(
+ mode,
+ arguments.map((arg) =>
+ arg.replaceAll(Repository.uri.toFilePath(), '/pkg/data/')));
+ }
+
+ Future<void> _start(int emuCpus) async {
+ // Start the emulator.
+ DebugLogger.info('Starting Fuchsia emulator with $emuCpus CPUs');
+ _emu = await Process.start('xvfb-run', [
+ femuTool,
+ '--image',
+ 'qemu-x64',
+ '-N',
+ '--headless',
+ '-s',
+ '$emuCpus'
+ ]);
+
+ // Wait until the emulator is ready and has a valid device name.
+ var deviceNameFuture = Completer<String>();
+ var emuStdout = StringBuffer();
+ var emuStderr = StringBuffer();
+ _emu.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen(
+ (String line) {
+ if (!deviceNameFuture.isCompleted) {
+ emuStdout.write(line);
+ emuStdout.write('\n');
+ var match = emulatorReadyPattern.firstMatch(line);
+ if (match != null) {
+ deviceNameFuture.complete(match.group(1));
+ }
+ }
+ }, onDone: () {
+ if (!deviceNameFuture.isCompleted) {
+ deviceNameFuture.completeError(
+ 'Fuchsia emulator terminated unexpectedly.\n\n' +
+ _formatOutputs(emuStdout.toString(), emuStderr.toString()));
+ }
+ _stop();
+ });
+ _emu.stderr
+ .transform(utf8.decoder)
+ .transform(const LineSplitter())
+ .listen((String line) {
+ if (!deviceNameFuture.isCompleted) {
+ emuStderr.write(line);
+ emuStderr.write('\n');
+ }
+ });
+ _deviceName = await deviceNameFuture.future;
+ DebugLogger.info('Fuchsia emulator ready: $_deviceName');
+
+ // Start the server.
+ DebugLogger.info('Starting Fuchsia package server');
+ _server = await Process.start(fserveTool, [
+ '--bucket',
+ 'fuchsia-sdk',
+ '--image',
+ 'qemu-x64',
+ '--device-name',
+ _deviceName
+ ]);
+
+ // Wait until the server is ready to serve packages.
+ var serverReadyFuture = Completer<String>();
+ var serverStdout = StringBuffer();
+ var serverStderr = StringBuffer();
+ _server.stdout
+ .transform(utf8.decoder)
+ .transform(const LineSplitter())
+ .listen((String line) {
+ if (!serverReadyFuture.isCompleted) {
+ serverStdout.write(line);
+ serverStdout.write('\n');
+ if (line.contains(serverReadyPattern)) {
+ serverReadyFuture.complete();
+ }
+ }
+ }, onDone: () {
+ if (!serverReadyFuture.isCompleted) {
+ serverReadyFuture.completeError(
+ 'Fuchsia package server terminated unexpectedly.\n\n' +
+ _formatOutputs(
+ serverStdout.toString(), serverStderr.toString()));
+ }
+ _stop();
+ });
+ _server.stderr
+ .transform(utf8.decoder)
+ .transform(const LineSplitter())
+ .listen((String line) {
+ if (!serverReadyFuture.isCompleted) {
+ serverStderr.write(line);
+ serverStderr.write('\n');
+ }
+ });
+ await serverReadyFuture.future;
+ DebugLogger.info('Fuchsia package server ready');
+ }
+
+ List<String> _getSshArgs(String mode, Iterable<String> args) {
+ var sshArgs = [
+ '--device-name',
+ _deviceName,
+ 'run',
+ 'fuchsia-pkg://fuchsia.com/dart_test_$mode#meta/dart.cmx'
+ ];
+ return sshArgs..addAll(args);
+ }
+
+ Future<void> _publishPackage(String buildDir, String mode) async {
+ var packageFile = '$buildDir/gen/dart_test_$mode/dart_test_$mode.far';
+ DebugLogger.info('Publishing package: $packageFile');
+ var result = await Process.run(fpubTool, [packageFile]);
+ if (result.exitCode != 0) {
+ _stop();
+ _throwResult('Publishing package', result);
+ }
+
+ // Verify that the publication was successful by running hello_test.dart.
+ // This also forces the emulator to download the published package from the
+ // server, rather than waiting until the first tests are run. It can take a
+ // minute or two to transfer, and we don't want to eat into the timeout
+ // timer of the first tests.
+ DebugLogger.info('Verifying publication');
+ result = await Process.run(fsshTool,
+ _getSshArgs(mode, ['/pkg/data/pkg/testing/test/hello_test.dart']));
+ if (result.exitCode != 0 || result.stdout != 'Hello, World!\n') {
+ _stop();
+ _throwResult('Verifying publication', result);
+ }
+ DebugLogger.info('Publication successful');
+ }
+
+ void _stop() {
+ if (_emu != null) {
+ DebugLogger.info('Stopping Fuchsia emulator');
+ _emu.kill(ProcessSignal.sigint);
+ _emu = null;
+
+ // Killing femu.sh seems to leave the underlying emulator running. So
+ // manually find the process and terminate it by PID.
+ var result = Process.runSync('ps', []);
+ var emuPid = int.tryParse(
+ emulatorPidPattern.firstMatch(result.stdout as String)?.group(1) ??
+ "");
+ if (result.exitCode != 0 || emuPid == null) {
+ _throwResult('Searching for emulator process', result);
+ }
+ Process.killPid(emuPid);
+ DebugLogger.info('Fuchsia emulator stopped');
+ }
+
+ if (_server != null) {
+ DebugLogger.info('Stopping Fuchsia package server');
+ _server.kill();
+ _server = null;
+
+ // fserve.sh starts a package manager process in the background. We need
+ // to manually kill this process, using fserve.sh again.
+ var result = Process.runSync(fserveTool, ['--kill']);
+ if (result.exitCode != 0) {
+ _throwResult('Killing package manager', result);
+ }
+ DebugLogger.info('Fuchsia package server stopped');
+ }
+ }
+
+ String _formatOutputs(String stdout, String stderr) {
+ var output = "";
+ if (stdout.isNotEmpty) output += "=== STDOUT ===\n$stdout\n";
+ if (stderr.isNotEmpty) output += "=== STDERR ===\n$stderr\n";
+ return output;
+ }
+
+ void _throwResult(String name, ProcessResult result) {
+ throw '$name failed with exit code: ${result.exitCode}\n\n' +
+ _formatOutputs(result.stdout as String, result.stderr as String);
+ }
+}
diff --git a/pkg/test_runner/lib/src/runtime_configuration.dart b/pkg/test_runner/lib/src/runtime_configuration.dart
index 477c73c..ad3f498 100644
--- a/pkg/test_runner/lib/src/runtime_configuration.dart
+++ b/pkg/test_runner/lib/src/runtime_configuration.dart
@@ -7,6 +7,7 @@
import 'command.dart';
import 'compiler_configuration.dart';
import 'configuration.dart';
+import 'fuchsia.dart';
import 'repository.dart';
import 'utils.dart';
@@ -42,6 +43,8 @@
case Runtime.vm:
if (configuration.system == System.android) {
return DartkAdbRuntimeConfiguration();
+ } else if (configuration.system == System.fuchsia) {
+ return DartkFuchsiaEmulatorRuntimeConfiguration();
}
return StandaloneDartRuntimeConfiguration();
@@ -379,6 +382,34 @@
}
}
+class DartkFuchsiaEmulatorRuntimeConfiguration
+ extends DartVmRuntimeConfiguration {
+ List<Command> computeRuntimeCommands(
+ CommandArtifact artifact,
+ List<String> arguments,
+ Map<String, String> environmentOverrides,
+ List<String> extraLibs,
+ bool isCrashExpected) {
+ var script = artifact.filename;
+ var type = artifact.mimeType;
+ if (script != null &&
+ type != 'application/dart' &&
+ type != 'application/dart-snapshot' &&
+ type != 'application/kernel-ir' &&
+ type != 'application/kernel-ir-fully-linked') {
+ throw "Dart VM cannot run files of type '$type'.";
+ }
+ var runtimeArgs =
+ FuchsiaEmulator.getTestArgs(_configuration.mode.name, arguments);
+ if (isCrashExpected) {
+ runtimeArgs.insert(0, '--suppress-core-dump');
+ }
+ return [
+ VMCommand(FuchsiaEmulator.fsshTool, runtimeArgs, environmentOverrides)
+ ];
+ }
+}
+
class SelfCheckRuntimeConfiguration extends DartVmRuntimeConfiguration {
final List<String> selfCheckers = <String>[];
diff --git a/pkg/test_runner/lib/src/test_configurations.dart b/pkg/test_runner/lib/src/test_configurations.dart
index 9e0d5a1..32201e8 100644
--- a/pkg/test_runner/lib/src/test_configurations.dart
+++ b/pkg/test_runner/lib/src/test_configurations.dart
@@ -10,6 +10,7 @@
import 'browser_controller.dart';
import 'co19_test_config.dart';
import 'configuration.dart';
+import 'fuchsia.dart';
import 'path.dart';
import 'process_queue.dart';
import 'terminal.dart';
@@ -152,6 +153,11 @@
}
}
}
+
+ if (configuration.system == System.fuchsia) {
+ await FuchsiaEmulator.publishPackage(configuration.taskCount,
+ configuration.buildDirectory, configuration.mode.name);
+ }
}
// If we only need to print out status files for test suites
@@ -170,6 +176,7 @@
for (var configuration in configurations) {
configuration.stopServers();
}
+ FuchsiaEmulator.stop();
DebugLogger.close();
if (!firstConf.keepGeneratedFiles) {
diff --git a/tools/bots/test_matrix.json b/tools/bots/test_matrix.json
index f6e7271..5ed6cd3 100644
--- a/tools/bots/test_matrix.json
+++ b/tools/bots/test_matrix.json
@@ -701,6 +701,7 @@
}
},
"dartk-(linux|mac|win)-(debug|product|release)-(ia32|x64)": {},
+ "dartk-fuchsia-(debug|product|release)-x64": {},
"dartk-linux-debug-(ia32|x64)-canary": {
"options": {
"builder-tag": "canary"