Unverified Commit 07caa0fb authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] Add plumbing for widget cache (#61766)

To support #61407 , the tool needs to check if a single widget reload is feasible, and then conditionally perform a fast reassemble.

To accomplish this, the FlutterDevice class will have a WidgetCache injected. This will eventually contain the logic for parsing the invalidated dart script. Concurrent with the devFS update, the widget cache will be updated/checked if a single widget reload is feasible. If so, an expression evaluation with the target type is performed and the success is communicated through the devFS result. An integration test which demonstrates that this works is already present in https://github.com/flutter/flutter/blob/master/packages/flutter_tools/test/integration.shard/hot_reload_test.dart#L86

Finally, when actually performing the reassemble the tool simply checks if this flag has been set and calls the alternative reassemble method.

Cleanups:

Remove modules, as this is unused now.
parent aa4b4d35
...@@ -851,7 +851,7 @@ class WebDevFS implements DevFS { ...@@ -851,7 +851,7 @@ class WebDevFS implements DevFS {
success: true, success: true,
syncedBytes: codeFile.lengthSync(), syncedBytes: codeFile.lengthSync(),
invalidatedSourcesCount: invalidatedFiles.length, invalidatedSourcesCount: invalidatedFiles.length,
)..invalidatedModules = modules; );
} }
@visibleForTesting @visibleForTesting
......
...@@ -15,6 +15,7 @@ import '../base/io.dart'; ...@@ -15,6 +15,7 @@ import '../base/io.dart';
import '../commands/daemon.dart'; import '../commands/daemon.dart';
import '../compile.dart'; import '../compile.dart';
import '../device.dart'; import '../device.dart';
import '../features.dart';
import '../fuchsia/fuchsia_device.dart'; import '../fuchsia/fuchsia_device.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../ios/devices.dart'; import '../ios/devices.dart';
...@@ -26,6 +27,7 @@ import '../resident_runner.dart'; ...@@ -26,6 +27,7 @@ import '../resident_runner.dart';
import '../run_cold.dart'; import '../run_cold.dart';
import '../run_hot.dart'; import '../run_hot.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import '../widget_cache.dart';
/// A Flutter-command that attaches to applications that have been launched /// A Flutter-command that attaches to applications that have been launched
/// without `flutter run`. /// without `flutter run`.
...@@ -369,6 +371,7 @@ class AttachCommand extends FlutterCommand { ...@@ -369,6 +371,7 @@ class AttachCommand extends FlutterCommand {
targetModel: TargetModel(stringArg('target-model')), targetModel: TargetModel(stringArg('target-model')),
buildInfo: getBuildInfo(), buildInfo: getBuildInfo(),
userIdentifier: userIdentifier, userIdentifier: userIdentifier,
widgetCache: WidgetCache(featureFlags: featureFlags),
); );
flutterDevice.observatoryUris = observatoryUris; flutterDevice.observatoryUris = observatoryUris;
final List<FlutterDevice> flutterDevices = <FlutterDevice>[flutterDevice]; final List<FlutterDevice> flutterDevices = <FlutterDevice>[flutterDevice];
......
...@@ -19,6 +19,7 @@ import '../build_info.dart'; ...@@ -19,6 +19,7 @@ import '../build_info.dart';
import '../convert.dart'; import '../convert.dart';
import '../device.dart'; import '../device.dart';
import '../emulator.dart'; import '../emulator.dart';
import '../features.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../project.dart'; import '../project.dart';
import '../resident_runner.dart'; import '../resident_runner.dart';
...@@ -26,6 +27,7 @@ import '../run_cold.dart'; ...@@ -26,6 +27,7 @@ import '../run_cold.dart';
import '../run_hot.dart'; import '../run_hot.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import '../web/web_runner.dart'; import '../web/web_runner.dart';
import '../widget_cache.dart';
const String protocolVersion = '0.6.0'; const String protocolVersion = '0.6.0';
...@@ -468,6 +470,7 @@ class AppDomain extends Domain { ...@@ -468,6 +470,7 @@ class AppDomain extends Domain {
viewFilter: isolateFilter, viewFilter: isolateFilter,
target: target, target: target,
buildInfo: options.buildInfo, buildInfo: options.buildInfo,
widgetCache: WidgetCache(featureFlags: featureFlags),
); );
ResidentRunner runner; ResidentRunner runner;
......
...@@ -23,6 +23,7 @@ import '../run_hot.dart'; ...@@ -23,6 +23,7 @@ import '../run_hot.dart';
import '../runner/flutter_command.dart'; import '../runner/flutter_command.dart';
import '../tracing.dart'; import '../tracing.dart';
import '../web/web_runner.dart'; import '../web/web_runner.dart';
import '../widget_cache.dart';
import 'daemon.dart'; import 'daemon.dart';
abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopmentArtifacts { abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopmentArtifacts {
...@@ -519,6 +520,7 @@ class RunCommand extends RunCommandBase { ...@@ -519,6 +520,7 @@ class RunCommand extends RunCommandBase {
target: stringArg('target'), target: stringArg('target'),
buildInfo: getBuildInfo(), buildInfo: getBuildInfo(),
userIdentifier: userIdentifier, userIdentifier: userIdentifier,
widgetCache: WidgetCache(featureFlags: featureFlags),
), ),
]; ];
// Only support "web mode" with a single web device due to resident runner // Only support "web mode" with a single web device due to resident runner
......
...@@ -302,34 +302,32 @@ class UpdateFSReport { ...@@ -302,34 +302,32 @@ class UpdateFSReport {
bool success = false, bool success = false,
int invalidatedSourcesCount = 0, int invalidatedSourcesCount = 0,
int syncedBytes = 0, int syncedBytes = 0,
}) { this.fastReassemble,
_success = success; }) : _success = success,
_invalidatedSourcesCount = invalidatedSourcesCount; _invalidatedSourcesCount = invalidatedSourcesCount,
_syncedBytes = syncedBytes; _syncedBytes = syncedBytes;
}
bool get success => _success; bool get success => _success;
int get invalidatedSourcesCount => _invalidatedSourcesCount; int get invalidatedSourcesCount => _invalidatedSourcesCount;
int get syncedBytes => _syncedBytes; int get syncedBytes => _syncedBytes;
/// JavaScript modules produced by the incremental compiler in `dartdevc` bool _success;
/// mode. bool fastReassemble;
/// int _invalidatedSourcesCount;
/// Only used for JavaScript compilation. int _syncedBytes;
List<String> invalidatedModules;
void incorporateResults(UpdateFSReport report) { void incorporateResults(UpdateFSReport report) {
if (!report._success) { if (!report._success) {
_success = false; _success = false;
} }
if (report.fastReassemble != null && fastReassemble != null) {
fastReassemble &= report.fastReassemble;
} else if (report.fastReassemble != null) {
fastReassemble = report.fastReassemble;
}
_invalidatedSourcesCount += report._invalidatedSourcesCount; _invalidatedSourcesCount += report._invalidatedSourcesCount;
_syncedBytes += report._syncedBytes; _syncedBytes += report._syncedBytes;
invalidatedModules ??= report.invalidatedModules;
} }
bool _success;
int _invalidatedSourcesCount;
int _syncedBytes;
} }
class DevFS { class DevFS {
......
...@@ -33,6 +33,7 @@ import 'project.dart'; ...@@ -33,6 +33,7 @@ import 'project.dart';
import 'run_cold.dart'; import 'run_cold.dart';
import 'run_hot.dart'; import 'run_hot.dart';
import 'vmservice.dart'; import 'vmservice.dart';
import 'widget_cache.dart';
class FlutterDevice { class FlutterDevice {
FlutterDevice( FlutterDevice(
...@@ -45,6 +46,7 @@ class FlutterDevice { ...@@ -45,6 +46,7 @@ class FlutterDevice {
TargetPlatform targetPlatform, TargetPlatform targetPlatform,
ResidentCompiler generator, ResidentCompiler generator,
this.userIdentifier, this.userIdentifier,
this.widgetCache,
}) : assert(buildInfo.trackWidgetCreation != null), }) : assert(buildInfo.trackWidgetCreation != null),
generator = generator ?? ResidentCompiler( generator = generator ?? ResidentCompiler(
globals.artifacts.getArtifactPath( globals.artifacts.getArtifactPath(
...@@ -78,6 +80,7 @@ class FlutterDevice { ...@@ -78,6 +80,7 @@ class FlutterDevice {
List<String> experimentalFlags, List<String> experimentalFlags,
ResidentCompiler generator, ResidentCompiler generator,
String userIdentifier, String userIdentifier,
WidgetCache widgetCache,
}) async { }) async {
ResidentCompiler generator; ResidentCompiler generator;
final TargetPlatform targetPlatform = await device.targetPlatform; final TargetPlatform targetPlatform = await device.targetPlatform;
...@@ -167,6 +170,7 @@ class FlutterDevice { ...@@ -167,6 +170,7 @@ class FlutterDevice {
generator: generator, generator: generator,
buildInfo: buildInfo, buildInfo: buildInfo,
userIdentifier: userIdentifier, userIdentifier: userIdentifier,
widgetCache: widgetCache,
); );
} }
...@@ -174,6 +178,7 @@ class FlutterDevice { ...@@ -174,6 +178,7 @@ class FlutterDevice {
final ResidentCompiler generator; final ResidentCompiler generator;
final BuildInfo buildInfo; final BuildInfo buildInfo;
final String userIdentifier; final String userIdentifier;
final WidgetCache widgetCache;
Stream<Uri> observatoryUris; Stream<Uri> observatoryUris;
vm_service.VmService vmService; vm_service.VmService vmService;
DevFS devFS; DevFS devFS;
...@@ -641,6 +646,44 @@ class FlutterDevice { ...@@ -641,6 +646,44 @@ class FlutterDevice {
return 0; return 0;
} }
/// Validates whether this hot reload is a candidate for a fast reassemble.
Future<bool> _attemptFastReassembleCheck(List<Uri> invalidatedFiles, PackageConfig packageConfig) async {
if (invalidatedFiles.length != 1 || widgetCache == null) {
return false;
}
final List<FlutterView> views = await vmService.getFlutterViews();
final String widgetName = await widgetCache?.validateLibrary(invalidatedFiles.single);
if (widgetName == null) {
return false;
}
final String packageUri = packageConfig.toPackageUri(invalidatedFiles.single)?.toString()
?? invalidatedFiles.single.toString();
for (final FlutterView view in views) {
final vm_service.Isolate isolate = await vmService.getIsolateOrNull(view.uiIsolate.id);
final vm_service.LibraryRef targetLibrary = isolate.libraries
.firstWhere(
(vm_service.LibraryRef libraryRef) => libraryRef.uri == packageUri,
orElse: () => null,
);
if (targetLibrary == null) {
return false;
}
try {
// Evaluate an expression to allow type checking for that invalidated widget
// name. For more information, see `debugFastReassembleMethod` in flutter/src/widgets/binding.dart
await vmService.evaluate(
view.uiIsolate.id,
targetLibrary.id,
'((){debugFastReassembleMethod=(Object _fastReassembleParam) => _fastReassembleParam is $widgetName})()',
);
} on Exception catch (err) {
globals.printTrace(err.toString());
return false;
}
}
return true;
}
Future<UpdateFSReport> updateDevFS({ Future<UpdateFSReport> updateDevFS({
Uri mainUri, Uri mainUri,
String target, String target,
...@@ -660,22 +703,34 @@ class FlutterDevice { ...@@ -660,22 +703,34 @@ class FlutterDevice {
timeout: timeoutConfiguration.fastOperation, timeout: timeoutConfiguration.fastOperation,
); );
UpdateFSReport report; UpdateFSReport report;
bool fastReassemble = false;
try { try {
report = await devFS.update( await Future.wait(<Future<void>>[
mainUri: mainUri, devFS.update(
target: target, mainUri: mainUri,
bundle: bundle, target: target,
firstBuildTime: firstBuildTime, bundle: bundle,
bundleFirstUpload: bundleFirstUpload, firstBuildTime: firstBuildTime,
generator: generator, bundleFirstUpload: bundleFirstUpload,
fullRestart: fullRestart, generator: generator,
dillOutputPath: dillOutputPath, fullRestart: fullRestart,
trackWidgetCreation: buildInfo.trackWidgetCreation, dillOutputPath: dillOutputPath,
projectRootPath: projectRootPath, trackWidgetCreation: buildInfo.trackWidgetCreation,
pathToReload: pathToReload, projectRootPath: projectRootPath,
invalidatedFiles: invalidatedFiles, pathToReload: pathToReload,
packageConfig: packageConfig, invalidatedFiles: invalidatedFiles,
); packageConfig: packageConfig,
).then((UpdateFSReport newReport) => report = newReport),
if (!fullRestart)
_attemptFastReassembleCheck(
invalidatedFiles,
packageConfig,
).then((bool newFastReassemble) => fastReassemble = newFastReassemble)
]);
if (fastReassemble) {
globals.logger.printTrace('Attempting fast reassemble.');
}
report.fastReassemble = fastReassemble;
} on DevFSException { } on DevFSException {
devFSStatus.cancel(); devFSStatus.cancel();
return UpdateFSReport(success: false); return UpdateFSReport(success: false);
......
...@@ -154,62 +154,15 @@ class HotRunner extends ResidentRunner { ...@@ -154,62 +154,15 @@ class HotRunner extends ResidentRunner {
@override @override
Future<OperationResult> reloadMethod({ String libraryId, String classId }) async { Future<OperationResult> reloadMethod({ String libraryId, String classId }) async {
final Stopwatch stopwatch = Stopwatch()..start(); final OperationResult result = await restart(pause: false);
final UpdateFSReport results = UpdateFSReport(success: true); if (!result.isOk) {
final List<Uri> invalidated = <Uri>[Uri.parse(libraryId)]; throw vm_service.RPCError(
final PackageConfig packageConfig = await loadPackageConfigWithLogging( 'Unable to reload sources',
globals.fs.file(debuggingOptions.buildInfo.packagesPath), RPCErrorCodes.kInternalError,
logger: globals.logger, '',
);
for (final FlutterDevice device in flutterDevices) {
results.incorporateResults(await device.updateDevFS(
mainUri: globals.fs.file(mainPath).absolute.uri,
target: target,
bundle: assetBundle,
firstBuildTime: firstBuildTime,
bundleFirstUpload: false,
bundleDirty: false,
fullRestart: false,
projectRootPath: projectRootPath,
pathToReload: getReloadPath(fullRestart: false),
invalidatedFiles: invalidated,
packageConfig: packageConfig,
dillOutputPath: dillOutputPath,
));
}
if (!results.success) {
return OperationResult(1, 'Failed to compile');
}
try {
final String entryPath = globals.fs.path.relative(
getReloadPath(fullRestart: false),
from: projectRootPath,
); );
for (final FlutterDevice device in flutterDevices) {
final List<Future<vm_service.ReloadReport>> reportFutures = await device.reloadSources(
entryPath, pause: false,
);
final List<vm_service.ReloadReport> reports = await Future.wait(reportFutures);
final vm_service.ReloadReport firstReport = reports.first;
await device.updateReloadStatus(validateReloadReport(firstReport.json, printErrors: false));
}
} on Exception catch (error) {
return OperationResult(1, error.toString());
}
for (final FlutterDevice device in flutterDevices) {
final List<FlutterView> views = await device.vmService.getFlutterViews();
for (final FlutterView view in views) {
await device.vmService.flutterFastReassemble(
classId,
isolateId: view.uiIsolate.id,
);
}
} }
return result;
globals.printStatus('reloadMethod took ${stopwatch.elapsedMilliseconds}');
globals.flutterUsage.sendTiming('hot', 'ui', stopwatch.elapsed);
return OperationResult.ok;
} }
// Returns the exit code of the flutter tool process, like [run]. // Returns the exit code of the flutter tool process, like [run].
...@@ -942,9 +895,19 @@ class HotRunner extends ResidentRunner { ...@@ -942,9 +895,19 @@ class HotRunner extends ResidentRunner {
} }
} else { } else {
reassembleViews[view] = device.vmService; reassembleViews[view] = device.vmService;
reassembleFutures.add(device.vmService.flutterReassemble( // If the tool identified a change in a single widget, do a fast instead
isolateId: view.uiIsolate.id, // of a full reassemble.
).catchError((dynamic error) { Future<void> reassembleWork;
if (updatedDevFS.fastReassemble == true) {
reassembleWork = device.vmService.flutterFastReassemble(
isolateId: view.uiIsolate.id,
);
} else {
reassembleWork = device.vmService.flutterReassemble(
isolateId: view.uiIsolate.id,
);
}
reassembleFutures.add(reassembleWork.catchError((dynamic error) {
failedReassemble = true; failedReassemble = true;
globals.printError('Reassembling ${view.uiIsolate.name} failed: $error'); globals.printError('Reassembling ${view.uiIsolate.name} failed: $error');
}, test: (dynamic error) => error is Exception)); }, test: (dynamic error) => error is Exception));
......
...@@ -628,15 +628,13 @@ extension FlutterVmService on vm_service.VmService { ...@@ -628,15 +628,13 @@ extension FlutterVmService on vm_service.VmService {
); );
} }
Future<Map<String, dynamic>> flutterFastReassemble(String classId, { Future<Map<String, dynamic>> flutterFastReassemble({
@required String isolateId, @required String isolateId,
}) { }) {
return invokeFlutterExtensionRpcRaw( return invokeFlutterExtensionRpcRaw(
'ext.flutter.fastReassemble', 'ext.flutter.fastReassemble',
isolateId: isolateId, isolateId: isolateId,
args: <String, Object>{ args: <String, Object>{},
'class': classId,
},
); );
} }
......
// 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 'package:meta/meta.dart';
import 'features.dart';
/// The widget cache determines if the body of a single widget was modified since
/// the last scan of the token stream.
class WidgetCache {
WidgetCache({
@required FeatureFlags featureFlags,
}) : _featureFlags = featureFlags;
final FeatureFlags _featureFlags;
/// If the build method of a single widget was modified, return the widget name.
///
/// If any other changes were made, or there is an error scanning the file,
/// return `null`.
Future<String> validateLibrary(Uri libraryUri) async {
if (!_featureFlags.isSingleWidgetReloadEnabled) {
return null;
}
return null;
}
}
...@@ -84,7 +84,7 @@ void main() { ...@@ -84,7 +84,7 @@ void main() {
', asyncScanning: $asyncScanning', () async { ', asyncScanning: $asyncScanning', () async {
final DateTime past = DateTime.now().subtract(const Duration(seconds: 1)); final DateTime past = DateTime.now().subtract(const Duration(seconds: 1));
final FileSystem fileSystem = MemoryFileSystem.test(); final FileSystem fileSystem = MemoryFileSystem.test();
final PackageConfig packageConfig = PackageConfig.empty; const PackageConfig packageConfig = PackageConfig.empty;
final ProjectFileInvalidator projectFileInvalidator = ProjectFileInvalidator( final ProjectFileInvalidator projectFileInvalidator = ProjectFileInvalidator(
fileSystem: fileSystem, fileSystem: fileSystem,
platform: FakePlatform(), platform: FakePlatform(),
...@@ -126,7 +126,7 @@ void main() { ...@@ -126,7 +126,7 @@ void main() {
testWithoutContext('Picks up changes to the .packages file and updates PackageConfig' testWithoutContext('Picks up changes to the .packages file and updates PackageConfig'
', asyncScanning: $asyncScanning', () async { ', asyncScanning: $asyncScanning', () async {
final FileSystem fileSystem = MemoryFileSystem.test(); final FileSystem fileSystem = MemoryFileSystem.test();
final PackageConfig packageConfig = PackageConfig.empty; const PackageConfig packageConfig = PackageConfig.empty;
final ProjectFileInvalidator projectFileInvalidator = ProjectFileInvalidator( final ProjectFileInvalidator projectFileInvalidator = ProjectFileInvalidator(
fileSystem: fileSystem, fileSystem: fileSystem,
platform: FakePlatform(), platform: FakePlatform(),
......
...@@ -139,7 +139,7 @@ void main() { ...@@ -139,7 +139,7 @@ void main() {
trackWidgetCreation: anyNamed('trackWidgetCreation'), trackWidgetCreation: anyNamed('trackWidgetCreation'),
packageConfig: anyNamed('packageConfig'), packageConfig: anyNamed('packageConfig'),
)).thenAnswer((Invocation _) async { )).thenAnswer((Invocation _) async {
return UpdateFSReport(success: true, syncedBytes: 0)..invalidatedModules = <String>[]; return UpdateFSReport(success: true, syncedBytes: 0);
}); });
when(mockDebugConnection.vmService).thenAnswer((Invocation invocation) { when(mockDebugConnection.vmService).thenAnswer((Invocation invocation) {
return fakeVmServiceHost.vmService; return fakeVmServiceHost.vmService;
...@@ -352,7 +352,7 @@ void main() { ...@@ -352,7 +352,7 @@ void main() {
trackWidgetCreation: anyNamed('trackWidgetCreation'), trackWidgetCreation: anyNamed('trackWidgetCreation'),
packageConfig: anyNamed('packageConfig'), packageConfig: anyNamed('packageConfig'),
)).thenAnswer((Invocation _) async { )).thenAnswer((Invocation _) async {
return UpdateFSReport(success: false, syncedBytes: 0)..invalidatedModules = <String>[]; return UpdateFSReport(success: false, syncedBytes: 0);
}); });
expect(await residentWebRunner.run(), 1); expect(await residentWebRunner.run(), 1);
...@@ -587,8 +587,7 @@ void main() { ...@@ -587,8 +587,7 @@ void main() {
)).thenAnswer((Invocation invocation) async { )).thenAnswer((Invocation invocation) async {
// Generated entrypoint file in temp dir. // Generated entrypoint file in temp dir.
expect(invocation.namedArguments[#mainUri].toString(), contains('entrypoint.dart')); expect(invocation.namedArguments[#mainUri].toString(), contains('entrypoint.dart'));
return UpdateFSReport(success: true) return UpdateFSReport(success: true);
..invalidatedModules = <String>['example'];
}); });
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run( unawaited(residentWebRunner.run(
...@@ -668,8 +667,7 @@ void main() { ...@@ -668,8 +667,7 @@ void main() {
packageConfig: anyNamed('packageConfig'), packageConfig: anyNamed('packageConfig'),
)).thenAnswer((Invocation invocation) async { )).thenAnswer((Invocation invocation) async {
entrypointFileUri = invocation.namedArguments[#mainUri] as Uri; entrypointFileUri = invocation.namedArguments[#mainUri] as Uri;
return UpdateFSReport(success: true) return UpdateFSReport(success: true);
..invalidatedModules = <String>['example'];
}); });
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run( unawaited(residentWebRunner.run(
...@@ -727,8 +725,7 @@ void main() { ...@@ -727,8 +725,7 @@ void main() {
invalidatedFiles: anyNamed('invalidatedFiles'), invalidatedFiles: anyNamed('invalidatedFiles'),
packageConfig: anyNamed('packageConfig'), packageConfig: anyNamed('packageConfig'),
)).thenAnswer((Invocation invocation) async { )).thenAnswer((Invocation invocation) async {
return UpdateFSReport(success: true) return UpdateFSReport(success: true);
..invalidatedModules = <String>['example'];
}); });
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run( unawaited(residentWebRunner.run(
...@@ -801,7 +798,7 @@ void main() { ...@@ -801,7 +798,7 @@ void main() {
packageConfig: anyNamed('packageConfig'), packageConfig: anyNamed('packageConfig'),
trackWidgetCreation: anyNamed('trackWidgetCreation'), trackWidgetCreation: anyNamed('trackWidgetCreation'),
)).thenAnswer((Invocation _) async { )).thenAnswer((Invocation _) async {
return UpdateFSReport(success: false, syncedBytes: 0)..invalidatedModules = <String>[]; return UpdateFSReport(success: false, syncedBytes: 0);
}); });
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run( unawaited(residentWebRunner.run(
...@@ -875,7 +872,7 @@ void main() { ...@@ -875,7 +872,7 @@ void main() {
packageConfig: anyNamed('packageConfig'), packageConfig: anyNamed('packageConfig'),
trackWidgetCreation: anyNamed('trackWidgetCreation'), trackWidgetCreation: anyNamed('trackWidgetCreation'),
)).thenAnswer((Invocation _) async { )).thenAnswer((Invocation _) async {
return UpdateFSReport(success: false, syncedBytes: 0)..invalidatedModules = <String>[]; return UpdateFSReport(success: false, syncedBytes: 0);
}); });
final OperationResult result = await residentWebRunner.restart(fullRestart: true); final OperationResult result = await residentWebRunner.restart(fullRestart: true);
......
// 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 'package:flutter_tools/src/widget_cache.dart';
import '../src/common.dart';
import '../src/testbed.dart';
void main() {
testWithoutContext('widget cache returns null when experiment is disabled', () async {
final WidgetCache widgetCache = WidgetCache(featureFlags: TestFeatureFlags(isSingleWidgetReloadEnabled: false));
expect(await widgetCache.validateLibrary(Uri.parse('package:hello_world/main.dart')), null);
});
testWithoutContext('widget cache returns null because functionality is not complete', () async {
final WidgetCache widgetCache = WidgetCache(featureFlags: TestFeatureFlags(isSingleWidgetReloadEnabled: true));
expect(await widgetCache.validateLibrary(Uri.parse('package:hello_world/main.dart')), null);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment