blob: ccab70bf835e05b032055a8c8177dbc193adc52a [file] [log] [blame]
// Copyright 2020 The Dart Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@TestOn('vm')
import 'dart:convert';
import 'dart:io';
import 'package:async/async.dart';
import 'package:frontend_server_client/frontend_server_client.dart';
import 'package:package_config/package_config.dart';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
import 'package:test_descriptor/test_descriptor.dart' as d;
import 'package:vm_service/vm_service.dart';
import 'package:vm_service/vm_service_io.dart';
void main() async {
late FrontendServerClient client;
late PackageConfig packageConfig;
late String packageRoot;
setUp(() async {
await d.dir('a', [
d.file('pubspec.yaml', '''
name: a
dependencies:
path: ^1.0.0
environment:
sdk: ^3.0.0
'''),
d.dir('bin', [
d.file('main.dart', '''
import 'package:path/path.dart' as p;
void main() async {
print(message);
/// Runs in a loop until it is hot reloaded with a new message.
while (!message.contains('goodbye')) {
await Future.delayed(const Duration(seconds: 1));
}
print(message);
}
String get message => p.join('hello', 'world');
''')
]),
]).create();
packageRoot = p.join(d.sandbox, 'a');
await Process.run(Platform.resolvedExecutable, ['pub', 'get'],
workingDirectory: packageRoot);
packageConfig = (await findPackageConfig(Directory(packageRoot)))!;
});
tearDown(() async {
await client.shutdown();
});
test('can compile, recompile, and hot reload a vm app', () async {
var entrypoint = p.join(packageRoot, 'bin', 'main.dart');
client = await FrontendServerClient.start(
entrypoint, p.join(packageRoot, 'out.dill'), vmPlatformDill);
var result = await client.compile();
client.accept();
expect(result.compilerOutputLines, isEmpty);
expect(result.errorCount, 0);
expect(
result.newSources,
containsAll([
File(entrypoint).uri,
packageConfig.resolve(Uri.parse('package:path/path.dart')),
]));
expect(result.removedSources, isEmpty);
expect(result.dillOutput, isNotNull);
expect(File(result.dillOutput!).existsSync(), true);
var process = await Process.start(Platform.resolvedExecutable, [
'--observe',
'--no-pause-isolates-on-exit',
'--pause-isolates-on-start',
result.dillOutput!
]);
addTearDown(process.kill);
var stdoutLines = StreamQueue(
process.stdout.transform(utf8.decoder).transform(const LineSplitter()));
var observatoryLine = await stdoutLines.next;
var observatoryUri =
'${observatoryLine.split(' ').last.replaceFirst('http', 'ws')}ws';
var vmService = await vmServiceConnectUri(observatoryUri);
var isolate = await waitForIsolatesAndResume(vmService);
await expectLater(stdoutLines, emitsThrough(p.join('hello', 'world')));
var appFile = File(entrypoint);
var originalContent = await appFile.readAsString();
var newContent = originalContent.replaceFirst('hello', 'goodbye');
await appFile.writeAsString(newContent);
result = await client.compile([File(entrypoint).uri]);
client.accept();
expect(result.newSources, isEmpty);
expect(result.removedSources, isEmpty);
expect(result.compilerOutputLines, isEmpty);
expect(result.errorCount, 0);
expect(result.dillOutput, endsWith('.incremental.dill'));
await vmService.reloadSources(isolate.id!, rootLibUri: result.dillOutput);
expect(await stdoutLines.next, p.join('goodbye', 'world'));
expect(await process.exitCode, 0);
// TODO(https://github.com/dart-lang/webdev/issues/2315): Fix and re-enable.
}, skip: true);
test('can handle compile errors and reload fixes', () async {
var entrypoint = p.join(packageRoot, 'bin', 'main.dart');
var entrypointFile = File(entrypoint);
var originalContent = await entrypointFile.readAsString();
// append two compile errors to the bottom
await entrypointFile
.writeAsString('$originalContent\nint foo = 1.0;\nString bar = 4;');
client = await FrontendServerClient.start(
entrypoint, p.join(packageRoot, 'out.dill'), vmPlatformDill);
var result = await client.compile();
client.accept();
expect(result.errorCount, 2);
expect(result.compilerOutputLines,
allOf(contains('int foo = 1.0;'), contains('String bar = 4;')));
expect(
result.newSources,
containsAll([
File(entrypoint).uri,
packageConfig.resolve(Uri.parse('package:path/path.dart')),
]));
expect(result.removedSources, isEmpty);
expect(result.dillOutput, isNotNull);
expect(File(result.dillOutput!).existsSync(), true);
var process = await Process.start(Platform.resolvedExecutable, [
'--observe',
'--no-pause-isolates-on-exit',
'--pause-isolates-on-start',
result.dillOutput!
]);
addTearDown(process.kill);
var stdoutLines = StreamQueue(
process.stdout.transform(utf8.decoder).transform(const LineSplitter()));
var observatoryLine = await stdoutLines.next;
var observatoryUri =
'${observatoryLine.split(' ').last.replaceFirst('http', 'ws')}ws';
var vmService = await vmServiceConnectUri(observatoryUri);
var isolate = await waitForIsolatesAndResume(vmService);
// The program actually runs regardless of the errors, as they don't affect
// the runtime behavior.
await expectLater(stdoutLines, emitsThrough(p.join('hello', 'world')));
await entrypointFile
.writeAsString(originalContent.replaceFirst('hello', 'goodbye'));
result = await client.compile([entrypointFile.uri]);
client.accept();
expect(result.errorCount, 0);
expect(result.compilerOutputLines, isEmpty);
expect(result.newSources, isEmpty);
expect(result.removedSources, isEmpty);
expect(result.dillOutput, isNotNull);
expect(File(result.dillOutput!).existsSync(), true);
await vmService.reloadSources(isolate.id!, rootLibUri: result.dillOutput);
expect(await stdoutLines.next, p.join('goodbye', 'world'));
expect(await process.exitCode, 0);
// TODO(https://github.com/dart-lang/webdev/issues/2315): Fix and re-enable.
}, skip: true);
test('can compile and recompile a dartdevc app', () async {
var entrypoint =
p.toUri(p.join(packageRoot, 'bin', 'main.dart')).toString();
var dartDevcClient = client = await DartDevcFrontendServerClient.start(
entrypoint, p.join(packageRoot, 'out.dill'),
platformKernel: p
.toUri(p.join(sdkDir, 'lib', '_internal', 'ddc_platform.dill'))
.toString());
var result = await client.compile();
client.accept();
expect(result.compilerOutputLines, isEmpty);
expect(result.errorCount, 0);
expect(
result.newSources,
containsAll([
Uri.parse(entrypoint),
packageConfig.resolve(Uri.parse('package:path/path.dart')),
]));
expect(result.removedSources, isEmpty);
expect(result.dillOutput, isNotNull);
expect(File(result.jsManifestOutput!).existsSync(), true);
expect(File(result.jsSourcesOutput!).existsSync(), true);
expect(File(result.jsSourceMapsOutput!).existsSync(), true);
var entrypointUri = Uri.parse(entrypoint);
expect(
utf8.decode(dartDevcClient.assetBytes('${entrypointUri.path}.lib.js')!),
contains('hello'));
var appFile = File(entrypointUri.toFilePath());
var originalContent = await appFile.readAsString();
var newContent = originalContent.replaceFirst('hello', 'goodbye');
await appFile.writeAsString(newContent);
result = await client.compile([entrypointUri]);
client.accept();
expect(result.newSources, isEmpty);
expect(result.removedSources, isEmpty);
expect(result.compilerOutputLines, isEmpty);
expect(result.errorCount, 0);
expect(result.jsManifestOutput, endsWith('.incremental.dill.json'));
expect(
utf8.decode(dartDevcClient.assetBytes('${entrypointUri.path}.lib.js')!),
contains('goodbye'));
});
test('can enable experiments', () async {
await d.dir('a', [
d.dir('bin', [
d.file('nnbd.dart', '''
// @dart=2.10
// Compile time error if nnbd is enabled
int x;
void main() {
print(x);
}
''')
])
]).create();
var entrypoint = p.join(packageRoot, 'bin', 'nnbd.dart');
client = await FrontendServerClient.start(
entrypoint, p.join(packageRoot, 'out.dill'), vmPlatformDill,
enabledExperiments: ['non-nullable']);
var result = await client.compile();
client.accept();
expect(result.errorCount, 1);
expect(result.compilerOutputLines, contains(contains('int x;')));
});
test('can compile and recompile filenames with spaces', () async {
await d.dir('a', [
d.dir('bin', [
d.file('main with spaces.dart', '''
void main() {
print('hello world');
}
''')
]),
]).create();
var entrypoint = p.join(packageRoot, 'bin', 'main with spaces.dart');
client = await FrontendServerClient.start(entrypoint,
p.join(packageRoot, 'out with spaces.dill'), vmPlatformDill);
var result = await client.compile();
client.accept();
expect(result.compilerOutputLines, isEmpty);
expect(result.errorCount, 0);
expect(
result.newSources,
containsAll([
File(entrypoint).uri,
]));
expect(result.removedSources, isEmpty);
expect(result.dillOutput, isNotNull);
expect(File(result.dillOutput!).existsSync(), true);
var processResult =
await Process.run(Platform.resolvedExecutable, [result.dillOutput!]);
expect(processResult.stdout, startsWith('hello world'));
expect(processResult.exitCode, 0);
var appFile = File(entrypoint);
var originalContent = await appFile.readAsString();
var newContent = originalContent.replaceFirst('hello', 'goodbye');
await appFile.writeAsString(newContent);
result = await client.compile([appFile.uri]);
expect(result.compilerOutputLines, isEmpty);
expect(result.errorCount, 0);
expect(result.newSources, isEmpty);
expect(result.removedSources, isEmpty);
processResult =
await Process.run(Platform.resolvedExecutable, [result.dillOutput!]);
expect(processResult.stdout, startsWith('goodbye world'));
expect(processResult.exitCode, 0);
});
}
Future<Isolate> waitForIsolatesAndResume(VmService vmService) async {
var vm = await vmService.getVM();
var isolates = vm.isolates;
while (isolates == null || isolates.isEmpty) {
await Future.delayed(const Duration(milliseconds: 100));
vm = await vmService.getVM();
isolates = vm.isolates;
}
var isolateRef = isolates.first;
var isolate = await vmService.getIsolate(isolateRef.id!);
while (isolate.pauseEvent?.kind != EventKind.kPauseStart) {
await Future.delayed(const Duration(milliseconds: 100));
isolate = await vmService.getIsolate(isolateRef.id!);
}
await vmService.resume(isolate.id!);
return isolate;
}
final vmPlatformDill = p
.toUri(p.join(sdkDir, 'lib', '_internal', 'vm_platform_strong.dill'))
.toString();
final sdkDir = p.dirname(p.dirname(Platform.resolvedExecutable));