blob: 14f15128cab46979b6f057788e0cc4e4f06b344c [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.
import 'dart:async';
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/command_help.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/dart/pub.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.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/vmservice.dart';
import 'package:unified_analytics/unified_analytics.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/package_config.dart';
import '../src/testbed.dart';
import '../src/throwing_pub.dart';
import 'resident_runner_helpers.dart';
FakeAnalytics get fakeAnalytics => globals.analytics as FakeAnalytics;
void main() {
late TestBed testbed;
late FakeFlutterDevice flutterDevice;
late FakeDevFS devFS;
late ResidentRunner residentRunner;
late FakeDevice device;
FakeVmServiceHost? fakeVmServiceHost;
setUp(() {
testbed = TestBed(
setup: () {
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',
analytics: fakeAnalytics,
);
},
overrides: <Type, Generator>{
Analytics: () => getInitializedFakeAnalyticsInstance(
fakeFlutterVersion: FakeFlutterVersion(),
fs: MemoryFileSystem.test(),
),
},
);
device = FakeDevice();
devFS = FakeDevFS();
flutterDevice = FakeFlutterDevice()
..testUri = testUri
..vmServiceHost = (() => fakeVmServiceHost)
..device = device
..fakeDevFS = devFS;
});
testUsingContext(
'ResidentRunner can attach to device successfully',
() => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[listViews, listViews]);
final futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final futureAppStart = Completer<void>.sync();
final Future<int?> result = residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
);
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 reports whether detach() was used',
() => testbed.run(() async {
expect(residentRunner.stopAppDuringCleanup, true);
await residentRunner.detach();
expect(residentRunner.stopAppDuringCleanup, 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 residentCompiler = FakeResidentCompiler()
..nextOutput = const CompilerOutput('foo', 0, <Uri>[]);
residentRunner = HotRunner(
<FlutterDevice>[flutterDevice],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
analytics: globals.analytics,
);
flutterDevice.generator = residentCompiler;
expect(await residentRunner.run(), 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 residentCompiler = FakeResidentCompiler()
..nextOutput = const CompilerOutput('foo', 1, <Uri>[]);
residentRunner = HotRunner(
<FlutterDevice>[flutterDevice],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
analytics: globals.analytics,
);
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',
);
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',
);
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 residentCompiler = FakeResidentCompiler()
..nextOutput = const CompilerOutput('foo', 0, <Uri>[]);
residentRunner = HotRunner(
<FlutterDevice>[flutterDevice],
applicationBinary: globals.fs.file('app-debug.apk'),
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
analytics: globals.analytics,
);
flutterDevice.generator = residentCompiler;
expect(await residentRunner.run(), 0);
expect(residentCompiler.didSuppressErrors, false);
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 futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final futureAppStart = Completer<void>.sync();
unawaited(
residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
),
);
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.analytics as FakeAnalytics).sentEvents,
contains(
Event.hotRunnerInfo(
label: 'exception',
targetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
sdkName: 'Android',
emulator: false,
fullRestart: 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 futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final futureAppStart = Completer<void>.sync();
unawaited(
residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
),
);
await futureAppStart.future;
flutterDevice.fakeDevFS = 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 futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final futureAppStart = Completer<void>.sync();
unawaited(
residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
),
);
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(
fakeAnalytics.sentEvents,
contains(
Event.hotRunnerInfo(
label: 'reload-barred',
targetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
sdkName: 'Android',
emulator: false,
fullRestart: 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'],
packageConfigPath: '.dart_tool/package_config.json',
),
enableDevTools: false,
),
analytics: fakeAnalytics,
);
final futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final futureAppStart = Completer<void>.sync();
unawaited(
residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
),
);
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(
fakeAnalytics.sentEvents,
contains(
Event.hotRunnerInfo(
label: 'exception',
targetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
sdkName: 'Android',
emulator: false,
fullRestart: 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: 'getIsolatePauseEvent',
args: <String, Object>{'isolateId': '1'},
jsonResponse: fakeUnpausedEvent.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',
analytics: fakeAnalytics,
);
final futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final futureAppStart = Completer<void>.sync();
unawaited(
residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
),
);
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: kReloadSourcesServiceName,
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: 'getIsolatePauseEvent',
args: <String, Object>{'isolateId': '1'},
jsonResponse: fakeUnpausedEvent.toJson(),
),
FakeVmServiceRequest(
method: 'ext.flutter.reassemble',
args: <String, Object?>{'isolateId': fakeUnpausedIsolate.id},
),
],
);
final futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final futureAppStart = Completer<void>.sync();
unawaited(
residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
),
);
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: kReloadSourcesServiceName,
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 futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final futureAppStart = Completer<void>.sync();
unawaited(
residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
),
);
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: kReloadSourcesServiceName,
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: 'getIsolatePauseEvent',
args: <String, Object>{'isolateId': '1'},
jsonResponse: fakeUnpausedEvent.toJson(),
),
FakeVmServiceRequest(
method: 'ext.flutter.reassemble',
args: <String, Object?>{'isolateId': fakeUnpausedIsolate.id},
),
],
);
final futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final futureAppStart = Completer<void>.sync();
unawaited(
residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
),
);
await futureAppStart.future;
final OperationResult result = await residentRunner.restart();
expect(result.fatal, false);
expect(result.code, 0);
final Event event = fakeAnalytics.sentEvents.first;
expect(event.eventName.label, 'hot_runner_info');
expect(event.eventData['label'], 'reload');
expect(
event.eventData['targetPlatform'],
getNameForTargetPlatform(TargetPlatform.android_arm),
);
}, overrides: <Type, Generator>{Usage: () => TestUsage()}),
);
testUsingContext(
'ResidentRunner reports hot reload time details',
() => testbed.run(
() async {
fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[
listViews,
listViews,
listViews,
FakeVmServiceRequest(method: 'getVM', jsonResponse: fakeVM.toJson()),
const FakeVmServiceRequest(
method: kReloadSourcesServiceName,
args: <String, Object>{
'isolateId': '1',
'pause': false,
'rootLibUri': 'main.dart.incremental.dill',
},
jsonResponse: <String, Object>{
'type': 'ReloadReport',
'success': true,
'details': <String, Object>{'loadedLibraryCount': 1, 'finalLibraryCount': 42},
},
),
FakeVmServiceRequest(
method: 'getIsolatePauseEvent',
args: <String, Object>{'isolateId': '1'},
jsonResponse: fakeUnpausedEvent.toJson(),
),
FakeVmServiceRequest(
method: 'ext.flutter.reassemble',
args: <String, Object?>{'isolateId': fakeUnpausedIsolate.id},
),
],
);
final flutterDevice = FakeDelegateFlutterDevice(
device,
BuildInfo.debug,
FakeResidentCompiler(),
devFS,
)..vmService = fakeVmServiceHost!.vmService;
residentRunner = HotRunner(
<FlutterDevice>[flutterDevice],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
analytics: fakeAnalytics,
);
devFS.nextUpdateReport = UpdateFSReport(success: true, invalidatedSourcesCount: 1);
final futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final futureAppStart = Completer<void>.sync();
unawaited(
residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
),
);
await futureAppStart.future;
await residentRunner.restart();
// The actual test: Expect to have compile, reload and reassemble times.
expect(
testLogger.statusText,
contains(
RegExp(
r'Reloaded 1 of 42 libraries in \d+ms '
r'\(compile: \d+ ms, reload: \d+ ms, reassemble: \d+ ms\)\.',
),
),
);
},
overrides: <Type, Generator>{
FileSystem: () => MemoryFileSystem.test(),
Platform: () => FakePlatform(),
ProjectFileInvalidator: () => FakeProjectFileInvalidator(),
Usage: () => TestUsage(),
},
),
);
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: 'setIsolatePauseMode',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'exceptionPauseMode': vm_service.ExceptionPauseMode.kNone,
},
jsonResponse: vm_service.Success().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 futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final futureAppStart = Completer<void>.sync();
unawaited(
residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
),
);
final OperationResult result = await residentRunner.restart(fullRestart: true);
expect(result.fatal, false);
expect(result.code, 0);
expect(fakeVmServiceHost?.hasRemainingExpectations, false);
final List<Event> hotRunnerInfoEvents = fakeAnalytics.sentEvents
.where((Event e) => e.eventName.label == 'hot_runner_info')
.toList();
expect(hotRunnerInfoEvents, hasLength(1));
final Event newEvent = hotRunnerInfoEvents.first;
expect(newEvent.eventData['label'], 'restart');
expect(
newEvent.eventData['targetPlatform'],
getNameForTargetPlatform(TargetPlatform.android_arm),
);
}, overrides: <Type, Generator>{Usage: () => TestUsage()}),
);
testUsingContext(
'ResidentRunner can remove breakpoints and exception-pause-mode 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: 'setIsolatePauseMode',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'exceptionPauseMode': vm_service.ExceptionPauseMode.kNone,
},
jsonResponse: vm_service.Success().toJson(),
),
FakeVmServiceRequest(
method: 'removeBreakpoint',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'breakpointId': 'test-breakpoint',
},
),
FakeVmServiceRequest(
method: 'resume',
args: <String, Object?>{'isolateId': fakeUnpausedIsolate.id},
),
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 futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final futureAppStart = Completer<void>.sync();
unawaited(
residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
),
);
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: 'setIsolatePauseMode',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'exceptionPauseMode': vm_service.ExceptionPauseMode.kNone,
},
jsonResponse: vm_service.Success().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: 'setIsolatePauseMode',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'exceptionPauseMode': vm_service.ExceptionPauseMode.kNone,
},
jsonResponse: vm_service.Success().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: 'setIsolatePauseMode',
args: <String, Object?>{
'isolateId': fakeUnpausedIsolate.id,
'exceptionPauseMode': vm_service.ExceptionPauseMode.kNone,
},
jsonResponse: vm_service.Success().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 futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final futureAppStart = Completer<void>.sync();
unawaited(
residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
),
);
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 futureConnectionInfo = Completer<DebugConnectionInfo>.sync();
final futureAppStart = Completer<void>.sync();
unawaited(
residentRunner.attach(
appStartedCompleter: futureAppStart,
connectionInfoCompleter: futureConnectionInfo,
),
);
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(
fakeAnalytics.sentEvents,
contains(
Event.hotRunnerInfo(
label: 'exception',
targetPlatform: getNameForTargetPlatform(TargetPlatform.android_arm),
sdkName: 'Android',
emulator: false,
fullRestart: true,
),
),
);
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',
analytics: fakeAnalytics,
);
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('''
name: my_app
flutter:
generate: true''');
// Create necessary files for [DartPluginRegistrantTarget]
writePackageConfigFiles(
directory: globals.fs.currentDirectory,
mainLibName: 'my_app',
packages: <String, String>{'path_provider_linux': 'path_provider_linux'},
);
// Start from an empty dart_plugin_registrant.dart file.
globals.fs
.directory('.dart_tool')
.childDirectory('flutter_build')
.childFile('dart_plugin_registrant.dart')
.createSync(recursive: true);
await residentRunner.runSourceGenerators();
expect(testLogger.errorText, isEmpty);
expect(testLogger.statusText, isEmpty);
}, overrides: <Type, Generator>{Pub: ThrowingPub.new}),
);
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('''
name: my_app
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.
writePackageConfigFiles(
directory: globals.fs.currentDirectory,
mainLibName: 'my_app',
packages: <String, String>{'path_provider_linux': 'path_provider_linux'},
);
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',
analytics: fakeAnalytics,
);
await residentRunner.runSourceGenerators();
final File generatedMain = globals.fs
.directory('.dart_tool')
.childDirectory('flutter_build')
.childFile('dart_plugin_registrant.dart');
expect(generatedMain.existsSync(), isTrue);
expect(testLogger.errorText, isEmpty);
expect(testLogger.statusText, isEmpty);
}, overrides: <Type, Generator>{Pub: ThrowingPub.new}),
);
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, contains('Error'));
expect(testLogger.statusText, isEmpty);
}),
);
testUsingContext(
'ResidentRunner generates files when l10n.yaml exists',
() => testbed.run(() async {
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
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');
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final residentCompiler = FakeResidentCompiler()
..nextOutput = const CompilerOutput('foo', 1, <Uri>[]);
residentRunner = HotRunner(
<FlutterDevice>[flutterDevice],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
analytics: fakeAnalytics,
);
flutterDevice.generator = residentCompiler;
await residentRunner.run();
final File generatedLocalizationsFile = globals.fs
.directory('lib')
.childDirectory('l10n')
.childFile('app_localizations.dart');
expect(generatedLocalizationsFile, exists);
// Completing this future ensures that the daemon can exit correctly.
expect(await residentRunner.waitForAppToFinish(), 1);
}),
);
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);
// commands
expect(
testLogger.statusText,
equals(
<dynamic>[
'Flutter run key commands.',
commandHelp.r,
commandHelp.R,
commandHelp.s,
commandHelp.v,
commandHelp.w,
commandHelp.t,
commandHelp.L,
commandHelp.f,
commandHelp.S,
commandHelp.U,
commandHelp.i,
commandHelp.p,
commandHelp.I,
commandHelp.o,
commandHelp.b,
commandHelp.P,
commandHelp.a,
commandHelp.g,
commandHelp.hWithDetails,
commandHelp.d,
commandHelp.c,
commandHelp.q,
'',
'A Dart VM Service 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);
// commands
expect(
testLogger.statusText,
equals(
<dynamic>[
'Flutter run key commands.',
commandHelp.r,
commandHelp.R,
commandHelp.hWithoutDetails,
commandHelp.d,
commandHelp.c,
commandHelp.q,
'',
'A Dart VM Service 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',
);
residentRunner.printHelp(details: true);
final CommandHelp commandHelp = residentRunner.commandHelp;
// does not supports service protocol
expect(residentRunner.supportsServiceProtocol, false);
// isRunningDebug
expect(residentRunner.isRunningDebug, false);
// commands
expect(
testLogger.statusText,
equals(
<dynamic>[
'Flutter run key commands.',
commandHelp.s,
commandHelp.hWithDetails,
commandHelp.c,
commandHelp.q,
'\n',
].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',
);
residentRunner.printHelp(details: false);
final CommandHelp commandHelp = residentRunner.commandHelp;
// does not supports service protocol
expect(residentRunner.supportsServiceProtocol, false);
// isRunningDebug
expect(residentRunner.isRunningDebug, false);
// commands
expect(
testLogger.statusText,
equals(
<dynamic>[
'Flutter run key commands.',
commandHelp.hWithoutDetails,
commandHelp.c,
commandHelp.q,
'\n',
].join('\n'),
),
);
}),
);
testUsingContext(
'ResidentRunner printHelpDetails hides v on web in profile mode',
() => testbed.run(() async {
final FlutterDevice flutterDevice = await FlutterDevice.create(
FakeDevice(targetPlatform: TargetPlatform.web_javascript),
target: 'lib/main.dart',
buildInfo: BuildInfo.profile,
platform: FakePlatform(),
);
final ResidentRunner residentRunner = HotRunner(
<FlutterDevice>[flutterDevice],
debuggingOptions: DebuggingOptions.disabled(BuildInfo.profile),
target: 'lib/main.dart',
analytics: fakeAnalytics,
);
residentRunner.printHelp(details: true);
final CommandHelp commandHelp = residentRunner.commandHelp;
expect(residentRunner.isRunningDebug, false);
expect(testLogger.statusText, isNot(contains(commandHelp.v)));
}),
);
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',
enableDevTools: false,
),
target: 'main.dart',
);
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 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 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',
analytics: fakeAnalytics,
);
await residentRunner.run();
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(
const BuildInfo(
BuildMode.debug,
null,
treeShakeIcons: false,
packageConfigPath: '.dart_tool/package_config.json',
),
),
target: 'main.dart',
analytics: fakeAnalytics,
);
residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('ABC');
await residentRunner.run();
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'],
packageConfigPath: '.dart_tool/package_config.json',
),
),
target: 'main.dart',
analytics: fakeAnalytics,
);
residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('ABC');
await residentRunner.run();
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'],
packageConfigPath: '.dart_tool/package_config.json',
),
),
target: 'main.dart',
analytics: fakeAnalytics,
);
residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('ABC');
await residentRunner.run();
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 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(BuildInfo.debug),
target: 'main.dart',
analytics: fakeAnalytics,
);
residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('ABC');
await residentRunner.run();
expect(
await globals.fs
.file(globals.fs.path.join('build', 'cache.dill.track.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',
analytics: fakeAnalytics,
);
residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('ABC');
await residentRunner.run();
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,
packageConfigPath: '.dart_tool/package_config.json',
),
),
target: 'main.dart',
analytics: fakeAnalytics,
);
residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('ABC');
await residentRunner.run();
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',
analytics: fakeAnalytics,
);
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',
analytics: fakeAnalytics,
);
await residentRunner.run();
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',
);
await residentRunner.run();
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 device = FakeDevice(targetPlatform: TargetPlatform.web_javascript);
final residentCompiler =
(await FlutterDevice.create(
device,
buildInfo: const BuildInfo(
BuildMode.debug,
'',
treeShakeIcons: false,
packageConfigPath: '.dart_tool/package_config.json',
),
target: null,
platform: FakePlatform(),
)).generator
as DefaultResidentCompiler?;
expect(
residentCompiler!.initializeFromDill,
globals.fs.path.join(getBuildDirectory(), '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.webPlatformKernelFolder/ddc_outline.dill',
);
},
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 device = FakeDevice(targetPlatform: TargetPlatform.web_javascript);
final residentCompiler =
(await FlutterDevice.create(
device,
buildInfo: const BuildInfo(
BuildMode.debug,
'',
treeShakeIcons: false,
extraFrontEndOptions: <String>['--enable-experiment=non-nullable'],
packageConfigPath: '.dart_tool/package_config.json',
),
target: null,
platform: FakePlatform(),
)).generator
as DefaultResidentCompiler?;
expect(
residentCompiler!.initializeFromDill,
globals.fs.path.join(getBuildDirectory(), '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.webPlatformKernelFolder/ddc_outline.dill',
);
},
overrides: <Type, Generator>{
Artifacts: () => Artifacts.test(),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
},
);
testUsingContext(
'FlutterDevice passes alternative-invalidation-strategy flag',
() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final device = FakeDevice();
final residentCompiler =
(await FlutterDevice.create(
device,
buildInfo: const BuildInfo(
BuildMode.debug,
'',
treeShakeIcons: false,
extraFrontEndOptions: <String>[],
packageConfigPath: '.dart_tool/package_config.json',
),
target: null,
platform: FakePlatform(),
)).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 device = FakeDevice();
final residentCompiler =
(await FlutterDevice.create(
device,
buildInfo: const BuildInfo(
BuildMode.debug,
'',
treeShakeIcons: false,
extraFrontEndOptions: <String>[],
initializeFromDill: '/foo/bar.dill',
packageConfigPath: '.dart_tool/package_config.json',
),
target: null,
platform: FakePlatform(),
)).generator
as DefaultResidentCompiler?;
expect(residentCompiler!.initializeFromDill, '/foo/bar.dill');
expect(residentCompiler.assumeInitializeFromDillUpToDate, false);
},
overrides: <Type, Generator>{
Artifacts: () => Artifacts.test(),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
},
);
testUsingContext(
'FlutterDevice passes assumeInitializeFromDillUpToDate parameter if specified',
() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final device = FakeDevice();
final residentCompiler =
(await FlutterDevice.create(
device,
buildInfo: const BuildInfo(
BuildMode.debug,
'',
treeShakeIcons: false,
extraFrontEndOptions: <String>[],
assumeInitializeFromDillUpToDate: true,
packageConfigPath: '.dart_tool/package_config.json',
),
target: null,
platform: FakePlatform(),
)).generator
as DefaultResidentCompiler?;
expect(residentCompiler!.assumeInitializeFromDillUpToDate, true);
},
overrides: <Type, Generator>{
Artifacts: () => Artifacts.test(),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
},
);
testUsingContext(
'FlutterDevice passes frontendServerStarterPath parameter if specified',
() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final device = FakeDevice();
final residentCompiler =
(await FlutterDevice.create(
device,
buildInfo: const BuildInfo(
BuildMode.debug,
'',
treeShakeIcons: false,
frontendServerStarterPath: '/foo/bar/frontend_server_starter.dart',
packageConfigPath: '.dart_tool/package_config.json',
),
target: null,
platform: FakePlatform(),
)).generator
as DefaultResidentCompiler?;
expect(residentCompiler!.frontendServerStarterPath, '/foo/bar/frontend_server_starter.dart');
},
overrides: <Type, Generator>{
Artifacts: () => Artifacts.test(),
FileSystem: () => MemoryFileSystem.test(),
ProcessManager: () => FakeProcessManager.any(),
},
);
testUsingContext(
'Uses existing DDS URI from exception field',
() => testbed.run(
() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
final device = FakeDevice()..dds = DartDevelopmentService(logger: testLogger);
ddsLauncherCallback =
({
required Uri remoteVmServiceUri,
Uri? serviceUri,
bool enableAuthCodes = true,
bool serveDevTools = false,
Uri? devToolsServerAddress,
bool enableServicePortFallback = false,
List<String> cachedUserTags = const <String>[],
String? dartExecutable,
String? google3WorkspaceRoot,
}) {
throw DartDevelopmentServiceException.existingDdsInstance(
'Existing DDS at http://localhost/existingDdsInMessage.',
ddsUri: Uri.parse('http://localhost/existingDdsInField'),
);
};
final flutterDevice = TestFlutterDevice(device, vmServiceUris: Stream<Uri>.value(testUri));
final done = Completer<void>();
unawaited(
runZonedGuarded(
() => flutterDevice
.connect(debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug))
.then((_) => done.complete()),
(_, _) => done.complete(),
),
);
await done.future;
expect(device.dds.uri, Uri.parse('http://localhost/existingDdsInField'));
},
overrides: <Type, Generator>{
VMServiceConnector: () =>
(
Uri httpUri, {
ReloadSources? reloadSources,
Restart? restart,
CompileExpression? compileExpression,
FlutterProject? flutterProject,
PrintStructuredErrorLogMethod? printStructuredErrorLogMethod,
io.CompressionOptions? compression,
Device? device,
required Logger logger,
}) async => FakeVmServiceHost(requests: <VmServiceExpectation>[]).vmService,
},
),
);
testUsingContext(
'Host VM service ipv6 defaults',
() => testbed.run(
() async {
fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[
const FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{'streamId': 'Isolate'},
),
const FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{'streamId': 'Isolate'},
),
const FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{'streamId': 'Isolate'},
),
],
);
final device = FakeDevice()..dds = DartDevelopmentService(logger: testLogger);
final done = Completer<void>();
ddsLauncherCallback =
({
required Uri remoteVmServiceUri,
Uri? serviceUri,
bool enableAuthCodes = true,
bool serveDevTools = false,
Uri? devToolsServerAddress,
bool enableServicePortFallback = false,
List<String> cachedUserTags = const <String>[],
String? dartExecutable,
String? google3WorkspaceRoot,
}) async {
expect(remoteVmServiceUri, Uri(scheme: 'foo', host: 'bar'));
expect(enableAuthCodes, isFalse);
expect(serviceUri, Uri(scheme: 'http', host: '::1', port: 0));
expect(cachedUserTags, isEmpty);
done.complete();
return FakeDartDevelopmentServiceLauncher(uri: remoteVmServiceUri);
};
final flutterDevice = TestFlutterDevice(device, vmServiceUris: Stream<Uri>.value(testUri));
await flutterDevice.connect(
debuggingOptions: DebuggingOptions.enabled(
BuildInfo.debug,
disableServiceAuthCodes: true,
ipv6: true,
),
);
await done.future;
},
overrides: <Type, Generator>{
VMServiceConnector: () =>
(
Uri httpUri, {
ReloadSources? reloadSources,
Restart? restart,
CompileExpression? compileExpression,
FlutterProject? flutterProject,
PrintStructuredErrorLogMethod? printStructuredErrorLogMethod,
io.CompressionOptions? compression,
Device? device,
required Logger logger,
}) async => fakeVmServiceHost!.vmService,
},
),
);
testUsingContext('nextPlatform moves through expected platforms', () {
expect(nextPlatform('android'), 'iOS');
expect(nextPlatform('iOS'), 'windows');
expect(nextPlatform('windows'), 'macOS');
expect(nextPlatform('macOS'), 'linux');
expect(nextPlatform('linux'), 'fuchsia');
expect(nextPlatform('fuchsia'), 'android');
expect(() => nextPlatform('unknown'), throwsAssertionError);
});
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',
analytics: fakeAnalytics,
);
(flutterDevice.devFS! as FakeDevFS).assetPathsToEvict = <String>{'asset'};
expect(flutterDevice.devFS!.hasSetAssetDirectory, isFalse);
await (residentRunner as HotRunner).evictDirtyAssets();
expect(flutterDevice.devFS!.hasSetAssetDirectory, isTrue);
expect(fakeVmServiceHost!.hasRemainingExpectations, isFalse);
}),
);
testUsingContext(
'HotRunner sets asset directory when first evict shaders',
() => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[listViews, setAssetBundlePath, evictShader],
);
residentRunner = HotRunner(
<FlutterDevice>[flutterDevice],
stayResident: false,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
analytics: fakeAnalytics,
);
(flutterDevice.devFS! as FakeDevFS).shaderPathsToEvict = <String>{'foo.frag'};
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',
analytics: fakeAnalytics,
);
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',
analytics: fakeAnalytics,
);
(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);
}),
);
testUsingContext(
'use the nativeAssetsYamlFile when provided',
() => testbed.run(() async {
final device = FakeDevice(targetPlatform: TargetPlatform.darwin, sdkNameAndVersion: 'Macos');
final residentCompiler = FakeResidentCompiler();
final flutterDevice = FakeFlutterDevice()
..testUri = testUri
..vmServiceHost = (() => fakeVmServiceHost)
..device = device
..fakeDevFS = devFS
..targetPlatform = TargetPlatform.darwin
..generator = residentCompiler;
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(
const BuildInfo(
BuildMode.debug,
'',
treeShakeIcons: false,
trackWidgetCreation: true,
packageConfigPath: '.dart_tool/package_config.json',
),
),
target: 'main.dart',
analytics: globals.analytics,
nativeAssetsYamlFile: 'foo.yaml',
);
final int? result = await residentRunner.run();
expect(result, 0);
expect(residentCompiler.recompileCalled, true);
expect(residentCompiler.receivedNativeAssetsYaml, globals.fs.path.toUri('foo.yaml'));
}),
overrides: <Type, Generator>{
ProcessManager: () => FakeProcessManager.any(),
FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true, isMacOSEnabled: true),
},
);
}