| // 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. |
| |
| @Timeout(Duration(minutes: 5)) |
| library; |
| |
| import 'dart:async'; |
| 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'; |
| import 'package:test/test.dart'; |
| import 'package:test_common/utilities.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 |
| final _testItems = <String, bool?>{ |
| 'main.dart.js': null, |
| 'main.dart.bootstrap.js': true, |
| 'main.ddc.js': true, |
| }; |
| |
| void main() { |
| // Change to true for debugging. |
| const debug = false; |
| |
| final testRunner = TestRunner(); |
| late String exampleDirectory; |
| setUpAll(() async { |
| configureLogWriter(debug); |
| await testRunner.setUpAll(); |
| exampleDirectory = |
| p.absolute(p.join(p.current, '..', 'fixtures', '_webdev_smoke')); |
| |
| final process = await TestProcess.start(dartPath, ['pub', 'upgrade'], |
| workingDirectory: exampleDirectory, 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); |
| }); |
| |
| tearDownAll(testRunner.tearDownAll); |
| |
| test('smoke test is configured properly', () async { |
| final smokeYaml = |
| loadYaml(await File('$exampleDirectory/pubspec.yaml').readAsString()) |
| as YamlMap; |
| final webdevYaml = |
| loadYaml(await File('pubspec.yaml').readAsString()) as YamlMap; |
| expect(smokeYaml['environment']['sdk'], |
| equals(webdevYaml['environment']['sdk'])); |
| expect( |
| buildRunnerConstraint.allowsAny(VersionConstraint.parse( |
| smokeYaml['dev_dependencies']['build_runner'])), |
| true); |
| 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(); |
| |
| final args = ['build', '-o', 'web:${d.sandbox}']; |
| |
| final process = |
| await testRunner.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 ${d.sandbox}. ' |
| '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 { |
| final args = [ |
| 'build', |
| '-o', |
| 'web:${d.sandbox}', |
| '--', |
| '--delete-conflicting-outputs' |
| ]; |
| |
| final process = |
| await testRunner.runWebDev(args, workingDirectory: exampleDirectory); |
| |
| await checkProcessStdout(process, ['Built with build_runner']); |
| await process.shouldExit(0); |
| }, |
| // https://github.com/dart-lang/webdev/issues/2489, |
| skip: Platform.isWindows, |
| ); |
| |
| group('should build with valid configuration', () { |
| for (final withDDC in [true, false]) { |
| test( |
| withDDC ? 'DDC' : 'dart2js', |
| () async { |
| final args = ['build', '-o', 'web:${d.sandbox}']; |
| if (withDDC) { |
| args.add('--no-release'); |
| } |
| |
| final process = await testRunner.runWebDev(args, |
| workingDirectory: exampleDirectory); |
| |
| final expectedItems = <Object>['Built with build_runner']; |
| |
| await checkProcessStdout(process, expectedItems); |
| await process.shouldExit(0); |
| |
| for (final entry in _testItems.entries) { |
| final shouldExist = (entry.value ?? withDDC) == withDDC; |
| |
| if (shouldExist) { |
| await d.file(entry.key, isNotEmpty).validate(); |
| } else { |
| await d.nothing(entry.key).validate(); |
| } |
| } |
| }, |
| // https://github.com/dart-lang/webdev/issues/2489 |
| skip: Platform.isWindows, |
| ); |
| } |
| test( |
| 'and --null-safety=sound', |
| () async { |
| final args = [ |
| 'build', |
| '-o', |
| 'web:${d.sandbox}', |
| '--no-release', |
| '--null-safety=sound' |
| ]; |
| |
| final process = await testRunner.runWebDev(args, |
| workingDirectory: exampleDirectory); |
| |
| final expectedItems = <Object>['Built with build_runner']; |
| |
| await checkProcessStdout(process, expectedItems); |
| await process.shouldExit(0); |
| |
| await d.file('main.ddc.js', isNotEmpty).validate(); |
| }, |
| // https://github.com/dart-lang/webdev/issues/2489 |
| skip: Platform.isWindows, |
| ); |
| }); |
| |
| group('should build with --output=NONE', () { |
| for (final withDDC in [true, false]) { |
| test(withDDC ? 'DDC' : 'dart2js', () async { |
| final args = ['build', '--output=NONE']; |
| if (withDDC) { |
| args.add('--no-release'); |
| } |
| |
| final process = await testRunner.runWebDev(args, |
| workingDirectory: exampleDirectory); |
| |
| final expectedItems = <Object>['Built with build_runner']; |
| |
| await checkProcessStdout(process, expectedItems); |
| await process.shouldExit(0); |
| |
| await d.nothing('build').validate(exampleDirectory); |
| }); |
| } |
| }); |
| |
| group('should serve with valid configuration', () { |
| for (final withDDC in [true, false]) { |
| final type = withDDC ? 'DDC' : 'dart2js'; |
| test('using $type', () async { |
| final openPort = await findUnusedPort(); |
| final args = ['serve', 'web:$openPort']; |
| if (!withDDC) { |
| args.add('--release'); |
| } |
| |
| final stdoutDone = Completer<void>(); |
| final stderrDone = Completer<void>(); |
| final process = await testRunner.runWebDev(args, |
| workingDirectory: exampleDirectory); |
| process.stdoutStream().listen((_) => {}, onDone: stdoutDone.complete); |
| process.stderrStream().listen((_) => {}, onDone: stderrDone.complete); |
| |
| final hostUrl = 'http://localhost:$openPort'; |
| |
| // Wait for the initial build to finish. |
| await expectLater( |
| process.stdout, emitsThrough(contains('Built with build_runner'))); |
| |
| final client = HttpClient(); |
| |
| try { |
| for (final entry in _testItems.entries) { |
| final url = Uri.parse('$hostUrl/${entry.key}'); |
| |
| final request = await client.getUrl(url); |
| final response = await request.close(); |
| |
| final 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(); |
| await Future.wait([stdoutDone.future, stderrDone.future]); |
| }); |
| } |
| }); |
| |
| group('Should fail with invalid build directories', () { |
| final invalidServeDirs = ['.', '../', '../foo', 'foo/bar', 'foo/../']; |
| for (final dir in invalidServeDirs) { |
| for (final command in ['build', 'serve']) { |
| test('cannot $command directory: `$dir`', () async { |
| final args = [ |
| command, |
| if (command == 'build') '--output=$dir:foo' else dir |
| ]; |
| |
| final process = await testRunner.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); |
| }); |
| group('and --enable-expression-evaluation:', () { |
| setUp(() async { |
| configureLogWriter(debug); |
| }); |
| test('evaluateInFrame', () async { |
| final openPort = await findUnusedPort(); |
| // running daemon command that starts dwds without keyboard input |
| final args = [ |
| 'daemon', |
| 'web:$openPort', |
| '--enable-expression-evaluation', |
| '--null-safety=sound', |
| '--verbose', |
| ]; |
| final process = await testRunner.runWebDev(args, |
| workingDirectory: 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!); |
| final vm = await vmService.getVM(); |
| final isolateId = vm.isolates!.first.id!; |
| final scripts = await vmService.getScripts(isolateId); |
| |
| await vmService.streamListen('Debug'); |
| final stream = vmService.onEvent('Debug'); |
| |
| final mainScript = scripts.scripts! |
| .firstWhere((each) => each.uri!.contains('main.dart')); |
| |
| final bpLine = await findBreakpointLine( |
| vmService, 'printCounter', isolateId, mainScript); |
| |
| final bp = await vmService.addBreakpointWithScriptUri( |
| isolateId, mainScript.uri!, bpLine); |
| expect(bp, isNotNull); |
| |
| await stream.firstWhere( |
| (Event event) => event.kind == EventKind.kPauseBreakpoint); |
| |
| final expression = |
| '() { const sound = !(<Null>[] is List<int>); return sound; } ()'; |
| final result = |
| await vmService.evaluateInFrame(isolateId, 0, expression); |
| |
| 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 { |
| final openPort = await findUnusedPort(); |
| // running daemon command that starts dwds without keyboard input |
| final args = [ |
| 'daemon', |
| 'web:$openPort', |
| '--enable-expression-evaluation', |
| '--verbose', |
| ]; |
| final process = await testRunner.runWebDev(args, |
| workingDirectory: exampleDirectory); |
| |
| process.stdoutStream().listen(Logger.root.fine); |
| process.stderrStream().listen(Logger.root.warning); |
| |
| 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!); |
| final vm = await vmService.getVM(); |
| final isolateId = vm.isolates!.first.id!; |
| final isolate = await vmService.getIsolate(isolateId); |
| final libraryId = isolate.rootLib!.id!; |
| |
| await vmService.streamListen('Debug'); |
| |
| var result = await vmService.evaluate(isolateId, libraryId, |
| '(document?.body?.children?.first as SpanElement)?.text'); |
| |
| expect( |
| result, |
| const TypeMatcher<InstanceRef>().having( |
| (instance) => instance.valueAsString, |
| 'valueAsString', |
| 'Hello World!!')); |
| |
| result = await vmService.evaluate( |
| isolateId, libraryId, 'topLevelMethod()'); |
| |
| expect( |
| result, |
| const TypeMatcher<InstanceRef>().having( |
| (instance) => instance.valueAsString, |
| 'valueAsString', |
| equals('verify this!'))); |
| } finally { |
| await vmService?.dispose(); |
| await exitWebdev(process); |
| await process.shouldExit(); |
| } |
| }, timeout: const Timeout.factor(2)); |
| |
| test('evaluate and get objects', () async { |
| final openPort = await findUnusedPort(); |
| // running daemon command that starts dwds without keyboard input |
| final args = [ |
| 'daemon', |
| 'web:$openPort', |
| '--enable-expression-evaluation', |
| '--verbose', |
| ]; |
| final process = await testRunner.runWebDev(args, |
| workingDirectory: exampleDirectory); |
| |
| process.stdoutStream().listen(Logger.root.fine); |
| process.stderrStream().listen(Logger.root.warning); |
| |
| 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!); |
| final vm = await vmService.getVM(); |
| final isolateId = vm.isolates!.first.id!; |
| final isolate = await vmService.getIsolate(isolateId); |
| final libraryId = isolate.rootLib!.id!; |
| |
| await vmService.streamListen('Debug'); |
| |
| final result = |
| await vmService.evaluate(isolateId, libraryId, '[true, false]'); |
| expect( |
| result, |
| const TypeMatcher<InstanceRef>().having( |
| (instance) => instance.classRef?.name, |
| 'class name', |
| dartSdkIsAtLeast('3.3.0-242.0.dev') |
| ? 'JSArray<bool>' |
| : 'List<bool>')); |
| |
| final instanceRef = result as InstanceRef; |
| final list = await vmService.getObject(isolateId, instanceRef.id!); |
| expect( |
| list, |
| const TypeMatcher<Instance>().having( |
| (instance) => instance.classRef?.name, |
| 'class name', |
| dartSdkIsAtLeast('3.3.0-242.0.dev') |
| ? 'JSArray<bool>' |
| : 'List<bool>')); |
| |
| final elements = (list as Instance).elements; |
| expect(elements, [ |
| const TypeMatcher<InstanceRef>() |
| .having((instance) => instance.valueAsString, 'value', 'true'), |
| const TypeMatcher<InstanceRef>() |
| .having((instance) => instance.valueAsString, 'value', 'false'), |
| ]); |
| } finally { |
| await vmService?.dispose(); |
| await exitWebdev(process); |
| await process.shouldExit(); |
| } |
| }, timeout: const Timeout.factor(2)); |
| }); |
| |
| group('and --no-enable-expression-evaluation:', () { |
| test('evaluateInFrame', () async { |
| final openPort = await findUnusedPort(); |
| final args = [ |
| 'daemon', |
| 'web:$openPort', |
| '--no-enable-expression-evaluation', |
| '--verbose', |
| ]; |
| final process = await testRunner.runWebDev(args, |
| workingDirectory: 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!); |
| final vm = await vmService.getVM(); |
| final isolateId = vm.isolates!.first.id!; |
| final scripts = await vmService.getScripts(isolateId); |
| |
| await vmService.streamListen('Debug'); |
| final stream = vmService.onEvent('Debug'); |
| |
| final mainScript = scripts.scripts! |
| .firstWhere((each) => each.uri!.contains('main.dart')); |
| |
| final bpLine = await findBreakpointLine( |
| vmService, 'printCounter', isolateId, mainScript); |
| |
| final bp = await vmService.addBreakpointWithScriptUri( |
| isolateId, mainScript.uri!, bpLine); |
| expect(bp, isNotNull); |
| |
| final event = await stream.firstWhere( |
| (Event event) => event.kind == EventKind.kPauseBreakpoint); |
| |
| expect( |
| () => vmService! |
| .evaluateInFrame(isolateId, event.topFrame!.index!, 'true'), |
| throwsRPCError); |
| } finally { |
| await vmService?.dispose(); |
| await exitWebdev(process); |
| await process.shouldExit(); |
| } |
| }); |
| |
| test('evaluate', () async { |
| final openPort = await findUnusedPort(); |
| // running daemon command that starts dwds without keyboard input |
| final args = [ |
| 'daemon', |
| 'web:$openPort', |
| '--no-enable-expression-evaluation', |
| '--verbose', |
| ]; |
| final process = await testRunner.runWebDev(args, |
| workingDirectory: 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!); |
| final vm = await vmService.getVM(); |
| final isolateId = vm.isolates!.first.id!; |
| final isolate = await vmService.getIsolate(isolateId); |
| final libraryId = isolate.rootLib!.id!; |
| |
| await vmService.streamListen('Debug'); |
| |
| expect( |
| () => |
| vmService!.evaluate(isolateId, libraryId, 'topLevelMethod()'), |
| throwsRPCError); |
| } finally { |
| await vmService?.dispose(); |
| await exitWebdev(process); |
| await process.shouldExit(); |
| } |
| }, timeout: const Timeout.factor(2)); |
| }); |
| }); |
| } |