blob: ecdb3536cc95f6fbfdb8dd1fa5d5bb28f3ea20e9 [file] [log] [blame]
// Copyright (c) 2018, 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.
// @dart = 2.9
@Timeout(Duration(minutes: 5))
import 'dart:io';
import 'package:io/io.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;
import 'package:pub_semver/pub_semver.dart' as semver;
import 'package:test/test.dart';
import 'package:test_descriptor/test_descriptor.dart' as d;
import 'package:test_process/test_process.dart';
import 'package:vm_service/vm_service.dart';
import 'package:vm_service/vm_service_io.dart';
import 'package:webdev/src/logging.dart';
import 'package:webdev/src/pubspec.dart';
import 'package:webdev/src/serve/utils.dart';
import 'package:webdev/src/util.dart';
import 'package:yaml/yaml.dart';
import 'daemon/utils.dart';
import 'test_utils.dart';
/// Key: name of file in web directory
/// Value: `null` - exists in both modes
/// `true` - DDC only
/// `false` - dart2js only
const _testItems = <String, bool>{
'main.dart.js': null,
'main.dart.bootstrap.js': true,
'main.unsound.ddc.js': true,
};
void main() {
// Change to true for debugging.
final debug = false;
String exampleDirectory;
String soundExampleDirectory;
setUpAll(() async {
configureLogWriter(debug);
exampleDirectory =
p.absolute(p.join(p.current, '..', 'fixtures', '_webdevSmoke'));
soundExampleDirectory =
p.absolute(p.join(p.current, '..', 'fixtures', '_webdevSoundSmoke'));
var process = await TestProcess.start(dartPath, ['pub', 'upgrade'],
workingDirectory: exampleDirectory, environment: getPubEnvironment());
await process.shouldExit(0);
process = await TestProcess.start(dartPath, ['pub', 'upgrade'],
workingDirectory: soundExampleDirectory,
environment: getPubEnvironment());
await process.shouldExit(0);
await d
.file('.dart_tool/package_config.json', isNotEmpty)
.validate(exampleDirectory);
await d.file('pubspec.lock', isNotEmpty).validate(exampleDirectory);
});
test('smoke test is configured properly', () async {
var smokeYaml =
loadYaml(await File('$exampleDirectory/pubspec.yaml').readAsString())
as YamlMap;
var webdevYaml =
loadYaml(await File('pubspec.yaml').readAsString()) as YamlMap;
expect(smokeYaml['environment']['sdk'],
equals(webdevYaml['environment']['sdk']));
expect(smokeYaml['dev_dependencies']['build_runner'],
equals(buildRunnerConstraint.toString()));
expect(smokeYaml['dev_dependencies']['build_web_compilers'],
equals(buildWebCompilersConstraint.toString()));
});
test('build should fail if targeting an existing directory', () async {
await d.file('simple thing', 'throw-away').create();
var args = ['build', '-o', 'web:${d.sandbox}'];
var process = await runWebDev(args, workingDirectory: exampleDirectory);
// NOTE: We'd like this to be more useful
// See https://github.com/dart-lang/build/issues/1283
await expectLater(
process.stdout,
emitsThrough(
contains('Unable to create merged directory at ${d.sandbox}.')));
await expectLater(
process.stdout,
emitsThrough(
'Choose a different directory or delete the contents of that '
'directory.'));
await process.shouldExit(isNot(0));
});
test('build should allow passing extra arguments to build_runner', () async {
var args = [
'build',
'-o',
'web:${d.sandbox}',
'--',
'--delete-conflicting-outputs'
];
var process = await runWebDev(args, workingDirectory: exampleDirectory);
await checkProcessStdout(process, ['Succeeded']);
await process.shouldExit(0);
});
group('should build with valid configuration', () {
for (var withDDC in [true, false]) {
test(withDDC ? 'DDC' : 'dart2js', () async {
var args = ['build', '-o', 'web:${d.sandbox}'];
if (withDDC) {
args.add('--no-release');
}
var process = await runWebDev(args, workingDirectory: exampleDirectory);
var expectedItems = <Object>['Succeeded'];
await checkProcessStdout(process, expectedItems);
await process.shouldExit(0);
for (var entry in _testItems.entries) {
var shouldExist = (entry.value ?? withDDC) == withDDC;
if (shouldExist) {
await d.file(entry.key, isNotEmpty).validate();
} else {
await d.nothing(entry.key).validate();
}
}
});
}
test(
'and --null-safety=sound',
() async {
var args = [
'build',
'-o',
'web:${d.sandbox}',
'--no-release',
'--null-safety=sound'
];
var process =
await runWebDev(args, workingDirectory: soundExampleDirectory);
var expectedItems = <Object>['Succeeded'];
await checkProcessStdout(process, expectedItems);
await process.shouldExit(0);
await d.file('main.sound.ddc.js', isNotEmpty).validate();
},
skip: semver.Version.parse(Platform.version.split(' ').first) >
semver.Version.parse('2.11.0')
? null
: 'SDK does not support sound null safety',
);
test(
'and --null-safety=unsound',
() async {
var args = [
'build',
'-o',
'web:${d.sandbox}',
'--no-release',
'--null-safety=unsound'
];
var process =
await runWebDev(args, workingDirectory: soundExampleDirectory);
var expectedItems = <Object>['Succeeded'];
await checkProcessStdout(process, expectedItems);
await process.shouldExit(0);
await d.file('main.unsound.ddc.js', isNotEmpty).validate();
},
skip: semver.Version.parse(Platform.version.split(' ').first) >
semver.Version.parse('2.11.0')
? null
: 'SDK does not support sound null safety',
);
});
group('should build with --output=NONE', () {
for (var withDDC in [true, false]) {
test(withDDC ? 'DDC' : 'dart2js', () async {
var args = ['build', '--output=NONE'];
if (withDDC) {
args.add('--no-release');
}
var process = await runWebDev(args, workingDirectory: exampleDirectory);
var expectedItems = <Object>['Succeeded'];
await checkProcessStdout(process, expectedItems);
await process.shouldExit(0);
await d.nothing('build').validate(exampleDirectory);
});
}
});
group('should serve with valid configuration', () {
for (var withDDC in [true, false]) {
var type = withDDC ? 'DDC' : 'dart2js';
test('using $type', () async {
var openPort = await findUnusedPort();
var args = ['serve', 'web:$openPort'];
if (!withDDC) {
args.add('--release');
}
var process = await runWebDev(args, workingDirectory: exampleDirectory);
var hostUrl = 'http://localhost:$openPort';
// Wait for the initial build to finish.
await expectLater(process.stdout, emitsThrough(contains('Succeeded')));
var client = HttpClient();
try {
for (var entry in _testItems.entries) {
var url = Uri.parse('$hostUrl/${entry.key}');
var request = await client.getUrl(url);
var response = await request.close();
var shouldExist = (entry.value ?? withDDC) == withDDC;
expect(response.statusCode, shouldExist ? 200 : 404,
reason: 'Expecting "$url"? $shouldExist');
}
} finally {
client.close(force: true);
}
await process.kill();
await process.shouldExit();
});
}
});
group('Should fail with invalid build directories', () {
var invalidServeDirs = ['.', '../', '../foo', 'foo/bar', 'foo/../'];
for (var dir in invalidServeDirs) {
for (var command in ['build', 'serve']) {
test('cannot $command directory: `$dir`', () async {
var args = [
command,
if (command == 'build') '--output=$dir:foo' else dir
];
var process =
await runWebDev(args, workingDirectory: exampleDirectory);
await expectLater(
process.stdout,
emitsThrough(contains(
'Invalid configuration: Only top level directories under the '
'package can be built')));
await expectLater(process.exitCode, completion(ExitCode.config.code));
});
}
}
});
group('should work with ', () {
setUp(() async {
configureLogWriter(debug);
});
for (var soundNullSafety in [false, true]) {
var nullSafetyOption = soundNullSafety ? 'sound' : 'unsound';
group('--null-safety=$nullSafetyOption', () {
setUp(() async {
configureLogWriter(debug);
});
group('and --enable-expression-evaluation:', () {
setUp(() async {
configureLogWriter(debug);
});
test('evaluateInFrame', () async {
var openPort = await findUnusedPort();
// running daemon command that starts dwds without keyboard input
var args = [
'daemon',
'web:$openPort',
'--enable-expression-evaluation',
'--verbose',
];
var process = await runWebDev(args,
workingDirectory:
soundNullSafety ? soundExampleDirectory : exampleDirectory);
VmService vmService;
process.stdoutStream().listen(Logger.root.fine);
process.stderrStream().listen(Logger.root.warning);
try {
// Wait for debug service Uri
String wsUri;
await expectLater(process.stdout, emitsThrough((message) {
wsUri = getDebugServiceUri(message as String);
return wsUri != null;
}));
Logger.root.fine('vm service uri: $wsUri');
expect(wsUri, isNotNull);
vmService = await vmServiceConnectUri(wsUri);
var vm = await vmService.getVM();
var isolate = vm.isolates.first;
var scripts = await vmService.getScripts(isolate.id);
await vmService.streamListen('Debug');
var stream = vmService.onEvent('Debug');
var mainScript = scripts.scripts
.firstWhere((each) => each.uri.contains('main.dart'));
var bpLine = await findBreakpointLine(
vmService, 'printCounter', isolate.id, mainScript);
var bp = await vmService.addBreakpointWithScriptUri(
isolate.id, mainScript.uri, bpLine);
expect(bp, isNotNull);
await stream.firstWhere(
(Event event) => event.kind == EventKind.kPauseBreakpoint);
var result =
await vmService.evaluateInFrame(isolate.id, 0, 'true');
expect(
result,
const TypeMatcher<InstanceRef>().having(
(instance) => instance.valueAsString,
'valueAsString',
'true'));
} finally {
await vmService?.dispose();
await exitWebdev(process);
await process.shouldExit();
}
}, timeout: const Timeout.factor(2));
test('evaluate', () async {
var openPort = await findUnusedPort();
// running daemon command that starts dwds without keyboard input
var args = [
'daemon',
'web:$openPort',
'--enable-expression-evaluation',
'--verbose',
];
var process = await runWebDev(args,
workingDirectory:
soundNullSafety ? soundExampleDirectory : exampleDirectory);
VmService vmService;
try {
// Wait for debug service Uri
String wsUri;
await expectLater(process.stdout, emitsThrough((message) {
wsUri = getDebugServiceUri(message as String);
return wsUri != null;
}));
expect(wsUri, isNotNull);
vmService = await vmServiceConnectUri(wsUri);
var vm = await vmService.getVM();
var isolate = await vmService.getIsolate(vm.isolates.first.id);
var library = isolate.rootLib;
await vmService.streamListen('Debug');
var result = await vmService.evaluate(isolate.id, library.id,
'(document?.body?.children?.first as SpanElement)?.text');
expect(
result,
const TypeMatcher<InstanceRef>().having(
(instance) => instance.valueAsString,
'valueAsString',
'Hello World!!'));
result = await vmService.evaluate(
isolate.id, library.id, 'main.toString()');
expect(
result,
const TypeMatcher<InstanceRef>().having(
(instance) => instance.valueAsString,
'valueAsString',
contains('Hello World!!')));
} finally {
await vmService?.dispose();
await exitWebdev(process);
await process.shouldExit();
}
}, timeout: const Timeout.factor(2));
});
group('and --no-enable-expression-evaluation:', () {
test('evaluateInFrame', () async {
var openPort = await findUnusedPort();
var args = [
'daemon',
'web:$openPort',
'--no-enable-expression-evaluation',
'--verbose',
];
var process = await runWebDev(args,
workingDirectory:
soundNullSafety ? soundExampleDirectory : exampleDirectory);
VmService vmService;
try {
// Wait for debug service Uri
String wsUri;
await expectLater(process.stdout, emitsThrough((message) {
wsUri = getDebugServiceUri(message as String);
return wsUri != null;
}));
expect(wsUri, isNotNull);
vmService = await vmServiceConnectUri(wsUri);
var vm = await vmService.getVM();
var isolate = vm.isolates.first;
var scripts = await vmService.getScripts(isolate.id);
await vmService.streamListen('Debug');
var stream = vmService.onEvent('Debug');
var mainScript = scripts.scripts
.firstWhere((each) => each.uri.contains('main.dart'));
var bpLine = await findBreakpointLine(
vmService, 'printCounter', isolate.id, mainScript);
var bp = await vmService.addBreakpointWithScriptUri(
isolate.id, mainScript.uri, bpLine);
expect(bp, isNotNull);
var event = await stream.firstWhere(
(Event event) => event.kind == EventKind.kPauseBreakpoint);
expect(
() => vmService.evaluateInFrame(
isolate.id, event.topFrame.index, 'true'),
throwsRPCError);
} finally {
await vmService?.dispose();
await exitWebdev(process);
await process.shouldExit();
}
});
test('evaluate', () async {
var openPort = await findUnusedPort();
// running daemon command that starts dwds without keyboard input
var args = [
'daemon',
'web:$openPort',
'--no-enable-expression-evaluation',
'--verbose',
];
var process = await runWebDev(args,
workingDirectory:
soundNullSafety ? soundExampleDirectory : exampleDirectory);
VmService vmService;
try {
// Wait for debug service Uri
String wsUri;
await expectLater(process.stdout, emitsThrough((message) {
wsUri = getDebugServiceUri(message as String);
return wsUri != null;
}));
expect(wsUri, isNotNull);
vmService = await vmServiceConnectUri(wsUri);
var vm = await vmService.getVM();
var isolate = await vmService.getIsolate(vm.isolates.first.id);
var library = isolate.rootLib;
await vmService.streamListen('Debug');
expect(
() => vmService.evaluate(
isolate.id, library.id, 'main.toString()'),
throwsRPCError);
} finally {
await vmService?.dispose();
await exitWebdev(process);
await process.shouldExit();
}
}, timeout: const Timeout.factor(2));
});
});
}
});
}