diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart index ef344aae0c8..fbba45e61e2 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart @@ -486,6 +486,12 @@ class ResidentWebRunner extends ResidentRunner { } } + if (_connectionResult == null) { + status.stop(); + _logger.printStatus('Recompile complete. No client connected..'); + return OperationResult.ok; + } + // Both will be null when not assigned. Duration? reloadDuration; Duration? reassembleDuration; diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart index ecc9750f6cd..ebe5090715b 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart @@ -12,6 +12,7 @@ import 'package:flutter_tools/src/application_package.dart'; import 'package:flutter_tools/src/asset.dart'; import 'package:flutter_tools/src/base/dds.dart'; import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/terminal.dart'; @@ -970,7 +971,7 @@ name: my_app expect(debugConnectionInfo, isNotNull); final OperationResult result = await residentWebRunner.restart(); - expect(logger.statusText, contains('Reloaded application in')); + expect(logger.statusText, contains('Recompile complete. No client connected.')); expect(result.code, 0); }, overrides: { @@ -1142,6 +1143,47 @@ name: my_app }, ); + testUsingContext( + 'Does not fail hot restart when not attached', + () async { + final logger = BufferLogger.test(); + final ResidentRunner residentWebRunner = setUpResidentRunner( + flutterDevice, + logger: logger, + systemClock: SystemClock.fixed(DateTime(2001)), + ); + fakeVmServiceHost = FakeVmServiceHost( + requests: [ + ...kAttachExpectations, + const FakeVmServiceRequest(method: 'hotRestart'), + ], + ); + flutterDevice.device = WebServerDevice(logger: logger); + webDevFS.report = UpdateFSReport(success: true); + + final appStartedCompleter = Completer(); + unawaited(residentWebRunner.run(appStartedCompleter: appStartedCompleter)); + + await appStartedCompleter.future; + + late final OperationResult result; + + await expectReturnsNormallyLater(() async { + result = await residentWebRunner.restart(fullRestart: true); + }()); + + expect(result.code, 0); + expect(result.isOk, isTrue); + expect(logger.statusText, contains('Recompile complete. No client connected.')); + }, + overrides: { + Analytics: () => fakeAnalytics, + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + Pub: ThrowingPub.new, + }, + ); + testUsingContext( 'web resident runner is debuggable', () { @@ -2054,6 +2096,8 @@ class FakeResidentCompiler extends Fake implements ResidentCompiler { class FakeWebDevFS extends Fake implements WebDevFS { Object? exception; + + final resultCompleter = Completer(); ConnectionResult? result; late UpdateFSReport report; @@ -2112,7 +2156,11 @@ class FakeWebDevFS extends Fake implements WebDevFS { // ignore: only_throw_errors, exception is either Error or Exception here. throw exception!; } - return result; + // Automatically complete the future if a non-null result has been set. + if (result != null) { + resultCompleter.complete(result); + } + return resultCompleter.future; } }