blob: f6adc3df654fd0dd3cfa079631acd02cdd046441 [file] [log] [blame]
// 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 'package:dds/dds.dart' as dds;
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/asset.dart';
import 'package:flutter_tools/src/base/command_help.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/io.dart' as io;
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/device_port_forwarder.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/globals_null_migrated.dart' as globals;
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/run_cold.dart';
import 'package:flutter_tools/src/run_hot.dart';
import 'package:flutter_tools/src/version.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart';
import 'package:test/fake.dart';
import 'package:vm_service/vm_service.dart' as vm_service;
import '../src/common.dart';
import '../src/context.dart';
import '../src/fake_vm_services.dart';
import '../src/fakes.dart';
import '../src/testbed.dart';
final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate(
id: '1',
pauseEvent: vm_service.Event(
kind: vm_service.EventKind.kResume,
timestamp: 0
),
breakpoints: <vm_service.Breakpoint>[],
exceptionPauseMode: null,
extensionRPCs: <String>[],
libraries: <vm_service.LibraryRef>[
vm_service.LibraryRef(
id: '1',
uri: 'file:///hello_world/main.dart',
name: '',
),
],
livePorts: 0,
name: 'test',
number: '1',
pauseOnExit: false,
runnable: true,
startTime: 0,
isSystemIsolate: false,
isolateFlags: <vm_service.IsolateFlag>[],
);
final vm_service.Isolate fakePausedIsolate = vm_service.Isolate(
id: '1',
pauseEvent: vm_service.Event(
kind: vm_service.EventKind.kPauseException,
timestamp: 0
),
breakpoints: <vm_service.Breakpoint>[
vm_service.Breakpoint(
breakpointNumber: 123,
id: 'test-breakpoint',
location: vm_service.SourceLocation(
tokenPos: 0,
script: vm_service.ScriptRef(id: 'test-script', uri: 'foo.dart'),
),
enabled: true,
resolved: true,
),
],
exceptionPauseMode: null,
libraries: <vm_service.LibraryRef>[],
livePorts: 0,
name: 'test',
number: '1',
pauseOnExit: false,
runnable: true,
startTime: 0,
isSystemIsolate: false,
isolateFlags: <vm_service.IsolateFlag>[],
);
final vm_service.VM fakeVM = vm_service.VM(
isolates: <vm_service.IsolateRef>[fakeUnpausedIsolate],
pid: 1,
hostCPU: '',
isolateGroups: <vm_service.IsolateGroupRef>[],
targetCPU: '',
startTime: 0,
name: 'dart',
architectureBits: 64,
operatingSystem: '',
version: '',
systemIsolateGroups: <vm_service.IsolateGroupRef>[],
systemIsolates: <vm_service.IsolateRef>[],
);
final FlutterView fakeFlutterView = FlutterView(
id: 'a',
uiIsolate: fakeUnpausedIsolate,
);
final FakeVmServiceRequest listViews = FakeVmServiceRequest(
method: kListViewsMethod,
jsonResponse: <String, Object>{
'views': <Object>[
fakeFlutterView.toJson(),
],
},
);
const FakeVmServiceRequest setAssetBundlePath = FakeVmServiceRequest(
method: '_flutter.setAssetBundlePath',
args: <String, Object>{
'viewId': 'a',
'assetDirectory': 'build/flutter_assets',
'isolateId': '1',
}
);
const FakeVmServiceRequest evict = FakeVmServiceRequest(
method: 'ext.flutter.evict',
args: <String, Object>{
'value': 'asset',
'isolateId': '1',
}
);
final Uri testUri = Uri.parse('foo://bar');
void main() {
Testbed testbed;
FakeFlutterDevice flutterDevice;
FakeDevFS devFS;
ResidentRunner residentRunner;
FakeDevice device;
FakeVmServiceHost fakeVmServiceHost;
setUp(() {
testbed = Testbed(setup: () {
globals.fs.file('.packages')
.writeAsStringSync('\n');
globals.fs.file(globals.fs.path.join('build', 'app.dill'))
..createSync(recursive: true)
..writeAsStringSync('ABC');
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
});
device = FakeDevice();
devFS = FakeDevFS();
flutterDevice = FakeFlutterDevice()
..testUri = testUri
..vmServiceHost = (() => fakeVmServiceHost)
..device = device
.._devFS = devFS;
});
testUsingContext('ResidentRunner can attach to device successfully', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
]);
final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> futureAppStart = Completer<void>.sync();
final Future<int> result = residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
enableDevTools: true,
);
final Future<DebugConnectionInfo> connectionInfo = futureConnectionInfo.future;
expect(await result, 0);
expect(futureConnectionInfo.isCompleted, true);
expect((await connectionInfo).baseUri, 'foo://bar');
expect(futureAppStart.isCompleted, true);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
testUsingContext('ResidentRunner suppresses errors for the initial compilation', () => testbed.run(() async {
globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
.createSync(recursive: true);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
]);
final FakeResidentCompiler residentCompiler = FakeResidentCompiler()
..nextOutput = const CompilerOutput('foo', 0 ,<Uri>[]);
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
flutterDevice.generator = residentCompiler;
expect(await residentRunner.run(enableDevTools: true), 0);
expect(residentCompiler.didSuppressErrors, true);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
// Regression test for https://github.com/flutter/flutter/issues/60613
testUsingContext('ResidentRunner calls appFailedToStart if initial compilation fails', () => testbed.run(() async {
globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
.createSync(recursive: true);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final FakeResidentCompiler residentCompiler = FakeResidentCompiler()
..nextOutput = const CompilerOutput('foo', 1 ,<Uri>[]);
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
flutterDevice.generator = residentCompiler;
expect(await residentRunner.run(), 1);
// Completing this future ensures that the daemon can exit correctly.
expect(await residentRunner.waitForAppToFinish(), 1);
}));
// Regression test for https://github.com/flutter/flutter/issues/60613
testUsingContext('ResidentRunner calls appFailedToStart if initial compilation fails - cold mode', () => testbed.run(() async {
globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
.createSync(recursive: true);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
residentRunner = ColdRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.release),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
flutterDevice.runColdCode = 1;
expect(await residentRunner.run(), 1);
// Completing this future ensures that the daemon can exit correctly.
expect(await residentRunner.waitForAppToFinish(), 1);
}));
// Regression test for https://github.com/flutter/flutter/issues/60613
testUsingContext('ResidentRunner calls appFailedToStart if exception is thrown - cold mode', () => testbed.run(() async {
globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
.createSync(recursive: true);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
residentRunner = ColdRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.release),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
flutterDevice.runColdError = Exception('BAD STUFF');
expect(await residentRunner.run(), 1);
// Completing this future ensures that the daemon can exit correctly.
expect(await residentRunner.waitForAppToFinish(), 1);
}));
testUsingContext('ResidentRunner does not suppressErrors if running with an applicationBinary', () => testbed.run(() async {
globals.fs.file(globals.fs.path.join('lib', 'main.dart'))
.createSync(recursive: true);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
]);
final FakeResidentCompiler residentCompiler = FakeResidentCompiler()
..nextOutput = const CompilerOutput('foo', 0 ,<Uri>[]);
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
applicationBinary: globals.fs.file('app.apk'),
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
flutterDevice.generator = residentCompiler;
expect(await residentRunner.run(enableDevTools: true), 0);
expect(residentCompiler.didSuppressErrors, false);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
testUsingContext('ResidentRunner can attach to device successfully with --fast-start', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
),
listViews,
const FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{
'streamId': 'Isolate',
}
),
FakeVmServiceRequest(
method: kRunInViewMethod,
args: <String, Object>{
'viewId': fakeFlutterView.id,
'mainScript': 'main.dart.dill',
'assetDirectory': 'build/flutter_assets',
}
),
FakeVmServiceStreamResponse(
streamId: 'Isolate',
event: vm_service.Event(
timestamp: 0,
kind: vm_service.EventKind.kIsolateRunnable,
)
),
]);
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(
BuildInfo.debug,
fastStart: true,
startPaused: true,
),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> futureAppStart = Completer<void>.sync();
final Future<int> result = residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
enableDevTools: true,
);
final Future<DebugConnectionInfo> connectionInfo = futureConnectionInfo.future;
expect(await result, 0);
expect(futureConnectionInfo.isCompleted, true);
expect((await connectionInfo).baseUri, 'foo://bar');
expect(futureAppStart.isCompleted, true);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
testUsingContext('ResidentRunner can handle an RPC exception from hot reload', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
listViews,
]);
final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> futureAppStart = Completer<void>.sync();
unawaited(residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
enableDevTools: true,
));
await futureAppStart.future;
flutterDevice.reportError = vm_service.RPCError('something bad happened', 666, '');
final OperationResult result = await residentRunner.restart();
expect(result.fatal, true);
expect(result.code, 1);
expect((globals.flutterUsage as TestUsage).events, contains(
TestUsageEvent('hot', 'exception', parameters: CustomDimensions(
hotEventTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
hotEventSdkName: 'Android',
hotEventEmulator: false,
hotEventFullRestart: false,
fastReassemble: false,
)),
));
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
Usage: () => TestUsage(),
}));
testUsingContext('ResidentRunner fails its operation if the device initialization is not complete', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
]);
final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> futureAppStart = Completer<void>.sync();
unawaited(residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
));
await futureAppStart.future;
flutterDevice._devFS = null;
final OperationResult result = await residentRunner.restart();
expect(result.fatal, false);
expect(result.code, 1);
expect(result.message, contains('Device initialization has not completed.'));
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
testUsingContext('ResidentRunner can handle an reload-barred exception from hot reload', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
listViews,
]);
final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> futureAppStart = Completer<void>.sync();
unawaited(residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
enableDevTools: true,
));
await futureAppStart.future;
flutterDevice.reportError = vm_service.RPCError('something bad happened', kIsolateReloadBarred, '');
final OperationResult result = await residentRunner.restart();
expect(result.fatal, true);
expect(result.code, kIsolateReloadBarred);
expect(result.message, contains('Unable to hot reload application due to an unrecoverable error'));
expect((globals.flutterUsage as TestUsage).events, contains(
TestUsageEvent('hot', 'reload-barred', parameters: CustomDimensions(
hotEventTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
hotEventSdkName: 'Android',
hotEventEmulator: false,
hotEventFullRestart: false,
fastReassemble: false,
)),
));
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
Usage: () => TestUsage(),
}));
testUsingContext('ResidentRunner reports hot reload event with null safety analytics', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
listViews,
]);
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
target: 'main.dart',
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(
BuildMode.debug, '', treeShakeIcons: false, extraFrontEndOptions: <String>[
'--enable-experiment=non-nullable',
],
)),
devtoolsHandler: createNoOpHandler,
);
final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> futureAppStart = Completer<void>.sync();
unawaited(residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
enableDevTools: true,
));
await futureAppStart.future;
flutterDevice.reportError = vm_service.RPCError('something bad happened', 666, '');
final OperationResult result = await residentRunner.restart();
expect(result.fatal, true);
expect(result.code, 1);
expect((globals.flutterUsage as TestUsage).events, contains(
TestUsageEvent('hot', 'exception', parameters: CustomDimensions(
hotEventTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
hotEventSdkName: 'Android',
hotEventEmulator: false,
hotEventFullRestart: false,
fastReassemble: false,
)),
));
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
Usage: () => TestUsage(),
}));
testUsingContext('ResidentRunner does not reload sources if no sources changed', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'ext.flutter.reassemble',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
),
]);
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> futureAppStart = Completer<void>.sync();
unawaited(residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
enableDevTools: true,
));
await futureAppStart.future;
flutterDevice.report = UpdateFSReport(success: true);
final OperationResult result = await residentRunner.restart();
expect(result.code, 0);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
testUsingContext('ResidentRunner reports error with missing entrypoint file', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
listViews,
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{
'isolates': <Object>[
fakeUnpausedIsolate.toJson(),
],
}).toJson(),
),
const FakeVmServiceRequest(
method: 'reloadSources',
args: <String, Object>{
'isolateId': '1',
'pause': false,
'rootLibUri': 'main.dart.incremental.dill'
},
jsonResponse: <String, Object>{
'type': 'ReloadReport',
'success': true,
'details': <String, Object>{
'loadedLibraryCount': 1,
},
},
),
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'ext.flutter.reassemble',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
),
]);
final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> futureAppStart = Completer<void>.sync();
unawaited(residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
enableDevTools: true,
));
await futureAppStart.future;
flutterDevice.report = UpdateFSReport(success: true, invalidatedSourcesCount: 1);
final OperationResult result = await residentRunner.restart();
expect(globals.fs.file(globals.fs.path.join('lib', 'main.dart')), isNot(exists));
expect(testLogger.errorText, contains('The entrypoint file (i.e. the file with main())'));
expect(result.fatal, false);
expect(result.code, 0);
}));
testUsingContext('ResidentRunner resets compilation time on reload reject', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
listViews,
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{
'isolates': <Object>[
fakeUnpausedIsolate.toJson(),
],
}).toJson(),
),
const FakeVmServiceRequest(
method: 'reloadSources',
args: <String, Object>{
'isolateId': '1',
'pause': false,
'rootLibUri': 'main.dart.incremental.dill'
},
jsonResponse: <String, Object>{
'type': 'ReloadReport',
'success': false,
'notices': <Object>[
<String, Object>{
'message': 'Failed to hot reload'
}
],
'details': <String, Object>{},
},
),
listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'ext.flutter.reassemble',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
),
]);
final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> futureAppStart = Completer<void>.sync();
unawaited(residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
enableDevTools: true,
));
await futureAppStart.future;
flutterDevice.report = UpdateFSReport(success: true, invalidatedSourcesCount: 1);
final OperationResult result = await residentRunner.restart();
expect(result.fatal, false);
expect(result.message, contains('Reload rejected: Failed to hot reload')); // contains error message from reload report.
expect(result.code, 1);
expect(devFS.lastCompiled, null);
}));
testUsingContext('ResidentRunner can send target platform to analytics from hot reload', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
listViews,
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{
'isolates': <Object>[
fakeUnpausedIsolate.toJson(),
],
}).toJson(),
),
const FakeVmServiceRequest(
method: 'reloadSources',
args: <String, Object>{
'isolateId': '1',
'pause': false,
'rootLibUri': 'main.dart.incremental.dill'
},
jsonResponse: <String, Object>{
'type': 'ReloadReport',
'success': true,
'details': <String, Object>{
'loadedLibraryCount': 1,
},
},
),
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'ext.flutter.reassemble',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
),
]);
final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> futureAppStart = Completer<void>.sync();
unawaited(residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
enableDevTools: true,
));
await futureAppStart.future;
final OperationResult result = await residentRunner.restart();
expect(result.fatal, false);
expect(result.code, 0);
final TestUsageEvent event = (globals.flutterUsage as TestUsage).events.first;
expect(event.category, 'hot');
expect(event.parameter, 'reload');
expect(event.parameters.hotEventTargetPlatform, getNameForTargetPlatform(TargetPlatform.android_arm));
}, overrides: <Type, Generator>{
Usage: () => TestUsage(),
}));
testUsingContext('ResidentRunner can perform fast reassemble', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: fakeVM.toJson(),
),
listViews,
listViews,
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: fakeVM.toJson(),
),
const FakeVmServiceRequest(
method: 'reloadSources',
args: <String, Object>{
'isolateId': '1',
'pause': false,
'rootLibUri': 'main.dart.incremental.dill',
},
jsonResponse: <String, Object>{
'type': 'ReloadReport',
'success': true,
'details': <String, Object>{
'loadedLibraryCount': 1,
},
},
),
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'ext.flutter.fastReassemble',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
'className': 'FOO',
},
),
]);
final FakeDelegateFlutterDevice flutterDevice = FakeDelegateFlutterDevice(
device,
BuildInfo.debug,
FakeResidentCompiler(),
devFS,
)..vmService = fakeVmServiceHost.vmService;
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
devFS.nextUpdateReport = UpdateFSReport(
success: true,
fastReassembleClassName: 'FOO',
invalidatedSourcesCount: 1,
);
final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> futureAppStart = Completer<void>.sync();
unawaited(residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
enableDevTools: true,
));
await futureAppStart.future;
final OperationResult result = await residentRunner.restart();
expect(result.fatal, false);
expect(result.code, 0);
final TestUsageEvent event = (globals.flutterUsage as TestUsage).events.first;
expect(event.category, 'hot');
expect(event.parameter, 'reload');
expect(event.parameters.fastReassemble, true);
}, overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
Platform: () => FakePlatform(),
ProjectFileInvalidator: () => FakeProjectFileInvalidator(),
Usage: () => TestUsage(),
FeatureFlags: () => TestFeatureFlags(isSingleWidgetReloadEnabled: true),
}));
testUsingContext('ResidentRunner can send target platform to analytics from full restart', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
),
listViews,
const FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{
'streamId': 'Isolate',
},
),
FakeVmServiceRequest(
method: kRunInViewMethod,
args: <String, Object>{
'viewId': fakeFlutterView.id,
'mainScript': 'main.dart.dill',
'assetDirectory': 'build/flutter_assets',
},
),
FakeVmServiceStreamResponse(
streamId: 'Isolate',
event: vm_service.Event(
timestamp: 0,
kind: vm_service.EventKind.kIsolateRunnable,
)
)
]);
final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> futureAppStart = Completer<void>.sync();
unawaited(residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
enableDevTools: true,
));
final OperationResult result = await residentRunner.restart(fullRestart: true);
expect(result.fatal, false);
expect(result.code, 0);
final TestUsageEvent event = (globals.flutterUsage as TestUsage).events.first;
expect(event.category, 'hot');
expect(event.parameter, 'restart');
expect(event.parameters.hotEventTargetPlatform, getNameForTargetPlatform(TargetPlatform.android_arm));
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
Usage: () => TestUsage(),
}));
testUsingContext('ResidentRunner can remove breakpoints from paused isolate during hot restart', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
jsonResponse: fakePausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
),
const FakeVmServiceRequest(
method: 'removeBreakpoint',
args: <String, String>{
'isolateId': '1',
'breakpointId': 'test-breakpoint',
}
),
const FakeVmServiceRequest(
method: 'resume',
args: <String, String>{
'isolateId': '1',
}
),
listViews,
const FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{
'streamId': 'Isolate',
},
),
FakeVmServiceRequest(
method: kRunInViewMethod,
args: <String, Object>{
'viewId': fakeFlutterView.id,
'mainScript': 'main.dart.dill',
'assetDirectory': 'build/flutter_assets',
},
),
FakeVmServiceStreamResponse(
streamId: 'Isolate',
event: vm_service.Event(
timestamp: 0,
kind: vm_service.EventKind.kIsolateRunnable,
)
)
]);
final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> futureAppStart = Completer<void>.sync();
unawaited(residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
enableDevTools: true,
));
final OperationResult result = await residentRunner.restart(fullRestart: true);
expect(result.isOk, true);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
testUsingContext('ResidentRunner will alternative the name of the dill file uploaded for a hot restart', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
),
listViews,
const FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{
'streamId': 'Isolate',
},
),
FakeVmServiceRequest(
method: kRunInViewMethod,
args: <String, Object>{
'viewId': fakeFlutterView.id,
'mainScript': 'main.dart.dill',
'assetDirectory': 'build/flutter_assets',
},
),
FakeVmServiceStreamResponse(
streamId: 'Isolate',
event: vm_service.Event(
timestamp: 0,
kind: vm_service.EventKind.kIsolateRunnable,
),
),
listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
),
listViews,
const FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{
'streamId': 'Isolate',
},
),
FakeVmServiceRequest(
method: kRunInViewMethod,
args: <String, Object>{
'viewId': fakeFlutterView.id,
'mainScript': 'main.dart.swap.dill',
'assetDirectory': 'build/flutter_assets',
},
),
FakeVmServiceStreamResponse(
streamId: 'Isolate',
event: vm_service.Event(
timestamp: 0,
kind: vm_service.EventKind.kIsolateRunnable,
),
),
listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
),
listViews,
const FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{
'streamId': 'Isolate',
},
),
FakeVmServiceRequest(
method: kRunInViewMethod,
args: <String, Object>{
'viewId': fakeFlutterView.id,
'mainScript': 'main.dart.dill',
'assetDirectory': 'build/flutter_assets',
},
),
FakeVmServiceStreamResponse(
streamId: 'Isolate',
event: vm_service.Event(
timestamp: 0,
kind: vm_service.EventKind.kIsolateRunnable,
),
)
]);
final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> futureAppStart = Completer<void>.sync();
unawaited(residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
enableDevTools: true,
));
await residentRunner.restart(fullRestart: true);
await residentRunner.restart(fullRestart: true);
await residentRunner.restart(fullRestart: true);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
testUsingContext('ResidentRunner Can handle an RPC exception from hot restart', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
]);
final Completer<DebugConnectionInfo> futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final Completer<void> futureAppStart = Completer<void>.sync();
unawaited(residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
enableDevTools: true,
));
await futureAppStart.future;
flutterDevice.reportError = vm_service.RPCError('something bad happened', 666, '');
final OperationResult result = await residentRunner.restart(fullRestart: true);
expect(result.fatal, true);
expect(result.code, 1);
expect((globals.flutterUsage as TestUsage).events, contains(
TestUsageEvent('hot', 'exception', parameters: CustomDimensions(
hotEventTargetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
hotEventSdkName: 'Android',
hotEventEmulator: false,
hotEventFullRestart: true,
fastReassemble: false,
)),
));
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
Usage: () => TestUsage(),
}));
testUsingContext('ResidentRunner uses temp directory when there is no output dill path', () => testbed.run(() {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
expect(residentRunner.artifactDirectory.path, contains('flutter_tool.'));
final ResidentRunner otherRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
dillOutputPath: globals.fs.path.join('foobar', 'app.dill'),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
expect(otherRunner.artifactDirectory.path, contains('foobar'));
}));
testUsingContext('ResidentRunner deletes artifact directory on preExit', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
residentRunner.artifactDirectory.childFile('app.dill').createSync();
await residentRunner.preExit();
expect(residentRunner.artifactDirectory, isNot(exists));
}));
testUsingContext('ResidentRunner can run source generation', () => testbed.run(() async {
final File arbFile = globals.fs.file(globals.fs.path.join('lib', 'l10n', 'app_en.arb'))
..createSync(recursive: true);
arbFile.writeAsStringSync('''
{
"helloWorld": "Hello, World!",
"@helloWorld": {
"description": "Sample description"
}
}''');
globals.fs.file('l10n.yaml').createSync();
globals.fs.file('pubspec.yaml').writeAsStringSync('flutter:\n generate: true\n');
// 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 from an empty generated_main.dart file.
globals.fs.directory('.dart_tool').childDirectory('flutter_build').childFile('generated_main.dart').createSync(recursive: true);
await residentRunner.runSourceGenerators();
expect(testLogger.errorText, isEmpty);
expect(testLogger.statusText, isEmpty);
}));
testUsingContext('generated main uses correct target', () => testbed.run(() async {
final File arbFile = globals.fs.file(globals.fs.path.join('lib', 'l10n', 'app_en.arb'))
..createSync(recursive: true);
arbFile.writeAsStringSync('''
{
"helloWorld": "Hello, World!",
"@helloWorld": {
"description": "Sample description"
}
}''');
globals.fs.file('l10n.yaml').createSync();
globals.fs.file('pubspec.yaml').writeAsStringSync('''
flutter:
generate: true
dependencies:
flutter:
sdk: flutter
path_provider_linux: 1.0.0
''');
// Create necessary files for [DartPluginRegistrantTarget], including a
// plugin that will trigger generation.
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"
}
]
}
''');
globals.fs.file('.packages').writeAsStringSync('''
path_provider_linux:/path_provider_linux/lib/
''');
final Directory fakePluginDir = globals.fs.directory('path_provider_linux');
final File pluginPubspec = fakePluginDir.childFile('pubspec.yaml');
pluginPubspec.createSync(recursive: true);
pluginPubspec.writeAsStringSync('''
name: path_provider_linux
flutter:
plugin:
implements: path_provider
platforms:
linux:
dartPluginClass: PathProviderLinux
''');
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'custom_main.dart',
devtoolsHandler: createNoOpHandler,
);
await residentRunner.runSourceGenerators();
final File generatedMain = globals.fs.directory('.dart_tool')
.childDirectory('flutter_build')
.childFile('generated_main.dart');
expect(generatedMain.readAsStringSync(), contains('custom_main.dart'));
expect(testLogger.errorText, isEmpty);
expect(testLogger.statusText, isEmpty);
}));
testUsingContext('ResidentRunner can run source generation - generation fails', () => testbed.run(() async {
// Intentionally define arb file with wrong name. generate_localizations defaults
// to app_en.arb.
final File arbFile = globals.fs.file(globals.fs.path.join('lib', 'l10n', 'foo.arb'))
..createSync(recursive: true);
arbFile.writeAsStringSync('''
{
"helloWorld": "Hello, World!",
"@helloWorld": {
"description": "Sample description"
}
}''');
globals.fs.file('l10n.yaml').createSync();
globals.fs.file('pubspec.yaml').writeAsStringSync('flutter:\n generate: true\n');
await residentRunner.runSourceGenerators();
expect(testLogger.errorText, allOf(contains('Exception')));
expect(testLogger.statusText, isEmpty);
}));
testUsingContext('ResidentRunner printHelpDetails hot runner', () => testbed.run(() {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
residentRunner.printHelp(details: true);
final CommandHelp commandHelp = residentRunner.commandHelp;
// supports service protocol
expect(residentRunner.supportsServiceProtocol, true);
// isRunningDebug
expect(residentRunner.isRunningDebug, true);
// does support SkSL
expect(residentRunner.supportsWriteSkSL, true);
// commands
expect(testLogger.statusText, equals(
<dynamic>[
'Flutter run key commands.',
commandHelp.r,
commandHelp.R,
commandHelp.v,
commandHelp.s,
commandHelp.w,
commandHelp.t,
commandHelp.L,
commandHelp.S,
commandHelp.U,
commandHelp.i,
commandHelp.p,
commandHelp.I,
commandHelp.o,
commandHelp.b,
commandHelp.P,
commandHelp.a,
commandHelp.M,
commandHelp.g,
commandHelp.hWithDetails,
commandHelp.c,
commandHelp.q,
'',
'💪 Running with sound null safety 💪',
'',
'An Observatory debugger and profiler on FakeDevice is available at: null',
'',
].join('\n')
));
}));
testUsingContext('ResidentRunner printHelp hot runner', () => testbed.run(() {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
residentRunner.printHelp(details: false);
final CommandHelp commandHelp = residentRunner.commandHelp;
// supports service protocol
expect(residentRunner.supportsServiceProtocol, true);
// isRunningDebug
expect(residentRunner.isRunningDebug, true);
// does support SkSL
expect(residentRunner.supportsWriteSkSL, true);
// commands
expect(testLogger.statusText, equals(
<dynamic>[
'Flutter run key commands.',
commandHelp.r,
commandHelp.R,
commandHelp.hWithoutDetails,
commandHelp.c,
commandHelp.q,
'',
'💪 Running with sound null safety 💪',
'',
'An Observatory debugger and profiler on FakeDevice is available at: null',
'',
].join('\n')
));
}));
testUsingContext('ResidentRunner printHelpDetails cold runner', () => testbed.run(() {
fakeVmServiceHost = null;
residentRunner = ColdRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
residentRunner.printHelp(details: true);
final CommandHelp commandHelp = residentRunner.commandHelp;
// does not supports service protocol
expect(residentRunner.supportsServiceProtocol, false);
// isRunningDebug
expect(residentRunner.isRunningDebug, false);
// does support SkSL
expect(residentRunner.supportsWriteSkSL, false);
// commands
expect(testLogger.statusText, equals(
<dynamic>[
'Flutter run key commands.',
commandHelp.v,
commandHelp.s,
commandHelp.hWithDetails,
commandHelp.c,
commandHelp.q,
''
].join('\n')
));
}));
testUsingContext('ResidentRunner printHelp cold runner', () => testbed.run(() {
fakeVmServiceHost = null;
residentRunner = ColdRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.release),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
residentRunner.printHelp(details: false);
final CommandHelp commandHelp = residentRunner.commandHelp;
// does not supports service protocol
expect(residentRunner.supportsServiceProtocol, false);
// isRunningDebug
expect(residentRunner.isRunningDebug, false);
// does support SkSL
expect(residentRunner.supportsWriteSkSL, false);
// commands
expect(testLogger.statusText, equals(
<dynamic>[
'Flutter run key commands.',
commandHelp.hWithoutDetails,
commandHelp.c,
commandHelp.q,
''
].join('\n')
));
}));
testUsingContext('ResidentRunner handles writeSkSL returning no data', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
FakeVmServiceRequest(
method: kGetSkSLsMethod,
args: <String, Object>{
'viewId': fakeFlutterView.id,
},
jsonResponse: <String, Object>{
'SkSLs': <String, Object>{}
}
),
]);
await residentRunner.writeSkSL();
expect(testLogger.statusText, contains('No data was received'));
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
testUsingContext('ResidentRunner can write SkSL data to a unique file with engine revision, platform, and device name', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
FakeVmServiceRequest(
method: kGetSkSLsMethod,
args: <String, Object>{
'viewId': fakeFlutterView.id,
},
jsonResponse: <String, Object>{
'SkSLs': <String, Object>{
'A': 'B',
}
}
)
]);
await residentRunner.writeSkSL();
expect(testLogger.statusText, contains('flutter_01.sksl.json'));
expect(globals.fs.file('flutter_01.sksl.json'), exists);
expect(json.decode(globals.fs.file('flutter_01.sksl.json').readAsStringSync()), <String, Object>{
'platform': 'android',
'name': 'FakeDevice',
'engineRevision': 'abcdefg',
'data': <String, Object>{'A': 'B'}
});
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
FileSystemUtils: () => FileSystemUtils(
fileSystem: globals.fs,
platform: globals.platform,
),
FlutterVersion: () => FakeFlutterVersion(engineRevision: 'abcdefg')
}));
testUsingContext('ResidentRunner ignores DevtoolsLauncher when attaching with enableDevTools: false - cold mode', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
]);
residentRunner = ColdRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.profile, vmserviceOutFile: 'foo'),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
final Future<int> result = residentRunner.attach();
expect(await result, 0);
}));
testUsingContext('FlutterDevice can exit from a release mode isolate with no VmService', () => testbed.run(() async {
final TestFlutterDevice flutterDevice = TestFlutterDevice(
device,
);
await flutterDevice.exitApps();
expect(device.appStopped, true);
}));
testUsingContext('FlutterDevice will exit an un-paused isolate using stopApp', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final TestFlutterDevice flutterDevice = TestFlutterDevice(
device,
);
flutterDevice.vmService = fakeVmServiceHost.vmService;
final Future<void> exitFuture = flutterDevice.exitApps();
await expectLater(exitFuture, completes);
expect(device.appStopped, true);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
testUsingContext('HotRunner writes vm service file when providing debugging option', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
], wsAddress: testUri);
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, vmserviceOutFile: 'foo'),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
await residentRunner.run(enableDevTools: true);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
expect(await globals.fs.file('foo').readAsString(), testUri.toString());
}));
testUsingContext('HotRunner copies compiled app.dill to cache during startup', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
], wsAddress: testUri);
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('ABC');
await residentRunner.run(enableDevTools: true);
expect(await globals.fs.file(globals.fs.path.join('build', 'cache.dill')).readAsString(), 'ABC');
}));
testUsingContext('HotRunner copies compiled app.dill to cache during startup with dart defines', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
], wsAddress: testUri);
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(
const BuildInfo(
BuildMode.debug,
'',
treeShakeIcons: false,
dartDefines: <String>['a', 'b'],
)
),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('ABC');
await residentRunner.run(enableDevTools: true);
expect(await globals.fs.file(globals.fs.path.join(
'build', '187ef4436122d1cc2f40dc2b92f0eba0.cache.dill')).readAsString(), 'ABC');
}));
testUsingContext('HotRunner copies compiled app.dill to cache during startup with null safety', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
], wsAddress: testUri);
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(
const BuildInfo(
BuildMode.debug,
'',
treeShakeIcons: false,
extraFrontEndOptions: <String>['--enable-experiment=non-nullable']
)
),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('ABC');
await residentRunner.run(enableDevTools: true);
expect(await globals.fs.file(globals.fs.path.join(
'build', 'cache.dill')).readAsString(), 'ABC');
}));
testUsingContext('HotRunner does not copy app.dill if a dillOutputPath is given', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
], wsAddress: testUri);
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
dillOutputPath: 'test',
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('ABC');
await residentRunner.run(enableDevTools: true);
expect(globals.fs.file(globals.fs.path.join('build', 'cache.dill')), isNot(exists));
}));
testUsingContext('HotRunner copies compiled app.dill to cache during startup with --track-widget-creation', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
], wsAddress: testUri);
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(const BuildInfo(
BuildMode.debug,
'',
treeShakeIcons: false,
trackWidgetCreation: true,
)),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('ABC');
await residentRunner.run(enableDevTools: true);
expect(await globals.fs.file(globals.fs.path.join('build', 'cache.dill.track.dill')).readAsString(), 'ABC');
}));
testUsingContext('HotRunner calls device dispose', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
], wsAddress: testUri);
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
await residentRunner.run();
expect(device.disposed, true);
}));
testUsingContext('HotRunner handles failure to write vmservice file', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
listViews,
]);
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, vmserviceOutFile: 'foo'),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
await residentRunner.run(enableDevTools: true);
expect(testLogger.errorText, contains('Failed to write vmservice-out-file at foo'));
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
FileSystem: () => ThrowingForwardingFileSystem(MemoryFileSystem.test()),
}));
testUsingContext('ColdRunner writes vm service file when providing debugging option', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
], wsAddress: testUri);
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
residentRunner = ColdRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.profile, vmserviceOutFile: 'foo'),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
await residentRunner.run(enableDevTools: true);
expect(await globals.fs.file('foo').readAsString(), testUri.toString());
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
testUsingContext('FlutterDevice uses dartdevc configuration when targeting web', () async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final FakeDevice device = FakeDevice(targetPlatform: TargetPlatform.web_javascript);
final DefaultResidentCompiler residentCompiler = (await FlutterDevice.create(
device,
buildInfo: const BuildInfo(
BuildMode.debug,
'',
treeShakeIcons: false,
nullSafetyMode: NullSafetyMode.unsound,
),
target: null,
platform: FakePlatform(),
)).generator as DefaultResidentCompiler;
expect(residentCompiler.initializeFromDill,
globals.fs.path.join(getBuildDirectory(), 'fbbe6a61fb7a1de317d381f8df4814e5.cache.dill'));
expect(residentCompiler.librariesSpec,
globals.fs.file(globals.artifacts.getHostArtifact(HostArtifact.flutterWebLibrariesJson))
.uri.toString());
expect(residentCompiler.targetModel, TargetModel.dartdevc);
expect(residentCompiler.sdkRoot,
'${globals.artifacts.getHostArtifact(HostArtifact.flutterWebSdk).path}/');
expect(residentCompiler.platformDill, 'file:///HostArtifact.webPlatformKernelDill');
}, overrides: <Type, Generator>{
Artifacts: () => Artifacts.test(),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('FlutterDevice uses dartdevc configuration when targeting web with null-safety autodetected', () async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final FakeDevice device = FakeDevice(targetPlatform: TargetPlatform.web_javascript);
final DefaultResidentCompiler residentCompiler = (await FlutterDevice.create(
device,
buildInfo: const BuildInfo(
BuildMode.debug,
'',
treeShakeIcons: false,
extraFrontEndOptions: <String>['--enable-experiment=non-nullable'],
),
target: null,
platform: FakePlatform(),
)).generator as DefaultResidentCompiler;
expect(residentCompiler.initializeFromDill,
globals.fs.path.join(getBuildDirectory(), '80b1a4cf4e7b90e1ab5f72022a0bc624.cache.dill'));
expect(residentCompiler.librariesSpec,
globals.fs.file(globals.artifacts.getHostArtifact(HostArtifact.flutterWebLibrariesJson))
.uri.toString());
expect(residentCompiler.targetModel, TargetModel.dartdevc);
expect(residentCompiler.sdkRoot,
'${globals.artifacts.getHostArtifact(HostArtifact.flutterWebSdk).path}/');
expect(residentCompiler.platformDill, 'file:///HostArtifact.webPlatformSoundKernelDill');
}, overrides: <Type, Generator>{
Artifacts: () => Artifacts.test(),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('FlutterDevice passes flutter-widget-cache flag when feature is enabled', () async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final FakeDevice device = FakeDevice();
final DefaultResidentCompiler residentCompiler = (await FlutterDevice.create(
device,
buildInfo: const BuildInfo(
BuildMode.debug,
'',
treeShakeIcons: false,
extraFrontEndOptions: <String>[],
),
target: null, platform: 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('FlutterDevice passes alternative-invalidation-strategy flag', () async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final FakeDevice device = FakeDevice();
final DefaultResidentCompiler residentCompiler = (await FlutterDevice.create(
device,
buildInfo: const BuildInfo(
BuildMode.debug,
'',
treeShakeIcons: false,
extraFrontEndOptions: <String>[],
),
target: null, platform: null,
)).generator as DefaultResidentCompiler;
expect(residentCompiler.extraFrontEndOptions,
contains('--enable-experiment=alternative-invalidation-strategy'));
}, overrides: <Type, Generator>{
Artifacts: () => Artifacts.test(),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('FlutterDevice passes initializeFromDill parameter if specified', () async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final FakeDevice device = FakeDevice();
final DefaultResidentCompiler residentCompiler = (await FlutterDevice.create(
device,
buildInfo: const BuildInfo(
BuildMode.debug,
'',
treeShakeIcons: false,
extraFrontEndOptions: <String>[],
initializeFromDill: '/foo/bar.dill',
),
target: null, platform: null,
)).generator as DefaultResidentCompiler;
expect(residentCompiler.initializeFromDill, '/foo/bar.dill');
}, overrides: <Type, Generator>{
Artifacts: () => Artifacts.test(),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('Handle existing VM service clients DDS error', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final FakeDevice device = FakeDevice()
..dds = DartDevelopmentService();
ddsLauncherCallback = (Uri uri, {bool enableAuthCodes, bool ipv6, Uri serviceUri}) {
expect(uri, Uri(scheme: 'foo', host: 'bar'));
expect(enableAuthCodes, isTrue);
expect(ipv6, isFalse);
expect(serviceUri, Uri(scheme: 'http', host: '127.0.0.1', port: 0));
throw FakeDartDevelopmentServiceException(message:
'Existing VM service clients prevent DDS from taking control.',
);
};
final TestFlutterDevice flutterDevice = TestFlutterDevice(
device,
observatoryUris: Stream<Uri>.value(testUri),
);
bool caught = false;
final Completer<void>done = Completer<void>();
runZonedGuarded(() {
flutterDevice.connect(allowExistingDdsInstance: true).then((_) => done.complete());
}, (Object e, StackTrace st) {
expect(e is ToolExit, true);
expect((e as ToolExit).message,
contains('Existing VM service clients prevent DDS from taking control.',
));
done.complete();
caught = true;
});
await done.future;
if (!caught) {
fail('Expected ToolExit to be thrown.');
}
}, overrides: <Type, Generator>{
VMServiceConnector: () => (Uri httpUri, {
ReloadSources reloadSources,
Restart restart,
CompileExpression compileExpression,
GetSkSLMethod getSkSLMethod,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
io.CompressionOptions compression,
Device device,
Logger logger,
}) async => FakeVmServiceHost(requests: <VmServiceExpectation>[]).vmService,
}));
testUsingContext('Host VM service ipv6 defaults', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final FakeDevice device = FakeDevice()
..dds = DartDevelopmentService();
final Completer<void>done = Completer<void>();
ddsLauncherCallback = (Uri uri, {bool enableAuthCodes, bool ipv6, Uri serviceUri}) async {
expect(uri, Uri(scheme: 'foo', host: 'bar'));
expect(enableAuthCodes, isFalse);
expect(ipv6, isTrue);
expect(serviceUri, Uri(scheme: 'http', host: '::1', port: 0));
done.complete();
return null;
};
final TestFlutterDevice flutterDevice = TestFlutterDevice(
device,
observatoryUris: Stream<Uri>.value(testUri),
);
await flutterDevice.connect(allowExistingDdsInstance: true, ipv6: true, disableServiceAuthCodes: true);
await done.future;
}, overrides: <Type, Generator>{
VMServiceConnector: () => (Uri httpUri, {
ReloadSources reloadSources,
Restart restart,
CompileExpression compileExpression,
GetSkSLMethod getSkSLMethod,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
io.CompressionOptions compression,
Device device,
Logger logger,
}) async => FakeVmServiceHost(requests: <VmServiceExpectation>[]).vmService,
}));
testUsingContext('Failed DDS start outputs error message', () => testbed.run(() async {
// See https://github.com/flutter/flutter/issues/72385 for context.
final FakeDevice device = FakeDevice()
..dds = DartDevelopmentService();
ddsLauncherCallback = (Uri uri, {bool enableAuthCodes, bool ipv6, Uri serviceUri}) {
expect(uri, Uri(scheme: 'foo', host: 'bar'));
expect(enableAuthCodes, isTrue);
expect(ipv6, isFalse);
expect(serviceUri, Uri(scheme: 'http', host: '127.0.0.1', port: 0));
throw FakeDartDevelopmentServiceException(message: 'No URI');
};
final TestFlutterDevice flutterDevice = TestFlutterDevice(
device,
observatoryUris: Stream<Uri>.value(testUri),
);
bool caught = false;
final Completer<void>done = Completer<void>();
runZonedGuarded(() {
flutterDevice.connect(allowExistingDdsInstance: true).then((_) => done.complete());
}, (Object e, StackTrace st) {
expect(e is StateError, true);
expect((e as StateError).message, contains('No URI'));
expect(testLogger.errorText, contains(
'DDS has failed to start and there is not an existing DDS instance',
));
done.complete();
caught = true;
});
await done.future;
if (!caught) {
fail('Expected a StateError to be thrown.');
}
}, overrides: <Type, Generator>{
VMServiceConnector: () => (Uri httpUri, {
ReloadSources reloadSources,
Restart restart,
CompileExpression compileExpression,
GetSkSLMethod getSkSLMethod,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
io.CompressionOptions compression,
Device device,
Logger logger,
}) async => FakeVmServiceHost(requests: <VmServiceExpectation>[]).vmService,
}));
testUsingContext('nextPlatform moves through expected platforms', () {
expect(nextPlatform('android'), 'iOS');
expect(nextPlatform('iOS'), 'fuchsia');
expect(nextPlatform('fuchsia'), 'macOS');
expect(nextPlatform('macOS'), 'android');
expect(() => nextPlatform('unknown'), throwsAssertionError);
});
testUsingContext('cleanupAtFinish shuts down resident devtools handler', () => testbed.run(() async {
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, vmserviceOutFile: 'foo'),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
await residentRunner.cleanupAtFinish();
expect((residentRunner.residentDevtoolsHandler as NoOpDevtoolsHandler).wasShutdown, true);
}));
testUsingContext('HotRunner sets asset directory when first evict assets', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
setAssetBundlePath,
evict,
]);
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
(flutterDevice.devFS as FakeDevFS).assetPathsToEvict = <String>{'asset'};
expect(flutterDevice.devFS.hasSetAssetDirectory, false);
await (residentRunner as HotRunner).evictDirtyAssets();
expect(flutterDevice.devFS.hasSetAssetDirectory, true);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
testUsingContext('HotRunner does not sets asset directory when no assets to evict', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
]);
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
expect(flutterDevice.devFS.hasSetAssetDirectory, false);
await (residentRunner as HotRunner).evictDirtyAssets();
expect(flutterDevice.devFS.hasSetAssetDirectory, false);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
testUsingContext('HotRunner does not set asset directory if it has been set before', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
evict,
]);
residentRunner = HotRunner(
<FlutterDevice>[
flutterDevice,
],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
(flutterDevice.devFS as FakeDevFS).assetPathsToEvict = <String>{'asset'};
flutterDevice.devFS.hasSetAssetDirectory = true;
await (residentRunner as HotRunner).evictDirtyAssets();
expect(flutterDevice.devFS.hasSetAssetDirectory, true);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
}
class FakeDartDevelopmentServiceException implements dds.DartDevelopmentServiceException {
FakeDartDevelopmentServiceException({this.message = defaultMessage});
@override
final int errorCode = dds.DartDevelopmentServiceException.existingDdsInstanceError;
@override
final String message;
static const String defaultMessage = 'A DDS instance is already connected at http://localhost:8181';
}
class TestFlutterDevice extends FlutterDevice {
TestFlutterDevice(Device device, { Stream<Uri> observatoryUris })
: super(device, buildInfo: BuildInfo.debug) {
_observatoryUris = observatoryUris;
}
@override
Stream<Uri> get observatoryUris => _observatoryUris;
Stream<Uri> _observatoryUris;
}
class ThrowingForwardingFileSystem extends ForwardingFileSystem {
ThrowingForwardingFileSystem(FileSystem delegate) : super(delegate);
@override
File file(dynamic path) {
if (path == 'foo') {
throw const FileSystemException();
}
return delegate.file(path);
}
}
class FakeFlutterDevice extends Fake implements FlutterDevice {
FakeVmServiceHost Function() vmServiceHost;
Uri testUri;
UpdateFSReport report = UpdateFSReport(
success: true,
invalidatedSourcesCount: 1,
);
Object reportError;
Object runColdError;
int runHotCode = 0;
int runColdCode = 0;
@override
ResidentCompiler generator;
@override
Stream<Uri> get observatoryUris => Stream<Uri>.value(testUri);
@override
FlutterVmService get vmService => vmServiceHost?.call()?.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<int> runHot({HotRunner hotRunner, String route}) async {
return runHotCode;
}
@override
Future<int> runCold({ColdRunner coldRunner, String route}) async {
if (runColdError != null) {
throw runColdError;
}
return runColdCode;
}
@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 { }
}
class FakeDelegateFlutterDevice extends FlutterDevice {
FakeDelegateFlutterDevice(
Device device,
BuildInfo buildInfo,
ResidentCompiler residentCompiler,
this.fakeDevFS,
) : super(device, buildInfo: buildInfo, generator: residentCompiler);
@override
Future<void> connect({
ReloadSources reloadSources,
Restart restart,
bool enableDds = true,
bool disableServiceAuthCodes = false,
bool ipv6 = false,
CompileExpression compileExpression,
GetSkSLMethod getSkSLMethod,
int hostVmServicePort,
int ddsPort,
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
bool allowExistingDdsInstance = false,
}) async { }
final DevFS fakeDevFS;
@override
DevFS get devFS => fakeDevFS;
@override
set devFS(DevFS value) {}
}
class FakeResidentCompiler extends Fake implements ResidentCompiler {
CompilerOutput nextOutput;
bool didSuppressErrors = false;
@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 {
didSuppressErrors = suppressErrors;
return nextOutput ?? const CompilerOutput('foo.dill', 0, <Uri>[]);
}
@override
void accept() { }
@override
void reset() { }
}
class FakeProjectFileInvalidator extends Fake implements ProjectFileInvalidator {
@override
Future<InvalidationResult> findInvalidated({
@required DateTime lastCompiled,
@required List<Uri> urisToMonitor,
@required String packagesPath,
@required PackageConfig packageConfig,
bool asyncScanning = false,
}) async {
return InvalidationResult(
packageConfig: packageConfig ?? PackageConfig.empty,
uris: <Uri>[Uri.parse('file:///hello_world/main.dart'),
]);
}
}
// 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 {
FakeDevice({
String sdkNameAndVersion = 'Android',
TargetPlatform targetPlatform = TargetPlatform.android_arm,
bool isLocalEmulator = false,
this.supportsHotRestart = true,
this.supportsScreenshot = true,
this.supportsFlutterExit = true,
}) : _isLocalEmulator = isLocalEmulator,
_targetPlatform = targetPlatform,
_sdkNameAndVersion = sdkNameAndVersion;
final bool _isLocalEmulator;
final TargetPlatform _targetPlatform;
final String _sdkNameAndVersion;
bool disposed = false;
bool appStopped = false;
bool failScreenshot = false;
@override
bool supportsHotRestart;
@override
bool supportsScreenshot;
@override
bool supportsFlutterExit;
@override
PlatformType get platformType => _targetPlatform == TargetPlatform.web_javascript
? PlatformType.web
: PlatformType.android;
@override
Future<String> get sdkNameAndVersion async => _sdkNameAndVersion;
@override
Future<TargetPlatform> get targetPlatform async => _targetPlatform;
@override
Future<bool> get isLocalEmulator async => _isLocalEmulator;
@override
String get name => 'FakeDevice';
@override
DartDevelopmentService dds;
@override
Future<void> dispose() async {
disposed = true;
}
@override
Future<bool> stopApp(covariant ApplicationPackage app, {String userIdentifier}) async {
appStopped = true;
return true;
}
@override
Future<void> takeScreenshot(File outputFile) async {
if (failScreenshot) {
throw Exception();
}
outputFile.writeAsBytesSync(List<int>.generate(1024, (int i) => i));
}
@override
FutureOr<DeviceLogReader> getLogReader({
covariant ApplicationPackage app,
bool includePastLogs = false,
}) => NoOpDeviceLogReader(name);
@override
DevicePortForwarder portForwarder = const NoOpDevicePortForwarder();
}
class FakeDevFS extends Fake implements DevFS {
@override
DateTime lastCompiled = DateTime(2000);
@override
PackageConfig lastPackageConfig = PackageConfig.empty;
@override
List<Uri> sources = <Uri>[];
@override
Uri baseUri = Uri();
@override
Future<void> destroy() async { }
@override
Set<String> assetPathsToEvict = <String>{};
UpdateFSReport nextUpdateReport = UpdateFSReport(success: true);
@override
bool hasSetAssetDirectory = false;
@override
Future<Uri> create() async {
return Uri();
}
@override
void resetLastCompiled() {
lastCompiled = null;
}
@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 {
return nextUpdateReport;
}
}