diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart index 36c3e4d62c3..1d5b3d4a17d 100644 --- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart +++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart @@ -198,19 +198,19 @@ class WebAssetServer implements AssetReader { address = (await InternetAddress.lookup(hostname)).first; } HttpServer httpServer; - dynamic lastError; - for (int i = 0; i < 5; i += 1) { + const int kMaxRetries = 4; + for (int i = 0; i <= kMaxRetries; i++) { try { httpServer = await HttpServer.bind(address, port ?? await globals.os.findFreePort()); break; - } on SocketException catch (error) { - lastError = error; + } on SocketException catch (e, s) { + if (i >= kMaxRetries) { + globals.printError('Failed to bind web development server:\n$e', stackTrace: s); + throwToolExit('Failed to bind web development server:\n$e'); + } await Future.delayed(const Duration(milliseconds: 100)); } } - if (httpServer == null) { - throwToolExit('Failed to bind web development server:\n$lastError'); - } // Allow rendering in a iframe. httpServer.defaultResponseHeaders.remove('x-frame-options', 'SAMEORIGIN'); @@ -240,7 +240,11 @@ class WebAssetServer implements AssetReader { webBuildDirectory: getWebBuildDirectory(), basePath: server.basePath, ); - shelf.serveRequests(httpServer, releaseAssetServer.handle); + runZonedGuarded(() { + shelf.serveRequests(httpServer, releaseAssetServer.handle); + }, (Object e, StackTrace s) { + globals.printTrace('Release asset server: error serving requests: $e:$s'); + }); return server; } @@ -266,9 +270,8 @@ class WebAssetServer implements AssetReader { }; } - logging.Logger.root.onRecord.listen((logging.LogRecord event) { - globals.printTrace('${event.loggerName}: ${event.message}'); - }); + logging.Logger.root.level = logging.Level.ALL; + logging.Logger.root.onRecord.listen(_log); // In debug builds, spin up DWDS and the full asset server. final Dwds dwds = await dwdsLauncher( @@ -302,7 +305,11 @@ class WebAssetServer implements AssetReader { pipeline.addHandler(server.handleRequest); final shelf.Cascade cascade = shelf.Cascade().add(dwds.handler).add(dwdsHandler); - shelf.serveRequests(httpServer, cascade.handler); + runZonedGuarded(() { + shelf.serveRequests(httpServer, cascade.handler); + }, (Object e, StackTrace s) { + globals.printTrace('Dwds server: error serving requests: $e:$s'); + }); server.dwds = dwds; return server; } @@ -995,6 +1002,17 @@ class ReleaseAssetServer { } } +void _log(logging.LogRecord event) { + final String error = event.error == null? '': 'Error: ${event.error}'; + if (event.level >= logging.Level.SEVERE) { + globals.printError('${event.loggerName}: ${event.message}$error', stackTrace: event.stackTrace); + } else if (event.level == logging.Level.WARNING) { + globals.printWarning('${event.loggerName}: ${event.message}$error'); + } else { + globals.printTrace('${event.loggerName}: ${event.message}$error'); + } +} + Future _loadDwdsDirectory( FileSystem fileSystem, Logger logger) async { final String toolPackagePath = diff --git a/packages/flutter_tools/lib/src/web/chrome.dart b/packages/flutter_tools/lib/src/web/chrome.dart index 41b2445985b..aab55dd8fc8 100644 --- a/packages/flutter_tools/lib/src/web/chrome.dart +++ b/packages/flutter_tools/lib/src/web/chrome.dart @@ -266,11 +266,13 @@ class ChromiumLauncher { // only required for flutter_test --platform=chrome and not flutter run. bool hitGlibcBug = false; bool shouldRetry = false; + final List errors = []; await process.stderr .transform(utf8.decoder) .transform(const LineSplitter()) .map((String line) { - _logger.printTrace('[CHROME]:$line'); + _logger.printTrace('[CHROME]: $line'); + errors.add('[CHROME]:$line'); if (line.contains(_kGlibcError)) { hitGlibcBug = true; shouldRetry = true; @@ -287,7 +289,8 @@ class ChromiumLauncher { return ''; } if (retry >= kMaxRetries) { - _logger.printTrace('Failed to launch browser after $kMaxRetries tries. Command used to launch it: ${args.join(' ')}'); + errors.forEach(_logger.printError); + _logger.printError('Failed to launch browser after $kMaxRetries tries. Command used to launch it: ${args.join(' ')}'); throw ToolExit( 'Failed to launch browser. Make sure you are using an up-to-date ' 'Chrome or Edge. Otherwise, consider using -d web-server instead ' @@ -395,7 +398,8 @@ class ChromiumLauncher { // connection is valid. if (!skipCheck) { try { - await chrome.chromeConnection.getTabs(); + await chrome.chromeConnection.getTab( + (ChromeTab tab) => true, retryFor: const Duration(seconds: 2)); } on Exception catch (error, stackTrace) { _logger.printError('$error', stackTrace: stackTrace); await chrome.close(); diff --git a/packages/flutter_tools/test/integration.shard/expression_evaluation_test.dart b/packages/flutter_tools/test/integration.shard/expression_evaluation_test.dart index 7f058764ea8..52118fae7e5 100644 --- a/packages/flutter_tools/test/integration.shard/expression_evaluation_test.dart +++ b/packages/flutter_tools/test/integration.shard/expression_evaluation_test.dart @@ -168,7 +168,7 @@ Future evaluateComplexReturningExpressions(FlutterTestDriver flutter) asyn // Ensure we got a reasonable approximation. The more accurate we try to // make this, the more likely it'll fail due to differences in the time // in the remote VM and the local VM at the time the code runs. - final InstanceRef res = await flutter.evaluate(resp.id, r'"$year-$month-$day"'); + final ObjRef res = await flutter.evaluate(resp.id, r'"$year-$month-$day"'); expectValue(res, '${now.year}-${now.month}-${now.day}'); } diff --git a/packages/flutter_tools/test/integration.shard/test_driver.dart b/packages/flutter_tools/test/integration.shard/test_driver.dart index 1b83ddf110c..f82ddee1de0 100644 --- a/packages/flutter_tools/test/integration.shard/test_driver.dart +++ b/packages/flutter_tools/test/integration.shard/test_driver.dart @@ -58,6 +58,7 @@ abstract class FlutterTestDriver { VmService? _vmService; String get lastErrorInfo => _errorBuffer.toString(); Stream get stdout => _stdout.stream; + Stream get stderr => _stderr.stream; int? get vmServicePort => _vmServiceWsUri?.port; bool get hasExited => _hasExited; Uri? get vmServiceWsUri => _vmServiceWsUri; @@ -355,9 +356,9 @@ abstract class FlutterTestDriver { ); } - Future evaluate(String targetId, String expression) async { - return _timeoutWithMessages( - () async => await _vmService!.evaluate(await _getFlutterIsolateId(), targetId, expression) as InstanceRef, + Future evaluate(String targetId, String expression) async { + return _timeoutWithMessages( + () async => await _vmService!.evaluate(await _getFlutterIsolateId(), targetId, expression) as ObjRef, task: 'Evaluating expression ($expression for $targetId)', ); } diff --git a/packages/flutter_tools/test/web.shard/chrome_test.dart b/packages/flutter_tools/test/web.shard/chrome_test.dart index ada39d994e1..56225b365b6 100644 --- a/packages/flutter_tools/test/web.shard/chrome_test.dart +++ b/packages/flutter_tools/test/web.shard/chrome_test.dart @@ -490,6 +490,15 @@ void main() { }); testWithoutContext('gives up retrying when an error happens more than 3 times', () async { + final BufferLogger logger = BufferLogger.test(); + final ChromiumLauncher chromiumLauncher = ChromiumLauncher( + fileSystem: fileSystem, + platform: platform, + processManager: processManager, + operatingSystemUtils: operatingSystemUtils, + browserFinder: findChromeExecutable, + logger: logger, + ); for (int i = 0; i < 4; i++) { processManager.addCommand(const FakeCommand( command: [ @@ -508,13 +517,14 @@ void main() { } await expectToolExitLater( - chromeLauncher.launch( + chromiumLauncher.launch( 'example_url', skipCheck: true, headless: true, ), contains('Failed to launch browser.'), ); + expect(logger.errorText, contains('nothing in the std error indicating glibc error')); }); testWithoutContext('Logs an error and exits if connection check fails.', () async { diff --git a/packages/flutter_tools/test/web.shard/output_web_test.dart b/packages/flutter_tools/test/web.shard/output_web_test.dart new file mode 100644 index 00000000000..b14660ee4a8 --- /dev/null +++ b/packages/flutter_tools/test/web.shard/output_web_test.dart @@ -0,0 +1,81 @@ +// Copyright 2014 The Flutter Authors. 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.8 + +import 'package:file/file.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:vm_service/vm_service.dart'; +import 'package:vm_service/vm_service_io.dart'; + +import '../integration.shard/test_data/basic_project.dart'; +import '../integration.shard/test_driver.dart'; +import '../integration.shard/test_utils.dart'; +import '../src/common.dart'; + +void main() { + Directory tempDir; + final BasicProjectWithUnaryMain project = BasicProjectWithUnaryMain(); + FlutterRunTestDriver flutter; + + setUp(() async { + tempDir = createResolvedTempDirectorySync('run_test.'); + await project.setUpIn(tempDir); + flutter = FlutterRunTestDriver(tempDir); + //flutter.stdout.listen(print); + }); + + tearDown(() async { + await flutter.stop(); + tryToDelete(tempDir); + }); + + Future start({bool verbose = false}) async { + // The non-test project has a loop around its breakpoints. + // No need to start paused as all breakpoint would be eventually reached. + await flutter.run( + withDebugger: true, + chrome: true, + expressionEvaluation: true, + additionalCommandArgs: [ + if (verbose) '--verbose', + '--web-renderer=html', + ]); + } + + Future evaluate() async { + final ObjRef res = + await flutter.evaluate('package:characters/characters.dart', 'true'); + expect(res, isA() + .having((InstanceRef o) => o.kind, 'kind', 'Bool')); + } + + Future sendEvent(Map event) async { + final VmService client = await vmServiceConnectUri( + '${flutter.vmServiceWsUri}'); + final Response result = await client.callServiceExtension( + 'ext.dwds.sendEvent', + args: event, + ); + expect(result, isA()); + await client.dispose(); + } + + testWithoutContext('flutter run outputs info messages from dwds in verbose mode', () async { + final Future info = expectLater( + flutter.stdout, emitsThrough(contains('Loaded debug metadata'))); + await start(verbose: true); + await evaluate(); + await flutter.stop(); + await info; + }); + + testWithoutContext('flutter run outputs warning messages from dwds in non-verbose mode', () async { + final Future warning = expectLater( + flutter.stderr, emitsThrough(contains('Ignoring unknown event'))); + await start(); + await sendEvent({'type': 'DevtoolsEvent'}); + await warning; + }); +} diff --git a/packages/flutter_tools/test/web.shard/vm_service_web_test.dart b/packages/flutter_tools/test/web.shard/vm_service_web_test.dart index d8c8e41a925..d0f910538f0 100644 --- a/packages/flutter_tools/test/web.shard/vm_service_web_test.dart +++ b/packages/flutter_tools/test/web.shard/vm_service_web_test.dart @@ -62,7 +62,7 @@ void main() { validateFlutterVersion(client1), validateFlutterVersion(client2)] ); - }, skip: true); // DDS failure: https://github.com/dart-lang/sdk/issues/46696 + }); }); group('Clients of flutter run on web with DDS disabled', () {