6eb85949fd
This adds support for printing the DTD connection information to stdout when --print-dtd-uri is passed. This change also fixes an issue where DDS would fail to spawn an isolate with the DTD snapshot when DDS was running in AOT mode. This means the SDK must be shipped with both AppJIT and AOT DTD snapshots, at least until dartdev is moved to run from AOT. Fixes https://github.com/dart-lang/sdk/issues/55034 TEST=run_test.dart Change-Id: I788ef9bfe76297a8d594992a2aac440ed9e2ecac Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/358541 Reviewed-by: Siva Annamalai <asiva@google.com> Commit-Queue: Ben Konyi <bkonyi@google.com> Reviewed-by: Kenzie Davisson <kenzieschmoll@google.com>
292 lines
9.0 KiB
Dart
292 lines
9.0 KiB
Dart
// Copyright (c) 2021, 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 'package:dartdev/src/commands/devtools.dart';
|
|
import 'package:dds/devtools_server.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
import '../utils.dart';
|
|
|
|
final dartVMServiceRegExp = RegExp(
|
|
r'The Dart VM service is listening on (http://127.0.0.1:.*)',
|
|
);
|
|
final ddsStartedRegExp = RegExp(
|
|
r'Started the Dart Development Service \(DDS\) at (http://127.0.0.1:.*)',
|
|
);
|
|
final dtdStartedRegExp = RegExp(
|
|
r'Serving the Dart Tooling Daemon at (ws://127.0.0.1:.*)',
|
|
);
|
|
final servingDevToolsRegExp = RegExp(
|
|
r'Serving DevTools at (http://127.0.0.1:.*)',
|
|
);
|
|
|
|
void main() {
|
|
group('devtools', devtools, timeout: longTimeout);
|
|
}
|
|
|
|
void devtools() {
|
|
late TestProject p;
|
|
|
|
test('--help', () async {
|
|
p = project();
|
|
var result = await p.run(['devtools', '--help']);
|
|
expect(result.exitCode, 0);
|
|
expect(result.stderr, isEmpty);
|
|
expect(result.stdout, contains('Open DevTools'));
|
|
expect(result.stdout,
|
|
contains('Usage: dart devtools [arguments] [service protocol uri]'));
|
|
|
|
// Does not show verbose help.
|
|
expect(result.stdout.contains('--try-ports'), isFalse);
|
|
});
|
|
|
|
test('--help --verbose', () async {
|
|
p = project();
|
|
var result = await p.run(['devtools', '--help', '--verbose']);
|
|
expect(result.exitCode, 0);
|
|
expect(result.stderr, isEmpty);
|
|
expect(result.stdout, contains('Open DevTools'));
|
|
expect(
|
|
result.stdout,
|
|
contains(
|
|
'Usage: dart [vm-options] devtools [arguments] [service protocol uri]'));
|
|
|
|
// Shows verbose help.
|
|
expect(result.stdout, contains('--try-ports'));
|
|
});
|
|
|
|
group('integration', () {
|
|
Process? process;
|
|
|
|
tearDown(() {
|
|
process?.kill();
|
|
});
|
|
|
|
test('serves resources', () async {
|
|
p = project();
|
|
|
|
// start the devtools server
|
|
process = await p.start(['devtools', '--no-launch-browser', '--machine']);
|
|
process!.stderr.transform(utf8.decoder).listen(print);
|
|
|
|
String? devToolsHost;
|
|
int? devToolsPort;
|
|
final devToolsServedCompleter = Completer<void>();
|
|
final dtdServedCompleter = Completer<void>();
|
|
|
|
late StreamSubscription sub;
|
|
sub = process!.stdout
|
|
.transform<String>(utf8.decoder)
|
|
.transform<String>(const LineSplitter())
|
|
.listen((line) async {
|
|
final json = jsonDecode(line);
|
|
final eventName = json['event'] as String?;
|
|
final params = (json['params'] as Map?)?.cast<String, Object?>();
|
|
switch (eventName) {
|
|
case 'server.dtdStarted':
|
|
// {"event":"server.dtdStarted","params":{
|
|
// "uri":"ws://127.0.0.1:50882/nQf49D0YcbONeKVq"
|
|
// }}
|
|
expect(params!['uri'], isA<String>());
|
|
dtdServedCompleter.complete();
|
|
case 'server.started':
|
|
// {"event":"server.started","method":"server.started","params":{
|
|
// "host":"127.0.0.1","port":9100,"pid":93508,"protocolVersion":"1.1.0"
|
|
// }}
|
|
expect(params!['host'], isA<String>());
|
|
expect(params['port'], isA<int>());
|
|
devToolsHost = params['host'] as String;
|
|
devToolsPort = params['port'] as int;
|
|
|
|
// We can cancel the subscription because the 'server.started' event
|
|
// is expected after the 'server.dtdStarted' event.
|
|
await sub.cancel();
|
|
devToolsServedCompleter.complete();
|
|
default:
|
|
}
|
|
});
|
|
|
|
await Future.wait([
|
|
dtdServedCompleter.future,
|
|
devToolsServedCompleter.future,
|
|
]).timeout(
|
|
const Duration(seconds: 5),
|
|
onTimeout: () => throw Exception(
|
|
'Expected DTD and DevTools to be served, but one or both were not.',
|
|
),
|
|
);
|
|
|
|
// Connect to the port and confirm we can load a devtools resource.
|
|
HttpClient client = HttpClient();
|
|
expect(devToolsHost, isNotNull);
|
|
expect(devToolsPort, isNotNull);
|
|
final httpRequest =
|
|
await client.get(devToolsHost!, devToolsPort!, 'index.html');
|
|
final httpResponse = await httpRequest.close();
|
|
|
|
final contents =
|
|
(await httpResponse.transform(utf8.decoder).toList()).join();
|
|
client.close();
|
|
|
|
expect(contents, contains('DevTools'));
|
|
|
|
// kill the process
|
|
process!.kill();
|
|
process = null;
|
|
});
|
|
});
|
|
|
|
Future<void> startDevTools({
|
|
String? vmServiceUri,
|
|
bool shouldStartDds = false,
|
|
bool shouldPrintDtd = false,
|
|
}) async {
|
|
final process = await p.start([
|
|
'devtools',
|
|
'--no-launch-browser',
|
|
if (shouldPrintDtd) '--print-dtd',
|
|
if (vmServiceUri != null) vmServiceUri,
|
|
]);
|
|
process.stderr.transform(utf8.decoder).listen(print);
|
|
|
|
bool startedDds = false;
|
|
bool startedDtd = false;
|
|
final devToolsServedCompleter = Completer<void>();
|
|
late StreamSubscription sub;
|
|
sub = process.stdout
|
|
.transform<String>(utf8.decoder)
|
|
.transform<String>(const LineSplitter())
|
|
.listen((event) async {
|
|
print(event);
|
|
if (event.contains(ddsStartedRegExp)) {
|
|
startedDds = true;
|
|
} else if (event.contains(dtdStartedRegExp)) {
|
|
startedDtd = true;
|
|
} else if (event.contains(servingDevToolsRegExp)) {
|
|
await sub.cancel();
|
|
devToolsServedCompleter.complete();
|
|
}
|
|
});
|
|
|
|
await devToolsServedCompleter.future;
|
|
expect(startedDds, shouldStartDds);
|
|
expect(startedDtd, shouldPrintDtd);
|
|
|
|
// kill the process
|
|
process.kill();
|
|
}
|
|
|
|
test('prints DTD URI', () async {
|
|
p = project();
|
|
await startDevTools(shouldPrintDtd: true);
|
|
});
|
|
|
|
group('spawns DDS integration', () {
|
|
late TestProject targetProject;
|
|
Process? targetProjectInstance;
|
|
|
|
setUp(() {
|
|
// NOTE: we don't use `project()` here since it registers a tear-down
|
|
// which can be called before the target process is killed. This can be
|
|
// problematic on Windows, which won't let us delete directories while a
|
|
// process is actively accessing it. Manually disposing the projects is
|
|
// the easiest way to work around this.
|
|
targetProject = TestProject(
|
|
mainSrc: '''
|
|
Future<void> main() async {
|
|
while (true) {
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
}
|
|
}
|
|
''',
|
|
);
|
|
p = TestProject();
|
|
});
|
|
|
|
tearDown(() {
|
|
targetProjectInstance?.kill();
|
|
targetProjectInstance = null;
|
|
targetProject.dispose();
|
|
p.dispose();
|
|
});
|
|
|
|
Future<String> startTargetProject({
|
|
required bool disableServiceAuthCodes,
|
|
}) async {
|
|
targetProjectInstance = await targetProject.start(
|
|
[
|
|
'--disable-dart-dev',
|
|
'--observe=0',
|
|
if (disableServiceAuthCodes) '--disable-service-auth-codes',
|
|
targetProject.relativeFilePath,
|
|
],
|
|
);
|
|
|
|
final serviceUriCompleter = Completer<String>();
|
|
late StreamSubscription sub;
|
|
sub = targetProjectInstance!.stdout
|
|
.transform(utf8.decoder)
|
|
.transform(const LineSplitter())
|
|
.listen((event) async {
|
|
if (event.contains(dartVMServiceRegExp)) {
|
|
await sub.cancel();
|
|
serviceUriCompleter.complete(
|
|
dartVMServiceRegExp.firstMatch(event)!.group(1),
|
|
);
|
|
}
|
|
});
|
|
|
|
return await serviceUriCompleter.future;
|
|
}
|
|
|
|
for (final disableAuthCodes in const [true, false]) {
|
|
final authCodesEnabledStr = disableAuthCodes ? 'disabled' : 'enabled';
|
|
test('with auth codes $authCodesEnabledStr', () async {
|
|
final vmServiceUri = await startTargetProject(
|
|
disableServiceAuthCodes: disableAuthCodes,
|
|
);
|
|
|
|
// The first run should cause DDS to be started.
|
|
await startDevTools(vmServiceUri: vmServiceUri, shouldStartDds: true);
|
|
|
|
// The second run should not since DDS is already running.
|
|
await startDevTools(vmServiceUri: vmServiceUri, shouldStartDds: false);
|
|
});
|
|
|
|
test('check for redirect with auth codes $authCodesEnabledStr', () async {
|
|
final vmServiceUri = Uri.parse(
|
|
await startTargetProject(
|
|
disableServiceAuthCodes: disableAuthCodes,
|
|
),
|
|
);
|
|
var updatedUri =
|
|
await DevToolsCommand.checkForRedirectToExistingDDSInstance(
|
|
vmServiceUri,
|
|
);
|
|
// We should not have followed a redirect since DDS isn't running.
|
|
expect(vmServiceUri, updatedUri);
|
|
|
|
// Start DDS for this VM service instance.
|
|
final ddsUri = await DevToolsCommand.maybeStartDDS(
|
|
uri: vmServiceUri,
|
|
ddsHost: DevToolsServer.defaultDdsHost,
|
|
ddsPort: DevToolsServer.defaultDdsPort.toString(),
|
|
);
|
|
|
|
// Ensure that navigating to the VM service URI will redirect us to the
|
|
// DDS URI.
|
|
updatedUri =
|
|
await DevToolsCommand.checkForRedirectToExistingDDSInstance(
|
|
vmServiceUri,
|
|
);
|
|
expect(updatedUri, ddsUri);
|
|
});
|
|
}
|
|
});
|
|
}
|