auto-submit[bot] 7cdc23b3e1
Reverts "Launch DDS from Dart SDK and prepare to serve DevTools from DDS (#146593)" (#151781)
Reverts: flutter/flutter#146593
Initiated by: zanderso
Reason for reverting: Consistently failing `Windows_android native_assets_android` as in https://ci.chromium.org/ui/p/flutter/builders/prod/Windows_android%20native_assets_android/2533/overview 
Original PR Author: bkonyi

Reviewed By: {christopherfujino, kenzieschmoll}

This change reverts the following previous change:
This change is a major step towards moving away from shipping DDS via Pub.

The first component of this PR is the move away from importing package:dds to launch DDS. Instead, DDS is launched out of process using the `dart development-service` command shipped with the Dart SDK. This makes Flutter's handling of DDS consistent with the standalone Dart VM.

The second component of this PR is the initial work to prepare for the removal of instances of DevTools being served manually by the flutter_tool, instead relying on DDS to serve DevTools. This will be consistent with how the standalone Dart VM serves DevTools, tying the DevTools lifecycle to a live DDS instance. This will allow for the removal of much of the logic needed to properly manage the lifecycle of the DevTools server in a future PR. Also, by serving DevTools from DDS, users will no longer need to forward a secondary port in remote workflows as DevTools will be available on the DDS port.

There's two remaining circumstances that will prevent us from removing DevtoolsRunner completely:

 - The daemon's `devtools.serve` endpoint
 - `flutter drive`'s `--profile-memory` flag used for recording memory profiles

This PR also includes some refactoring around `DebuggingOptions` to reduce the number of debugging related arguments being passed as parameters adjacent to a `DebuggingOptions` instance.
2024-07-15 19:55:18 +00:00

567 lines
19 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 'dart:async';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/dds.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/process.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/drive/drive_service.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/resident_runner.dart';
import 'package:flutter_tools/src/version.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:package_config/package_config_types.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';
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>[],
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.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(),
],
},
);
final FakeVmServiceRequest getVM = FakeVmServiceRequest(
method: 'getVM',
args: <String, Object>{},
jsonResponse: fakeVM.toJson(),
);
void main() {
testWithoutContext('Exits if device fails to start', () {
final DriverService driverService = setUpDriverService();
final Device device = FakeDevice(LaunchResult.failed());
expect(
() => driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile), true),
throwsToolExit(message: 'Application failed to start. Will not run test. Quitting.'),
);
});
testWithoutContext('Retries application launch if it fails the first time', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
getVM,
]);
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['dart', '--enable-experiment=non-nullable', 'foo.test', '-rexpanded'],
exitCode: 23,
environment: <String, String>{
'FOO': 'BAR',
'VM_SERVICE_URL': 'http://127.0.0.1:1234/', // dds forwarded URI
},
),
]);
final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService);
final Device device = FakeDevice(LaunchResult.succeeded(
vmServiceUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'),
))..failOnce = true;
await expectLater(
() async => driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile), true),
returnsNormally,
);
});
testWithoutContext('Connects to device VM Service and runs test application', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
getVM,
]);
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['dart', '--enable-experiment=non-nullable', 'foo.test', '-rexpanded'],
exitCode: 23,
environment: <String, String>{
'FOO': 'BAR',
'VM_SERVICE_URL': 'http://127.0.0.1:1234/', // dds forwarded URI
},
),
]);
final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService);
final Device device = FakeDevice(LaunchResult.succeeded(
vmServiceUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'),
));
await driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile), true);
final int testResult = await driverService.startTest(
'foo.test',
<String>['--enable-experiment=non-nullable'],
<String, String>{'FOO': 'BAR'},
PackageConfig(<Package>[Package('test', Uri.base)]),
);
expect(testResult, 23);
});
testWithoutContext('Connects to device VM Service and runs test application with devtools memory profile', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
getVM,
]);
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['dart', '--enable-experiment=non-nullable', 'foo.test', '-rexpanded'],
exitCode: 23,
environment: <String, String>{
'FOO': 'BAR',
'VM_SERVICE_URL': 'http://127.0.0.1:1234/', // dds forwarded URI
},
),
]);
final FakeDevtoolsLauncher launcher = FakeDevtoolsLauncher();
final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService, devtoolsLauncher: launcher);
final Device device = FakeDevice(LaunchResult.succeeded(
vmServiceUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'),
));
await driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile), true);
final int testResult = await driverService.startTest(
'foo.test',
<String>['--enable-experiment=non-nullable'],
<String, String>{'FOO': 'BAR'},
PackageConfig(<Package>[Package('test', Uri.base)]),
profileMemory: 'devtools_memory.json',
);
expect(launcher.closed, true);
expect(testResult, 23);
});
testWithoutContext('Uses dart to execute the test if there is no package:test dependency', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
getVM,
]);
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['dart', '--enable-experiment=non-nullable', 'foo.test', '-rexpanded'],
exitCode: 23,
environment: <String, String>{
'FOO': 'BAR',
'VM_SERVICE_URL': 'http://127.0.0.1:1234/', // dds forwarded URI
},
),
]);
final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService);
final Device device = FakeDevice(LaunchResult.succeeded(
vmServiceUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'),
));
await driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile), true);
final int testResult = await driverService.startTest(
'foo.test',
<String>['--enable-experiment=non-nullable'],
<String, String>{'FOO': 'BAR'},
PackageConfig.empty,
);
expect(testResult, 23);
});
testWithoutContext('Connects to device VM Service and runs test application without dds', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
getVM,
]);
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
const FakeCommand(
command: <String>['dart', 'foo.test', '-rexpanded'],
exitCode: 11,
environment: <String, String>{
'VM_SERVICE_URL': 'http://127.0.0.1:63426/1UasC_ihpXY=/',
},
),
]);
final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService);
final Device device = FakeDevice(LaunchResult.succeeded(
vmServiceUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'),
));
final FakeDartDevelopmentService dds = device.dds as FakeDartDevelopmentService;
expect(dds.started, false);
await driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile, enableDds: false), true);
expect(dds.started, false);
final int testResult = await driverService.startTest(
'foo.test',
<String>[],
<String, String>{},
PackageConfig(<Package>[Package('test', Uri.base)]),
);
expect(testResult, 11);
expect(dds.started, false);
});
testWithoutContext('Safely stops and uninstalls application', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
getVM,
]);
final FakeProcessManager processManager = FakeProcessManager.empty();
final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService);
final FakeDevice device = FakeDevice(LaunchResult.succeeded(
vmServiceUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'),
));
await driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile), true);
await driverService.stop();
expect(device.didStopApp, true);
expect(device.didUninstallApp, true);
expect(device.didDispose, true);
});
// FlutterVersion requires context.
testUsingContext('Writes SkSL to file when provided with out file', () async {
final MemoryFileSystem fileSystem = MemoryFileSystem.test();
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
getVM,
listViews,
const FakeVmServiceRequest(
method: '_flutter.getSkSLs',
args: <String, Object>{
'viewId': 'a',
},
jsonResponse: <String, Object>{
'SkSLs': <String, Object>{
'A': 'B',
},
},
),
]);
final FakeProcessManager processManager = FakeProcessManager.empty();
final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService);
final FakeDevice device = FakeDevice(LaunchResult.succeeded(
vmServiceUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'),
));
await driverService.start(BuildInfo.profile, device, DebuggingOptions.enabled(BuildInfo.profile), true);
await driverService.stop(writeSkslOnExit: fileSystem.file('out.json'));
expect(device.didStopApp, true);
expect(device.didUninstallApp, true);
expect(json.decode(fileSystem.file('out.json').readAsStringSync()), <String, Object>{
'platform': 'android',
'name': 'test',
'engineRevision': 'abcdefghijklmnopqrstuvwxyz',
'data': <String, Object>{'A': 'B'},
});
}, overrides: <Type, Generator>{
FlutterVersion: () => FakeFlutterVersion(),
});
testWithoutContext('Can connect to existing application and stop it during cleanup', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
getVM,
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.exit',
args: <String, Object>{
'isolateId': '1',
},
),
]);
final FakeProcessManager processManager = FakeProcessManager.empty();
final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService);
final FakeDevice device = FakeDevice(LaunchResult.failed());
await driverService.reuseApplication(
Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'),
device,
DebuggingOptions.enabled(BuildInfo.debug),
false,
);
await driverService.stop();
});
testWithoutContext('Can connect to existing application using ws URI', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
getVM,
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.exit',
args: <String, Object>{
'isolateId': '1',
},
),
]);
final FakeProcessManager processManager = FakeProcessManager.empty();
final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService);
final FakeDevice device = FakeDevice(LaunchResult.failed());
await driverService.reuseApplication(
Uri.parse('ws://127.0.0.1:63426/1UasC_ihpXY=/ws/'),
device,
DebuggingOptions.enabled(BuildInfo.debug),
false,
);
await driverService.stop();
});
testWithoutContext('Can connect to existing application using ws URI (no trailing slash)', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
getVM,
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.exit',
args: <String, Object>{
'isolateId': '1',
},
),
]);
final FakeProcessManager processManager = FakeProcessManager.empty();
final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService);
final FakeDevice device = FakeDevice(LaunchResult.failed());
await driverService.reuseApplication(
Uri.parse('ws://127.0.0.1:63426/1UasC_ihpXY=/ws'),
device,
DebuggingOptions.enabled(BuildInfo.debug),
false,
);
await driverService.stop();
});
testWithoutContext('Can connect to existing application using ws URI (no trailing slash, ws in auth code)', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
getVM,
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.exit',
args: <String, Object>{
'isolateId': '1',
},
),
]);
final FakeProcessManager processManager = FakeProcessManager.empty();
final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService);
final FakeDevice device = FakeDevice(LaunchResult.failed());
await driverService.reuseApplication(
Uri.parse('ws://127.0.0.1:63426/wsasC_ihpXY=/ws'),
device,
DebuggingOptions.enabled(BuildInfo.debug),
false,
);
await driverService.stop();
});
testWithoutContext('Does not call flutterExit on device types that do not support it', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <FakeVmServiceRequest>[
getVM,
]);
final FakeProcessManager processManager = FakeProcessManager.empty();
final DriverService driverService = setUpDriverService(processManager: processManager, vmService: fakeVmServiceHost.vmService);
final FakeDevice device = FakeDevice(LaunchResult.failed(), supportsFlutterExit: false);
await driverService.reuseApplication(
Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/'),
device,
DebuggingOptions.enabled(BuildInfo.debug),
false,
);
await driverService.stop();
});
}
FlutterDriverService setUpDriverService({
Logger? logger,
ProcessManager? processManager,
FlutterVmService? vmService,
DevtoolsLauncher? devtoolsLauncher,
}) {
logger ??= BufferLogger.test();
return FlutterDriverService(
applicationPackageFactory: FakeApplicationPackageFactory(FakeApplicationPackage()),
logger: logger,
processUtils: ProcessUtils(
logger: logger,
processManager: processManager ?? FakeProcessManager.any(),
),
dartSdkPath: 'dart',
devtoolsLauncher: devtoolsLauncher ?? FakeDevtoolsLauncher(),
vmServiceConnector: (Uri httpUri, {
ReloadSources? reloadSources,
Restart? restart,
CompileExpression? compileExpression,
GetSkSLMethod? getSkSLMethod,
FlutterProject? flutterProject,
PrintStructuredErrorLogMethod? printStructuredErrorLogMethod,
io.CompressionOptions compression = io.CompressionOptions.compressionDefault,
Device? device,
required Logger logger,
}) async {
if (httpUri.scheme != 'http') {
fail('Expected an HTTP scheme, found $httpUri');
}
if (httpUri.path.endsWith('/ws')) {
fail('Expected HTTP uri to not contain `/ws`, found $httpUri');
}
return vmService!;
}
);
}
class FakeApplicationPackageFactory extends Fake implements ApplicationPackageFactory {
FakeApplicationPackageFactory(this.applicationPackage);
ApplicationPackage applicationPackage;
@override
Future<ApplicationPackage> getPackageForPlatform(
TargetPlatform platform, {
BuildInfo? buildInfo,
File? applicationBinary,
}) async => applicationPackage;
}
class FakeApplicationPackage extends Fake implements ApplicationPackage { }
class FakeDevice extends Fake implements Device {
FakeDevice(this.result, {this.supportsFlutterExit = true});
LaunchResult result;
bool didStopApp = false;
bool didUninstallApp = false;
bool didDispose = false;
bool failOnce = false;
@override
final PlatformType platformType = PlatformType.web;
@override
String get name => 'test';
@override
final bool supportsFlutterExit;
@override
final DartDevelopmentService dds = FakeDartDevelopmentService();
@override
Future<TargetPlatform> get targetPlatform async => TargetPlatform.android_arm;
@override
Future<DeviceLogReader> getLogReader({
ApplicationPackage? app,
bool includePastLogs = false,
}) async => NoOpDeviceLogReader('test');
@override
Future<LaunchResult> startApp(
ApplicationPackage? package, {
String? mainPath,
String? route,
required DebuggingOptions debuggingOptions,
Map<String, Object?> platformArgs = const <String, Object?>{},
bool prebuiltApplication = false,
bool ipv6 = false,
String? userIdentifier,
}) async {
if (failOnce) {
failOnce = false;
return LaunchResult.failed();
}
return result;
}
@override
Future<bool> stopApp(ApplicationPackage? app, {String? userIdentifier}) async {
didStopApp = true;
return true;
}
@override
Future<bool> uninstallApp(ApplicationPackage app, {String? userIdentifier}) async {
didUninstallApp = true;
return true;
}
@override
Future<void> dispose() async {
didDispose = true;
}
}
class FakeDartDevelopmentService extends Fake implements DartDevelopmentService {
bool started = false;
bool disposed = false;
@override
final Uri uri = Uri.parse('http://127.0.0.1:1234/');
@override
Future<void> startDartDevelopmentService(
Uri vmServiceUri, {
required Logger logger,
int? hostPort,
bool? ipv6,
bool? disableServiceAuthCodes,
bool cacheStartupProfile = false,
}) async {
started = true;
}
@override
Future<void> shutdown() async {
disposed = true;
}
}