blob: 53de48122591a643d58f9b9aeab1ae6b32ae44dc [file] [log] [blame]
// 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 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);
final Stream<String> inStream = process!.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter());
final line = await inStream.first;
final json = jsonDecode(line);
// {"event":"server.started","method":"server.started","params":{
// "host":"127.0.0.1","port":9100,"pid":93508,"protocolVersion":"1.1.0"
// }}
expect(json['event'], 'server.started');
expect(json['params'], isNotNull);
final host = json['params']['host'];
final port = json['params']['port'];
expect(host, isA<String>());
expect(port, isA<int>());
// Connect to the port and confirm we can load a devtools resource.
HttpClient client = HttpClient();
final httpRequest = await client.get(host, port, '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;
});
});
group('spawns DDS integration', () {
late TestProject targetProject;
Process? targetProjectInstance;
Process? process;
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();
process?.kill();
targetProjectInstance = null;
process = 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;
}
Future<void> startDevTools({
required String vmServiceUri,
required bool shouldStartDds,
}) async {
process = await p.start([
'devtools',
'--no-launch-browser',
vmServiceUri,
]);
process!.stderr.transform(utf8.decoder).listen(print);
bool startedDds = false;
final devToolsServedCompleter = Completer<void>();
late StreamSubscription sub;
sub = process!.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((event) async {
if (event.contains(ddsStartedRegExp)) {
startedDds = true;
} else if (event.contains(servingDevToolsRegExp)) {
await sub.cancel();
devToolsServedCompleter.complete();
}
});
await devToolsServedCompleter.future;
expect(startedDds, shouldStartDds);
// kill the process
process!.kill();
process = null;
}
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);
});
}
});
}