Unverified Commit a19f5bac authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] connect widget cache from frontend_server (#65951)

parent ae630b42
...@@ -433,21 +433,16 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB ...@@ -433,21 +433,16 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
registerServiceExtension( registerServiceExtension(
name: 'fastReassemble', name: 'fastReassemble',
callback: (Map<String, Object> params) async { callback: (Map<String, Object> params) async {
final FastReassemblePredicate? fastReassemblePredicate = _debugFastReassembleMethod; final String? className = params['className'] as String?;
_debugFastReassembleMethod = null; void markElementsDirty(Element element) {
if (fastReassemblePredicate == null) { if (element.widget.runtimeType.toString() == className) {
throw FlutterError('debugFastReassembleMethod must be set to use fastReassemble.');
}
void markElementsDirty(Element? element) {
if (element == null) {
return;
}
if (fastReassemblePredicate(element.widget)) {
element.markNeedsBuild(); element.markNeedsBuild();
} }
element.visitChildElements(markElementsDirty); element.visitChildElements(markElementsDirty);
} }
markElementsDirty(renderViewElement); if (renderViewElement != null) {
markElementsDirty(renderViewElement!);
}
await endOfFrame; await endOfFrame;
return <String, String>{'type': 'Success'}; return <String, String>{'type': 'Success'};
}, },
...@@ -1061,36 +1056,6 @@ void runApp(Widget app) { ...@@ -1061,36 +1056,6 @@ void runApp(Widget app) {
..scheduleWarmUpFrame(); ..scheduleWarmUpFrame();
} }
/// A function that should validate that the provided object is assignable to a
/// given type.
typedef FastReassemblePredicate = bool Function(Object);
/// Debug-only functionality used to perform faster hot reloads.
///
/// This field is set by expression evaluation in the flutter tool and is
/// used to invalidate specific types of [Element]s. This setter
/// should not be referenced in user code and is only public so that expression
/// evaluation can be done in the context of an almost-arbitrary Dart library.
///
/// For example, expression evaluation might be performed with the following code:
///
/// ```dart
/// (debugFastReassembleMethod=(Object x) => x is Foo)()
/// ```
///
/// And then followed by a call to `ext.flutter.fastReassemble`. This will read
/// the provided predicate and use it to mark specific elements dirty wherever
/// [Element.widget] is a `Foo`. Afterwards, the internal field will be nulled
/// out.
FastReassemblePredicate? get debugFastReassembleMethod => _debugFastReassembleMethod;
set debugFastReassembleMethod(FastReassemblePredicate? fastReassemblePredicate) {
assert(() {
_debugFastReassembleMethod = fastReassemblePredicate;
return true;
}());
}
FastReassemblePredicate? _debugFastReassembleMethod;
/// Print a string representation of the currently running app. /// Print a string representation of the currently running app.
void debugDumpApp() { void debugDumpApp() {
assert(WidgetsBinding.instance != null); assert(WidgetsBinding.instance != null);
......
...@@ -740,8 +740,4 @@ void main() { ...@@ -740,8 +740,4 @@ void main() {
expect(brightnessValue, 'Brightness.light'); expect(brightnessValue, 'Brightness.light');
}); });
test('Service extensions - fastReassemble', () async {
expect(binding.testExtension('fastReassemble', <String, String>{}), throwsA(isA<FlutterError>()));
});
} }
...@@ -588,6 +588,7 @@ class _ResidentWebRunner extends ResidentWebRunner { ...@@ -588,6 +588,7 @@ class _ResidentWebRunner extends ResidentWebRunner {
reason: reason, reason: reason,
overallTimeInMs: timer.elapsed.inMilliseconds, overallTimeInMs: timer.elapsed.inMilliseconds,
nullSafety: usageNullSafety, nullSafety: usageNullSafety,
fastReassemble: null,
).send(); ).send();
} }
return OperationResult.ok; return OperationResult.ok;
......
...@@ -15,7 +15,6 @@ import '../base/io.dart'; ...@@ -15,7 +15,6 @@ 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';
...@@ -27,7 +26,6 @@ import '../resident_runner.dart'; ...@@ -27,7 +26,6 @@ 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`.
...@@ -384,7 +382,6 @@ class AttachCommand extends FlutterCommand { ...@@ -384,7 +382,6 @@ 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,7 +19,6 @@ import '../build_info.dart'; ...@@ -19,7 +19,6 @@ 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';
...@@ -27,7 +26,6 @@ import '../run_cold.dart'; ...@@ -27,7 +26,6 @@ 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';
...@@ -472,7 +470,6 @@ class AppDomain extends Domain { ...@@ -472,7 +470,6 @@ class AppDomain extends Domain {
flutterProject: flutterProject, flutterProject: flutterProject,
target: target, target: target,
buildInfo: options.buildInfo, buildInfo: options.buildInfo,
widgetCache: WidgetCache(featureFlags: featureFlags),
); );
ResidentRunner runner; ResidentRunner runner;
......
...@@ -23,7 +23,6 @@ import '../run_hot.dart'; ...@@ -23,7 +23,6 @@ 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 {
...@@ -532,7 +531,6 @@ class RunCommand extends RunCommandBase { ...@@ -532,7 +531,6 @@ 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,7 +302,7 @@ class UpdateFSReport { ...@@ -302,7 +302,7 @@ class UpdateFSReport {
bool success = false, bool success = false,
int invalidatedSourcesCount = 0, int invalidatedSourcesCount = 0,
int syncedBytes = 0, int syncedBytes = 0,
this.fastReassemble, this.fastReassembleClassName,
}) : _success = success, }) : _success = success,
_invalidatedSourcesCount = invalidatedSourcesCount, _invalidatedSourcesCount = invalidatedSourcesCount,
_syncedBytes = syncedBytes; _syncedBytes = syncedBytes;
...@@ -312,7 +312,7 @@ class UpdateFSReport { ...@@ -312,7 +312,7 @@ class UpdateFSReport {
int get syncedBytes => _syncedBytes; int get syncedBytes => _syncedBytes;
bool _success; bool _success;
bool fastReassemble; String fastReassembleClassName;
int _invalidatedSourcesCount; int _invalidatedSourcesCount;
int _syncedBytes; int _syncedBytes;
...@@ -320,11 +320,7 @@ class UpdateFSReport { ...@@ -320,11 +320,7 @@ class UpdateFSReport {
if (!report._success) { if (!report._success) {
_success = false; _success = false;
} }
if (report.fastReassemble != null && fastReassemble != null) { fastReassembleClassName ??= report.fastReassembleClassName;
fastReassemble &= report.fastReassemble;
} else if (report.fastReassemble != null) {
fastReassemble = report.fastReassemble;
}
_invalidatedSourcesCount += report._invalidatedSourcesCount; _invalidatedSourcesCount += report._invalidatedSourcesCount;
_syncedBytes += report._syncedBytes; _syncedBytes += report._syncedBytes;
} }
...@@ -365,6 +361,7 @@ class DevFS { ...@@ -365,6 +361,7 @@ class DevFS {
List<Uri> sources = <Uri>[]; List<Uri> sources = <Uri>[];
DateTime lastCompiled; DateTime lastCompiled;
PackageConfig lastPackageConfig; PackageConfig lastPackageConfig;
File _widgetCacheOutputFile;
Uri _baseUri; Uri _baseUri;
Uri get baseUri => _baseUri; Uri get baseUri => _baseUri;
...@@ -404,6 +401,20 @@ class DevFS { ...@@ -404,6 +401,20 @@ class DevFS {
_logger.printTrace('DevFS: Deleted filesystem on the device ($_baseUri)'); _logger.printTrace('DevFS: Deleted filesystem on the device ($_baseUri)');
} }
/// 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`.
String _checkIfSingleWidgetReloadApplied() {
if (_widgetCacheOutputFile != null && _widgetCacheOutputFile.existsSync()) {
final String widget = _widgetCacheOutputFile.readAsStringSync().trim();
if (widget.isNotEmpty) {
return widget;
}
}
return null;
}
/// Updates files on the device. /// Updates files on the device.
/// ///
/// Returns the number of bytes synced. /// Returns the number of bytes synced.
...@@ -427,6 +438,7 @@ class DevFS { ...@@ -427,6 +438,7 @@ class DevFS {
assert(generator != null); assert(generator != null);
final DateTime candidateCompileTime = DateTime.now(); final DateTime candidateCompileTime = DateTime.now();
lastPackageConfig = packageConfig; lastPackageConfig = packageConfig;
_widgetCacheOutputFile = _fileSystem.file('$dillOutputPath.incremental.dill.widget_cache');
// Update modified files // Update modified files
final Map<Uri, DevFSContent> dirtyEntries = <Uri, DevFSContent>{}; final Map<Uri, DevFSContent> dirtyEntries = <Uri, DevFSContent>{};
...@@ -502,8 +514,12 @@ class DevFS { ...@@ -502,8 +514,12 @@ class DevFS {
} }
} }
_logger.printTrace('DevFS: Sync finished'); _logger.printTrace('DevFS: Sync finished');
return UpdateFSReport(success: true, syncedBytes: syncedBytes, return UpdateFSReport(
invalidatedSourcesCount: invalidatedFiles.length); success: true,
syncedBytes: syncedBytes,
invalidatedSourcesCount: invalidatedFiles.length,
fastReassembleClassName: _checkIfSingleWidgetReloadApplied(),
);
} }
/// Converts a platform-specific file path to a platform-independent URL path. /// Converts a platform-specific file path to a platform-independent URL path.
......
...@@ -239,6 +239,7 @@ const Feature flutterFuchsiaFeature = Feature( ...@@ -239,6 +239,7 @@ const Feature flutterFuchsiaFeature = Feature(
const Feature singleWidgetReload = Feature( const Feature singleWidgetReload = Feature(
name: 'Hot reload optimization for changes to class body of a single widget', name: 'Hot reload optimization for changes to class body of a single widget',
configSetting: 'single-widget-reload-optimization', configSetting: 'single-widget-reload-optimization',
environmentOverride: 'FLUTTER_SINGLE_WIDGET_RELOAD',
master: FeatureChannelSetting( master: FeatureChannelSetting(
available: true, available: true,
enabledByDefault: false, enabledByDefault: false,
......
...@@ -40,6 +40,7 @@ class HotEvent extends UsageEvent { ...@@ -40,6 +40,7 @@ class HotEvent extends UsageEvent {
@required this.emulator, @required this.emulator,
@required this.fullRestart, @required this.fullRestart,
@required this.nullSafety, @required this.nullSafety,
@required this.fastReassemble,
this.reason, this.reason,
this.finalLibraryCount, this.finalLibraryCount,
this.syncedLibraryCount, this.syncedLibraryCount,
...@@ -57,6 +58,7 @@ class HotEvent extends UsageEvent { ...@@ -57,6 +58,7 @@ class HotEvent extends UsageEvent {
final bool emulator; final bool emulator;
final bool fullRestart; final bool fullRestart;
final bool nullSafety; final bool nullSafety;
final bool fastReassemble;
final int finalLibraryCount; final int finalLibraryCount;
final int syncedLibraryCount; final int syncedLibraryCount;
final int syncedClassesCount; final int syncedClassesCount;
...@@ -93,6 +95,8 @@ class HotEvent extends UsageEvent { ...@@ -93,6 +95,8 @@ class HotEvent extends UsageEvent {
CustomDimensions.hotEventOverallTimeInMs: overallTimeInMs.toString(), CustomDimensions.hotEventOverallTimeInMs: overallTimeInMs.toString(),
if (nullSafety != null) if (nullSafety != null)
CustomDimensions.nullSafety: nullSafety.toString(), CustomDimensions.nullSafety: nullSafety.toString(),
if (fastReassemble != null)
CustomDimensions.fastReassemble: fastReassemble.toString(),
}); });
flutterUsage.sendEvent(category, parameter, parameters: parameters); flutterUsage.sendEvent(category, parameter, parameters: parameters);
} }
......
...@@ -58,6 +58,7 @@ enum CustomDimensions { ...@@ -58,6 +58,7 @@ enum CustomDimensions {
commandRunAndroidEmbeddingVersion, // cd45 commandRunAndroidEmbeddingVersion, // cd45
commandPackagesAndroidEmbeddingVersion, // cd46 commandPackagesAndroidEmbeddingVersion, // cd46
nullSafety, // cd47 nullSafety, // cd47
fastReassemble, // cd48
} }
String cdKey(CustomDimensions cd) => 'cd${cd.index + 1}'; String cdKey(CustomDimensions cd) => 'cd${cd.index + 1}';
......
...@@ -34,7 +34,6 @@ import 'project.dart'; ...@@ -34,7 +34,6 @@ 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(
...@@ -46,7 +45,6 @@ class FlutterDevice { ...@@ -46,7 +45,6 @@ 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(
...@@ -79,7 +77,6 @@ class FlutterDevice { ...@@ -79,7 +77,6 @@ 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;
...@@ -134,6 +131,14 @@ class FlutterDevice { ...@@ -134,6 +131,14 @@ class FlutterDevice {
logger: globals.logger, logger: globals.logger,
); );
} else { } else {
// The flutter-widget-cache feature only applies to run mode.
List<String> extraFrontEndOptions = buildInfo.extraFrontEndOptions;
if (featureFlags.isSingleWidgetReloadEnabled) {
extraFrontEndOptions = <String>[
'--flutter-widget-cache',
...?extraFrontEndOptions,
];
}
generator = ResidentCompiler( generator = ResidentCompiler(
globals.artifacts.getArtifactPath( globals.artifacts.getArtifactPath(
Artifact.flutterPatchedSdkPath, Artifact.flutterPatchedSdkPath,
...@@ -146,11 +151,11 @@ class FlutterDevice { ...@@ -146,11 +151,11 @@ class FlutterDevice {
fileSystemScheme: fileSystemScheme, fileSystemScheme: fileSystemScheme,
targetModel: targetModel, targetModel: targetModel,
dartDefines: buildInfo.dartDefines, dartDefines: buildInfo.dartDefines,
extraFrontEndOptions: buildInfo.extraFrontEndOptions, extraFrontEndOptions: extraFrontEndOptions,
initializeFromDill: getDefaultCachedKernelPath( initializeFromDill: getDefaultCachedKernelPath(
trackWidgetCreation: buildInfo.trackWidgetCreation, trackWidgetCreation: buildInfo.trackWidgetCreation,
dartDefines: buildInfo.dartDefines, dartDefines: buildInfo.dartDefines,
extraFrontEndOptions: buildInfo.extraFrontEndOptions, extraFrontEndOptions: extraFrontEndOptions,
), ),
packagesPath: buildInfo.packagesPath, packagesPath: buildInfo.packagesPath,
artifacts: globals.artifacts, artifacts: globals.artifacts,
...@@ -168,7 +173,6 @@ class FlutterDevice { ...@@ -168,7 +173,6 @@ class FlutterDevice {
generator: generator, generator: generator,
buildInfo: buildInfo, buildInfo: buildInfo,
userIdentifier: userIdentifier, userIdentifier: userIdentifier,
widgetCache: widgetCache,
); );
} }
...@@ -176,7 +180,6 @@ class FlutterDevice { ...@@ -176,7 +180,6 @@ 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;
...@@ -655,44 +658,6 @@ class FlutterDevice { ...@@ -655,44 +658,6 @@ 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,
...@@ -712,34 +677,22 @@ class FlutterDevice { ...@@ -712,34 +677,22 @@ class FlutterDevice {
timeout: timeoutConfiguration.fastOperation, timeout: timeoutConfiguration.fastOperation,
); );
UpdateFSReport report; UpdateFSReport report;
bool fastReassemble = false;
try { try {
await Future.wait(<Future<void>>[ report = await devFS.update(
devFS.update( mainUri: mainUri,
mainUri: mainUri, target: target,
target: target, bundle: bundle,
bundle: bundle, firstBuildTime: firstBuildTime,
firstBuildTime: firstBuildTime, bundleFirstUpload: bundleFirstUpload,
bundleFirstUpload: bundleFirstUpload, generator: generator,
generator: generator, fullRestart: fullRestart,
fullRestart: fullRestart, dillOutputPath: dillOutputPath,
dillOutputPath: dillOutputPath, trackWidgetCreation: buildInfo.trackWidgetCreation,
trackWidgetCreation: buildInfo.trackWidgetCreation, projectRootPath: projectRootPath,
projectRootPath: projectRootPath, pathToReload: pathToReload,
pathToReload: pathToReload, invalidatedFiles: invalidatedFiles,
invalidatedFiles: invalidatedFiles, packageConfig: packageConfig,
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);
......
...@@ -21,6 +21,7 @@ import 'convert.dart'; ...@@ -21,6 +21,7 @@ import 'convert.dart';
import 'dart/package_map.dart'; import 'dart/package_map.dart';
import 'devfs.dart'; import 'devfs.dart';
import 'device.dart'; import 'device.dart';
import 'features.dart';
import 'globals.dart' as globals; import 'globals.dart' as globals;
import 'reporting/reporting.dart'; import 'reporting/reporting.dart';
import 'resident_runner.dart'; import 'resident_runner.dart';
...@@ -744,7 +745,9 @@ class HotRunner extends ResidentRunner { ...@@ -744,7 +745,9 @@ class HotRunner extends ResidentRunner {
emulator: emulator, emulator: emulator,
fullRestart: true, fullRestart: true,
nullSafety: usageNullSafety, nullSafety: usageNullSafety,
reason: reason).send(); reason: reason,
fastReassemble: null,
).send();
status?.cancel(); status?.cancel();
} }
return result; return result;
...@@ -787,6 +790,7 @@ class HotRunner extends ResidentRunner { ...@@ -787,6 +790,7 @@ class HotRunner extends ResidentRunner {
fullRestart: false, fullRestart: false,
nullSafety: usageNullSafety, nullSafety: usageNullSafety,
reason: reason, reason: reason,
fastReassemble: null,
).send(); ).send();
return OperationResult(1, 'hot reload failed to complete', fatal: true); return OperationResult(1, 'hot reload failed to complete', fatal: true);
} finally { } finally {
...@@ -866,6 +870,7 @@ class HotRunner extends ResidentRunner { ...@@ -866,6 +870,7 @@ class HotRunner extends ResidentRunner {
fullRestart: false, fullRestart: false,
reason: reason, reason: reason,
nullSafety: usageNullSafety, nullSafety: usageNullSafety,
fastReassemble: null,
).send(); ).send();
return OperationResult(1, 'Reload rejected'); return OperationResult(1, 'Reload rejected');
} }
...@@ -894,6 +899,7 @@ class HotRunner extends ResidentRunner { ...@@ -894,6 +899,7 @@ class HotRunner extends ResidentRunner {
fullRestart: false, fullRestart: false,
reason: reason, reason: reason,
nullSafety: usageNullSafety, nullSafety: usageNullSafety,
fastReassemble: null,
).send(); ).send();
return OperationResult(errorCode, errorMessage); return OperationResult(errorCode, errorMessage);
} }
...@@ -936,9 +942,10 @@ class HotRunner extends ResidentRunner { ...@@ -936,9 +942,10 @@ class HotRunner extends ResidentRunner {
// If the tool identified a change in a single widget, do a fast instead // If the tool identified a change in a single widget, do a fast instead
// of a full reassemble. // of a full reassemble.
Future<void> reassembleWork; Future<void> reassembleWork;
if (updatedDevFS.fastReassemble == true) { if (updatedDevFS.fastReassembleClassName != null) {
reassembleWork = device.vmService.flutterFastReassemble( reassembleWork = device.vmService.flutterFastReassemble(
isolateId: view.uiIsolate.id, isolateId: view.uiIsolate.id,
className: updatedDevFS.fastReassembleClassName,
); );
} else { } else {
reassembleWork = device.vmService.flutterReassemble( reassembleWork = device.vmService.flutterReassemble(
...@@ -1030,6 +1037,9 @@ class HotRunner extends ResidentRunner { ...@@ -1030,6 +1037,9 @@ class HotRunner extends ResidentRunner {
invalidatedSourcesCount: updatedDevFS.invalidatedSourcesCount, invalidatedSourcesCount: updatedDevFS.invalidatedSourcesCount,
transferTimeInMs: devFSTimer.elapsed.inMilliseconds, transferTimeInMs: devFSTimer.elapsed.inMilliseconds,
nullSafety: usageNullSafety, nullSafety: usageNullSafety,
fastReassemble: featureFlags.isSingleWidgetReloadEnabled
? updatedDevFS.fastReassembleClassName != null
: null,
).send(); ).send();
if (shouldReportReloadTime) { if (shouldReportReloadTime) {
......
...@@ -628,11 +628,14 @@ extension FlutterVmService on vm_service.VmService { ...@@ -628,11 +628,14 @@ extension FlutterVmService on vm_service.VmService {
Future<Map<String, dynamic>> flutterFastReassemble({ Future<Map<String, dynamic>> flutterFastReassemble({
@required String isolateId, @required String isolateId,
@required String className,
}) { }) {
return invokeFlutterExtensionRpcRaw( return invokeFlutterExtensionRpcRaw(
'ext.flutter.fastReassemble', 'ext.flutter.fastReassemble',
isolateId: isolateId, isolateId: isolateId,
args: <String, Object>{}, args: <String, Object>{
'className': className,
},
); );
} }
......
// 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;
}
}
...@@ -6,7 +6,7 @@ import 'dart:async'; ...@@ -6,7 +6,7 @@ import 'dart:async';
import 'package:flutter_tools/src/base/dds.dart'; import 'package:flutter_tools/src/base/dds.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/widget_cache.dart'; import 'package:flutter_tools/src/features.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart'; import 'package:package_config/package_config.dart';
import 'package:vm_service/vm_service.dart' as vm_service; import 'package:vm_service/vm_service.dart' as vm_service;
...@@ -714,22 +714,6 @@ void main() { ...@@ -714,22 +714,6 @@ void main() {
listViews, listViews,
listViews, listViews,
listViews, listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
const FakeVmServiceRequest(
method: 'evaluate',
args: <String, String>{
'isolateId': '1',
'targetId': '1',
'expression': '((){debugFastReassembleMethod=(Object _fastReassembleParam) => _fastReassembleParam is FakeWidget})()',
}
),
listViews,
const FakeVmServiceRequest( const FakeVmServiceRequest(
method: '_flutter.setAssetBundlePath', method: '_flutter.setAssetBundlePath',
args: <String, Object>{ args: <String, Object>{
...@@ -769,13 +753,13 @@ void main() { ...@@ -769,13 +753,13 @@ void main() {
method: 'ext.flutter.fastReassemble', method: 'ext.flutter.fastReassemble',
args: <String, Object>{ args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id, 'isolateId': fakeUnpausedIsolate.id,
'className': 'FOO',
}, },
), ),
]); ]);
final FakeFlutterDevice flutterDevice = FakeFlutterDevice( final FakeFlutterDevice flutterDevice = FakeFlutterDevice(
mockDevice, mockDevice,
BuildInfo.debug, BuildInfo.debug,
FakeWidgetCache(),
FakeResidentCompiler(), FakeResidentCompiler(),
mockDevFS, mockDevFS,
)..vmService = fakeVmServiceHost.vmService; )..vmService = fakeVmServiceHost.vmService;
...@@ -811,7 +795,7 @@ void main() { ...@@ -811,7 +795,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, fastReassembleClassName: 'FOO');
}); });
final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync(); final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
...@@ -822,142 +806,21 @@ void main() { ...@@ -822,142 +806,21 @@ void main() {
)); ));
final OperationResult result = await residentRunner.restart(fullRestart: false); final OperationResult result = await residentRunner.restart(fullRestart: false);
expect(result.fatal, false);
expect(result.code, 0);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
Platform: () => FakePlatform(operatingSystem: 'linux'),
ProjectFileInvalidator: () => FakeProjectFileInvalidator(),
}));
testUsingContext('ResidentRunner bails out of fast reassemble if evaluation fails', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: fakeVM.toJson(),
),
listViews,
listViews,
listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
const FakeVmServiceRequest(
method: 'evaluate',
args: <String, String>{
'isolateId': '1',
'targetId': '1',
'expression': '((){debugFastReassembleMethod=(Object _fastReassembleParam) => _fastReassembleParam is FakeWidget})()',
},
errorCode: 500,
),
listViews,
const FakeVmServiceRequest(
method: '_flutter.setAssetBundlePath',
args: <String, Object>{
'viewId': 'a',
'assetDirectory': 'build/flutter_assets',
'isolateId': '1',
}
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: fakeVM.toJson(),
),
const FakeVmServiceRequest(
method: 'reloadSources',
args: <String, Object>{
'isolateId': '1',
'pause': false,
'rootLibUri': 'lib/main.dart.incremental.dill',
},
jsonResponse: <String, Object>{
'type': 'ReloadReport',
'success': true,
'details': <String, Object>{
'loadedLibraryCount': 1,
},
},
),
listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'ext.flutter.reassemble',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
),
]);
final FakeFlutterDevice flutterDevice = FakeFlutterDevice(
mockDevice,
BuildInfo.debug,
FakeWidgetCache(),
FakeResidentCompiler(),
mockDevFS,
)..vmService = fakeVmServiceHost.vmService;
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
);
when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
return 'Example';
});
when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
return TargetPlatform.android_arm;
});
when(mockDevice.isLocalEmulator).thenAnswer((Invocation invocation) async {
return false;
});
when(mockDevice.getLogReader(app: anyNamed('app'))).thenReturn(NoOpDeviceLogReader('test'));
when(mockDevFS.update(
mainUri: anyNamed('mainUri'),
target: anyNamed('target'),
bundle: anyNamed('bundle'),
firstBuildTime: anyNamed('firstBuildTime'),
bundleFirstUpload: anyNamed('bundleFirstUpload'),
generator: anyNamed('generator'),
fullRestart: anyNamed('fullRestart'),
dillOutputPath: anyNamed('dillOutputPath'),
trackWidgetCreation: anyNamed('trackWidgetCreation'),
projectRootPath: anyNamed('projectRootPath'),
pathToReload: anyNamed('pathToReload'),
invalidatedFiles: anyNamed('invalidatedFiles'),
packageConfig: anyNamed('packageConfig'),
)).thenAnswer((Invocation invocation) async {
return UpdateFSReport(success: true);
});
final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> onAppStart = Completer<void>.sync();
unawaited(residentRunner.attach(
appStartedCompleter: onAppStart,
connectionInfoCompleter: onConnectionInfo,
));
final OperationResult result = await residentRunner.restart(fullRestart: false);
expect(result.fatal, false); expect(result.fatal, false);
expect(result.code, 0); expect(result.code, 0);
verify(globals.flutterUsage.sendEvent('hot', 'reload', parameters: argThat(
containsPair('cd48', 'true'),
named: 'parameters',
))).called(1);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(), FileSystem: () => MemoryFileSystem.test(),
Platform: () => FakePlatform(operatingSystem: 'linux'), Platform: () => FakePlatform(operatingSystem: 'linux'),
ProjectFileInvalidator: () => FakeProjectFileInvalidator(), ProjectFileInvalidator: () => FakeProjectFileInvalidator(),
Usage: () => MockUsage(),
FeatureFlags: () => TestFeatureFlags(isSingleWidgetReloadEnabled: true),
})); }));
testUsingContext('ResidentRunner can send target platform to analytics from full restart', () => testbed.run(() async { testUsingContext('ResidentRunner can send target platform to analytics from full restart', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews, listViews,
...@@ -2251,6 +2114,34 @@ void main() { ...@@ -2251,6 +2114,34 @@ void main() {
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
testUsingContext('FlutterDevice passes flutter-widget-cache flag when feature is enabled', () async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final MockDevice mockDevice = MockDevice();
when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async {
return TargetPlatform.android_arm;
});
final DefaultResidentCompiler residentCompiler = (await FlutterDevice.create(
mockDevice,
buildInfo: const BuildInfo(
BuildMode.debug,
'',
treeShakeIcons: false,
extraFrontEndOptions: <String>[],
),
flutterProject: FlutterProject.current(),
target: null,
)).generator as DefaultResidentCompiler;
expect(residentCompiler.extraFrontEndOptions,
contains('--flutter-widget-cache'));
}, overrides: <Type, Generator>{
Artifacts: () => Artifacts.test(),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isSingleWidgetReloadEnabled: true)
});
testUsingContext('connect sets up log reader', () => testbed.run(() async { testUsingContext('connect sets up log reader', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final MockDevice mockDevice = MockDevice(); final MockDevice mockDevice = MockDevice();
...@@ -2325,21 +2216,13 @@ class ThrowingForwardingFileSystem extends ForwardingFileSystem { ...@@ -2325,21 +2216,13 @@ class ThrowingForwardingFileSystem extends ForwardingFileSystem {
} }
} }
class FakeWidgetCache implements WidgetCache {
@override
Future<String> validateLibrary(Uri libraryUri) async {
return 'FakeWidget';
}
}
class FakeFlutterDevice extends FlutterDevice { class FakeFlutterDevice extends FlutterDevice {
FakeFlutterDevice( FakeFlutterDevice(
Device device, Device device,
BuildInfo buildInfo, BuildInfo buildInfo,
WidgetCache widgetCache,
ResidentCompiler residentCompiler, ResidentCompiler residentCompiler,
this.fakeDevFS, this.fakeDevFS,
) : super(device, buildInfo: buildInfo, widgetCache:widgetCache, generator: residentCompiler); ) : super(device, buildInfo: buildInfo, generator: residentCompiler);
@override @override
Future<void> connect({ Future<void> connect({
......
// 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);
});
}
...@@ -7,7 +7,6 @@ import 'dart:async'; ...@@ -7,7 +7,6 @@ import 'dart:async';
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/common.dart';
import 'package:vm_service/vm_service.dart'; import 'package:vm_service/vm_service.dart';
import 'package:vm_service/vm_service_io.dart';
import '../src/common.dart'; import '../src/common.dart';
import 'test_data/hot_reload_project.dart'; import 'test_data/hot_reload_project.dart';
...@@ -82,73 +81,6 @@ void main() { ...@@ -82,73 +81,6 @@ void main() {
} }
}); });
testWithoutContext('fastReassemble behavior triggers hot reload behavior with evaluation of expression', () async {
final Completer<void> tick1 = Completer<void>();
final Completer<void> tick2 = Completer<void>();
final Completer<void> tick3 = Completer<void>();
final StreamSubscription<String> subscription = flutter.stdout.listen((String line) {
if (line.contains('TICK 1')) {
tick1.complete();
}
if (line.contains('TICK 2')) {
tick2.complete();
}
if (line.contains('TICK 3')) {
tick3.complete();
}
});
await flutter.run(withDebugger: true);
final int port = flutter.vmServicePort;
final VmService vmService = await vmServiceConnectUri('ws://localhost:$port/ws');
await tick1.future;
try {
// Since the single-widget reload feature is not yet implemented, manually
// evaluate the expression for the reload.
final Isolate isolate = await waitForExtension(vmService);
final LibraryRef targetRef = isolate.libraries.firstWhere((LibraryRef libraryRef) {
return libraryRef.uri == 'package:test/main.dart';
});
await vmService.evaluate(
isolate.id,
targetRef.id,
'((){debugFastReassembleMethod=(Object x) => x is MyApp})()',
);
final Response fastReassemble1 = await vmService
.callServiceExtension('ext.flutter.fastReassemble', isolateId: isolate.id);
// _extensionType indicates success.
expect(fastReassemble1.type, '_extensionType');
await tick2.future;
// verify evaluation did not produce invalidat type by checking with dart:core
// type.
await vmService.evaluate(
isolate.id,
targetRef.id,
'((){debugFastReassembleMethod=(Object x) => x is bool})()',
);
final Response fastReassemble2 = await vmService
.callServiceExtension('ext.flutter.fastReassemble', isolateId: isolate.id);
// _extensionType indicates success.
expect(fastReassemble2.type, '_extensionType');
unawaited(tick3.future.whenComplete(() {
fail('Should not complete');
}));
// Invocation without evaluation leads to runtime error.
expect(vmService
.callServiceExtension('ext.flutter.fastReassemble', isolateId: isolate.id),
throwsA(isA<Exception>())
);
} finally {
await subscription.cancel();
}
});
testWithoutContext('hot restart works without error', () async { testWithoutContext('hot restart works without error', () async {
await flutter.run(); await flutter.run();
await flutter.hotRestart(); await flutter.hotRestart();
......
// 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 'package:flutter_tools/src/base/file_system.dart';
import '../src/common.dart';
import 'test_data/single_widget_reload_project.dart';
import 'test_driver.dart';
import 'test_utils.dart';
void main() {
Directory tempDir;
final SingleWidgetReloadProject project = SingleWidgetReloadProject();
FlutterRunTestDriver flutter;
setUp(() async {
tempDir = createResolvedTempDirectorySync('hot_reload_test.');
await project.setUpIn(tempDir);
flutter = FlutterRunTestDriver(tempDir);
});
tearDown(() async {
await flutter?.stop();
tryToDelete(tempDir);
});
testWithoutContext('newly added code executes during hot reload with single widget reloads, but only invalidated widget', () async {
final StringBuffer stdout = StringBuffer();
final StreamSubscription<String> subscription = flutter.stdout.listen(stdout.writeln);
await flutter.run(singleWidgetReloads: true);
project.uncommentHotReloadPrint();
try {
await flutter.hotReload();
expect(stdout.toString(), allOf(
contains('(((TICK 1)))'),
contains('(((((RELOAD WORKED)))))'),
// Does not invalidate parent widget, so second tick is not output.
isNot(contains('(((TICK 2)))'),
)));
} finally {
await subscription.cancel();
}
});
testWithoutContext('changes outside of the class body triggers a full reload', () async {
final StringBuffer stdout = StringBuffer();
final StreamSubscription<String> subscription = flutter.stdout.listen(stdout.writeln);
await flutter.run(singleWidgetReloads: true);
project.modifyFunction();
try {
await flutter.hotReload();
expect(stdout.toString(), allOf(
contains('(((TICK 1)))'),
contains('(((TICK 2)))'),
));
} finally {
await subscription.cancel();
}
});
}
// 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 '../test_utils.dart';
import 'project.dart';
class SingleWidgetReloadProject extends Project {
@override
final String pubspec = '''
name: test
environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
''';
@override
final String main = r'''
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final ByteData message = const StringCodec().encodeMessage('AppLifecycleState.resumed');
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
runApp(MyApp());
}
int count = 1;
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// PARENT WIDGET
print('((((TICK $count))))');
count += 1;
return MaterialApp(
title: 'Flutter Demo',
home: SecondWidget(),
);
}
}
class SecondWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Do not remove the next line, it's uncommented by a test to verify that
// hot reloading worked:
// printHotReloadWorked();
return Container();
}
}
void printHotReloadWorked() {
// The call to this function is uncommented by a test to verify that hot
// reloading worked.
print('(((((RELOAD WORKED)))))');
}
''';
Uri get parentWidgetUri => mainDart;
int get parentWidgetLine => lineContaining(main, '// PARENT WIDGET');
void uncommentHotReloadPrint() {
final String newMainContents = main.replaceAll(
'// printHotReloadWorked();',
'printHotReloadWorked();',
);
writeFile(fileSystem.path.join(dir.path, 'lib', 'main.dart'), newMainContents);
}
void modifyFunction() {
final String newMainContents = main.replaceAll(
'(((((RELOAD WORKED)))))',
'(((((RELOAD WORKED 2)))))',
);
writeFile(fileSystem.path.join(dir.path, 'lib', 'main.dart'), newMainContents);
}
}
...@@ -84,6 +84,7 @@ abstract class FlutterTestDriver { ...@@ -84,6 +84,7 @@ abstract class FlutterTestDriver {
String script, String script,
bool withDebugger = false, bool withDebugger = false,
File pidFile, File pidFile,
bool singleWidgetReloads = false,
}) async { }) async {
final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter'); final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
if (withDebugger) { if (withDebugger) {
...@@ -107,7 +108,12 @@ abstract class FlutterTestDriver { ...@@ -107,7 +108,12 @@ abstract class FlutterTestDriver {
.toList(), .toList(),
workingDirectory: _projectFolder.path, workingDirectory: _projectFolder.path,
// The web environment variable has the same effect as `flutter config --enable-web`. // The web environment variable has the same effect as `flutter config --enable-web`.
environment: <String, String>{'FLUTTER_TEST': 'true', 'FLUTTER_WEB': 'true'}, environment: <String, String>{
'FLUTTER_TEST': 'true',
'FLUTTER_WEB': 'true',
if (singleWidgetReloads)
'FLUTTER_SINGLE_WIDGET_RELOAD': 'true',
},
); );
// This class doesn't use the result of the future. It's made available // This class doesn't use the result of the future. It's made available
...@@ -442,6 +448,7 @@ class FlutterRunTestDriver extends FlutterTestDriver { ...@@ -442,6 +448,7 @@ class FlutterRunTestDriver extends FlutterTestDriver {
bool expressionEvaluation = true, bool expressionEvaluation = true,
bool structuredErrors = false, bool structuredErrors = false,
bool machine = true, bool machine = true,
bool singleWidgetReloads = false,
File pidFile, File pidFile,
String script, String script,
}) async { }) async {
...@@ -470,6 +477,7 @@ class FlutterRunTestDriver extends FlutterTestDriver { ...@@ -470,6 +477,7 @@ class FlutterRunTestDriver extends FlutterTestDriver {
pauseOnExceptions: pauseOnExceptions, pauseOnExceptions: pauseOnExceptions,
pidFile: pidFile, pidFile: pidFile,
script: script, script: script,
singleWidgetReloads: singleWidgetReloads,
); );
} }
...@@ -479,6 +487,7 @@ class FlutterRunTestDriver extends FlutterTestDriver { ...@@ -479,6 +487,7 @@ class FlutterRunTestDriver extends FlutterTestDriver {
bool startPaused = false, bool startPaused = false,
bool pauseOnExceptions = false, bool pauseOnExceptions = false,
File pidFile, File pidFile,
bool singleWidgetReloads = false,
}) async { }) async {
await _setupProcess( await _setupProcess(
<String>[ <String>[
...@@ -496,6 +505,7 @@ class FlutterRunTestDriver extends FlutterTestDriver { ...@@ -496,6 +505,7 @@ class FlutterRunTestDriver extends FlutterTestDriver {
startPaused: startPaused, startPaused: startPaused,
pauseOnExceptions: pauseOnExceptions, pauseOnExceptions: pauseOnExceptions,
pidFile: pidFile, pidFile: pidFile,
singleWidgetReloads: singleWidgetReloads,
); );
} }
...@@ -506,6 +516,7 @@ class FlutterRunTestDriver extends FlutterTestDriver { ...@@ -506,6 +516,7 @@ class FlutterRunTestDriver extends FlutterTestDriver {
bool withDebugger = false, bool withDebugger = false,
bool startPaused = false, bool startPaused = false,
bool pauseOnExceptions = false, bool pauseOnExceptions = false,
bool singleWidgetReloads = false,
File pidFile, File pidFile,
}) async { }) async {
assert(!startPaused || withDebugger); assert(!startPaused || withDebugger);
...@@ -514,6 +525,7 @@ class FlutterRunTestDriver extends FlutterTestDriver { ...@@ -514,6 +525,7 @@ class FlutterRunTestDriver extends FlutterTestDriver {
script: script, script: script,
withDebugger: withDebugger, withDebugger: withDebugger,
pidFile: pidFile, pidFile: pidFile,
singleWidgetReloads: singleWidgetReloads,
); );
final Completer<void> prematureExitGuard = Completer<void>(); final Completer<void> prematureExitGuard = Completer<void>();
...@@ -723,12 +735,14 @@ class FlutterTestTestDriver extends FlutterTestDriver { ...@@ -723,12 +735,14 @@ class FlutterTestTestDriver extends FlutterTestDriver {
bool pauseOnExceptions = false, bool pauseOnExceptions = false,
File pidFile, File pidFile,
Future<void> Function() beforeStart, Future<void> Function() beforeStart,
bool singleWidgetReloads = false,
}) async { }) async {
await super._setupProcess( await super._setupProcess(
args, args,
script: script, script: script,
withDebugger: withDebugger, withDebugger: withDebugger,
pidFile: pidFile, pidFile: pidFile,
singleWidgetReloads: singleWidgetReloads,
); );
// Stash the PID so that we can terminate the VM more reliably than using // Stash the PID so that we can terminate the VM more reliably than using
......
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