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:stream_channel/stream_channel.dart';
|
||||||
import 'package:test_core/src/platform.dart'; // ignore: implementation_imports
|
import 'package:test_core/src/platform.dart'; // ignore: implementation_imports
|
||||||
|
|
||||||
|
import '../base/async_guard.dart';
|
||||||
import '../base/common.dart';
|
import '../base/common.dart';
|
||||||
import '../base/file_system.dart';
|
import '../base/file_system.dart';
|
||||||
import '../base/io.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(
|
Future<_AsyncError?> _startTest(
|
||||||
String testPath,
|
String testPath,
|
||||||
StreamChannel<dynamic> testHarnessChannel,
|
StreamChannel<dynamic> testHarnessChannel,
|
||||||
@ -530,7 +540,16 @@ class FlutterPlatform extends PlatformPlugin {
|
|||||||
globals.printTrace('test $ourTestCount: starting test device');
|
globals.printTrace('test $ourTestCount: starting test device');
|
||||||
final TestDevice testDevice = _createTestDevice(ourTestCount);
|
final TestDevice testDevice = _createTestDevice(ourTestCount);
|
||||||
final Stopwatch? testTimeRecorderStopwatch = testTimeRecorder?.start(TestTimePhases.Run);
|
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 {
|
finalizers.add(() async {
|
||||||
globals.printTrace('test $ourTestCount: ensuring test device is terminated.');
|
globals.printTrace('test $ourTestCount: ensuring test device is terminated.');
|
||||||
await testDevice.kill();
|
await testDevice.kill();
|
||||||
@ -545,15 +564,21 @@ class FlutterPlatform extends PlatformPlugin {
|
|||||||
await Future.any<void>(<Future<void>>[
|
await Future.any<void>(<Future<void>>[
|
||||||
testDevice.finished,
|
testDevice.finished,
|
||||||
() async {
|
() async {
|
||||||
final Uri? processVmServiceUri = await testDevice.vmServiceUri;
|
final [Object? first, Object? _] = await Future.wait<Object?>(
|
||||||
if (processVmServiceUri != null) {
|
<Future<Object?>>[
|
||||||
globals.printTrace('test $ourTestCount: VM Service uri is available at $processVmServiceUri');
|
// This future may depend on [_handleStartedDevice] having been called
|
||||||
} else {
|
remoteChannelCompleter.future,
|
||||||
globals.printTrace('test $ourTestCount: VM Service uri is not available');
|
testDevice.vmServiceUri.then<void>((Uri? processVmServiceUri) {
|
||||||
}
|
_handleStartedDevice(processVmServiceUri, ourTestCount);
|
||||||
watcher?.handleStartedDevice(processVmServiceUri);
|
}),
|
||||||
|
],
|
||||||
|
// 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');
|
globals.printTrace('test $ourTestCount: connected to test device, now awaiting test result');
|
||||||
|
|
||||||
await _pipeHarnessToRemote(
|
await _pipeHarnessToRemote(
|
||||||
|
@ -3,11 +3,17 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:file/memory.dart';
|
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/file_system.dart';
|
||||||
import 'package:flutter_tools/src/base/io.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/build_info.dart';
|
||||||
import 'package:flutter_tools/src/device.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:flutter_tools/src/test/flutter_platform.dart';
|
||||||
|
import 'package:test/fake.dart';
|
||||||
import 'package:test_core/backend.dart';
|
import 'package:test_core/backend.dart';
|
||||||
|
|
||||||
import '../src/common.dart';
|
import '../src/common.dart';
|
||||||
@ -63,6 +69,34 @@ void main() {
|
|||||||
ProcessManager: () => FakeProcessManager.any(),
|
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', () {
|
testUsingContext('installHook creates a FlutterPlatform', () {
|
||||||
expect(() => installHook(
|
expect(() => installHook(
|
||||||
shellPath: 'abc',
|
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