flutter/packages/flutter_tools/test/general.shard/hot_test.dart
Elias Yishak 2d60241d61
[Reland] Migration for the sendTiming events for package:unified_analytics (#139299)
Relanding based on this comment:
- https://github.com/flutter/flutter/pull/139278#issuecomment-1832951108

Related to tracker issue:
- https://github.com/flutter/flutter/issues/128251

<img width="278" alt="image" src="https://github.com/flutter/flutter/assets/42216813/cee7b9be-48d6-48e5-8c39-de28d0a1f0de">

The image above shows all of the instances where we have `sendTiming`. All of the call sites have been updated to use the new `Event.timing` event from `package:unified_analytics`.
2023-12-04 21:39:18 +00:00

1081 lines
36 KiB
Dart

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:file/memory.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/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/build_system/targets/shader_compiler.dart';
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/devfs.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:flutter_tools/src/resident_devtools_handler.dart';
import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/run_hot.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:native_assets_cli/native_assets_cli.dart'
hide BuildMode, Target;
import 'package:native_assets_cli/native_assets_cli.dart' as native_assets_cli;
import 'package:package_config/package_config.dart';
import 'package:test/fake.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/fakes.dart';
import 'fake_native_assets_build_runner.dart';
void main() {
group('validateReloadReport', () {
testUsingContext('invalid', () async {
expect(
HotRunner.validateReloadReport(
vm_service.ReloadReport.parse(<String, dynamic>{
'type': 'ReloadReport',
'success': false,
'details': <String, dynamic>{},
})),
false);
expect(
HotRunner.validateReloadReport(
vm_service.ReloadReport.parse(<String, dynamic>{
'type': 'ReloadReport',
'success': false,
'details': <String, dynamic>{
'notices': <Map<String, dynamic>>[],
},
})),
false);
expect(
HotRunner.validateReloadReport(
vm_service.ReloadReport.parse(<String, dynamic>{
'type': 'ReloadReport',
'success': false,
'details': <String, dynamic>{
'notices': <String, dynamic>{
'message': 'error',
},
},
})),
false);
expect(
HotRunner.validateReloadReport(
vm_service.ReloadReport.parse(<String, dynamic>{
'type': 'ReloadReport',
'success': false,
'details': <String, dynamic>{
'notices': <Map<String, dynamic>>[],
},
})),
false);
expect(
HotRunner.validateReloadReport(
vm_service.ReloadReport.parse(<String, dynamic>{
'type': 'ReloadReport',
'success': false,
'details': <String, dynamic>{
'notices': <Map<String, dynamic>>[
<String, dynamic>{'message': false},
],
},
})),
false);
expect(
HotRunner.validateReloadReport(
vm_service.ReloadReport.parse(<String, dynamic>{
'type': 'ReloadReport',
'success': false,
'details': <String, dynamic>{
'notices': <Map<String, dynamic>>[
<String, dynamic>{
'message': <String>['error']
},
],
},
})),
false);
expect(
HotRunner.validateReloadReport(
vm_service.ReloadReport.parse(<String, dynamic>{
'type': 'ReloadReport',
'success': false,
'details': <String, dynamic>{
'notices': <Map<String, dynamic>>[
<String, dynamic>{'message': 'error'},
<String, dynamic>{
'message': <String>['error']
},
],
},
})),
false);
expect(
HotRunner.validateReloadReport(
vm_service.ReloadReport.parse(<String, dynamic>{
'type': 'ReloadReport',
'success': false,
'details': <String, dynamic>{
'notices': <Map<String, dynamic>>[
<String, dynamic>{'message': 'error'},
],
},
})),
false);
expect(
HotRunner.validateReloadReport(
vm_service.ReloadReport.parse(<String, dynamic>{
'type': 'ReloadReport',
'success': true,
})),
true);
});
testWithoutContext(
'ReasonForCancelling toString has a hint for specific errors', () {
final ReasonForCancelling reasonForCancelling = ReasonForCancelling(
message: 'Const class cannot remove fields',
);
expect(reasonForCancelling.toString(),
contains('Try performing a hot restart instead.'));
});
});
group('hotRestart', () {
final FakeResidentCompiler residentCompiler = FakeResidentCompiler();
late FileSystem fileSystem;
late TestUsage testUsage;
late FakeAnalytics fakeAnalytics;
setUp(() {
fileSystem = MemoryFileSystem.test();
testUsage = TestUsage();
fakeAnalytics = getInitializedFakeAnalyticsInstance(
fs: fileSystem,
fakeFlutterVersion: FakeFlutterVersion(),
);
});
group('fails to setup', () {
late TestHotRunnerConfig failingTestingConfig;
setUp(() {
failingTestingConfig = TestHotRunnerConfig(
successfulHotRestartSetup: false,
successfulHotReloadSetup: false,
);
});
testUsingContext('setupHotRestart function fails', () async {
fileSystem.file('.packages')
..createSync(recursive: true)
..writeAsStringSync('\n');
final FakeDevice device = FakeDevice();
final List<FlutterDevice> devices = <FlutterDevice>[
FlutterDevice(device,
generator: residentCompiler,
buildInfo: BuildInfo.debug,
developmentShaderCompiler: const FakeShaderCompiler())
..devFS = FakeDevFs(),
];
final OperationResult result = await HotRunner(
devices,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
analytics: fakeAnalytics,
).restart(fullRestart: true);
expect(result.isOk, false);
expect(result.message, 'setupHotRestart failed');
expect(failingTestingConfig.updateDevFSCompleteCalled, false);
}, overrides: <Type, Generator>{
HotRunnerConfig: () => failingTestingConfig,
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('setupHotReload function fails', () async {
fileSystem.file('.packages')
..createSync(recursive: true)
..writeAsStringSync('\n');
final FakeDevice device = FakeDevice();
final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device);
final List<FlutterDevice> devices = <FlutterDevice>[
fakeFlutterDevice,
];
final OperationResult result = await HotRunner(
devices,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
reassembleHelper: (
List<FlutterDevice?> flutterDevices,
Map<FlutterDevice?, List<FlutterView>> viewCache,
void Function(String message)? onSlow,
String reloadMessage,
) async =>
ReassembleResult(
<FlutterView?, FlutterVmService?>{null: null},
false,
true,
),
analytics: fakeAnalytics,
).restart();
expect(result.isOk, false);
expect(result.message, 'setupHotReload failed');
expect(failingTestingConfig.updateDevFSCompleteCalled, false);
}, overrides: <Type, Generator>{
HotRunnerConfig: () => failingTestingConfig,
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(),
ProcessManager: () => FakeProcessManager.any(),
});
});
group('shutdown hook tests', () {
late TestHotRunnerConfig shutdownTestingConfig;
setUp(() {
shutdownTestingConfig = TestHotRunnerConfig();
});
testUsingContext('shutdown hook called after signal', () async {
fileSystem.file('.packages')
..createSync(recursive: true)
..writeAsStringSync('\n');
final FakeDevice device = FakeDevice();
final List<FlutterDevice> devices = <FlutterDevice>[
FlutterDevice(device,
generator: residentCompiler,
buildInfo: BuildInfo.debug,
developmentShaderCompiler: const FakeShaderCompiler()),
];
await HotRunner(
devices,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
target: 'main.dart',
analytics: fakeAnalytics,
).cleanupAfterSignal();
expect(shutdownTestingConfig.shutdownHookCalled, true);
}, overrides: <Type, Generator>{
HotRunnerConfig: () => shutdownTestingConfig,
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('shutdown hook called after app stop', () async {
fileSystem.file('.packages')
..createSync(recursive: true)
..writeAsStringSync('\n');
final FakeDevice device = FakeDevice();
final List<FlutterDevice> devices = <FlutterDevice>[
FlutterDevice(device,
generator: residentCompiler,
buildInfo: BuildInfo.debug,
developmentShaderCompiler: const FakeShaderCompiler()),
];
await HotRunner(
devices,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
target: 'main.dart',
analytics: fakeAnalytics,
).preExit();
expect(shutdownTestingConfig.shutdownHookCalled, true);
}, overrides: <Type, Generator>{
HotRunnerConfig: () => shutdownTestingConfig,
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(),
ProcessManager: () => FakeProcessManager.any(),
});
});
group('successful hot restart', () {
late TestHotRunnerConfig testingConfig;
setUp(() {
testingConfig = TestHotRunnerConfig(
successfulHotRestartSetup: true,
);
});
testUsingContext(
'correctly tracks time spent for analytics for hot restart',
() async {
final FakeDevice device = FakeDevice();
final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device);
final List<FlutterDevice> devices = <FlutterDevice>[
fakeFlutterDevice,
];
fakeFlutterDevice.updateDevFSReportCallback =
() async => UpdateFSReport(
success: true,
invalidatedSourcesCount: 2,
syncedBytes: 4,
scannedSourcesCount: 8,
compileDuration: const Duration(seconds: 16),
transferDuration: const Duration(seconds: 32),
);
final FakeStopwatchFactory fakeStopwatchFactory = FakeStopwatchFactory(
stopwatches: <String, Stopwatch>{
'fullRestartHelper': FakeStopwatch()
..elapsed = const Duration(seconds: 64),
'updateDevFS': FakeStopwatch()
..elapsed = const Duration(seconds: 128),
},
);
(fakeFlutterDevice.devFS! as FakeDevFs).baseUri =
Uri.parse('file:///base_uri');
final OperationResult result = await HotRunner(
devices,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
stopwatchFactory: fakeStopwatchFactory,
analytics: fakeAnalytics,
).restart(fullRestart: true);
expect(result.isOk, true);
expect(testUsage.events, <TestUsageEvent>[
const TestUsageEvent('hot', 'restart',
parameters: CustomDimensions(
hotEventTargetPlatform: 'flutter-tester',
hotEventSdkName: 'Tester',
hotEventEmulator: false,
hotEventFullRestart: true,
hotEventOverallTimeInMs: 64000,
hotEventSyncedBytes: 4,
hotEventInvalidatedSourcesCount: 2,
hotEventTransferTimeInMs: 32000,
hotEventCompileTimeInMs: 16000,
hotEventFindInvalidatedTimeInMs: 128000,
hotEventScannedSourcesCount: 8,
)),
]);
expect(
fakeAnalytics.sentEvents,
contains(Event.hotRunnerInfo(
label: 'restart',
targetPlatform: 'flutter-tester',
sdkName: 'Tester',
emulator: false,
fullRestart: true,
syncedBytes: 4,
invalidatedSourcesCount: 2,
transferTimeInMs: 32000,
overallTimeInMs: 64000,
compileTimeInMs: 16000,
findInvalidatedTimeInMs: 128000,
scannedSourcesCount: 8)));
expect(
analyticsTimingEventExists(
sentEvents: fakeAnalytics.sentEvents,
workflow: 'hot',
variableName: 'restart',
),
true,
);
expect(testingConfig.updateDevFSCompleteCalled, true);
}, overrides: <Type, Generator>{
HotRunnerConfig: () => testingConfig,
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(),
ProcessManager: () => FakeProcessManager.any(),
Usage: () => testUsage,
});
});
group('successful hot reload', () {
late TestHotRunnerConfig testingConfig;
setUp(() {
testingConfig = TestHotRunnerConfig(
successfulHotReloadSetup: true,
);
});
testUsingContext(
'correctly tracks time spent for analytics for hot reload', () async {
final FakeDevice device = FakeDevice();
final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device);
final List<FlutterDevice> devices = <FlutterDevice>[
fakeFlutterDevice,
];
fakeFlutterDevice.updateDevFSReportCallback =
() async => UpdateFSReport(
success: true,
invalidatedSourcesCount: 6,
syncedBytes: 8,
scannedSourcesCount: 16,
compileDuration: const Duration(seconds: 16),
transferDuration: const Duration(seconds: 32),
);
final FakeStopwatchFactory fakeStopwatchFactory = FakeStopwatchFactory(
stopwatches: <String, Stopwatch>{
'updateDevFS': FakeStopwatch()
..elapsed = const Duration(seconds: 64),
'reloadSources:reload': FakeStopwatch()
..elapsed = const Duration(seconds: 128),
'reloadSources:reassemble': FakeStopwatch()
..elapsed = const Duration(seconds: 256),
'reloadSources:vm': FakeStopwatch()
..elapsed = const Duration(seconds: 512),
},
);
(fakeFlutterDevice.devFS! as FakeDevFs).baseUri =
Uri.parse('file:///base_uri');
final OperationResult result = await HotRunner(
devices,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
stopwatchFactory: fakeStopwatchFactory,
analytics: fakeAnalytics,
reloadSourcesHelper: (
HotRunner hotRunner,
List<FlutterDevice?> flutterDevices,
bool? pause,
Map<String, dynamic> firstReloadDetails,
String? targetPlatform,
String? sdkName,
bool? emulator,
String? reason,
Usage usage,
Analytics? analytics,
) async {
firstReloadDetails['finalLibraryCount'] = 2;
firstReloadDetails['receivedLibraryCount'] = 3;
firstReloadDetails['receivedClassesCount'] = 4;
firstReloadDetails['receivedProceduresCount'] = 5;
return OperationResult.ok;
},
reassembleHelper: (
List<FlutterDevice?> flutterDevices,
Map<FlutterDevice?, List<FlutterView>> viewCache,
void Function(String message)? onSlow,
String reloadMessage,
) async =>
ReassembleResult(
<FlutterView?, FlutterVmService?>{null: null},
false,
true,
),
).restart();
expect(result.isOk, true);
expect(testUsage.events, <TestUsageEvent>[
const TestUsageEvent('hot', 'reload',
parameters: CustomDimensions(
hotEventFinalLibraryCount: 2,
hotEventSyncedLibraryCount: 3,
hotEventSyncedClassesCount: 4,
hotEventSyncedProceduresCount: 5,
hotEventSyncedBytes: 8,
hotEventInvalidatedSourcesCount: 6,
hotEventTransferTimeInMs: 32000,
hotEventOverallTimeInMs: 128000,
hotEventTargetPlatform: 'flutter-tester',
hotEventSdkName: 'Tester',
hotEventEmulator: false,
hotEventFullRestart: false,
hotEventCompileTimeInMs: 16000,
hotEventFindInvalidatedTimeInMs: 64000,
hotEventScannedSourcesCount: 16,
hotEventReassembleTimeInMs: 256000,
hotEventReloadVMTimeInMs: 512000,
)),
]);
expect(
fakeAnalytics.sentEvents,
contains(
Event.hotRunnerInfo(
label: 'reload',
targetPlatform: 'flutter-tester',
sdkName: 'Tester',
emulator: false,
fullRestart: false,
finalLibraryCount: 2,
syncedLibraryCount: 3,
syncedClassesCount: 4,
syncedProceduresCount: 5,
syncedBytes: 8,
invalidatedSourcesCount: 6,
transferTimeInMs: 32000,
overallTimeInMs: 128000,
compileTimeInMs: 16000,
findInvalidatedTimeInMs: 64000,
scannedSourcesCount: 16,
reassembleTimeInMs: 256000,
reloadVMTimeInMs: 512000),
));
expect(
analyticsTimingEventExists(
sentEvents: fakeAnalytics.sentEvents,
workflow: 'hot',
variableName: 'reload',
),
true,
);
expect(testingConfig.updateDevFSCompleteCalled, true);
}, overrides: <Type, Generator>{
HotRunnerConfig: () => testingConfig,
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(),
ProcessManager: () => FakeProcessManager.any(),
Usage: () => testUsage,
});
});
group('hot restart that failed to sync dev fs', () {
late TestHotRunnerConfig testingConfig;
setUp(() {
testingConfig = TestHotRunnerConfig(
successfulHotRestartSetup: true,
);
});
testUsingContext('still calls the devfs complete callback', () async {
final FakeDevice device = FakeDevice();
final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device);
final List<FlutterDevice> devices = <FlutterDevice>[
fakeFlutterDevice,
];
fakeFlutterDevice.updateDevFSReportCallback =
() async => throw Exception('updateDevFS failed');
final HotRunner runner = HotRunner(
devices,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
analytics: fakeAnalytics,
);
await expectLater(
runner.restart(fullRestart: true),
throwsA(isA<Exception>().having((Exception e) => e.toString(),
'message', 'Exception: updateDevFS failed')));
expect(testingConfig.updateDevFSCompleteCalled, true);
}, overrides: <Type, Generator>{
HotRunnerConfig: () => testingConfig,
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(),
ProcessManager: () => FakeProcessManager.any(),
Usage: () => testUsage,
});
});
group('hot reload that failed to sync dev fs', () {
late TestHotRunnerConfig testingConfig;
setUp(() {
testingConfig = TestHotRunnerConfig(
successfulHotReloadSetup: true,
);
});
testUsingContext('still calls the devfs complete callback', () async {
final FakeDevice device = FakeDevice();
final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device);
final List<FlutterDevice> devices = <FlutterDevice>[
fakeFlutterDevice,
];
fakeFlutterDevice.updateDevFSReportCallback =
() async => throw Exception('updateDevFS failed');
final HotRunner runner = HotRunner(
devices,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
analytics: fakeAnalytics,
);
await expectLater(
runner.restart(),
throwsA(isA<Exception>().having((Exception e) => e.toString(),
'message', 'Exception: updateDevFS failed')));
expect(testingConfig.updateDevFSCompleteCalled, true);
}, overrides: <Type, Generator>{
HotRunnerConfig: () => testingConfig,
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(),
ProcessManager: () => FakeProcessManager.any(),
Usage: () => testUsage,
});
});
});
group('hot attach', () {
late FileSystem fileSystem;
late FakeAnalytics fakeAnalytics;
setUp(() {
fileSystem = MemoryFileSystem.test();
fakeAnalytics = getInitializedFakeAnalyticsInstance(
fs: fileSystem,
fakeFlutterVersion: FakeFlutterVersion(),
);
});
testUsingContext(
'Exits with code 2 when HttpException is thrown '
'during VM service connection', () async {
fileSystem.file('.packages')
..createSync(recursive: true)
..writeAsStringSync('\n');
final FakeResidentCompiler residentCompiler = FakeResidentCompiler();
final FakeDevice device = FakeDevice();
final List<FlutterDevice> devices = <FlutterDevice>[
TestFlutterDevice(
device: device,
generator: residentCompiler,
exception: const HttpException(
'Connection closed before full header was received, '
'uri = http://127.0.0.1:63394/5ZmLv8A59xY=/ws'),
),
];
final int exitCode = await HotRunner(
devices,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
analytics: fakeAnalytics,
).attach(needsFullRestart: false);
expect(exitCode, 2);
}, overrides: <Type, Generator>{
HotRunnerConfig: () => TestHotRunnerConfig(),
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(),
ProcessManager: () => FakeProcessManager.any(),
});
});
group('hot cleanupAtFinish()', () {
late FileSystem fileSystem;
late FakeAnalytics fakeAnalytics;
setUp(() {
fileSystem = MemoryFileSystem.test();
fakeAnalytics = getInitializedFakeAnalyticsInstance(
fs: fileSystem,
fakeFlutterVersion: FakeFlutterVersion(),
);
});
testUsingContext('disposes each device', () async {
final FakeDevice device1 = FakeDevice();
final FakeDevice device2 = FakeDevice();
final FakeFlutterDevice flutterDevice1 = FakeFlutterDevice(device1);
final FakeFlutterDevice flutterDevice2 = FakeFlutterDevice(device2);
final List<FlutterDevice> devices = <FlutterDevice>[
flutterDevice1,
flutterDevice2,
];
await HotRunner(
devices,
debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug),
target: 'main.dart',
analytics: fakeAnalytics,
).cleanupAtFinish();
expect(device1.disposed, true);
expect(device2.disposed, true);
expect(flutterDevice1.stoppedEchoingDeviceLog, true);
expect(flutterDevice2.stoppedEchoingDeviceLog, true);
});
});
group('native assets', () {
late TestHotRunnerConfig testingConfig;
late FileSystem fileSystem;
late FakeAnalytics fakeAnalytics;
setUp(() {
fileSystem = MemoryFileSystem.test();
testingConfig = TestHotRunnerConfig(
successfulHotRestartSetup: true,
);
fakeAnalytics = getInitializedFakeAnalyticsInstance(
fs: fileSystem,
fakeFlutterVersion: FakeFlutterVersion(),
);
});
testUsingContext('native assets restart', () async {
final FakeDevice device = FakeDevice();
final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device);
final List<FlutterDevice> devices = <FlutterDevice>[
fakeFlutterDevice,
];
fakeFlutterDevice.updateDevFSReportCallback = () async => UpdateFSReport(
success: true,
invalidatedSourcesCount: 6,
syncedBytes: 8,
scannedSourcesCount: 16,
compileDuration: const Duration(seconds: 16),
transferDuration: const Duration(seconds: 32),
);
(fakeFlutterDevice.devFS! as FakeDevFs).baseUri =
Uri.parse('file:///base_uri');
final FakeNativeAssetsBuildRunner buildRunner =
FakeNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('bar', fileSystem.currentDirectory.uri),
],
dryRunResult: FakeNativeAssetsBuilderResult(
assets: <Asset>[
Asset(
id: 'package:bar/bar.dart',
linkMode: LinkMode.dynamic,
target: native_assets_cli.Target.macOSArm64,
path: AssetAbsolutePath(Uri.file('bar.dylib')),
),
],
),
);
final HotRunner hotRunner = HotRunner(
devices,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
buildRunner: buildRunner,
analytics: fakeAnalytics,
);
final OperationResult result = await hotRunner.restart(fullRestart: true);
expect(result.isOk, true);
// Hot restart does not require reruning anything for native assets.
// The previous native assets mapping should be used.
expect(buildRunner.buildInvocations, 0);
expect(buildRunner.dryRunInvocations, 0);
expect(buildRunner.hasPackageConfigInvocations, 0);
expect(buildRunner.packagesWithNativeAssetsInvocations, 0);
}, overrides: <Type, Generator>{
HotRunnerConfig: () => testingConfig,
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(),
ProcessManager: () => FakeProcessManager.empty(),
FeatureFlags: () =>
TestFeatureFlags(isNativeAssetsEnabled: true, isMacOSEnabled: true),
});
testUsingContext('native assets run unsupported', () async {
final FakeDevice device =
FakeDevice(targetPlatform: TargetPlatform.android_arm64);
final FakeFlutterDevice fakeFlutterDevice = FakeFlutterDevice(device);
final List<FlutterDevice> devices = <FlutterDevice>[
fakeFlutterDevice,
];
fakeFlutterDevice.updateDevFSReportCallback = () async => UpdateFSReport(
success: true,
invalidatedSourcesCount: 6,
syncedBytes: 8,
scannedSourcesCount: 16,
compileDuration: const Duration(seconds: 16),
transferDuration: const Duration(seconds: 32),
);
(fakeFlutterDevice.devFS! as FakeDevFs).baseUri =
Uri.parse('file:///base_uri');
final FakeNativeAssetsBuildRunner buildRunner =
FakeNativeAssetsBuildRunner(
packagesWithNativeAssetsResult: <Package>[
Package('bar', fileSystem.currentDirectory.uri),
],
dryRunResult: FakeNativeAssetsBuilderResult(
assets: <Asset>[
Asset(
id: 'package:bar/bar.dart',
linkMode: LinkMode.dynamic,
target: native_assets_cli.Target.macOSArm64,
path: AssetAbsolutePath(Uri.file('bar.dylib')),
),
],
),
);
final HotRunner hotRunner = HotRunner(
devices,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
buildRunner: buildRunner,
analytics: fakeAnalytics,
);
expect(
() => hotRunner.run(),
throwsToolExit(
message: 'Package(s) bar require the native assets feature. '
'This feature has not yet been implemented for `TargetPlatform.android_arm64`. '
'For more info see https://github.com/flutter/flutter/issues/129757.',
));
}, overrides: <Type, Generator>{
HotRunnerConfig: () => testingConfig,
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(),
ProcessManager: () => FakeProcessManager.empty(),
FeatureFlags: () =>
TestFeatureFlags(isNativeAssetsEnabled: true, isMacOSEnabled: true),
});
});
}
class FakeDevFs extends Fake implements DevFS {
@override
Future<void> destroy() async {}
@override
List<Uri> sources = <Uri>[];
@override
DateTime? lastCompiled;
@override
PackageConfig? lastPackageConfig;
@override
Set<String> assetPathsToEvict = <String>{};
@override
Set<String> shaderPathsToEvict = <String>{};
@override
Set<String> scenePathsToEvict = <String>{};
@override
Uri? baseUri;
}
// 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({
TargetPlatform targetPlatform = TargetPlatform.tester,
}) : _targetPlatform = targetPlatform;
final TargetPlatform _targetPlatform;
bool disposed = false;
@override
bool isSupported() => true;
@override
bool supportsHotReload = true;
@override
bool supportsHotRestart = true;
@override
bool supportsFlutterExit = true;
@override
Future<TargetPlatform> get targetPlatform async => _targetPlatform;
@override
Future<String> get sdkNameAndVersion async => 'Tester';
@override
Future<bool> get isLocalEmulator async => false;
@override
String get name => 'Fake Device';
@override
Future<bool> stopApp(
ApplicationPackage? app, {
String? userIdentifier,
}) async {
return true;
}
@override
Future<void> dispose() async {
disposed = true;
}
}
class FakeFlutterDevice extends Fake implements FlutterDevice {
FakeFlutterDevice(this.device);
bool stoppedEchoingDeviceLog = false;
late Future<UpdateFSReport> Function() updateDevFSReportCallback;
@override
final FakeDevice device;
@override
Future<void> stopEchoingDeviceLog() async {
stoppedEchoingDeviceLog = true;
}
@override
DevFS? devFS = FakeDevFs();
@override
FlutterVmService get vmService => FakeFlutterVmService();
@override
ResidentCompiler? generator;
@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,
required String dillOutputPath,
required List<Uri> invalidatedFiles,
required PackageConfig packageConfig,
}) =>
updateDevFSReportCallback();
@override
TargetPlatform? get targetPlatform => device._targetPlatform;
}
class TestFlutterDevice extends FlutterDevice {
TestFlutterDevice({
required Device device,
required this.exception,
required ResidentCompiler generator,
}) : super(device,
buildInfo: BuildInfo.debug,
generator: generator,
developmentShaderCompiler: const FakeShaderCompiler());
/// The exception to throw when the connect method is called.
final Exception exception;
@override
Future<void> connect({
ReloadSources? reloadSources,
Restart? restart,
CompileExpression? compileExpression,
GetSkSLMethod? getSkSLMethod,
FlutterProject? flutterProject,
PrintStructuredErrorLogMethod? printStructuredErrorLogMethod,
bool disableServiceAuthCodes = false,
bool enableDds = true,
bool cacheStartupProfile = false,
bool? ipv6 = false,
int? hostVmServicePort,
int? ddsPort,
bool allowExistingDdsInstance = false,
}) async {
throw exception;
}
}
class TestHotRunnerConfig extends HotRunnerConfig {
TestHotRunnerConfig(
{this.successfulHotRestartSetup, this.successfulHotReloadSetup});
bool? successfulHotRestartSetup;
bool? successfulHotReloadSetup;
bool shutdownHookCalled = false;
bool updateDevFSCompleteCalled = false;
@override
Future<bool?> setupHotRestart() async {
assert(successfulHotRestartSetup != null,
'setupHotRestart is not expected to be called in this test.');
return successfulHotRestartSetup;
}
@override
Future<bool?> setupHotReload() async {
assert(successfulHotReloadSetup != null,
'setupHotReload is not expected to be called in this test.');
return successfulHotReloadSetup;
}
@override
void updateDevFSComplete() {
updateDevFSCompleteCalled = true;
}
@override
Future<void> runPreShutdownOperations() async {
shutdownHookCalled = true;
}
}
class FakeResidentCompiler extends Fake implements ResidentCompiler {
@override
void accept() {}
}
class FakeFlutterVmService extends Fake implements FlutterVmService {
@override
vm_service.VmService get service => FakeVmService();
@override
Future<List<FlutterView>> getFlutterViews(
{bool returnEarly = false,
Duration delay = const Duration(milliseconds: 50)}) async {
return <FlutterView>[];
}
}
class FakeVmService extends Fake implements vm_service.VmService {
@override
Future<vm_service.VM> getVM() async => FakeVm();
}
class FakeVm extends Fake implements vm_service.VM {
@override
List<vm_service.IsolateRef> get isolates => <vm_service.IsolateRef>[];
}
class FakeShaderCompiler implements DevelopmentShaderCompiler {
const FakeShaderCompiler();
@override
void configureCompiler(
TargetPlatform? platform, {
required ImpellerStatus impellerStatus,
}) {}
@override
Future<DevFSContent> recompileShader(DevFSContent inputShader) {
throw UnimplementedError();
}
}