Unverified Commit 80ee3dd0 authored by Emmanuel Garcia's avatar Emmanuel Garcia Committed by GitHub

Print message when HttpException is thrown after running `flutter run` (#37440)

parent 5c147051
......@@ -512,6 +512,23 @@ class FlutterDevice {
}
}
// Issue: https://github.com/flutter/flutter/issues/33050
// Matches the following patterns:
// HttpException: Connection closed before full header was received, uri = *
// HttpException: , uri = *
final RegExp kAndroidQHttpConnectionClosedExp = RegExp(r'^HttpException\:.+\, uri \=.+$');
/// Returns `true` if any of the devices is running Android Q.
Future<bool> hasDeviceRunningAndroidQ(List<FlutterDevice> flutterDevices) async {
for (FlutterDevice flutterDevice in flutterDevices) {
final String sdkNameAndVersion = await flutterDevice.device.sdkNameAndVersion;
if (sdkNameAndVersion != null && sdkNameAndVersion.startsWith('Android 10')) {
return true;
}
}
return false;
}
// Shared code between different resident application runners.
abstract class ResidentRunner {
ResidentRunner(
......
......@@ -131,6 +131,13 @@ class ColdRunner extends ResidentRunner {
await connectToServiceProtocol();
} catch (error) {
printError('Error connecting to the service protocol: $error');
// https://github.com/flutter/flutter/issues/33050
// TODO(blasten): Remove this check once https://issuetracker.google.com/issues/132325318 has been fixed.
if (await hasDeviceRunningAndroidQ(flutterDevices) &&
error.toString().contains(kAndroidQHttpConnectionClosedExp)) {
printStatus('🔨 If you are using an emulator running Android Q Beta, consider using an emulator running API level 29 or lower.');
printStatus('Learn more about the status of this issue on https://issuetracker.google.com/issues/132325318');
}
return 2;
}
for (FlutterDevice device in flutterDevices) {
......
......@@ -155,6 +155,13 @@ class HotRunner extends ResidentRunner {
);
} catch (error) {
printError('Error connecting to the service protocol: $error');
// https://github.com/flutter/flutter/issues/33050
// TODO(blasten): Remove this check once https://issuetracker.google.com/issues/132325318 has been fixed.
if (await hasDeviceRunningAndroidQ(flutterDevices) &&
error.toString().contains(kAndroidQHttpConnectionClosedExp)) {
printStatus('🔨 If you are using an emulator running Android Q Beta, consider using an emulator running API level 29 or lower.');
printStatus('Learn more about the status of this issue on https://issuetracker.google.com/issues/132325318.');
}
return 2;
}
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
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/compile.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/run_cold.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart';
import '../src/common.dart';
import '../src/context.dart';
import '../src/mocks.dart';
void main() {
group('cold attach', () {
MockResidentCompiler residentCompiler;
BufferLogger mockLogger;
setUp(() {
mockLogger = BufferLogger();
residentCompiler = MockResidentCompiler();
});
testUsingContext('Prints message when HttpException is thrown - 1', () async {
final MockDevice mockDevice = MockDevice();
when(mockDevice.supportsHotReload).thenReturn(true);
when(mockDevice.supportsHotRestart).thenReturn(false);
when(mockDevice.targetPlatform).thenAnswer((Invocation _) async => TargetPlatform.tester);
when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation _) async => 'Android 10');
final List<FlutterDevice> devices = <FlutterDevice>[
TestFlutterDevice(
device: mockDevice,
generator: residentCompiler,
exception: const HttpException('Connection closed before full header was received, '
'uri = http://127.0.0.1:63394/5ZmLv8A59xY=/ws')
),
];
final int exitCode = await ColdRunner(devices,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
).attach();
expect(exitCode, 2);
expect(mockLogger.statusText, contains('If you are using an emulator running Android Q Beta, '
'consider using an emulator running API level 29 or lower.'));
expect(mockLogger.statusText, contains('Learn more about the status of this issue on '
'https://issuetracker.google.com/issues/132325318'));
}, overrides: <Type, Generator>{
Logger: () => mockLogger,
});
testUsingContext('Prints message when HttpException is thrown - 2', () async {
final MockDevice mockDevice = MockDevice();
when(mockDevice.supportsHotReload).thenReturn(true);
when(mockDevice.supportsHotRestart).thenReturn(false);
when(mockDevice.targetPlatform).thenAnswer((Invocation _) async => TargetPlatform.tester);
when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation _) async => 'Android 10');
final List<FlutterDevice> devices = <FlutterDevice>[
TestFlutterDevice(
device: mockDevice,
generator: residentCompiler,
exception: const HttpException(', uri = http://127.0.0.1:63394/5ZmLv8A59xY=/ws')
),
];
final int exitCode = await ColdRunner(devices,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
).attach();
expect(exitCode, 2);
expect(mockLogger.statusText, contains('If you are using an emulator running Android Q Beta, '
'consider using an emulator running API level 29 or lower.'));
expect(mockLogger.statusText, contains('Learn more about the status of this issue on '
'https://issuetracker.google.com/issues/132325318'));
}, overrides: <Type, Generator>{
Logger: () => mockLogger,
});
});
}
class MockDevice extends Mock implements Device {
MockDevice() {
when(isSupported()).thenReturn(true);
}
}
class TestFlutterDevice extends FlutterDevice {
TestFlutterDevice({
@required Device device,
@required this.exception,
@required ResidentCompiler generator
}) : assert(exception != null),
super(device, buildMode: BuildMode.debug, generator: generator, trackWidgetCreation: false);
/// The exception to throw when the connect method is called.
final Exception exception;
@override
Future<void> connect({
ReloadSources reloadSources,
Restart restart,
CompileExpression compileExpression,
}) async {
throw exception;
}
}
......@@ -5,11 +5,15 @@
import 'dart:async';
import 'package:flutter_tools/src/artifacts.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/compile.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/run_hot.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:meta/meta.dart';
import 'package:mockito/mockito.dart';
......@@ -262,6 +266,77 @@ void main() {
});
});
});
group('hot attach', () {
MockResidentCompiler residentCompiler = MockResidentCompiler();
BufferLogger mockLogger;
MockLocalEngineArtifacts mockArtifacts;
setUp(() {
residentCompiler = MockResidentCompiler();
mockLogger = BufferLogger();
mockArtifacts = MockLocalEngineArtifacts();
});
testUsingContext('Prints message when HttpException is thrown - 1', () async {
final MockDevice mockDevice = MockDevice();
when(mockDevice.supportsHotReload).thenReturn(true);
when(mockDevice.supportsHotRestart).thenReturn(false);
when(mockDevice.targetPlatform).thenAnswer((Invocation _) async => TargetPlatform.tester);
when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation _) async => 'Android 10');
final List<FlutterDevice> devices = <FlutterDevice>[
TestFlutterDevice(
device: mockDevice,
generator: residentCompiler,
exception: const HttpException('Connection closed before full header was received, '
'uri = http://127.0.0.1:63394/5ZmLv8A59xY=/ws')
),
];
final int exitCode = await HotRunner(devices,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
).attach();
expect(exitCode, 2);
expect(mockLogger.statusText, contains('If you are using an emulator running Android Q Beta, '
'consider using an emulator running API level 29 or lower.'));
expect(mockLogger.statusText, contains('Learn more about the status of this issue on '
'https://issuetracker.google.com/issues/132325318'));
}, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
Logger: () => mockLogger,
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
});
testUsingContext('Prints message when HttpException is thrown - 2', () async {
final MockDevice mockDevice = MockDevice();
when(mockDevice.supportsHotReload).thenReturn(true);
when(mockDevice.supportsHotRestart).thenReturn(false);
when(mockDevice.targetPlatform).thenAnswer((Invocation _) async => TargetPlatform.tester);
when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation _) async => 'Android 10');
final List<FlutterDevice> devices = <FlutterDevice>[
TestFlutterDevice(
device: mockDevice,
generator: residentCompiler,
exception: const HttpException(', uri = http://127.0.0.1:63394/5ZmLv8A59xY=/ws')
),
];
final int exitCode = await HotRunner(devices,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
).attach();
expect(exitCode, 2);
expect(mockLogger.statusText, contains('If you are using an emulator running Android Q Beta, '
'consider using an emulator running API level 29 or lower.'));
expect(mockLogger.statusText, contains('Learn more about the status of this issue on '
'https://issuetracker.google.com/issues/132325318'));
}, overrides: <Type, Generator>{
Artifacts: () => mockArtifacts,
Logger: () => mockLogger,
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
});
});
}
class MockDevFs extends Mock implements DevFS {}
......@@ -274,6 +349,27 @@ class MockDevice extends Mock implements Device {
}
}
class TestFlutterDevice extends FlutterDevice {
TestFlutterDevice({
@required Device device,
@required this.exception,
@required ResidentCompiler generator
}) : assert(exception != null),
super(device, buildMode: BuildMode.debug, generator: generator, trackWidgetCreation: false);
/// The exception to throw when the connect method is called.
final Exception exception;
@override
Future<void> connect({
ReloadSources reloadSources,
Restart restart,
CompileExpression compileExpression,
}) async {
throw exception;
}
}
class TestHotRunnerConfig extends HotRunnerConfig {
TestHotRunnerConfig({@required this.successfulSetup});
bool successfulSetup;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment