mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

This change re-enables DDS and outputs the DDS URI in place of the VM service URI on the console. If --disable-dds is not provided, --host-vmservice-port will be used to determine the port for DDS rather than the host port for the VM service, which will instead be randomly chosen.
826 lines
30 KiB
Dart
826 lines
30 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 'dart:io';
|
|
|
|
import 'package:file/memory.dart';
|
|
import 'package:flutter_tools/src/base/common.dart';
|
|
import 'package:flutter_tools/src/base/dds.dart';
|
|
import 'package:flutter_tools/src/base/file_system.dart';
|
|
import 'package:flutter_tools/src/base/io.dart';
|
|
import 'package:flutter_tools/src/base/logger.dart';
|
|
import 'package:flutter_tools/src/base/terminal.dart';
|
|
import 'package:flutter_tools/src/cache.dart';
|
|
import 'package:flutter_tools/src/commands/attach.dart';
|
|
import 'package:flutter_tools/src/device.dart';
|
|
import 'package:flutter_tools/src/globals.dart' as globals;
|
|
import 'package:flutter_tools/src/ios/devices.dart';
|
|
import 'package:flutter_tools/src/mdns_discovery.dart';
|
|
import 'package:flutter_tools/src/project.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:meta/meta.dart';
|
|
import 'package:mockito/mockito.dart';
|
|
import 'package:process/process.dart';
|
|
import 'package:vm_service/vm_service.dart' as vm_service;
|
|
|
|
import '../../src/common.dart';
|
|
import '../../src/context.dart';
|
|
import '../../src/fakes.dart';
|
|
import '../../src/mocks.dart';
|
|
|
|
final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate(
|
|
id: '1',
|
|
pauseEvent: vm_service.Event(
|
|
kind: vm_service.EventKind.kResume,
|
|
timestamp: 0
|
|
),
|
|
breakpoints: <vm_service.Breakpoint>[],
|
|
exceptionPauseMode: null,
|
|
libraries: <vm_service.LibraryRef>[],
|
|
livePorts: 0,
|
|
name: 'test',
|
|
number: '1',
|
|
pauseOnExit: false,
|
|
runnable: true,
|
|
startTime: 0,
|
|
);
|
|
|
|
void main() {
|
|
group('attach', () {
|
|
StreamLogger logger;
|
|
FileSystem testFileSystem;
|
|
|
|
setUp(() {
|
|
Cache.disableLocking();
|
|
logger = StreamLogger();
|
|
testFileSystem = MemoryFileSystem(
|
|
style: globals.platform.isWindows
|
|
? FileSystemStyle.windows
|
|
: FileSystemStyle.posix,
|
|
);
|
|
testFileSystem.directory('lib').createSync();
|
|
testFileSystem.file(testFileSystem.path.join('lib', 'main.dart')).createSync();
|
|
});
|
|
|
|
group('with one device and no specified target file', () {
|
|
const int devicePort = 499;
|
|
const int hostPort = 42;
|
|
|
|
FakeDeviceLogReader mockLogReader;
|
|
MockPortForwarder portForwarder;
|
|
MockDartDevelopmentService mockDds;
|
|
MockAndroidDevice device;
|
|
MockHttpClient httpClient;
|
|
|
|
setUp(() {
|
|
mockLogReader = FakeDeviceLogReader();
|
|
portForwarder = MockPortForwarder();
|
|
device = MockAndroidDevice();
|
|
mockDds = MockDartDevelopmentService();
|
|
when(device.portForwarder)
|
|
.thenReturn(portForwarder);
|
|
when(portForwarder.forward(devicePort, hostPort: anyNamed('hostPort')))
|
|
.thenAnswer((_) async => hostPort);
|
|
when(portForwarder.forwardedPorts)
|
|
.thenReturn(<ForwardedPort>[ForwardedPort(hostPort, devicePort)]);
|
|
when(portForwarder.unforward(any))
|
|
.thenAnswer((_) async {});
|
|
when(device.dds).thenReturn(mockDds);
|
|
when(mockDds.startDartDevelopmentService(any, any, false, any)).thenReturn(null);
|
|
when(mockDds.uri).thenReturn(Uri.parse('http://localhost:8181'));
|
|
final HttpClientRequest httpClientRequest = MockHttpClientRequest();
|
|
httpClient = MockHttpClient();
|
|
when(httpClient.putUrl(any))
|
|
.thenAnswer((_) => Future<HttpClientRequest>.value(httpClientRequest));
|
|
when(httpClientRequest.headers).thenReturn(MockHttpHeaders());
|
|
when(httpClientRequest.close())
|
|
.thenAnswer((_) => Future<HttpClientResponse>.value(MockHttpClientResponse()));
|
|
|
|
// We cannot add the device to a device manager because that is
|
|
// only enabled by the context of each testUsingContext call.
|
|
//
|
|
// Instead each test will add the device to the device manager
|
|
// on its own.
|
|
});
|
|
|
|
tearDown(() {
|
|
mockLogReader.dispose();
|
|
});
|
|
|
|
testUsingContext('finds observatory port and forwards', () async {
|
|
when(device.getLogReader(includePastLogs: anyNamed('includePastLogs')))
|
|
.thenAnswer((_) {
|
|
// Now that the reader is used, start writing messages to it.
|
|
mockLogReader.addLine('Foo');
|
|
mockLogReader.addLine('Observatory listening on http://127.0.0.1:$devicePort');
|
|
return mockLogReader;
|
|
});
|
|
testDeviceManager.addDevice(device);
|
|
final Completer<void> completer = Completer<void>();
|
|
final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
|
|
if (message == '[verbose] Observatory URL on device: http://127.0.0.1:$devicePort') {
|
|
// The "Observatory URL on device" message is output by the ProtocolDiscovery when it found the observatory.
|
|
completer.complete();
|
|
}
|
|
});
|
|
final Future<void> task = createTestCommandRunner(AttachCommand()).run(<String>['attach']);
|
|
await completer.future;
|
|
verify(
|
|
portForwarder.forward(devicePort, hostPort: anyNamed('hostPort')),
|
|
).called(1);
|
|
await mockLogReader.dispose();
|
|
await expectLoggerInterruptEndsTask(task, logger);
|
|
await loggerSubscription.cancel();
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => testFileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Logger: () => logger,
|
|
});
|
|
|
|
testUsingContext('Fails with tool exit on bad Observatory uri', () async {
|
|
when(device.getLogReader(includePastLogs: anyNamed('includePastLogs')))
|
|
.thenAnswer((_) {
|
|
// Now that the reader is used, start writing messages to it.
|
|
mockLogReader.addLine('Foo');
|
|
mockLogReader.addLine('Observatory listening on http:/:/127.0.0.1:$devicePort');
|
|
mockLogReader.dispose();
|
|
return mockLogReader;
|
|
});
|
|
testDeviceManager.addDevice(device);
|
|
expect(createTestCommandRunner(AttachCommand()).run(<String>['attach']),
|
|
throwsToolExit());
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => testFileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Logger: () => logger,
|
|
});
|
|
|
|
testUsingContext('accepts filesystem parameters', () async {
|
|
when(device.getLogReader(includePastLogs: anyNamed('includePastLogs')))
|
|
.thenAnswer((_) {
|
|
// Now that the reader is used, start writing messages to it.
|
|
mockLogReader.addLine('Foo');
|
|
mockLogReader.addLine('Observatory listening on http://127.0.0.1:$devicePort');
|
|
return mockLogReader;
|
|
});
|
|
testDeviceManager.addDevice(device);
|
|
|
|
const String filesystemScheme = 'foo';
|
|
const String filesystemRoot = '/build-output/';
|
|
const String projectRoot = '/build-output/project-root';
|
|
const String outputDill = '/tmp/output.dill';
|
|
|
|
final MockHotRunner mockHotRunner = MockHotRunner();
|
|
when(mockHotRunner.attach(appStartedCompleter: anyNamed('appStartedCompleter')))
|
|
.thenAnswer((_) async => 0);
|
|
when(mockHotRunner.exited).thenReturn(false);
|
|
when(mockHotRunner.isWaitingForObservatory).thenReturn(false);
|
|
|
|
final MockHotRunnerFactory mockHotRunnerFactory = MockHotRunnerFactory();
|
|
when(
|
|
mockHotRunnerFactory.build(
|
|
any,
|
|
target: anyNamed('target'),
|
|
projectRootPath: anyNamed('projectRootPath'),
|
|
dillOutputPath: anyNamed('dillOutputPath'),
|
|
debuggingOptions: anyNamed('debuggingOptions'),
|
|
packagesFilePath: anyNamed('packagesFilePath'),
|
|
flutterProject: anyNamed('flutterProject'),
|
|
ipv6: false,
|
|
),
|
|
).thenReturn(mockHotRunner);
|
|
|
|
final AttachCommand command = AttachCommand(
|
|
hotRunnerFactory: mockHotRunnerFactory,
|
|
);
|
|
await createTestCommandRunner(command).run(<String>[
|
|
'attach',
|
|
'--filesystem-scheme',
|
|
filesystemScheme,
|
|
'--filesystem-root',
|
|
filesystemRoot,
|
|
'--project-root',
|
|
projectRoot,
|
|
'--output-dill',
|
|
outputDill,
|
|
'-v', // enables verbose logging
|
|
]);
|
|
|
|
// Validate the attach call built a mock runner with the right
|
|
// project root and output dill.
|
|
final VerificationResult verificationResult = verify(
|
|
mockHotRunnerFactory.build(
|
|
captureAny,
|
|
target: anyNamed('target'),
|
|
projectRootPath: projectRoot,
|
|
dillOutputPath: outputDill,
|
|
debuggingOptions: anyNamed('debuggingOptions'),
|
|
packagesFilePath: anyNamed('packagesFilePath'),
|
|
flutterProject: anyNamed('flutterProject'),
|
|
ipv6: false,
|
|
),
|
|
)..called(1);
|
|
|
|
final List<FlutterDevice> flutterDevices = verificationResult.captured.first as List<FlutterDevice>;
|
|
expect(flutterDevices, hasLength(1));
|
|
|
|
// Validate that the attach call built a flutter device with the right
|
|
// output dill, filesystem scheme, and filesystem root.
|
|
final FlutterDevice flutterDevice = flutterDevices.first;
|
|
|
|
expect(flutterDevice.fileSystemScheme, filesystemScheme);
|
|
expect(flutterDevice.fileSystemRoots, const <String>[filesystemRoot]);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => testFileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
});
|
|
|
|
testUsingContext('exits when ipv6 is specified and debug-port is not', () async {
|
|
when(device.getLogReader(includePastLogs: anyNamed('includePastLogs')))
|
|
.thenAnswer((_) {
|
|
// Now that the reader is used, start writing messages to it.
|
|
mockLogReader.addLine('Foo');
|
|
mockLogReader.addLine('Observatory listening on http://127.0.0.1:$devicePort');
|
|
return mockLogReader;
|
|
});
|
|
testDeviceManager.addDevice(device);
|
|
|
|
final AttachCommand command = AttachCommand();
|
|
await expectLater(
|
|
createTestCommandRunner(command).run(<String>['attach', '--ipv6']),
|
|
throwsToolExit(
|
|
message: 'When the --debug-port or --debug-uri is unknown, this command determines '
|
|
'the value of --ipv6 on its own.',
|
|
),
|
|
);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => testFileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
},);
|
|
|
|
testUsingContext('exits when observatory-port is specified and debug-port is not', () async {
|
|
when(device.getLogReader(includePastLogs: anyNamed('includePastLogs')))
|
|
.thenAnswer((_) {
|
|
// Now that the reader is used, start writing messages to it.
|
|
mockLogReader.addLine('Foo');
|
|
mockLogReader.addLine('Observatory listening on http://127.0.0.1:$devicePort');
|
|
return mockLogReader;
|
|
});
|
|
testDeviceManager.addDevice(device);
|
|
|
|
final AttachCommand command = AttachCommand();
|
|
await expectLater(
|
|
createTestCommandRunner(command).run(<String>['attach', '--observatory-port', '100']),
|
|
throwsToolExit(
|
|
message: 'When the --debug-port or --debug-uri is unknown, this command does not use '
|
|
'the value of --observatory-port.',
|
|
),
|
|
);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => testFileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
},);
|
|
});
|
|
|
|
testUsingContext('selects specified target', () async {
|
|
const int devicePort = 499;
|
|
const int hostPort = 42;
|
|
final FakeDeviceLogReader mockLogReader = FakeDeviceLogReader();
|
|
final MockPortForwarder portForwarder = MockPortForwarder();
|
|
final MockDartDevelopmentService mockDds = MockDartDevelopmentService();
|
|
final MockAndroidDevice device = MockAndroidDevice();
|
|
final MockHotRunner mockHotRunner = MockHotRunner();
|
|
final MockHotRunnerFactory mockHotRunnerFactory = MockHotRunnerFactory();
|
|
when(device.portForwarder)
|
|
.thenReturn(portForwarder);
|
|
when(device.dds)
|
|
.thenReturn(mockDds);
|
|
when(portForwarder.forward(devicePort, hostPort: anyNamed('hostPort')))
|
|
.thenAnswer((_) async => hostPort);
|
|
when(portForwarder.forwardedPorts)
|
|
.thenReturn(<ForwardedPort>[ForwardedPort(hostPort, devicePort)]);
|
|
when(portForwarder.unforward(any))
|
|
.thenAnswer((_) async {});
|
|
when(mockHotRunner.attach(appStartedCompleter: anyNamed('appStartedCompleter')))
|
|
.thenAnswer((_) async => 0);
|
|
when(mockHotRunnerFactory.build(
|
|
any,
|
|
target: anyNamed('target'),
|
|
debuggingOptions: anyNamed('debuggingOptions'),
|
|
packagesFilePath: anyNamed('packagesFilePath'),
|
|
flutterProject: anyNamed('flutterProject'),
|
|
ipv6: false,
|
|
)).thenReturn(mockHotRunner);
|
|
when(mockHotRunner.exited).thenReturn(false);
|
|
when(mockHotRunner.isWaitingForObservatory).thenReturn(false);
|
|
when(mockDds.startDartDevelopmentService(any, any, false, any)).thenReturn(null);
|
|
when(mockDds.uri).thenReturn(Uri.parse('http://localhost:8181'));
|
|
|
|
testDeviceManager.addDevice(device);
|
|
when(device.getLogReader(includePastLogs: anyNamed('includePastLogs')))
|
|
.thenAnswer((_) {
|
|
// Now that the reader is used, start writing messages to it.
|
|
mockLogReader.addLine('Foo');
|
|
mockLogReader.addLine(
|
|
'Observatory listening on http://127.0.0.1:$devicePort');
|
|
return mockLogReader;
|
|
});
|
|
final File foo = globals.fs.file('lib/foo.dart')
|
|
..createSync();
|
|
|
|
// Delete the main.dart file to be sure that attach works without it.
|
|
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).deleteSync();
|
|
|
|
final AttachCommand command = AttachCommand(hotRunnerFactory: mockHotRunnerFactory);
|
|
await createTestCommandRunner(command).run(<String>[
|
|
'attach',
|
|
'-t',
|
|
foo.path,
|
|
'-v',
|
|
'--device-user',
|
|
'10',
|
|
]);
|
|
final VerificationResult verificationResult = verify(
|
|
mockHotRunnerFactory.build(
|
|
captureAny,
|
|
target: foo.path,
|
|
debuggingOptions: anyNamed('debuggingOptions'),
|
|
packagesFilePath: anyNamed('packagesFilePath'),
|
|
flutterProject: anyNamed('flutterProject'),
|
|
ipv6: false,
|
|
),
|
|
)..called(1);
|
|
|
|
final List<FlutterDevice> flutterDevices = verificationResult.captured.first as List<FlutterDevice>;
|
|
expect(flutterDevices, hasLength(1));
|
|
final FlutterDevice flutterDevice = flutterDevices.first;
|
|
expect(flutterDevice.userIdentifier, '10');
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => testFileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
});
|
|
|
|
testUsingContext('fallbacks to protocol observatory if MDNS failed on iOS', () async {
|
|
const int devicePort = 499;
|
|
const int hostPort = 42;
|
|
final FakeDeviceLogReader mockLogReader = FakeDeviceLogReader();
|
|
final MockPortForwarder portForwarder = MockPortForwarder();
|
|
final MockDartDevelopmentService mockDds = MockDartDevelopmentService();
|
|
final MockIOSDevice device = MockIOSDevice();
|
|
final MockHotRunner mockHotRunner = MockHotRunner();
|
|
final MockHotRunnerFactory mockHotRunnerFactory = MockHotRunnerFactory();
|
|
when(device.portForwarder)
|
|
.thenReturn(portForwarder);
|
|
when(device.dds)
|
|
.thenReturn(mockDds);
|
|
when(device.getLogReader(includePastLogs: anyNamed('includePastLogs')))
|
|
.thenAnswer((_) => mockLogReader);
|
|
when(portForwarder.forward(devicePort, hostPort: anyNamed('hostPort')))
|
|
.thenAnswer((_) async => hostPort);
|
|
when(portForwarder.forwardedPorts)
|
|
.thenReturn(<ForwardedPort>[ForwardedPort(hostPort, devicePort)]);
|
|
when(portForwarder.unforward(any))
|
|
.thenAnswer((_) async {});
|
|
when(mockHotRunner.attach(appStartedCompleter: anyNamed('appStartedCompleter')))
|
|
.thenAnswer((_) async => 0);
|
|
when(mockHotRunnerFactory.build(
|
|
any,
|
|
target: anyNamed('target'),
|
|
debuggingOptions: anyNamed('debuggingOptions'),
|
|
packagesFilePath: anyNamed('packagesFilePath'),
|
|
flutterProject: anyNamed('flutterProject'),
|
|
ipv6: false,
|
|
)).thenReturn(mockHotRunner);
|
|
when(mockHotRunner.exited).thenReturn(false);
|
|
when(mockHotRunner.isWaitingForObservatory).thenReturn(false);
|
|
when(mockDds.startDartDevelopmentService(any, any, false, any)).thenReturn(null);
|
|
when(mockDds.uri).thenReturn(Uri.parse('http://localhost:8181'));
|
|
|
|
testDeviceManager.addDevice(device);
|
|
|
|
final File foo = globals.fs.file('lib/foo.dart')..createSync();
|
|
|
|
// Delete the main.dart file to be sure that attach works without it.
|
|
globals.fs.file(globals.fs.path.join('lib', 'main.dart')).deleteSync();
|
|
|
|
final AttachCommand command = AttachCommand(hotRunnerFactory: mockHotRunnerFactory);
|
|
await createTestCommandRunner(command).run(<String>['attach', '-t', foo.path, '-v']);
|
|
|
|
verify(mockHotRunnerFactory.build(
|
|
any,
|
|
target: foo.path,
|
|
debuggingOptions: anyNamed('debuggingOptions'),
|
|
packagesFilePath: anyNamed('packagesFilePath'),
|
|
flutterProject: anyNamed('flutterProject'),
|
|
ipv6: false,
|
|
)).called(1);
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => testFileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
}, skip: Platform.isWindows); // mDNS does not work on Windows.
|
|
|
|
group('forwarding to given port', () {
|
|
const int devicePort = 499;
|
|
const int hostPort = 42;
|
|
MockPortForwarder portForwarder;
|
|
MockAndroidDevice device;
|
|
|
|
setUp(() {
|
|
portForwarder = MockPortForwarder();
|
|
final MockDartDevelopmentService mockDds = MockDartDevelopmentService();
|
|
device = MockAndroidDevice();
|
|
|
|
when(device.portForwarder)
|
|
.thenReturn(portForwarder);
|
|
when(portForwarder.forward(devicePort))
|
|
.thenAnswer((_) async => hostPort);
|
|
when(portForwarder.forwardedPorts)
|
|
.thenReturn(<ForwardedPort>[ForwardedPort(hostPort, devicePort)]);
|
|
when(portForwarder.unforward(any))
|
|
.thenAnswer((_) async {});
|
|
when(device.dds)
|
|
.thenReturn(mockDds);
|
|
when(mockDds.startDartDevelopmentService(any, any, any, any))
|
|
.thenReturn(null);
|
|
when(mockDds.uri).thenReturn(Uri.parse('http://localhost:8181'));
|
|
});
|
|
|
|
testUsingContext('succeeds in ipv4 mode', () async {
|
|
testDeviceManager.addDevice(device);
|
|
|
|
final Completer<void> completer = Completer<void>();
|
|
final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
|
|
if (message == '[verbose] Connecting to service protocol: http://127.0.0.1:42/') {
|
|
// Wait until resident_runner.dart tries to connect.
|
|
// There's nothing to connect _to_, so that's as far as we care to go.
|
|
completer.complete();
|
|
}
|
|
});
|
|
final Future<void> task = createTestCommandRunner(AttachCommand())
|
|
.run(<String>['attach', '--debug-port', '$devicePort']);
|
|
await completer.future;
|
|
verify(portForwarder.forward(devicePort)).called(1);
|
|
|
|
await expectLoggerInterruptEndsTask(task, logger);
|
|
await loggerSubscription.cancel();
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => testFileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Logger: () => logger,
|
|
});
|
|
|
|
testUsingContext('succeeds in ipv6 mode', () async {
|
|
testDeviceManager.addDevice(device);
|
|
|
|
final Completer<void> completer = Completer<void>();
|
|
final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
|
|
if (message == '[verbose] Connecting to service protocol: http://[::1]:42/') {
|
|
// Wait until resident_runner.dart tries to connect.
|
|
// There's nothing to connect _to_, so that's as far as we care to go.
|
|
completer.complete();
|
|
}
|
|
});
|
|
final Future<void> task = createTestCommandRunner(AttachCommand())
|
|
.run(<String>['attach', '--debug-port', '$devicePort', '--ipv6']);
|
|
await completer.future;
|
|
verify(portForwarder.forward(devicePort)).called(1);
|
|
|
|
await expectLoggerInterruptEndsTask(task, logger);
|
|
await loggerSubscription.cancel();
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => testFileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Logger: () => logger,
|
|
});
|
|
|
|
testUsingContext('skips in ipv4 mode with a provided observatory port', () async {
|
|
testDeviceManager.addDevice(device);
|
|
|
|
final Completer<void> completer = Completer<void>();
|
|
final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
|
|
if (message == '[verbose] Connecting to service protocol: http://127.0.0.1:42/') {
|
|
// Wait until resident_runner.dart tries to connect.
|
|
// There's nothing to connect _to_, so that's as far as we care to go.
|
|
completer.complete();
|
|
}
|
|
});
|
|
final Future<void> task = createTestCommandRunner(AttachCommand()).run(
|
|
<String>[
|
|
'attach',
|
|
'--debug-port',
|
|
'$devicePort',
|
|
'--observatory-port',
|
|
'$hostPort',
|
|
],
|
|
);
|
|
await completer.future;
|
|
verifyNever(portForwarder.forward(devicePort));
|
|
|
|
await expectLoggerInterruptEndsTask(task, logger);
|
|
await loggerSubscription.cancel();
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => testFileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Logger: () => logger,
|
|
});
|
|
|
|
testUsingContext('skips in ipv6 mode with a provided observatory port', () async {
|
|
testDeviceManager.addDevice(device);
|
|
|
|
final Completer<void> completer = Completer<void>();
|
|
final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
|
|
if (message == '[verbose] Connecting to service protocol: http://[::1]:42/') {
|
|
// Wait until resident_runner.dart tries to connect.
|
|
// There's nothing to connect _to_, so that's as far as we care to go.
|
|
completer.complete();
|
|
}
|
|
});
|
|
final Future<void> task = createTestCommandRunner(AttachCommand()).run(
|
|
<String>[
|
|
'attach',
|
|
'--debug-port',
|
|
'$devicePort',
|
|
'--observatory-port',
|
|
'$hostPort',
|
|
'--ipv6',
|
|
],
|
|
);
|
|
await completer.future;
|
|
verifyNever(portForwarder.forward(devicePort));
|
|
|
|
await expectLoggerInterruptEndsTask(task, logger);
|
|
await loggerSubscription.cancel();
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => testFileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
Logger: () => logger,
|
|
});
|
|
});
|
|
|
|
testUsingContext('exits when no device connected', () async {
|
|
final AttachCommand command = AttachCommand();
|
|
await expectLater(
|
|
createTestCommandRunner(command).run(<String>['attach']),
|
|
throwsToolExit(),
|
|
);
|
|
expect(testLogger.statusText, containsIgnoringWhitespace('No supported devices connected'));
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => testFileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
});
|
|
|
|
testUsingContext('fails when targeted device is not Android with --device-user', () async {
|
|
final MockIOSDevice device = MockIOSDevice();
|
|
testDeviceManager.addDevice(device);
|
|
expect(createTestCommandRunner(AttachCommand()).run(<String>[
|
|
'attach',
|
|
'--device-user',
|
|
'10',
|
|
]), throwsToolExit(message: '--device-user is only supported for Android'));
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => testFileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
});
|
|
|
|
testUsingContext('exits when multiple devices connected', () async {
|
|
Device aDeviceWithId(String id) {
|
|
final MockAndroidDevice device = MockAndroidDevice();
|
|
when(device.name).thenReturn('d$id');
|
|
when(device.id).thenReturn(id);
|
|
when(device.isLocalEmulator).thenAnswer((_) async => false);
|
|
when(device.sdkNameAndVersion).thenAnswer((_) async => 'Android 46');
|
|
return device;
|
|
}
|
|
|
|
final AttachCommand command = AttachCommand();
|
|
testDeviceManager.addDevice(aDeviceWithId('xx1'));
|
|
testDeviceManager.addDevice(aDeviceWithId('yy2'));
|
|
await expectLater(
|
|
createTestCommandRunner(command).run(<String>['attach']),
|
|
throwsToolExit(),
|
|
);
|
|
expect(testLogger.statusText, containsIgnoringWhitespace('More than one device'));
|
|
expect(testLogger.statusText, contains('xx1'));
|
|
expect(testLogger.statusText, contains('yy2'));
|
|
}, overrides: <Type, Generator>{
|
|
FileSystem: () => testFileSystem,
|
|
ProcessManager: () => FakeProcessManager.any(),
|
|
});
|
|
});
|
|
}
|
|
|
|
class MockHotRunner extends Mock implements HotRunner {}
|
|
class MockHotRunnerFactory extends Mock implements HotRunnerFactory {}
|
|
class MockIOSDevice extends Mock implements IOSDevice {}
|
|
class MockMDnsObservatoryDiscovery extends Mock implements MDnsObservatoryDiscovery {}
|
|
class MockPortForwarder extends Mock implements DevicePortForwarder {}
|
|
|
|
class StreamLogger extends Logger {
|
|
@override
|
|
bool get isVerbose => true;
|
|
|
|
@override
|
|
void printError(
|
|
String message, {
|
|
StackTrace stackTrace,
|
|
bool emphasis,
|
|
TerminalColor color,
|
|
int indent,
|
|
int hangingIndent,
|
|
bool wrap,
|
|
}) {
|
|
_log('[stderr] $message');
|
|
}
|
|
|
|
@override
|
|
void printStatus(
|
|
String message, {
|
|
bool emphasis,
|
|
TerminalColor color,
|
|
bool newline,
|
|
int indent,
|
|
int hangingIndent,
|
|
bool wrap,
|
|
}) {
|
|
_log('[stdout] $message');
|
|
}
|
|
|
|
@override
|
|
void printTrace(String message) {
|
|
_log('[verbose] $message');
|
|
}
|
|
|
|
@override
|
|
Status startProgress(
|
|
String message, {
|
|
@required Duration timeout,
|
|
String progressId,
|
|
bool multilineOutput = false,
|
|
int progressIndicatorPadding = kDefaultStatusPadding,
|
|
}) {
|
|
_log('[progress] $message');
|
|
return SilentStatus(
|
|
timeout: timeout,
|
|
timeoutConfiguration: timeoutConfiguration,
|
|
stopwatch: Stopwatch(),
|
|
)..start();
|
|
}
|
|
|
|
bool _interrupt = false;
|
|
|
|
void interrupt() {
|
|
_interrupt = true;
|
|
}
|
|
|
|
final StreamController<String> _controller = StreamController<String>.broadcast();
|
|
|
|
void _log(String message) {
|
|
_controller.add(message);
|
|
if (_interrupt) {
|
|
_interrupt = false;
|
|
throw const LoggerInterrupted();
|
|
}
|
|
}
|
|
|
|
Stream<String> get stream => _controller.stream;
|
|
|
|
@override
|
|
void sendEvent(String name, [Map<String, dynamic> args]) { }
|
|
|
|
@override
|
|
bool get supportsColor => throw UnimplementedError();
|
|
|
|
@override
|
|
bool get hasTerminal => false;
|
|
|
|
@override
|
|
void clear() => _log('[stdout] ${globals.terminal.clearScreen()}\n');
|
|
}
|
|
|
|
class LoggerInterrupted implements Exception {
|
|
const LoggerInterrupted();
|
|
}
|
|
|
|
Future<void> expectLoggerInterruptEndsTask(Future<void> task, StreamLogger logger) async {
|
|
logger.interrupt(); // an exception during the task should cause it to fail...
|
|
try {
|
|
await task;
|
|
expect(false, isTrue); // (shouldn't reach here)
|
|
} on ToolExit catch (error) {
|
|
expect(error.exitCode, 2); // ...with exit code 2.
|
|
}
|
|
}
|
|
|
|
VMServiceConnector getFakeVmServiceFactory({
|
|
@required Completer<void> vmServiceDoneCompleter,
|
|
}) {
|
|
assert(vmServiceDoneCompleter != null);
|
|
|
|
return (
|
|
Uri httpUri, {
|
|
ReloadSources reloadSources,
|
|
Restart restart,
|
|
CompileExpression compileExpression,
|
|
ReloadMethod reloadMethod,
|
|
GetSkSLMethod getSkSLMethod,
|
|
PrintStructuredErrorLogMethod printStructuredErrorLogMethod,
|
|
CompressionOptions compression,
|
|
Device device,
|
|
}) async {
|
|
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
|
|
requests: <VmServiceExpectation>[
|
|
FakeVmServiceRequest(
|
|
method: kListViewsMethod,
|
|
args: null,
|
|
jsonResponse: <String, Object>{
|
|
'views': <Object>[
|
|
<String, Object>{
|
|
'id': '1',
|
|
'isolate': fakeUnpausedIsolate.toJson()
|
|
},
|
|
],
|
|
},
|
|
),
|
|
FakeVmServiceRequest(
|
|
method: 'getVM',
|
|
args: null,
|
|
jsonResponse: vm_service.VM.parse(<String, Object>{})
|
|
.toJson(),
|
|
),
|
|
FakeVmServiceRequest(
|
|
method: '_createDevFS',
|
|
args: <String, Object>{
|
|
'fsName': globals.fs.currentDirectory.absolute.path,
|
|
},
|
|
jsonResponse: <String, Object>{
|
|
'uri': globals.fs.currentDirectory.absolute.path,
|
|
},
|
|
),
|
|
FakeVmServiceRequest(
|
|
method: kListViewsMethod,
|
|
args: null,
|
|
jsonResponse: <String, Object>{
|
|
'views': <Object>[
|
|
<String, Object>{
|
|
'id': '1',
|
|
'isolate': fakeUnpausedIsolate.toJson()
|
|
},
|
|
],
|
|
},
|
|
),
|
|
],
|
|
);
|
|
return fakeVmServiceHost.vmService;
|
|
};
|
|
}
|
|
|
|
class TestHotRunnerFactory extends HotRunnerFactory {
|
|
HotRunner _runner;
|
|
|
|
@override
|
|
HotRunner build(
|
|
List<FlutterDevice> devices, {
|
|
String target,
|
|
DebuggingOptions debuggingOptions,
|
|
bool benchmarkMode = false,
|
|
File applicationBinary,
|
|
bool hostIsIde = false,
|
|
String projectRootPath,
|
|
String packagesFilePath,
|
|
String dillOutputPath,
|
|
bool stayResident = true,
|
|
bool ipv6 = false,
|
|
FlutterProject flutterProject,
|
|
}) {
|
|
_runner ??= HotRunner(
|
|
devices,
|
|
target: target,
|
|
debuggingOptions: debuggingOptions,
|
|
benchmarkMode: benchmarkMode,
|
|
applicationBinary: applicationBinary,
|
|
hostIsIde: hostIsIde,
|
|
projectRootPath: projectRootPath,
|
|
dillOutputPath: dillOutputPath,
|
|
stayResident: stayResident,
|
|
ipv6: ipv6,
|
|
);
|
|
return _runner;
|
|
}
|
|
|
|
Future<void> exitApp() async {
|
|
assert(_runner != null);
|
|
await _runner.exit();
|
|
}
|
|
}
|
|
|
|
class MockDartDevelopmentService extends Mock implements DartDevelopmentService {}
|
|
class MockProcessManager extends Mock implements ProcessManager {}
|
|
class MockProcess extends Mock implements Process {}
|
|
class MockHttpClientRequest extends Mock implements HttpClientRequest {}
|
|
class MockHttpClientResponse extends Mock implements HttpClientResponse {}
|
|
class MockHttpHeaders extends Mock implements HttpHeaders {}
|