Catch unable to start app exception (#154970)

Fixes https://github.com/flutter/flutter/issues/153433
This commit is contained in:
Christopher Fujino 2024-09-16 10:32:49 -07:00 committed by GitHub
parent 0a400f8cd8
commit b565379812
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 109 additions and 9 deletions

View File

@ -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(

View File

@ -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 {}