mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

[flutter_tools] Enable hot reload on the web Update the defaults so hot reload is enabled on web development builds by default. This enables the use of a new module representation in the compiled JavaScript. Passing `--no-web-experimental-hot-reload` will disable the ability to hot reload and return to the AMD JavaScript module representation. This change avoids enabling hot reload in the flutter drive tests since they rely on `-d web-server` which has known startup issues. When https://github.com/dart-lang/sdk/issues/60289 is resolved it should be safe to enable hot reload by default for the `flutter drive` tests. Fixes: https://github.com/flutter/flutter/issues/167510
2298 lines
79 KiB
Dart
2298 lines
79 KiB
Dart
// Copyright 2014 The Flutter 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 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:dwds/dwds.dart';
|
|
import 'package:file/memory.dart';
|
|
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/logger.dart';
|
|
import 'package:flutter_tools/src/base/platform.dart';
|
|
import 'package:flutter_tools/src/base/terminal.dart';
|
|
import 'package:flutter_tools/src/base/time.dart';
|
|
import 'package:flutter_tools/src/build_info.dart';
|
|
import 'package:flutter_tools/src/build_system/build_system.dart';
|
|
import 'package:flutter_tools/src/build_system/tools/shader_compiler.dart';
|
|
import 'package:flutter_tools/src/compile.dart';
|
|
import 'package:flutter_tools/src/dart/pub.dart';
|
|
import 'package:flutter_tools/src/devfs.dart';
|
|
import 'package:flutter_tools/src/device.dart';
|
|
import 'package:flutter_tools/src/globals.dart' as globals;
|
|
import 'package:flutter_tools/src/isolated/devfs_web.dart';
|
|
import 'package:flutter_tools/src/isolated/resident_web_runner.dart';
|
|
import 'package:flutter_tools/src/project.dart';
|
|
import 'package:flutter_tools/src/resident_devtools_handler.dart';
|
|
import 'package:flutter_tools/src/resident_runner.dart';
|
|
import 'package:flutter_tools/src/vmservice.dart';
|
|
import 'package:flutter_tools/src/web/chrome.dart';
|
|
import 'package:flutter_tools/src/web/web_device.dart';
|
|
import 'package:package_config/package_config.dart';
|
|
import 'package:package_config/package_config_types.dart';
|
|
import 'package:test/fake.dart';
|
|
import 'package:unified_analytics/unified_analytics.dart';
|
|
import 'package:vm_service/vm_service.dart' as vm_service;
|
|
import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart';
|
|
|
|
import '../src/common.dart';
|
|
import '../src/context.dart';
|
|
import '../src/fake_process_manager.dart';
|
|
import '../src/fake_vm_services.dart';
|
|
import '../src/fakes.dart' as test_fakes;
|
|
import '../src/package_config.dart';
|
|
import '../src/test_build_system.dart';
|
|
import '../src/throwing_pub.dart';
|
|
import 'resident_runner_helpers.dart';
|
|
|
|
const List<VmServiceExpectation> kSetPauseIsolatesOnStartExpectations = <VmServiceExpectation>[
|
|
FakeVmServiceRequest(
|
|
method: 'setFlag',
|
|
args: <String, Object>{'name': 'pause_isolates_on_start', 'value': 'true'},
|
|
),
|
|
];
|
|
|
|
const List<VmServiceExpectation> kAttachLogExpectations = <VmServiceExpectation>[
|
|
FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{'streamId': 'Stdout'}),
|
|
FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{'streamId': 'Stderr'}),
|
|
];
|
|
|
|
const List<VmServiceExpectation> kAttachIsolateExpectations = <VmServiceExpectation>[
|
|
FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{'streamId': 'Service'}),
|
|
FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{'streamId': 'Isolate'}),
|
|
FakeVmServiceRequest(
|
|
method: 'registerService',
|
|
args: <String, Object>{'service': kReloadSourcesServiceName, 'alias': kFlutterToolAlias},
|
|
),
|
|
FakeVmServiceRequest(
|
|
method: 'registerService',
|
|
args: <String, Object>{'service': kFlutterVersionServiceName, 'alias': kFlutterToolAlias},
|
|
),
|
|
FakeVmServiceRequest(
|
|
method: 'registerService',
|
|
args: <String, Object>{'service': kFlutterMemoryInfoServiceName, 'alias': kFlutterToolAlias},
|
|
),
|
|
FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{'streamId': 'Extension'}),
|
|
];
|
|
|
|
const List<VmServiceExpectation> kAttachExpectations = <VmServiceExpectation>[
|
|
...kAttachLogExpectations,
|
|
...kAttachIsolateExpectations,
|
|
];
|
|
|
|
const List<VmServiceExpectation> kStartPausedAndAttachExpectations = <VmServiceExpectation>[
|
|
...kSetPauseIsolatesOnStartExpectations,
|
|
...kAttachLogExpectations,
|
|
...kAttachIsolateExpectations,
|
|
];
|
|
|
|
const List<String> kDdcLibraryBundleFlags = <String>[
|
|
'--dartdevc-module-format=ddc',
|
|
'--dartdevc-canary',
|
|
];
|
|
|
|
void main() {
|
|
late FakeDebugConnection debugConnection;
|
|
late FakeChromeDevice chromeDevice;
|
|
late FakeAppConnection appConnection;
|
|
late FakeFlutterDevice flutterDevice;
|
|
late FakeWebDevFS webDevFS;
|
|
late FakeResidentCompiler residentCompiler;
|
|
late FakeChromeConnection chromeConnection;
|
|
late FakeChromeTab chromeTab;
|
|
late FakeWebServerDevice webServerDevice;
|
|
late FakeDevice mockDevice;
|
|
late FakeVmServiceHost fakeVmServiceHost;
|
|
late MemoryFileSystem fileSystem;
|
|
late ProcessManager processManager;
|
|
late FakeAnalytics fakeAnalytics;
|
|
|
|
setUp(() {
|
|
fileSystem = MemoryFileSystem.test();
|
|
processManager = FakeProcessManager.any();
|
|
debugConnection = FakeDebugConnection();
|
|
mockDevice = FakeDevice();
|
|
appConnection = FakeAppConnection();
|
|
webDevFS = FakeWebDevFS();
|
|
residentCompiler = FakeResidentCompiler();
|
|
chromeDevice = FakeChromeDevice();
|
|
chromeConnection = FakeChromeConnection();
|
|
chromeTab = FakeChromeTab('index.html');
|
|
webServerDevice = FakeWebServerDevice();
|
|
flutterDevice =
|
|
FakeFlutterDevice()
|
|
.._devFS = webDevFS
|
|
..device = mockDevice
|
|
..generator = residentCompiler;
|
|
fileSystem.file('pubspec.yaml').writeAsStringSync('''
|
|
name: my_app
|
|
''');
|
|
writePackageConfigFiles(directory: fileSystem.currentDirectory, mainLibName: 'my_app');
|
|
fakeAnalytics = getInitializedFakeAnalyticsInstance(
|
|
fs: fileSystem,
|
|
fakeFlutterVersion: test_fakes.FakeFlutterVersion(),
|
|
);
|
|
});
|
|
|
|
void setupMocks() {
|
|
fileSystem.file('pubspec.yaml').createSync();
|
|
fileSystem.file('lib/main.dart').createSync(recursive: true);
|
|
fileSystem.file('web/index.html').createSync(recursive: true);
|
|
webDevFS.report = UpdateFSReport(success: true);
|
|
debugConnection.fakeVmServiceHost = () => fakeVmServiceHost;
|
|
webDevFS.result = ConnectionResult(appConnection, debugConnection, debugConnection.vmService);
|
|
debugConnection.uri = 'ws://127.0.0.1/abcd/';
|
|
chromeConnection.tabs.add(chromeTab);
|
|
}
|
|
|
|
testUsingContext(
|
|
'runner with web server device does not support debugging without --start-paused',
|
|
() {
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
|
|
flutterDevice.device = WebServerDevice(logger: BufferLogger.test());
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
|
|
final ResidentRunner profileResidentWebRunner = ResidentWebRunner(
|
|
flutterDevice,
|
|
flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
|
|
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
|
|
fileSystem: fileSystem,
|
|
logger: BufferLogger.test(),
|
|
terminal: Terminal.test(),
|
|
platform: FakePlatform(),
|
|
outputPreferences: OutputPreferences.test(),
|
|
analytics: globals.analytics,
|
|
systemClock: globals.systemClock,
|
|
);
|
|
|
|
expect(profileResidentWebRunner.debuggingEnabled, false);
|
|
|
|
flutterDevice.device = chromeDevice;
|
|
|
|
expect(residentWebRunner.debuggingEnabled, true);
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'runner with web server device supports debugging with --start-paused',
|
|
() {
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
|
|
setupMocks();
|
|
flutterDevice.device = WebServerDevice(logger: BufferLogger.test());
|
|
final ResidentRunner profileResidentWebRunner = ResidentWebRunner(
|
|
flutterDevice,
|
|
flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
|
|
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, startPaused: true),
|
|
fileSystem: fileSystem,
|
|
logger: BufferLogger.test(),
|
|
terminal: Terminal.test(),
|
|
platform: FakePlatform(),
|
|
outputPreferences: OutputPreferences.test(),
|
|
analytics: globals.analytics,
|
|
systemClock: globals.systemClock,
|
|
);
|
|
|
|
expect(profileResidentWebRunner.uri, webDevFS.baseUri);
|
|
expect(profileResidentWebRunner.debuggingEnabled, true);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
},
|
|
);
|
|
testUsingContext(
|
|
'profile does not supportsServiceProtocol',
|
|
() {
|
|
final ResidentRunner residentWebRunner = ResidentWebRunner(
|
|
flutterDevice,
|
|
flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
|
|
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
|
|
fileSystem: fileSystem,
|
|
logger: BufferLogger.test(),
|
|
terminal: Terminal.test(),
|
|
platform: FakePlatform(),
|
|
outputPreferences: OutputPreferences.test(),
|
|
analytics: globals.analytics,
|
|
systemClock: globals.systemClock,
|
|
);
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
|
|
flutterDevice.device = chromeDevice;
|
|
final ResidentRunner profileResidentWebRunner = ResidentWebRunner(
|
|
flutterDevice,
|
|
flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
|
|
debuggingOptions: DebuggingOptions.enabled(BuildInfo.profile),
|
|
fileSystem: fileSystem,
|
|
logger: BufferLogger.test(),
|
|
terminal: Terminal.test(),
|
|
platform: FakePlatform(),
|
|
outputPreferences: OutputPreferences.test(),
|
|
analytics: globals.analytics,
|
|
systemClock: globals.systemClock,
|
|
);
|
|
|
|
expect(profileResidentWebRunner.supportsServiceProtocol, false);
|
|
expect(residentWebRunner.supportsServiceProtocol, true);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Can successfully run and connect to vmservice',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, logger: logger);
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
|
|
setupMocks();
|
|
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
final DebugConnectionInfo debugConnectionInfo = await connectionInfoCompleter.future;
|
|
|
|
expect(appConnection.ranMain, true);
|
|
expect(logger.statusText, contains('Debug service listening on ws://127.0.0.1/abcd/'));
|
|
expect(debugConnectionInfo.wsUri.toString(), 'ws://127.0.0.1/abcd/');
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'WebRunner copies compiled app.dill to cache during startup',
|
|
() async {
|
|
final DebuggingOptions debuggingOptions = DebuggingOptions.enabled(
|
|
const BuildInfo(
|
|
BuildMode.debug,
|
|
null,
|
|
treeShakeIcons: false,
|
|
packageConfigPath: '.dart_tool/package_config.json',
|
|
),
|
|
);
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(
|
|
flutterDevice,
|
|
debuggingOptions: debuggingOptions,
|
|
);
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
|
|
setupMocks();
|
|
|
|
residentWebRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('ABC');
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
|
|
expect(
|
|
await fileSystem.file(fileSystem.path.join('build', 'cache.dill')).readAsString(),
|
|
'ABC',
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'WebRunner copies compiled app.dill to cache during startup with track-widget-creation',
|
|
() async {
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
|
|
setupMocks();
|
|
|
|
residentWebRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('ABC');
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
|
|
expect(
|
|
await fileSystem
|
|
.file(fileSystem.path.join('build', 'cache.dill.track.dill'))
|
|
.readAsString(),
|
|
'ABC',
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/60613
|
|
testUsingContext(
|
|
'ResidentWebRunner calls appFailedToStart if initial compilation fails',
|
|
() async {
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
|
|
setupMocks();
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
|
|
fileSystem.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
|
|
webDevFS.report = UpdateFSReport();
|
|
|
|
expect(await residentWebRunner.run(), 1);
|
|
// Completing this future ensures that the daemon can exit correctly.
|
|
expect(await residentWebRunner.waitForAppToFinish(), 1);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Can successfully run without an index.html including status warning',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
|
|
setupMocks();
|
|
fileSystem.directory('web').deleteSync(recursive: true);
|
|
final ResidentWebRunner residentWebRunner = ResidentWebRunner(
|
|
flutterDevice,
|
|
flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
|
|
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
|
|
stayResident: false,
|
|
fileSystem: fileSystem,
|
|
logger: logger,
|
|
terminal: Terminal.test(),
|
|
platform: FakePlatform(),
|
|
outputPreferences: OutputPreferences.test(),
|
|
analytics: globals.analytics,
|
|
systemClock: globals.systemClock,
|
|
devtoolsHandler: createNoOpHandler,
|
|
);
|
|
|
|
expect(await residentWebRunner.run(), 0);
|
|
expect(logger.statusText, contains('This application is not configured to build on the web'));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Can successfully run and disconnect with --no-resident',
|
|
() async {
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
|
|
setupMocks();
|
|
final ResidentRunner residentWebRunner = ResidentWebRunner(
|
|
flutterDevice,
|
|
flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
|
|
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
|
|
stayResident: false,
|
|
fileSystem: fileSystem,
|
|
logger: BufferLogger.test(),
|
|
terminal: Terminal.test(),
|
|
platform: FakePlatform(),
|
|
outputPreferences: OutputPreferences.test(),
|
|
analytics: globals.analytics,
|
|
systemClock: globals.systemClock,
|
|
devtoolsHandler: createNoOpHandler,
|
|
);
|
|
|
|
expect(await residentWebRunner.run(), 0);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Detach keeps device running',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
|
|
setupMocks();
|
|
fileSystem.directory('web').deleteSync(recursive: true);
|
|
final ResidentWebRunner residentWebRunner = ResidentWebRunner(
|
|
flutterDevice,
|
|
flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
|
|
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
|
|
fileSystem: fileSystem,
|
|
logger: logger,
|
|
terminal: Terminal.test(),
|
|
platform: FakePlatform(),
|
|
outputPreferences: OutputPreferences.test(),
|
|
analytics: globals.analytics,
|
|
systemClock: globals.systemClock,
|
|
devtoolsHandler: createNoOpHandler,
|
|
);
|
|
|
|
mockDevice.dds = DartDevelopmentService(logger: logger);
|
|
|
|
expect(mockDevice.isRunning, false);
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
expect(mockDevice.isRunning, true);
|
|
await residentWebRunner.detach();
|
|
expect(residentWebRunner.stopAppDuringCleanup, false);
|
|
await residentWebRunner.exit();
|
|
await residentWebRunner.cleanupAtFinish();
|
|
expect(mockDevice.isRunning, true);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Quit stops device',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
|
|
setupMocks();
|
|
fileSystem.directory('web').deleteSync(recursive: true);
|
|
final ResidentWebRunner residentWebRunner = ResidentWebRunner(
|
|
flutterDevice,
|
|
flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
|
|
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
|
|
fileSystem: fileSystem,
|
|
logger: logger,
|
|
terminal: Terminal.test(),
|
|
platform: FakePlatform(),
|
|
outputPreferences: OutputPreferences.test(),
|
|
analytics: globals.analytics,
|
|
systemClock: globals.systemClock,
|
|
devtoolsHandler: createNoOpHandler,
|
|
);
|
|
|
|
mockDevice.dds = DartDevelopmentService(logger: logger);
|
|
|
|
expect(mockDevice.isRunning, false);
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
expect(mockDevice.isRunning, true);
|
|
expect(residentWebRunner.stopAppDuringCleanup, true);
|
|
await residentWebRunner.cleanupAtFinish();
|
|
expect(mockDevice.isRunning, false);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Listens to stdout and stderr streams before running main',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, logger: logger);
|
|
fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
...kAttachLogExpectations,
|
|
FakeVmServiceStreamResponse(
|
|
streamId: 'Stdout',
|
|
event: vm_service.Event(
|
|
timestamp: 0,
|
|
kind: vm_service.EventStreams.kStdout,
|
|
bytes: base64.encode(utf8.encode('THIS MESSAGE IS IMPORTANT')),
|
|
),
|
|
),
|
|
FakeVmServiceStreamResponse(
|
|
streamId: 'Stderr',
|
|
event: vm_service.Event(
|
|
timestamp: 0,
|
|
kind: vm_service.EventStreams.kStderr,
|
|
bytes: base64.encode(utf8.encode('SO IS THIS')),
|
|
),
|
|
),
|
|
...kAttachIsolateExpectations,
|
|
],
|
|
);
|
|
setupMocks();
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
|
|
expect(logger.statusText, contains('THIS MESSAGE IS IMPORTANT'));
|
|
expect(logger.statusText, contains('SO IS THIS'));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Listens to extension events with structured errors',
|
|
() async {
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(
|
|
flutterDevice,
|
|
logger: testLogger,
|
|
);
|
|
final List<VmServiceExpectation> requests = <VmServiceExpectation>[
|
|
...kAttachExpectations,
|
|
// This is the first error message of a session.
|
|
FakeVmServiceStreamResponse(
|
|
streamId: 'Extension',
|
|
event: vm_service.Event(
|
|
timestamp: 0,
|
|
extensionKind: 'Flutter.Error',
|
|
extensionData: vm_service.ExtensionData.parse(<String, Object?>{
|
|
'errorsSinceReload': 0,
|
|
'renderedErrorText': 'first',
|
|
}),
|
|
kind: vm_service.EventStreams.kExtension,
|
|
),
|
|
),
|
|
// This is the second error message of a session.
|
|
FakeVmServiceStreamResponse(
|
|
streamId: 'Extension',
|
|
event: vm_service.Event(
|
|
timestamp: 0,
|
|
extensionKind: 'Flutter.Error',
|
|
extensionData: vm_service.ExtensionData.parse(<String, Object?>{
|
|
'errorsSinceReload': 1,
|
|
'renderedErrorText': 'second',
|
|
}),
|
|
kind: vm_service.EventStreams.kExtension,
|
|
),
|
|
),
|
|
// This is not Flutter.Error kind data, so it should not be logged, even though it has a renderedErrorText field.
|
|
FakeVmServiceStreamResponse(
|
|
streamId: 'Extension',
|
|
event: vm_service.Event(
|
|
timestamp: 0,
|
|
extensionKind: 'Other',
|
|
extensionData: vm_service.ExtensionData.parse(<String, Object?>{
|
|
'errorsSinceReload': 2,
|
|
'renderedErrorText': 'not an error',
|
|
}),
|
|
kind: vm_service.EventStreams.kExtension,
|
|
),
|
|
),
|
|
// This is the third error message of a session.
|
|
FakeVmServiceStreamResponse(
|
|
streamId: 'Extension',
|
|
event: vm_service.Event(
|
|
timestamp: 0,
|
|
extensionKind: 'Flutter.Error',
|
|
extensionData: vm_service.ExtensionData.parse(<String, Object?>{
|
|
'errorsSinceReload': 2,
|
|
'renderedErrorText': 'third',
|
|
}),
|
|
kind: vm_service.EventStreams.kExtension,
|
|
),
|
|
),
|
|
// This is bogus error data.
|
|
FakeVmServiceStreamResponse(
|
|
streamId: 'Extension',
|
|
event: vm_service.Event(
|
|
timestamp: 0,
|
|
extensionKind: 'Flutter.Error',
|
|
extensionData: vm_service.ExtensionData.parse(<String, Object?>{'other': 'bad stuff'}),
|
|
kind: vm_service.EventStreams.kExtension,
|
|
),
|
|
),
|
|
// Empty error text should not break anything.
|
|
FakeVmServiceStreamResponse(
|
|
streamId: 'Extension',
|
|
event: vm_service.Event(
|
|
timestamp: 0,
|
|
extensionKind: 'Flutter.Error',
|
|
extensionData: vm_service.ExtensionData.parse(<String, Object?>{
|
|
'test': 'data',
|
|
'renderedErrorText': '',
|
|
}),
|
|
kind: vm_service.EventStreams.kExtension,
|
|
),
|
|
),
|
|
// Messages without errorsSinceReload should act as if errorsSinceReload: 0
|
|
FakeVmServiceStreamResponse(
|
|
streamId: 'Extension',
|
|
event: vm_service.Event(
|
|
timestamp: 0,
|
|
extensionKind: 'Flutter.Error',
|
|
extensionData: vm_service.ExtensionData.parse(<String, Object?>{
|
|
'test': 'data',
|
|
'renderedErrorText': 'error text',
|
|
}),
|
|
kind: vm_service.EventStreams.kExtension,
|
|
),
|
|
),
|
|
// When adding things here, make sure the last one is supposed to output something
|
|
// to the statusLog, otherwise you won't be able to distinguish the absence of output
|
|
// due to it passing the test from absence due to it not running the test.
|
|
];
|
|
// We use requests below, so make a copy of it here (FakeVmServiceHost will
|
|
// clear its copy internally, which would affect our pumping below).
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: requests.toList());
|
|
|
|
setupMocks();
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
assert(requests.length > 5, 'requests was modified');
|
|
for (final Object _ in requests) {
|
|
// pump the task queue once for each message
|
|
await null;
|
|
}
|
|
|
|
expect(
|
|
testLogger.statusText,
|
|
'Launching lib/main.dart on FakeDevice in debug mode...\n'
|
|
'Waiting for connection from debug service on FakeDevice...\n'
|
|
'Debug service listening on ws://127.0.0.1/abcd/\n'
|
|
'\n'
|
|
'first\n'
|
|
'\n'
|
|
'second\n'
|
|
'third\n'
|
|
'\n'
|
|
'\n' // the empty message
|
|
'\n'
|
|
'\n'
|
|
'error text\n'
|
|
'\n',
|
|
);
|
|
|
|
expect(
|
|
testLogger.errorText,
|
|
'Received an invalid Flutter.Error message from app: {other: bad stuff}\n',
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Does not run main with --start-paused',
|
|
() async {
|
|
final ResidentRunner residentWebRunner = ResidentWebRunner(
|
|
flutterDevice,
|
|
flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
|
|
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, startPaused: true),
|
|
fileSystem: fileSystem,
|
|
logger: BufferLogger.test(),
|
|
terminal: Terminal.test(),
|
|
platform: FakePlatform(),
|
|
outputPreferences: OutputPreferences.test(),
|
|
analytics: globals.analytics,
|
|
systemClock: globals.systemClock,
|
|
devtoolsHandler: createNoOpHandler,
|
|
);
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: kStartPausedAndAttachExpectations.toList());
|
|
setupMocks();
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
|
|
expect(appConnection.ranMain, false);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Can hot reload after attaching',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(
|
|
flutterDevice,
|
|
logger: logger,
|
|
systemClock: SystemClock.fixed(DateTime(2001)),
|
|
debuggingOptions: DebuggingOptions.enabled(
|
|
const BuildInfo(
|
|
BuildMode.debug,
|
|
null,
|
|
trackWidgetCreation: true,
|
|
treeShakeIcons: false,
|
|
packageConfigPath: '.dart_tool/package_config.json',
|
|
// TODO(nshahan): Remove when hot reload can no longer be disabled.
|
|
webEnableHotReload: true,
|
|
extraFrontEndOptions: kDdcLibraryBundleFlags,
|
|
),
|
|
),
|
|
);
|
|
fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
...kAttachExpectations,
|
|
FakeVmServiceRequest(method: 'getVM', jsonResponse: fakeVM.toJson()),
|
|
const FakeVmServiceRequest(
|
|
method: kReloadSourcesServiceName,
|
|
args: <String, Object>{'isolateId': '1'},
|
|
jsonResponse: <String, Object>{'type': 'ReloadReport', 'success': true},
|
|
),
|
|
const FakeVmServiceRequest(
|
|
method: 'ext.flutter.reassemble',
|
|
jsonResponse: <String, Object>{'type': 'ReloadReport', 'success': true},
|
|
),
|
|
const FakeVmServiceRequest(
|
|
method: 'streamListen',
|
|
args: <String, Object>{'streamId': 'Isolate'},
|
|
),
|
|
],
|
|
);
|
|
setupMocks();
|
|
final TestChromiumLauncher chromiumLauncher = TestChromiumLauncher();
|
|
final FakeProcess process = FakeProcess();
|
|
final Chromium chrome = Chromium(
|
|
1,
|
|
chromeConnection,
|
|
chromiumLauncher: chromiumLauncher,
|
|
process: process,
|
|
logger: logger,
|
|
);
|
|
chromiumLauncher.setInstance(chrome);
|
|
|
|
flutterDevice.device = GoogleChromeDevice(
|
|
fileSystem: fileSystem,
|
|
chromiumLauncher: chromiumLauncher,
|
|
logger: BufferLogger.test(),
|
|
platform: FakePlatform(),
|
|
processManager: FakeProcessManager.any(),
|
|
);
|
|
webDevFS.report = UpdateFSReport(success: true);
|
|
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
final DebugConnectionInfo debugConnectionInfo = await connectionInfoCompleter.future;
|
|
|
|
expect(debugConnectionInfo, isNotNull);
|
|
|
|
final OperationResult result = await residentWebRunner.restart();
|
|
|
|
expect(logger.statusText, contains('Reloaded application in'));
|
|
expect(result.code, 0);
|
|
expect(webDevFS.mainUri.toString(), contains('entrypoint.dart'));
|
|
|
|
expect(
|
|
fakeAnalytics.sentEvents,
|
|
contains(
|
|
Event.hotRunnerInfo(
|
|
label: 'reload',
|
|
targetPlatform: 'web-javascript',
|
|
sdkName: '',
|
|
emulator: false,
|
|
fullRestart: false,
|
|
overallTimeInMs: 0,
|
|
syncedBytes: 0,
|
|
invalidatedSourcesCount: 0,
|
|
transferTimeInMs: 0,
|
|
compileTimeInMs: 0,
|
|
findInvalidatedTimeInMs: 0,
|
|
scannedSourcesCount: 0,
|
|
reassembleTimeInMs: 0,
|
|
reloadVMTimeInMs: 0,
|
|
),
|
|
),
|
|
);
|
|
expect(
|
|
fakeAnalytics.sentEvents,
|
|
contains(Event.timing(workflow: 'hot', variableName: 'reload', elapsedMilliseconds: 0)),
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
Analytics: () => fakeAnalytics,
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Hot reload reject reports correct analytics',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(
|
|
flutterDevice,
|
|
logger: logger,
|
|
systemClock: SystemClock.fixed(DateTime(2001)),
|
|
debuggingOptions: DebuggingOptions.enabled(
|
|
const BuildInfo(
|
|
BuildMode.debug,
|
|
null,
|
|
trackWidgetCreation: true,
|
|
treeShakeIcons: false,
|
|
packageConfigPath: '.dart_tool/package_config.json',
|
|
// TODO(nshahan): Remove when hot reload can no longer be disabled.
|
|
webEnableHotReload: true,
|
|
extraFrontEndOptions: kDdcLibraryBundleFlags,
|
|
),
|
|
),
|
|
);
|
|
fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
...kAttachExpectations,
|
|
const FakeVmServiceRequest(
|
|
method: 'streamListen',
|
|
args: <String, Object>{'streamId': 'Isolate'},
|
|
),
|
|
],
|
|
);
|
|
setupMocks();
|
|
final TestChromiumLauncher chromiumLauncher = TestChromiumLauncher();
|
|
final FakeProcess process = FakeProcess();
|
|
final Chromium chrome = Chromium(
|
|
1,
|
|
chromeConnection,
|
|
chromiumLauncher: chromiumLauncher,
|
|
process: process,
|
|
logger: logger,
|
|
);
|
|
chromiumLauncher.setInstance(chrome);
|
|
|
|
flutterDevice.device = GoogleChromeDevice(
|
|
fileSystem: fileSystem,
|
|
chromiumLauncher: chromiumLauncher,
|
|
logger: BufferLogger.test(),
|
|
platform: FakePlatform(),
|
|
processManager: FakeProcessManager.any(),
|
|
);
|
|
webDevFS.report = UpdateFSReport(success: true);
|
|
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
final DebugConnectionInfo debugConnectionInfo = await connectionInfoCompleter.future;
|
|
|
|
expect(debugConnectionInfo, isNotNull);
|
|
|
|
webDevFS.report = UpdateFSReport(hotReloadRejected: true);
|
|
final OperationResult result = await residentWebRunner.restart();
|
|
|
|
expect(result.code, 1);
|
|
expect(webDevFS.mainUri.toString(), contains('entrypoint.dart'));
|
|
|
|
expect(
|
|
fakeAnalytics.sentEvents,
|
|
contains(
|
|
Event.hotRunnerInfo(
|
|
label: 'reload-reject',
|
|
targetPlatform: 'web-javascript',
|
|
sdkName: '',
|
|
emulator: false,
|
|
fullRestart: false,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
Analytics: () => fakeAnalytics,
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/167887.
|
|
testUsingContext(
|
|
'WASM builds report analysis without crashing',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(
|
|
flutterDevice,
|
|
logger: logger,
|
|
systemClock: SystemClock.fixed(DateTime(2001)),
|
|
debuggingOptions: DebuggingOptions.enabled(
|
|
const BuildInfo(
|
|
BuildMode.debug,
|
|
null,
|
|
trackWidgetCreation: true,
|
|
treeShakeIcons: false,
|
|
packageConfigPath: '.dart_tool/package_config.json',
|
|
// TODO(nshahan): Remove when hot reload can no longer be disabled.
|
|
webEnableHotReload: true,
|
|
extraFrontEndOptions: kDdcLibraryBundleFlags,
|
|
),
|
|
webUseWasm: true,
|
|
),
|
|
);
|
|
fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
...kAttachExpectations,
|
|
const FakeVmServiceRequest(
|
|
method: kReloadSourcesServiceName,
|
|
args: <String, Object>{'isolateId': ''},
|
|
jsonResponse: <String, Object>{'type': 'ReloadReport', 'success': true},
|
|
),
|
|
const FakeVmServiceRequest(
|
|
method: 'ext.flutter.reassemble',
|
|
jsonResponse: <String, Object>{'type': 'ReloadReport', 'success': true},
|
|
),
|
|
const FakeVmServiceRequest(
|
|
method: 'streamListen',
|
|
args: <String, Object>{'streamId': 'Isolate'},
|
|
),
|
|
],
|
|
);
|
|
setupMocks();
|
|
final TestChromiumLauncher chromiumLauncher = TestChromiumLauncher();
|
|
final FakeProcess process = FakeProcess();
|
|
final Chromium chrome = Chromium(
|
|
1,
|
|
chromeConnection,
|
|
chromiumLauncher: chromiumLauncher,
|
|
process: process,
|
|
logger: logger,
|
|
);
|
|
chromiumLauncher.setInstance(chrome);
|
|
|
|
flutterDevice.device = GoogleChromeDevice(
|
|
fileSystem: fileSystem,
|
|
chromiumLauncher: chromiumLauncher,
|
|
logger: BufferLogger.test(),
|
|
platform: FakePlatform(),
|
|
processManager: FakeProcessManager.any(),
|
|
);
|
|
webDevFS.report = UpdateFSReport(success: true);
|
|
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
final DebugConnectionInfo debugConnectionInfo = await connectionInfoCompleter.future;
|
|
|
|
expect(debugConnectionInfo, isNotNull);
|
|
|
|
final OperationResult result = await residentWebRunner.restart();
|
|
expect(logger.statusText, contains('Reloaded application in'));
|
|
expect(result.code, 0);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
Analytics: () => fakeAnalytics,
|
|
BuildSystem: () => TestBuildSystem.all(BuildResult(success: true)),
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
// Hot restart is available with and without the DDC library bundle format.
|
|
// Test one extra config where `fullRestart` is false without the DDC library
|
|
// bundle format - we should do a hot restart in this case because hot reload
|
|
// is not available.
|
|
for (final (bool webEnableHotReload, bool fullRestart) in <(bool, bool)>[
|
|
(true, true),
|
|
(false, true),
|
|
(false, false),
|
|
]) {
|
|
testUsingContext(
|
|
'Can hot restart after attaching with '
|
|
'webEnableHotReload: $webEnableHotReload fullRestart: $fullRestart',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(
|
|
flutterDevice,
|
|
logger: logger,
|
|
systemClock: SystemClock.fixed(DateTime(2001)),
|
|
debuggingOptions: DebuggingOptions.enabled(
|
|
BuildInfo(
|
|
BuildMode.debug,
|
|
null,
|
|
trackWidgetCreation: true,
|
|
treeShakeIcons: false,
|
|
packageConfigPath: '.dart_tool/package_config.json',
|
|
extraFrontEndOptions: webEnableHotReload ? kDdcLibraryBundleFlags : const <String>[],
|
|
webEnableHotReload: webEnableHotReload,
|
|
),
|
|
),
|
|
);
|
|
fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
...kAttachExpectations,
|
|
const FakeVmServiceRequest(
|
|
method: kHotRestartServiceName,
|
|
jsonResponse: <String, Object>{'type': 'Success'},
|
|
),
|
|
],
|
|
);
|
|
setupMocks();
|
|
final TestChromiumLauncher chromiumLauncher = TestChromiumLauncher();
|
|
final FakeProcess process = FakeProcess();
|
|
final Chromium chrome = Chromium(
|
|
1,
|
|
chromeConnection,
|
|
chromiumLauncher: chromiumLauncher,
|
|
process: process,
|
|
logger: logger,
|
|
);
|
|
chromiumLauncher.setInstance(chrome);
|
|
|
|
flutterDevice.device = GoogleChromeDevice(
|
|
fileSystem: fileSystem,
|
|
chromiumLauncher: chromiumLauncher,
|
|
logger: BufferLogger.test(),
|
|
platform: FakePlatform(),
|
|
processManager: FakeProcessManager.any(),
|
|
);
|
|
webDevFS.report = UpdateFSReport(success: true);
|
|
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
final OperationResult result = await residentWebRunner.restart(fullRestart: fullRestart);
|
|
|
|
// Ensure that generated entrypoint is generated correctly.
|
|
expect(webDevFS.mainUri, isNotNull);
|
|
final String entrypointContents = fileSystem.file(webDevFS.mainUri).readAsStringSync();
|
|
expect(entrypointContents, contains('// Flutter web bootstrap script'));
|
|
expect(entrypointContents, contains("import 'dart:ui_web' as ui_web;"));
|
|
expect(entrypointContents, contains('await ui_web.bootstrapEngine('));
|
|
|
|
expect(logger.statusText, contains('Restarted application in'));
|
|
expect(result.code, 0);
|
|
|
|
expect(
|
|
fakeAnalytics.sentEvents,
|
|
contains(
|
|
Event.hotRunnerInfo(
|
|
label: 'restart',
|
|
targetPlatform: 'web-javascript',
|
|
sdkName: '',
|
|
emulator: false,
|
|
fullRestart: true,
|
|
overallTimeInMs: 0,
|
|
syncedBytes: 0,
|
|
invalidatedSourcesCount: 0,
|
|
transferTimeInMs: 0,
|
|
compileTimeInMs: 0,
|
|
findInvalidatedTimeInMs: 0,
|
|
scannedSourcesCount: 0,
|
|
),
|
|
),
|
|
);
|
|
expect(
|
|
fakeAnalytics.sentEvents,
|
|
contains(
|
|
Event.timing(
|
|
workflow: 'hot',
|
|
variableName: 'web-incremental-restart',
|
|
elapsedMilliseconds: 0,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
Analytics: () => fakeAnalytics,
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
}
|
|
|
|
testUsingContext(
|
|
'Can hot restart after attaching with web-server device',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(
|
|
flutterDevice,
|
|
logger: logger,
|
|
systemClock: SystemClock.fixed(DateTime(2001)),
|
|
);
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations);
|
|
setupMocks();
|
|
flutterDevice.device = webServerDevice;
|
|
webDevFS.report = UpdateFSReport(success: true);
|
|
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
final OperationResult result = await residentWebRunner.restart(fullRestart: true);
|
|
|
|
expect(logger.statusText, contains('Restarted application in'));
|
|
expect(result.code, 0);
|
|
|
|
// web-server device does not send restart analytics
|
|
expect(fakeAnalytics.sentEvents, isEmpty);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
Analytics: () => fakeAnalytics,
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'web resident runner is debuggable',
|
|
() {
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
|
|
|
|
expect(residentWebRunner.debuggingEnabled, true);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Exits when initial compile fails',
|
|
() async {
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
|
|
setupMocks();
|
|
webDevFS.report = UpdateFSReport();
|
|
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
|
|
expect(await residentWebRunner.run(), 1);
|
|
expect(fakeAnalytics.sentEvents, isEmpty);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
Analytics: () => fakeAnalytics,
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Faithfully displays stdout messages with leading/trailing spaces',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, logger: logger);
|
|
fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
...kAttachLogExpectations,
|
|
FakeVmServiceStreamResponse(
|
|
streamId: 'Stdout',
|
|
event: vm_service.Event(
|
|
timestamp: 0,
|
|
kind: vm_service.EventStreams.kStdout,
|
|
bytes: base64.encode(
|
|
utf8.encode(' This is a message with 4 leading and trailing spaces '),
|
|
),
|
|
),
|
|
),
|
|
...kAttachIsolateExpectations,
|
|
],
|
|
);
|
|
setupMocks();
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
|
|
expect(
|
|
logger.statusText,
|
|
contains(' This is a message with 4 leading and trailing spaces '),
|
|
);
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Fails on compilation errors in hot restart',
|
|
() async {
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
|
|
setupMocks();
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
webDevFS.report = UpdateFSReport();
|
|
|
|
final OperationResult result = await residentWebRunner.restart(fullRestart: true);
|
|
|
|
expect(result.code, 1);
|
|
expect(result.message, contains('Failed to recompile application.'));
|
|
expect(fakeAnalytics.sentEvents, isEmpty);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
Analytics: () => fakeAnalytics,
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
// TODO(nshahan): Delete this test case when hot reload can no longer be disabled.
|
|
testUsingContext(
|
|
'Fails non-fatally on vmservice response error for hot restart (legacy default case)',
|
|
() async {
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
|
|
fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
...kAttachExpectations,
|
|
const FakeVmServiceRequest(
|
|
method: kHotRestartServiceName,
|
|
jsonResponse: <String, Object>{'type': 'Failed'},
|
|
),
|
|
],
|
|
);
|
|
setupMocks();
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
|
|
// Historically the .restart() would perform a hot restart even without
|
|
// passing fullRestart: true.
|
|
final OperationResult result = await residentWebRunner.restart();
|
|
|
|
expect(result.code, 0);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
// TODO(nshahan): Delete this test case when hot reload can no longer be disabled.
|
|
testUsingContext(
|
|
'Fails fatally on Vm Service error response (legacy default case)',
|
|
() async {
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
|
|
fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
...kAttachExpectations,
|
|
FakeVmServiceRequest(
|
|
method: kHotRestartServiceName,
|
|
// Failed response,
|
|
error: FakeRPCError(code: vm_service.RPCErrorKind.kInternalError.code),
|
|
),
|
|
],
|
|
);
|
|
setupMocks();
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
final OperationResult result = await residentWebRunner.restart();
|
|
|
|
expect(result.code, 1);
|
|
expect(result.message, contains(vm_service.RPCErrorKind.kInternalError.code.toString()));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
for (final bool webEnableHotReload in <bool>[true, false]) {
|
|
testUsingContext(
|
|
'Fails non-fatally on vmservice response error for hot restart with webEnableHotReload: $webEnableHotReload',
|
|
() async {
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(
|
|
flutterDevice,
|
|
debuggingOptions: DebuggingOptions.enabled(
|
|
BuildInfo(
|
|
BuildMode.debug,
|
|
null,
|
|
trackWidgetCreation: true,
|
|
treeShakeIcons: false,
|
|
packageConfigPath: '.dart_tool/package_config.json',
|
|
webEnableHotReload: webEnableHotReload,
|
|
extraFrontEndOptions: webEnableHotReload ? kDdcLibraryBundleFlags : <String>[],
|
|
),
|
|
),
|
|
);
|
|
fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
...kAttachExpectations,
|
|
const FakeVmServiceRequest(
|
|
method: kHotRestartServiceName,
|
|
jsonResponse: <String, Object>{'type': 'Failed'},
|
|
),
|
|
],
|
|
);
|
|
setupMocks();
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
|
|
final OperationResult result = await residentWebRunner.restart(fullRestart: true);
|
|
|
|
expect(result.code, 0);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Fails fatally on Vm Service error response with webEnableHotReload: $webEnableHotReload',
|
|
() async {
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(
|
|
flutterDevice,
|
|
debuggingOptions: DebuggingOptions.enabled(
|
|
BuildInfo(
|
|
BuildMode.debug,
|
|
null,
|
|
trackWidgetCreation: true,
|
|
treeShakeIcons: false,
|
|
packageConfigPath: '.dart_tool/package_config.json',
|
|
webEnableHotReload: webEnableHotReload,
|
|
extraFrontEndOptions: webEnableHotReload ? kDdcLibraryBundleFlags : <String>[],
|
|
),
|
|
),
|
|
);
|
|
fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
...kAttachExpectations,
|
|
FakeVmServiceRequest(
|
|
method: kHotRestartServiceName,
|
|
// Failed response,
|
|
error: FakeRPCError(code: vm_service.RPCErrorKind.kInternalError.code),
|
|
),
|
|
],
|
|
);
|
|
setupMocks();
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
final OperationResult result = await residentWebRunner.restart(fullRestart: true);
|
|
|
|
expect(result.code, 1);
|
|
expect(result.message, contains(vm_service.RPCErrorKind.kInternalError.code.toString()));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
}
|
|
testUsingContext(
|
|
'printHelp without details shows only hot restart help message',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, logger: logger);
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
|
|
residentWebRunner.printHelp(details: false);
|
|
|
|
expect(logger.statusText, contains('Hot restart'));
|
|
expect(logger.statusText.contains('Hot reload'), false);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'printHelp without details shows hot restart and hot reload help message '
|
|
'if using DDC library bundle format',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(
|
|
flutterDevice,
|
|
logger: logger,
|
|
debuggingOptions: DebuggingOptions.enabled(
|
|
const BuildInfo(
|
|
BuildMode.debug,
|
|
null,
|
|
trackWidgetCreation: true,
|
|
treeShakeIcons: false,
|
|
packageConfigPath: '.dart_tool/package_config.json',
|
|
// TODO(nshahan): Remove when hot reload can no longer be disabled.
|
|
webEnableHotReload: true,
|
|
extraFrontEndOptions: kDdcLibraryBundleFlags,
|
|
),
|
|
),
|
|
);
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
|
|
residentWebRunner.printHelp(details: false);
|
|
|
|
expect(logger.statusText, contains('Hot restart'));
|
|
expect(logger.statusText, contains('Hot reload'));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'cleanup of resources is safe to call multiple times',
|
|
() async {
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
|
|
mockDevice.dds = DartDevelopmentService(logger: test_fakes.FakeLogger());
|
|
fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[...kAttachExpectations],
|
|
);
|
|
setupMocks();
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
|
|
await residentWebRunner.exit();
|
|
await residentWebRunner.exit();
|
|
|
|
expect(debugConnection.didClose, false);
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'cleans up Chrome if tab is closed',
|
|
() async {
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
|
|
fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[...kAttachExpectations],
|
|
);
|
|
setupMocks();
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
final Future<int?> result = residentWebRunner.run(
|
|
connectionInfoCompleter: connectionInfoCompleter,
|
|
);
|
|
await connectionInfoCompleter.future;
|
|
debugConnection.completer.complete();
|
|
|
|
await result;
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Prints target and device name on run',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, logger: logger);
|
|
fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[...kAttachExpectations],
|
|
);
|
|
setupMocks();
|
|
mockDevice.name = 'Chromez';
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(residentWebRunner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
|
|
expect(
|
|
logger.statusText,
|
|
contains(
|
|
'Launching ${fileSystem.path.join('lib', 'main.dart')} on '
|
|
'Chromez in debug mode',
|
|
),
|
|
);
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Sends launched app.webLaunchUrl event for Chrome device',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[...kAttachLogExpectations, ...kAttachIsolateExpectations],
|
|
);
|
|
setupMocks();
|
|
final FakeChromeConnection chromeConnection = FakeChromeConnection();
|
|
final TestChromiumLauncher chromiumLauncher = TestChromiumLauncher();
|
|
final FakeProcess process = FakeProcess();
|
|
final Chromium chrome = Chromium(
|
|
1,
|
|
chromeConnection,
|
|
chromiumLauncher: chromiumLauncher,
|
|
process: process,
|
|
logger: logger,
|
|
);
|
|
chromiumLauncher.setInstance(chrome);
|
|
|
|
flutterDevice.device = GoogleChromeDevice(
|
|
fileSystem: fileSystem,
|
|
chromiumLauncher: chromiumLauncher,
|
|
logger: logger,
|
|
platform: FakePlatform(),
|
|
processManager: FakeProcessManager.any(),
|
|
);
|
|
webDevFS.baseUri = Uri.parse('http://localhost:8765/app/');
|
|
|
|
final FakeChromeTab chromeTab = FakeChromeTab('index.html');
|
|
chromeConnection.tabs.add(chromeTab);
|
|
|
|
final ResidentWebRunner runner = ResidentWebRunner(
|
|
flutterDevice,
|
|
flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
|
|
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
|
|
fileSystem: fileSystem,
|
|
logger: logger,
|
|
terminal: Terminal.test(),
|
|
platform: FakePlatform(),
|
|
outputPreferences: OutputPreferences.test(),
|
|
analytics: globals.analytics,
|
|
systemClock: globals.systemClock,
|
|
devtoolsHandler: createNoOpHandler,
|
|
);
|
|
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(runner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
|
|
// Ensure we got the URL and that it was already launched.
|
|
expect(
|
|
logger.eventText,
|
|
contains(
|
|
json.encode(<String, Object>{
|
|
'name': 'app.webLaunchUrl',
|
|
'args': <String, Object>{'url': 'http://localhost:8765/app/', 'launched': true},
|
|
}),
|
|
),
|
|
);
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Sends unlaunched app.webLaunchUrl event for Web Server device',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
|
|
setupMocks();
|
|
flutterDevice.device = WebServerDevice(logger: logger);
|
|
webDevFS.baseUri = Uri.parse('http://localhost:8765/app/');
|
|
|
|
final ResidentWebRunner runner = ResidentWebRunner(
|
|
flutterDevice,
|
|
flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
|
|
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
|
|
fileSystem: fileSystem,
|
|
logger: logger,
|
|
terminal: Terminal.test(),
|
|
platform: FakePlatform(),
|
|
outputPreferences: OutputPreferences.test(),
|
|
analytics: globals.analytics,
|
|
systemClock: globals.systemClock,
|
|
devtoolsHandler: createNoOpHandler,
|
|
);
|
|
|
|
final Completer<DebugConnectionInfo> connectionInfoCompleter =
|
|
Completer<DebugConnectionInfo>();
|
|
unawaited(runner.run(connectionInfoCompleter: connectionInfoCompleter));
|
|
await connectionInfoCompleter.future;
|
|
|
|
// Ensure we got the URL and that it was not already launched.
|
|
expect(
|
|
logger.eventText,
|
|
contains(
|
|
json.encode(<String, Object>{
|
|
'name': 'app.webLaunchUrl',
|
|
'args': <String, Object>{'url': 'http://localhost:8765/app/', 'launched': false},
|
|
}),
|
|
),
|
|
);
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'ResidentWebRunner generates files when l10n.yaml exists',
|
|
() async {
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
|
|
setupMocks();
|
|
final ResidentRunner residentWebRunner = ResidentWebRunner(
|
|
flutterDevice,
|
|
flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
|
|
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
|
|
stayResident: false,
|
|
fileSystem: fileSystem,
|
|
logger: BufferLogger.test(),
|
|
terminal: Terminal.test(),
|
|
platform: FakePlatform(),
|
|
outputPreferences: OutputPreferences.test(),
|
|
analytics: globals.analytics,
|
|
systemClock: globals.systemClock,
|
|
devtoolsHandler: createNoOpHandler,
|
|
);
|
|
|
|
// Create necessary files.
|
|
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
|
|
globals.fs.file(globals.fs.path.join('lib', 'l10n', 'app_en.arb'))
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('''
|
|
{
|
|
"helloWorld": "Hello, World!",
|
|
"@helloWorld": {
|
|
"description": "Sample description"
|
|
}
|
|
}''');
|
|
globals.fs.file('l10n.yaml').createSync();
|
|
globals.fs.file('pubspec.yaml').writeAsStringSync('''
|
|
name: my_app
|
|
flutter:
|
|
generate: true
|
|
''');
|
|
writePackageConfigFiles(
|
|
directory: globals.fs.currentDirectory,
|
|
mainLibName: 'my_app',
|
|
packages: <String, String>{'path_provider_linux': '../../path_provider_linux'},
|
|
);
|
|
expect(await residentWebRunner.run(), 0);
|
|
final File generatedLocalizationsFile = globals.fs
|
|
.directory('lib')
|
|
.childDirectory('l10n')
|
|
.childFile('app_localizations.dart');
|
|
expect(generatedLocalizationsFile.existsSync(), isTrue);
|
|
// Completing this future ensures that the daemon can exit correctly.
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
// While this file should be ignored on web, generating it here will cause a
|
|
// perf regression in hot restart.
|
|
testUsingContext(
|
|
'Does not generate dart_plugin_registrant.dart',
|
|
() async {
|
|
// Create necessary files for [DartPluginRegistrantTarget]
|
|
writePackageConfigFiles(
|
|
directory: globals.fs.currentDirectory,
|
|
mainLibName: 'my_app',
|
|
packages: <String, String>{'path_provider_linux': '../../path_provider_linux'},
|
|
);
|
|
|
|
// Start with a dart_plugin_registrant.dart file.
|
|
globals.fs
|
|
.directory('.dart_tool')
|
|
.childDirectory('flutter_build')
|
|
.childFile('dart_plugin_registrant.dart')
|
|
.createSync(recursive: true);
|
|
|
|
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
|
|
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
|
|
await residentWebRunner.runSourceGenerators();
|
|
|
|
// dart_plugin_registrant.dart should be untouched, indicating that its
|
|
// generation didn't run. If it had run, the file would have been removed as
|
|
// there are no plugins in the project.
|
|
expect(project.dartPluginRegistrant.existsSync(), true);
|
|
expect(project.dartPluginRegistrant.readAsStringSync(), '');
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Successfully turns WebSocketException into ToolExit',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, logger: logger);
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
|
|
setupMocks();
|
|
webDevFS.exception = const WebSocketException();
|
|
|
|
await expectLater(residentWebRunner.run, throwsToolExit());
|
|
expect(logger.errorText, contains('WebSocketException'));
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Turns HttpException from ChromeTab::connect into ToolExit',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, logger: logger);
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
|
|
setupMocks();
|
|
final FakeChromeConnection chromeConnection = FakeChromeConnection();
|
|
final TestChromiumLauncher chromiumLauncher = TestChromiumLauncher();
|
|
final FakeProcess process = FakeProcess();
|
|
final Chromium chrome = Chromium(
|
|
1,
|
|
chromeConnection,
|
|
chromiumLauncher: chromiumLauncher,
|
|
process: process,
|
|
logger: logger,
|
|
);
|
|
chromiumLauncher.setInstance(chrome);
|
|
|
|
flutterDevice.device = GoogleChromeDevice(
|
|
fileSystem: fileSystem,
|
|
chromiumLauncher: chromiumLauncher,
|
|
logger: logger,
|
|
platform: FakePlatform(),
|
|
processManager: FakeProcessManager.any(),
|
|
);
|
|
webDevFS.baseUri = Uri.parse('http://localhost:8765/app/');
|
|
|
|
final FakeChromeTab chromeTab = FakeChromeTab(
|
|
'index.html',
|
|
connectException: HttpException(
|
|
'Connection closed before full header was received',
|
|
uri: Uri(path: 'http://localhost:50094/devtools/page/3036A94908353E86E183B6A40F54104B'),
|
|
),
|
|
);
|
|
chromeConnection.tabs.add(chromeTab);
|
|
|
|
await expectLater(
|
|
residentWebRunner.run,
|
|
throwsToolExit(
|
|
message: 'Failed to establish connection with the application instance in Chrome.',
|
|
),
|
|
);
|
|
expect(logger.errorText, contains('HttpException'));
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, isFalse);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Successfully turns AppConnectionException into ToolExit',
|
|
() async {
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
|
|
setupMocks();
|
|
webDevFS.exception = AppConnectionException('');
|
|
|
|
await expectLater(residentWebRunner.run, throwsToolExit());
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Successfully turns ChromeDebugError into ToolExit',
|
|
() async {
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
|
|
setupMocks();
|
|
|
|
webDevFS.exception = ChromeDebugException(<String, Object?>{'text': 'error'});
|
|
|
|
await expectLater(residentWebRunner.run, throwsToolExit());
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Rethrows unknown Exception type from dwds',
|
|
() async {
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
|
|
setupMocks();
|
|
webDevFS.exception = Exception();
|
|
|
|
await expectLater(residentWebRunner.run, throwsException);
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'Rethrows unknown Error type from dwds tooling',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, logger: logger);
|
|
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
|
|
setupMocks();
|
|
webDevFS.exception = StateError('');
|
|
|
|
await expectLater(residentWebRunner.run, throwsStateError);
|
|
expect(fakeVmServiceHost.hasRemainingExpectations, false);
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
Pub: ThrowingPub.new,
|
|
},
|
|
);
|
|
|
|
testUsingContext(
|
|
'throws when port is an integer outside the valid TCP range',
|
|
() async {
|
|
final BufferLogger logger = BufferLogger.test();
|
|
|
|
DebuggingOptions debuggingOptions = DebuggingOptions.enabled(BuildInfo.debug, port: '65536');
|
|
ResidentRunner residentWebRunner = setUpResidentRunner(
|
|
flutterDevice,
|
|
logger: logger,
|
|
debuggingOptions: debuggingOptions,
|
|
);
|
|
await expectToolExitLater(residentWebRunner.run(), matches('Invalid port: 65536.*'));
|
|
|
|
debuggingOptions = DebuggingOptions.enabled(BuildInfo.debug, port: '-1');
|
|
residentWebRunner = setUpResidentRunner(
|
|
flutterDevice,
|
|
logger: logger,
|
|
debuggingOptions: debuggingOptions,
|
|
);
|
|
await expectToolExitLater(residentWebRunner.run(), matches('Invalid port: -1.*'));
|
|
},
|
|
overrides: <Type, Generator>{
|
|
FileSystem: () => fileSystem,
|
|
ProcessManager: () => processManager,
|
|
},
|
|
);
|
|
}
|
|
|
|
ResidentRunner setUpResidentRunner(
|
|
FlutterDevice flutterDevice, {
|
|
Logger? logger,
|
|
SystemClock? systemClock,
|
|
DebuggingOptions? debuggingOptions,
|
|
}) {
|
|
return ResidentWebRunner(
|
|
flutterDevice,
|
|
flutterProject: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory),
|
|
debuggingOptions: debuggingOptions ?? DebuggingOptions.enabled(BuildInfo.debug),
|
|
analytics: globals.analytics,
|
|
systemClock: systemClock ?? SystemClock.fixed(DateTime.now()),
|
|
fileSystem: globals.fs,
|
|
logger: logger ?? BufferLogger.test(),
|
|
terminal: Terminal.test(),
|
|
platform: FakePlatform(),
|
|
outputPreferences: OutputPreferences.test(),
|
|
devtoolsHandler: createNoOpHandler,
|
|
);
|
|
}
|
|
|
|
class FakeWebServerDevice extends FakeDevice implements WebServerDevice {}
|
|
|
|
class FakeDevice extends Fake implements Device {
|
|
@override
|
|
String name = 'FakeDevice';
|
|
|
|
@override
|
|
String get displayName => name;
|
|
|
|
int count = 0;
|
|
|
|
bool isRunning = false;
|
|
|
|
@override
|
|
Future<String> get sdkNameAndVersion async => 'SDK Name and Version';
|
|
|
|
@override
|
|
late DartDevelopmentService dds;
|
|
|
|
@override
|
|
bool get supportsHotRestart => true;
|
|
|
|
@override
|
|
Future<LaunchResult> startApp(
|
|
ApplicationPackage? package, {
|
|
String? mainPath,
|
|
String? route,
|
|
DebuggingOptions? debuggingOptions,
|
|
Map<String, dynamic>? platformArgs,
|
|
bool prebuiltApplication = false,
|
|
bool ipv6 = false,
|
|
String? userIdentifier,
|
|
}) async {
|
|
isRunning = true;
|
|
return LaunchResult.succeeded();
|
|
}
|
|
|
|
@override
|
|
Future<bool> stopApp(ApplicationPackage? app, {String? userIdentifier}) async {
|
|
if (count > 0) {
|
|
throw StateError('stopApp called more than once.');
|
|
}
|
|
count += 1;
|
|
isRunning = false;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
class FakeDebugConnection extends Fake implements DebugConnection {
|
|
late FakeVmServiceHost Function() fakeVmServiceHost;
|
|
|
|
@override
|
|
vm_service.VmService get vmService => fakeVmServiceHost.call().vmService.service;
|
|
|
|
@override
|
|
late String uri;
|
|
|
|
final Completer<void> completer = Completer<void>();
|
|
bool didClose = false;
|
|
|
|
@override
|
|
Future<void> get onDone => completer.future;
|
|
|
|
@override
|
|
Future<void> close() async {
|
|
didClose = true;
|
|
}
|
|
}
|
|
|
|
class FakeAppConnection extends Fake implements AppConnection {
|
|
bool ranMain = false;
|
|
|
|
@override
|
|
void runMain() {
|
|
ranMain = true;
|
|
}
|
|
}
|
|
|
|
class FakeChromeDevice extends Fake implements ChromiumDevice {}
|
|
|
|
class FakeWipDebugger extends Fake implements WipDebugger {}
|
|
|
|
class FakeResidentCompiler extends Fake implements ResidentCompiler {
|
|
@override
|
|
Future<CompilerOutput> recompile(
|
|
Uri mainUri,
|
|
List<Uri>? invalidatedFiles, {
|
|
required String outputPath,
|
|
required PackageConfig packageConfig,
|
|
required FileSystem fs,
|
|
String? projectRootPath,
|
|
bool suppressErrors = false,
|
|
bool checkDartPluginRegistry = false,
|
|
File? dartPluginRegistrant,
|
|
Uri? nativeAssetsYaml,
|
|
bool recompileRestart = false,
|
|
}) async {
|
|
return const CompilerOutput('foo.dill', 0, <Uri>[]);
|
|
}
|
|
|
|
@override
|
|
void accept() {}
|
|
|
|
@override
|
|
void reset() {}
|
|
|
|
@override
|
|
Future<CompilerOutput> reject() async {
|
|
return const CompilerOutput('foo.dill', 0, <Uri>[]);
|
|
}
|
|
|
|
@override
|
|
void addFileSystemRoot(String root) {}
|
|
}
|
|
|
|
class FakeWebDevFS extends Fake implements WebDevFS {
|
|
Object? exception;
|
|
ConnectionResult? result;
|
|
late UpdateFSReport report;
|
|
|
|
Uri? mainUri;
|
|
|
|
@override
|
|
List<Uri> sources = <Uri>[];
|
|
|
|
@override
|
|
Uri baseUri = Uri.parse('http://localhost:12345');
|
|
|
|
@override
|
|
DateTime? lastCompiled = DateTime.now();
|
|
|
|
@override
|
|
PackageConfig? lastPackageConfig = PackageConfig.empty;
|
|
|
|
@override
|
|
Future<Uri> create() async {
|
|
return baseUri;
|
|
}
|
|
|
|
@override
|
|
Future<UpdateFSReport> update({
|
|
required Uri mainUri,
|
|
required ResidentCompiler generator,
|
|
required bool trackWidgetCreation,
|
|
required String pathToReload,
|
|
required List<Uri> invalidatedFiles,
|
|
required PackageConfig packageConfig,
|
|
required String dillOutputPath,
|
|
required DevelopmentShaderCompiler shaderCompiler,
|
|
DevFSWriter? devFSWriter,
|
|
String? target,
|
|
AssetBundle? bundle,
|
|
bool bundleFirstUpload = false,
|
|
bool fullRestart = false,
|
|
bool resetCompiler = false,
|
|
String? projectRootPath,
|
|
File? dartPluginRegistrant,
|
|
}) async {
|
|
this.mainUri = mainUri;
|
|
return report;
|
|
}
|
|
|
|
@override
|
|
Future<ConnectionResult?> connect(
|
|
bool useDebugExtension, {
|
|
VmServiceFactory vmServiceFactory = createVmServiceDelegate,
|
|
}) async {
|
|
if (exception != null) {
|
|
assert(exception is Exception || exception is Error);
|
|
// ignore: only_throw_errors, exception is either Error or Exception here.
|
|
throw exception!;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
class FakeChromeConnection extends Fake implements ChromeConnection {
|
|
final List<ChromeTab> tabs = <ChromeTab>[];
|
|
|
|
@override
|
|
Future<ChromeTab> getTab(bool Function(ChromeTab tab) accept, {Duration? retryFor}) async {
|
|
return tabs.firstWhere(accept);
|
|
}
|
|
|
|
@override
|
|
Future<List<ChromeTab>> getTabs({Duration? retryFor}) async {
|
|
return tabs;
|
|
}
|
|
}
|
|
|
|
class FakeChromeTab extends Fake implements ChromeTab {
|
|
FakeChromeTab(this.url, {Exception? connectException}) : _connectException = connectException;
|
|
|
|
@override
|
|
final String url;
|
|
|
|
final Exception? _connectException;
|
|
final FakeWipConnection connection = FakeWipConnection();
|
|
|
|
@override
|
|
Future<WipConnection> connect({Function? onError}) async {
|
|
if (_connectException != null) {
|
|
throw _connectException;
|
|
}
|
|
return connection;
|
|
}
|
|
}
|
|
|
|
class FakeWipConnection extends Fake implements WipConnection {
|
|
@override
|
|
final WipDebugger debugger = FakeWipDebugger();
|
|
|
|
@override
|
|
Future<WipResponse> sendCommand(String method, [Map<String, dynamic>? params]) async {
|
|
return WipResponse(<String, dynamic>{'id': 0, 'result': <String, dynamic>{}});
|
|
}
|
|
}
|
|
|
|
/// A test implementation of the [ChromiumLauncher] that launches a fixed instance.
|
|
class TestChromiumLauncher implements ChromiumLauncher {
|
|
TestChromiumLauncher();
|
|
|
|
bool _hasInstance = false;
|
|
void setInstance(Chromium chromium) {
|
|
_hasInstance = true;
|
|
currentCompleter.complete(chromium);
|
|
}
|
|
|
|
@override
|
|
Completer<Chromium> currentCompleter = Completer<Chromium>();
|
|
|
|
@override
|
|
bool canFindExecutable() {
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Future<Chromium> get connectedInstance => currentCompleter.future;
|
|
|
|
@override
|
|
String findExecutable() {
|
|
return 'chrome';
|
|
}
|
|
|
|
@override
|
|
bool get hasChromeInstance => _hasInstance;
|
|
|
|
@override
|
|
Future<Chromium> launch(
|
|
String url, {
|
|
bool headless = false,
|
|
int? debugPort,
|
|
bool skipCheck = false,
|
|
Directory? cacheDir,
|
|
List<String> webBrowserFlags = const <String>[],
|
|
}) async {
|
|
return currentCompleter.future;
|
|
}
|
|
|
|
@override
|
|
Future<Chromium> connect(Chromium chrome, bool skipCheck) {
|
|
return currentCompleter.future;
|
|
}
|
|
}
|
|
|
|
class FakeFlutterDevice extends Fake implements FlutterDevice {
|
|
Uri? testUri;
|
|
UpdateFSReport report = UpdateFSReport(success: true, invalidatedSourcesCount: 1);
|
|
Exception? reportError;
|
|
|
|
@override
|
|
ResidentCompiler? generator;
|
|
|
|
@override
|
|
Stream<Uri?> get vmServiceUris => Stream<Uri?>.value(testUri);
|
|
|
|
@override
|
|
DevelopmentShaderCompiler get developmentShaderCompiler => const FakeShaderCompiler();
|
|
|
|
@override
|
|
FlutterVmService? vmService;
|
|
|
|
DevFS? _devFS;
|
|
|
|
@override
|
|
DevFS? get devFS => _devFS;
|
|
|
|
@override
|
|
set devFS(DevFS? value) {}
|
|
|
|
@override
|
|
Device? device;
|
|
|
|
@override
|
|
Future<void> stopEchoingDeviceLog() async {}
|
|
|
|
@override
|
|
Future<Uri?> setupDevFS(String fsName, Directory rootDirectory) async {
|
|
return testUri;
|
|
}
|
|
|
|
@override
|
|
Future<void> exitApps({Duration timeoutDelay = const Duration(seconds: 10)}) async {}
|
|
|
|
@override
|
|
Future<void> connect({
|
|
ReloadSources? reloadSources,
|
|
Restart? restart,
|
|
CompileExpression? compileExpression,
|
|
FlutterProject? flutterProject,
|
|
PrintStructuredErrorLogMethod? printStructuredErrorLogMethod,
|
|
required DebuggingOptions debuggingOptions,
|
|
int? hostVmServicePort,
|
|
bool? ipv6 = false,
|
|
bool enableDevTools = false,
|
|
bool allowExistingDdsInstance = false,
|
|
}) async {}
|
|
|
|
@override
|
|
Future<UpdateFSReport> updateDevFS({
|
|
Uri? mainUri,
|
|
String? target,
|
|
AssetBundle? bundle,
|
|
bool bundleFirstUpload = false,
|
|
bool bundleDirty = false,
|
|
bool fullRestart = false,
|
|
String? projectRootPath,
|
|
String? pathToReload,
|
|
String? dillOutputPath,
|
|
List<Uri>? invalidatedFiles,
|
|
PackageConfig? packageConfig,
|
|
File? dartPluginRegistrant,
|
|
}) async {
|
|
if (reportError != null) {
|
|
throw reportError!;
|
|
}
|
|
return report;
|
|
}
|
|
|
|
@override
|
|
Future<void> updateReloadStatus(bool wasReloadSuccessful) async {}
|
|
}
|
|
|
|
class FakeShaderCompiler implements DevelopmentShaderCompiler {
|
|
const FakeShaderCompiler();
|
|
|
|
@override
|
|
void configureCompiler(TargetPlatform? platform) {}
|
|
|
|
@override
|
|
Future<DevFSContent> recompileShader(DevFSContent inputShader) {
|
|
throw UnimplementedError();
|
|
}
|
|
}
|