flutter/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart
stuartmorgan 64fd68ed42
Don't generate plugin registry in ResidentWebRunner (#91281)
* Don't generate plugin registry in ResidentWebRunner

generateDartPluginRegistry was being set to true unconditionally in
ResidentRunner, bypassing the primary check in
DartPluginRegistrantTarget, and the targetPlatform was not set in that
codepath, bypassing the second after the changes in
https://github.com/flutter/flutter/pull/87991. This caused web hot
restarts to be slower due to doing unnecessary work.

This ensures that generateDartPluginRegistry is false in the
ResidentWebRunner to skip that unnecessary step.

Fixes https://github.com/flutter/flutter/issues/91262

* Formatting

Co-authored-by: Zachary Anderson <zanderso@users.noreply.github.com>

Co-authored-by: Zachary Anderson <zanderso@users.noreply.github.com>
2021-10-05 12:27:53 -07:00

1386 lines
47 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.
// @dart = 2.8
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/common.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/time.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/globals_null_migrated.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/reporting/reporting.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:meta/meta.dart';
import 'package:package_config/package_config.dart';
import 'package:package_config/package_config_types.dart';
import 'package:test/fake.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_vm_services.dart';
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': 'Isolate'
}
),
FakeVmServiceRequest(
method: 'registerService',
args: <String, Object>{
'service': 'reloadSources',
'alias': 'Flutter Tools',
}
),
FakeVmServiceRequest(
method: 'registerService',
args: <String, Object>{
'service': 'flutterVersion',
'alias': 'Flutter Tools',
}
),
FakeVmServiceRequest(
method: 'registerService',
args: <String, Object>{
'service': 'flutterMemoryInfo',
'alias': 'Flutter Tools',
}
),
FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{
'streamId': 'Extension',
},
),
];
const List<VmServiceExpectation> kAttachExpectations = <VmServiceExpectation>[
...kAttachLogExpectations,
...kAttachIsolateExpectations,
];
void main() {
FakeDebugConnection debugConnection;
FakeChromeDevice chromeDevice;
FakeAppConnection appConnection;
FakeFlutterDevice flutterDevice;
FakeWebDevFS webDevFS;
FakeResidentCompiler residentCompiler;
FakeChromeConnection chromeConnection;
FakeChromeTab chromeTab;
FakeWebServerDevice webServerDevice;
FakeDevice mockDevice;
FakeVmServiceHost fakeVmServiceHost;
FileSystem fileSystem;
ProcessManager processManager;
TestUsage testUsage;
setUp(() {
testUsage = TestUsage();
fileSystem = MemoryFileSystem.test();
processManager = FakeProcessManager.any();
debugConnection = FakeDebugConnection();
mockDevice = FakeDevice();
appConnection = FakeAppConnection();
webDevFS = FakeWebDevFS();
residentCompiler = FakeResidentCompiler();
chromeConnection = FakeChromeConnection();
chromeTab = FakeChromeTab('index.html');
webServerDevice = FakeWebServerDevice();
flutterDevice = FakeFlutterDevice()
.._devFS = webDevFS
..device = mockDevice
..generator = residentCompiler;
fileSystem.file('.packages').writeAsStringSync('\n');
});
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, syncedBytes: 0);
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),
ipv6: true,
stayResident: true,
urlTunneller: null,
fileSystem: fileSystem,
logger: BufferLogger.test(),
usage: globals.flutterUsage,
systemClock: globals.systemClock,
);
expect(profileResidentWebRunner.debuggingEnabled, false);
flutterDevice.device = FakeChromeDevice();
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),
ipv6: true,
stayResident: true,
urlTunneller: null,
fileSystem: fileSystem,
logger: BufferLogger.test(),
usage: globals.flutterUsage,
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),
ipv6: true,
stayResident: true,
urlTunneller: null,
fileSystem: fileSystem,
logger: BufferLogger.test(),
usage: globals.flutterUsage,
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),
ipv6: true,
stayResident: true,
urlTunneller: null,
fileSystem: fileSystem,
logger: BufferLogger.test(),
usage: globals.flutterUsage,
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,
});
testUsingContext('WebRunner copies compiled app.dill to cache during startup', () 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')).readAsString(), 'ABC');
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
// 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(success: false, syncedBytes: 0);
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,
});
testUsingContext('Can successfully run without an index.html including status warning', () async {
final BufferLogger logger = BufferLogger.test();
fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList());
_setupMocks();
fileSystem.file(fileSystem.path.join('web', 'index.html'))
.deleteSync();
final ResidentWebRunner residentWebRunner = ResidentWebRunner(
flutterDevice,
flutterProject: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
ipv6: true,
stayResident: false,
urlTunneller: null,
fileSystem: fileSystem,
logger: logger,
usage: globals.flutterUsage,
systemClock: globals.systemClock,
);
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,
});
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),
ipv6: true,
stayResident: false,
urlTunneller: null,
fileSystem: fileSystem,
logger: BufferLogger.test(),
usage: globals.flutterUsage,
systemClock: globals.systemClock,
);
expect(await residentWebRunner.run(), 0);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
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,
});
testUsingContext('Listens to extension events with structured errors', () async {
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice, logger: testLogger);
final Map<String, String> extensionData = <String, String>{
'test': 'data',
'renderedErrorText': 'error text',
};
final Map<String, String> emptyExtensionData = <String, String>{
'test': 'data',
'renderedErrorText': '',
};
final Map<String, String> nonStructuredErrorData = <String, String>{
'other': 'other stuff',
};
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachExpectations,
FakeVmServiceStreamResponse(
streamId: 'Extension',
event: vm_service.Event(
timestamp: 0,
extensionKind: 'Flutter.Error',
extensionData: vm_service.ExtensionData.parse(extensionData),
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(emptyExtensionData),
kind: vm_service.EventStreams.kExtension,
),
),
// This is not Flutter.Error kind data, so it should not be logged.
FakeVmServiceStreamResponse(
streamId: 'Extension',
event: vm_service.Event(
timestamp: 0,
extensionKind: 'Other',
extensionData: vm_service.ExtensionData.parse(nonStructuredErrorData),
kind: vm_service.EventStreams.kExtension,
),
),
]);
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
await null;
expect(testLogger.statusText, contains('\nerror text'));
expect(testLogger.statusText, isNot(contains('other stuff')));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
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),
ipv6: true,
stayResident: true,
urlTunneller: null,
fileSystem: fileSystem,
logger: BufferLogger.test(),
usage: globals.flutterUsage,
systemClock: globals.systemClock,
);
fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.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,
});
testUsingContext('Can hot reload after attaching', () async {
final BufferLogger logger = BufferLogger.test();
final ResidentRunner residentWebRunner = setUpResidentRunner(
flutterDevice,
logger: logger,
systemClock: SystemClock.fixed(DateTime(2001, 1, 1)),
);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachExpectations,
const FakeVmServiceRequest(
method: 'hotRestart',
jsonResponse: <String, Object>{
'type': 'Success',
}
),
const FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{
'streamId': 'Isolate',
},
),
]);
_setupMocks();
final TestChromiumLauncher chromiumLauncher = TestChromiumLauncher();
final Chromium chrome = Chromium(1, chromeConnection, chromiumLauncher: chromiumLauncher);
chromiumLauncher.setInstance(chrome);
flutterDevice.device = GoogleChromeDevice(
fileSystem: fileSystem,
chromiumLauncher: chromiumLauncher,
logger: BufferLogger.test(),
platform: FakePlatform(operatingSystem: 'linux'),
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(fullRestart: false);
expect(logger.statusText, contains('Restarted application in'));
expect(result.code, 0);
expect(webDevFS.mainUri.toString(), contains('entrypoint.dart'));
// ensure that analytics are sent.
expect(testUsage.events, <TestUsageEvent>[
TestUsageEvent('hot', 'restart', parameters: CustomDimensions.fromMap(<String, String>{'cd27': 'web-javascript', 'cd28': '', 'cd29': 'false', 'cd30': 'true', 'cd13': '0'})),
]);
expect(testUsage.timings, const <TestTimingEvent>[
TestTimingEvent('hot', 'web-incremental-restart', Duration.zero),
]);
}, overrides: <Type, Generator>{
Usage: () => testUsage,
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('Can hot restart after attaching', () async {
final BufferLogger logger = BufferLogger.test();
final ResidentRunner residentWebRunner = setUpResidentRunner(
flutterDevice,
logger: logger,
systemClock: SystemClock.fixed(DateTime(2001, 1, 1)),
);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachExpectations,
const FakeVmServiceRequest(
method: 'hotRestart',
jsonResponse: <String, Object>{
'type': 'Success',
}
),
]);
_setupMocks();
final TestChromiumLauncher chromiumLauncher = TestChromiumLauncher();
final Chromium chrome = Chromium(1, chromeConnection, chromiumLauncher: chromiumLauncher);
chromiumLauncher.setInstance(chrome);
flutterDevice.device = GoogleChromeDevice(
fileSystem: fileSystem,
chromiumLauncher: chromiumLauncher,
logger: BufferLogger.test(),
platform: FakePlatform(operatingSystem: 'linux'),
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: true);
// 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' as ui;"));
expect(entrypointContents, contains('await ui.webOnlyInitializePlatform();'));
expect(logger.statusText, contains('Restarted application in'));
expect(result.code, 0);
// ensure that analytics are sent.
expect(testUsage.events, <TestUsageEvent>[
TestUsageEvent('hot', 'restart', parameters: CustomDimensions.fromMap(<String, String>{'cd27': 'web-javascript', 'cd28': '', 'cd29': 'false', 'cd30': 'true', 'cd13': '0'})),
]);
expect(testUsage.timings, const <TestTimingEvent>[
TestTimingEvent('hot', 'web-incremental-restart', Duration.zero),
]);
}, overrides: <Type, Generator>{
Usage: () => testUsage,
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
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, 1, 1)),
);
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(testUsage.events, isEmpty);
expect(testUsage.timings, isEmpty);
}, overrides: <Type, Generator>{
Usage: () => testUsage,
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
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(success: false, syncedBytes: 0);
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
expect(await residentWebRunner.run(), 1);
expect(testUsage.events, isEmpty);
expect(testUsage.timings, isEmpty);
}, overrides: <Type, Generator>{
Usage: () => testUsage,
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
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,
});
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(success: false, syncedBytes: 0);
final OperationResult result = await residentWebRunner.restart(fullRestart: true);
expect(result.code, 1);
expect(result.message, contains('Failed to recompile application.'));
expect(testUsage.events, isEmpty);
expect(testUsage.timings, isEmpty);
}, overrides: <Type, Generator>{
Usage: () => testUsage,
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('Fails non-fatally on vmservice response error for hot restart', () async {
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachExpectations,
const FakeVmServiceRequest(
method: 'hotRestart',
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: false);
expect(result.code, 0);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('Fails fatally on Vm Service error response', () async {
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachExpectations,
const FakeVmServiceRequest(
method: 'hotRestart',
// Failed response,
errorCode: RPCErrorCodes.kInternalError,
),
]);
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
final OperationResult result = await residentWebRunner.restart(fullRestart: false);
expect(result.code, 1);
expect(result.message,
contains(RPCErrorCodes.kInternalError.toString()));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('printHelp without details shows 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('To hot restart changes'));
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('cleanup of resources is safe to call multiple times', () async {
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
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,
});
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,
});
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,
});
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 Chromium chrome = Chromium(1, chromeConnection, chromiumLauncher: chromiumLauncher);
chromiumLauncher.setInstance(chrome);
flutterDevice.device = GoogleChromeDevice(
fileSystem: fileSystem,
chromiumLauncher: chromiumLauncher,
logger: logger,
platform: FakePlatform(operatingSystem: 'linux'),
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),
ipv6: true,
stayResident: true,
urlTunneller: null,
fileSystem: fileSystem,
logger: logger,
usage: globals.flutterUsage,
systemClock: globals.systemClock,
);
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,
});
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),
ipv6: true,
stayResident: true,
urlTunneller: null,
fileSystem: fileSystem,
logger: logger,
usage: globals.flutterUsage,
systemClock: globals.systemClock,
);
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,
});
// While this file should be ignored on web, generating it here will cause a
// perf regression in hot restart.
testUsingContext('Does not generate generated_main.dart', () async {
// Create necessary files for [DartPluginRegistrantTarget]
final File packageConfig = globals.fs.directory('.dart_tool')
.childFile('package_config.json');
packageConfig.createSync(recursive: true);
packageConfig.writeAsStringSync('''
{
"configVersion": 2,
"packages": [
{
"name": "path_provider_linux",
"rootUri": "../../../path_provider_linux",
"packageUri": "lib/",
"languageVersion": "2.12"
}
]
}
''');
// Start with a generated_main.dart file.
globals.fs.directory('.dart_tool')
.childDirectory('flutter_build')
.childFile('generated_main.dart')
.createSync(recursive: true);
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
await residentWebRunner.runSourceGenerators();
// generated_main.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,
});
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,
});
testUsingContext('Successfully turns ChromeDebugError into ToolExit', () async {
final ResidentRunner residentWebRunner = setUpResidentRunner(flutterDevice);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
_setupMocks();
webDevFS.exception = ChromeDebugException(<String, dynamic>{});
await expectLater(residentWebRunner.run, throwsToolExit());
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
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,
});
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,
});
}
ResidentRunner setUpResidentRunner(FlutterDevice flutterDevice, {
Logger logger,
SystemClock systemClock,
}) {
return ResidentWebRunner(
flutterDevice,
flutterProject: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory),
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
ipv6: true,
stayResident: true,
urlTunneller: null,
usage: globals.flutterUsage,
systemClock: systemClock ?? SystemClock.fixed(DateTime.now()),
fileSystem: globals.fs,
logger: logger ?? BufferLogger.test(),
devtoolsHandler: createNoOpHandler,
);
}
// Unfortunately Device, despite not being immutable, has an `operator ==`.
// Until we fix that, we have to also ignore related lints here.
// ignore: avoid_implementing_value_types
class FakeWebServerDevice extends FakeDevice implements WebServerDevice { }
// Unfortunately Device, despite not being immutable, has an `operator ==`.
// Until we fix that, we have to also ignore related lints here.
// ignore: avoid_implementing_value_types
class FakeDevice extends Fake implements Device {
@override
String name;
int count = 0;
@override
Future<String> get sdkNameAndVersion async => 'SDK Name and Version';
@override
DartDevelopmentService dds;
@override
Future<LaunchResult> startApp(
covariant ApplicationPackage package, {
String mainPath,
String route,
DebuggingOptions debuggingOptions,
Map<String, dynamic> platformArgs,
bool prebuiltApplication = false,
bool ipv6 = false,
String userIdentifier,
}) async {
return LaunchResult.succeeded();
}
@override
Future<bool> stopApp(
covariant ApplicationPackage app, {
String userIdentifier,
}) async {
if (count > 0) {
throw StateError('stopApp called more than once.');
}
count += 1;
return true;
}
}
class FakeDebugConnection extends Fake implements DebugConnection {
FakeVmServiceHost Function() fakeVmServiceHost;
@override
vm_service.VmService get vmService => fakeVmServiceHost.call().vmService.service;
@override
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;
}
}
// Unfortunately Device, despite not being immutable, has an `operator ==`.
// Until we fix that, we have to also ignore related lints here.
// ignore: avoid_implementing_value_types
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 String projectRootPath,
@required FileSystem fs,
bool suppressErrors = false,
bool checkDartPluginRegistry = 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;
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,
DevFSWriter devFSWriter,
String target,
AssetBundle bundle,
DateTime firstBuildTime,
bool bundleFirstUpload = false,
bool fullRestart = false,
String projectRootPath,
}) async {
this.mainUri = mainUri;
return report;
}
@override
Future<ConnectionResult> connect(bool useDebugExtension) async {
if (exception != null) {
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);
}
}
class FakeChromeTab extends Fake implements ChromeTab {
FakeChromeTab(this.url);
@override
final String url;
final FakeWipConnection connection = FakeWipConnection();
@override
Future<WipConnection> connect() async {
return connection;
}
}
class FakeWipConnection extends Fake implements WipConnection {
@override
final WipDebugger debugger = FakeWipDebugger();
}
/// 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}) async {
return currentCompleter.future;
}
}
class FakeFlutterDevice extends Fake implements FlutterDevice {
Uri testUri;
UpdateFSReport report = UpdateFSReport(
success: true,
syncedBytes: 0,
invalidatedSourcesCount: 1,
);
Object reportError;
@override
ResidentCompiler generator;
@override
Stream<Uri> get observatoryUris => Stream<Uri>.value(testUri);
@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<void> initLogReader() 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,
GetSkSLMethod getSkSLMethod,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
int hostVmServicePort,
int ddsPort,
bool disableServiceAuthCodes = false,
bool enableDds = true,
@required bool allowExistingDdsInstance,
bool ipv6 = false,
}) async { }
@override
Future<UpdateFSReport> updateDevFS({
Uri mainUri,
String target,
AssetBundle bundle,
DateTime firstBuildTime,
bool bundleFirstUpload = false,
bool bundleDirty = false,
bool fullRestart = false,
String projectRootPath,
String pathToReload,
String dillOutputPath,
List<Uri> invalidatedFiles,
PackageConfig packageConfig,
}) async {
if (reportError != null) {
throw reportError;
}
return report;
}
@override
Future<void> updateReloadStatus(bool wasReloadSuccessful) async { }
}