Catch unable to start app exception (#154970)
Fixes https://github.com/flutter/flutter/issues/153433
This commit is contained in:
parent
0a400f8cd8
commit
b565379812
@ -11,6 +11,7 @@ import 'package:package_config/package_config.dart';
|
||||
import 'package:stream_channel/stream_channel.dart';
|
||||
import 'package:test_core/src/platform.dart'; // ignore: implementation_imports
|
||||
|
||||
import '../base/async_guard.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
@ -459,6 +460,15 @@ class FlutterPlatform extends PlatformPlugin {
|
||||
);
|
||||
}
|
||||
|
||||
void _handleStartedDevice(Uri? uri, int testCount) {
|
||||
if (uri != null) {
|
||||
globals.printTrace('test $testCount: VM Service uri is available at $uri');
|
||||
} else {
|
||||
globals.printTrace('test $testCount: VM Service uri is not available');
|
||||
}
|
||||
watcher?.handleStartedDevice(uri);
|
||||
}
|
||||
|
||||
Future<_AsyncError?> _startTest(
|
||||
String testPath,
|
||||
StreamChannel<dynamic> testHarnessChannel,
|
||||
@ -530,7 +540,16 @@ class FlutterPlatform extends PlatformPlugin {
|
||||
globals.printTrace('test $ourTestCount: starting test device');
|
||||
final TestDevice testDevice = _createTestDevice(ourTestCount);
|
||||
final Stopwatch? testTimeRecorderStopwatch = testTimeRecorder?.start(TestTimePhases.Run);
|
||||
final Future<StreamChannel<String>> remoteChannelFuture = testDevice.start(mainDart!);
|
||||
final Completer<StreamChannel<String>> remoteChannelCompleter = Completer<StreamChannel<String>>();
|
||||
unawaited(asyncGuard(
|
||||
() async {
|
||||
final StreamChannel<String> channel = await testDevice.start(mainDart!);
|
||||
remoteChannelCompleter.complete(channel);
|
||||
},
|
||||
onError: (Object err, StackTrace stackTrace) {
|
||||
remoteChannelCompleter.completeError(err, stackTrace);
|
||||
},
|
||||
));
|
||||
finalizers.add(() async {
|
||||
globals.printTrace('test $ourTestCount: ensuring test device is terminated.');
|
||||
await testDevice.kill();
|
||||
@ -545,15 +564,21 @@ class FlutterPlatform extends PlatformPlugin {
|
||||
await Future.any<void>(<Future<void>>[
|
||||
testDevice.finished,
|
||||
() async {
|
||||
final Uri? processVmServiceUri = await testDevice.vmServiceUri;
|
||||
if (processVmServiceUri != null) {
|
||||
globals.printTrace('test $ourTestCount: VM Service uri is available at $processVmServiceUri');
|
||||
} else {
|
||||
globals.printTrace('test $ourTestCount: VM Service uri is not available');
|
||||
}
|
||||
watcher?.handleStartedDevice(processVmServiceUri);
|
||||
final [Object? first, Object? _] = await Future.wait<Object?>(
|
||||
<Future<Object?>>[
|
||||
// This future may depend on [_handleStartedDevice] having been called
|
||||
remoteChannelCompleter.future,
|
||||
testDevice.vmServiceUri.then<void>((Uri? processVmServiceUri) {
|
||||
_handleStartedDevice(processVmServiceUri, ourTestCount);
|
||||
}),
|
||||
],
|
||||
// If [remoteChannelCompleter.future] errors, we may never get the
|
||||
// VM service URI, so erroring eagerly is necessary to avoid a
|
||||
// deadlock.
|
||||
eagerError: true,
|
||||
);
|
||||
final StreamChannel<String> remoteChannel = first! as StreamChannel<String>;
|
||||
|
||||
final StreamChannel<String> remoteChannel = await remoteChannelFuture;
|
||||
globals.printTrace('test $ourTestCount: connected to test device, now awaiting test result');
|
||||
|
||||
await _pipeHarnessToRemote(
|
||||
|
@ -3,11 +3,17 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/application_package.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/build_info.dart';
|
||||
import 'package:flutter_tools/src/device.dart';
|
||||
import 'package:flutter_tools/src/flutter_manifest.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:flutter_tools/src/test/flutter_platform.dart';
|
||||
import 'package:test/fake.dart';
|
||||
import 'package:test_core/backend.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
@ -63,6 +69,34 @@ void main() {
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
});
|
||||
|
||||
testUsingContext('an exception from the app not starting bubbles up to the test runner', () async {
|
||||
final _UnstartableDevice testDevice = _UnstartableDevice();
|
||||
final FlutterPlatform flutterPlatform = FlutterPlatform(
|
||||
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
|
||||
shellPath: '/',
|
||||
enableVmService: false,
|
||||
integrationTestDevice: testDevice,
|
||||
flutterProject: _FakeFlutterProject(),
|
||||
host: InternetAddress.anyIPv4,
|
||||
updateGoldens: false,
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
() => flutterPlatform.loadChannel('test1.dart', fakeSuitePlatform).stream.drain<void>(),
|
||||
// we intercept the actual exception and throw a string for the test runner to catch
|
||||
throwsA(isA<String>().having(
|
||||
(String msg) => msg,
|
||||
'string',
|
||||
'Unable to start the app on the device.',
|
||||
)),
|
||||
);
|
||||
expect((globals.logger as BufferLogger).traceText, contains('test 0: error caught during test;'));
|
||||
}, overrides: <Type, Generator>{
|
||||
FileSystem: () => fileSystem,
|
||||
ProcessManager: () => FakeProcessManager.any(),
|
||||
ApplicationPackageFactory: () => _FakeApplicationPackageFactory(),
|
||||
});
|
||||
|
||||
testUsingContext('installHook creates a FlutterPlatform', () {
|
||||
expect(() => installHook(
|
||||
shellPath: 'abc',
|
||||
@ -158,3 +192,44 @@ void main() {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class _UnstartableDevice extends Fake implements Device {
|
||||
@override
|
||||
Future<void> dispose() => Future<void>.value();
|
||||
|
||||
@override
|
||||
Future<TargetPlatform> get targetPlatform => Future<TargetPlatform>.value(TargetPlatform.android);
|
||||
|
||||
@override
|
||||
Future<bool> stopApp(ApplicationPackage? app, {String? userIdentifier}) async {
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> uninstallApp(ApplicationPackage app, {String? userIdentifier}) async => true;
|
||||
|
||||
@override
|
||||
Future<LaunchResult> startApp(covariant ApplicationPackage? package, {String? mainPath, String? route, required DebuggingOptions debuggingOptions, Map<String, Object?> platformArgs = const <String, Object>{}, bool prebuiltApplication = false, String? userIdentifier}) async {
|
||||
return LaunchResult.failed();
|
||||
}
|
||||
}
|
||||
|
||||
class _FakeFlutterProject extends Fake implements FlutterProject {
|
||||
@override
|
||||
FlutterManifest get manifest => FlutterManifest.empty(logger: BufferLogger.test());
|
||||
}
|
||||
|
||||
class _FakeApplicationPackageFactory implements ApplicationPackageFactory {
|
||||
TargetPlatform? platformRequested;
|
||||
File? applicationBinaryRequested;
|
||||
ApplicationPackage applicationPackage = _FakeApplicationPackage();
|
||||
|
||||
@override
|
||||
Future<ApplicationPackage?> getPackageForPlatform(TargetPlatform platform, {BuildInfo? buildInfo, File? applicationBinary}) async {
|
||||
platformRequested = platform;
|
||||
applicationBinaryRequested = applicationBinary;
|
||||
return applicationPackage;
|
||||
}
|
||||
}
|
||||
|
||||
class _FakeApplicationPackage extends Fake implements ApplicationPackage {}
|
||||
|
Loading…
x
Reference in New Issue
Block a user