Skip to content

Commit f66eb72

Browse files
liamappelbecommit-bot@chromium.org
authored and
commit-bot@chromium.org
committed
[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>
1 parent d870a71 commit f66eb72

File tree

6 files changed

+281
-4
lines changed

6 files changed

+281
-4
lines changed

BUILD.gn

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,14 +157,18 @@ if (is_fuchsia) {
157157
import("third_party/fuchsia/sdk/linux/build/component.gni")
158158
import("third_party/fuchsia/sdk/linux/build/package.gni")
159159

160-
fuchsia_component("dart_sdk_fuchsia_test_component") {
160+
fuchsia_component("fuchsia_test_component") {
161161
testonly = true
162162
data_deps = [ "runtime/bin:dart" ]
163163
manifest = "build/fuchsia/dart.cmx"
164164

165-
resource_files = [ ".packages" ]
165+
resource_files = [
166+
".packages",
167+
"pkg/testing/test/hello_test.dart",
168+
]
166169
resource_dirs = [
167170
"tests/standalone",
171+
"tests/language_2",
168172
"pkg/async_helper",
169173
"pkg/expect",
170174
"pkg/meta",
@@ -190,8 +194,16 @@ if (is_fuchsia) {
190194
exec_script("tools/fuchsia/find_resources.py", resource_dirs, "json")
191195
}
192196

193-
fuchsia_package("dart_sdk_fuchsia_test_package") {
197+
fuchsia_package("fuchsia_test_package") {
198+
package_name = "dart_test_"
199+
if (is_debug) {
200+
package_name += "debug"
201+
} else if (is_release) {
202+
package_name += "release"
203+
} else if (is_product) {
204+
package_name += "product"
205+
}
194206
testonly = true
195-
deps = [ ":dart_sdk_fuchsia_test_component" ]
207+
deps = [ ":fuchsia_test_component" ]
196208
}
197209
}

pkg/test_runner/lib/src/configuration.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,7 @@ class TestConfiguration {
459459
mode.name.substring(0, 1).toUpperCase() + mode.name.substring(1);
460460

461461
if (system == System.android) result += "Android";
462+
if (system == System.fuchsia) result += "Fuchsia";
462463

463464
if (sanitizer != Sanitizer.none) {
464465
result += sanitizer.name.toUpperCase();

pkg/test_runner/lib/src/fuchsia.dart

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:convert';
7+
import 'dart:io';
8+
9+
import 'repository.dart';
10+
import 'utils.dart';
11+
12+
class FuchsiaEmulator {
13+
static final Uri toolsDir =
14+
Repository.uri.resolve('third_party/fuchsia/sdk/linux/bin/');
15+
static final String femuTool = toolsDir.resolve('femu.sh').toFilePath();
16+
static final String fserveTool = toolsDir.resolve('fserve.sh').toFilePath();
17+
static final String fpubTool = toolsDir.resolve('fpublish.sh').toFilePath();
18+
static final String fsshTool = toolsDir.resolve('fssh.sh').toFilePath();
19+
static final RegExp emulatorReadyPattern =
20+
RegExp(r'Using unique host name (.+)\.local\.');
21+
static final RegExp emulatorPidPattern =
22+
RegExp(r'([0-9]+) .* qemu-system-x86');
23+
static final String serverReadyPattern = '[pm serve] serving';
24+
25+
static FuchsiaEmulator _inst;
26+
27+
Process _emu;
28+
Process _server;
29+
String _deviceName;
30+
31+
static Future<void> publishPackage(
32+
int emuCpus, String buildDir, String mode) async {
33+
if (_inst == null) {
34+
_inst = FuchsiaEmulator();
35+
await _inst._start(emuCpus);
36+
}
37+
await _inst._publishPackage(buildDir, mode);
38+
}
39+
40+
static void stop() {
41+
_inst?._stop();
42+
}
43+
44+
static List<String> getTestArgs(String mode, List<String> arguments) {
45+
return _inst._getSshArgs(
46+
mode,
47+
arguments.map((arg) =>
48+
arg.replaceAll(Repository.uri.toFilePath(), '/pkg/data/')));
49+
}
50+
51+
Future<void> _start(int emuCpus) async {
52+
// Start the emulator.
53+
DebugLogger.info('Starting Fuchsia emulator with $emuCpus CPUs');
54+
_emu = await Process.start('xvfb-run', [
55+
femuTool,
56+
'--image',
57+
'qemu-x64',
58+
'-N',
59+
'--headless',
60+
'-s',
61+
'$emuCpus'
62+
]);
63+
64+
// Wait until the emulator is ready and has a valid device name.
65+
var deviceNameFuture = Completer<String>();
66+
var emuStdout = StringBuffer();
67+
var emuStderr = StringBuffer();
68+
_emu.stdout.transform(utf8.decoder).transform(const LineSplitter()).listen(
69+
(String line) {
70+
if (!deviceNameFuture.isCompleted) {
71+
emuStdout.write(line);
72+
emuStdout.write('\n');
73+
var match = emulatorReadyPattern.firstMatch(line);
74+
if (match != null) {
75+
deviceNameFuture.complete(match.group(1));
76+
}
77+
}
78+
}, onDone: () {
79+
if (!deviceNameFuture.isCompleted) {
80+
deviceNameFuture.completeError(
81+
'Fuchsia emulator terminated unexpectedly.\n\n' +
82+
_formatOutputs(emuStdout.toString(), emuStderr.toString()));
83+
}
84+
_stop();
85+
});
86+
_emu.stderr
87+
.transform(utf8.decoder)
88+
.transform(const LineSplitter())
89+
.listen((String line) {
90+
if (!deviceNameFuture.isCompleted) {
91+
emuStderr.write(line);
92+
emuStderr.write('\n');
93+
}
94+
});
95+
_deviceName = await deviceNameFuture.future;
96+
DebugLogger.info('Fuchsia emulator ready: $_deviceName');
97+
98+
// Start the server.
99+
DebugLogger.info('Starting Fuchsia package server');
100+
_server = await Process.start(fserveTool, [
101+
'--bucket',
102+
'fuchsia-sdk',
103+
'--image',
104+
'qemu-x64',
105+
'--device-name',
106+
_deviceName
107+
]);
108+
109+
// Wait until the server is ready to serve packages.
110+
var serverReadyFuture = Completer<String>();
111+
var serverStdout = StringBuffer();
112+
var serverStderr = StringBuffer();
113+
_server.stdout
114+
.transform(utf8.decoder)
115+
.transform(const LineSplitter())
116+
.listen((String line) {
117+
if (!serverReadyFuture.isCompleted) {
118+
serverStdout.write(line);
119+
serverStdout.write('\n');
120+
if (line.contains(serverReadyPattern)) {
121+
serverReadyFuture.complete();
122+
}
123+
}
124+
}, onDone: () {
125+
if (!serverReadyFuture.isCompleted) {
126+
serverReadyFuture.completeError(
127+
'Fuchsia package server terminated unexpectedly.\n\n' +
128+
_formatOutputs(
129+
serverStdout.toString(), serverStderr.toString()));
130+
}
131+
_stop();
132+
});
133+
_server.stderr
134+
.transform(utf8.decoder)
135+
.transform(const LineSplitter())
136+
.listen((String line) {
137+
if (!serverReadyFuture.isCompleted) {
138+
serverStderr.write(line);
139+
serverStderr.write('\n');
140+
}
141+
});
142+
await serverReadyFuture.future;
143+
DebugLogger.info('Fuchsia package server ready');
144+
}
145+
146+
List<String> _getSshArgs(String mode, Iterable<String> args) {
147+
var sshArgs = [
148+
'--device-name',
149+
_deviceName,
150+
'run',
151+
'fuchsia-pkg://fuchsia.com/dart_test_$mode#meta/dart.cmx'
152+
];
153+
return sshArgs..addAll(args);
154+
}
155+
156+
Future<void> _publishPackage(String buildDir, String mode) async {
157+
var packageFile = '$buildDir/gen/dart_test_$mode/dart_test_$mode.far';
158+
DebugLogger.info('Publishing package: $packageFile');
159+
var result = await Process.run(fpubTool, [packageFile]);
160+
if (result.exitCode != 0) {
161+
_stop();
162+
_throwResult('Publishing package', result);
163+
}
164+
165+
// Verify that the publication was successful by running hello_test.dart.
166+
// This also forces the emulator to download the published package from the
167+
// server, rather than waiting until the first tests are run. It can take a
168+
// minute or two to transfer, and we don't want to eat into the timeout
169+
// timer of the first tests.
170+
DebugLogger.info('Verifying publication');
171+
result = await Process.run(fsshTool,
172+
_getSshArgs(mode, ['/pkg/data/pkg/testing/test/hello_test.dart']));
173+
if (result.exitCode != 0 || result.stdout != 'Hello, World!\n') {
174+
_stop();
175+
_throwResult('Verifying publication', result);
176+
}
177+
DebugLogger.info('Publication successful');
178+
}
179+
180+
void _stop() {
181+
if (_emu != null) {
182+
DebugLogger.info('Stopping Fuchsia emulator');
183+
_emu.kill(ProcessSignal.sigint);
184+
_emu = null;
185+
186+
// Killing femu.sh seems to leave the underlying emulator running. So
187+
// manually find the process and terminate it by PID.
188+
var result = Process.runSync('ps', []);
189+
var emuPid = int.tryParse(
190+
emulatorPidPattern.firstMatch(result.stdout as String)?.group(1) ??
191+
"");
192+
if (result.exitCode != 0 || emuPid == null) {
193+
_throwResult('Searching for emulator process', result);
194+
}
195+
Process.killPid(emuPid);
196+
DebugLogger.info('Fuchsia emulator stopped');
197+
}
198+
199+
if (_server != null) {
200+
DebugLogger.info('Stopping Fuchsia package server');
201+
_server.kill();
202+
_server = null;
203+
204+
// fserve.sh starts a package manager process in the background. We need
205+
// to manually kill this process, using fserve.sh again.
206+
var result = Process.runSync(fserveTool, ['--kill']);
207+
if (result.exitCode != 0) {
208+
_throwResult('Killing package manager', result);
209+
}
210+
DebugLogger.info('Fuchsia package server stopped');
211+
}
212+
}
213+
214+
String _formatOutputs(String stdout, String stderr) {
215+
var output = "";
216+
if (stdout.isNotEmpty) output += "=== STDOUT ===\n$stdout\n";
217+
if (stderr.isNotEmpty) output += "=== STDERR ===\n$stderr\n";
218+
return output;
219+
}
220+
221+
void _throwResult(String name, ProcessResult result) {
222+
throw '$name failed with exit code: ${result.exitCode}\n\n' +
223+
_formatOutputs(result.stdout as String, result.stderr as String);
224+
}
225+
}

pkg/test_runner/lib/src/runtime_configuration.dart

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'dart:io';
77
import 'command.dart';
88
import 'compiler_configuration.dart';
99
import 'configuration.dart';
10+
import 'fuchsia.dart';
1011
import 'repository.dart';
1112
import 'utils.dart';
1213

@@ -42,6 +43,8 @@ abstract class RuntimeConfiguration {
4243
case Runtime.vm:
4344
if (configuration.system == System.android) {
4445
return DartkAdbRuntimeConfiguration();
46+
} else if (configuration.system == System.fuchsia) {
47+
return DartkFuchsiaEmulatorRuntimeConfiguration();
4548
}
4649
return StandaloneDartRuntimeConfiguration();
4750

@@ -379,6 +382,34 @@ class DartPrecompiledAdbRuntimeConfiguration
379382
}
380383
}
381384

385+
class DartkFuchsiaEmulatorRuntimeConfiguration
386+
extends DartVmRuntimeConfiguration {
387+
List<Command> computeRuntimeCommands(
388+
CommandArtifact artifact,
389+
List<String> arguments,
390+
Map<String, String> environmentOverrides,
391+
List<String> extraLibs,
392+
bool isCrashExpected) {
393+
var script = artifact.filename;
394+
var type = artifact.mimeType;
395+
if (script != null &&
396+
type != 'application/dart' &&
397+
type != 'application/dart-snapshot' &&
398+
type != 'application/kernel-ir' &&
399+
type != 'application/kernel-ir-fully-linked') {
400+
throw "Dart VM cannot run files of type '$type'.";
401+
}
402+
var runtimeArgs =
403+
FuchsiaEmulator.getTestArgs(_configuration.mode.name, arguments);
404+
if (isCrashExpected) {
405+
runtimeArgs.insert(0, '--suppress-core-dump');
406+
}
407+
return [
408+
VMCommand(FuchsiaEmulator.fsshTool, runtimeArgs, environmentOverrides)
409+
];
410+
}
411+
}
412+
382413
class SelfCheckRuntimeConfiguration extends DartVmRuntimeConfiguration {
383414
final List<String> selfCheckers = <String>[];
384415

pkg/test_runner/lib/src/test_configurations.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'android.dart';
1010
import 'browser_controller.dart';
1111
import 'co19_test_config.dart';
1212
import 'configuration.dart';
13+
import 'fuchsia.dart';
1314
import 'path.dart';
1415
import 'process_queue.dart';
1516
import 'terminal.dart';
@@ -152,6 +153,11 @@ Future testConfigurations(List<TestConfiguration> configurations) async {
152153
}
153154
}
154155
}
156+
157+
if (configuration.system == System.fuchsia) {
158+
await FuchsiaEmulator.publishPackage(configuration.taskCount,
159+
configuration.buildDirectory, configuration.mode.name);
160+
}
155161
}
156162

157163
// If we only need to print out status files for test suites
@@ -170,6 +176,7 @@ Future testConfigurations(List<TestConfiguration> configurations) async {
170176
for (var configuration in configurations) {
171177
configuration.stopServers();
172178
}
179+
FuchsiaEmulator.stop();
173180

174181
DebugLogger.close();
175182
if (!firstConf.keepGeneratedFiles) {

tools/bots/test_matrix.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,7 @@
701701
}
702702
},
703703
"dartk-(linux|mac|win)-(debug|product|release)-(ia32|x64)": {},
704+
"dartk-fuchsia-(debug|product|release)-x64": {},
704705
"dartk-linux-debug-(ia32|x64)-canary": {
705706
"options": {
706707
"builder-tag": "canary"

0 commit comments

Comments
 (0)